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"