# -*- coding: utf-8 -*- import threading from PyQt4 import QtCore, QtGui from serial import Serial, SerialException from colourterm import SettingsDialog, ConnectDialog, SComboBox, Highlight, fromUtf8, translate, create_default_highlights from colourterm.cwebview import CWebView class Ui_MainWindow(object): def setupUi(self, mainWindow): mainWindow.setObjectName(fromUtf8('MainWindow')) mainWindow.resize(800, 600) self.centralwidget = QtGui.QWidget(mainWindow) self.centralwidget.setObjectName(fromUtf8('centralwidget')) self.centralLayout = QtGui.QVBoxLayout(self.centralwidget) self.centralLayout.setSpacing(8) self.centralLayout.setContentsMargins(0, 0, 0, 8) self.centralLayout.setObjectName(fromUtf8('centralLayout')) self.outputBrowser = CWebView(self.centralwidget) self.outputBrowser.setHtml('
' % str(QtGui.QApplication.palette().color(QtGui.QPalette.Text).name()))
        self.outputBrowser.setObjectName(fromUtf8('outputBrowser'))
        self.centralLayout.addWidget(self.outputBrowser)
        self.sendLayout = QtGui.QHBoxLayout()
        self.sendLayout.setSpacing(8)
        self.sendLayout.setObjectName(fromUtf8('sendLayout'))
        self.sendComboBox = SComboBox(self.centralwidget)
        self.sendComboBox.setEditable(True)
        self.sendComboBox.setEnabled(False)
        self.sendComboBox.setObjectName(fromUtf8('sendComboBox'))
        self.sendLayout.addWidget(self.sendComboBox)
        self.sendButton = QtGui.QPushButton(self.centralwidget)
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.sendButton.sizePolicy().hasHeightForWidth())
        self.sendButton.setSizePolicy(sizePolicy)
        self.sendButton.setMaximumSize(QtCore.QSize(100, 16777215))
        self.sendButton.setObjectName(fromUtf8('sendButton'))
        self.sendLayout.addWidget(self.sendButton)
        self.centralLayout.addLayout(self.sendLayout)
        mainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtGui.QStatusBar(mainWindow)
        self.statusbar.setObjectName(fromUtf8('statusbar'))
        mainWindow.setStatusBar(self.statusbar)
        self.toolBar = QtGui.QToolBar(mainWindow)
        self.toolBar.setMovable(False)
        self.toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
        self.toolBar.setFloatable(False)
        self.toolBar.setObjectName(fromUtf8('toolBar'))
        mainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar)
        self.openAction = QtGui.QAction(mainWindow)
        connectIcon = QtGui.QIcon()
        connectIcon.addPixmap(QtGui.QPixmap(fromUtf8(':/toolbar/network-connect.png')), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.openAction.setIcon(connectIcon)
        self.openAction.setObjectName(fromUtf8('openAction'))
        self.closeAction = QtGui.QAction(mainWindow)
        disconnectIcon = QtGui.QIcon()
        disconnectIcon.addPixmap(QtGui.QPixmap(fromUtf8(':/toolbar/network-disconnect.png')), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.closeAction.setIcon(disconnectIcon)
        self.closeAction.setObjectName(fromUtf8('closeAction'))
        self.captureAction = QtGui.QAction(mainWindow)
        captureIcon = QtGui.QIcon()
        captureIcon.addPixmap(QtGui.QPixmap(fromUtf8(':/toolbar/capture-to-disk.png')), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.captureAction.setIcon(captureIcon)
        self.captureAction.setCheckable(True)
        self.captureAction.setChecked(False)
        self.captureAction.setObjectName(fromUtf8('captureAction'))
        self.followAction = QtGui.QAction(mainWindow)
        self.followAction.setShortcut(QtCore.Qt.Key_F)
        followIcon = QtGui.QIcon()
        followIcon.addPixmap(QtGui.QPixmap(fromUtf8(':/toolbar/follow-output.png')), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.followAction.setIcon(followIcon)
        self.followAction.setCheckable(True)
        self.followAction.setChecked(True)
        self.followAction.setObjectName(fromUtf8('followAction'))
        self.configureAction = QtGui.QAction(mainWindow)
        configureIcon = QtGui.QIcon()
        configureIcon.addPixmap(QtGui.QPixmap(fromUtf8(':/toolbar/configure.png')), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.configureAction.setIcon(configureIcon)
        self.configureAction.setObjectName(fromUtf8('configureAction'))
        self.exitAction = QtGui.QAction(mainWindow)
        exitIcon = QtGui.QIcon()
        exitIcon.addPixmap(QtGui.QPixmap(fromUtf8(':/toolbar/application-exit.png')), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.exitAction.setIcon(exitIcon)
        self.exitAction.setObjectName(fromUtf8('exitAction'))
        self.toolBar.addAction(self.openAction)
        self.toolBar.addAction(self.closeAction)
        self.toolBar.addAction(self.captureAction)
        self.toolBar.addAction(self.followAction)
        self.toolBar.addAction(self.configureAction)
        self.toolBar.addAction(self.exitAction)

        self.retranslateUi(mainWindow)
        QtCore.QMetaObject.connectSlotsByName(mainWindow)

    def retranslateUi(self, mainWindow):
        mainWindow.setWindowTitle(translate('MainWindow', 'ColourTerm'))
        self.sendButton.setText(translate('MainWindow', 'Send'))
        self.toolBar.setWindowTitle(translate('MainWindow', 'toolBar'))
        self.openAction.setText(translate('MainWindow', 'Open...'))
        self.openAction.setToolTip(translate('MainWindow', 'Open...'))
        self.closeAction.setText(translate('MainWindow', 'Close'))
        self.closeAction.setToolTip(translate('MainWindow', 'Close'))
        self.captureAction.setText(translate('MainWindow', 'Capture'))
        self.captureAction.setToolTip(translate('MainWindow', 'Capture to File'))
        self.followAction.setText(translate('MainWindow', '&Follow'))
        self.configureAction.setText(translate('MainWindow', 'Configure...'))
        self.configureAction.setToolTip(translate('MainWindow', 'Configure...'))
        self.exitAction.setText(translate('MainWindow', 'Exit'))


class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
    updateOutput = QtCore.pyqtSignal(str)

    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.terminalLines = []
        self.maxLines = 5000
        self.setupUi(self)
        self.device = None
        self.deviceClosed = True
        self.followOutput = True
        self.captureFile = None
        self.captureFileName = u''
        self.highlights = self.loadHighlights()
        if not self.highlights:
            self.highlights = create_default_highlights()
        self.settingsDialog = SettingsDialog()
        self.connectDialog = ConnectDialog()
        self.openAction.triggered.connect(self.onOpenActionTriggered)
        self.closeAction.triggered.connect(self.onCloseActionTriggered)
        self.captureAction.toggled.connect(self.onCaptureActionToggled)
        self.followAction.toggled.connect(self.onFollowActionToggled)
        self.configureAction.triggered.connect(self.onConfigureActionTriggered)
        self.exitAction.triggered.connect(self.close)
        self.sendComboBox.keyPressed.connect(self.onSendComboBoxKeyPressed)
        self.sendButton.clicked.connect(self.onSendButtonClicked)
        self.outputBrowser.page().mainFrame().contentsSizeChanged.connect(self.onContentsSizeChanged)
        self.outputBrowser.onScroll.connect(self.onOutputBrowserScrolled)
        self.updateOutput.connect(self.onUpdateOutput)

    def close(self):
        if not self.deviceClosed:
            self.deviceClosed = True
        if self.captureFile:
            self.captureFile.flush()
            self.captureFile.close()
        return QtGui.QMainWindow.close(self)

    def documentBody(self):
        return self.outputBrowser.page().mainFrame().documentElement().findFirst('pre')

    def receiveText(self):
        output = ''
        while not self.deviceClosed:
            output += self.device.read(1)
            if output.endswith('\r\n'):
                #self.terminalLines.append(output.strip('\r\n'))
                #if len(self.terminalLines) > self.maxLines:
                #    self.terminalLines = self.terminalLines[-self.maxLines:]
                #    self.refreshOutput()
                #else:
                self.updateOutput.emit(output.strip('\r\n'))
                output = ''

    def onOpenActionTriggered(self):
        if self.connectDialog.exec_() == QtGui.QDialog.Accepted:
            if not self.deviceClosed:
                self.deviceClosed = True
                self.device.close()
            try:
                self.device = Serial(
                    port=self.connectDialog.getPort(),
                    baudrate=self.connectDialog.getBaud(),
                    bytesize=self.connectDialog.getDataBits(),
                    parity=self.connectDialog.getParity(),
                    stopbits=self.connectDialog.getStopBits(),
                    timeout=0,
                    xonxoff=self.connectDialog.getSoftwareHandshake(),
                    rtscts=self.connectDialog.getHardwareHandshake(),
                    dsrdtr=None
                )
                self.deviceClosed = False
                if not self.device.isOpen():
                    self.device.open()
                outputThread = threading.Thread(target=self.receiveText)
                outputThread.start()
            except SerialException, e:
                QtGui.QMessageBox.critical(self, 'Error opening port', e.args[0])
        self.sendComboBox.setEnabled(not self.deviceClosed)
        if self.sendComboBox.isEnabled():
            self.sendComboBox.setFocus()

    def onCloseActionTriggered(self):
        self.deviceClosed = True
        if self.device.isOpen():
            self.device.close()
        self.sendComboBox.setEnabled(not self.deviceClosed)

    def onCaptureActionToggled(self, enabled):
        if enabled and not self.captureFile:
            if self.captureFileName:
                baseDir = os.path.basename(self.captureFileName)
            else:
                baseDir = u''
            self.captureFileName = QtGui.QFileDialog.getSaveFileName(self, u'Capture To File',
                baseDir, u'Text files (*.txt *.log);;All files (*)')
            self.captureFile = open(self.captureFileName, u'w')
            self.statusbar.showMessage(self.captureFileName)
        elif self.captureFile and not enabled:
            self.captureFileName = u''
            self.captureFile.flush()
            self.captureFile.close()
            self.captureFile = None
            self.statusbar.clearMessage()

    def onFollowActionToggled(self, enabled):
        self.followOutput = enabled
        if enabled:
            self.outputBrowser.page().mainFrame().scroll(0,
                self.outputBrowser.page().mainFrame().contentsSize().height())

    def onConfigureActionTriggered(self):
        self.settingsDialog.setHighlights(self.highlights)
        self.settingsDialog.exec_()
        self.highlights = self.settingsDialog.highlights()
        self.saveHighlights(self.highlights)
        self.refreshOutput()

    def onSendComboBoxKeyPressed(self, key):
        if key == QtCore.Qt.Key_Return or key == QtCore.Qt.Key_Enter:
            self.onSendButtonClicked()

    def onSendButtonClicked(self):
        if self.device.isOpen():
            output = str(self.sendComboBox.currentText())
            self.sendComboBox.insertItem(0, output)
            self.sendComboBox.setCurrentIndex(0)
            self.sendComboBox.clearEditText()
            self.device.write(output + u'\r\n')

    def onContentsSizeChanged(self, size):
        if self.followOutput:
            self.outputBrowser.page().mainFrame().scroll(0, size.height())
        self.outputBrowser.update()

    def onUpdateOutput(self, output):
        #self.terminalLines.append(output)
        if self.captureFile:
            self.captureFile.write(output + u'\n')
            self.captureFile.flush()
        #if len(self.terminalLines) > 5000:
        #    self.terminalLines = self.terminalLines[-5000:]
        #    self.refreshOutput()
        #else:
        output = self.styleOutput(output)
        self.documentBody().appendInside(output)

    def onOutputBrowserScrolled(self):
        scrollValue = self.outputBrowser.page().mainFrame().scrollBarValue(QtCore.Qt.Vertical)
        scrollMax = self.outputBrowser.page().mainFrame().scrollBarMaximum(QtCore.Qt.Vertical)
        if scrollValue < scrollMax:
            self.onFollowActionToggled(False)
            self.followAction.setChecked(False)
        else:
            self.onFollowActionToggled(True)
            self.followAction.setChecked(True)

    def refreshOutput(self):
        elements = self.outputBrowser.page().mainFrame().findAllElements('div')
        lines = [unicode(element.toPlainText()) for element in elements]
        pre = self.outputBrowser.page().mainFrame().findFirstElement('pre')
        pre.setInnerXml('')
        for line in lines:
            output = self.styleOutput(line)
            self.documentBody().appendInside(output)
        self.outputBrowser.page().mainFrame().scroll(0, self.outputBrowser.page().mainFrame().contentsSize().height())
        self.outputBrowser.update()

    def styleOutput(self, output):
        style = u'font-family: Ubuntu Mono; '
        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'
%s
' % (style, unicode(output, u'utf-8')) except TypeError: output = u'
%s
' % (style, output) else: output = u'
%s
' % output return output def saveHighlights(self, highlights): settings = QtCore.QSettings() settings.setValue(u'highlights/count', len(highlights)) for index, highlight in enumerate(highlights): settings.beginGroup(u'highlight-%s' % index) settings.setValue(u'pattern', highlight.pattern) settings.setValue(u'foreground', highlight.foreground) if highlight.background: settings.setValue(u'background', highlight.background) else: if settings.contains(u'background'): settings.remove(u'background') settings.endGroup() def loadHighlights(self): settings = QtCore.QSettings() highlight_count = settings.value(u'highlights/count', 0).toInt()[0] highlights = [] for index in range(highlight_count): settings.beginGroup('highlight-%s' % index) pattern = unicode(settings.value('pattern', '').toString()) foreground = unicode(settings.value('foreground', '').toString()) background = None if settings.contains('background'): background = unicode(settings.value('background', '').toString()) settings.endGroup() highlights.append(Highlight(pattern, foreground, background)) return highlights