341 lines
16 KiB
Python
341 lines
16 KiB
Python
# -*- coding: utf-8 -*-
|
|
import os
|
|
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('<html><head><style>body { color: %s; font-family: monospace; margin: 0; '
|
|
'padding: 0; }</style></head><body><pre></pre></body></html>' %
|
|
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):
|
|
self.connectDialog.updatePortCombobox()
|
|
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 + '\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 + '\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'<div style="%s">%s</div>' % (style, unicode(output, u'utf-8'))
|
|
except TypeError:
|
|
output = u'<div style="%s">%s</div>' % (style, output)
|
|
else:
|
|
output = u'<div>%s</div>' % 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
|