12 import re |
12 import re |
13 |
13 |
14 from PyQt5.QtCore import ( |
14 from PyQt5.QtCore import ( |
15 pyqtSlot, pyqtSignal, Qt, QPoint, QEvent, QIODevice, QTimer |
15 pyqtSlot, pyqtSignal, Qt, QPoint, QEvent, QIODevice, QTimer |
16 ) |
16 ) |
17 from PyQt5.QtGui import QColor, QKeySequence, QTextCursor |
17 from PyQt5.QtGui import QColor, QKeySequence, QTextCursor, QBrush |
18 from PyQt5.QtWidgets import ( |
18 from PyQt5.QtWidgets import ( |
19 QWidget, QMenu, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy) |
19 QWidget, QMenu, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy) |
20 try: |
20 try: |
21 from PyQt5.QtSerialPort import QSerialPort |
21 from PyQt5.QtSerialPort import QSerialPort |
22 HAS_QTSERIALPORT = True |
22 HAS_QTSERIALPORT = True |
52 |
52 |
53 DeviceTypeRole = Qt.UserRole |
53 DeviceTypeRole = Qt.UserRole |
54 DevicePortRole = Qt.UserRole + 1 |
54 DevicePortRole = Qt.UserRole + 1 |
55 |
55 |
56 dataReceived = pyqtSignal(bytes) |
56 dataReceived = pyqtSignal(bytes) |
|
57 |
|
58 # ANSI Colors |
|
59 AnsiColors = { |
|
60 (1, 30): QBrush(Qt.darkGray), |
|
61 (1, 31): QBrush(Qt.red), |
|
62 (1, 32): QBrush(Qt.green), |
|
63 (1, 33): QBrush(Qt.yellow), |
|
64 (1, 34): QBrush(Qt.blue), |
|
65 (1, 35): QBrush(Qt.magenta), |
|
66 (1, 36): QBrush(Qt.cyan), |
|
67 (1, 37): QBrush(Qt.white), |
|
68 (2, 30): QBrush(Qt.black), |
|
69 (2, 31): QBrush(Qt.darkRed), |
|
70 (2, 32): QBrush(Qt.darkGreen), |
|
71 (2, 33): QBrush(Qt.darkYellow), |
|
72 (2, 34): QBrush(Qt.darkBlue), |
|
73 (2, 35): QBrush(Qt.darkMagenta), |
|
74 (2, 36): QBrush(Qt.darkCyan), |
|
75 (2, 37): QBrush(Qt.lightGray), |
|
76 } |
57 |
77 |
58 def __init__(self, parent=None): |
78 def __init__(self, parent=None): |
59 """ |
79 """ |
60 Constructor |
80 Constructor |
61 |
81 |
113 "MicroPython support is deactivated.</h3>")) |
133 "MicroPython support is deactivated.</h3>")) |
114 self.setEnabled(False) |
134 self.setEnabled(False) |
115 return |
135 return |
116 |
136 |
117 self.__vt100Re = re.compile( |
137 self.__vt100Re = re.compile( |
118 r'(?P<count>[\d]*)(;?[\d]*)*(?P<action>[ABCDKm])') |
138 r'(?P<count>\d*);?(?P<color>\d*)(?P<action>[ABCDKm])') |
119 |
139 |
120 self.__populateDeviceTypeComboBox() |
140 self.__populateDeviceTypeComboBox() |
121 |
141 |
122 self.replEdit.setAcceptRichText(False) |
142 self.replEdit.setAcceptRichText(False) |
123 self.replEdit.setUndoRedoEnabled(False) |
143 self.replEdit.setUndoRedoEnabled(False) |
125 |
145 |
126 self.replEdit.installEventFilter(self) |
146 self.replEdit.installEventFilter(self) |
127 |
147 |
128 self.replEdit.customContextMenuRequested.connect( |
148 self.replEdit.customContextMenuRequested.connect( |
129 self.__showContextMenu) |
149 self.__showContextMenu) |
|
150 |
|
151 defaultCharFormat = self.replEdit.textCursor().charFormat() |
|
152 self.DefaultForeground = defaultCharFormat.foreground() |
|
153 self.DefaultBackground = defaultCharFormat.background() |
130 |
154 |
131 def __populateDeviceTypeComboBox(self): |
155 def __populateDeviceTypeComboBox(self): |
132 """ |
156 """ |
133 Private method to populate the device type selector. |
157 Private method to populate the device type selector. |
134 """ |
158 """ |
216 copyKeys = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_C) |
240 copyKeys = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_C) |
217 pasteKeys = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_V) |
241 pasteKeys = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_V) |
218 menu = QMenu(self) |
242 menu = QMenu(self) |
219 menu.addAction(self.tr("Copy"), self.replEdit.copy, copyKeys) |
243 menu.addAction(self.tr("Copy"), self.replEdit.copy, copyKeys) |
220 menu.addAction(self.tr("Paste"), self.__paste, pasteKeys) |
244 menu.addAction(self.tr("Paste"), self.__paste, pasteKeys) |
|
245 menu.addSeparator() |
221 menu.exec_(self.replEdit.mapToGlobal(pos)) |
246 menu.exec_(self.replEdit.mapToGlobal(pos)) |
222 |
247 |
223 def setConnected(self, connected): |
248 def setConnected(self, connected): |
224 """ |
249 """ |
225 Public method to set the connection status LED. |
250 Public method to set the connection status LED. |
307 """ |
332 """ |
308 Private slot to disconnect the serial connection. |
333 Private slot to disconnect the serial connection. |
309 """ |
334 """ |
310 self.__closeSerialLink() |
335 self.__closeSerialLink() |
311 self.setConnected(False) |
336 self.setConnected(False) |
312 |
|
313 def __activatePlotter(self): |
|
314 """ |
|
315 Private method to activate a data plotter widget. |
|
316 """ |
|
317 # TODO: not implemented yet |
|
318 |
|
319 def __deactivatePlotter(self): |
|
320 """ |
|
321 Private method to deactivate the plotter widget. |
|
322 """ |
|
323 # TODO: not implemented yet |
|
324 |
337 |
325 @pyqtSlot() |
338 @pyqtSlot() |
326 def __paste(self): |
339 def __paste(self): |
327 """ |
340 """ |
328 Private slot to perform a paste operation. |
341 Private slot to perform a paste operation. |
439 if match.group("count") == "": # delete to eol |
452 if match.group("count") == "": # delete to eol |
440 tc.movePosition(QTextCursor.EndOfLine, |
453 tc.movePosition(QTextCursor.EndOfLine, |
441 mode=QTextCursor.KeepAnchor) |
454 mode=QTextCursor.KeepAnchor) |
442 tc.removeSelectedText() |
455 tc.removeSelectedText() |
443 self.replEdit.setTextCursor(tc) |
456 self.replEdit.setTextCursor(tc) |
444 # TODO: add handling of 'm' (colors) |
457 elif action == "m": |
|
458 print(match.group("count"), match.group("color")) |
|
459 charFormat = tc.charFormat() |
|
460 if count == 0 and match.group("color") == "": |
|
461 # reset color |
|
462 charFormat.setForeground(self.DefaultForeground) |
|
463 charFormat.setBackground(self.DefaultBackground) |
|
464 elif count in (0, 1, 2): |
|
465 if match.group("color") != "": |
|
466 color = int(match.group("color")) |
|
467 if count == 0: |
|
468 count = 1 |
|
469 if 30 <= color <= 37: |
|
470 charFormat.setForeground( |
|
471 self.AnsiColors[(count, color)]) |
|
472 elif 40 <= color <= 47: |
|
473 charFormat.setBackground( |
|
474 self.AnsiColors[(count, color - 10)]) |
|
475 tc.setCharFormat(charFormat) |
|
476 self.replEdit.setTextCursor(tc) |
445 elif data[index] == 10: # \n |
477 elif data[index] == 10: # \n |
446 tc.movePosition(QTextCursor.End) |
478 tc.movePosition(QTextCursor.End) |
447 self.replEdit.setTextCursor(tc) |
479 self.replEdit.setTextCursor(tc) |
448 self.replEdit.insertPlainText(chr(data[index])) |
480 self.replEdit.insertPlainText(chr(data[index])) |
449 else: |
481 else: |
631 |
663 |
632 if self.__ui is None: |
664 if self.__ui is None: |
633 self.__ui = e5App().getObject("UserInterface") |
665 self.__ui = e5App().getObject("UserInterface") |
634 |
666 |
635 if self.__plotterRunning: |
667 if self.__plotterRunning: |
636 if self.__chartWidget.hasData(): |
668 if self.__chartWidget.isDirty(): |
637 res = E5MessageBox.okToClearData( |
669 res = E5MessageBox.okToClearData( |
638 self, |
670 self, |
639 self.tr("Unsaved Chart Data"), |
671 self.tr("Unsaved Chart Data"), |
640 self.tr("""The chart contains unsaved data."""), |
672 self.tr("""The chart contains unsaved data."""), |
641 self.__chartWidget.saveData) |
673 self.__chartWidget.saveData) |
666 self.dataReceived.connect(self.__chartWidget.processData) |
698 self.dataReceived.connect(self.__chartWidget.processData) |
667 self.__chartWidget.dataFlood.connect(self.handleDataFlood) |
699 self.__chartWidget.dataFlood.connect(self.handleDataFlood) |
668 |
700 |
669 self.__ui.addSideWidget(self.__ui.BottomSide, self.__chartWidget, |
701 self.__ui.addSideWidget(self.__ui.BottomSide, self.__chartWidget, |
670 UI.PixmapCache.getIcon("chart"), |
702 UI.PixmapCache.getIcon("chart"), |
671 self.tr("Chart")) |
703 self.tr("μPy Chart")) |
672 self.__ui.showSideWidget(self.__chartWidget) |
704 self.__ui.showSideWidget(self.__chartWidget) |
673 |
705 |
674 if not self.__serial: |
706 if not self.__serial: |
675 self.__openSerialLink() |
707 self.__openSerialLink() |
676 if self.__serial: |
708 if self.__serial: |