multiserialviewer 25.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- multiserialviewer/__init__.py +1 -0
- multiserialviewer/__main__.py +12 -0
- multiserialviewer/__version__.py +13 -0
- multiserialviewer/application/__init__.py +0 -0
- multiserialviewer/application/application.py +189 -0
- multiserialviewer/application/counterHandler.py +40 -0
- multiserialviewer/application/counterTableModel.py +82 -0
- multiserialviewer/application/proxyStyle.py +10 -0
- multiserialviewer/application/serialViewerController.py +70 -0
- multiserialviewer/gui_main/__init__.py +0 -0
- multiserialviewer/gui_main/mainWindow.py +148 -0
- multiserialviewer/gui_main/settingsDialog.py +150 -0
- multiserialviewer/gui_viewer/__init__.py +0 -0
- multiserialviewer/gui_viewer/autoscrollHandler.py +84 -0
- multiserialviewer/gui_viewer/counterWidget.py +58 -0
- multiserialviewer/gui_viewer/searchHandler.py +45 -0
- multiserialviewer/gui_viewer/searchWidget.py +41 -0
- multiserialviewer/gui_viewer/serialViewerCreateDialog.py +44 -0
- multiserialviewer/gui_viewer/serialViewerSettingsWidget.py +145 -0
- multiserialviewer/gui_viewer/serialViewerTextEdit.py +62 -0
- multiserialviewer/gui_viewer/serialViewerWindow.py +134 -0
- multiserialviewer/icons/__init__.py +0 -0
- multiserialviewer/icons/google/434343/add.png +0 -0
- multiserialviewer/icons/google/434343/app.png +0 -0
- multiserialviewer/icons/google/434343/capture_start.png +0 -0
- multiserialviewer/icons/google/434343/capture_stop.png +0 -0
- multiserialviewer/icons/google/434343/clear.png +0 -0
- multiserialviewer/icons/google/434343/counter.png +0 -0
- multiserialviewer/icons/google/434343/folder.png +0 -0
- multiserialviewer/icons/google/434343/highlighter.png +0 -0
- multiserialviewer/icons/google/434343/search.png +0 -0
- multiserialviewer/icons/google/434343/serial_viewer.png +0 -0
- multiserialviewer/icons/google/434343/settings.png +0 -0
- multiserialviewer/icons/google/434343/stats.png +0 -0
- multiserialviewer/icons/google/434343/tile.png +0 -0
- multiserialviewer/icons/google/CCCCCC/README_ICONS.txt +2 -0
- multiserialviewer/icons/google/CCCCCC/add.png +0 -0
- multiserialviewer/icons/google/CCCCCC/app.png +0 -0
- multiserialviewer/icons/google/CCCCCC/capture_start.png +0 -0
- multiserialviewer/icons/google/CCCCCC/capture_stop.png +0 -0
- multiserialviewer/icons/google/CCCCCC/cascade.png +0 -0
- multiserialviewer/icons/google/CCCCCC/clear.png +0 -0
- multiserialviewer/icons/google/CCCCCC/counter.png +0 -0
- multiserialviewer/icons/google/CCCCCC/folder.png +0 -0
- multiserialviewer/icons/google/CCCCCC/highlighter.png +0 -0
- multiserialviewer/icons/google/CCCCCC/search.png +0 -0
- multiserialviewer/icons/google/CCCCCC/serial_viewer.png +0 -0
- multiserialviewer/icons/google/CCCCCC/settings.png +0 -0
- multiserialviewer/icons/google/CCCCCC/stats.png +0 -0
- multiserialviewer/icons/google/CCCCCC/tile.png +0 -0
- multiserialviewer/icons/iconSet.py +45 -0
- multiserialviewer/serial_data/__init__.py +0 -0
- multiserialviewer/serial_data/serialDataProcessor.py +48 -0
- multiserialviewer/serial_data/serialDataReceiver.py +50 -0
- multiserialviewer/serial_data/streamingTextExtractor.py +62 -0
- multiserialviewer/settings/__init__.py +0 -0
- multiserialviewer/settings/counterSettings.py +6 -0
- multiserialviewer/settings/serialConnectionSettings.py +9 -0
- multiserialviewer/settings/serialViewerSettings.py +19 -0
- multiserialviewer/settings/settings.py +284 -0
- multiserialviewer/settings/textHighlighterSettings.py +19 -0
- multiserialviewer/text_highlighter/__init__.py +0 -0
- multiserialviewer/text_highlighter/colorSelectorItemDelegate.py +41 -0
- multiserialviewer/text_highlighter/textHighlighter.py +32 -0
- multiserialviewer/text_highlighter/textHighlighterTableModel.py +124 -0
- multiserialviewer/ui_files/__init__.py +0 -0
- multiserialviewer/ui_files/counterWidget.ui +133 -0
- multiserialviewer/ui_files/mainWindow.ui +24 -0
- multiserialviewer/ui_files/searchWidget.ui +161 -0
- multiserialviewer/ui_files/serialViewerSettingsWidget.ui +252 -0
- multiserialviewer/ui_files/serialViewerWindow.ui +88 -0
- multiserialviewer/ui_files/settingsDialog.ui +189 -0
- multiserialviewer/ui_files/uiFileHelper.py +23 -0
- multiserialviewer-25.1.4.dist-info/METADATA +732 -0
- multiserialviewer-25.1.4.dist-info/RECORD +78 -0
- multiserialviewer-25.1.4.dist-info/WHEEL +4 -0
- multiserialviewer-25.1.4.dist-info/entry_points.txt +2 -0
- multiserialviewer-25.1.4.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .__version__ import __version__
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from multiserialviewer import __version__
|
|
3
|
+
from multiserialviewer.application.application import Application
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main() -> int | str:
|
|
7
|
+
app = Application(__version__, sys.argv)
|
|
8
|
+
return app.exec()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if __name__ == '__main__':
|
|
12
|
+
sys.exit(main())
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Version scheme:
|
|
2
|
+
# YEAR.MONTH.COUNTER
|
|
3
|
+
# YEAR, MONTH are two digits and zero-padded
|
|
4
|
+
# COUNTER resets every month and starts with 0
|
|
5
|
+
# For example:
|
|
6
|
+
# - 23.09.0
|
|
7
|
+
# - 23.09.1
|
|
8
|
+
# - 23.10.0
|
|
9
|
+
#
|
|
10
|
+
# Development versions contain the DEV-COUNTER postfix:
|
|
11
|
+
# - 24.01.0.DEV-1
|
|
12
|
+
|
|
13
|
+
__version__ = '25.1.4'
|
|
File without changes
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from PySide6.QtWidgets import QApplication
|
|
2
|
+
from PySide6.QtCore import Slot, Qt, QUrl
|
|
3
|
+
from PySide6.QtGui import QDesktopServices, QGuiApplication
|
|
4
|
+
from typing import List
|
|
5
|
+
from platformdirs import user_config_dir
|
|
6
|
+
import copy
|
|
7
|
+
|
|
8
|
+
from multiserialviewer.gui_main.mainWindow import MainWindow
|
|
9
|
+
from multiserialviewer.settings.serialConnectionSettings import SerialConnectionSettings
|
|
10
|
+
from multiserialviewer.settings.textHighlighterSettings import TextHighlighterSettings
|
|
11
|
+
from multiserialviewer.settings.serialViewerSettings import SerialViewerSettings, CounterSettings
|
|
12
|
+
from multiserialviewer.application.serialViewerController import SerialViewerController
|
|
13
|
+
from multiserialviewer.application.proxyStyle import ProxyStyle
|
|
14
|
+
from multiserialviewer.icons.iconSet import IconSet
|
|
15
|
+
from multiserialviewer.settings.settings import Settings
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Application(QApplication):
|
|
19
|
+
NAME = 'MultiSerialViewer'
|
|
20
|
+
|
|
21
|
+
def __init__(self, version: str, arguments):
|
|
22
|
+
super().__init__(arguments)
|
|
23
|
+
|
|
24
|
+
self.configDir = user_config_dir(appname=Application.NAME, roaming=False, ensure_exists=True, appauthor=False)
|
|
25
|
+
self.settings: Settings = Settings(self.configDir)
|
|
26
|
+
self.settings.loadSettings()
|
|
27
|
+
|
|
28
|
+
self.captureActive: bool = False
|
|
29
|
+
self.controller = {}
|
|
30
|
+
|
|
31
|
+
colorScheme: Qt.ColorScheme = QGuiApplication.styleHints().colorScheme()
|
|
32
|
+
if colorScheme == Qt.ColorScheme.Dark:
|
|
33
|
+
self.icon_set = IconSet('google', 'CCCCCC')
|
|
34
|
+
else:
|
|
35
|
+
self.icon_set = IconSet('google', '434343')
|
|
36
|
+
|
|
37
|
+
self.mainWindow = MainWindow(f'{Application.NAME} {version}', self.icon_set)
|
|
38
|
+
self.mainWindow.updateCaptureButton(self.captureActive)
|
|
39
|
+
|
|
40
|
+
self.mainWindow.signal_showSerialViewerCreateDialog.connect(self.showSerialViewerCreateDialog)
|
|
41
|
+
self.mainWindow.signal_createSerialViewer.connect(self.createSerialViewer)
|
|
42
|
+
self.mainWindow.signal_clearAll.connect(self.clearAll)
|
|
43
|
+
self.mainWindow.signal_toggleCaptureState.connect(self.toggleCaptureState)
|
|
44
|
+
self.mainWindow.signal_aboutToBeClosed.connect(self.persistCurrentSettings)
|
|
45
|
+
self.mainWindow.signal_aboutToBeClosed.connect(self.stopAllSerialViewer)
|
|
46
|
+
self.mainWindow.signal_editSettings.connect(self.showSettingsDialog)
|
|
47
|
+
self.mainWindow.signal_applySettings.connect(self.applyModifiedSettings)
|
|
48
|
+
self.mainWindow.signal_createTextHighlightEntry.connect(self.createTextHighlightEntry)
|
|
49
|
+
|
|
50
|
+
self.applySettings()
|
|
51
|
+
self.setStyle(ProxyStyle())
|
|
52
|
+
self.mainWindow.show()
|
|
53
|
+
|
|
54
|
+
if len(self.controller) == 0:
|
|
55
|
+
self.showSerialViewerCreateDialog()
|
|
56
|
+
|
|
57
|
+
@Slot(str)
|
|
58
|
+
def createTextHighlightEntry(self, text_to_highlight: str):
|
|
59
|
+
highlighterSettings = TextHighlighterSettings()
|
|
60
|
+
highlighterSettings.pattern = text_to_highlight
|
|
61
|
+
|
|
62
|
+
settings = copy.deepcopy(self.settings)
|
|
63
|
+
settings.textHighlighter.entries.append(highlighterSettings)
|
|
64
|
+
self.mainWindow.showSettingsDialog(settings)
|
|
65
|
+
|
|
66
|
+
@Slot()
|
|
67
|
+
def showSerialViewerCreateDialog(self):
|
|
68
|
+
already_used_ports = list(self.controller.keys())
|
|
69
|
+
self.mainWindow.showSerialViewerCreateDialog(already_used_ports)
|
|
70
|
+
|
|
71
|
+
@Slot()
|
|
72
|
+
def showSettingsDialog(self):
|
|
73
|
+
self.mainWindow.showSettingsDialog(copy.deepcopy(self.settings))
|
|
74
|
+
|
|
75
|
+
@Slot()
|
|
76
|
+
def applyModifiedSettings(self, modifiedSettings: Settings):
|
|
77
|
+
# this could cause some bugs if new values are added to Settings, but not here... -> refactor it
|
|
78
|
+
self.settings.application = modifiedSettings.application
|
|
79
|
+
self.settings.textHighlighter = modifiedSettings.textHighlighter
|
|
80
|
+
for ctrl in self.controller.values():
|
|
81
|
+
ctrl.view.setHighlighterSettings(self.settings.textHighlighter.entries)
|
|
82
|
+
|
|
83
|
+
@Slot(str, SerialConnectionSettings)
|
|
84
|
+
def createSerialViewer(self, settings: SerialViewerSettings):
|
|
85
|
+
if settings.connection.portName in self.controller:
|
|
86
|
+
raise Exception(f"{settings.connection.portName} exists already")
|
|
87
|
+
|
|
88
|
+
view = self.mainWindow.createSerialViewerWindow(settings.title,
|
|
89
|
+
self.settings.textHighlighter.entries,
|
|
90
|
+
size=settings.size,
|
|
91
|
+
position=settings.position,
|
|
92
|
+
splitterState=settings.splitterState,
|
|
93
|
+
currentTabName=settings.currentTabName)
|
|
94
|
+
view.setSerialViewerSettings(settings)
|
|
95
|
+
ctrl = SerialViewerController(settings, view)
|
|
96
|
+
|
|
97
|
+
ctrl.terminated.connect(self.deleteSerialViewer, type=Qt.ConnectionType.QueuedConnection)
|
|
98
|
+
self.controller[settings.connection.portName] = ctrl
|
|
99
|
+
|
|
100
|
+
if self.captureActive:
|
|
101
|
+
if not ctrl.start():
|
|
102
|
+
self.stopAllSerialViewer()
|
|
103
|
+
|
|
104
|
+
@Slot()
|
|
105
|
+
def deleteSerialViewer(self, portName):
|
|
106
|
+
if portName in self.controller:
|
|
107
|
+
del self.controller[portName]
|
|
108
|
+
else:
|
|
109
|
+
raise Exception("Controller to remove does not exist in list")
|
|
110
|
+
|
|
111
|
+
@Slot()
|
|
112
|
+
def clearAll(self):
|
|
113
|
+
for ctrl in self.controller.values():
|
|
114
|
+
ctrl.clearAll()
|
|
115
|
+
|
|
116
|
+
@Slot(bool)
|
|
117
|
+
def toggleCaptureState(self):
|
|
118
|
+
if self.captureActive:
|
|
119
|
+
self.stopAllSerialViewer()
|
|
120
|
+
elif len(self.controller.values()) > 0:
|
|
121
|
+
self.startAllSerialViewer()
|
|
122
|
+
|
|
123
|
+
def startAllSerialViewer(self):
|
|
124
|
+
controller_started_count = 0
|
|
125
|
+
for ctrl in self.controller.values():
|
|
126
|
+
if ctrl.start():
|
|
127
|
+
controller_started_count += 1
|
|
128
|
+
|
|
129
|
+
if controller_started_count == len(self.controller):
|
|
130
|
+
self.captureActive = True
|
|
131
|
+
self.mainWindow.updateCaptureButton(self.captureActive)
|
|
132
|
+
else:
|
|
133
|
+
self.stopAllSerialViewer()
|
|
134
|
+
|
|
135
|
+
@Slot()
|
|
136
|
+
def stopAllSerialViewer(self):
|
|
137
|
+
for ctrl in self.controller.values():
|
|
138
|
+
ctrl.stop()
|
|
139
|
+
self.captureActive = False
|
|
140
|
+
self.mainWindow.updateCaptureButton(self.captureActive)
|
|
141
|
+
|
|
142
|
+
def applySettings(self):
|
|
143
|
+
self.mainWindow.resize(self.settings.mainWindow.size)
|
|
144
|
+
self.mainWindow.addToolBar(self.settings.mainWindow.toolBarArea, self.mainWindow.toolBar)
|
|
145
|
+
|
|
146
|
+
for serialViewerSetting in self.settings.serialViewer.entries:
|
|
147
|
+
self.createSerialViewer(serialViewerSetting)
|
|
148
|
+
# note: highlighter settings do not need to be applied here, because this is
|
|
149
|
+
# done inside function createSerialViewer
|
|
150
|
+
|
|
151
|
+
if self.settings.application.restoreCaptureState:
|
|
152
|
+
if self.settings.application.captureActive:
|
|
153
|
+
self.startAllSerialViewer()
|
|
154
|
+
else:
|
|
155
|
+
self.stopAllSerialViewer()
|
|
156
|
+
|
|
157
|
+
def persistCurrentSettings(self):
|
|
158
|
+
self.settings.application.captureActive = self.captureActive
|
|
159
|
+
|
|
160
|
+
self.settings.mainWindow.size = self.mainWindow.size()
|
|
161
|
+
self.settings.mainWindow.toolBarArea = self.mainWindow.toolBarArea(self.mainWindow.toolBar)
|
|
162
|
+
|
|
163
|
+
self.settings.serialViewer.entries.clear()
|
|
164
|
+
|
|
165
|
+
ctrl: SerialViewerController
|
|
166
|
+
for ctrl in self.controller.values():
|
|
167
|
+
settings = SerialViewerSettings()
|
|
168
|
+
settings.title = ctrl.view.windowTitle()
|
|
169
|
+
settings.size = ctrl.view.size()
|
|
170
|
+
settings.position = ctrl.view.pos()
|
|
171
|
+
settings.splitterState = ctrl.view.splitter.saveState()
|
|
172
|
+
settings.currentTabName = ctrl.view.getCurrentTab()
|
|
173
|
+
settings.showNonPrintableCharsAsHex = ctrl.view.getSettingConvertNonPrintableCharsToHex()
|
|
174
|
+
|
|
175
|
+
settings.autoscrollActive = ctrl.view.autoscroll.autoscrollIsActive()
|
|
176
|
+
settings.autoscrollReactivate = ctrl.view.autoscroll.autoReactivateIsActive()
|
|
177
|
+
|
|
178
|
+
settings.counters = ctrl.counterHandler.getSettings()
|
|
179
|
+
|
|
180
|
+
connection: SerialConnectionSettings = ctrl.receiver.getSettings()
|
|
181
|
+
settings.connection.portName = connection.portName
|
|
182
|
+
settings.connection.baudrate = connection.baudrate
|
|
183
|
+
settings.connection.dataBits = connection.dataBits
|
|
184
|
+
settings.connection.parity = connection.parity
|
|
185
|
+
settings.connection.stopBits = connection.stopBits
|
|
186
|
+
|
|
187
|
+
self.settings.serialViewer.entries.append(settings)
|
|
188
|
+
|
|
189
|
+
self.settings.saveSettings()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from PySide6.QtCore import QObject, Slot
|
|
2
|
+
|
|
3
|
+
from multiserialviewer.settings.counterSettings import CounterSettings
|
|
4
|
+
from multiserialviewer.serial_data.streamingTextExtractor import StreamingTextExtractor
|
|
5
|
+
from multiserialviewer.serial_data.serialDataProcessor import SerialDataProcessor
|
|
6
|
+
from multiserialviewer.application.counterTableModel import CounterTableModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CounterHandler(QObject):
|
|
10
|
+
def __init__(self, settings: list[CounterSettings], dataSource: SerialDataProcessor):
|
|
11
|
+
super().__init__()
|
|
12
|
+
self.dataSource: SerialDataProcessor = dataSource
|
|
13
|
+
self.textExtractors: list[StreamingTextExtractor] = []
|
|
14
|
+
self.counterTableModel: CounterTableModel = CounterTableModel()
|
|
15
|
+
|
|
16
|
+
for counterSettings in settings:
|
|
17
|
+
self.createCounter(counterSettings.pattern)
|
|
18
|
+
|
|
19
|
+
def getSettings(self) -> list[CounterSettings]:
|
|
20
|
+
return self.counterTableModel.settings
|
|
21
|
+
|
|
22
|
+
def clear(self):
|
|
23
|
+
self.counterTableModel.resetCounters()
|
|
24
|
+
|
|
25
|
+
@Slot(str)
|
|
26
|
+
def createCounter(self, pattern: str):
|
|
27
|
+
idx = self.counterTableModel.addCounterEntry(pattern)
|
|
28
|
+
if idx >= 0:
|
|
29
|
+
textExtractor = StreamingTextExtractor(pattern, pattern)
|
|
30
|
+
self.dataSource.signal_asciiDataAvailable.connect(textExtractor.processBytesFromStream)
|
|
31
|
+
textExtractor.signal_textExtracted.connect(self.counterTableModel.incrementCounterValue)
|
|
32
|
+
self.textExtractors.append(textExtractor)
|
|
33
|
+
|
|
34
|
+
@Slot(int)
|
|
35
|
+
def removeCounter(self, index: int):
|
|
36
|
+
if self.counterTableModel.removeCounterEntry(index):
|
|
37
|
+
textExtractor = self.textExtractors[index]
|
|
38
|
+
self.dataSource.signal_asciiDataAvailable.disconnect(textExtractor.processBytesFromStream)
|
|
39
|
+
textExtractor.signal_textExtracted.disconnect(self.counterTableModel.incrementCounterValue)
|
|
40
|
+
del self.textExtractors[index]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from PySide6.QtCore import Qt, QAbstractTableModel, QModelIndex, Slot
|
|
2
|
+
from multiserialviewer.settings.counterSettings import CounterSettings
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CounterTableModel(QAbstractTableModel):
|
|
6
|
+
class CounterEntry:
|
|
7
|
+
def __init__(self, pattern: str, initialValue: int = 0):
|
|
8
|
+
self.pattern: str = pattern
|
|
9
|
+
self.value: int = initialValue
|
|
10
|
+
|
|
11
|
+
def increment(self):
|
|
12
|
+
self.value += 1
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
QAbstractTableModel.__init__(self)
|
|
16
|
+
|
|
17
|
+
self.settings: list[CounterSettings] = []
|
|
18
|
+
self.entries: list[CounterTableModel.CounterEntry] = []
|
|
19
|
+
self.patternToIndex: dict[str, int] = {}
|
|
20
|
+
|
|
21
|
+
@Slot()
|
|
22
|
+
def incrementCounterValue(self, pattern: str, unused: object):
|
|
23
|
+
if pattern in self.patternToIndex:
|
|
24
|
+
rowIndex = self.patternToIndex[pattern]
|
|
25
|
+
self.entries[rowIndex].increment()
|
|
26
|
+
self.dataChanged.emit(self.index(rowIndex, 1), self.index(rowIndex, 1))
|
|
27
|
+
|
|
28
|
+
def rowCount(self, parent=QModelIndex()):
|
|
29
|
+
return len(self.entries)
|
|
30
|
+
|
|
31
|
+
def columnCount(self, parent=QModelIndex()):
|
|
32
|
+
return 2 # pattern and value
|
|
33
|
+
|
|
34
|
+
def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
|
|
35
|
+
if role != Qt.ItemDataRole.DisplayRole:
|
|
36
|
+
return None
|
|
37
|
+
if orientation == Qt.Orientation.Horizontal:
|
|
38
|
+
return ("Text/Pattern", "Counted")[section]
|
|
39
|
+
else: # vertical
|
|
40
|
+
return f"{section}"
|
|
41
|
+
|
|
42
|
+
def data(self, index: QModelIndex, role=Qt.ItemDataRole.DisplayRole):
|
|
43
|
+
column = index.column()
|
|
44
|
+
row = index.row()
|
|
45
|
+
|
|
46
|
+
if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
|
|
47
|
+
if column == 0:
|
|
48
|
+
return self.entries[row].pattern
|
|
49
|
+
elif column == 1:
|
|
50
|
+
return str(self.entries[row].value)
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def resetCounters(self):
|
|
54
|
+
for idx, entry in enumerate(self.entries):
|
|
55
|
+
entry.value = 0
|
|
56
|
+
self.dataChanged.emit(self.index(idx, 1), self.index(idx, 1))
|
|
57
|
+
|
|
58
|
+
def addCounterEntry(self, pattern) -> int:
|
|
59
|
+
rowIdx = -1
|
|
60
|
+
if pattern not in self.patternToIndex.keys():
|
|
61
|
+
rowIdx = len(self.settings)
|
|
62
|
+
self.beginInsertRows(QModelIndex(), rowIdx, rowIdx)
|
|
63
|
+
self.patternToIndex[pattern] = rowIdx
|
|
64
|
+
self.settings.append(CounterSettings(pattern))
|
|
65
|
+
self.entries.append(CounterTableModel.CounterEntry(pattern))
|
|
66
|
+
self.endInsertRows()
|
|
67
|
+
|
|
68
|
+
assert len(self.entries) == len(self.settings)
|
|
69
|
+
return rowIdx
|
|
70
|
+
|
|
71
|
+
def removeCounterEntry(self, index: int) -> bool:
|
|
72
|
+
assert len(self.entries) == len(self.settings)
|
|
73
|
+
|
|
74
|
+
if index < len(self.settings):
|
|
75
|
+
self.beginRemoveRows(QModelIndex(), index, index)
|
|
76
|
+
del self.settings[index]
|
|
77
|
+
del self.patternToIndex[self.entries[index].pattern]
|
|
78
|
+
del self.entries[index]
|
|
79
|
+
self.endRemoveRows()
|
|
80
|
+
return True
|
|
81
|
+
else:
|
|
82
|
+
return False
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from PySide6.QtWidgets import QProxyStyle, QStyle
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ProxyStyle(QProxyStyle):
|
|
5
|
+
def subElementRect(self, element, opt, widget=None):
|
|
6
|
+
if element == QStyle.SE_ItemViewItemCheckIndicator and not opt.text:
|
|
7
|
+
rect = super().subElementRect(element, opt, widget)
|
|
8
|
+
rect.moveCenter(opt.rect.center())
|
|
9
|
+
return rect
|
|
10
|
+
return super().subElementRect(element, opt, widget)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from PySide6.QtCore import QObject, Slot, Signal
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from multiserialviewer.settings.serialViewerSettings import SerialViewerSettings
|
|
5
|
+
from multiserialviewer.gui_viewer.serialViewerWindow import SerialViewerWindow
|
|
6
|
+
from multiserialviewer.serial_data.serialDataReceiver import SerialDataReceiver
|
|
7
|
+
from multiserialviewer.serial_data.serialDataProcessor import SerialDataProcessor
|
|
8
|
+
from multiserialviewer.application.counterHandler import CounterHandler
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SerialViewerController(QObject):
|
|
12
|
+
terminated = Signal(str)
|
|
13
|
+
|
|
14
|
+
def __init__(self, settings: SerialViewerSettings, view: SerialViewerWindow):
|
|
15
|
+
super().__init__()
|
|
16
|
+
|
|
17
|
+
self.receiver: SerialDataReceiver = SerialDataReceiver(settings.connection)
|
|
18
|
+
self.processor: SerialDataProcessor = SerialDataProcessor()
|
|
19
|
+
self.counterHandler: CounterHandler = CounterHandler(settings.counters, self.processor)
|
|
20
|
+
|
|
21
|
+
self.processor.setConvertNonPrintableCharsToHex(settings.showNonPrintableCharsAsHex)
|
|
22
|
+
|
|
23
|
+
self.view: SerialViewerWindow = view
|
|
24
|
+
self.view.counterWidget.setCounterTableModel(self.counterHandler.counterTableModel)
|
|
25
|
+
self.view.textEdit.signal_createCounter.connect(self.counterHandler.createCounter)
|
|
26
|
+
self.view.textEdit.signal_createCounter.connect(self.view.selectTab_Count)
|
|
27
|
+
self.view.counterWidget.signal_createCounter.connect(self.counterHandler.createCounter)
|
|
28
|
+
self.view.counterWidget.signal_removeCounter.connect(self.counterHandler.removeCounter)
|
|
29
|
+
self.view.signal_settingConvertNonPrintableCharsToHexChanged.connect(self.processor.setConvertNonPrintableCharsToHex)
|
|
30
|
+
|
|
31
|
+
self.processor.signal_asciiDataAvailable.connect(self.view.appendData)
|
|
32
|
+
self.receiver.signal_rawDataAvailable.connect(self.processor.handleRawData)
|
|
33
|
+
self.view.signal_closed.connect(self.terminate)
|
|
34
|
+
|
|
35
|
+
def start(self) -> bool:
|
|
36
|
+
if self.receiver.openPort():
|
|
37
|
+
self.processor.start()
|
|
38
|
+
self.receiver.start()
|
|
39
|
+
|
|
40
|
+
self.show_message(f'Opened {self.receiver.getSettings().portName}')
|
|
41
|
+
|
|
42
|
+
return True
|
|
43
|
+
else:
|
|
44
|
+
self.show_error(f'Failed to open {self.receiver.getSettings().portName}')
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
def stop(self):
|
|
48
|
+
if self.receiver.isReceiving():
|
|
49
|
+
self.receiver.stop()
|
|
50
|
+
self.receiver.closePort()
|
|
51
|
+
self.processor.stop()
|
|
52
|
+
self.show_message(f'Closed {self.receiver.getSettings().portName}')
|
|
53
|
+
|
|
54
|
+
def clearAll(self):
|
|
55
|
+
self.view.clear()
|
|
56
|
+
self.counterHandler.clear()
|
|
57
|
+
|
|
58
|
+
def show_message(self, text):
|
|
59
|
+
self.view.appendData(f'\n[MSG: {datetime.now().strftime("%Y/%b/%d %H:%M:%S")}: {text} :MSG]\n')
|
|
60
|
+
|
|
61
|
+
def show_error(self, text):
|
|
62
|
+
self.view.appendData(f'\n[ERR: {datetime.now().strftime("%Y/%b/%d %H:%M:%S")}: {text} :ERR]\n')
|
|
63
|
+
|
|
64
|
+
@Slot()
|
|
65
|
+
def terminate(self):
|
|
66
|
+
# view is already closed
|
|
67
|
+
self.receiver.stop()
|
|
68
|
+
self.receiver.closePort()
|
|
69
|
+
self.processor.stop()
|
|
70
|
+
self.terminated.emit(self.receiver.getSettings().portName)
|
|
File without changes
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from PySide6.QtWidgets import QMdiArea, QMainWindow, QToolBar, QMenu, QWidget, QSizePolicy, QScrollArea
|
|
2
|
+
from PySide6.QtCore import Qt, QSize, QPoint, Signal, Slot, QByteArray
|
|
3
|
+
from PySide6.QtGui import QAction
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
from multiserialviewer.ui_files.uiFileHelper import createWidgetFromUiFile
|
|
7
|
+
from multiserialviewer.gui_viewer.serialViewerWindow import SerialViewerWindow
|
|
8
|
+
from multiserialviewer.gui_viewer.serialViewerCreateDialog import SerialViewerCreateDialog
|
|
9
|
+
from multiserialviewer.gui_main.settingsDialog import SettingsDialog
|
|
10
|
+
from multiserialviewer.settings.settings import Settings
|
|
11
|
+
from multiserialviewer.settings.textHighlighterSettings import TextHighlighterSettings
|
|
12
|
+
from multiserialviewer.settings.serialViewerSettings import SerialViewerSettings
|
|
13
|
+
from multiserialviewer.icons.iconSet import IconSet
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MainWindow(QMainWindow):
|
|
17
|
+
signal_showSerialViewerCreateDialog: Signal = Signal()
|
|
18
|
+
signal_createSerialViewer: Signal = Signal(SerialViewerSettings)
|
|
19
|
+
signal_clearAll: Signal = Signal()
|
|
20
|
+
signal_toggleCaptureState: Signal = Signal()
|
|
21
|
+
signal_aboutToBeClosed: Signal = Signal()
|
|
22
|
+
signal_editSettings: Signal = Signal()
|
|
23
|
+
signal_applySettings: Signal = Signal(object)
|
|
24
|
+
signal_createTextHighlightEntry: Signal = Signal(str)
|
|
25
|
+
signal_openSettingsDirectory: Signal = Signal()
|
|
26
|
+
|
|
27
|
+
def __init__(self, title: str, iconSet: IconSet):
|
|
28
|
+
super(MainWindow, self).__init__()
|
|
29
|
+
self.iconSet = iconSet
|
|
30
|
+
self.actions = self.__createActions()
|
|
31
|
+
self.__connectActions()
|
|
32
|
+
self.toolBar: QToolBar = self.__createToolBar()
|
|
33
|
+
|
|
34
|
+
widget = createWidgetFromUiFile("mainWindow.ui")
|
|
35
|
+
|
|
36
|
+
self.setWindowTitle(title)
|
|
37
|
+
self.setWindowIcon(iconSet.getAppIcon())
|
|
38
|
+
self.setCentralWidget(widget)
|
|
39
|
+
self.mdiArea: QMdiArea = widget.findChild(QMdiArea, 'mdiArea')
|
|
40
|
+
self.updateCaptureButton(False)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def __createActions(self):
|
|
44
|
+
actions = {}
|
|
45
|
+
|
|
46
|
+
action: QAction = QAction(icon=self.iconSet.getSettingsIcon(), text="Settings", parent=self)
|
|
47
|
+
action.setToolTip("Edit settings")
|
|
48
|
+
actions['openSettingsDialog'] = action
|
|
49
|
+
|
|
50
|
+
action: QAction = QAction(icon=self.iconSet.getAddIcon(), text="Create Viewer", parent=self)
|
|
51
|
+
action.setToolTip("Create additional SerialViewer window")
|
|
52
|
+
actions['createSerialViewer'] = action
|
|
53
|
+
|
|
54
|
+
action: QAction = QAction(icon=self.iconSet.getClearContentIcon(), text="Clear Content", parent=self)
|
|
55
|
+
action.setToolTip("Remove received serial data")
|
|
56
|
+
actions['clearContent'] = action
|
|
57
|
+
|
|
58
|
+
action: QAction = QAction(icon=self.iconSet.getCaptureStartIcon(), text="Start Capture", parent=self)
|
|
59
|
+
action.setToolTip("Start/stop receiving serial data")
|
|
60
|
+
actions['capture'] = action
|
|
61
|
+
|
|
62
|
+
action: QAction = QAction(icon=self.iconSet.getTileIcon(), text="Arrange", parent=self)
|
|
63
|
+
action.setToolTip("Automatically arrange all SerialViewer windows side by side")
|
|
64
|
+
actions['arrangeSerialViewerWindows'] = action
|
|
65
|
+
|
|
66
|
+
return actions
|
|
67
|
+
|
|
68
|
+
def __connectActions(self):
|
|
69
|
+
self.actions['openSettingsDialog'].triggered.connect(self.signal_editSettings)
|
|
70
|
+
self.actions['createSerialViewer'].triggered.connect(self.signal_showSerialViewerCreateDialog)
|
|
71
|
+
self.actions['clearContent'].triggered.connect(self.signal_clearAll)
|
|
72
|
+
self.actions['capture'].triggered.connect(self.signal_toggleCaptureState)
|
|
73
|
+
self.actions['arrangeSerialViewerWindows'].triggered.connect(self.arrangeWindowsInTile)
|
|
74
|
+
|
|
75
|
+
def __createToolBar(self) -> QToolBar:
|
|
76
|
+
toolBar: QToolBar = QToolBar(self)
|
|
77
|
+
toolBar.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextUnderIcon)
|
|
78
|
+
toolBar.setIconSize(QSize(32, 32))
|
|
79
|
+
|
|
80
|
+
toolBar.addAction(self.actions['createSerialViewer'])
|
|
81
|
+
toolBar.addAction(self.actions['capture'])
|
|
82
|
+
toolBar.addAction(self.actions['clearContent'])
|
|
83
|
+
|
|
84
|
+
# expanding space
|
|
85
|
+
space = QWidget()
|
|
86
|
+
space.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
87
|
+
toolBar.addWidget(space)
|
|
88
|
+
|
|
89
|
+
toolBar.addSeparator()
|
|
90
|
+
toolBar.addAction(self.actions['arrangeSerialViewerWindows'])
|
|
91
|
+
toolBar.addSeparator()
|
|
92
|
+
|
|
93
|
+
toolBar.addAction(self.actions['openSettingsDialog'])
|
|
94
|
+
return toolBar
|
|
95
|
+
|
|
96
|
+
def showSerialViewerCreateDialog(self, disabled_ports: list):
|
|
97
|
+
dialog = SerialViewerCreateDialog(self)
|
|
98
|
+
dialog.disablePorts(disabled_ports)
|
|
99
|
+
if dialog.exec():
|
|
100
|
+
self.signal_createSerialViewer.emit(dialog.getSerialViewerSettings())
|
|
101
|
+
|
|
102
|
+
def createSerialViewerWindow(self,
|
|
103
|
+
view_title: str,
|
|
104
|
+
highlighterSettings: List[TextHighlighterSettings],
|
|
105
|
+
size: QSize = None,
|
|
106
|
+
position: QPoint = None,
|
|
107
|
+
splitterState: QByteArray = None,
|
|
108
|
+
currentTabName: str = None):
|
|
109
|
+
view = SerialViewerWindow(view_title, self.iconSet)
|
|
110
|
+
view.setHighlighterSettings(highlighterSettings)
|
|
111
|
+
if size:
|
|
112
|
+
view.resize(size)
|
|
113
|
+
if position:
|
|
114
|
+
view.move(position)
|
|
115
|
+
if splitterState:
|
|
116
|
+
view.splitter.restoreState(splitterState)
|
|
117
|
+
if currentTabName:
|
|
118
|
+
view.setCurrentTab(currentTabName)
|
|
119
|
+
|
|
120
|
+
self.mdiArea.addSubWindow(view)
|
|
121
|
+
view.signal_createTextHighlightEntry.connect(self.signal_createTextHighlightEntry)
|
|
122
|
+
view.show()
|
|
123
|
+
return view
|
|
124
|
+
|
|
125
|
+
def showSettingsDialog(self, settings: Settings):
|
|
126
|
+
dialog = SettingsDialog(self, settings, self.iconSet)
|
|
127
|
+
if dialog.exec():
|
|
128
|
+
self.signal_applySettings.emit(dialog.settings)
|
|
129
|
+
|
|
130
|
+
def updateCaptureButton(self, captureState):
|
|
131
|
+
if captureState:
|
|
132
|
+
self.actions['capture'].setIcon(self.iconSet.getCaptureStopIcon())
|
|
133
|
+
self.actions['capture'].setText('Stop Capture')
|
|
134
|
+
else:
|
|
135
|
+
self.actions['capture'].setIcon(self.iconSet.getCaptureStartIcon())
|
|
136
|
+
self.actions['capture'].setText('Start Capture')
|
|
137
|
+
|
|
138
|
+
def closeEvent(self, event):
|
|
139
|
+
self.signal_aboutToBeClosed.emit()
|
|
140
|
+
event.accept()
|
|
141
|
+
|
|
142
|
+
@Slot()
|
|
143
|
+
def arrangeWindowsInCascade(self):
|
|
144
|
+
self.mdiArea.cascadeSubWindows()
|
|
145
|
+
|
|
146
|
+
@Slot()
|
|
147
|
+
def arrangeWindowsInTile(self):
|
|
148
|
+
self.mdiArea.tileSubWindows()
|