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.
Files changed (78) hide show
  1. multiserialviewer/__init__.py +1 -0
  2. multiserialviewer/__main__.py +12 -0
  3. multiserialviewer/__version__.py +13 -0
  4. multiserialviewer/application/__init__.py +0 -0
  5. multiserialviewer/application/application.py +189 -0
  6. multiserialviewer/application/counterHandler.py +40 -0
  7. multiserialviewer/application/counterTableModel.py +82 -0
  8. multiserialviewer/application/proxyStyle.py +10 -0
  9. multiserialviewer/application/serialViewerController.py +70 -0
  10. multiserialviewer/gui_main/__init__.py +0 -0
  11. multiserialviewer/gui_main/mainWindow.py +148 -0
  12. multiserialviewer/gui_main/settingsDialog.py +150 -0
  13. multiserialviewer/gui_viewer/__init__.py +0 -0
  14. multiserialviewer/gui_viewer/autoscrollHandler.py +84 -0
  15. multiserialviewer/gui_viewer/counterWidget.py +58 -0
  16. multiserialviewer/gui_viewer/searchHandler.py +45 -0
  17. multiserialviewer/gui_viewer/searchWidget.py +41 -0
  18. multiserialviewer/gui_viewer/serialViewerCreateDialog.py +44 -0
  19. multiserialviewer/gui_viewer/serialViewerSettingsWidget.py +145 -0
  20. multiserialviewer/gui_viewer/serialViewerTextEdit.py +62 -0
  21. multiserialviewer/gui_viewer/serialViewerWindow.py +134 -0
  22. multiserialviewer/icons/__init__.py +0 -0
  23. multiserialviewer/icons/google/434343/add.png +0 -0
  24. multiserialviewer/icons/google/434343/app.png +0 -0
  25. multiserialviewer/icons/google/434343/capture_start.png +0 -0
  26. multiserialviewer/icons/google/434343/capture_stop.png +0 -0
  27. multiserialviewer/icons/google/434343/clear.png +0 -0
  28. multiserialviewer/icons/google/434343/counter.png +0 -0
  29. multiserialviewer/icons/google/434343/folder.png +0 -0
  30. multiserialviewer/icons/google/434343/highlighter.png +0 -0
  31. multiserialviewer/icons/google/434343/search.png +0 -0
  32. multiserialviewer/icons/google/434343/serial_viewer.png +0 -0
  33. multiserialviewer/icons/google/434343/settings.png +0 -0
  34. multiserialviewer/icons/google/434343/stats.png +0 -0
  35. multiserialviewer/icons/google/434343/tile.png +0 -0
  36. multiserialviewer/icons/google/CCCCCC/README_ICONS.txt +2 -0
  37. multiserialviewer/icons/google/CCCCCC/add.png +0 -0
  38. multiserialviewer/icons/google/CCCCCC/app.png +0 -0
  39. multiserialviewer/icons/google/CCCCCC/capture_start.png +0 -0
  40. multiserialviewer/icons/google/CCCCCC/capture_stop.png +0 -0
  41. multiserialviewer/icons/google/CCCCCC/cascade.png +0 -0
  42. multiserialviewer/icons/google/CCCCCC/clear.png +0 -0
  43. multiserialviewer/icons/google/CCCCCC/counter.png +0 -0
  44. multiserialviewer/icons/google/CCCCCC/folder.png +0 -0
  45. multiserialviewer/icons/google/CCCCCC/highlighter.png +0 -0
  46. multiserialviewer/icons/google/CCCCCC/search.png +0 -0
  47. multiserialviewer/icons/google/CCCCCC/serial_viewer.png +0 -0
  48. multiserialviewer/icons/google/CCCCCC/settings.png +0 -0
  49. multiserialviewer/icons/google/CCCCCC/stats.png +0 -0
  50. multiserialviewer/icons/google/CCCCCC/tile.png +0 -0
  51. multiserialviewer/icons/iconSet.py +45 -0
  52. multiserialviewer/serial_data/__init__.py +0 -0
  53. multiserialviewer/serial_data/serialDataProcessor.py +48 -0
  54. multiserialviewer/serial_data/serialDataReceiver.py +50 -0
  55. multiserialviewer/serial_data/streamingTextExtractor.py +62 -0
  56. multiserialviewer/settings/__init__.py +0 -0
  57. multiserialviewer/settings/counterSettings.py +6 -0
  58. multiserialviewer/settings/serialConnectionSettings.py +9 -0
  59. multiserialviewer/settings/serialViewerSettings.py +19 -0
  60. multiserialviewer/settings/settings.py +284 -0
  61. multiserialviewer/settings/textHighlighterSettings.py +19 -0
  62. multiserialviewer/text_highlighter/__init__.py +0 -0
  63. multiserialviewer/text_highlighter/colorSelectorItemDelegate.py +41 -0
  64. multiserialviewer/text_highlighter/textHighlighter.py +32 -0
  65. multiserialviewer/text_highlighter/textHighlighterTableModel.py +124 -0
  66. multiserialviewer/ui_files/__init__.py +0 -0
  67. multiserialviewer/ui_files/counterWidget.ui +133 -0
  68. multiserialviewer/ui_files/mainWindow.ui +24 -0
  69. multiserialviewer/ui_files/searchWidget.ui +161 -0
  70. multiserialviewer/ui_files/serialViewerSettingsWidget.ui +252 -0
  71. multiserialviewer/ui_files/serialViewerWindow.ui +88 -0
  72. multiserialviewer/ui_files/settingsDialog.ui +189 -0
  73. multiserialviewer/ui_files/uiFileHelper.py +23 -0
  74. multiserialviewer-25.1.4.dist-info/METADATA +732 -0
  75. multiserialviewer-25.1.4.dist-info/RECORD +78 -0
  76. multiserialviewer-25.1.4.dist-info/WHEEL +4 -0
  77. multiserialviewer-25.1.4.dist-info/entry_points.txt +2 -0
  78. 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()