commit 50b9942ecfdba7c79b4443f9d4da99247e9130d7 Author: Ondřej Hruška Date: Mon Jun 18 09:14:43 2018 +0200 code import diff --git a/gexync.py b/gexync.py new file mode 100755 index 0000000..81a6922 --- /dev/null +++ b/gexync.py @@ -0,0 +1,128 @@ +#!/bin/env python3 + +import gex + +import sys +from PyQt4 import QtGui, QtCore +import ini_syntax + +class GexIniEditor(QtGui.QMainWindow): + """ + Gexync is a GEX ini file editor. + The editor loads the INI file through the communication interface + without having to mount the virtual filesystem, which is unreliable + on some systems. + + This utility allows live editing of the UNITS.INI file. + + On save, a new version is loaded with formatting and error messages + generated by GEX, as if the virtual config filesystem was re-mounted. + + The editor does not keep GEX claimed, instead does so only when needed. + This allows testing of the current configuration without having to close + and reopen the editor. + """ + + def __init__(self, xferLambda): + self.xferLambda = xferLambda + + self.filenum = int(sys.argv[1]) if len(sys.argv)>1 else 0 + + super().__init__() + self.initUI() + # TODO let user pick GEX device if multiple + + def initToolbar(self): + icon = self.style().standardIcon(QtGui.QStyle.SP_BrowserReload) + loadAction = QtGui.QAction(icon, 'Reload', self) + loadAction.setShortcut('Ctrl+O') + loadAction.triggered.connect(self.gexLoad) + + icon = self.style().standardIcon(QtGui.QStyle.SP_DialogSaveButton) + syncAction = QtGui.QAction(icon, 'Write Changes', self) + syncAction.setShortcut('Ctrl+S') + syncAction.triggered.connect(self.gexSync) + + icon = self.style().standardIcon(QtGui.QStyle.SP_DialogOkButton) + persAction = QtGui.QAction(icon, 'Persist', self) + persAction.setShortcut('Ctrl+P') + persAction.triggered.connect(self.gexPersist) + + self.toolbar = self.addToolBar('Toolbar') + self.toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) + self.toolbar.addAction(loadAction) + self.toolbar.addAction(syncAction) + self.toolbar.addSeparator() + self.toolbar.addAction(persAction) + + def initEditor(self): + self.editor = QtGui.QPlainTextEdit() + + # Editor background and text color + pal = QtGui.QPalette() + bgc = QtGui.QColor(0xFFFFF6) + pal.setColor(QtGui.QPalette.Base, bgc) + textc = QtGui.QColor(0x000000) + pal.setColor(QtGui.QPalette.Text, textc) + self.editor.setPalette(pal) + # Font + font = QtGui.QFont('Liberation Mono', 12) + font.setStyleHint(QtGui.QFont.TypeWriter) + self.editor.setFont(font) + # Initial size + self.highlight = ini_syntax.IniHighlighter(self.editor.document()) + + def initUI(self): + self.setWindowTitle('GEX config file editor') + self.initToolbar() + self.initEditor() + self.setCentralWidget(self.editor) + self.show() + + self.gexLoad() + + def gexLoad(self): + self.editor.setPlainText("") + self.editor.repaint() + + client = gex.Client(self.xferLambda(), load_units=False) + read_ini = client.ini_read(self.filenum) + client.close() + + self.editor.setPlainText(read_ini) + self.highlight.rehighlight() + self.setWindowTitle('GEX config file editor') + + def gexSync(self): + new_txt = self.editor.toPlainText() + self.editor.setPlainText("") + self.editor.repaint() + + client = gex.Client(self.xferLambda(), load_units=False) + client.ini_write(new_txt) + read_ini = client.ini_read(self.filenum) + client.close() + + self.editor.setPlainText(read_ini) + self.highlight.rehighlight() + self.setWindowTitle('*GEX config file editor') + + def gexPersist(self): + client = gex.Client(self.xferLambda(), load_units=False) + client.ini_persist() + client.close() + self.setWindowTitle('GEX config file editor') + +if __name__ == '__main__': + app = QtGui.QApplication(sys.argv) + editor = GexIniEditor(lambda: gex.TrxRawUSB()) + # editor = GexIniEditor(lambda: gex.TrxSerialThread(port='/dev/ttyUSB1', + # baud=57600)) + + # centered resize + w = 800 + h = 900 + ss = app.desktop().availableGeometry().size() + editor.setGeometry(int(ss.width() / 2 - w / 2), int(ss.height() / 2 - h / 2), w, h) + + sys.exit(app.exec_()) diff --git a/ini_syntax.py b/ini_syntax.py new file mode 100644 index 0000000..4d3cc29 --- /dev/null +++ b/ini_syntax.py @@ -0,0 +1,81 @@ +# syntax.py + +# This is a companion file to gexync.py + +# based on https://wiki.python.org/moin/PyQt/Python%20syntax%20highlighting + +from PyQt4.QtCore import QRegExp +from PyQt4.QtGui import QColor, QTextCharFormat, QFont, QSyntaxHighlighter + +def format(color, style=''): + """Return a QTextCharFormat with the given attributes. + """ + _color = QColor() + _color.setNamedColor(color) + + _format = QTextCharFormat() + _format.setForeground(_color) + if 'bold' in style: + _format.setFontWeight(QFont.Bold) + if 'italic' in style: + _format.setFontItalic(True) + + return _format + + +# Syntax styles that can be shared by all languages +STYLES = { + 'operator': format('red'), + 'string': format('magenta'), + 'comment': format('#6C8A70', 'italic'), + 'key': format('#008AFF'), + 'numbers': format('brown'), + 'section': format('black', 'bold'), +} + +class IniHighlighter (QSyntaxHighlighter): + # Python braces + def __init__(self, document): + QSyntaxHighlighter.__init__(self, document) + + rules = [ + (r'=', 0, STYLES['operator']), + (r'\b[YN]\b', 0, STYLES['numbers']), + + # Double-quoted string, possibly containing escape sequences + (r'"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']), + # Single-quoted string, possibly containing escape sequences + (r"'[^'\\]*(\\.[^'\\]*)*'", 0, STYLES['string']), + + # Numeric literals + (r'\b[+-]?[0-9]+\b', 0, STYLES['numbers']), + (r'\b[+-]?0[xX][0-9A-Fa-f]+\b', 0, STYLES['numbers']), + (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, STYLES['numbers']), + + # From '#' until a newline + (r'#[^\n]*', 0, STYLES['comment']), + + (r'^\[.+\]', 0, STYLES['section']), + + (r'^[a-zA-Z0-9_-]+\s?=', 0, STYLES['key']), + ] + + # Build a QRegExp for each pattern + self.rules = [(QRegExp(pat), index, fmt) + for (pat, index, fmt) in rules] + + def highlightBlock(self, text): + """Apply syntax highlighting to the given block of text. + """ + # Do other syntax formatting + for expression, nth, format in self.rules: + index = expression.indexIn(text, 0) + + while index >= 0: + # We actually want the index of the nth match + index = expression.pos(nth) + length = len(expression.cap(nth)) + self.setFormat(index, length, format) + index = expression.indexIn(text, index + length) + + self.setCurrentBlockState(0)