diff --git a/.gitignore b/.gitignore index e8fd9802e841a8210b2e70b4ee31e1cff1fde055..3e54c7b48c53adb28c97c75804a2a89e80abd317 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ mp_py_um.toc # Python __pycache__ +# Doxygen +doc/doxygen/html diff --git a/python/Makefile b/python/Makefile index 32e6e164d42880038b6f6eb6e3dab98117438995..7fba6be3af517ec44d2a543ae0125f50fcdc8568 100644 --- a/python/Makefile +++ b/python/Makefile @@ -18,7 +18,7 @@ # Changed dist to also depend on all # -VERSION = Pre-Alpha-v0.3.9 +VERSION = Pre-Alpha-v0.3.10 WIN = MP_Gryphon_GUI-$(VERSION)-windowsinstaller.exe ZIP_DIR = MP_Gryphon_GUI-$(VERSION) ZIP = $(ZIP_DIR).zip diff --git a/python/about_mp_dialog_wrapper.py b/python/about_mp_dialog_wrapper.py index 88a68b861cd13ff9092af567969153b21ef7a358..e22575bb621f6d524ee71d718fca709a3766ac78 100644 --- a/python/about_mp_dialog_wrapper.py +++ b/python/about_mp_dialog_wrapper.py @@ -20,7 +20,6 @@ class AboutMPDialogWrapper(QDialog): # put in text from file with open("html/about.html") as f: text = f.read() -# self.ui.about_te.appendHtml(text) self.ui.about_tb.setHtml(text) self.ui.about_tb.setOpenExternalLinks(True) diff --git a/python/color_selector_widget.py b/python/color_selector_widget.py index 98904c625575cba3ad9c5b0ae1a6c0709c64c488..ee108b7eee477ea03ca708420441197535fa3b4b 100644 --- a/python/color_selector_widget.py +++ b/python/color_selector_widget.py @@ -1,5 +1,3 @@ -#from PyQt5.QtCore import QObject # for signal/slot support -#from PyQt5.QtCore import pyqtSlot from PyQt5.QtWidgets import QColorDialog from PyQt5.QtGui import QColor from settings_manager import settings diff --git a/python/command_runner.py b/python/command_runner.py index eaa406c61e12686936470bab68e596b0447225a3..524a221ead2d7f1cdde2acc9516c228f460f6f82 100644 --- a/python/command_runner.py +++ b/python/command_runner.py @@ -23,7 +23,7 @@ class _RunnerThread(threading.Thread): # run the command try: self.cmd_p = subprocess.Popen(self._cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + stderr=subprocess.PIPE) # start readers stdout_reader = _ReaderThread("stdout", self.cmd_p.stdout, self._queue) @@ -39,7 +39,7 @@ class _RunnerThread(threading.Thread): # python 3 uses FileNotFoundError, python 2.7 uses superclass IOError except IOError: self._queue.put(("Error", "%s not found. Please check that it " - "is installed.\n" % self._cmd[0])) + "is installed.\n" % self._cmd[0])) return 1 # private reader helper @@ -53,8 +53,8 @@ class _ReaderThread(threading.Thread): def run(self): # read pipe until pipe closes for line in self._pipe: - line_string = line.decode( - encoding=sys.stderr.encoding, errors='replace') + line_string = line.decode(encoding=sys.stderr.encoding, + errors='replace') # write stderr line to queue self._queue.put((self._name, line_string)) diff --git a/python/edge.py b/python/edge.py index 3c3a277d9c66f9af8b8bac8dc310d4ee0bca9b03..8cc6c2e902897334d7dc573d3fed40611afac4ec 100644 --- a/python/edge.py +++ b/python/edge.py @@ -1,32 +1,21 @@ # Adapted from https://raw.githubusercontent.com/baoboa/pyqt5/master/examples/graphicsview/elasticnodes.py from math import sin, cos, tan, atan2, pi -from PyQt5.QtCore import (QLineF, QPointF, QRectF, QSizeF) +from PyQt5.QtCore import QLineF, QPointF from PyQt5.QtCore import Qt -from PyQt5.QtGui import (QBrush, QColor, QLinearGradient, QPainter, - QPainterPath, QPen, QPolygonF, QRadialGradient) -from PyQt5.QtGui import QTransform -from PyQt5.QtGui import QPolygonF -from PyQt5.QtGui import QFontMetrics -from PyQt5.QtGui import QFont -from PyQt5.QtGui import QPainterPath +from PyQt5.QtGui import QColor, QPainterPath, QPen, QPolygonF from PyQt5.QtGui import QPainterPathStroker -from PyQt5.QtWidgets import (QGraphicsItem, QGraphicsView, QStyle) -from PyQt5.QtWidgets import QGraphicsSimpleTextItem -from PyQt5.QtWidgets import QMenu -from PyQt5.QtWidgets import QAction -from PyQt5.QtWidgets import QGraphicsSceneContextMenuEvent +from PyQt5.QtWidgets import QGraphicsItem from settings_manager import settings # Qt line style from MP line style name def _line_style_lookup(name): if name == "solid_line": return Qt.SolidLine - elif name == "dash_line": + if name == "dash_line": return Qt.DashLine - else: - print("Invalid line style name '%s'" % name) - return Qt.SolidLine + print("Invalid line style name '%s'" % name) + return Qt.SolidLine # source point is at edge of source node def _source_point(source_node, line): @@ -113,7 +102,7 @@ class Edge(QGraphicsItem): Type = QGraphicsItem.UserType + 2 def __init__(self, source_id, relation, target_id, label, node_lookup, - cbp1 = None, cbp2 = None): + cbp1=None, cbp2=None): super(Edge, self).__init__() # MP attributes @@ -243,7 +232,7 @@ class Edge(QGraphicsItem): dest_path = self.mapFromItem(self.dest_node, self.dest_node.mouse_path) t = 0.5 m = 0.25 - for i in range(10): # binary search edge with resolution of 12 + for _i in range(10): # binary search edge with resolution of 12 arrow_tip = edge_path.pointAtPercent(t) if dest_path.contains(arrow_tip): # inside dest so move back @@ -309,20 +298,21 @@ class Edge(QGraphicsItem): def boundingRect(self): extra = 1 return self.edge_path.boundingRect().adjusted(-extra, -extra, - extra, extra) + extra, extra) def shape(self): # note: path without stroker includes concave shape, not just edge path return self.mouse_path - def paint(self, painter, option, widget): + def paint(self, painter, _option, _widget): # paint edge shape of path without brush fill painter.strokePath(self.edge_path, QPen(self.color, self.line_width, self.style, Qt.RoundCap, Qt.RoundJoin)) # draw the arrow - painter.setPen(QPen(self.color, self.line_width, self.style, Qt.RoundCap, Qt.RoundJoin)) + painter.setPen(QPen(self.color, self.line_width, self.style, + Qt.RoundCap, Qt.RoundJoin)) painter.setBrush(self.color) painter.drawPolygon(self.arrow) @@ -334,14 +324,7 @@ class Edge(QGraphicsItem): offset = QPointF(label_width/2, label_descent+1) painter.drawText(self.label_point - offset, self.label) - # paint using p0, p1, p2, p3, color, line_width, style - def draw_cubic_bezier(self): - painter.setPen(QPen(self.color, self.line_width, self.style, - Qt.RoundCap, Qt.RoundJoin)) - painter.moveTo(self.p0) - painter.cubicTo(self.p1, self.p2, self.p3) - - def mousePressEvent(self, event): + def mousePressEvent(self, _event): # deselect any nodes for node in self.source_node.graph_item.nodes: node.setSelected(False) diff --git a/python/edge_grip.py b/python/edge_grip.py index 801a4dca0589ece7b02a0f91d7e4a9a22ee4166d..0edda1f5e9d13b52aa06ae293a261d8f382d627b 100644 --- a/python/edge_grip.py +++ b/python/edge_grip.py @@ -1,14 +1,7 @@ from PyQt5.QtCore import QPointF, QRectF -from PyQt5.QtCore import pyqtSlot from PyQt5.QtCore import Qt -from PyQt5.QtGui import (QBrush, QColor, QLinearGradient, QPainter, - QPainterPath, QPen, QPolygonF, QRadialGradient) -from PyQt5.QtGui import QTransform -from PyQt5.QtGui import QPolygonF -from PyQt5.QtGui import QFontMetrics -from PyQt5.QtGui import QFont -from PyQt5.QtGui import QCursor -from PyQt5.QtWidgets import (QGraphicsItem, QGraphicsView, QStyle) +from PyQt5.QtGui import QBrush, QColor, QPainterPath, QPen, QRadialGradient +from PyQt5.QtWidgets import QGraphicsItem from settings_manager import settings # EdgeGrip @@ -61,7 +54,7 @@ class EdgeGrip(QGraphicsItem): def clear_grip(self): self.setVisible(False) - + def type(self): return EdgeGrip.Type @@ -73,25 +66,25 @@ class EdgeGrip(QGraphicsItem): def shape(self): return self.grip_shape - def paint(self, painter, option, widget): + def paint(self, painter, _option, _widget): - # draw circle shadow - painter.setPen(Qt.NoPen) - painter.setBrush(QColor(Qt.darkGray)) - painter.drawPath(self.grip_shadow) + # draw circle shadow + painter.setPen(Qt.NoPen) + painter.setBrush(QColor(Qt.darkGray)) + painter.drawPath(self.grip_shadow) - # circle gradient color - if self.isSelected(): - self.gradient.setColorAt(0, self.color.lighter(170)) - self.gradient.setColorAt(1, self.color.darker(110)) - else: - self.gradient.setColorAt(0, self.color.lighter(140)) - self.gradient.setColorAt(1, self.color.darker(140)) + # circle gradient color + if self.isSelected(): + self.gradient.setColorAt(0, self.color.lighter(170)) + self.gradient.setColorAt(1, self.color.darker(110)) + else: + self.gradient.setColorAt(0, self.color.lighter(140)) + self.gradient.setColorAt(1, self.color.darker(140)) - # draw circle - painter.setBrush(QBrush(self.gradient)) - painter.setPen(QPen(QColor(Qt.red), 0)) - painter.drawPath(self.grip_shape) + # draw circle + painter.setBrush(QBrush(self.gradient)) + painter.setPen(QPen(QColor(Qt.red), 0)) + painter.drawPath(self.grip_shape) def itemChange(self, change, value): if change == QGraphicsItem.ItemPositionHasChanged: diff --git a/python/edge_point_placer.py b/python/edge_point_placer.py index 64950faea67ed8bf9d4ef0eac1410cf6c5a1ebf3..bdef52fa327f994e0adf053012ae05e6373aa3ba 100644 --- a/python/edge_point_placer.py +++ b/python/edge_point_placer.py @@ -11,7 +11,7 @@ def _place_points(edge, count): # edge to same node so identify a length higher than the node h = 200 + 2*scale * count - l = QLineF(QPointF(0,0), QPointF(h,0)) + l = QLineF(QPointF(0, 0), QPointF(h, 0)) # 0 degrees is at 3 o'clock, increasing clockwise l.setAngle(90+60) edge.cbp1 = p + l.p2() @@ -30,7 +30,7 @@ def _place_points(edge, count): # skew overlapping edges if count > 0: angle = QLineF(source_p, dest_p).angle() - l = QLineF(QPointF(0,0), QPointF(scale * count,0)) + l = QLineF(QPointF(0, 0), QPointF(scale * count, 0)) l.setAngle(angle+90) # bow depending on orientation of nodes @@ -42,8 +42,8 @@ def _place_points(edge, count): edge.cbp1 -= l.p2() edge.cbp2 -= l.p2() -"""Set Cubic Bezier points cbp1 and cbp2 for the edge if not already set.""" class EdgePointPlacer(): + """Set Cubic Bezier points cbp1 and cbp2 for the edge if not already set.""" def __init__(self): diff --git a/python/event_menu.py b/python/event_menu.py index 90ff6bcbe324121f9bc2aebafcc962840adc81f7..392a489ee137ad48f3dd9156146efd5787150a2d 100644 --- a/python/event_menu.py +++ b/python/event_menu.py @@ -2,13 +2,11 @@ from PyQt5.QtCore import QObject # for signal/slot support from PyQt5.QtCore import pyqtSlot from PyQt5.QtWidgets import QMenu from PyQt5.QtWidgets import QAction -from PyQt5.QtGui import QIcon from graph_collapse_helpers import collapse_below, uncollapse_below from graph_item import GraphItem -"""Provides event_menu.""" - class EventMenu(QObject): + """Provides event_menu.""" def __init__(self, graphs_manager, graph_list_widget, settings_manager): super(EventMenu, self).__init__() diff --git a/python/export_trace_manager.py b/python/export_trace_manager.py index 068de188fb0f19d789d0faedeb8833bfb6db2760..14849492ea42f173602a3ab688a493808fa7dcb7 100644 --- a/python/export_trace_manager.py +++ b/python/export_trace_manager.py @@ -1,7 +1,3 @@ -import os -from subprocess import Popen, PIPE -#import shutil -import json from PyQt5.QtCore import Qt from PyQt5.QtGui import QFont from PyQt5.QtGui import QFontMetrics @@ -9,20 +5,17 @@ from PyQt5.QtGui import QPixmap from PyQt5.QtGui import QPainter from PyQt5.QtGui import QColor, QLinearGradient, QBrush, QRadialGradient, QPen from PyQt5.QtWidgets import QStyleOptionViewItem -from graph_item import GraphItem from settings_manager import settings -"""export_trace_manager manages export of trace image files. -""" - # export trace as image file. Return (status) def export_trace(trace_filename, graph_item): + """export_trace_manager manages export of trace image files.""" # painting is much like GraphListItemDelegate.paint(). # also ref GraphMainView.drawBackground(). if graph_item == None: - return("Error: graph not available") + return "Error: graph not available" # font height and banner height font_height = QFontMetrics(QFont()).height() @@ -46,7 +39,7 @@ def export_trace(trace_filename, graph_item): gradient.setColorAt(0.5, c) gradient.setColorAt(1, c.darker(settings["graph_gradient"])) # zz NOTE: painter must go beyond. Why? Use this workaround. - fill_rect = bounding_rect.adjusted(0, 0, 100,100) + fill_rect = bounding_rect.adjusted(0, 0, 100, 100) painter.fillRect(fill_rect, QBrush(gradient)) # paint the graph index @@ -115,5 +108,5 @@ def export_trace(trace_filename, graph_item): except Exception as e: status = "Error exporting trace image file '%s': %s" % ( trace_filename, str(e)) - return (status) + return status diff --git a/python/graph_collapse_helpers.py b/python/graph_collapse_helpers.py index 606cfc035c204d8d6556332440238000134d63ad..4cb2396c6926c208c04b8231b959b25738d2bfa3 100644 --- a/python/graph_collapse_helpers.py +++ b/python/graph_collapse_helpers.py @@ -95,7 +95,7 @@ def collapsed_edges(graph_item): print("collapsed_edges.a") # make sure there are edges to work with - if not len(graph_item.edges): + if not graph_item.edges: return (list(), list()) # identify the existing collapsed source_node, dest_node edge pairs @@ -105,22 +105,25 @@ def collapsed_edges(graph_item): if edge.relation == "COLLAPSED_FOLLOWS": existing_edge_pairs.add((edge.source_node, edge.dest_node)) existing_edges[(edge.source_node, edge.dest_node)] = edge - print("identified existing edge %s to %s" % (edge.source_node.label, edge.dest_node.label)) + print("identified existing edge %s to %s" % ( + edge.source_node.label, edge.dest_node.label)) # calculate the set of source_node, dest_node edge pairs edge_pairs = set() for edge in graph_item.edges: if edge.relation == "FOLLOWS" and (edge.source_node.collapse or edge.dest_node.collapse): - print("will add COLLAPSED_FOLLOWS to bridge: %s to %s" % (edge.source_node.label, edge.dest_node.label)) + print("will add COLLAPSED_FOLLOWS to bridge: %s to %s" % ( + edge.source_node.label, edge.dest_node.label)) # source and dest nodes source_node_set = at_and_above_in_uncollapsed(edge.source_node) dest_node_set = at_and_above_in_uncollapsed(edge.dest_node) for source_node in source_node_set: - for dest_node in dest_node_set: - if source_node != dest_node: - print("adding %s to %s" % (source_node.label, dest_node.label)) - edge_pairs.add((source_node, dest_node)) + for dest_node in dest_node_set: + if source_node != dest_node: + print("adding %s to %s" % ( + source_node.label, dest_node.label)) + edge_pairs.add((source_node, dest_node)) # identify edges that need removed removable_edges = list() diff --git a/python/graph_constants.py b/python/graph_constants.py index 1e62ce18e48877e9cb708a9020997c215a175b09..72c44fdb3a307b1ff63cdcdc5ef8247745f050af 100644 --- a/python/graph_constants.py +++ b/python/graph_constants.py @@ -1,5 +1,5 @@ from PyQt5.QtCore import QRectF # global constants and defaults -graphics_rect = QRectF(-50, -50, 25000, 25000) +GRAPHICS_RECT = QRectF(-50, -50, 25000, 25000) diff --git a/python/graph_item.py b/python/graph_item.py index eca5d0d0ff39f67cdb6eb49c4dc3815a847c4286..b7ee8eab1faa1defd19ff12fb4910d59af972ba7 100644 --- a/python/graph_item.py +++ b/python/graph_item.py @@ -1,21 +1,20 @@ from PyQt5.QtCore import QRect -from graph_constants import graphics_rect +from graph_constants import GRAPHICS_RECT from graph_collapse_helpers import collapsed_edges from edge import Edge from edge_point_placer import EdgePointPlacer -"""Defines one graph item. -Data structures: - * index (int) - * mark (str) - * nodes (list<Node>) - * edges (list<Edge>) - -NOTE: Optimization: call initialize_items and appearance just-in-time. -Set appearance when graph should be painted differently. -""" - class GraphItem(): + """Defines one graph item. + Data structures: + * index (int) + * mark (str) + * nodes (list<Node>) + * edges (list<Edge>) + + NOTE: Optimization: call initialize_items and appearance just-in-time. + Set appearance when graph should be painted differently. + """ def __init__(self, index, mark, probability, nodes, edges): super(GraphItem, self).__init__() @@ -54,7 +53,7 @@ class GraphItem(): for node in self.nodes: x = (node.x() - old_indent) / old_spacing * new_spacing + new_indent node.setPos(x, node.y()) - + def change_v_spacing(self, old_spacing, old_indent, new_spacing, new_indent): for node in self.nodes: @@ -67,19 +66,19 @@ class GraphItem(): self.set_appearance() # find the corners of this graph - min_x = graphics_rect.x() + graphics_rect.width() - max_x = graphics_rect.x() - min_y = graphics_rect.y() + graphics_rect.height() - max_y = graphics_rect.y() + min_x = GRAPHICS_RECT.x() + GRAPHICS_RECT.width() + max_x = GRAPHICS_RECT.x() + min_y = GRAPHICS_RECT.y() + GRAPHICS_RECT.height() + max_y = GRAPHICS_RECT.y() for node in self.nodes: - if node.x() - node.w/2 < min_x: - min_x = node.x() - node.w/2 - if node.x() + node.w/2 > max_x: - max_x = node.x() + node.w/2 - if node.y() - node.h/2 < min_y: - min_y = node.y() - node.h/2 - if node.y() + node.h/2 > max_y: - max_y = node.y() + node.h/2 + if node.x() - node.w/2 < min_x: + min_x = node.x() - node.w/2 + if node.x() + node.w/2 > max_x: + max_x = node.x() + node.w/2 + if node.y() - node.h/2 < min_y: + min_y = node.y() - node.h/2 + if node.y() + node.h/2 > max_y: + max_y = node.y() + node.h/2 return QRect(min_x, min_y, max_x-min_x, max_y-min_y) @@ -94,7 +93,7 @@ class GraphItem(): # diagnostics def _print_totals(self): - if not len(self.edges): + if not self.edges: print("Totals: None, empty graph.") return @@ -110,7 +109,10 @@ class GraphItem(): for node in self.nodes: edge_list_total += len(node.edge_list) - print("Totals: scene: %d, graph: %d, graph nodes: %d, graph edges: %d, edge list total: %d" % (scene_item_total, graph_item_total, len(self.nodes), len(self.edges), edge_list_total)) + print("Totals: scene: %d, graph: %d, graph nodes: %d, " + "graph edges: %d, edge list total: %d" % ( + scene_item_total, graph_item_total, + len(self.nodes), len(self.edges), edge_list_total)) # redo the list of collapsed edges in edges[] based on what is collapsed def set_collapsed_edges(self): diff --git a/python/graph_list_column.py b/python/graph_list_column.py index 07c8ba07f29b4fb07e9e2e19d5b36db8d845554a..f71c757d208027da9481c9c63787311abf2d267c 100644 --- a/python/graph_list_column.py +++ b/python/graph_list_column.py @@ -4,8 +4,7 @@ from PyQt5.QtWidgets import QVBoxLayout from PyQt5.QtWidgets import QSplitter class GraphListColumn(QWidget): - """Container for trace navigation and graph list - """ + """Container for trace navigation and graph list""" def __init__(self, gui_manager): super(GraphListColumn, self).__init__() diff --git a/python/graph_list_widget.py b/python/graph_list_widget.py index e61fa24220347141e4a8c3481ba34ab746df83e2..ecd7f49fa270f49e5e25a114a23b9d17e73c219e 100644 --- a/python/graph_list_widget.py +++ b/python/graph_list_widget.py @@ -1,49 +1,29 @@ from PyQt5.QtCore import QObject # for signal/slot support - from PyQt5.QtGui import QColor from PyQt5.QtGui import QRadialGradient from PyQt5.QtGui import QBrush from PyQt5.QtGui import QPen -from PyQt5.QtGui import QTransform from PyQt5.QtGui import QFontMetrics from PyQt5.QtGui import QFont from PyQt5.QtCore import pyqtSignal from PyQt5.QtCore import pyqtSlot from PyQt5.QtCore import Qt -from PyQt5.QtCore import QRectF, QRect from PyQt5.QtCore import QAbstractListModel -from PyQt5.QtCore import QSortFilterProxyModel +#from PyQt5.QtCore import QSortFilterProxyModel from PyQt5.QtCore import QVariant -#from PyQt5.QtCore import QModelIndex from PyQt5.QtCore import QSize - from PyQt5.QtWidgets import QListView from PyQt5.QtWidgets import QStyledItemDelegate -from PyQt5.QtWidgets import QListWidgetItem from PyQt5.QtWidgets import QStyle from graph_item import GraphItem -from graph_constants import graphics_rect - -"""GraphListWidget provides the graph list. It wraps details of parts, -ref. http://doc.qt.io/qt-5/model-view-programming.html. -Parts: - * GraphListModel Provides graphs from graph_manager -zz FUTURE * GraphProxyModel Sits between model and view to provide sort and - filter functions - * GraphListView Manages view - * GraphListItemDelegate Provides the paint function for each graph list item - -Signals: - * signal_graph_selection_changed(GraphItem) -""" # GraphListModel -"""Links the QAbstractListModel to the graph_manager's list subscript. -Listens to graphs_manager graph changed event to replace the data.""" -# ref. https://stackoverflow.com/questions/17231184/insert-and-remove-items-from-a-qabstractlistmodel class GraphListModel(QAbstractListModel): + """Links the QAbstractListModel to the graph_manager's list subscript. + Listens to graphs_manager graph changed event to replace the data.""" + # ref. https://stackoverflow.com/questions/17231184/insert-and-remove-items-from-a-qabstractlistmodel def __init__(self, graphs_manager): super(GraphListModel, self).__init__() @@ -84,12 +64,12 @@ class GraphListView(QListView): self.changed_width) # user selection changed - def currentChanged(self, current, previous): + def currentChanged(self, current, _previous): self.graph_list_widget.selection_changed(current.row()) # width changed @pyqtSlot(int) - def changed_width(self, width): + def changed_width(self, _width): # no: self.update() # hack from https://stackoverflow.com/questions/16444558/how-to-force-qabstractitemview-recalculate-items-sizehints hack_size = self.viewport().size() @@ -152,7 +132,7 @@ class GraphListItemDelegate(QStyledItemDelegate): else: background_color = QColor(Qt.blue).lighter(192) - painter.fillRect(option.rect.adjusted(0,0,0,-1), background_color) + painter.fillRect(option.rect.adjusted(0, 0, 0, -1), background_color) # translate axis to cell painter.translate(option.rect.x(), option.rect.y()) @@ -223,7 +203,7 @@ class GraphListItemDelegate(QStyledItemDelegate): painter.restore() # sizeHint - def sizeHint(self, option, model_index): + def sizeHint(self, _option, _model_index): # Make width hint narrow to prevent horizontal scrollbar. # Use a small width hint. It needs to be smaller than list width # minus scrollbar width minus widget padding, which might be four. @@ -254,7 +234,7 @@ class GraphListItemDelegate(QStyledItemDelegate): # self.sort(0) # list so sort column 0 # # def lessThan(self, left, right): # QModelIndex -# print("lessThan %s %d %d" % (self.sort_mode, +# print("lessThan %s %d %d" % (self.sort_mode, # self.graphs_manager.graphs[left.row()].index, # self.graphs_manager.graphs[right.row()].index)) # @@ -274,6 +254,19 @@ class GraphListItemDelegate(QStyledItemDelegate): # self.graphs_manager.graphs[right.row()].index class GraphListWidget(QObject): + """GraphListWidget provides the graph list. It wraps details of parts, + ref. http://doc.qt.io/qt-5/model-view-programming.html. + + Parts: + * GraphListModel Provides graphs from graph_manager + zz FUTURE * GraphProxyModel Sits between model and view to provide sort and + filter functions + * GraphListView Manages view + * GraphListItemDelegate Provides the paint function for each graph list item + + Signals: + * signal_graph_selection_changed(GraphItem) +""" # signals signal_graph_selection_changed = pyqtSignal(GraphItem, @@ -296,7 +289,7 @@ class GraphListWidget(QObject): # view calls this when the user selection changed def selection_changed(self, graph_subscript): - if (graph_subscript == -1): + if graph_subscript == -1: # nothing selected return @@ -310,17 +303,16 @@ class GraphListWidget(QObject): self.graphs_manager.graphs.index(graph)) self.view.setCurrentIndex(list_model_index) return True - else: - return False + return False # get the graph that is selected in the model else None def selected_graph(self): graph_subscript = self.view.currentIndex().row() if graph_subscript == -1: return None - else: - # graph_index associated with the selected data model index - return self.graphs_manager.graphs[graph_subscript] + + # use graph_index associated with the selected data model index + return self.graphs_manager.graphs[graph_subscript] # signal this to signal when the graph item needs repainted in the list @pyqtSlot(GraphItem) diff --git a/python/graph_list_width_manager.py b/python/graph_list_width_manager.py index 7f40cd0fdb985c959313902365aa62560d6d2bbd..35dcd64f3c3cfaf7d1d4b53a64d952b919b8eaa0 100644 --- a/python/graph_list_width_manager.py +++ b/python/graph_list_width_manager.py @@ -1,17 +1,15 @@ from PyQt5.QtCore import QObject # for signal/slot support from PyQt5.QtCore import pyqtSignal # for signal/slot support -from PyQt5.QtCore import pyqtSlot # for signal/slot support -"""Track graph list width and signal change - -Data: - * width - width of the graph list available in the list splitpane +class GraphListWidthManager(QObject): + """Track graph list width and signal change -Signals: - * signal_graph_list_width_changed(int) - width of graph list changed -""" + Data: + * width - width of the graph list available in the list splitpane -class GraphListWidthManager(QObject): + Signals: + * signal_graph_list_width_changed(int) - width of graph list changed + """ # signals signal_graph_list_width_changed = pyqtSignal(int, diff --git a/python/graph_main_widget.py b/python/graph_main_widget.py index cdc87d1bf122b9b84a4fc661033385ec6ab249b3..94ae1bf95bb5212da98c71f398c2cef92f40e2f8 100644 --- a/python/graph_main_widget.py +++ b/python/graph_main_widget.py @@ -9,35 +9,33 @@ # Adapted from https://raw.githubusercontent.com/baoboa/pyqt5/master/examples/graphicsview/elasticnodes.py import math -from PyQt5.QtCore import (QLineF, QPointF, qrand, QRect, QRectF, QSizeF, Qt) -from PyQt5.QtCore import QPoint +from PyQt5.QtCore import QRectF, Qt from PyQt5.QtCore import pyqtSignal from PyQt5.QtCore import pyqtSlot from PyQt5.QtCore import QObject -from PyQt5.QtGui import (QBrush, QColor, QLinearGradient, QPainter, - QPainterPath, QPen, QPolygonF, QRadialGradient) +from PyQt5.QtGui import QBrush, QColor, QLinearGradient, QPainter, QPen from PyQt5.QtGui import QTransform -from PyQt5.QtWidgets import (QGraphicsItem, QGraphicsView, QStyle) +from PyQt5.QtWidgets import QGraphicsView from PyQt5.QtWidgets import QGraphicsScene -from graph_constants import graphics_rect +from graph_constants import GRAPHICS_RECT from graph_item import GraphItem from settings_manager import settings from graph_collapse_helpers import collapse_below, uncollapse_below #from node import Node # for Node detection from edge_grip import EdgeGrip -"""GraphMainWidget provides the main QGraphicsView. It manages signals -and wraps these: - * GraphMainView - * GraphMainScene - -GraphMainWidget also issues signal: - * signal_graph_item_view_changed = pyqtSignal(GraphItem, - name='graphItemViewChanged') -""" - # GraphicsView class GraphMainView(QGraphicsView): + """GraphMainWidget provides the main QGraphicsView. It manages signals + and wraps these: + * GraphMainView + * GraphMainScene + + GraphMainWidget also issues signal: + * signal_graph_item_view_changed = pyqtSignal(GraphItem, + name='graphItemViewChanged') + """ + def __init__(self): super(GraphMainView, self).__init__() @@ -49,7 +47,7 @@ class GraphMainView(QGraphicsView): # enable user rectangular selection self.setDragMode(QGraphicsView.RubberBandDrag) self.viewport().setCursor(self.viewport_cursor) - self.setSceneRect(graphics_rect) + self.setSceneRect(GRAPHICS_RECT) self.setMinimumSize(300, 300) # Manage restorable scaling using scale_value, _rescale, @@ -82,7 +80,7 @@ class GraphMainView(QGraphicsView): # poll specific view orientation # return scale, x_slider, y_slider def get_view_orientation(self): - return (self._scale_value, + return (self._scale_value, self.horizontalScrollBar().value(), self.verticalScrollBar().value()) @@ -280,7 +278,7 @@ class GraphMainScene(QGraphicsScene): # if down click happens and not over grip then turn off grips g1 = self.source_edge_grip g2 = self.dest_edge_grip - if not ((g1.isUnderMouse() and g1.isVisible()) or + if not ((g1.isUnderMouse() and g1.isVisible()) or (g2.isUnderMouse() and g2.isVisible())): self.clear_grips() super(GraphMainScene, self).mousePressEvent(event) @@ -328,7 +326,7 @@ class GraphMainWidget(QObject): # call this to emit indication that the main view geometry changed somehow @pyqtSlot('QList<QRectF>') - def changed_main_view(self, region): + def changed_main_view(self, _region): if self.scene.graph_item: self.signal_graph_item_view_changed.emit(self.scene.graph_item) diff --git a/python/graph_status_label.py b/python/graph_status_label.py index 77ae879e9166a9dc40ea08f218e389a057244b0c..3b55859bc091699e4c5d43d402edc4e178f4843c 100644 --- a/python/graph_status_label.py +++ b/python/graph_status_label.py @@ -9,8 +9,8 @@ from PyQt5.QtCore import QObject # for signal/slot support from PyQt5.QtCore import pyqtSlot from PyQt5.QtWidgets import QLabel -"""Provides status text for graph traces in a QLabel.""" class GraphStatusLabel(QObject): + """Provides status text for graph traces in a QLabel.""" def __init__(self, graphs_manager): super(GraphStatusLabel, self).__init__() diff --git a/python/graphs_manager.py b/python/graphs_manager.py index e6177dd71ebc97ee39abe4c3ef9693ec157627f9..63ca2eb6cbed16212f190b47fb835e366cb4e8da 100644 --- a/python/graphs_manager.py +++ b/python/graphs_manager.py @@ -1,23 +1,22 @@ from PyQt5.QtCore import QObject # for signal/slot support from PyQt5.QtCore import pyqtSignal # for signal/slot support from PyQt5.QtCore import pyqtSlot # for signal/slot support -from PyQt5.QtCore import QPointF # for node location float -"""Provides graphs (list<GraphItem>) and signals when the graph list -is loaded or cleared. -Register with signal_graphs_loaded to provide current graph list. +class GraphsManager(QObject): + """Provides graphs (list<GraphItem>) and signals when the graph list + is loaded or cleared. -Data structures: - * schema_name (str) - * scope (int) - * graphs (list<GraphItem>) + Register with signal_graphs_loaded to provide current graph list. -Signals: - * signal_graphs_loaded() - graphs were loaded or cleared - * signal_appearance_changed() - graph appearance changed -""" + Data structures: + * schema_name (str) + * scope (int) + * graphs (list<GraphItem>) -class GraphsManager(QObject): + Signals: + * signal_graphs_loaded() - graphs were loaded or cleared + * signal_appearance_changed() - graph appearance changed + """ # signals signal_graphs_loaded = pyqtSignal(name='graphsLoaded') @@ -29,7 +28,7 @@ class GraphsManager(QObject): # set initial state self.clear_graphs() - # accept graph changes and signal appearance changed + # accept graph changes and signal appearance changed settings_manager.signal_settings_changed.connect( self.appearance_changed) diff --git a/python/gui_manager.py b/python/gui_manager.py index e231a779643758c5cb2f6d62593f39d02f2d8007..63396055f141675aaf8226f684d972941b9ff3b1 100644 --- a/python/gui_manager.py +++ b/python/gui_manager.py @@ -5,20 +5,17 @@ # Added graph_list_column and trace_navigation classes # -from PyQt5.QtWidgets import QMainWindow, QAction +import os +import webbrowser +from PyQt5.QtWidgets import QAction from PyQt5.QtWidgets import QFileDialog -from PyQt5.QtWidgets import QSizePolicy from PyQt5.QtWidgets import QStyle # for PM_ScrollBarExtent -from PyQt5.QtWidgets import QActionGroup from PyQt5.QtWidgets import qApp from PyQt5.QtWidgets import QLabel from PyQt5.QtGui import QIcon from PyQt5.QtCore import Qt -from PyQt5.QtCore import QUrl from PyQt5.QtCore import QObject from PyQt5.QtCore import pyqtSlot -import os -import webbrowser from version_file import VERSION from main_splitter import MainSplitter from mp_code_column import MPCodeColumn @@ -33,27 +30,26 @@ from graphs_manager import GraphsManager from mp_code_editor import MPCodeEditor from logger import Logger import mp_code_io_manager -from mp_code_syntax_checker import parse_error_line_number +from mp_code_syntax_checker import parse_error_line_number, MPCodeSyntaxChecker from trace_generator_manager import TraceGeneratorManager import export_trace_manager -from settings_manager import SettingsManager, settings_themes +from settings_manager import SettingsManager, SETTINGS_THEMES from settings_dialog_wrapper import SettingsDialogWrapper from keyboard_dialog_wrapper import KeyboardDialogWrapper from about_mp_dialog_wrapper import AboutMPDialogWrapper from event_menu import EventMenu -from mp_code_syntax_checker import MPCodeSyntaxChecker from path_constants import MP_CODE_EXAMPLES -"""MP main window containing menu, toolbar, statusbar, and central widget. -Central widget contains split pane areas, ref. http://firebird.nps.edu/: - code_area - console_area - graph_area - graph_list_area -""" - class GUIManager(QObject): + """MP main window containing menu, toolbar, statusbar, and central widget. + Central widget contains split pane areas, ref. http://firebird.nps.edu/: + code_area + console_area + graph_area + graph_list_area + """ + def __init__(self, main_window): super(GUIManager, self).__init__() @@ -80,7 +76,7 @@ class GUIManager(QObject): self.w = main_window # main window decoration - self.w.setGeometry(0,0,850,800) + self.w.setGeometry(0, 0, 850, 800) self.w.setWindowTitle("Monterey Phoenix v4 - Gryphon GUI %s"%VERSION) self.w.setWindowIcon(QIcon('icons/MP-logo-small-blue.png')) @@ -146,13 +142,13 @@ class GUIManager(QObject): # the central widget containing the main split pane self.mp_code_column = MPCodeColumn(self) - self.mp_code_column.set_sizes([600,200]) + self.mp_code_column.set_sizes([600, 200]) self.graph_list_column = GraphListColumn(self) - self.graph_list_column.set_sizes([50,600]) + self.graph_list_column.set_sizes([50, 600]) main_splitter.addWidget(self.mp_code_column) main_splitter.addWidget(self.graph_main_widget.view) main_splitter.addWidget(self.graph_list_column) - main_splitter.setSizes([250,500,100]) + main_splitter.setSizes([250, 500, 100]) self.w.setCentralWidget(main_splitter) # clean shutdown @@ -295,7 +291,7 @@ class GUIManager(QObject): os.path.join(MP_CODE_EXAMPLES, filename): self.open_mp_code(filename)) self.open_examples_menu.addAction(action) - + # menu | file | save MP Code file_menu.addAction(self.action_save_mp_code_file) @@ -345,7 +341,7 @@ class GUIManager(QObject): settings_menu = preferences_menu.addMenu("Settings") # menu | preferences | settings options - for theme_name, theme in settings_themes: + for theme_name, theme in SETTINGS_THEMES: # add action with dedicated lambda function containing filename action = QAction(theme_name, self) @@ -415,7 +411,7 @@ class GUIManager(QObject): if filename: # remember the preferred path - head, tail = os.path.split(filename) + head, _tail = os.path.split(filename) self.preferred_mp_code_dir = head # open the file @@ -532,8 +528,7 @@ class GUIManager(QObject): @pyqtSlot() def select_and_export_all_traces(self): - graph = self.graph_list_widget.selected_graph() - if not len(self.graphs_manager.graphs): + if not self.graphs_manager.graphs: self.logger.log("Error exporting trace: There are no traces") return @@ -659,7 +654,7 @@ class GUIManager(QObject): # select graph at first row # ref. https://stackoverflow.com/questions/6925951/how-to-select-a-row-in-a-qlistview - if len(graphs) > 0: + if graphs: self.graph_list_widget.select_graph(graphs[0]) # set visual state diff --git a/python/keyboard_dialog_wrapper.py b/python/keyboard_dialog_wrapper.py index 7b8b6118f0e35f92e4e2e2be974d37c04cfc9c54..f7dae1113e16001c9721c4ada0d9bb20f1f22ab1 100644 --- a/python/keyboard_dialog_wrapper.py +++ b/python/keyboard_dialog_wrapper.py @@ -1,10 +1,5 @@ # wrapper from https://stackoverflow.com/questions/2398800/linking-a-qtdesigner-ui-file-to-python-pyqt -import webbrowser -from PyQt5.QtCore import QObject # for signal/slot support -from PyQt5.QtCore import pyqtSlot # for signal/slot support -from PyQt5.QtCore import QRegExp from PyQt5.QtWidgets import QDialog -from PyQt5.QtGui import QRegExpValidator from keyboard_dialog import Ui_KeyboardDialog class KeyboardDialogWrapper(QDialog): diff --git a/python/logger.py b/python/logger.py index e6713e31300463346a4a491a634eab35d994bc17..e13a571e313aa2e5a03a1a67130aac367168e807 100644 --- a/python/logger.py +++ b/python/logger.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python3 - from PyQt5.QtWidgets import QPlainTextEdit -from PyQt5.QtCore import pyqtSlot -from PyQt5.QtCore import QObject -"""Log to log_pane and possibly also to status bar""" class Logger(): + """Log to log_pane and possibly also to status bar""" def __init__(self, statusbar): self.statusbar = statusbar self.log_pane = QPlainTextEdit() diff --git a/python/main_splitter.py b/python/main_splitter.py index 0705473161be68bb8c4826bf72fc371107ffa85d..d187be29730fa50748879e83c48d78c5f52b7177 100644 --- a/python/main_splitter.py +++ b/python/main_splitter.py @@ -2,13 +2,12 @@ from PyQt5.QtCore import pyqtSignal # for signal/slot support from PyQt5.QtCore import pyqtSlot # for signal/slot support from PyQt5.QtWidgets import QSplitter -"""The main splitter containing the graphs list, main graph, code_column. - -Signals: - * signal_graph_list_width_changed(int) - width of graph list changed -""" - class MainSplitter(QSplitter): + """The main splitter containing the graphs list, main graph, code_column. + + Signals: + * signal_graph_list_width_changed(int) - width of graph list changed + """ # signals signal_graph_list_width_changed = pyqtSignal(int, @@ -26,6 +25,6 @@ class MainSplitter(QSplitter): # QSplitter splitter bar moved @pyqtSlot(int, int) - def splitter_bar_moved(self, pos, index): + def splitter_bar_moved(self, _pos, _index): self.graph_list_width_manager.set_width(self.sizes()[2]) diff --git a/python/message_popup.py b/python/message_popup.py index d2a5416d7b96d12c4f0e9c7202d0601578615050..98dadc9b3acad510d67d6470bc108a6d1188de95 100644 --- a/python/message_popup.py +++ b/python/message_popup.py @@ -1,9 +1,7 @@ from PyQt5.QtWidgets import QMessageBox -"""Simple message box for simple popup warnings. -""" - -def message_popup(parent, message): +def message_popup(_parent, message): + """Simple message box for simple popup warnings.""" mb = QMessageBox() mb.setText(message) mb.exec() diff --git a/python/mp.py b/python/mp.py index 8d2a2c608503b5092a2b31d00089065dbcdec2aa..76063e352ae8344f59c95c8ad4d4cf91337eac1a 100755 --- a/python/mp.py +++ b/python/mp.py @@ -1,20 +1,17 @@ #!/usr/bin/env python3 -from argparse import ArgumentParser -import os -from os.path import exists, isdir -from os import mkdir +from os.path import exists import sys from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QMainWindow from gui_manager import GUIManager -from path_constants import RIGAL_ROOT, RIGAL_RC, RIGAL_SCRATCH, \ +from path_constants import RIGAL_ROOT, RIGAL_RC, RIGAL_BIN, \ MP_CODE_DEFAULT_EXAMPLE, make_rigal_scratch from message_popup import message_popup # main -if __name__=="__main__": +if __name__ == "__main__": # create the "application" and the main window application = QApplication(sys.argv) @@ -27,7 +24,7 @@ if __name__=="__main__": # bad setup if exists(RIGAL_ROOT): error = "Trace Generator compiler is not available at %s." \ - " Is it built? Aborting."% RIGAL_SC + " Is it built? Aborting."% RIGAL_BIN else: error = "Trace Generator is not available at %s." \ " Is it installed? Aborting."% RIGAL_ROOT diff --git a/python/mp_code_cursor_highlighter.py b/python/mp_code_cursor_highlighter.py index 6cd383456dbf17067f58b9122be250c6d2d8725e..36f4c34eda8fb8d6bebd10bbdf6eb928b47934f0 100644 --- a/python/mp_code_cursor_highlighter.py +++ b/python/mp_code_cursor_highlighter.py @@ -7,11 +7,12 @@ from PyQt5.QtCore import QObject from PyQt5.QtCore import pyqtSlot from PyQt5.QtCore import QRegularExpression -from PyQt5.QtGui import QColor, QFont, QBrush +from PyQt5.QtGui import QColor from PyQt5.QtGui import QTextCursor from PyQt5.QtGui import QTextDocument from PyQt5.QtWidgets import QTextEdit - +from mp_code_expressions import PAREN_EXPRESSION, BRACE_EXPRESSION, \ + BRACKET_EXPRESSION, IF_EXPRESSION, DO_EXPRESSION class MPCodeCursorHighlighter(QObject): def __init__(self, code_editor): @@ -28,17 +29,6 @@ class MPCodeCursorHighlighter(QObject): code_editor.cursorPositionChanged.connect(self.highlight_extra) code_editor.textChanged.connect(self.highlight_extra) - # regular expressions - self.paren_expression = QRegularExpression("[\(\)]") # "(" or ")" - self.brace_expression = QRegularExpression("[\{\}]") # "{" or "}" - self.bracket_expression = QRegularExpression("[\[\]]") # "[" or "]" - self.if_expression = QRegularExpression("\\b(IF|FI)\\b") # IF or FI - self.do_expression = QRegularExpression("\\b(DO|OD)\\b") # DO or OD - if not self.paren_expression: raise Exception("Bad") - if not self.brace_expression: raise Exception("Bad") - if not self.if_expression: raise Exception("Bad") - if not self.do_expression: raise Exception("Bad") - # any selected word self.selected_word = "" @@ -183,36 +173,36 @@ class MPCodeCursorHighlighter(QObject): if char == "(": cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) - self._highlight_pair_forward(self.paren_expression, cursor) + self._highlight_pair_forward(PAREN_EXPRESSION, cursor) # { elif char == "{": cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) - self._highlight_pair_forward(self.brace_expression, cursor) + self._highlight_pair_forward(BRACE_EXPRESSION, cursor) elif char == "[": cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) - self._highlight_pair_forward(self.bracket_expression, cursor) + self._highlight_pair_forward(BRACKET_EXPRESSION, cursor) # ) elif char == ")": cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) - self._highlight_pair_backward(self.paren_expression, cursor) + self._highlight_pair_backward(PAREN_EXPRESSION, cursor) # } elif char == "}": cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) - self._highlight_pair_backward(self.brace_expression, cursor) + self._highlight_pair_backward(BRACE_EXPRESSION, cursor) # ] elif char == "]": cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) - self._highlight_pair_backward(self.bracket_expression, cursor) + self._highlight_pair_backward(BRACKET_EXPRESSION, cursor) # evaluate word else: @@ -225,13 +215,13 @@ class MPCodeCursorHighlighter(QObject): # IF, FI, DO, OD if word == "IF": - self._highlight_pair_forward(self.if_expression, cursor) + self._highlight_pair_forward(IF_EXPRESSION, cursor) elif word == "FI": - self._highlight_pair_backward(self.if_expression, cursor) + self._highlight_pair_backward(IF_EXPRESSION, cursor) elif word == "DO": - self._highlight_pair_forward(self.do_expression, cursor) + self._highlight_pair_forward(DO_EXPRESSION, cursor) elif word == "OD": - self._highlight_pair_backward(self.do_expression, cursor) + self._highlight_pair_backward(DO_EXPRESSION, cursor) else: # not an IF or DO block pass @@ -273,7 +263,7 @@ class MPCodeCursorHighlighter(QObject): self.selected_word = cursor.selectedText() # disallow word if it starts with potential regex control codes - if len(self.selected_word) and self.selected_word[0] != "_" and \ + if self.selected_word and self.selected_word[0] != "_" and \ not self.selected_word[0].isalnum(): self.selected_word = "" diff --git a/python/mp_code_dynamic_tokens.py b/python/mp_code_dynamic_tokens.py index 2a353a3431e1f1c411c9fead62120e5736e4f0b8..dd97408238eb1530d5b958ffa5ac5f0626ebb974 100644 --- a/python/mp_code_dynamic_tokens.py +++ b/python/mp_code_dynamic_tokens.py @@ -1,20 +1,6 @@ -"""Identify dynamically defined tokens of type: {root, atomic, composite}. -""" - -from PyQt5.QtCore import QRegularExpression - -# comments -comment_start_expression = QRegularExpression("/\\*") -comment_end_expression = QRegularExpression("\\*/") - -# root names -root_name_expression = QRegularExpression(r"^\s*ROOT\s*(\w*)\s*:") - -# composite names -composite_name_expression = QRegularExpression(r"^\s*(\w*)\s*:") - -# schema names -schema_name_expression = QRegularExpression(r"^\s*SCHEMA\s*(\w*)") +from mp_code_expressions import COMMENT_START_EXPRESSION, \ + COMMENT_END_EXPRESSION, ROOT_NAME_EXPRESSION, \ + COMPOSITE_NAME_EXPRESSION, SCHEMA_NAME_EXPRESSION # scan provided line # NOTE: this does not correctly parse across lines, but dynamic tokens @@ -22,24 +8,28 @@ schema_name_expression = QRegularExpression(r"^\s*SCHEMA\s*(\w*)") def _scan_line(text, tokens): # ROOT - match = root_name_expression.match(text) + match = ROOT_NAME_EXPRESSION.match(text) if match.hasMatch(): tokens.append(("root", match.captured(1))) else: # SCHEMA - match = schema_name_expression.match(text) + match = SCHEMA_NAME_EXPRESSION.match(text) if match.hasMatch(): tokens.append(("schema", match.captured(1))) else: # composite - match = composite_name_expression.match(text) + match = COMPOSITE_NAME_EXPRESSION.match(text) if match.hasMatch(): tokens.append(("composite", match.captured(1))) # get dynamically defined tokens as list of tuple: (token_type, name) def dynamic_tokens(document): + """Identify dynamically defined tokens of type: {root, atomic, composite} + as list of tuple: (token_type, name) + """ + tokens = list() in_comment = False start_index = 0 @@ -50,11 +40,11 @@ def dynamic_tokens(document): # loop to end while text_block != document.end(): text = text_block.text() - + # handle multiline comment based on state of previous block if not in_comment: # not in comment so move start index forward - match = comment_start_expression.match(text) + match = COMMENT_START_EXPRESSION.match(text) start_index = match.capturedStart() if start_index == -1: @@ -70,19 +60,19 @@ def dynamic_tokens(document): # do not process text bisected by multiple comments # on a line. while start_index >= 0: - match = comment_end_expression.match(text, start_index) + match = COMMENT_END_EXPRESSION.match(text, start_index) start_index = match.capturedStart() if start_index == -1: in_comment = True else: - match = comment_start_expression.match(text, - start_index) + match = COMMENT_START_EXPRESSION.match(text, + start_index) start_index = match.capturedStart() in_comment = False else: # in comment so throw away line until not in comment - match = comment_end_expression.match(text) + match = COMMENT_END_EXPRESSION.match(text) start_index = match.capturedStart() if start_index == -1: @@ -95,12 +85,12 @@ def dynamic_tokens(document): # do not process text bisected by multiple comments # on a line. while start_index >= 0: - match = comment_start_expression.match(text, start_index) + match = COMMENT_START_EXPRESSION.match(text, start_index) start_index = match.capturedStart() if start_index == -1: in_comment = False else: - match = comment_end_expression.match(text, start_index) + match = COMMENT_END_EXPRESSION.match(text, start_index) start_index = match.capturedStart() in_comment = True diff --git a/python/mp_code_editor.py b/python/mp_code_editor.py index 8450959f4630281e359c524c14d5c2d1e3ebe258..3bc02f0032eee75cf9d9a82c0ca30f307c993ea9 100644 --- a/python/mp_code_editor.py +++ b/python/mp_code_editor.py @@ -9,16 +9,12 @@ # https://stackoverflow.com/questions/33243852/codeeditor-example-in-pyqt # http://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html -import sys -from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QWidget from PyQt5.QtWidgets import QPlainTextEdit -from PyQt5.QtWidgets import QTextEdit from PyQt5.QtWidgets import QCompleter from PyQt5.QtGui import QFont from PyQt5.QtGui import QFontMetrics from PyQt5.QtGui import QColor -from PyQt5.QtGui import QTextFormat from PyQt5.QtGui import QTextCursor from PyQt5.QtGui import QPainter from PyQt5.QtGui import QKeyEvent @@ -28,23 +24,12 @@ from PyQt5.QtCore import QRect from PyQt5.QtCore import pyqtSlot from PyQt5.QtCore import QObject from PyQt5.QtCore import QStringListModel -from PyQt5.QtCore import QRegularExpression from mp_code_highlighter import MPCodeHighlighter from mp_code_cursor_highlighter import MPCodeCursorHighlighter from os_compatibility import control_modifier - -_keywords = set() -for keyword in "ADD|AFTER|ALL|AND|BEFORE|BUILD|CHECK|CONTAINS|"\ - "COORDINATE|DISJ|DO|ELSE|ENCLOSING|ENSURE|EXISTS|FI|FOLLOWS|"\ - "FOREACH|FROM|IF|IN|IS|MAP|MARK|MAY_OVERLAP|NOT|OD|ON|ONFAIL|"\ - "OR|PRECEDES|REJECT|ROOT|SAY|SCHEMA|SHARE|SUCH|THAT|THEN|THIS|"\ - "WHEN|SORT|REVERSE|SHIFT_RIGHT|SHIFT_LEFT|CUR_FRONT|CUT_END|"\ - "FIRST|LAST".split("|"): - _keywords.add(keyword) +from mp_code_expressions import KEYWORD_SET, WORD_EXPRESSION def _refresh_word_list(code_editor, word_list_model): - # regex for word - word_expression = QRegularExpression("\\b\\w*") # get word at cursor cursor = code_editor.textCursor() @@ -54,12 +39,12 @@ def _refresh_word_list(code_editor, word_list_model): # move to front of document cursor.movePosition(QTextCursor.Start) - + # get words from the document words = set() document = code_editor.document() while True: - cursor = document.find(word_expression, cursor) + cursor = document.find(WORD_EXPRESSION, cursor) word = cursor.selectedText() if word: # add word to Set @@ -73,7 +58,7 @@ def _refresh_word_list(code_editor, word_list_model): words.discard(word_at_cursor) # add keywords to set - words.update(_keywords) + words.update(KEYWORD_SET) # put words into word list word_list = list() @@ -86,7 +71,6 @@ def _refresh_word_list(code_editor, word_list_model): # update the word list model word_list_model.setStringList(word_list) -"""MPCodeEditor""" class MPCodeEditor(QObject): def __init__(self, settings_manager, statusbar): super(MPCodeEditor, self).__init__() @@ -257,7 +241,7 @@ class CodeEditor(QPlainTextEdit): # abort completer under these conditions: if not is_shortcut: - if has_modifier or not text or len(completion_prefix)<3 or \ + if has_modifier or not text or len(completion_prefix) < 3 or \ is_invalid_key: self.completer.popup().hide() return @@ -266,12 +250,12 @@ class CodeEditor(QPlainTextEdit): self.completer.setCompletionPrefix(completion_prefix) _refresh_word_list(self, self.mp_code_string_list_model) self.completer.popup().setCurrentIndex( - self.completer.completionModel().index(0,0)) + self.completer.completionModel().index(0, 0)) # configure and open popup cursor_rectangle = self.cursorRect() cursor_rectangle.setWidth(self.completer.popup().sizeHintForColumn(0) - + self.completer.popup().verticalScrollBar().sizeHint().width()) + + self.completer.popup().verticalScrollBar().sizeHint().width()) self.completer.complete(cursor_rectangle) # intercept mouse press for cursor highlighting @@ -310,7 +294,7 @@ class CodeEditor(QPlainTextEdit): def resizeEvent(self, event): super().resizeEvent(event) - cr = self.contentsRect(); + cr = self.contentsRect() self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height())) diff --git a/python/mp_code_expressions.py b/python/mp_code_expressions.py new file mode 100644 index 0000000000000000000000000000000000000000..ccf91e6f65e487f14a77d399c9ad6bc550bcf2b8 --- /dev/null +++ b/python/mp_code_expressions.py @@ -0,0 +1,48 @@ +from PyQt5.QtCore import QRegularExpression +# root names +ROOT_NAME_EXPRESSION = QRegularExpression(r"^\s*ROOT\s*(\w*)\s*:") + +# composite names +COMPOSITE_NAME_EXPRESSION = QRegularExpression(r"^\s*(\w*)\s*:") + +# schema names +SCHEMA_NAME_EXPRESSION = QRegularExpression(r"^\s*SCHEMA\s*(\w*)") + + +# atomic names are all words +ATOMIC_NAME_EXPRESSION = QRegularExpression(r"\b\w*\b") + +# multiline comment expressions +COMMENT_START_EXPRESSION = QRegularExpression("/\\*") +COMMENT_END_EXPRESSION = QRegularExpression("\\*/") + +# keywords, ref. MP documentation +_KEYWORDS = "ADD|AFTER|ALL|AND|APPLY|AT|ATTRIBUTES|AVG|BEFORE|BUILD|CHAIN|CHECK|CONTAINS|COORDINATE|CUT_END|CUT_FRONT|DIAGRAM|DISJ|DO|ELSE|ENCLOSING|ENSURE|EXISTS|FI|FIRST|FOLLOWS|FOREACH|FROM|IF|IN|IS|LAST|LEAST|MAP|MARK|MAX|MAY_OVERLAP|MIN|NOT|OD|ON|ONFAIL|OR|PRECEDES|REJECT|REVERSE|ROOT|SAY|SCHEMA|SET|SHARE|SHIFT_LEFT|SHIFT_RIGHT|SORT|SUCH|SUM|THAT|THEN|THIS|TIMES|WHEN" +KEYWORD_SET = set(_KEYWORDS.split('|')) +KEYWORD_EXPRESSION = QRegularExpression(r'\b(%s)\b'%_KEYWORDS) + +# words +WORD_EXPRESSION = QRegularExpression("\\b\\w*") + +# meta-symbols +META_SYMBOL_EXPRESSION = QRegularExpression(r'\$\$(EVENT|ROOT|COMPOSITE|ATOM|scope)') + +# operators, any of: -+*=<>!(){}| +OPERATOR_EXPRESSION = QRegularExpression("[-+\/*=<>!\(\)\{\}\|]+") + +# numbers +NUMBER_EXPRESSION = QRegularExpression(r'[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?') + +# variables +VARIABLE_EXPRESSION = QRegularExpression("(<.*>)?\$[a-z][a-z0-9_]*") + +# Double-quoted string, possibly containing escape sequences +QUOTED_TEXT_EXPRESSION = QRegularExpression(r'"[^"\\]*(\\.[^"\\]*)*"') + +# brace, bracket IF, DO +PAREN_EXPRESSION = QRegularExpression("[\(\)]") # "(" or ")" +BRACE_EXPRESSION = QRegularExpression("[\{\}]") # "{" or "}" +BRACKET_EXPRESSION = QRegularExpression("[\[\]]") # "[" or "]" +IF_EXPRESSION = QRegularExpression("\\b(IF|FI)\\b") # IF or FI +DO_EXPRESSION = QRegularExpression("\\b(DO|OD)\\b") # DO or OD + diff --git a/python/mp_code_highlighter.py b/python/mp_code_highlighter.py index 282f61456d7cc1c22812902ca7cc2d1f153eeb20..2755527881110f3bdcbaa7c4c12741dab809a700 100644 --- a/python/mp_code_highlighter.py +++ b/python/mp_code_highlighter.py @@ -8,25 +8,20 @@ # Added APPLY to the list of keywords # -"""Manages MP Code syntax highlighting. -See http://doc.qt.io/qt-5/qtwidgets-richtext-syntaxhighlighter-example.html -Adapted from https://wiki.python.org/moin/PyQt/Python%20syntax%20highlighting -Also ref. https://github.com/baoboa/pyqt5/blob/master/examples/richtext/syntaxhighlighter.py - -For keywords: ref. MP documentation -""" - from PyQt5.QtCore import pyqtSlot from PyQt5.QtCore import QRegularExpression from PyQt5.QtGui import QColor, QTextCharFormat, QFont, QSyntaxHighlighter -from PyQt5.QtGui import QTextCursor -from mp_code_dynamic_tokens import dynamic_tokens from settings_manager import settings +from mp_code_dynamic_tokens import dynamic_tokens +from mp_code_expressions import ATOMIC_NAME_EXPRESSION, \ + COMMENT_START_EXPRESSION, COMMENT_END_EXPRESSION, \ + KEYWORD_EXPRESSION, META_SYMBOL_EXPRESSION, \ + OPERATOR_EXPRESSION, NUMBER_EXPRESSION, \ + VARIABLE_EXPRESSION, QUOTED_TEXT_EXPRESSION # color is a valid QT color, format is one of "b" for bold or "i" for italic def _text_char_format(color, style=''): - """Return a QTextCharFormat with the given attributes. - """ + """Return a QTextCharFormat with the given attributes.""" text_char_format = QTextCharFormat() text_char_format.setForeground(QColor(color)) if style == 'b': @@ -38,6 +33,11 @@ def _text_char_format(color, style=''): class MPCodeHighlighter(QSyntaxHighlighter): """Syntax highlighter for MP Code. + + Manages MP Code syntax highlighting. + See http://doc.qt.io/qt-5/qtwidgets-richtext-syntaxhighlighter-example.html + Adapted from https://wiki.python.org/moin/PyQt/Python%20syntax%20highlighting + Also ref. https://github.com/baoboa/pyqt5/blob/master/examples/richtext/syntaxhighlighter.py """ def __init__(self, document, settings_manager): @@ -57,42 +57,6 @@ class MPCodeHighlighter(QSyntaxHighlighter): # handle settings change event to get changed colors settings_manager.signal_settings_changed.connect(self.set_colors) - # atomic names are all words - self.atomic_name_expression = QRegularExpression(r"\b\w*\b") - - # multiline comment expressions - self.comment_start_expression = QRegularExpression("/\\*") - self.comment_end_expression = QRegularExpression("\\*/") - - # keywords - self.keyword_expression = QRegularExpression(r'\b(ADD|AFTER|ALL|AND|APPLY|AT|ATTRIBUTES|AVG|BEFORE|BUILD|CHAIN|CHECK|CONTAINS|COORDINATE|CUT_END|CUT_FRONT|DIAGRAM|DISJ|DO|ELSE|ENCLOSING|ENSURE|EXISTS|FI|FIRST|FOLLOWS|FOREACH|FROM|IF|IN|IS|LAST|LEAST|MAP|MARK|MAX|MAY_OVERLAP|MIN|NOT|OD|ON|ONFAIL|OR|PRECEDES|REJECT|REVERSE|ROOT|SAY|SCHEMA|SET|SHARE|SHIFT_LEFT|SHIFT_RIGHT|SORT|SUCH|SUM|THAT|THEN|THIS|TIMES|WHEN)\b') - - # meta-symbols - self.meta_symbol_expression = QRegularExpression(r'\$\$(EVENT|ROOT|COMPOSITE|ATOM|scope)') - - # operators, any of: -+*=<>!(){}| - self.operator_expression = QRegularExpression("[-+\/*=<>!\(\)\{\}\|]+") - - # numbers - self.number_expression = QRegularExpression(r'[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?') - - # variables - self.variable_expression = QRegularExpression("(<.*>)?\$[a-z][a-z0-9_]*") - - # Double-quoted string, possibly containing escape sequences - self.quoted_text_expression = QRegularExpression(r'"[^"\\]*(\\.[^"\\]*)*"') - - # validate these hardcoded regular expressions, remove if desired - if not self.atomic_name_expression: raise Exception("Bad") - if not self.comment_start_expression: raise Exception("Bad") - if not self.comment_end_expression: raise Exception("Bad") - if not self.keyword_expression: raise Exception("Bad") - if not self.meta_symbol_expression: raise Exception("Bad") - if not self.operator_expression: raise Exception("Bad") - if not self.number_expression: raise Exception("Bad") - if not self.variable_expression: raise Exception("Bad") - if not self.quoted_text_expression: raise Exception("Bad") - # set colors self.set_colors() @@ -100,7 +64,7 @@ class MPCodeHighlighter(QSyntaxHighlighter): # for numbers, don't re-format if the preceeding character # is a letter or underscore def _scan_numbers(self, color_format, text): - match_iterator = self.number_expression.globalMatch(text) + match_iterator = NUMBER_EXPRESSION.globalMatch(text) while match_iterator.hasNext(): match = match_iterator.next() capStart = match.capturedStart() @@ -130,7 +94,7 @@ class MPCodeHighlighter(QSyntaxHighlighter): def highlightBlock(self, text): # atomic: start by calling all words atomic events - self._scan(self.atomic_name_expression, self.atomic_name_format, text) + self._scan(ATOMIC_NAME_EXPRESSION, self.atomic_name_format, text) # composite, root, schema for name, regex in self.dynamic_highlighting_rules: @@ -144,22 +108,22 @@ class MPCodeHighlighter(QSyntaxHighlighter): print("Warning: unrecognized name '%s'"%name) # keywords - self._scan(self.keyword_expression, self.keyword_format, text) + self._scan(KEYWORD_EXPRESSION, self.keyword_format, text) # operators - self._scan(self.operator_expression, self.operator_format, text) + self._scan(OPERATOR_EXPRESSION, self.operator_format, text) # numbers self._scan_numbers(self.number_format, text) # variables - self._scan(self.variable_expression, self.variable_format, text) + self._scan(VARIABLE_EXPRESSION, self.variable_format, text) # meta-symbols - self._scan(self.meta_symbol_expression, self.meta_symbol_format, text) + self._scan(META_SYMBOL_EXPRESSION, self.meta_symbol_format, text) # quoted text which can override any previous tokens - self._scan(self.quoted_text_expression, self.quoted_text_format, text) + self._scan(QUOTED_TEXT_EXPRESSION, self.quoted_text_format, text) # now highlight any text in multiline comments # set initial state, 0=not in comment, 1=in comment @@ -169,11 +133,11 @@ class MPCodeHighlighter(QSyntaxHighlighter): # handle multiline comment based on state of previous block if self.previousBlockState() != 1: # move start index forward if not in comment - match = self.comment_start_expression.match(text) + match = COMMENT_START_EXPRESSION.match(text) start_index = match.capturedStart() # now process forward from start index while start_index >= 0: - match = self.comment_end_expression.match(text, start_index) + match = COMMENT_END_EXPRESSION.match(text, start_index) end_index = match.capturedStart() if end_index == -1: self.setCurrentBlockState(1) @@ -184,7 +148,7 @@ class MPCodeHighlighter(QSyntaxHighlighter): self.setFormat(start_index, comment_length, self.comment_format) # move to next start expression after current end expression - match = self.comment_start_expression.match(text, start_index+2) + match = COMMENT_START_EXPRESSION.match(text, start_index+2) start_index = match.capturedStart() # set all clors diff --git a/python/mp_code_io_manager.py b/python/mp_code_io_manager.py index 10645d57fda869de81d2cd80f780b9601bead5f8..7b3f20b846e20828929f6972772dd30ea9d401dc 100644 --- a/python/mp_code_io_manager.py +++ b/python/mp_code_io_manager.py @@ -7,8 +7,8 @@ import os from subprocess import Popen, PIPE -from PyQt5.QtCore import QPointF import json +from PyQt5.QtCore import QPointF from node import Node from edge import Edge from graph_item import GraphItem @@ -87,7 +87,7 @@ def read_generated_json(generated_json_text): if "GLOBAL" in generated_json: print("Implementation TBD for GLOBAL:%s"%generated_json["GLOBAL"]) - i=1 + i = 1 for trace in generated_json["traces"]: nodes = list() edges = list() @@ -141,7 +141,7 @@ def read_generated_json(generated_json_text): # items 5...n-1: the user-defined named relations for user_defined_json_edges in trace[5:]: - if type(user_defined_json_edges) ==list: + if isinstance(user_defined_json_edges, list): label = user_defined_json_edges[0] for json_edge in user_defined_json_edges[1:]: # source_id, relation, target_id, label @@ -149,7 +149,7 @@ def read_generated_json(generated_json_text): "USER_DEFINED", "%s"%json_edge[1], label, node_lookup)) - elif type(user_defined_json_edges) ==dict: + elif isinstance(user_defined_json_edges, dict): # VIEWS for key, value in user_defined_json_edges.items(): if value: @@ -297,5 +297,5 @@ def export_gry_file(gry_filename, mp_code_text, scope, except Exception as e: status = "Error exporting Gryphon file '%s': %s" % ( gry_filename, str(e)) - return (status) + return status diff --git a/python/mp_code_syntax_checker.py b/python/mp_code_syntax_checker.py index 9b2120a17c54192c0c279a401f23e120dca7f4d5..88bdb181fdd9139344b7efbd8fca9eb20c81bebb 100644 --- a/python/mp_code_syntax_checker.py +++ b/python/mp_code_syntax_checker.py @@ -1,6 +1,4 @@ -from PyQt5.QtWidgets import qApp from PyQt5.QtCore import QObject -from PyQt5.QtCore import QThread from PyQt5.QtCore import pyqtSignal, pyqtSlot # helper diff --git a/python/node.py b/python/node.py index 0b7a13805a5fa1ff4dbffe493c878fce5efdf1ea..8750eca24a622f61cbc77bd0b29055421949d908 100644 --- a/python/node.py +++ b/python/node.py @@ -1,18 +1,14 @@ # Adapted from https://raw.githubusercontent.com/baoboa/pyqt5/master/examples/graphicsview/elasticnodes.py from PyQt5.QtCore import QPointF, QRectF -from PyQt5.QtCore import pyqtSlot from PyQt5.QtCore import Qt -from PyQt5.QtGui import (QBrush, QColor, QLinearGradient, QPainter, +from PyQt5.QtGui import (QBrush, QColor, QLinearGradient, QPainterPath, QPen, QPolygonF, QRadialGradient) -from PyQt5.QtGui import QTransform -from PyQt5.QtGui import QPolygonF from PyQt5.QtGui import QFontMetrics from PyQt5.QtGui import QFont -from PyQt5.QtGui import QCursor -from PyQt5.QtWidgets import (QGraphicsItem, QGraphicsView, QStyle) +from PyQt5.QtWidgets import QGraphicsItem from PyQt5.QtWidgets import QGraphicsSceneContextMenuEvent -from graph_constants import graphics_rect +from graph_constants import GRAPHICS_RECT from settings_manager import settings from graph_collapse_helpers import at_and_below_in from node_menu import NodeMenu @@ -81,10 +77,10 @@ class Node(QGraphicsItem): self.mouse_path.addRect(-self.w/2, -self.h/2, self.w, self.h) # define the graphics view bounds for this node - self.min_x = graphics_rect.x() + self.w/2 - self.max_x = graphics_rect.width() + graphics_rect.x() - self.w/2 - self.min_y = graphics_rect.y() + self.h/2 - self.max_y = graphics_rect.height()+ graphics_rect.y() - self.h/2 + self.min_x = GRAPHICS_RECT.x() + self.w/2 + self.max_x = GRAPHICS_RECT.width() + GRAPHICS_RECT.x() - self.w/2 + self.min_y = GRAPHICS_RECT.y() + self.h/2 + self.max_y = GRAPHICS_RECT.height()+ GRAPHICS_RECT.y() - self.h/2 # define decoration for this node self.right_shadow = QRectF(self.w/2, -self.h/2+self.s, self.s, self.h) @@ -181,7 +177,7 @@ class Node(QGraphicsItem): def shape(self): return self.mouse_path - def paint(self, painter, option, widget): + def paint(self, painter, _option, _widget): # box gradient if self.isSelected(): diff --git a/python/node_menu.py b/python/node_menu.py index 64aba2c4dbc7c42fc9136d740e99026c88737f37..5208f2d44ae8477ac9a89957abc5905a68fd1ef7 100644 --- a/python/node_menu.py +++ b/python/node_menu.py @@ -1,5 +1,4 @@ from PyQt5.QtCore import QObject # for signal/slot support -from PyQt5.QtCore import pyqtSlot # for signal/slot support from PyQt5.QtWidgets import QMenu from PyQt5.QtWidgets import QAction from PyQt5.QtGui import QCursor @@ -53,7 +52,7 @@ class NodeMenu(QObject): self.action_hide.setEnabled(not node.hide) self.action_unhide.setEnabled(node.hide) - action = self.menu.exec(QCursor.pos()) + _action = self.menu.exec(QCursor.pos()) def do_collapse(self): collapse_below(self.node) diff --git a/python/path_constants.py b/python/path_constants.py index a34c27db1c8894727aab365ed65b762c765b346c..8cc7a3c50ec658491fd18e55daeea57849e346ca 100644 --- a/python/path_constants.py +++ b/python/path_constants.py @@ -1,6 +1,6 @@ +from os import mkdir from os.path import expanduser, normpath, abspath, isdir, join from glob import glob -from os import mkdir from shutil import copy2 # GUI @@ -10,8 +10,9 @@ TRACE_GENERATED_OUTFILE = join(RIGAL_SCRATCH, "_mp_trace_generator_output") # RIGAL RIGAL_ROOT = abspath(normpath("../../trace-generator")) -RIGAL_RC = join(RIGAL_ROOT, "RIGAL/rigsc.446/bin/rc") -RIGAL_IC = join(RIGAL_ROOT, "RIGAL/rigsc.446/bin/ic") +RIGAL_BIN = join(RIGAL_ROOT, "RIGAL", "rigsc.446", "bin") +RIGAL_RC = join(RIGAL_BIN, "rc") +RIGAL_IC = join(RIGAL_BIN, "ic") RIGAL_CODE = join(RIGAL_ROOT, "Code") # examples @@ -28,4 +29,4 @@ def make_rigal_scratch(): files.extend(glob(join(RIGAL_CODE, "*.rig"))) for f in files: copy2(f, RIGAL_SCRATCH) - + diff --git a/python/scope_spinner.py b/python/scope_spinner.py index 50b50312b653bbd0d08d300562b76ab8eaf2eae0..e17cc0890ebcf3f02fb4ef541446f95b9052cfa6 100644 --- a/python/scope_spinner.py +++ b/python/scope_spinner.py @@ -1,19 +1,17 @@ -from PyQt5.QtCore import QObject from PyQt5.QtWidgets import QSpinBox from PyQt5.QtWidgets import QSizePolicy -"""ScopeSpinner provides spinner and scope accessors. -""" class ScopeSpinner(): + """ScopeSpinner provides spinner and scope accessors.""" def __init__(self): self.spinner = QSpinBox() - self.spinner.setRange(1,5) + self.spinner.setRange(1, 5) self.spinner.setStatusTip("set scope for MP Code run") self.spinner.setToolTip("scope") self.spinner.setSizePolicy(QSizePolicy.Maximum, - QSizePolicy.Maximum) + QSizePolicy.Maximum) def scope(self): return self.spinner.value() diff --git a/python/settings_dialog_wrapper.py b/python/settings_dialog_wrapper.py index d4b51d609135ea3b6dc8459108257914501772a5..2023232f9de84490fde85dd1a3c6cdeaabaea53e 100644 --- a/python/settings_dialog_wrapper.py +++ b/python/settings_dialog_wrapper.py @@ -1,6 +1,5 @@ # wrapper from https://stackoverflow.com/questions/2398800/linking-a-qtdesigner-ui-file-to-python-pyqt import os -from PyQt5.QtCore import QObject # for signal/slot support from PyQt5.QtCore import pyqtSlot # for signal/slot support from PyQt5.QtWidgets import QDialog from PyQt5.QtWidgets import QFileDialog @@ -149,7 +148,7 @@ class SettingsDialogWrapper(QDialog): self.ui.mp_code_meta_symbol_c_pb.setStyleSheet( "background-color: %s" % settings["mp_code_meta_symbol_c"]) - def closeEvent(self, e): + def closeEvent(self, _e): # abort by restoring settngs when user deliberately closes the window self.settings_manager.change(self.old_settings) diff --git a/python/settings_manager.py b/python/settings_manager.py index 4a026ff3b7efd75f22faf90918431deeaeb951c4..d35d46fade1b73d3903e2ee4a57fe211231ef510 100644 --- a/python/settings_manager.py +++ b/python/settings_manager.py @@ -8,34 +8,15 @@ """Settings are kept in the settings directory. Access them using this manager. -Provides the following services: - Global variables, do not directly change their values. - settings - the active settings - nps_theme_settings - firebird_theme_settings - printer_friendly_settings - good on printer - bright_settings - good on screen - Note: settings is global and also signal_settings_changed provides settings. - - Class SettingsManager, provides methods to safely change and notify settings: - copy() - get your copy of settings - change(provided settings) - change provided settings values, signal change - _load(), load_from() - change all settings values from file, signal change - _save(), save_to(fname), save settings to user default file or named file - -Usage: - Use SettingsManager to initialize or modify the global settings variable. - SettingsManager is a singleton because it manages the settings resource. - Instantiate SettingsManager before relying on settings values. - Listen to the signal_settings_changed signal if you need to respond to change. - Access the settings global variable without needing SettingsManager. +Provides settings and SETTINGS_THEMES. +Change settings using interfaces. Do not change settings directly. +Note: settings is global and also signal_settings_changed provides settings. """ from os.path import expanduser import os import json from PyQt5.QtCore import QObject # for signal/slot support from PyQt5.QtCore import pyqtSignal, pyqtSlot # for signal/slot support -from PyQt5.QtCore import Qt from message_popup import message_popup from path_constants import MP_SETTINGS_FILENAME @@ -46,45 +27,61 @@ def _settings_filename(): return settings_filename # NPS theme -nps_theme_settings = json.loads('{"node_width": 127, "node_height": 10, "node_t_contrast": 171, "node_border": false, "node_shadow": false, "node_root_c": "#00486f", "node_atomic_c": "#1f7dbc", "node_composite_c": "#ffcc00", "node_schema_c": "#70017f", "node_say_c": "#ffffa0", "edge_arrow_size": 10, "edge_in_c": "#dbdbdb", "edge_follows_c": "#000000", "edge_user_defined_c": "#7aa7bc", "edge_in_style": "dash_line", "edge_follows_style": "solid_line", "edge_user_defined_style": "solid_line", "graph_background_c": "#ffffff", "graph_gradient": 111, "graph_h_spacing": 165, "graph_v_spacing": 55, "graph_hide_collapse_opacity": 127, "mp_code_comment_c":"#606060", "mp_code_keyword_c":"#760f7f","mp_code_meta_symbol_c":"#760f7f","mp_code_operator_c":"#000000","mp_code_number_c":"#000000","mp_code_variable_c":"#404040", "mp_code_quoted_text_c":"#ffaa00"}') +_NPS_THEME_SETTINGS = json.loads('{"node_width": 127, "node_height": 10, "node_t_contrast": 171, "node_border": false, "node_shadow": false, "node_root_c": "#00486f", "node_atomic_c": "#1f7dbc", "node_composite_c": "#ffcc00", "node_schema_c": "#70017f", "node_say_c": "#ffffa0", "edge_arrow_size": 10, "edge_in_c": "#dbdbdb", "edge_follows_c": "#000000", "edge_user_defined_c": "#7aa7bc", "edge_in_style": "dash_line", "edge_follows_style": "solid_line", "edge_user_defined_style": "solid_line", "graph_background_c": "#ffffff", "graph_gradient": 111, "graph_h_spacing": 165, "graph_v_spacing": 55, "graph_hide_collapse_opacity": 127, "mp_code_comment_c":"#606060", "mp_code_keyword_c":"#760f7f","mp_code_meta_symbol_c":"#760f7f","mp_code_operator_c":"#000000","mp_code_number_c":"#000000","mp_code_variable_c":"#404040", "mp_code_quoted_text_c":"#ffaa00"}') # Firebird theme -firebird_theme_settings = json.loads('{"node_width": 127, "node_height": 10, "node_t_contrast": 210, "node_border": false, "node_shadow": false, "node_root_c": "#027731", "node_atomic_c": "#1f7dbc", "node_composite_c": "#ff6a00", "node_schema_c": "#866ec4", "node_say_c": "#ffffa0", "edge_arrow_size": 10, "edge_in_c": "#7d7d82", "edge_follows_c": "#000000", "edge_user_defined_c": "#0000ff", "edge_in_style": "dash_line", "edge_follows_style": "solid_line", "edge_user_defined_style": "solid_line", "graph_background_c": "#ffffff", "graph_gradient": 100, "graph_h_spacing": 165, "graph_v_spacing": 55}') +_FIREBIRD_THEME_SETTINGS = json.loads('{"node_width": 127, "node_height": 10, "node_t_contrast": 210, "node_border": false, "node_shadow": false, "node_root_c": "#027731", "node_atomic_c": "#1f7dbc", "node_composite_c": "#ff6a00", "node_schema_c": "#866ec4", "node_say_c": "#ffffa0", "edge_arrow_size": 10, "edge_in_c": "#7d7d82", "edge_follows_c": "#000000", "edge_user_defined_c": "#0000ff", "edge_in_style": "dash_line", "edge_follows_style": "solid_line", "edge_user_defined_style": "solid_line", "graph_background_c": "#ffffff", "graph_gradient": 100, "graph_h_spacing": 165, "graph_v_spacing": 55}') # printer-friendly -printer_friendly_settings = json.loads('{"node_width": 127, "node_height": 10, "node_border": true, "node_shadow": true, "node_root_c": "#aaff7f", "node_atomic_c": "#c5e9ff", "node_composite_c": "#ffaa7f", "node_schema_c": "#008000", "node_say_c": "#ffff66", "edge_arrow_size": 10, "edge_in_c": "#808080", "edge_follows_c": "#000000", "edge_user_defined_c": "#0000ff", "edge_in_style": "dash_line", "edge_follows_style": "solid_line", "edge_user_defined_style": "solid_line", "graph_background_c": "#e8e8e8", "graph_gradient": 130, "graph_h_spacing": 165, "graph_v_spacing": 55, "node_t_contrast": 96}') +_PRINTER_FRIENDLY_SETTINGS = json.loads('{"node_width": 127, "node_height": 10, "node_border": true, "node_shadow": true, "node_root_c": "#aaff7f", "node_atomic_c": "#c5e9ff", "node_composite_c": "#ffaa7f", "node_schema_c": "#008000", "node_say_c": "#ffff66", "edge_arrow_size": 10, "edge_in_c": "#808080", "edge_follows_c": "#000000", "edge_user_defined_c": "#0000ff", "edge_in_style": "dash_line", "edge_follows_style": "solid_line", "edge_user_defined_style": "solid_line", "graph_background_c": "#e8e8e8", "graph_gradient": 130, "graph_h_spacing": 165, "graph_v_spacing": 55, "node_t_contrast": 96}') # high contrast -high_contrast_settings = json.loads('{"node_width": 127, "node_height": 10, "node_border": true, "node_shadow": true, "node_root_c": "#00d600", "node_atomic_c": "#92beff", "node_composite_c": "#ff9966", "node_schema_c": "#008000", "node_say_c": "#ffff66", "edge_arrow_size": 10, "edge_in_c": "#808080", "edge_follows_c": "#000000", "edge_user_defined_c": "#0000ff", "edge_in_style": "dash_line", "edge_follows_style": "solid_line", "edge_user_defined_style": "solid_line", "graph_background_c": "#c0c0c0", "graph_gradient": 120, "graph_h_spacing": 165, "graph_v_spacing": 55, "node_t_contrast": 100}') +_HIGH_CONTRAST_SETTINGS = json.loads('{"node_width": 127, "node_height": 10, "node_border": true, "node_shadow": true, "node_root_c": "#00d600", "node_atomic_c": "#92beff", "node_composite_c": "#ff9966", "node_schema_c": "#008000", "node_say_c": "#ffff66", "edge_arrow_size": 10, "edge_in_c": "#808080", "edge_follows_c": "#000000", "edge_user_defined_c": "#0000ff", "edge_in_style": "dash_line", "edge_follows_style": "solid_line", "edge_user_defined_style": "solid_line", "graph_background_c": "#c0c0c0", "graph_gradient": 120, "graph_h_spacing": 165, "graph_v_spacing": 55, "node_t_contrast": 100}') # black and white -black_and_white_settings = json.loads('{"node_width": 127, "node_height": 10, "node_t_contrast": 0, "node_border": true, "node_shadow": false, "node_root_c": "#000000", "node_atomic_c": "#dddddd", "node_composite_c": "#dbdbdb", "node_schema_c": "#000000", "node_say_c": "#f1f1f1", "edge_arrow_size": 10, "edge_in_c": "#dbdbdb", "edge_follows_c": "#000000", "edge_user_defined_c": "#a0a0a0", "edge_in_style": "dash_line", "edge_follows_style": "solid_line", "edge_user_defined_style": "solid_line", "graph_background_c": "#ffffff", "graph_gradient": 100, "graph_h_spacing": 165, "graph_v_spacing": 55}') +_BLACK_AND_WHITE_SETTINGS = json.loads('{"node_width": 127, "node_height": 10, "node_t_contrast": 0, "node_border": true, "node_shadow": false, "node_root_c": "#000000", "node_atomic_c": "#dddddd", "node_composite_c": "#dbdbdb", "node_schema_c": "#000000", "node_say_c": "#f1f1f1", "edge_arrow_size": 10, "edge_in_c": "#dbdbdb", "edge_follows_c": "#000000", "edge_user_defined_c": "#a0a0a0", "edge_in_style": "dash_line", "edge_follows_style": "solid_line", "edge_user_defined_style": "solid_line", "graph_background_c": "#ffffff", "graph_gradient": 100, "graph_h_spacing": 165, "graph_v_spacing": 55}') # Grayscale -grayscale_settings = json.loads('{"node_width": 127, "node_height": 10, "node_t_contrast": 176, "node_border": false, "node_shadow": false, "node_root_c": "#555555", "node_atomic_c": "#e8e8e8", "node_composite_c": "#a2a2a2", "node_schema_c": "#000000", "node_say_c": "#e0e0e0", "edge_arrow_size": 10, "edge_in_c": "#dbdbdb", "edge_follows_c": "#000000", "edge_user_defined_c": "#a0a0a0", "edge_in_style": "dash_line", "edge_follows_style": "solid_line", "edge_user_defined_style": "solid_line", "graph_background_c": "#ffffff", "graph_gradient": 111, "graph_h_spacing": 165, "graph_v_spacing": 55}') +_GRAYSCALE_SETTINGS = json.loads('{"node_width": 127, "node_height": 10, "node_t_contrast": 176, "node_border": false, "node_shadow": false, "node_root_c": "#555555", "node_atomic_c": "#e8e8e8", "node_composite_c": "#a2a2a2", "node_schema_c": "#000000", "node_say_c": "#e0e0e0", "edge_arrow_size": 10, "edge_in_c": "#dbdbdb", "edge_follows_c": "#000000", "edge_user_defined_c": "#a0a0a0", "edge_in_style": "dash_line", "edge_follows_style": "solid_line", "edge_user_defined_style": "solid_line", "graph_background_c": "#ffffff", "graph_gradient": 111, "graph_h_spacing": 165, "graph_v_spacing": 55}') # SERC -serc_theme_settings = json.loads('{"node_width": 127, "node_height": 10, "node_t_contrast": 171, "node_border": false, "node_shadow": false, "node_root_c": "#aa1039", "node_atomic_c": "#e1e3e8", "node_composite_c": "#969696", "node_schema_c": "#550000", "node_say_c": "#e0dfc2", "edge_arrow_size": 10, "edge_in_c": "#dbdbdb", "edge_follows_c": "#000000", "edge_user_defined_c": "#a07c7c", "edge_in_style": "dash_line", "edge_follows_style": "solid_line", "edge_user_defined_style": "solid_line", "graph_background_c": "#ffffff", "graph_gradient": 111, "graph_h_spacing": 165, "graph_v_spacing": 55}') +_SERC_THEME_SETTINGS = json.loads('{"node_width": 127, "node_height": 10, "node_t_contrast": 171, "node_border": false, "node_shadow": false, "node_root_c": "#aa1039", "node_atomic_c": "#e1e3e8", "node_composite_c": "#969696", "node_schema_c": "#550000", "node_say_c": "#e0dfc2", "edge_arrow_size": 10, "edge_in_c": "#dbdbdb", "edge_follows_c": "#000000", "edge_user_defined_c": "#a07c7c", "edge_in_style": "dash_line", "edge_follows_style": "solid_line", "edge_user_defined_style": "solid_line", "graph_background_c": "#ffffff", "graph_gradient": 111, "graph_h_spacing": 165, "graph_v_spacing": 55}') # Navy -navy_theme_settings = json.loads('{"node_width": 127, "node_height": 10, "node_t_contrast": 185, "node_border": false, "node_shadow": false, "node_root_c": "#7b0000", "node_atomic_c": "#333a6a", "node_composite_c": "#bca15d", "node_schema_c": "#550000", "node_say_c": "#e6e1ab", "edge_arrow_size": 10, "edge_in_c": "#dbdbdb", "edge_follows_c": "#000000", "edge_user_defined_c": "#5d627f", "edge_in_style": "dash_line", "edge_follows_style": "solid_line", "edge_user_defined_style": "solid_line", "graph_background_c": "#ffffff", "graph_gradient": 119, "graph_h_spacing": 165, "graph_v_spacing": 55}') - -settings_themes = [ - ("NPS theme", nps_theme_settings), - ("Firebird theme", firebird_theme_settings), - ("Black-and-white", black_and_white_settings), - ("Grayscale", grayscale_settings), - ("SERC theme", serc_theme_settings), - ("Navy theme", navy_theme_settings), - ("Printer-friendly", printer_friendly_settings), - ("High contrast", high_contrast_settings) +_NAVY_THEME_SETTINGS = json.loads('{"node_width": 127, "node_height": 10, "node_t_contrast": 185, "node_border": false, "node_shadow": false, "node_root_c": "#7b0000", "node_atomic_c": "#333a6a", "node_composite_c": "#bca15d", "node_schema_c": "#550000", "node_say_c": "#e6e1ab", "edge_arrow_size": 10, "edge_in_c": "#dbdbdb", "edge_follows_c": "#000000", "edge_user_defined_c": "#5d627f", "edge_in_style": "dash_line", "edge_follows_style": "solid_line", "edge_user_defined_style": "solid_line", "graph_background_c": "#ffffff", "graph_gradient": 119, "graph_h_spacing": 165, "graph_v_spacing": 55}') + +SETTINGS_THEMES = [ + ("NPS theme", _NPS_THEME_SETTINGS), + ("Firebird theme", _FIREBIRD_THEME_SETTINGS), + ("Black-and-white", _BLACK_AND_WHITE_SETTINGS), + ("Grayscale", _GRAYSCALE_SETTINGS), + ("SERC theme", _SERC_THEME_SETTINGS), + ("Navy theme", _NAVY_THEME_SETTINGS), + ("Printer-friendly", _PRINTER_FRIENDLY_SETTINGS), + ("High contrast", _HIGH_CONTRAST_SETTINGS) ] settings = dict() -# SettingsManager provides services to manage the global settings variable -# and to signal change. Do not modify settings directly. class SettingsManager(QObject): + """ + SettingsManager provides services to manage the global settings variable + and to signal change. Do not modify settings directly. + + Provides methods to safely change and notify settings: + copy() - get your copy of settings + change(provided settings) - change provided settings values, signal change + _load(), load_from() - change all settings values from file, signal change + _save(), save_to(fname), save settings to user default file or named file + + Usage: + Use SettingsManager to initialize or modify the global settings variable. + SettingsManager is a singleton because it manages the settings resource. + Instantiate SettingsManager before relying on settings values. + Listen to the signal_settings_changed signal if you need to respond + to change. + Access the settings global variable without needing SettingsManager. + """ # signal signal_settings_changed = pyqtSignal(dict, dict, name='settingsChanged') @@ -96,7 +93,7 @@ class SettingsManager(QObject): if os.path.exists(_settings_filename()): self._load() else: - self.change(nps_theme_settings) + self.change(_NPS_THEME_SETTINGS) # get a copy of settings def copy(self): @@ -130,16 +127,16 @@ class SettingsManager(QObject): def _save(self): self.save_to(_settings_filename()) - + def load_from(self, filename): try: with open(filename) as f: new_settings = json.load(f) # add any missing settings - missing_keys = nps_theme_settings.keys() - new_settings.keys() + missing_keys = _NPS_THEME_SETTINGS.keys() - new_settings.keys() for key in missing_keys: - new_settings[key] = nps_theme_settings[key] + new_settings[key] = _NPS_THEME_SETTINGS[key] self.change(new_settings) diff --git a/python/trace_generator_manager.py b/python/trace_generator_manager.py index bb652d8dd26ddb0e401c0f5faf6b7f50b910f703..a6bf6330f7aeef8d1d7d1eb764fb1c8519b220d7 100644 --- a/python/trace_generator_manager.py +++ b/python/trace_generator_manager.py @@ -1,7 +1,5 @@ import os from subprocess import Popen, PIPE -#import shutil -import json import queue from PyQt5.QtCore import QObject from PyQt5.QtCore import pyqtSignal, pyqtSlot @@ -9,20 +7,6 @@ from PyQt5.QtCore import QTimer from command_runner import CommandRunner from path_constants import RIGAL_SCRATCH, RIGAL_RC, RIGAL_IC -"""Run MP Code trace generator engines. - -Interfaces: - mp_compile(schema_name, scope, mp_code_text): - return (status, generated_json, log) - note that status is "" on no error and may start with "*** error: at " - mp_cancel_compile() - mp_check_syntax() - mp_clean_shutdown() - -NOTE: RIGAL functions read/write in same directory, so we change to the -scratch RIGAL work directory to work, then move back when done. -""" - # the syntax checker runs RIGAL commands in the GUI thread def _run_rigal_now(cmd): p = Popen(cmd, stdout=PIPE) @@ -36,6 +20,19 @@ def _run_rigal_now(cmd): return lines class TraceGeneratorManager(QObject): + """Run MP Code trace generator engines. + + Interfaces: + mp_compile(schema_name, scope, mp_code_text): + return (status, generated_json, log) + note that status is "" on no error and may start with "*** error: at " + mp_cancel_compile() + mp_check_syntax() + mp_clean_shutdown() + + NOTE: RIGAL functions read/write in same directory, so we change to the + scratch RIGAL work directory to work, then move back when done. + """ # signals # register with this to receive completed compile response @@ -74,7 +71,7 @@ class TraceGeneratorManager(QObject): if do_add: self._log.append("Error: %s" % line) print("Error: %s: %s" % (name, line)) - + else: if do_add: self._log.append(line) @@ -112,7 +109,7 @@ class TraceGeneratorManager(QObject): if self.command_runner.return_code(): # signal error self.signal_compile_response.emit("Return code error", "{}", - self._log_string()) + self._log_string()) self._return_to_idle() return True else: @@ -136,13 +133,13 @@ class TraceGeneratorManager(QObject): return (status, "") # initiate stateful compilation process - """Call this to get compilation started""" def mp_compile(self, schema_name, scope, mp_code_text): + """Call this to get compilation started""" # validate state if self.state != "IDLE": self.signal_compile_response.emit("Unexpected state: %s" % - self.state, "{}", "") + self.state, "{}", "") return # bad if RIGAL is not built @@ -220,7 +217,7 @@ class TraceGeneratorManager(QObject): # https://stackoverflow.com/questions/8196254/how-to-iterate-queue-queue-items-in-python/8196904 # note queue.queue is not threadsafe but this is not in a thread # so this is okay - for stream_name, line in list(self._queue.queue): + for _stream_name, line in list(self._queue.queue): if line[:14] == "*** error: at ": # error self.signal_compile_response.emit(line, "{}", "") @@ -282,14 +279,14 @@ class TraceGeneratorManager(QObject): # everything worked self.signal_compile_response.emit("", generated_json, - self._log_string()) + self._log_string()) self._return_to_idle() return - """Call this to abort the compilation process.""" def mp_cancel_compile(self): + """Call this to abort the compilation process.""" # validate state - if (self.state == "IDLE"): + if self.state == "IDLE": self.signal_compile_response.emit("Unexpected state: %s"%self.state) return @@ -299,9 +296,9 @@ class TraceGeneratorManager(QObject): self._return_to_idle() self.signal_compile_response.emit("Canceled", "{}", "") - """Check MP Code syntax synchronously. Returns "" else error which - may include the line number of ther error""" def mp_check_syntax(self, schema_name, mp_code_text): + """Check MP Code syntax synchronously. Returns "" else error which + may include the line number of ther error""" try: @@ -320,7 +317,7 @@ class TraceGeneratorManager(QObject): str(e)) # rc MP2-parser - lines_1 = _run_rigal_now([RIGAL_RC, "MP2-parser"]) + _lines_1 = _run_rigal_now([RIGAL_RC, "MP2-parser"]) # rc schema lines_2 = _run_rigal_now([RIGAL_IC, "MP2-parser", schema_name, diff --git a/python/trace_navigation.py b/python/trace_navigation.py index 484a74f9ab8d5d8ff1011fecf71664860fe7c7db..d89f205ab207178671fdeeb6556eba128dea4283 100644 --- a/python/trace_navigation.py +++ b/python/trace_navigation.py @@ -34,7 +34,7 @@ class TraceNavigation(QWidget): self.trace_min = 0 self.trace_max = 0 self.trace_value.setText(str(self.current_trace)) - self.trace_value.setFixedSize(50,20) + self.trace_value.setFixedSize(50, 20) self.trace_value.setMaxLength(5) # what to do when the user inputs a trace number @@ -69,9 +69,9 @@ class TraceNavigation(QWidget): def trace_edited(self, trace_text): try: trace_num = int(trace_text) - if ((trace_num >= self.trace_min) and - (trace_num <= self.trace_max) and - (trace_num != self.current_trace)): + if ((trace_num >= self.trace_min) + and (trace_num <= self.trace_max) + and (trace_num != self.current_trace)): # use -1 because traces are displayed starting at 1, # but indexed starting at 0 glw_new = self.graph_list_widget.model.index(trace_num-1) @@ -83,9 +83,9 @@ class TraceNavigation(QWidget): trace_text = self.trace_value.text() try: trace_num = int(trace_text) - if ((trace_num >= self.trace_min) and - (trace_num <= self.trace_max) and - (trace_num != self.current_trace)): + if ((trace_num >= self.trace_min) + and (trace_num <= self.trace_max) + and (trace_num != self.current_trace)): # use -1 because traces are displayed starting at 1, # but indexed starting at 0 glw_new = self.graph_list_widget.model.index(trace_num-1) @@ -96,7 +96,7 @@ class TraceNavigation(QWidget): def go_previous(self): glw_current = self.graph_list_widget.view.currentIndex().row() - if (glw_current > 0): + if glw_current > 0: glw_new = self.graph_list_widget.model.index(glw_current-1) self.graph_list_widget.view.setCurrentIndex(glw_new) self.graph_list_widget.view.scrollTo(glw_new) @@ -106,7 +106,7 @@ class TraceNavigation(QWidget): def go_next(self): glw_current = self.graph_list_widget.view.currentIndex().row() - if (glw_current < (self.trace_max - 1)): + if glw_current < (self.trace_max - 1): glw_new = self.graph_list_widget.model.index(glw_current+1) self.graph_list_widget.view.setCurrentIndex(glw_new) self.graph_list_widget.view.scrollTo(glw_new) diff --git a/python/version_file.py b/python/version_file.py index 49e5d9d3def68d8a26b913e5fcaa3538d9f55f0f..a81dcb967de67c387c342a36f01ee460673206c3 100644 --- a/python/version_file.py +++ b/python/version_file.py @@ -1,2 +1,2 @@ # This file is auto-generated by Makefile. Do not edit this file. -VERSION = "Pre-Alpha v0.3.9" +VERSION = "Pre-Alpha-v0.3.10"