# -*- coding: utf-8 -*- import os import threading from PyQt4 import QtCore, QtGui from serial import Serial, SerialException from colourterm import SettingsDialog, ConnectDialog, SComboBox, Highlight, from_utf8, translate, \ create_default_highlights from colourterm.cwebview import CWebView class UiMainWindow(object): def __init__(self): """ Just to satisfy PEP8/PyLint """ self.central_widget = None self.central_layout = None self.output_browser = None self.send_layout = None self.send_combobox = None self.send_button = None self.status_bar = None self.tool_bar = None self.open_action = None self.close_action = None self.capture_action = None self.follow_action = None self.configure_action = None self.exit_action = None def setup_ui(self, main_window): """ Set up the user interface """ main_window.setObjectName(from_utf8('MainWindow')) main_window.resize(800, 600) self.central_widget = QtGui.QWidget(main_window) self.central_widget.setObjectName(from_utf8('central_widget')) self.central_layout = QtGui.QVBoxLayout(self.central_widget) self.central_layout.setSpacing(8) self.central_layout.setContentsMargins(0, 0, 0, 8) self.central_layout.setObjectName(from_utf8('centralLayout')) self.output_browser = CWebView(self.central_widget) self.output_browser.setHtml('
' % str(QtGui.QApplication.palette().color(QtGui.QPalette.Text).name())) self.output_browser.setObjectName(from_utf8('outputBrowser')) self.central_layout.addWidget(self.output_browser) self.send_layout = QtGui.QHBoxLayout() self.send_layout.setSpacing(8) self.send_layout.setObjectName(from_utf8('sendLayout')) self.send_combobox = SComboBox(self.central_widget) self.send_combobox.setEditable(True) self.send_combobox.setEnabled(False) self.send_combobox.setObjectName(from_utf8('sendComboBox')) self.send_layout.addWidget(self.send_combobox) self.send_button = QtGui.QPushButton(self.central_widget) size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) size_policy.setHorizontalStretch(0) size_policy.setVerticalStretch(0) size_policy.setHeightForWidth(self.send_button.sizePolicy().hasHeightForWidth()) self.send_button.setSizePolicy(size_policy) self.send_button.setMaximumSize(QtCore.QSize(100, 16777215)) self.send_button.setObjectName(from_utf8('sendButton')) self.send_layout.addWidget(self.send_button) self.central_layout.addLayout(self.send_layout) main_window.setCentralWidget(self.central_widget) self.status_bar = QtGui.QStatusBar(main_window) self.status_bar.setObjectName(from_utf8('status_bar')) main_window.setStatusBar(self.status_bar) self.tool_bar = QtGui.QToolBar(main_window) self.tool_bar.setMovable(False) self.tool_bar.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) self.tool_bar.setFloatable(False) self.tool_bar.setObjectName(from_utf8('tool_bar')) main_window.addToolBar(QtCore.Qt.TopToolBarArea, self.tool_bar) self.open_action = QtGui.QAction(main_window) connect_icon = QtGui.QIcon() connect_icon.addPixmap(QtGui.QPixmap(from_utf8(':/toolbar/network-connect.png')), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.open_action.setIcon(connect_icon) self.open_action.setObjectName(from_utf8('open_action')) self.close_action = QtGui.QAction(main_window) disconnect_icon = QtGui.QIcon() disconnect_icon.addPixmap(QtGui.QPixmap(from_utf8(':/toolbar/network-disconnect.png')), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.close_action.setIcon(disconnect_icon) self.close_action.setObjectName(from_utf8('close_action')) self.capture_action = QtGui.QAction(main_window) capture_icon = QtGui.QIcon() capture_icon.addPixmap(QtGui.QPixmap(from_utf8(':/toolbar/capture-to-disk.png')), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.capture_action.setIcon(capture_icon) self.capture_action.setCheckable(True) self.capture_action.setChecked(False) self.capture_action.setObjectName(from_utf8('capture_action')) self.follow_action = QtGui.QAction(main_window) self.follow_action.setShortcut(QtCore.Qt.Key_F) follow_icon = QtGui.QIcon() follow_icon.addPixmap(QtGui.QPixmap(from_utf8(':/toolbar/follow-output.png')), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.follow_action.setIcon(follow_icon) self.follow_action.setCheckable(True) self.follow_action.setChecked(True) self.follow_action.setObjectName(from_utf8('follow_action')) self.configure_action = QtGui.QAction(main_window) configure_icon = QtGui.QIcon() configure_icon.addPixmap(QtGui.QPixmap(from_utf8(':/toolbar/configure.png')), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.configure_action.setIcon(configure_icon) self.configure_action.setObjectName(from_utf8('configure_action')) self.exit_action = QtGui.QAction(main_window) exit_icon = QtGui.QIcon() exit_icon.addPixmap(QtGui.QPixmap(from_utf8(':/toolbar/application-exit.png')), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.exit_action.setIcon(exit_icon) self.exit_action.setObjectName(from_utf8('exit_action')) self.tool_bar.addAction(self.open_action) self.tool_bar.addAction(self.close_action) self.tool_bar.addAction(self.capture_action) self.tool_bar.addAction(self.follow_action) self.tool_bar.addAction(self.configure_action) self.tool_bar.addAction(self.exit_action) self.retranslate_ui(main_window) def retranslate_ui(self, main_window): """ Translate the user interface """ main_window.setWindowTitle(translate('MainWindow', 'ColourTerm')) self.send_button.setText(translate('MainWindow', 'Send')) self.tool_bar.setWindowTitle(translate('MainWindow', 'tool_bar')) self.open_action.setText(translate('MainWindow', 'Open...')) self.open_action.setToolTip(translate('MainWindow', 'Open...')) self.close_action.setText(translate('MainWindow', 'Close')) self.close_action.setToolTip(translate('MainWindow', 'Close')) self.capture_action.setText(translate('MainWindow', 'Capture')) self.capture_action.setToolTip(translate('MainWindow', 'Capture to File')) self.follow_action.setText(translate('MainWindow', '&Follow')) self.configure_action.setText(translate('MainWindow', 'Configure...')) self.configure_action.setToolTip(translate('MainWindow', 'Configure...')) self.exit_action.setText(translate('MainWindow', 'Exit')) class MainWindow(QtGui.QMainWindow, UiMainWindow): updateOutput = QtCore.pyqtSignal(str) def __init__(self): super(MainWindow, self).__init__() self.terminal_lines = [] self.max_lines = 5000 self.setup_ui(self) self.device = None self.device_closed = True self.follow_output = True self.capture_file = None self.capture_filename = u'' self.highlights = self.load_highlights() if not self.highlights: self.highlights = create_default_highlights() self.settings_dialog = SettingsDialog() self.connect_dialog = ConnectDialog(self) self.open_action.triggered.connect(self.on_open_action_triggered) self.close_action.triggered.connect(self.on_close_action_triggered) self.capture_action.toggled.connect(self.on_capture_action_toggled) self.follow_action.toggled.connect(self.on_follow_action_toggled) self.configure_action.triggered.connect(self.on_configure_action_triggered) self.exit_action.triggered.connect(self.close) self.send_combobox.keyPressed.connect(self.on_send_combobox_key_pressed) self.send_button.clicked.connect(self.on_send_button_clicked) self.output_browser.page().mainFrame().contentsSizeChanged.connect(self.on_contents_size_changed) self.output_browser.onScroll.connect(self.on_output_browser_scrolled) self.updateOutput.connect(self.on_update_output) def close(self): if not self.device_closed: self.device_closed = True if self.capture_file: self.capture_file.flush() self.capture_file.close() return QtGui.QMainWindow.close(self) def document_body(self): return self.output_browser.page().mainFrame().documentElement().findFirst('pre') def receive_text(self): output = '' while not self.device_closed: output += self.device.read(1) if output.endswith('\r\n'): #self.terminal_lines.append(output.strip('\r\n')) #if len(self.terminal_lines) > self.max_lines: # self.terminal_lines = self.terminal_lines[-self.max_lines:] # self.refreshOutput() #else: self.updateOutput.emit(output.strip('\r\n')) output = '' def on_open_action_triggered(self): self.connect_dialog.update_port_combobox() settings = QtCore.QSettings() self.connect_dialog.set_port(unicode(settings.value(u'previous-port', u'').toString())) if self.connect_dialog.exec_() == QtGui.QDialog.Accepted: if not self.device_closed: self.device_closed = True self.device.close() try: settings.setValue(u'previous-port', self.connect_dialog.get_port()) self.device = Serial( port=self.connect_dialog.get_port(), baudrate=self.connect_dialog.get_baud(), bytesize=self.connect_dialog.get_data_bits(), parity=self.connect_dialog.get_parity(), stopbits=self.connect_dialog.get_stop_bits(), timeout=0, xonxoff=self.connect_dialog.get_software_handshake(), rtscts=self.connect_dialog.get_hardware_handshake(), dsrdtr=None ) self.device_closed = False if not self.device.isOpen(): self.device.open() output_thread = threading.Thread(target=self.receive_text) output_thread.start() except SerialException as e: QtGui.QMessageBox.critical(self, 'Error opening port', e.args[0]) self.send_combobox.setEnabled(not self.device_closed) if self.send_combobox.isEnabled(): self.send_combobox.setFocus() def on_close_action_triggered(self): self.device_closed = True if self.device.isOpen(): self.device.close() self.send_combobox.setEnabled(not self.device_closed) def on_capture_action_toggled(self, enabled): if enabled and not self.capture_file: if self.capture_filename: base_dir = os.path.basename(self.capture_filename) else: base_dir = u'' self.capture_filename = QtGui.QFileDialog.getSaveFileName(self, u'Capture To File', base_dir, u'Text files (*.txt *.log);;All files (*)') self.capture_file = open(self.capture_filename, u'w') self.status_bar.showMessage(self.capture_filename) elif self.capture_file and not enabled: self.capture_filename = u'' self.capture_file.flush() self.capture_file.close() self.capture_file = None self.status_bar.clearMessage() def on_follow_action_toggled(self, enabled): self.follow_output = enabled if enabled: self.output_browser.page().mainFrame().scroll( 0, self.output_browser.page().mainFrame().contentsSize().height()) def on_configure_action_triggered(self): self.settings_dialog.set_highlights(self.highlights) self.settings_dialog.exec_() self.highlights = self.settings_dialog.highlights() self.save_highlights(self.highlights) self.refresh_output() def on_send_combobox_key_pressed(self, key): if key == QtCore.Qt.Key_Return or key == QtCore.Qt.Key_Enter: self.on_send_button_clicked() def on_send_button_clicked(self): if self.device.isOpen(): output = str(self.send_combobox.currentText()) self.send_combobox.insertItem(0, output) self.send_combobox.setCurrentIndex(0) self.send_combobox.clearEditText() self.device.write(output + '\r\n') def on_contents_size_changed(self, size): if self.follow_output: self.output_browser.page().mainFrame().scroll(0, size.height()) self.output_browser.update() def on_update_output(self, output): #self.terminal_lines.append(output) if self.capture_file: self.capture_file.write(output + '\n') self.capture_file.flush() #if len(self.terminal_lines) > 5000: # self.terminal_lines = self.terminal_lines[-5000:] # self.refreshOutput() #else: output = self.style_output(output) self.document_body().appendInside(output) def on_output_browser_scrolled(self): scroll_value = self.output_browser.page().mainFrame().scrollBarValue(QtCore.Qt.Vertical) scroll_max = self.output_browser.page().mainFrame().scrollBarMaximum(QtCore.Qt.Vertical) if scroll_value < scroll_max: self.on_follow_action_toggled(False) self.follow_action.setChecked(False) else: self.on_follow_action_toggled(True) self.follow_action.setChecked(True) def refresh_output(self): elements = self.output_browser.page().mainFrame().findAllElements('div') lines = [unicode(element.toPlainText()) for element in elements] pre = self.output_browser.page().mainFrame().findFirstElement('pre') pre.setInnerXml('') for line in lines: output = self.style_output(line) self.document_body().appendInside(output) self.output_browser.page().mainFrame().scroll(0, self.output_browser.page().mainFrame().contentsSize().height()) self.output_browser.update() def style_output(self, output): style = u'font-family: \'Ubuntu Mono\', monospace; ' if not output: output = u' ' for highlight in self.highlights: if highlight.regex.search(output): if highlight.foreground: style = u'%scolor: %s; ' % (style, highlight.foreground) if highlight.background: style = u'%sbackground-color: %s; ' % (style, highlight.background) break if style: try: output = u'