colourterm/colourterm/mainwindow.py

330 lines
15 KiB
Python

# -*- 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('<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):
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'&nbsp;'
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