From e66cfa31ff52b95a9c3df27c8a1f3b02bef6db3d Mon Sep 17 00:00:00 2001
From: Seth Hitefield <sdhitefield@gmail.com>
Date: Mon, 11 Apr 2016 22:09:16 -0400
Subject: grc: Main window opens with pygobject and gtk3. Still throws tons of
 errors.

---
 grc/gui/FlowGraph.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index 02d5197fb0..f7af93fe8e 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -23,7 +23,7 @@ from distutils.spawn import find_executable
 from itertools import chain, count
 from operator import methodcaller
 
-import gobject
+from gi.repository import GObject
 
 from . import Actions, Colors, Constants, Utils, Bars, Dialogs
 from .Constants import SCROLL_PROXIMITY_SENSITIVITY, SCROLL_DISTANCE
@@ -98,7 +98,7 @@ class FlowGraph(Element, _Flowgraph):
             editor = self._external_updaters[target] = ExternalEditor(
                 editor=editor,
                 name=target[0], value=param.get_value(),
-                callback=functools.partial(gobject.idle_add, updater)
+                callback=functools.partial(GObject.idle_add, updater)
             )
             editor.start()
         try:
@@ -706,7 +706,7 @@ class FlowGraph(Element, _Flowgraph):
         Auto-scroll the scroll bars at the boundaries.
         """
         #to perform a movement, the mouse must be pressed
-        # (no longer checking pending events via gtk.events_pending() - always true in Windows)
+        # (no longer checking pending events via Gtk.events_pending() - always true in Windows)
         if not self.mouse_pressed:
             # only continue if mouse-over stuff is enabled (just the auto-hide port label stuff for now)
             if not Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active(): return
-- 
cgit v1.2.3


From 5352dfd80fd238256da7bbd5efd15c154f3f5a14 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Mon, 18 Apr 2016 18:11:52 +0200
Subject: gtk3: add flowgraph draw code and other gtk3 fixes (WIP)

---
 grc/core/Platform.py      |  7 ++--
 grc/gui/ActionHandler.py  |  9 ++---
 grc/gui/Actions.py        | 21 ++++++-----
 grc/gui/Bars.py           |  4 +-
 grc/gui/Block.py          | 91 ++++++++++++++++++++++++---------------------
 grc/gui/Colors.py         | 25 +++++++------
 grc/gui/Connection.py     | 29 +++++++--------
 grc/gui/DrawingArea.py    | 57 ++++++----------------------
 grc/gui/Element.py        | 26 +++++++------
 grc/gui/FlowGraph.py      | 29 ++++++++-------
 grc/gui/MainWindow.py     | 28 ++++++--------
 grc/gui/NotebookPage.py   | 16 ++++----
 grc/gui/Port.py           | 63 +++++++++++++------------------
 grc/gui/VariableEditor.py | 95 ++++++++++++++++++++++++-----------------------
 14 files changed, 230 insertions(+), 270 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/core/Platform.py b/grc/core/Platform.py
index dfb60ee6a5..82d91ddb72 100644
--- a/grc/core/Platform.py
+++ b/grc/core/Platform.py
@@ -219,10 +219,9 @@ class Platform(Element):
 
         color = n.find('color') or ''
         try:
-            import gi # ugly but handy
-            from gi.repository import Gdk
-            Gdk.color_parse(color)
-        except (ValueError, ImportError):
+            chars_per_color = 2 if len(color) > 4 else 1
+            tuple(int(color[o:o + 2], 16) / 255.0 for o in range(1, 3 * chars_per_color, chars_per_color))
+        except ValueError:
             if color:  # no color is okay, default set in GUI
                 print >> sys.stderr, 'Warning: Can\'t parse color code "{}" for domain "{}" '.format(color, key)
                 color = None
diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py
index 96f7080c40..e25fa19030 100644
--- a/grc/gui/ActionHandler.py
+++ b/grc/gui/ActionHandler.py
@@ -84,9 +84,8 @@ class ActionHandler:
         # prevent key event stealing while the search box is active
         # .has_focus() only in newer versions 2.17+?
         # .is_focus() seems to work, but exactly the same
-        if self.main_window.btwin.search_entry.flags() & Gtk.HAS_FOCUS:
+        if self.main_window.btwin.search_entry.has_focus():
             return False
-        if not self.get_focus_flag(): return False
         return Actions.handle_key_press(event)
 
     def _quit(self, window, event):
@@ -447,9 +446,9 @@ class ActionHandler:
             action.save_to_preferences()
         elif action == Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR:
             if self.init:
-                md = gtk.MessageDialog(main,
-                    gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO,
-                    gtk.BUTTONS_CLOSE, "Moving the variable editor requires a restart of GRC.")
+                md = Gtk.MessageDialog(main,
+                    Gtk.DIALOG_DESTROY_WITH_PARENT, Gtk.MESSAGE_INFO,
+                    Gtk.BUTTONS_CLOSE, "Moving the variable editor requires a restart of GRC.")
                 md.run()
                 md.destroy()
                 action.save_to_preferences()
diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py
index b2745c995a..d0e114293f 100644
--- a/grc/gui/Actions.py
+++ b/grc/gui/Actions.py
@@ -27,6 +27,7 @@ import Preferences
 
 NO_MODS_MASK = 0
 
+
 ########################################################################
 # Actions API
 ########################################################################
@@ -48,7 +49,7 @@ def handle_key_press(event):
     """
     _used_mods_mask = reduce(lambda x, y: x | y, [mod_mask for keyval, mod_mask in _actions_keypress_dict], NO_MODS_MASK)
     # extract the key value and the consumed modifiers
-    keyval, egroup, level, consumed = _keymap.translate_keyboard_state(
+    _unused, keyval, egroup, level, consumed = _keymap.translate_keyboard_state(
         event.hardware_keycode, event.get_state(), event.group)
     # get the modifier mask and ignore irrelevant modifiers
     mod_mask = event.get_state() & ~consumed & _used_mods_mask
@@ -261,32 +262,32 @@ BLOCK_ROTATE_CW = Action(
 BLOCK_VALIGN_TOP = Action(
     label='Vertical Align Top',
     tooltip='Align tops of selected blocks',
-    keypresses=(gtk.keysyms.t, gtk.gdk.SHIFT_MASK),
+    keypresses=(Gdk.KEY_t, Gdk.ModifierType.SHIFT_MASK),
 )
 BLOCK_VALIGN_MIDDLE = Action(
     label='Vertical Align Middle',
     tooltip='Align centers of selected blocks vertically',
-    keypresses=(gtk.keysyms.m, gtk.gdk.SHIFT_MASK),
+    keypresses=(Gdk.KEY_m, Gdk.ModifierType.SHIFT_MASK),
 )
 BLOCK_VALIGN_BOTTOM = Action(
     label='Vertical Align Bottom',
     tooltip='Align bottoms of selected blocks',
-    keypresses=(gtk.keysyms.b, gtk.gdk.SHIFT_MASK),
+    keypresses=(Gdk.KEY_b, Gdk.ModifierType.SHIFT_MASK),
 )
 BLOCK_HALIGN_LEFT = Action(
     label='Horizontal Align Left',
     tooltip='Align left edges of blocks selected blocks',
-    keypresses=(gtk.keysyms.l, gtk.gdk.SHIFT_MASK),
+    keypresses=(Gdk.KEY_l, Gdk.ModifierType.SHIFT_MASK),
 )
 BLOCK_HALIGN_CENTER = Action(
     label='Horizontal Align Center',
     tooltip='Align centers of selected blocks horizontally',
-    keypresses=(gtk.keysyms.c, gtk.gdk.SHIFT_MASK),
+    keypresses=(Gdk.KEY_c, Gdk.ModifierType.SHIFT_MASK),
 )
 BLOCK_HALIGN_RIGHT = Action(
     label='Horizontal Align Right',
     tooltip='Align right edges of selected blocks',
-    keypresses=(gtk.keysyms.r, gtk.gdk.SHIFT_MASK),
+    keypresses=(Gdk.KEY_r, Gdk.ModifierType.SHIFT_MASK),
 )
 BLOCK_ALIGNMENTS = [
     BLOCK_VALIGN_TOP,
@@ -341,9 +342,9 @@ TOGGLE_HIDE_VARIABLES = ToggleAction(
 TOGGLE_FLOW_GRAPH_VAR_EDITOR = ToggleAction(
     label='Show _Variable Editor',
     tooltip='Show the variable editor. Modify variables and imports in this flow graph',
-    stock_id=gtk.STOCK_EDIT,
+    stock_id=Gtk.STOCK_EDIT,
     default=True,
-    keypresses=(gtk.keysyms.e, gtk.gdk.CONTROL_MASK),
+    keypresses=(Gdk.KEY_e, Gdk.ModifierType.CONTROL_MASK),
     preference_name='variable_editor_visable',
 )
 TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR = ToggleAction(
@@ -407,7 +408,7 @@ ERRORS_WINDOW_DISPLAY = Action(
 TOGGLE_CONSOLE_WINDOW = ToggleAction(
     label='Show _Console Panel',
     tooltip='Toggle visibility of the console',
-    keypresses=(Gdk.KEY_c, Gdk.ModifierType.CONTROL_MASK),
+    keypresses=(Gdk.KEY_r, Gdk.ModifierType.CONTROL_MASK),
     preference_name='console_window_visible'
 )
 TOGGLE_BLOCKS_WINDOW = ToggleAction(
diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py
index 2d0709309c..c8631aa298 100644
--- a/grc/gui/Bars.py
+++ b/grc/gui/Bars.py
@@ -61,6 +61,7 @@ TOOLBAR_LIST = (
     Actions.OPEN_HIER,
 )
 
+
 # The list of actions and categories for the menu bar.
 MENU_BAR_LIST = (
     (Gtk.Action(name='File', label='_File'), [
@@ -88,7 +89,7 @@ MENU_BAR_LIST = (
         None,
         Actions.BLOCK_ROTATE_CCW,
         Actions.BLOCK_ROTATE_CW,
-        (gtk.Action('Align', '_Align', None, None), Actions.BLOCK_ALIGNMENTS),
+        (Gtk.Action('Align', '_Align', None, None), Actions.BLOCK_ALIGNMENTS),
         None,
         Actions.BLOCK_ENABLE,
         Actions.BLOCK_DISABLE,
@@ -140,6 +141,7 @@ MENU_BAR_LIST = (
     ]),
 )
 
+
 # The list of actions for the context menu.
 CONTEXT_MENU_LIST = [
     Actions.BLOCK_CUT,
diff --git a/grc/gui/Block.py b/grc/gui/Block.py
index 64c9e022b5..d16c9d01c6 100644
--- a/grc/gui/Block.py
+++ b/grc/gui/Block.py
@@ -17,10 +17,11 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+import math
 import gi
 gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk
-from gi.repository import Pango
+gi.require_version('PangoCairo', '1.0')
+from gi.repository import Gtk, Pango, PangoCairo
 
 from . import Actions, Colors, Utils
 
@@ -89,7 +90,9 @@ class Block(Element, _Block):
         ))
         Element.__init__(self)
         self._comment_pixmap = None
+        self._bg_color = Colors.BLOCK_ENABLED_COLOR
         self.has_busses = [False, False]  # source, sink
+        self.layouts = []
 
     def get_coordinate(self):
         """
@@ -196,14 +199,14 @@ class Block(Element, _Block):
     def create_labels(self):
         """Create the labels for the signal block."""
         Element.create_labels(self)
-        self._bg_color = self.is_dummy_block and Colors.MISSING_BLOCK_BACKGROUND_COLOR or \
-                         self.get_bypassed() and Colors.BLOCK_BYPASSED_COLOR or \
-                         self.get_enabled() and Colors.BLOCK_ENABLED_COLOR or Colors.BLOCK_DISABLED_COLOR
-
-        layouts = list()
+        self._bg_color = Colors.MISSING_BLOCK_BACKGROUND_COLOR if self.is_dummy_block else \
+                         Colors.BLOCK_BYPASSED_COLOR if self.get_bypassed() else \
+                         Colors.BLOCK_ENABLED_COLOR if self.get_enabled() else \
+                         Colors.BLOCK_DISABLED_COLOR
+        del self.layouts[:]
         #create the main layout
         layout = Gtk.DrawingArea().create_pango_layout('')
-        layouts.append(layout)
+        self.layouts.append(layout)
         layout.set_markup(Utils.parse_template(BLOCK_MARKUP_TMPL, block=self, font=BLOCK_FONT))
         self.label_width, self.label_height = layout.get_pixel_size()
         #display the params
@@ -217,30 +220,11 @@ class Block(Element, _Block):
             layout = Gtk.DrawingArea().create_pango_layout('')
             layout.set_spacing(LABEL_SEPARATION*Pango.SCALE)
             layout.set_markup('\n'.join(markups))
-            layouts.append(layout)
+            self.layouts.append(layout)
             w, h = layout.get_pixel_size()
             self.label_width = max(w, self.label_width)
             self.label_height += h + LABEL_SEPARATION
-        width = self.label_width
-        height = self.label_height
-        #setup the pixmap
-        pixmap = self.get_parent().new_pixmap(width, height)
-        gc = pixmap.new_gc()
-        gc.set_foreground(self._bg_color)
-        pixmap.draw_rectangle(gc, True, 0, 0, width, height)
-        #draw the layouts
-        h_off = 0
-        for i,layout in enumerate(layouts):
-            w,h = layout.get_pixel_size()
-            if i == 0: w_off = (width-w)/2
-            else: w_off = 0
-            pixmap.draw_layout(gc, w_off, h_off, layout)
-            h_off = h + h_off + LABEL_SEPARATION
-        #create vertical and horizontal pixmaps
-        self.horizontal_label = pixmap
-        if self.is_vertical():
-            self.vertical_label = self.get_parent().new_pixmap(height, width)
-            Utils.rotate_pixmap(gc, self.horizontal_label, self.vertical_label)
+
         #calculate width and height needed
         W = self.label_width + 2 * BLOCK_LABEL_PADDING
 
@@ -301,29 +285,50 @@ class Block(Element, _Block):
         else:
             self._comment_pixmap = None
 
-    def draw(self, gc, window):
+    def draw(self, widget, cr):
         """
         Draw the signal block with label and inputs/outputs.
-
-        Args:
-            gc: the graphics context
-            window: the gtk window to draw on
         """
         # draw ports
         for port in self.get_ports_gui():
-            port.draw(gc, window)
+            port.draw(widget, cr)
         # draw main block
-        x, y = self.get_coordinate()
-        Element.draw(
-            self, gc, window, bg_color=self._bg_color,
-            border_color=self.is_highlighted() and Colors.HIGHLIGHT_COLOR or
-                         self.is_dummy_block and Colors.MISSING_BLOCK_BORDER_COLOR or Colors.BORDER_COLOR,
+        border_color = (
+            Colors.HIGHLIGHT_COLOR if self.is_highlighted() else
+            Colors.MISSING_BLOCK_BORDER_COLOR if self.is_dummy_block else
+            Colors.BORDER_COLOR
         )
-        #draw label image
+        Element.draw(self, widget, cr, border_color, self._bg_color)
+        x, y = self.get_coordinate()
+        # create the image surface
+        width = self.label_width
+        height = self.label_height
+        cr.set_source_rgb(*self._bg_color)
+        cr.save()
         if self.is_horizontal():
-            window.draw_drawable(gc, self.horizontal_label, 0, 0, x+BLOCK_LABEL_PADDING, y+(self.H-self.label_height)/2, -1, -1)
+            cr.translate(x + BLOCK_LABEL_PADDING, y + (self.H - self.label_height) / 2)
         elif self.is_vertical():
-            window.draw_drawable(gc, self.vertical_label, 0, 0, x+(self.H-self.label_height)/2, y+BLOCK_LABEL_PADDING, -1, -1)
+            cr.translate(x + (self.H - self.label_height) / 2, y + BLOCK_LABEL_PADDING)
+            cr.rotate(-90 * math.pi / 180.)
+            cr.translate(-width, 0)
+
+        # cr.rectangle(0, 0, width, height)
+        # cr.fill()
+
+        # draw the layouts
+        h_off = 0
+        for i, layout in enumerate(self.layouts):
+            w, h = layout.get_pixel_size()
+            if i == 0:
+                w_off = (width - w) / 2
+            else:
+                w_off = 0
+            cr.translate(w_off, h_off)
+            PangoCairo.update_layout(cr, layout)
+            PangoCairo.show_layout(cr, layout)
+            cr.translate(-w_off, -h_off)
+            h_off = h + h_off + LABEL_SEPARATION
+        cr.restore()
 
     def what_is_selected(self, coor, coor_m=None):
         """
diff --git a/grc/gui/Colors.py b/grc/gui/Colors.py
index 2c5c73017e..686b378c38 100644
--- a/grc/gui/Colors.py
+++ b/grc/gui/Colors.py
@@ -16,26 +16,30 @@ You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
+
 try:
     import gi
     gi.require_version('Gtk', '3.0')
-    from gi.repository import Gtk
     from gi.repository import Gdk
 
+    # Not gtk3?
+    #COLORMAP = Gdk.colormap_get_system() #create all of the colors
+    #def get_color(color_code): return _COLORMAP.alloc_color(color_code, True, True)
+
     def get_color(color_code):
-        color = Gdk.RGBA()
-        color.parse(color_code)
-        return color
+        chars_per_color = 2 if len(color_code) > 4 else 1
+        offsets = range(1, 3 * chars_per_color + 1, chars_per_color)
+        return tuple(int(color_code[o:o + 2], 16) / 255.0 for o in offsets)
 
     HIGHLIGHT_COLOR = get_color('#00FFFF')
     BORDER_COLOR = get_color('#444444')
 
     # Missing blocks stuff
     MISSING_BLOCK_BACKGROUND_COLOR = get_color('#FFF2F2')
-    MISSING_BLOCK_BORDER_COLOR = get_color('red')
+    MISSING_BLOCK_BORDER_COLOR = get_color('#FF0000')
 
     # Param entry boxes
-    PARAM_ENTRY_TEXT_COLOR = get_color('black')
+    PARAM_ENTRY_TEXT_COLOR = get_color('#000000')
     ENTRYENUM_CUSTOM_COLOR = get_color('#EEEEEE')
 
     # Flow graph color constants
@@ -49,12 +53,9 @@ try:
     BLOCK_BYPASSED_COLOR = get_color('#F4FF81')
 
     # Connection color constants
-    CONNECTION_ENABLED_COLOR = get_color('black')
+    CONNECTION_ENABLED_COLOR = get_color('#000000')
     CONNECTION_DISABLED_COLOR = get_color('#BBBBBB')
-    CONNECTION_ERROR_COLOR = get_color('red')
+    CONNECTION_ERROR_COLOR = get_color('#FF0000')
 
 except Exception as e:
-    print 'Unable to import Colors'
-
-
-DEFAULT_DOMAIN_COLOR_CODE = '#777777'
+    print 'Unable to import Colors', e
diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py
index 356a55d83b..46414c94c8 100644
--- a/grc/gui/Connection.py
+++ b/grc/gui/Connection.py
@@ -91,10 +91,10 @@ class Connection(Element, _Connection):
         ]
         source_domain = self.get_source().get_domain()
         sink_domain = self.get_sink().get_domain()
-        self.line_attributes[0] = 2 if source_domain != sink_domain else 0
-        self.line_attributes[1] = Gdk.LINE_DOUBLE_DASH \
-            if not source_domain == sink_domain == GR_MESSAGE_DOMAIN \
-            else Gdk.LINE_ON_OFF_DASH
+        # self.line_attributes[0] = 2 if source_domain != sink_domain else 0
+        # self.line_attributes[1] = Gdk.LINE_DOUBLE_DASH \
+        #     if not source_domain == sink_domain == GR_MESSAGE_DOMAIN \
+        #     else Gdk.LINE_ON_OFF_DASH
         get_domain_color = lambda d: Colors.get_color((
             self.get_parent().get_parent().domains.get(d, {})
         ).get('color') or Colors.DEFAULT_DOMAIN_COLOR_CODE)
@@ -147,13 +147,9 @@ class Connection(Element, _Connection):
             self.add_line((x1, y1), points[0])
             self.add_line((x2, y2), points[0])
 
-    def draw(self, gc, window):
+    def draw(self, widget, cr):
         """
         Draw the connection.
-
-        Args:
-            gc: the graphics context
-            window: the gtk window to draw on
         """
         sink = self.get_sink()
         source = self.get_source()
@@ -175,11 +171,12 @@ class Connection(Element, _Connection):
             Colors.CONNECTION_DISABLED_COLOR if not self.get_enabled() else
             color
         )
-        Element.draw(self, gc, window, mod_color(self._color), mod_color(self._bg_color))
+        Element.draw(self, widget, cr, mod_color(self._color), mod_color(self._bg_color))
         # draw arrow on sink port
-        try:
-            gc.set_foreground(mod_color(self._arrow_color))
-            gc.set_line_attributes(0, Gdk.LINE_SOLID, Gdk.CAP_BUTT, Gdk.JOIN_MITER)
-            window.draw_polygon(gc, True, self._arrow)
-        except:
-            pass
+        cr.set_source_rgb(*self._arrow_color)
+        # TODO: gc.set_line_attributes(0, Gdk.LINE_SOLID, Gdk.CAP_BUTT, Gdk.JOIN_MITER)
+        cr.move_to(*self._arrow[0])
+        cr.line_to(*self._arrow[1])
+        cr.line_to(*self._arrow[2])
+        cr.close_path()
+        cr.fill()
diff --git a/grc/gui/DrawingArea.py b/grc/gui/DrawingArea.py
index f91fb2a3de..2bce21fa6d 100644
--- a/grc/gui/DrawingArea.py
+++ b/grc/gui/DrawingArea.py
@@ -48,8 +48,7 @@ class DrawingArea(Gtk.DrawingArea):
         GObject.GObject.__init__(self)
         self.set_size_request(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
         self.connect('realize', self._handle_window_realize)
-        self.connect('configure-event', self._handle_window_configure)
-        self.connect('expose-event', self._handle_window_expose)
+        self.connect('draw', self.draw)
         self.connect('motion-notify-event', self._handle_mouse_motion)
         self.connect('button-press-event', self._handle_mouse_button_press)
         self.connect('button-release-event', self._handle_mouse_button_release)
@@ -59,35 +58,22 @@ class DrawingArea(Gtk.DrawingArea):
             Gdk.EventMask.POINTER_MOTION_MASK | \
             Gdk.EventMask.BUTTON_RELEASE_MASK | \
             Gdk.EventMask.LEAVE_NOTIFY_MASK | \
-            Gdk.EventMask.ENTER_NOTIFY_MASK | \
-            Gdk.EventMask.FOCUS_CHANGE_MASK
+            Gdk.EventMask.ENTER_NOTIFY_MASK #| \
+            #Gdk.EventMask.FOCUS_CHANGE_MASK
         )
         #setup drag and drop
-        self.drag_dest_set(Gtk.DestDefaults.ALL, DND_TARGETS, Gdk.DragAction.COPY)
+        self.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY)
         self.connect('drag-data-received', self._handle_drag_data_received)
+        self.drag_dest_set_target_list(None)
+        self.drag_dest_add_text_targets()
         #setup the focus flag
         self._focus_flag = False
         self.get_focus_flag = lambda: self._focus_flag
         def _handle_notify_event(widget, event, focus_flag): self._focus_flag = focus_flag
         self.connect('leave-notify-event', _handle_notify_event, False)
         self.connect('enter-notify-event', _handle_notify_event, True)
-        self.set_flags(Gtk.CAN_FOCUS)  # self.set_can_focus(True)
-        self.connect('focus-out-event', self._handle_focus_lost_event)
-
-    def new_pixmap(self, width, height):
-        return Gdk.Pixmap(self.window, width, height, -1)
-
-    def get_screenshot(self, transparent_bg=False):
-        pixmap = self._pixmap
-        W, H = pixmap.get_size()
-        pixbuf = GdkPixbuf.Pixbuf(GdkPixbuf.Colorspace.RGB, 0, 8, W, H)
-        pixbuf.fill(0xFF + Colors.FLOWGRAPH_BACKGROUND_COLOR.pixel << 8)
-        pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, W-1, H-1)
-        if transparent_bg:
-            bgc = Colors.FLOWGRAPH_BACKGROUND_COLOR
-            pixbuf = pixbuf.add_alpha(True, bgc.red, bgc.green, bgc.blue)
-        return pixbuf
-
+#        self.set_flags(Gtk.CAN_FOCUS)  # self.set_can_focus(True)
+#        self.connect('focus-out-event', self._handle_focus_lost_event)
 
     ##########################################################################
     ## Handlers
@@ -96,7 +82,7 @@ class DrawingArea(Gtk.DrawingArea):
         """
         Handle a drag and drop by adding a block at the given coordinate.
         """
-        self._flow_graph.add_new_block(selection_data.data, (x, y))
+        self._flow_graph.add_new_block(selection_data.get_text(), (x, y))
 
     def _handle_mouse_scroll(self, widget, event):
         if event.get_state() & Gdk.ModifierType.SHIFT_MASK:
@@ -113,7 +99,7 @@ class DrawingArea(Gtk.DrawingArea):
         self.ctrl_mask = event.get_state() & Gdk.ModifierType.CONTROL_MASK
         self.mod1_mask = event.get_state() & Gdk.ModifierType.MOD1_MASK
         if event.button == 1: self._flow_graph.handle_mouse_selector_press(
-            double_click=(event.type == Gdk._2BUTTON_PRESS),
+            double_click=(event.type == Gdk.EventType._2BUTTON_PRESS),
             coordinate=(event.x, event.y),
         )
         if event.button == 3: self._flow_graph.handle_mouse_context_press(
@@ -148,27 +134,8 @@ class DrawingArea(Gtk.DrawingArea):
         """
         self._flow_graph.update()
 
-    def _handle_window_configure(self, widget, event):
-        """
-        Called when the window is resized.
-        Create a new pixmap for background buffer.
-        """
-        self._pixmap = self.new_pixmap(*self.get_size_request())
-
-    def _handle_window_expose(self, widget, event):
-        """
-        Called when window is exposed, or queue_draw is called.
-        Double buffering: draw to pixmap, then draw pixmap to window.
-        """
-        gc = self.window.new_gc()
-        self._flow_graph.draw(gc, self._pixmap)
-        self.window.draw_drawable(gc, self._pixmap, 0, 0, 0, 0, -1, -1)
-        # draw a light grey line on the bottom and right end of the canvas.
-        # this is useful when the theme uses the same panel bg color as the canvas
-        W, H = self._pixmap.get_size()
-        gc.set_foreground(Colors.FLOWGRAPH_EDGE_COLOR)
-        self.window.draw_line(gc, 0, H-1, W, H-1)
-        self.window.draw_line(gc, W-1, 0, W-1, H)
+    def draw(self, widget, cr):
+        self._flow_graph.draw(widget, cr)
 
     def _handle_focus_lost_event(self, widget, event):
         # don't clear selection while context menu is active
diff --git a/grc/gui/Element.py b/grc/gui/Element.py
index 3f6017def7..30c0f5dba7 100644
--- a/grc/gui/Element.py
+++ b/grc/gui/Element.py
@@ -90,29 +90,33 @@ class Element(object):
         self.clear()
         for child in self.get_children(): child.create_shapes()
 
-    def draw(self, gc, window, border_color, bg_color):
+    def draw(self, widget, cr, border_color, bg_color):
         """
         Draw in the given window.
 
         Args:
-            gc: the graphics context
-            window: the gtk window to draw on
+            widget:
+            cr:
             border_color: the color for lines and rectangle borders
             bg_color: the color for the inside of the rectangle
         """
         X, Y = self.get_coordinate()
-        gc.set_line_attributes(*self.line_attributes)
+        # TODO: gc.set_line_attributes(*self.line_attributes)
         for (rX, rY), (W, H) in self._areas_list:
             aX = X + rX
             aY = Y + rY
-            gc.set_foreground(bg_color)
-            window.draw_rectangle(gc, True, aX, aY, W, H)
-            gc.set_foreground(border_color)
-            window.draw_rectangle(gc, False, aX, aY, W, H)
+            cr.set_source_rgb(*bg_color)
+            cr.rectangle(aX, aY, W, H)
+            cr.fill()
+            cr.set_source_rgb(*border_color)
+            cr.rectangle(aX, aY, W, H)
+            cr.stroke()
+
         for (x1, y1), (x2, y2) in self._lines_list:
-            gc.set_foreground(border_color)
-            gc.set_background(bg_color)
-            window.draw_line(gc, X+x1, Y+y1, X+x2, Y+y2)
+            cr.set_source_rgb(*border_color)
+            cr.move_to(X + x1, Y + y1)
+            cr.line_to(X + x2, Y + y2)
+            cr.stroke()
 
     def rotate(self, rotation):
         """
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index f7af93fe8e..c3ea6770c1 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -397,23 +397,21 @@ class FlowGraph(Element, _Flowgraph):
             changed = True
         return changed
 
-    def draw(self, gc, window):
+    def draw(self, widget, cr):
         """
         Draw the background and grid if enabled.
-        Draw all of the elements in this flow graph onto the pixmap.
-        Draw the pixmap to the drawable window of this flow graph.
         """
 
-        W,H = self.get_size()
-        #draw the background
-        gc.set_foreground(Colors.FLOWGRAPH_BACKGROUND_COLOR)
-        window.draw_rectangle(gc, True, 0, 0, W, H)
+        cr.set_source_rgb(*Colors.FLOWGRAPH_BACKGROUND_COLOR)
+        cr.rectangle(0, 0, *self.get_size())
+        cr.fill()
 
         # draw comments first
         if Actions.TOGGLE_SHOW_BLOCK_COMMENTS.get_active():
             for block in self.blocks:
                 if block.get_enabled():
-                    block.draw_comment(gc, window)
+                    # block.draw_comment(widget, cr)
+                    pass
         #draw multi select rectangle
         if self.mouse_pressed and (not self.get_selected_elements() or self.get_ctrl_mask()):
             #coordinates
@@ -423,10 +421,13 @@ class FlowGraph(Element, _Flowgraph):
             x, y = int(min(x1, x2)), int(min(y1, y2))
             w, h = int(abs(x1 - x2)), int(abs(y1 - y2))
             #draw
-            gc.set_foreground(Colors.HIGHLIGHT_COLOR)
-            window.draw_rectangle(gc, True, x, y, w, h)
-            gc.set_foreground(Colors.BORDER_COLOR)
-            window.draw_rectangle(gc, False, x, y, w, h)
+            cr.set_source_rgb(*Colors.HIGHLIGHT_COLOR)
+            cr.rectangle(x, y, w, h)
+            cr.fill()
+            cr.set_source_rgb(*Colors.BORDER_COLOR)
+            cr.rectangle(x, y, w, h)
+            cr.stroke()
+
         #draw blocks on top of connections
         hide_disabled_blocks = Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active()
         hide_variables = Actions.TOGGLE_HIDE_VARIABLES.get_active()
@@ -437,10 +438,10 @@ class FlowGraph(Element, _Flowgraph):
                 continue  # skip hidden disabled blocks and connections
             if hide_variables and (element.is_variable or element.is_import):
                 continue  # skip hidden disabled blocks and connections
-            element.draw(gc, window)
+            element.draw(widget, cr)
         #draw selected blocks on top of selected connections
         for selected_element in self.get_selected_connections() + self.get_selected_blocks():
-            selected_element.draw(gc, window)
+            selected_element.draw(widget, cr)
 
     def update_selected(self):
         """
diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py
index 649b8b29c5..95a5c89867 100644
--- a/grc/gui/MainWindow.py
+++ b/grc/gui/MainWindow.py
@@ -36,6 +36,7 @@ from .NotebookPage import NotebookPage
 
 from ..core import Messages
 
+
 MAIN_WINDOW_TITLE_TMPL = """\
 #if not $saved
 *#slurp
@@ -104,8 +105,8 @@ class MainWindow(Gtk.Window):
         vbox.pack_start(self.tool_bar, False, False, 0)
 
         # Main parent container for the different panels
-        self.container = Gtk.HPaned()
-        vbox.pack_start(self.container)
+        self.main = Gtk.HPaned() #(orientation=Gtk.Orientation.HORIZONTAL)
+        vbox.pack_start(self.main, True, True, 0)
 
         # Create the notebook
         self.notebook = Gtk.Notebook()
@@ -127,9 +128,9 @@ class MainWindow(Gtk.Window):
         self.vars = VariableEditor(platform, self.get_flow_graph)
 
         # Figure out which place to put the variable editor
-        self.left = Gtk.VPaned()
-        self.right = Gtk.VPaned()
-        self.left_subpanel = Gtk.HPaned()
+        self.left = Gtk.VPaned() #orientation=Gtk.Orientation.VERTICAL)
+        self.right = Gtk.VPaned() #orientation=Gtk.Orientation.VERTICAL)
+        self.left_subpanel = Gtk.HPaned() #orientation=Gtk.Orientation.HORIZONTAL)
 
         self.variable_panel_sidebar = Preferences.variable_editor_sidebar()
         if self.variable_panel_sidebar:
@@ -147,12 +148,12 @@ class MainWindow(Gtk.Window):
             # Create the right panel
             self.right.pack1(self.btwin)
 
-        self.container.pack1(self.left)
-        self.container.pack2(self.right, False)
+        self.main.pack1(self.left)
+        self.main.pack2(self.right, False)
 
         # Load preferences and show the main window
         self.resize(*Preferences.main_window_size())
-        self.container.set_position(Preferences.blocks_window_position())
+        self.main.set_position(Preferences.blocks_window_position())
         self.left.set_position(Preferences.console_window_position())
         if self.variable_panel_sidebar:
             self.right.set_position(Preferences.variable_editor_position(sidebar=True))
@@ -276,9 +277,7 @@ class MainWindow(Gtk.Window):
             return
         #add this page to the notebook
         self.notebook.append_page(page, page.get_tab())
-        try: self.notebook.set_tab_reorderable(page, True)
-        except: pass #gtk too old
-        self.notebook.set_tab_label_packing(page, False, False, Gtk.PACK_START)
+        self.notebook.set_tab_reorderable(page, True)
         #only show if blank or manual
         if not file_path or show: self._set_page(page)
 
@@ -303,7 +302,7 @@ class MainWindow(Gtk.Window):
         Preferences.file_open(open_file)
         Preferences.main_window_size(self.get_size())
         Preferences.console_window_position(self.left.get_position())
-        Preferences.blocks_window_position(self.container.get_position())
+        Preferences.blocks_window_position(self.main.get_position())
         if self.variable_panel_sidebar:
             Preferences.variable_editor_position(self.right.get_position(), sidebar=True)
         else:
@@ -405,14 +404,11 @@ class MainWindow(Gtk.Window):
         Returns:
             the selected flow graph
         """
-        return None
-        # TODO: Issues with flowgraphs
-        #return self.get_page().get_flow_graph()
+        return self.get_page().get_flow_graph()
 
     def get_focus_flag(self):
         """
         Get the focus flag from the current page.
-
         Returns:
             the focus flag
         """
diff --git a/grc/gui/NotebookPage.py b/grc/gui/NotebookPage.py
index 3f49cd0223..9a76897fe6 100644
--- a/grc/gui/NotebookPage.py
+++ b/grc/gui/NotebookPage.py
@@ -54,7 +54,7 @@ class NotebookPage(Gtk.HBox):
         GObject.GObject.__init__(self)
         self.show()
         #tab box to hold label and close button
-        self.tab = Gtk.HBox(False, 0)
+        self.tab = Gtk.HBox(homogeneous=False, spacing=0)
         #setup tab label
         self.label = Gtk.Label()
         self.tab.pack_start(self.label, False, False, 0)
@@ -62,7 +62,7 @@ class NotebookPage(Gtk.HBox):
         image = Gtk.Image()
         image.set_from_stock('gtk-close', Gtk.IconSize.MENU)
         #setup image box
-        image_box = Gtk.HBox(False, 0)
+        image_box = Gtk.HBox(homogeneous=False, spacing=0)
         image_box.pack_start(image, True, False, 0)
         #setup the button
         button = Gtk.Button()
@@ -79,20 +79,18 @@ class NotebookPage(Gtk.HBox):
         self.scrolled_window.set_size_request(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
         self.scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
         self.scrolled_window.connect('key-press-event', self._handle_scroll_window_key_press)
-        #self.drawing_area = DrawingArea(self.get_flow_graph())
-        #self.scrolled_window.add_with_viewport(self.get_drawing_area())
+        self.drawing_area = DrawingArea(self.get_flow_graph())
+        self.scrolled_window.add_with_viewport(self.drawing_area)
         self.pack_start(self.scrolled_window, True, True, 0)
         #inject drawing area into flow graph
-        #self.get_flow_graph().drawing_area = self.get_drawing_area()
+        self.get_flow_graph().drawing_area = self.drawing_area
         self.show_all()
 
-    def get_drawing_area(self): return self.drawing_area
-
     def _handle_scroll_window_key_press(self, widget, event):
         """forward Ctrl-PgUp/Down to NotebookPage (switch fg instead of horiz. scroll"""
         is_ctrl_pg = (
-            event.state & gtk.gdk.CONTROL_MASK and
-            event.keyval in (gtk.keysyms.Page_Up, gtk.keysyms.Page_Down)
+            event.state & Gdk.ModifierType.CONTROL_MASK and
+            event.keyval in (Gdk.KEY_Page_Up, Gdk.KEY_Page_Down)
         )
         if is_ctrl_pg:
             return self.get_parent().event(event)
diff --git a/grc/gui/Port.py b/grc/gui/Port.py
index 02cef5d04b..eb15f6c788 100644
--- a/grc/gui/Port.py
+++ b/grc/gui/Port.py
@@ -17,9 +17,10 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+import math
 import gi
 gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk
+from gi.repository import Gtk, PangoCairo
 
 from . import Actions, Colors, Utils
 from .Constants import (
@@ -45,15 +46,19 @@ class Port(_Port, Element):
         """
         _Port.__init__(self, block, n, dir)
         Element.__init__(self)
-        self.W = self.H = self.w = self.h = 0
+        self.W = self.w = self.h = 0
+        self.H = 20  # todo: fix
         self._connector_coordinate = (0, 0)
         self._connector_length = 0
         self._hovering = True
         self._force_label_unhidden = False
+        self.layout = Gtk.DrawingArea().create_pango_layout('')
+        self._bg_color = Colors.get_color(self.get_color())
 
     def create_shapes(self):
         """Create new areas and labels for the port."""
         Element.create_shapes(self)
+        self._bg_color = Colors.get_color(self.get_color())
         if self.get_hide():
             return  # this port is hidden, no need to create shapes
         if self.get_domain() == GR_MESSAGE_DOMAIN:
@@ -112,50 +117,34 @@ class Port(_Port, Element):
 
     def create_labels(self):
         """Create the labels for the socket."""
-        Element.create_labels(self)
-        self._bg_color = Colors.get_color(self.get_color())
-        # create the layout
-        layout = Gtk.DrawingArea().create_pango_layout('')
-        layout.set_markup(Utils.parse_template(PORT_MARKUP_TMPL, port=self, font=PORT_FONT))
-        self.w, self.h = layout.get_pixel_size()
-        self.W = 2 * PORT_LABEL_PADDING + self.w
-        self.H = 2 * PORT_LABEL_PADDING + self.h * (
-            3 if self.get_type() == 'bus' else 1)
-        self.H += self.H % 2
-        # create the pixmap
-        pixmap = self.get_parent().get_parent().new_pixmap(self.w, self.h)
-        gc = pixmap.new_gc()
-        gc.set_foreground(self._bg_color)
-        pixmap.draw_rectangle(gc, True, 0, 0, self.w, self.h)
-        pixmap.draw_layout(gc, 0, 0, layout)
-        # create vertical and horizontal pixmaps
-        self.horizontal_label = pixmap
-        if self.is_vertical():
-            self.vertical_label = self.get_parent().get_parent().new_pixmap(self.h, self.w)
-            Utils.rotate_pixmap(gc, self.horizontal_label, self.vertical_label)
-
-    def draw(self, gc, window):
+        self.layout.set_markup(Utils.parse_template(PORT_MARKUP_TMPL, port=self, font=PORT_FONT))
+
+    def draw(self, widget, cr):
         """
         Draw the socket with a label.
-
-        Args:
-            gc: the graphics context
-            window: the gtk window to draw on
         """
-        Element.draw(
-            self, gc, window, bg_color=self._bg_color,
-            border_color=self.is_highlighted() and Colors.HIGHLIGHT_COLOR or
-                         self.get_parent().is_dummy_block and Colors.MISSING_BLOCK_BORDER_COLOR or
-                         Colors.BORDER_COLOR,
+        border_color = (
+            Colors.HIGHLIGHT_COLOR if self.is_highlighted() else
+            Colors.MISSING_BLOCK_BORDER_COLOR if self.get_parent().is_dummy_block else
+            Colors.BORDER_COLOR
         )
+        Element.draw(self, widget, cr, border_color, self._bg_color)
+
         if not self._areas_list or self._label_hidden():
             return  # this port is either hidden (no areas) or folded (no label)
         X, Y = self.get_coordinate()
-        (x, y), (w, h) = self._areas_list[0]  # use the first area's sizes to place the labels
+        (x, y), _ = self._areas_list[0]
+        cr.set_source_rgb(*self._bg_color)
+        cr.save()
         if self.is_horizontal():
-            window.draw_drawable(gc, self.horizontal_label, 0, 0, x+X+(self.W-self.w)/2, y+Y+(self.H-self.h)/2, -1, -1)
+            cr.translate(x + X + (self.W - self.w) / 2, y + Y + (self.H - self.h) / 2)
         elif self.is_vertical():
-            window.draw_drawable(gc, self.vertical_label, 0, 0, x+X+(self.H-self.h)/2, y+Y+(self.W-self.w)/2, -1, -1)
+            cr.translate(x + X + (self.H - self.h) / 2, y + Y + (self.W - self.w) / 2)
+            cr.rotate(-90 * math.pi / 180.)
+            cr.translate(-self.w, 0)
+        PangoCairo.update_layout(cr, self.layout)
+        PangoCairo.show_layout(cr, self.layout)
+        cr.restore()
 
     def get_connector_coordinate(self):
         """
diff --git a/grc/gui/VariableEditor.py b/grc/gui/VariableEditor.py
index 7721f3bda6..8729762928 100644
--- a/grc/gui/VariableEditor.py
+++ b/grc/gui/VariableEditor.py
@@ -1,5 +1,5 @@
 """
-Copyright 2015 Free Software Foundation, Inc.
+Copyright 2015, 2016 Free Software Foundation, Inc.
 This file is part of GNU Radio
 
 GNU Radio Companion is free software; you can redistribute it and/or
@@ -19,10 +19,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
 from operator import attrgetter
 
-import pygtk
-pygtk.require('2.0')
-import gtk
-import gobject
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import GObject
 
 from . import Actions
 from . import Preferences
@@ -32,34 +33,34 @@ BLOCK_INDEX = 0
 ID_INDEX = 1
 
 
-class VariableEditorContextMenu(gtk.Menu):
+class VariableEditorContextMenu(Gtk.Menu):
     """ A simple context menu for our variable editor """
     def __init__(self, var_edit):
-        gtk.Menu.__init__(self)
+        Gtk.Menu.__init__(self)
 
-        self.imports = gtk.MenuItem("Add _Import")
+        self.imports = Gtk.MenuItem("Add _Import")
         self.imports.connect('activate', var_edit.handle_action, var_edit.ADD_IMPORT)
         self.add(self.imports)
 
-        self.variables = gtk.MenuItem("Add _Variable")
+        self.variables = Gtk.MenuItem("Add _Variable")
         self.variables.connect('activate', var_edit.handle_action, var_edit.ADD_VARIABLE)
         self.add(self.variables)
-        self.add(gtk.SeparatorMenuItem())
+        self.add(Gtk.SeparatorMenuItem())
 
-        self.enable = gtk.MenuItem("_Enable")
+        self.enable = Gtk.MenuItem("_Enable")
         self.enable.connect('activate', var_edit.handle_action, var_edit.ENABLE_BLOCK)
-        self.disable = gtk.MenuItem("_Disable")
+        self.disable = Gtk.MenuItem("_Disable")
         self.disable.connect('activate', var_edit.handle_action, var_edit.DISABLE_BLOCK)
         self.add(self.enable)
         self.add(self.disable)
-        self.add(gtk.SeparatorMenuItem())
+        self.add(Gtk.SeparatorMenuItem())
 
-        self.delete = gtk.MenuItem("_Delete")
+        self.delete = Gtk.MenuItem("_Delete")
         self.delete.connect('activate', var_edit.handle_action, var_edit.DELETE_BLOCK)
         self.add(self.delete)
-        self.add(gtk.SeparatorMenuItem())
+        self.add(Gtk.SeparatorMenuItem())
 
-        self.properties = gtk.MenuItem("_Properties...")
+        self.properties = Gtk.MenuItem("_Properties...")
         self.properties.connect('activate', var_edit.handle_action, var_edit.OPEN_PROPERTIES)
         self.add(self.properties)
         self.show_all()
@@ -71,7 +72,7 @@ class VariableEditorContextMenu(gtk.Menu):
         self.disable.set_sensitive(selected and enabled)
 
 
-class VariableEditor(gtk.VBox):
+class VariableEditor(Gtk.VBox):
 
     # Actions that are handled by the editor
     ADD_IMPORT = 0
@@ -83,7 +84,7 @@ class VariableEditor(gtk.VBox):
     DISABLE_BLOCK = 6
 
     def __init__(self, platform, get_flow_graph):
-        gtk.VBox.__init__(self)
+        Gtk.VBox.__init__(self)
         self.platform = platform
         self.get_flow_graph = get_flow_graph
         self._block = None
@@ -91,14 +92,14 @@ class VariableEditor(gtk.VBox):
 
         # Only use the model to store the block reference and name.
         # Generate everything else dynamically
-        self.treestore = gtk.TreeStore(gobject.TYPE_PYOBJECT,  # Block reference
-                                       gobject.TYPE_STRING)    # Category and block name
-        self.treeview = gtk.TreeView(self.treestore)
+        self.treestore = Gtk.TreeStore(GObject.TYPE_PYOBJECT,  # Block reference
+                                       GObject.TYPE_STRING)    # Category and block name
+        self.treeview = Gtk.TreeView(self.treestore)
         self.treeview.set_enable_search(False)
         self.treeview.set_search_column(-1)
         #self.treeview.set_enable_search(True)
         #self.treeview.set_search_column(ID_INDEX)
-        self.treeview.get_selection().set_mode('single')
+        self.treeview.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
         self.treeview.set_headers_visible(True)
         self.treeview.connect('button-press-event', self._handle_mouse_button_press)
         self.treeview.connect('button-release-event', self._handle_mouse_button_release)
@@ -106,67 +107,67 @@ class VariableEditor(gtk.VBox):
         self.treeview.connect('key-press-event', self._handle_key_button_press)
 
         # Block Name or Category
-        self.id_cell = gtk.CellRendererText()
+        self.id_cell = Gtk.CellRendererText()
         self.id_cell.connect('edited', self._handle_name_edited_cb)
-        id_column = gtk.TreeViewColumn("Id", self.id_cell, text=ID_INDEX)
+        id_column = Gtk.TreeViewColumn("Id", self.id_cell, text=ID_INDEX)
         id_column.set_name("id")
         id_column.set_resizable(True)
         id_column.set_max_width(300)
         id_column.set_min_width(80)
         id_column.set_fixed_width(100)
-        id_column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
+        id_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
         id_column.set_cell_data_func(self.id_cell, self.set_properties)
         self.id_column = id_column
         self.treeview.append_column(id_column)
-        self.treestore.set_sort_column_id(ID_INDEX, gtk.SORT_ASCENDING)
+        self.treestore.set_sort_column_id(ID_INDEX, Gtk.SortType.ASCENDING)
         # For forcing resize
         self._col_width = 0
 
         # Block Value
-        self.value_cell = gtk.CellRendererText()
+        self.value_cell = Gtk.CellRendererText()
         self.value_cell.connect('edited', self._handle_value_edited_cb)
-        value_column = gtk.TreeViewColumn("Value", self.value_cell)
+        value_column = Gtk.TreeViewColumn("Value", self.value_cell)
         value_column.set_name("value")
         value_column.set_resizable(False)
         value_column.set_expand(True)
         value_column.set_min_width(100)
-        value_column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
+        value_column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
         value_column.set_cell_data_func(self.value_cell, self.set_value)
         self.value_column = value_column
         self.treeview.append_column(value_column)
 
         # Block Actions (Add, Remove)
-        self.action_cell = gtk.CellRendererPixbuf()
+        self.action_cell = Gtk.CellRendererPixbuf()
         value_column.pack_start(self.action_cell, False)
         value_column.set_cell_data_func(self.action_cell, self.set_icon)
 
         # Make the scrolled window to hold the tree view
-        scrolled_window = gtk.ScrolledWindow()
-        scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        scrolled_window = Gtk.ScrolledWindow()
+        scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
         scrolled_window.add_with_viewport(self.treeview)
         scrolled_window.set_size_request(DEFAULT_BLOCKS_WINDOW_WIDTH, -1)
-        self.pack_start(scrolled_window)
+        self.pack_start(scrolled_window, True, True, 0)
 
         # Context menus
         self._context_menu = VariableEditorContextMenu(self)
         self._confirm_delete = Preferences.variable_editor_confirm_delete()
 
     # Sets cell contents
-    def set_icon(self, col, cell, model, iter):
+    def set_icon(self, col, cell, model, iter, data):
         block = model.get_value(iter, BLOCK_INDEX)
         if block:
-            pb = self.treeview.render_icon(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU, None)
+            pb = self.treeview.render_icon(Gtk.STOCK_CLOSE, 16, None)
         else:
-            pb = self.treeview.render_icon(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU, None)
+            pb = self.treeview.render_icon(Gtk.STOCK_ADD, 16, None)
         cell.set_property('pixbuf', pb)
 
-    def set_value(self, col, cell, model, iter):
+    def set_value(self, col, cell, model, iter, data):
         sp = cell.set_property
         block = model.get_value(iter, BLOCK_INDEX)
 
         # Set the default properties for this column first.
         # Some set in set_properties() may be overridden (editable for advanced variable blocks)
-        self.set_properties(col, cell, model, iter)
+        self.set_properties(col, cell, model, iter, data)
 
         # Set defaults
         value = None
@@ -198,7 +199,7 @@ class VariableEditor(gtk.VBox):
         # Always set the text value.
         sp('text', value)
 
-    def set_properties(self, col, cell, model, iter):
+    def set_properties(self, col, cell, model, iter, data):
         sp = cell.set_property
         block = model.get_value(iter, BLOCK_INDEX)
         # Set defaults
@@ -268,13 +269,13 @@ class VariableEditor(gtk.VBox):
         elif key == self.DELETE_CONFIRM:
             if self._confirm_delete:
                 # Create a context menu to confirm the delete operation
-                confirmation_menu = gtk.Menu()
+                confirmation_menu = Gtk.Menu()
                 block_id = self._block.get_param('id').get_value().replace("_", "__")
-                confirm = gtk.MenuItem("Delete {}".format(block_id))
+                confirm = Gtk.MenuItem("Delete {}".format(block_id))
                 confirm.connect('activate', self.handle_action, self.DELETE_BLOCK)
                 confirmation_menu.add(confirm)
                 confirmation_menu.show_all()
-                confirmation_menu.popup(None, None, None, event.button, event.time)
+                confirmation_menu.popup(None, None, None, None, event.button, event.time)
             else:
                 self.handle_action(None, self.DELETE_BLOCK, None)
         elif key == self.ENABLE_BLOCK:
@@ -302,12 +303,12 @@ class VariableEditor(gtk.VBox):
 
             if event.button == 1 and col.get_name() == "value":
                 # Make sure this has a block (not the import/variable rows)
-                if self._block and event.type == gtk.gdk._2BUTTON_PRESS:
+                if self._block and event.type == Gdk.EventType._2BUTTON_PRESS:
                     # Open the advanced dialog if it is a gui variable
                     if self._block.get_key() not in ("variable", "import"):
                         self.handle_action(None, self.OPEN_PROPERTIES, event=event)
                         return True
-                if event.type == gtk.gdk.BUTTON_PRESS:
+                if event.type == Gdk.EventType.BUTTON_PRESS:
                     # User is adding/removing blocks
                     # Make sure this is the action cell (Add/Remove Icons)
                     if path[2] > col.cell_get_position(self.action_cell)[0]:
@@ -320,15 +321,15 @@ class VariableEditor(gtk.VBox):
                         else:
                             self.handle_action(None, self.DELETE_CONFIRM, event=event)
                         return True
-            elif event.button == 3 and event.type == gtk.gdk.BUTTON_PRESS:
+            elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS:
                 if self._block:
                     self._context_menu.update_sensitive(True, enabled=self._block.get_enabled())
                 else:
                     self._context_menu.update_sensitive(False)
-                self._context_menu.popup(None, None, None, event.button, event.time)
+                self._context_menu.popup(None, None, None, None, event.button, event.time)
 
             # Null handler. Stops the treeview from handling double click events.
-            if event.type == gtk.gdk._2BUTTON_PRESS:
+            if event.type == Gdk.EventType._2BUTTON_PRESS:
                 return True
         return False
 
-- 
cgit v1.2.3


From 69da909690bb8bc2072cffb622ddf7bf2e971cce Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Tue, 31 May 2016 17:35:10 +0200
Subject: grc: gtk3: remove canvas size and enable zooming

---
 grc/gui/ActionHandler.py   |  17 ++----
 grc/gui/Block.py           |  70 ++++------------------
 grc/gui/BlockTreeWindow.py |   2 +-
 grc/gui/Constants.py       |   5 +-
 grc/gui/DrawingArea.py     | 126 ++++++++++++++++++++++++++++----------
 grc/gui/FlowGraph.py       | 146 ++++++++++++++++++++++-----------------------
 grc/gui/MainWindow.py      |   4 +-
 grc/gui/NotebookPage.py    |  68 +++++++++------------
 grc/gui/PropsDialog.py     |   4 +-
 grc/gui/Utils.py           |  30 +---------
 10 files changed, 218 insertions(+), 254 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py
index 7dc37cc91d..de635b61fd 100644
--- a/grc/gui/ActionHandler.py
+++ b/grc/gui/ActionHandler.py
@@ -676,7 +676,7 @@ class ActionHandler:
         can_disable = any(block.get_state() != Constants.BLOCK_DISABLED
                           for block in selected_blocks)
         can_bypass_all = all(block.can_bypass() for block in selected_blocks) \
-                          and any (not block.get_bypassed() for block in selected_blocks)
+                         and any(not block.get_bypassed() for block in selected_blocks)
         Actions.BLOCK_ENABLE.set_sensitive(can_enable)
         Actions.BLOCK_DISABLE.set_sensitive(can_disable)
         Actions.BLOCK_BYPASS.set_sensitive(can_bypass_all)
@@ -687,21 +687,16 @@ class ActionHandler:
         Actions.BUSSIFY_SINKS.set_sensitive(bool(selected_blocks))
         Actions.RELOAD_BLOCKS.set_sensitive(True)
         Actions.FIND_BLOCKS.set_sensitive(True)
-        #set the exec and stop buttons
+
         self.update_exec_stop()
-        #saved status
+
         Actions.FLOW_GRAPH_SAVE.set_sensitive(not page.get_saved())
         main.update()
-        try: #set the size of the flow graph area (if changed)
-            new_size = (flow_graph.get_option('window_size') or
-                        self.platform.config.default_canvas_size)
-            if flow_graph.get_size() != tuple(new_size):
-                flow_graph.set_size(*new_size)
-        except: pass
-        #draw the flow graph
+
         flow_graph.update_selected()
         flow_graph.queue_draw()
-        return True #action was handled
+
+        return True  # action was handled
 
     def update_exec_stop(self):
         """
diff --git a/grc/gui/Block.py b/grc/gui/Block.py
index 5e5c5c402c..1b90cf69a0 100644
--- a/grc/gui/Block.py
+++ b/grc/gui/Block.py
@@ -27,8 +27,7 @@ from . import Actions, Colors, Utils
 
 from .Constants import (
     BLOCK_LABEL_PADDING, PORT_SPACING, PORT_SEPARATION, LABEL_SEPARATION,
-    PORT_BORDER_SEPARATION, POSSIBLE_ROTATIONS, BLOCK_FONT, PARAM_FONT,
-    BORDER_PROXIMITY_SENSITIVITY
+    PORT_BORDER_SEPARATION, POSSIBLE_ROTATIONS, BLOCK_FONT, PARAM_FONT
 )
 from . Element import Element
 from ..core.Param import num_to_str
@@ -64,22 +63,13 @@ class Block(Element, _Block):
         Returns:
             the coordinate tuple (x, y) or (0, 0) if failure
         """
-        try: #should evaluate to tuple
-            coor = eval(self.get_param('_coordinate').get_value())
-            x, y = map(int, coor)
-            fgW,fgH = self.get_parent().get_size()
-            if x <= 0:
-                x = 0
-            elif x >= fgW - BORDER_PROXIMITY_SENSITIVITY:
-                x = fgW - BORDER_PROXIMITY_SENSITIVITY
-            if y <= 0:
-                y = 0
-            elif y >= fgH - BORDER_PROXIMITY_SENSITIVITY:
-                y = fgH - BORDER_PROXIMITY_SENSITIVITY
-            return (x, y)
+        try:
+            coor = self.get_param('_coordinate').get_value()  # should evaluate to tuple
+            coor = tuple(int(x) for x in coor[1:-1].split(','))
         except:
-            self.set_coordinate((0, 0))
-            return (0, 0)
+            coor = 0, 0
+            self.set_coordinate(coor)
+        return coor
 
     def set_coordinate(self, coor):
         """
@@ -94,41 +84,7 @@ class Block(Element, _Block):
                 Utils.align_to_grid(coor[0] + offset_x) - offset_x,
                 Utils.align_to_grid(coor[1] + offset_y) - offset_y
             )
-        self.get_param('_coordinate').set_value(str(coor))
-
-    def bound_move_delta(self, delta_coor):
-        """
-        Limit potential moves from exceeding the bounds of the canvas
-
-        Args:
-            delta_coor: requested delta coordinate (dX, dY) to move
-
-        Returns:
-            The delta coordinate possible to move while keeping the block on the canvas
-            or the input (dX, dY) on failure
-        """
-        dX, dY = delta_coor
-
-        try:
-            fgW, fgH = self.get_parent().get_size()
-            x, y = map(int, eval(self.get_param("_coordinate").get_value()))
-            if self.is_horizontal():
-               sW, sH = self.W, self.H
-            else:
-               sW, sH = self.H, self.W
-
-            if x + dX < 0:
-                dX = -x
-            elif dX + x + sW >= fgW:
-                dX = fgW - x - sW
-            if y + dY < 0:
-                dY = -y
-            elif dY + y + sH >= fgH:
-               dY = fgH - y - sH
-        except:
-            pass
-
-        return ( dX, dY )
+        self.get_param('_coordinate').set_value(repr(coor))
 
     def get_rotation(self):
         """
@@ -138,11 +94,11 @@ class Block(Element, _Block):
             the rotation in degrees or 0 if failure
         """
         try: #should evaluate to dict
-            rotation = eval(self.get_param('_rotation').get_value())
-            return int(rotation)
+            rotation = int(self.get_param('_rotation').get_value())
         except:
-            self.set_rotation(POSSIBLE_ROTATIONS[0])
-            return POSSIBLE_ROTATIONS[0]
+            rotation = POSSIBLE_ROTATIONS[0]
+            self.set_rotation(rotation)
+        return rotation
 
     def set_rotation(self, rot):
         """
@@ -151,7 +107,7 @@ class Block(Element, _Block):
         Args:
             rot: the rotation in degrees
         """
-        self.get_param('_rotation').set_value(str(rot))
+        self.get_param('_rotation').set_value(repr(int(rot)))
 
     def create_shapes(self):
         """Update the block, parameters, and ports when a change occurs."""
diff --git a/grc/gui/BlockTreeWindow.py b/grc/gui/BlockTreeWindow.py
index e8de6e9277..9a147bd8be 100644
--- a/grc/gui/BlockTreeWindow.py
+++ b/grc/gui/BlockTreeWindow.py
@@ -281,5 +281,5 @@ class BlockTreeWindow(Gtk.VBox):
         Handle the mouse button press.
         If a left double click is detected, call add selected block.
         """
-        if event.button == 1 and event.type == Gdk._2BUTTON_PRESS:
+        if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
             self._add_selected_block()
diff --git a/grc/gui/Constants.py b/grc/gui/Constants.py
index 4ab644e8a1..55739a7c03 100644
--- a/grc/gui/Constants.py
+++ b/grc/gui/Constants.py
@@ -86,11 +86,8 @@ CONNECTOR_ARROW_HEIGHT = 17
 # possible rotations in degrees
 POSSIBLE_ROTATIONS = (0, 90, 180, 270)
 
-# How close can the mouse get to the window border before mouse events are ignored.
-BORDER_PROXIMITY_SENSITIVITY = 50
-
 # How close the mouse can get to the edge of the visible window before scrolling is invoked.
-SCROLL_PROXIMITY_SENSITIVITY = 30
+SCROLL_PROXIMITY_SENSITIVITY = 50
 
 # When the window has to be scrolled, move it this distance in the required direction.
 SCROLL_DISTANCE = 15
diff --git a/grc/gui/DrawingArea.py b/grc/gui/DrawingArea.py
index 2bce21fa6d..d1e0e78634 100644
--- a/grc/gui/DrawingArea.py
+++ b/grc/gui/DrawingArea.py
@@ -17,15 +17,10 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
-import gi
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GObject
+from gi.repository import Gtk, Gdk, GObject
 
 
-from Constants import MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT, DND_TARGETS
-import Colors
+from . import Constants, Colors
 
 
 class DrawingArea(Gtk.DrawingArea):
@@ -42,11 +37,16 @@ class DrawingArea(Gtk.DrawingArea):
         Args:
             main_window: the main_window containing all flow graphs
         """
+        Gtk.DrawingArea.__init__(self)
+
+        self._flow_graph = flow_graph
+
+        self.zoom_factor = 1.0
         self.ctrl_mask = False
         self.mod1_mask = False
-        self._flow_graph = flow_graph
-        GObject.GObject.__init__(self)
-        self.set_size_request(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
+        self.button_state = [False] * 10
+
+        # self.set_size_request(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
         self.connect('realize', self._handle_window_realize)
         self.connect('draw', self.draw)
         self.connect('motion-notify-event', self._handle_mouse_motion)
@@ -54,24 +54,28 @@ class DrawingArea(Gtk.DrawingArea):
         self.connect('button-release-event', self._handle_mouse_button_release)
         self.connect('scroll-event', self._handle_mouse_scroll)
         self.add_events(
-            Gdk.EventMask.BUTTON_PRESS_MASK | \
-            Gdk.EventMask.POINTER_MOTION_MASK | \
-            Gdk.EventMask.BUTTON_RELEASE_MASK | \
-            Gdk.EventMask.LEAVE_NOTIFY_MASK | \
-            Gdk.EventMask.ENTER_NOTIFY_MASK #| \
+            Gdk.EventMask.BUTTON_PRESS_MASK |
+            Gdk.EventMask.POINTER_MOTION_MASK |
+            Gdk.EventMask.BUTTON_RELEASE_MASK |
+            Gdk.EventMask.SCROLL_MASK |
+            Gdk.EventMask.LEAVE_NOTIFY_MASK |
+            Gdk.EventMask.ENTER_NOTIFY_MASK
             #Gdk.EventMask.FOCUS_CHANGE_MASK
         )
-        #setup drag and drop
+
+        # setup drag and drop
         self.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY)
         self.connect('drag-data-received', self._handle_drag_data_received)
         self.drag_dest_set_target_list(None)
         self.drag_dest_add_text_targets()
-        #setup the focus flag
+
+        # setup the focus flag
         self._focus_flag = False
         self.get_focus_flag = lambda: self._focus_flag
         def _handle_notify_event(widget, event, focus_flag): self._focus_flag = focus_flag
         self.connect('leave-notify-event', _handle_notify_event, False)
         self.connect('enter-notify-event', _handle_notify_event, True)
+        # todo: fix
 #        self.set_flags(Gtk.CAN_FOCUS)  # self.set_can_focus(True)
 #        self.connect('focus-out-event', self._handle_focus_lost_event)
 
@@ -86,10 +90,21 @@ class DrawingArea(Gtk.DrawingArea):
 
     def _handle_mouse_scroll(self, widget, event):
         if event.get_state() & Gdk.ModifierType.SHIFT_MASK:
-           if event.direction == Gdk.ScrollDirection.UP:
-               event.direction = Gdk.ScrollDirection.LEFT
-           else:
-               event.direction = Gdk.ScrollDirection.RIGHT
+            if event.direction == Gdk.ScrollDirection.UP:
+                event.direction = Gdk.ScrollDirection.LEFT
+            else:
+                event.direction = Gdk.ScrollDirection.RIGHT
+
+        elif event.get_state() & Gdk.ModifierType.CONTROL_MASK:
+            change = 1.2 if event.direction == Gdk.ScrollDirection.UP else 1/1.2
+            zoom_factor = min(max(self.zoom_factor * change, 0.1), 5.0)
+
+            if zoom_factor != self.zoom_factor:
+                self.zoom_factor = zoom_factor
+                self.queue_draw()
+            return True
+
+        return False
 
     def _handle_mouse_button_press(self, widget, event):
         """
@@ -98,14 +113,18 @@ class DrawingArea(Gtk.DrawingArea):
         self.grab_focus()
         self.ctrl_mask = event.get_state() & Gdk.ModifierType.CONTROL_MASK
         self.mod1_mask = event.get_state() & Gdk.ModifierType.MOD1_MASK
-        if event.button == 1: self._flow_graph.handle_mouse_selector_press(
-            double_click=(event.type == Gdk.EventType._2BUTTON_PRESS),
-            coordinate=(event.x, event.y),
-        )
-        if event.button == 3: self._flow_graph.handle_mouse_context_press(
-            coordinate=(event.x, event.y),
-            event=event,
-        )
+        self.button_state[event.button] = True
+
+        if event.button == 1:
+            self._flow_graph.handle_mouse_selector_press(
+                double_click=(event.type == Gdk.EventType._2BUTTON_PRESS),
+                coordinate=self._translate_event_coords(event),
+            )
+        elif event.button == 3:
+            self._flow_graph.handle_mouse_context_press(
+                coordinate=self._translate_event_coords(event),
+                event=event,
+            )
 
     def _handle_mouse_button_release(self, widget, event):
         """
@@ -113,9 +132,11 @@ class DrawingArea(Gtk.DrawingArea):
         """
         self.ctrl_mask = event.get_state() & Gdk.ModifierType.CONTROL_MASK
         self.mod1_mask = event.get_state() & Gdk.ModifierType.MOD1_MASK
-        if event.button == 1: self._flow_graph.handle_mouse_selector_release(
-            coordinate=(event.x, event.y),
-        )
+        self.button_state[event.button] = False
+        if event.button == 1:
+            self._flow_graph.handle_mouse_selector_release(
+                coordinate=self._translate_event_coords(event),
+            )
 
     def _handle_mouse_motion(self, widget, event):
         """
@@ -123,20 +144,59 @@ class DrawingArea(Gtk.DrawingArea):
         """
         self.ctrl_mask = event.get_state() & Gdk.ModifierType.CONTROL_MASK
         self.mod1_mask = event.get_state() & Gdk.ModifierType.MOD1_MASK
+
+        if self.button_state[1]:
+            self._auto_scroll(event)
+
         self._flow_graph.handle_mouse_motion(
-            coordinate=(event.x, event.y),
+            coordinate=self._translate_event_coords(event),
+            button1_pressed=self.button_state[1]
         )
 
+    def _auto_scroll(self, event):
+        x, y = event.x, event.y
+        scrollbox = self.get_parent().get_parent()
+
+        w, h = self._flow_graph.get_max_coords(initial=(x, y))
+        self.set_size_request(w + 100, h + 100)
+
+        def scroll(pos, adj):
+            """scroll if we moved near the border"""
+            adj_val = adj.get_value()
+            adj_len = adj.get_page_size()
+            if pos - adj_val > adj_len - Constants.SCROLL_PROXIMITY_SENSITIVITY:
+                adj.set_value(adj_val + Constants.SCROLL_DISTANCE)
+                adj.emit('changed')
+            elif pos - adj_val < Constants.SCROLL_PROXIMITY_SENSITIVITY:
+                adj.set_value(adj_val - Constants.SCROLL_DISTANCE)
+                adj.emit('changed')
+
+        scroll(x, scrollbox.get_hadjustment())
+        scroll(y, scrollbox.get_vadjustment())
+
     def _handle_window_realize(self, widget):
         """
         Called when the window is realized.
         Update the flowgraph, which calls new pixmap.
         """
         self._flow_graph.update()
+        w, h = self._flow_graph.get_max_coords()
+        self.set_size_request(w + 100, h + 100)
 
     def draw(self, widget, cr):
+        width = widget.get_allocated_width()
+        height = widget.get_allocated_height()
+        cr.set_source_rgb(*Colors.FLOWGRAPH_BACKGROUND_COLOR)
+        cr.rectangle(0, 0, width, height)
+
+        cr.scale(self.zoom_factor, self.zoom_factor)
+        cr.fill()
+
         self._flow_graph.draw(widget, cr)
 
+    def _translate_event_coords(self, event):
+        return event.x / self.zoom_factor, event.y / self.zoom_factor
+
     def _handle_focus_lost_event(self, widget, event):
         # don't clear selection while context menu is active
         if not self._flow_graph.get_context_menu().flags() & Gtk.VISIBLE:
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index c3ea6770c1..802c54f7a7 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -76,9 +76,10 @@ class FlowGraph(Element, _Flowgraph):
         Returns:
             a unique id
         """
+        block_ids = set(b.get_id() for b in self.blocks)
         for index in count():
             block_id = '{}_{}'.format(base_id, index)
-            if block_id not in (b.get_id() for b in self.blocks):
+            if block_id not in block_ids:
                 break
         return block_id
 
@@ -128,7 +129,7 @@ class FlowGraph(Element, _Flowgraph):
     def queue_draw(self): self.get_drawing_area().queue_draw()
     def get_size(self): return self.get_drawing_area().get_size_request()
     def set_size(self, *args): self.get_drawing_area().set_size_request(*args)
-    def get_scroll_pane(self): return self.drawing_area.get_parent()
+    def get_scroll_pane(self): return self.drawing_area.get_parent().get_parent()
     def get_ctrl_mask(self): return self.drawing_area.ctrl_mask
     def get_mod1_mask(self): return self.drawing_area.mod1_mask
     def new_pixmap(self, *args): return self.get_drawing_area().new_pixmap(*args)
@@ -146,8 +147,8 @@ class FlowGraph(Element, _Flowgraph):
         h_adj = self.get_scroll_pane().get_hadjustment()
         v_adj = self.get_scroll_pane().get_vadjustment()
         if coor is None: coor = (
-            int(random.uniform(.25, .75)*h_adj.page_size + h_adj.get_value()),
-            int(random.uniform(.25, .75)*v_adj.page_size + v_adj.get_value()),
+            int(random.uniform(.25, .75)*h_adj.get_page_size() + h_adj.get_value()),
+            int(random.uniform(.25, .75)*v_adj.get_page_size() + v_adj.get_value()),
         )
         #get the new block
         block = self.new_block(key)
@@ -169,7 +170,8 @@ class FlowGraph(Element, _Flowgraph):
         """
         #get selected blocks
         blocks = self.get_selected_blocks()
-        if not blocks: return None
+        if not blocks:
+            return None
         #calc x and y min
         x_min, y_min = blocks[0].get_coordinate()
         for block in blocks:
@@ -281,7 +283,8 @@ class FlowGraph(Element, _Flowgraph):
         """
         changed = False
         for selected_block in self.get_selected_blocks():
-            if selected_block.set_enabled(enable): changed = True
+            if selected_block.set_enabled(enable):
+                changed = True
         return changed
 
     def bypass_selected(self):
@@ -295,7 +298,8 @@ class FlowGraph(Element, _Flowgraph):
         """
         changed = False
         for selected_block in self.get_selected_blocks():
-            if selected_block.set_bypassed(): changed = True
+            if selected_block.set_bypassed():
+                changed = True
         return changed
 
     def move_selected(self, delta_coordinate):
@@ -305,9 +309,6 @@ class FlowGraph(Element, _Flowgraph):
         Args:
             delta_coordinate: the change in coordinates
         """
-        for selected_block in self.get_selected_blocks():
-            delta_coordinate = selected_block.bound_move_delta(delta_coordinate)
-
         for selected_block in self.get_selected_blocks():
             selected_block.move(delta_coordinate)
             self.element_moved = True
@@ -401,26 +402,18 @@ class FlowGraph(Element, _Flowgraph):
         """
         Draw the background and grid if enabled.
         """
-
-        cr.set_source_rgb(*Colors.FLOWGRAPH_BACKGROUND_COLOR)
-        cr.rectangle(0, 0, *self.get_size())
-        cr.fill()
-
         # draw comments first
         if Actions.TOGGLE_SHOW_BLOCK_COMMENTS.get_active():
             for block in self.blocks:
                 if block.get_enabled():
                     # block.draw_comment(widget, cr)
                     pass
-        #draw multi select rectangle
+        # draw multi select rectangle
         if self.mouse_pressed and (not self.get_selected_elements() or self.get_ctrl_mask()):
-            #coordinates
             x1, y1 = self.press_coor
             x2, y2 = self.get_coordinate()
-            #calculate top-left coordinate and width/height
             x, y = int(min(x1, x2)), int(min(y1, y2))
             w, h = int(abs(x1 - x2)), int(abs(y1 - y2))
-            #draw
             cr.set_source_rgb(*Colors.HIGHLIGHT_COLOR)
             cr.rectangle(x, y, w, h)
             cr.fill()
@@ -428,7 +421,7 @@ class FlowGraph(Element, _Flowgraph):
             cr.rectangle(x, y, w, h)
             cr.stroke()
 
-        #draw blocks on top of connections
+        # draw blocks on top of connections
         hide_disabled_blocks = Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active()
         hide_variables = Actions.TOGGLE_HIDE_VARIABLES.get_active()
         blocks = sorted(self.blocks, key=methodcaller('get_enabled'))
@@ -439,7 +432,8 @@ class FlowGraph(Element, _Flowgraph):
             if hide_variables and (element.is_variable or element.is_import):
                 continue  # skip hidden disabled blocks and connections
             element.draw(widget, cr)
-        #draw selected blocks on top of selected connections
+
+        # draw selected blocks on top of selected connections
         for selected_element in self.get_selected_connections() + self.get_selected_blocks():
             selected_element.draw(widget, cr)
 
@@ -666,7 +660,7 @@ class FlowGraph(Element, _Flowgraph):
             self.mouse_pressed = True
             self.update_selected_elements()
             self.mouse_pressed = False
-        self._context_menu.popup(None, None, None, event.button, event.time)
+        self._context_menu.popup(None, None, None, None, event.button, event.time)
 
     def handle_mouse_selector_press(self, double_click, coordinate):
         """
@@ -677,11 +671,12 @@ class FlowGraph(Element, _Flowgraph):
         """
         self.press_coor = coordinate
         self.set_coordinate(coordinate)
-        self.time = 0
         self.mouse_pressed = True
-        if double_click: self.unselect()
+
+        if double_click:
+            self.unselect()
         self.update_selected_elements()
-        #double click detected, bring up params dialog if possible
+
         if double_click and self.get_selected_block():
             self.mouse_pressed = False
             Actions.BLOCK_PARAM_MODIFY()
@@ -693,69 +688,70 @@ class FlowGraph(Element, _Flowgraph):
         And update the selected flowgraph elements.
         """
         self.set_coordinate(coordinate)
-        self.time = 0
         self.mouse_pressed = False
         if self.element_moved:
             Actions.BLOCK_MOVE()
             self.element_moved = False
         self.update_selected_elements()
 
-    def handle_mouse_motion(self, coordinate):
+    def handle_mouse_motion(self, coordinate, button1_pressed):
         """
         The mouse has moved, respond to mouse dragging or notify elements
         Move a selected element to the new coordinate.
         Auto-scroll the scroll bars at the boundaries.
         """
-        #to perform a movement, the mouse must be pressed
+        # to perform a movement, the mouse must be pressed
         # (no longer checking pending events via Gtk.events_pending() - always true in Windows)
-        if not self.mouse_pressed:
-            # only continue if mouse-over stuff is enabled (just the auto-hide port label stuff for now)
-            if not Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active(): return
-            redraw = False
-            for element in reversed(self.get_elements()):
-                over_element = element.what_is_selected(coordinate)
-                if not over_element: continue
-                if over_element != self.element_under_mouse:  # over sth new
-                    if self.element_under_mouse:
-                        redraw |= self.element_under_mouse.mouse_out() or False
-                    self.element_under_mouse = over_element
-                    redraw |= over_element.mouse_over() or False
-                break
-            else:
+        if not button1_pressed:
+            self._handle_mouse_motion_move(coordinate)
+        else:
+            self._handle_mouse_motion_drag(coordinate)
+
+    def _handle_mouse_motion_move(self, coordinate):
+        # only continue if mouse-over stuff is enabled (just the auto-hide port label stuff for now)
+        if not Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active():
+            return
+        redraw = False
+        for element in reversed(self.get_elements()):
+            over_element = element.what_is_selected(coordinate)
+            if not over_element:
+                continue
+            if over_element != self.element_under_mouse:  # over sth new
                 if self.element_under_mouse:
                     redraw |= self.element_under_mouse.mouse_out() or False
-                    self.element_under_mouse = None
-            if redraw:
-                #self.create_labels()
-                self.create_shapes()
-                self.queue_draw()
+                self.element_under_mouse = over_element
+                redraw |= over_element.mouse_over() or False
+            break
         else:
-            #perform auto-scrolling
-            width, height = self.get_size()
-            x, y = coordinate
-            h_adj = self.get_scroll_pane().get_hadjustment()
-            v_adj = self.get_scroll_pane().get_vadjustment()
-            for pos, length, adj, adj_val, adj_len in (
-                (x, width, h_adj, h_adj.get_value(), h_adj.page_size),
-                (y, height, v_adj, v_adj.get_value(), v_adj.page_size),
-            ):
-                #scroll if we moved near the border
-                if pos-adj_val > adj_len-SCROLL_PROXIMITY_SENSITIVITY and adj_val+SCROLL_DISTANCE < length-adj_len:
-                    adj.set_value(adj_val+SCROLL_DISTANCE)
-                    adj.emit('changed')
-                elif pos-adj_val < SCROLL_PROXIMITY_SENSITIVITY:
-                    adj.set_value(adj_val-SCROLL_DISTANCE)
-                    adj.emit('changed')
-            #remove the connection if selected in drag event
-            if len(self.get_selected_elements()) == 1 and self.get_selected_element().is_connection:
-                Actions.ELEMENT_DELETE()
-            #move the selected elements and record the new coordinate
-            if not self.get_ctrl_mask():
-                X, Y = self.get_coordinate()
-                dX, dY = int(x - X), int(y - Y)
-                active = Actions.TOGGLE_SNAP_TO_GRID.get_active() or self.get_mod1_mask()
-                if not active or abs(dX) >= Utils.CANVAS_GRID_SIZE or abs(dY) >= Utils.CANVAS_GRID_SIZE:
-                    self.move_selected((dX, dY))
-                    self.set_coordinate((x, y))
-            #queue draw for animation
+            if self.element_under_mouse:
+                redraw |= self.element_under_mouse.mouse_out() or False
+                self.element_under_mouse = None
+        if redraw:
+            # self.create_labels()
+            self.create_shapes()
             self.queue_draw()
+
+    def _handle_mouse_motion_drag(self, coordinate):
+        # remove the connection if selected in drag event
+        if len(self.get_selected_elements()) == 1 and self.get_selected_element().is_connection:
+            Actions.ELEMENT_DELETE()
+
+        # move the selected elements and record the new coordinate
+        x, y = coordinate
+        if not self.get_ctrl_mask():
+            X, Y = self.get_coordinate()
+            dX, dY = int(x - X), int(y - Y)
+            active = Actions.TOGGLE_SNAP_TO_GRID.get_active() or self.get_mod1_mask()
+            if not active or abs(dX) >= Utils.CANVAS_GRID_SIZE or abs(dY) >= Utils.CANVAS_GRID_SIZE:
+                self.move_selected((dX, dY))
+                self.set_coordinate((x, y))
+        # queue draw for animation
+        self.queue_draw()
+
+    def get_max_coords(self, initial=(0, 0)):
+        w, h = initial
+        for block in self.blocks:
+            x, y = block.get_coordinate()
+            w = max(w, x + block.W)
+            h = max(h, y + block.H)
+        return w, h
diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py
index 751e0d9b3f..126a9afff9 100644
--- a/grc/gui/MainWindow.py
+++ b/grc/gui/MainWindow.py
@@ -241,14 +241,14 @@ class MainWindow(Gtk.Window):
                 file_path=file_path,
             )
             if file_path: Messages.send_end_load()
-        except Exception, e: #return on failure
+        except Exception as e: #return on failure
             Messages.send_fail_load(e)
             if isinstance(e, KeyError) and str(e) == "'options'":
                 # This error is unrecoverable, so crash gracefully
                 exit(-1)
             return
         #add this page to the notebook
-        self.notebook.append_page(page, page.get_tab())
+        self.notebook.append_page(page, page.tab)
         self.notebook.set_tab_reorderable(page, True)
         #only show if blank or manual
         if not file_path or show: self._set_page(page)
diff --git a/grc/gui/NotebookPage.py b/grc/gui/NotebookPage.py
index 9a76897fe6..e15617aec9 100644
--- a/grc/gui/NotebookPage.py
+++ b/grc/gui/NotebookPage.py
@@ -17,17 +17,15 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
-import gi
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk
-from gi.repository import GObject
-
-import Actions
-from StateCache import StateCache
-from Constants import MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT
-from DrawingArea import DrawingArea
 import os
 
+from gi.repository import Gtk, Gdk, GObject
+
+from . import Actions
+from .StateCache import StateCache
+from .Constants import MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT
+from .DrawingArea import DrawingArea
+
 
 class NotebookPage(Gtk.HBox):
     """A page in the notebook."""
@@ -40,49 +38,46 @@ class NotebookPage(Gtk.HBox):
             main_window: main window
             file_path: path to a flow graph file
         """
+        Gtk.HBox.__init__(self)
+
+        self.main_window = main_window
         self._flow_graph = flow_graph
         self.process = None
-        #import the file
-        self.main_window = main_window
+
+        # import the file
         self.file_path = file_path
         initial_state = flow_graph.get_parent().parse_flow_graph(file_path)
+        self.get_flow_graph().import_data(initial_state)
         self.state_cache = StateCache(initial_state)
         self.saved = True
-        #import the data to the flow graph
-        self.get_flow_graph().import_data(initial_state)
-        #initialize page gui
-        GObject.GObject.__init__(self)
-        self.show()
-        #tab box to hold label and close button
-        self.tab = Gtk.HBox(homogeneous=False, spacing=0)
-        #setup tab label
+
+        # tab box to hold label and close button
         self.label = Gtk.Label()
-        self.tab.pack_start(self.label, False, False, 0)
-        #setup button image
         image = Gtk.Image()
         image.set_from_stock('gtk-close', Gtk.IconSize.MENU)
-        #setup image box
         image_box = Gtk.HBox(homogeneous=False, spacing=0)
         image_box.pack_start(image, True, False, 0)
-        #setup the button
         button = Gtk.Button()
         button.connect("clicked", self._handle_button)
         button.set_relief(Gtk.ReliefStyle.NONE)
         button.add(image_box)
-        #button size
-        #w, h = Gtk.icon_size_lookup_for_settings(button.get_settings(), Gtk.IconSize.MENU)
-        #button.set_size_request(w+6, h+6)
-        self.tab.pack_start(button, False, False, 0)
-        self.tab.show_all()
-        #setup scroll window and drawing area
+
+        tab = self.tab = Gtk.HBox(homogeneous=False, spacing=0)
+        tab.pack_start(self.label, False, False, 0)
+        tab.pack_start(button, False, False, 0)
+        tab.show_all()
+
+        # setup scroll window and drawing area
+        self.drawing_area = DrawingArea(self.get_flow_graph())
+
         self.scrolled_window = Gtk.ScrolledWindow()
         self.scrolled_window.set_size_request(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
-        self.scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
+        self.scrolled_window.set_policy(Gtk.PolicyType.ALWAYS, Gtk.PolicyType.ALWAYS)
         self.scrolled_window.connect('key-press-event', self._handle_scroll_window_key_press)
-        self.drawing_area = DrawingArea(self.get_flow_graph())
         self.scrolled_window.add_with_viewport(self.drawing_area)
         self.pack_start(self.scrolled_window, True, True, 0)
-        #inject drawing area into flow graph
+
+        # inject drawing area into flow graph
         self.get_flow_graph().drawing_area = self.drawing_area
         self.show_all()
 
@@ -125,15 +120,6 @@ class NotebookPage(Gtk.HBox):
         """
         self.label.set_markup(markup)
 
-    def get_tab(self):
-        """
-        Get the gtk widget for this page's tab.
-
-        Returns:
-            gtk widget
-        """
-        return self.tab
-
     def get_proc(self):
         """
         Get the subprocess for the flow graph.
diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py
index ca41abc003..d6b64944cc 100644
--- a/grc/gui/PropsDialog.py
+++ b/grc/gui/PropsDialog.py
@@ -87,7 +87,9 @@ class PropsDialog(Gtk.Dialog):
             self._code_text_display = code_view = SimpleTextDisplay()
             code_view.set_wrap_mode(Gtk.WrapMode.NONE)
             code_view.get_buffer().create_tag('b', weight=Pango.Weight.BOLD)
-            code_view.override_font(Pango.FontDescription('monospace %d' % Constants.FONT_SIZE))
+            code_view.set_monospace(True)
+            # todo: set font size in non-deprecated way
+            # code_view.override_font(Pango.FontDescription('monospace %d' % Constants.FONT_SIZE))
             code_box = Gtk.ScrolledWindow()
             code_box.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
             code_box.add_with_viewport(self._code_text_display)
diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py
index e5d69bcf54..311b37f468 100644
--- a/grc/gui/Utils.py
+++ b/grc/gui/Utils.py
@@ -19,36 +19,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
 import gi
 gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GdkPixbuf
-from gi.repository import GObject
 from gi.repository import GLib
 
-from Constants import POSSIBLE_ROTATIONS, CANVAS_GRID_SIZE
-
-
-def rotate_pixmap(gc, src_pixmap, dst_pixmap, angle=GdkPixbuf.PixbufRotation.COUNTERCLOCKWISE):
-    """
-    Load the destination pixmap with a rotated version of the source pixmap.
-    The source pixmap will be loaded into a pixbuf, rotated, and drawn to the destination pixmap.
-    The pixbuf is a client-side drawable, where a pixmap is a server-side drawable.
-
-    Args:
-        gc: the graphics context
-        src_pixmap: the source pixmap
-        dst_pixmap: the destination pixmap
-        angle: the angle to rotate by
-    """
-    width, height = src_pixmap.get_size()
-    pixbuf = GdkPixbuf.Pixbuf(
-        colorspace=GdkPixbuf.Colorspace.RGB,
-        has_alpha=False, bits_per_sample=8,
-        width=width, height=height,
-    )
-    pixbuf.get_from_drawable(src_pixmap, src_pixmap.get_colormap(), 0, 0, 0, 0, -1, -1)
-    pixbuf = pixbuf.rotate_simple(angle)
-    dst_pixmap.draw_pixbuf(gc, pixbuf, 0, 0, 0, 0)
+from .Constants import POSSIBLE_ROTATIONS, CANVAS_GRID_SIZE
 
 
 def get_rotated_coordinate(coor, rotation):
@@ -97,7 +70,6 @@ def encode(value):
     Older versions of glib seg fault if the last byte starts a multi-byte
     character.
     """
-
     valid_utf8 = value.decode('utf-8', errors='replace').encode('utf-8')
     return GLib.markup_escape_text(valid_utf8)
 
-- 
cgit v1.2.3


From 94c4606edd30dc8b1278580782f2809b69f04641 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Fri, 3 Jun 2016 10:02:36 +0200
Subject: grc: py3k compat using python-modernize

---
 grc/checks.py                        |  1 +
 grc/core/Block.py                    | 33 +++++++++--------
 grc/core/Config.py                   |  1 +
 grc/core/Connection.py               |  8 +++--
 grc/core/Constants.py                | 10 ++++--
 grc/core/Element.py                  |  6 ++--
 grc/core/FlowGraph.py                | 57 +++++++++++++++--------------
 grc/core/Messages.py                 |  3 +-
 grc/core/Param.py                    | 69 ++++++++++++++++++------------------
 grc/core/ParseXML.py                 | 21 +++++++----
 grc/core/Platform.py                 | 34 ++++++++++--------
 grc/core/Port.py                     | 41 +++++++++++----------
 grc/core/generator/FlowGraphProxy.py | 16 +++++----
 grc/core/generator/Generator.py      | 30 +++++++++-------
 grc/core/generator/__init__.py       |  3 +-
 grc/core/utils/__init__.py           | 10 +++---
 grc/core/utils/complexity.py         | 13 +++----
 grc/core/utils/epy_block_io.py       | 11 ++++--
 grc/core/utils/expr_utils.py         | 21 ++++++-----
 grc/core/utils/extract_docs.py       | 28 ++++++++-------
 grc/core/utils/odict.py              |  2 ++
 grc/gui/ActionHandler.py             |  8 +++--
 grc/gui/Actions.py                   | 14 +++++---
 grc/gui/Bars.py                      |  3 +-
 grc/gui/Block.py                     |  3 +-
 grc/gui/BlockTreeWindow.py           | 14 +++++---
 grc/gui/Colors.py                    |  1 +
 grc/gui/Config.py                    |  7 ++--
 grc/gui/Connection.py                | 16 ++++-----
 grc/gui/Constants.py                 |  1 +
 grc/gui/Dialogs.py                   |  1 +
 grc/gui/DrawingArea.py               |  1 +
 grc/gui/Element.py                   |  6 ++--
 grc/gui/Executor.py                  |  1 +
 grc/gui/FileDialogs.py               |  9 ++---
 grc/gui/FlowGraph.py                 | 14 +++++---
 grc/gui/MainWindow.py                |  6 ++--
 grc/gui/NotebookPage.py              |  1 +
 grc/gui/Param.py                     |  1 +
 grc/gui/ParamWidgets.py              |  1 +
 grc/gui/ParserErrorsDialog.py        |  8 +++--
 grc/gui/Platform.py                  |  4 ++-
 grc/gui/Port.py                      |  8 ++---
 grc/gui/Preferences.py               | 21 ++++++-----
 grc/gui/PropsDialog.py               |  6 ++--
 grc/gui/StateCache.py                |  5 +--
 grc/gui/Utils.py                     |  8 +++--
 grc/gui/VariableEditor.py            |  6 ++--
 grc/gui/external_editor.py           |  6 ++--
 grc/main.py                          |  1 +
 50 files changed, 356 insertions(+), 243 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/checks.py b/grc/checks.py
index 66c114d723..40a0b2b270 100755
--- a/grc/checks.py
+++ b/grc/checks.py
@@ -15,6 +15,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
+from __future__ import absolute_import
 import os
 import warnings
 
diff --git a/grc/core/Block.py b/grc/core/Block.py
index aafc5db6f1..062598e9d1 100644
--- a/grc/core/Block.py
+++ b/grc/core/Block.py
@@ -17,9 +17,13 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
+
 import collections
 import itertools
 
+from six.moves import map, range
+
 from Cheetah.Template import Template
 
 from .utils import epy_block_io, odict
@@ -70,7 +74,7 @@ class Block(Element):
             self._flags += BLOCK_FLAG_THROTTLE
 
         self._doc = (n.find('doc') or '').strip('\n').replace('\\\n', '')
-        self._imports = map(lambda i: i.strip(), n.findall('import'))
+        self._imports = [i.strip() for i in n.findall('import')]
         self._make = n.find('make')
         self._var_make = n.find('var_make')
         self._var_value = n.find('var_value') or '$value'
@@ -250,7 +254,7 @@ class Block(Element):
             self.back_ofthe_bus(ports)
             # Renumber non-message/message ports
             domain_specific_port_index = collections.defaultdict(int)
-            for port in filter(lambda p: p.get_key().isdigit(), ports):
+            for port in [p for p in ports if p.get_key().isdigit()]:
                 domain = port.get_domain()
                 port._key = str(domain_specific_port_index[domain])
                 domain_specific_port_index[domain] += 1
@@ -275,7 +279,8 @@ class Block(Element):
         """
         if raw:
             return self._imports
-        return filter(lambda i: i, sum(map(lambda i: self.resolve_dependencies(i).split('\n'), self._imports), []))
+        return [i for i in sum([self.resolve_dependencies(i).split('\n')
+                                for i in self._imports], []) if i]
 
     def get_make(self, raw=False):
         if raw:
@@ -300,7 +305,7 @@ class Block(Element):
             if 'self.' in callback:
                 return callback
             return 'self.{}.{}'.format(self.get_id(), callback)
-        return map(make_callback, self._callbacks)
+        return [make_callback(c) for c in self._callbacks]
 
     def is_virtual_sink(self):
         return self.get_key() == 'virtual_sink'
@@ -622,10 +627,10 @@ class Block(Element):
         """
         changed = False
         type_param = None
-        for param in filter(lambda p: p.is_enum(), self.get_params()):
+        for param in [p for p in self.get_params() if p.is_enum()]:
             children = self.get_ports() + self.get_params()
             # Priority to the type controller
-            if param.get_key() in ' '.join(map(lambda p: p._type, children)): type_param = param
+            if param.get_key() in ' '.join([p._type for p in children]): type_param = param
             # Use param if type param is unset
             if not type_param:
                 type_param = param
@@ -681,10 +686,10 @@ class Block(Element):
         """
         n = odict()
         n['key'] = self.get_key()
-        n['param'] = map(lambda p: p.export_data(), sorted(self.get_params(), key=str))
-        if 'bus' in map(lambda a: a.get_type(), self.get_sinks()):
+        n['param'] = [p.export_data() for p in sorted(self.get_params(), key=str)]
+        if 'bus' in [a.get_type() for a in self.get_sinks()]:
             n['bus_sink'] = str(1)
-        if 'bus' in map(lambda a: a.get_type(), self.get_sources()):
+        if 'bus' in [a.get_type() for a in self.get_sources()]:
             n['bus_source'] = str(1)
         return n
 
@@ -773,12 +778,12 @@ class Block(Element):
             get_p_gui = self.get_sinks_gui
             bus_structure = self.get_bus_structure('sink')
 
-        struct = [range(len(get_p()))]
-        if True in map(lambda a: isinstance(a.get_nports(), int), get_p()):
+        struct = [list(range(len(get_p())))]
+        if True in [isinstance(a.get_nports(), int) for a in get_p()]:
             structlet = []
             last = 0
             for j in [i.get_nports() for i in get_p() if isinstance(i.get_nports(), int)]:
-                structlet.extend(map(lambda a: a+last, range(j)))
+                structlet.extend([a+last for a in range(j)])
                 last = structlet[-1] + 1
                 struct = [structlet]
         if bus_structure:
@@ -802,7 +807,7 @@ class Block(Element):
             for connect in elt.get_connections():
                 self.get_parent().remove_element(connect)
 
-        if ('bus' not in map(lambda a: a.get_type(), get_p())) and len(get_p()) > 0:
+        if ('bus' not in [a.get_type() for a in get_p()]) and len(get_p()) > 0:
             struct = self.form_bus_structure(direc)
             self.current_bus_structure[direc] = struct
             if get_p()[0].get_nports():
@@ -813,7 +818,7 @@ class Block(Element):
                 n = odict(n)
                 port = self.get_parent().get_parent().Port(block=self, n=n, dir=direc)
                 get_p().append(port)
-        elif 'bus' in map(lambda a: a.get_type(), get_p()):
+        elif 'bus' in [a.get_type() for a in get_p()]:
             for elt in get_p_gui():
                 get_p().remove(elt)
             self.current_bus_structure[direc] = ''
diff --git a/grc/core/Config.py b/grc/core/Config.py
index ac38d9978c..400d5d365f 100644
--- a/grc/core/Config.py
+++ b/grc/core/Config.py
@@ -17,6 +17,7 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
 import os
 from os.path import expanduser, normpath, expandvars, exists
 
diff --git a/grc/core/Connection.py b/grc/core/Connection.py
index 3aa32ef183..ddc6c0256f 100644
--- a/grc/core/Connection.py
+++ b/grc/core/Connection.py
@@ -17,6 +17,10 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
+
+from six.moves import range
+
 from . import Constants
 from .Element import Element
 from .utils import odict
@@ -51,8 +55,8 @@ class Connection(Element):
             raise ValueError('Connection could not isolate source')
         if not sink:
             raise ValueError('Connection could not isolate sink')
-        busses = len(filter(lambda a: a.get_type() == 'bus', [source, sink])) % 2
-        if not busses == 0:
+
+        if (source.get_type() == 'bus') != (sink.get_type() == 'bus'):
             raise ValueError('busses must get with busses')
 
         if not len(source.get_associated_ports()) == len(sink.get_associated_ports()):
diff --git a/grc/core/Constants.py b/grc/core/Constants.py
index 4f278bb22d..8a99f8b256 100644
--- a/grc/core/Constants.py
+++ b/grc/core/Constants.py
@@ -17,10 +17,14 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
+
 import os
-import numpy
 import stat
 
+import numpy
+import six
+
 # Data files
 DATA_DIR = os.path.dirname(__file__)
 FLOW_GRAPH_DTD = os.path.join(DATA_DIR, 'flow_graph.dtd')
@@ -63,7 +67,7 @@ HIER_BLOCK_FILE_MODE = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP
 VECTOR_TYPES = (tuple, list, set, numpy.ndarray)
 COMPLEX_TYPES = [complex, numpy.complex, numpy.complex64, numpy.complex128]
 REAL_TYPES = [float, numpy.float, numpy.float32, numpy.float64]
-INT_TYPES = [int, long, numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.uint64,
+INT_TYPES = [int, numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.uint64,
              numpy.uint, numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64]
 # Cast to tuple for isinstance, concat subtypes
 COMPLEX_TYPES = tuple(COMPLEX_TYPES + REAL_TYPES + INT_TYPES)
@@ -129,6 +133,6 @@ for name, key, sizeof, color in CORE_TYPES:
     TYPE_TO_COLOR[key] = color
     TYPE_TO_SIZEOF[key] = sizeof
 
-for key, (sizeof, color) in ALIAS_TYPES.iteritems():
+for key, (sizeof, color) in six.iteritems(ALIAS_TYPES):
     TYPE_TO_COLOR[key] = color
     TYPE_TO_SIZEOF[key] = sizeof
diff --git a/grc/core/Element.py b/grc/core/Element.py
index 67c36e12b4..e697d293fb 100644
--- a/grc/core/Element.py
+++ b/grc/core/Element.py
@@ -22,7 +22,7 @@ class Element(object):
 
     def __init__(self, parent=None):
         self._parent = parent
-        self._error_messages = list()
+        self._error_messages = []
 
     ##################################################
     # Element Validation API
@@ -64,7 +64,9 @@ class Element(object):
             a list of error message strings
         """
         error_messages = list(self._error_messages)  # Make a copy
-        for child in filter(lambda c: c.get_enabled() and not c.get_bypassed(), self.get_children()):
+        for child in self.get_children():
+            if not child.get_enabled() or child.get_bypassed():
+                continue
             for msg in child.get_error_messages():
                 error_messages.append("{}:\n\t{}".format(child, msg.replace("\n", "\n\t")))
         return error_messages
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index 949eecaa71..9edd4f24d8 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -15,12 +15,15 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
+from __future__ import absolute_import, print_function
+
 import imp
 import time
-from itertools import ifilter, chain
+import re
+from itertools import chain
 from operator import methodcaller, attrgetter
 
-import re
+from six.moves import filter
 
 from . import Messages
 from .Constants import FLOW_GRAPH_FILE_FORMAT_VERSION
@@ -87,7 +90,7 @@ class FlowGraph(Element):
         Returns:
             a sorted list of variable blocks in order of dependency (indep -> dep)
         """
-        variables = filter(attrgetter('is_variable'), self.iter_enabled_blocks())
+        variables = list(filter(attrgetter('is_variable'), self.iter_enabled_blocks()))
         return expr_utils.sort_objects(variables, methodcaller('get_id'), methodcaller('get_var_make'))
 
     def get_parameters(self):
@@ -97,15 +100,14 @@ class FlowGraph(Element):
         Returns:
             a list of parameterized variables
         """
-        parameters = filter(lambda b: _parameter_matcher.match(b.get_key()), self.iter_enabled_blocks())
+        parameters = [b for b in self.iter_enabled_blocks() if _parameter_matcher.match(b.get_key())]
         return parameters
 
     def get_monitors(self):
         """
         Get a list of all ControlPort monitors
         """
-        monitors = filter(lambda b: _monitors_searcher.search(b.get_key()),
-                          self.iter_enabled_blocks())
+        monitors = [b for b in self.iter_enabled_blocks() if _monitors_searcher.search(b.get_key())]
         return monitors
 
     def get_python_modules(self):
@@ -115,7 +117,7 @@ class FlowGraph(Element):
                 yield block.get_id(), block.get_param('source_code').get_value()
 
     def get_bussink(self):
-        bussink = filter(lambda b: _bussink_searcher.search(b.get_key()), self.get_enabled_blocks())
+        bussink = [b for b in self.get_enabled_blocks() if _bussink_searcher.search(b.get_key())]
 
         for i in bussink:
             for j in i.get_params():
@@ -124,7 +126,7 @@ class FlowGraph(Element):
         return False
 
     def get_bussrc(self):
-        bussrc = filter(lambda b: _bussrc_searcher.search(b.get_key()), self.get_enabled_blocks())
+        bussrc = [b for b in self.get_enabled_blocks() if _bussrc_searcher.search(b.get_key())]
 
         for i in bussrc:
             for j in i.get_params():
@@ -133,18 +135,18 @@ class FlowGraph(Element):
         return False
 
     def get_bus_structure_sink(self):
-        bussink = filter(lambda b: _bus_struct_sink_searcher.search(b.get_key()), self.get_enabled_blocks())
+        bussink = [b for b in self.get_enabled_blocks() if _bus_struct_sink_searcher.search(b.get_key())]
         return bussink
 
     def get_bus_structure_src(self):
-        bussrc = filter(lambda b: _bus_struct_src_searcher.search(b.get_key()), self.get_enabled_blocks())
+        bussrc = [b for b in self.get_enabled_blocks() if _bus_struct_src_searcher.search(b.get_key())]
         return bussrc
 
     def iter_enabled_blocks(self):
         """
         Get an iterator of all blocks that are enabled and not bypassed.
         """
-        return ifilter(methodcaller('get_enabled'), self.blocks)
+        return filter(methodcaller('get_enabled'), self.blocks)
 
     def get_enabled_blocks(self):
         """
@@ -162,7 +164,7 @@ class FlowGraph(Element):
         Returns:
             a list of blocks
         """
-        return filter(methodcaller('get_bypassed'), self.blocks)
+        return list(filter(methodcaller('get_bypassed'), self.blocks))
 
     def get_enabled_connections(self):
         """
@@ -171,7 +173,7 @@ class FlowGraph(Element):
         Returns:
             a list of connections
         """
-        return filter(methodcaller('get_enabled'), self.connections)
+        return list(filter(methodcaller('get_enabled'), self.connections))
 
     def get_option(self, key):
         """
@@ -206,7 +208,7 @@ class FlowGraph(Element):
         options_block_count = self.blocks.count(self._options_block)
         if not options_block_count:
             self.blocks.append(self._options_block)
-        for i in range(options_block_count-1):
+        for _ in range(options_block_count-1):
             self.blocks.remove(self._options_block)
 
         return self.blocks + self.connections
@@ -229,14 +231,14 @@ class FlowGraph(Element):
         # Load imports
         for expr in self.get_imports():
             try:
-                exec expr in namespace
+                exec(expr, namespace)
             except:
                 pass
 
         for id, expr in self.get_python_modules():
             try:
                 module = imp.new_module(id)
-                exec expr in module.__dict__
+                exec(expr, module.__dict__)
                 namespace[id] = module
             except:
                 pass
@@ -333,15 +335,15 @@ class FlowGraph(Element):
         if element in self.blocks:
             # Remove block, remove all involved connections
             for port in element.get_ports():
-                map(self.remove_element, port.get_connections())
+                for connection in port.get_connections():
+                    self.remove_element(connection)
             self.blocks.remove(element)
 
         elif element in self.connections:
             if element.is_bus():
-                cons_list = []
-                for i in map(lambda a: a.get_connections(), element.get_source().get_associated_ports()):
-                    cons_list.extend(i)
-                map(self.remove_element, cons_list)
+                for port in element.get_source().get_associated_ports():
+                    for connection in port.get_connections():
+                        self.remove_element(connection)
             self.connections.remove(element)
 
     ##############################################
@@ -484,21 +486,19 @@ class FlowGraph(Element):
                     get_p_gui = block.get_sinks_gui
                     bus_structure = block.form_bus_structure('sink')
 
-                if 'bus' in map(lambda a: a.get_type(), get_p_gui()):
+                if 'bus' in [a.get_type() for a in get_p_gui()]:
                     if len(get_p_gui()) > len(bus_structure):
-                        times = range(len(bus_structure), len(get_p_gui()))
+                        times = list(range(len(bus_structure), len(get_p_gui())))
                         for i in times:
                             for connect in get_p_gui()[-1].get_connections():
                                 block.get_parent().remove_element(connect)
                             get_p().remove(get_p_gui()[-1])
                     elif len(get_p_gui()) < len(bus_structure):
                         n = {'name': 'bus', 'type': 'bus'}
-                        if True in map(
-                                lambda a: isinstance(a.get_nports(), int),
-                                get_p()):
+                        if any(isinstance(a.get_nports(), int) for a in get_p()):
                             n['nports'] = str(1)
 
-                        times = range(len(get_p_gui()), len(bus_structure))
+                        times = list(range(len(get_p_gui()), len(bus_structure)))
 
                         for i in times:
                             n['key'] = str(len(get_p()))
@@ -507,8 +507,7 @@ class FlowGraph(Element):
                                 block=block, n=n, dir=direc)
                             get_p().append(port)
 
-                if 'bus' in map(lambda a: a.get_type(),
-                                block.get_sources_gui()):
+                if 'bus' in [a.get_type() for a in block.get_sources_gui()]:
                     for i in range(len(block.get_sources_gui())):
                         if len(block.get_sources_gui()[
                                    i].get_connections()) > 0:
diff --git a/grc/core/Messages.py b/grc/core/Messages.py
index 8daa12c33f..596b6197d8 100644
--- a/grc/core/Messages.py
+++ b/grc/core/Messages.py
@@ -16,9 +16,10 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
 
+from __future__ import absolute_import
+
 import traceback
 import sys
-import os
 
 #  A list of functions that can receive a message.
 MESSENGERS_LIST = list()
diff --git a/grc/core/Param.py b/grc/core/Param.py
index 73d54b6aff..45f0187d27 100644
--- a/grc/core/Param.py
+++ b/grc/core/Param.py
@@ -17,20 +17,20 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
+
 import ast
 import weakref
 import re
 
+from six.moves import builtins, filter, map, range, zip
+
 from . import Constants
-from .Constants import VECTOR_TYPES, COMPLEX_TYPES, REAL_TYPES, INT_TYPES
 from .Element import Element
 from .utils import odict
 
 # Blacklist certain ids, its not complete, but should help
-import __builtin__
-
-
-ID_BLACKLIST = ['self', 'options', 'gr', 'blks2', 'wxgui', 'wx', 'math', 'forms', 'firdes'] + dir(__builtin__)
+ID_BLACKLIST = ['self', 'options', 'gr', 'blks2', 'wxgui', 'wx', 'math', 'forms', 'firdes'] + dir(builtins)
 try:
     from gnuradio import gr
     ID_BLACKLIST.extend(attr for attr in dir(gr.top_block()) if not attr.startswith('_'))
@@ -64,7 +64,7 @@ def num_to_str(num):
                 return template.format(value / factor, symbol.strip())
         return template.format(value, '')
 
-    if isinstance(num, COMPLEX_TYPES):
+    if isinstance(num, Constants.COMPLEX_TYPES):
         num = complex(num)  # Cast to python complex
         if num == 0:
             return '0'
@@ -112,13 +112,13 @@ class Option(Element):
     # Access Opts
     ##############################################
     def get_opt_keys(self):
-        return self._opts.keys()
+        return list(self._opts.keys())
 
     def get_opt(self, key):
         return self._opts[key]
 
     def get_opts(self):
-        return self._opts.values()
+        return list(self._opts.values())
 
 
 class TemplateArg(object):
@@ -176,7 +176,8 @@ class Param(Element):
         # Create the Option objects from the n data
         self._options = list()
         self._evaluated = None
-        for option in map(lambda o: Option(param=self, n=o), n.findall('option')):
+        for o_n in n.findall('option'):
+            option = Option(param=self, n=o_n)
             key = option.get_key()
             # Test against repeated keys
             if key in self.get_option_keys():
@@ -257,9 +258,9 @@ class Param(Element):
         t = self.get_type()
         if isinstance(e, bool):
             return str(e)
-        elif isinstance(e, COMPLEX_TYPES):
+        elif isinstance(e, Constants.COMPLEX_TYPES):
             dt_str = num_to_str(e)
-        elif isinstance(e, VECTOR_TYPES):
+        elif isinstance(e, Constants.VECTOR_TYPES):
             # Vector types
             if len(e) > 8:
                 # Large vectors use code
@@ -310,13 +311,10 @@ class Param(Element):
         if self.get_key() == 'id' and not _show_id_matcher.match(self.get_parent().get_key()):
             return 'part'
         # Hide port controllers for type and nports
-        if self.get_key() in ' '.join(map(lambda p: ' '.join([p._type, p._nports]),
-                                          self.get_parent().get_ports())):
+        if self.get_key() in ' '.join([' '.join([p._type, p._nports]) for p in self.get_parent().get_ports()]):
             return 'part'
         # Hide port controllers for vlen, when == 1
-        if self.get_key() in ' '.join(map(
-            lambda p: p._vlen, self.get_parent().get_ports())
-        ):
+        if self.get_key() in ' '.join(p._vlen for p in self.get_parent().get_ports()):
             try:
                 if int(self.get_evaluated()) == 1:
                     return 'part'
@@ -339,7 +337,7 @@ class Param(Element):
         self._evaluated = None
         try:
             self._evaluated = self.evaluate()
-        except Exception, e:
+        except Exception as e:
             self.add_error_message(str(e))
 
     def get_evaluated(self):
@@ -372,21 +370,21 @@ class Param(Element):
             # Raise exception if python cannot evaluate this value
             try:
                 e = self.get_parent().get_parent().evaluate(v)
-            except Exception, e:
+            except Exception as e:
                 raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e))
             # Raise an exception if the data is invalid
             if t == 'raw':
                 return e
             elif t == 'complex':
-                if not isinstance(e, COMPLEX_TYPES):
+                if not isinstance(e, Constants.COMPLEX_TYPES):
                     raise Exception('Expression "{}" is invalid for type complex.'.format(str(e)))
                 return e
             elif t == 'real' or t == 'float':
-                if not isinstance(e, REAL_TYPES):
+                if not isinstance(e, Constants.REAL_TYPES):
                     raise Exception('Expression "{}" is invalid for type float.'.format(str(e)))
                 return e
             elif t == 'int':
-                if not isinstance(e, INT_TYPES):
+                if not isinstance(e, Constants.INT_TYPES):
                     raise Exception('Expression "{}" is invalid for type integer.'.format(str(e)))
                 return e
             elif t == 'hex':
@@ -407,28 +405,28 @@ class Param(Element):
             # Raise exception if python cannot evaluate this value
             try:
                 e = self.get_parent().get_parent().evaluate(v)
-            except Exception, e:
+            except Exception as e:
                 raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e))
             # Raise an exception if the data is invalid
             if t == 'complex_vector':
-                if not isinstance(e, VECTOR_TYPES):
+                if not isinstance(e, Constants.VECTOR_TYPES):
                     self._lisitify_flag = True
                     e = [e]
-                if not all([isinstance(ei, COMPLEX_TYPES) for ei in e]):
+                if not all([isinstance(ei, Constants.COMPLEX_TYPES) for ei in e]):
                     raise Exception('Expression "{}" is invalid for type complex vector.'.format(str(e)))
                 return e
             elif t == 'real_vector' or t == 'float_vector':
-                if not isinstance(e, VECTOR_TYPES):
+                if not isinstance(e, Constants.VECTOR_TYPES):
                     self._lisitify_flag = True
                     e = [e]
-                if not all([isinstance(ei, REAL_TYPES) for ei in e]):
+                if not all([isinstance(ei, Constants.REAL_TYPES) for ei in e]):
                     raise Exception('Expression "{}" is invalid for type float vector.'.format(str(e)))
                 return e
             elif t == 'int_vector':
-                if not isinstance(e, VECTOR_TYPES):
+                if not isinstance(e, Constants.VECTOR_TYPES):
                     self._lisitify_flag = True
                     e = [e]
-                if not all([isinstance(ei, INT_TYPES) for ei in e]):
+                if not all([isinstance(ei, Constants.INT_TYPES) for ei in e]):
                     raise Exception('Expression "{}" is invalid for type integer vector.'.format(str(e)))
                 return e
         #########################
@@ -545,7 +543,7 @@ class Param(Element):
                 for c in range(col_span):
                     self._hostage_cells.append((my_parent, (row+r, col+c)))
             # Avoid collisions
-            params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
+            params = [p for p in self.get_all_params('grid_pos') if p is not self]
             for param in params:
                 for parent, cell in param._hostage_cells:
                     if (parent, cell) in self._hostage_cells:
@@ -560,7 +558,7 @@ class Param(Element):
                 return ''
 
             # Get a list of all notebooks
-            notebook_blocks = filter(lambda b: b.get_key() == 'notebook', self.get_parent().get_parent().get_enabled_blocks())
+            notebook_blocks = [b for b in self.get_parent().get_parent().get_enabled_blocks() if b.get_key() == 'notebook']
             # Check for notebook param syntax
             try:
                 notebook_id, page_index = map(str.strip, v.split(','))
@@ -568,7 +566,7 @@ class Param(Element):
                 raise Exception('Bad notebook page format.')
             # Check that the notebook id is valid
             try:
-                notebook_block = filter(lambda b: b.get_id() == notebook_id, notebook_blocks)[0]
+                notebook_block = [b for b in notebook_blocks if b.get_id() == notebook_id][0]
             except:
                 raise Exception('Notebook id "{}" is not an existing notebook id.'.format(notebook_id))
 
@@ -584,12 +582,12 @@ class Param(Element):
             # New namespace
             n = dict()
             try:
-                exec v in n
+                exec(v, n)
             except ImportError:
                 raise Exception('Import "{}" failed.'.format(v))
             except Exception:
                 raise Exception('Bad import syntax: "{}".'.format(v))
-            return filter(lambda k: str(k) != '__builtins__', n.keys())
+            return [k for k in list(n.keys()) if str(k) != '__builtins__']
 
         #########################
         else:
@@ -635,7 +633,10 @@ class Param(Element):
         Returns:
             a list of params
         """
-        return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])
+        params = []
+        for block in self.get_parent().get_parent().get_enabled_blocks():
+            params.extend(p for p in block.get_params() if p.get_type() == type)
+        return params
 
     def is_enum(self):
         return self._type == 'enum'
diff --git a/grc/core/ParseXML.py b/grc/core/ParseXML.py
index c9f6541ee7..d1306fcab4 100644
--- a/grc/core/ParseXML.py
+++ b/grc/core/ParseXML.py
@@ -17,8 +17,13 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
+
 from lxml import etree
 
+import six
+from six.moves import map
+
 from .utils import odict
 
 xml_failures = {}
@@ -80,7 +85,9 @@ def from_file(xml_file):
     # Get the embedded instructions and build a dictionary item
     nested_data['_instructions'] = {}
     xml_instructions = xml.xpath('/processing-instruction()')
-    for inst in filter(lambda i: i.target == 'grc', xml_instructions):
+    for inst in xml_instructions:
+        if inst.target != 'grc':
+            continue
         nested_data['_instructions'] = odict(inst.attrib)
     return nested_data
 
@@ -100,13 +107,13 @@ def _from_file(xml):
         return odict({tag: xml.text or ''})  # store empty tags (text is None) as empty string
     nested_data = odict()
     for elem in xml:
-        key, value = _from_file(elem).items()[0]
+        key, value = list(_from_file(elem).items())[0]
         if key in nested_data:
             nested_data[key].append(value)
         else:
             nested_data[key] = [value]
     # Delistify if the length of values is 1
-    for key, values in nested_data.iteritems():
+    for key, values in six.iteritems(nested_data):
         if len(values) == 1:
             nested_data[key] = values[0]
 
@@ -127,7 +134,7 @@ def to_file(nested_data, xml_file):
     if instructions:
         xml_data += etree.tostring(etree.ProcessingInstruction(
             'grc', ' '.join(
-                "{0}='{1}'".format(*item) for item in instructions.iteritems())
+                "{0}='{1}'".format(*item) for item in six.iteritems(instructions))
         ), xml_declaration=True, pretty_print=True, encoding='utf-8')
     xml_data += etree.tostring(_to_file(nested_data)[0],
                                pretty_print=True, encoding='utf-8')
@@ -146,14 +153,14 @@ def _to_file(nested_data):
         the xml tree filled with child nodes
     """
     nodes = list()
-    for key, values in nested_data.iteritems():
+    for key, values in six.iteritems(nested_data):
         # Listify the values if not a list
         if not isinstance(values, (list, set, tuple)):
             values = [values]
         for value in values:
             node = etree.Element(key)
-            if isinstance(value, (str, unicode)):
-                node.text = unicode(value)
+            if isinstance(value, (str, six.text_type)):
+                node.text = six.text_type(value)
             else:
                 node.extend(_to_file(value))
             nodes.append(node)
diff --git a/grc/core/Platform.py b/grc/core/Platform.py
index 02a625bbf4..25f415639a 100644
--- a/grc/core/Platform.py
+++ b/grc/core/Platform.py
@@ -17,6 +17,8 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
+from __future__ import print_function
 import os
 import sys
 
@@ -32,6 +34,9 @@ from .Port import Port
 from .Param import Param
 
 from .utils import odict, extract_docs
+import six
+from six.moves import map
+from six.moves import range
 
 
 class Platform(Element):
@@ -156,7 +161,7 @@ class Platform(Element):
                 # print >> sys.stderr, 'Warning: Block validation failed:\n\t%s\n\tIgnoring: %s' % (e, xml_file)
                 pass
             except Exception as e:
-                print >> sys.stderr, 'Warning: XML parsing failed:\n\t%r\n\tIgnoring: %s' % (e, xml_file)
+                print('Warning: XML parsing failed:\n\t%r\n\tIgnoring: %s' % (e, xml_file), file=sys.stderr)
 
         self._docstring_extractor.finish()
         # self._docstring_extractor.wait()
@@ -168,7 +173,7 @@ class Platform(Element):
                 yield block_path
             elif os.path.isdir(block_path):
                 for dirpath, dirnames, filenames in os.walk(block_path):
-                    for filename in sorted(filter(lambda f: f.endswith('.xml'), filenames)):
+                    for filename in sorted(f for f in filenames if f.endswith('.xml')):
                         yield os.path.join(dirpath, filename)
 
     def load_block_xml(self, xml_file):
@@ -181,7 +186,7 @@ class Platform(Element):
         block = self.Block(self._flow_graph, n)
         key = block.get_key()
         if key in self.blocks:
-            print >> sys.stderr, 'Warning: Block with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file)
+            print('Warning: Block with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file), file=sys.stderr)
         else:  # Store the block
             self.blocks[key] = block
             self._blocks_n[key] = n
@@ -205,13 +210,13 @@ class Platform(Element):
 
         key = n.find('key')
         if not key:
-            print >> sys.stderr, 'Warning: Domain with emtpy key.\n\tIgnoring: {}'.format(xml_file)
+            print('Warning: Domain with emtpy key.\n\tIgnoring: {}'.format(xml_file), file=sys.stderr)
             return
         if key in self.domains:  # test against repeated keys
-            print >> sys.stderr, 'Warning: Domain with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file)
+            print('Warning: Domain with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file), file=sys.stderr)
             return
 
-        #to_bool = lambda s, d: d if s is None else s.lower() not in ('false', 'off', '0', '')
+        # to_bool = lambda s, d: d if s is None else s.lower() not in ('false', 'off', '0', '')
         def to_bool(s, d):
             if s is not None:
                 return s.lower() not in ('false', 'off', '0', '')
@@ -223,7 +228,7 @@ class Platform(Element):
             tuple(int(color[o:o + 2], 16) / 255.0 for o in range(1, 3 * chars_per_color, chars_per_color))
         except ValueError:
             if color:  # no color is okay, default set in GUI
-                print >> sys.stderr, 'Warning: Can\'t parse color code "{}" for domain "{}" '.format(color, key)
+                print('Warning: Can\'t parse color code "{}" for domain "{}" '.format(color, key), file=sys.stderr)
                 color = None
 
         self.domains[key] = dict(
@@ -235,9 +240,9 @@ class Platform(Element):
         for connection_n in n.findall('connection'):
             key = (connection_n.find('source_domain'), connection_n.find('sink_domain'))
             if not all(key):
-                print >> sys.stderr, 'Warning: Empty domain key(s) in connection template.\n\t{}'.format(xml_file)
+                print('Warning: Empty domain key(s) in connection template.\n\t{}'.format(xml_file), file=sys.stderr)
             elif key in self.connection_templates:
-                print >> sys.stderr, 'Warning: Connection template "{}" already exists.\n\t{}'.format(key, xml_file)
+                print('Warning: Connection template "{}" already exists.\n\t{}'.format(key, xml_file), file=sys.stderr)
             else:
                 self.connection_templates[key] = connection_n.find('make') or ''
 
@@ -256,11 +261,12 @@ class Platform(Element):
             parent = (parent or []) + [cat_n.find('name')]
             block_tree.add_block(parent)
             # Recursive call to load sub categories
-            map(lambda c: load_category(c, parent), cat_n.findall('cat'))
+            for cat in cat_n.findall('cat'):
+                load_category(cat, parent)
             # Add blocks in this category
             for block_key in cat_n.findall('block'):
                 if block_key not in self.blocks:
-                    print >> sys.stderr, 'Warning: Block key "{}" not found when loading category tree.'.format(block_key)
+                    print('Warning: Block key "{}" not found when loading category tree.'.format(block_key), file=sys.stderr)
                     continue
                 block = self.blocks[block_key]
                 # If it exists, the block's category shall not be overridden by the xml tree
@@ -272,7 +278,7 @@ class Platform(Element):
             load_category(category_tree_n)
 
         # Add blocks to block tree
-        for block in self.blocks.itervalues():
+        for block in six.itervalues(self.blocks):
             # Blocks with empty categories are hidden
             if not block.get_category():
                 continue
@@ -280,7 +286,7 @@ class Platform(Element):
 
     def _save_docstring_extraction_result(self, key, docstrings):
         docs = {}
-        for match, docstring in docstrings.iteritems():
+        for match, docstring in six.iteritems(docstrings):
             if not docstring or match.endswith('_sptr'):
                 continue
             docstring = docstring.replace('\n\n', '\n').strip()
@@ -312,7 +318,7 @@ class Platform(Element):
         return self.FlowGraph(platform=self)
 
     def get_blocks(self):
-        return self.blocks.values()
+        return list(self.blocks.values())
 
     def get_new_block(self, flow_graph, key):
         return self.Block(flow_graph, n=self._blocks_n[key])
diff --git a/grc/core/Port.py b/grc/core/Port.py
index 6a8f484082..a24262da6b 100644
--- a/grc/core/Port.py
+++ b/grc/core/Port.py
@@ -17,7 +17,10 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
-from .Constants import DEFAULT_DOMAIN, GR_STREAM_DOMAIN, GR_MESSAGE_DOMAIN
+from __future__ import absolute_import
+
+from six.moves import filter
+
 from .Element import Element
 
 from . import Constants
@@ -47,13 +50,13 @@ def _get_source_from_virtual_source_port(vsp, traversed=[]):
     try:
         return _get_source_from_virtual_source_port(
             _get_source_from_virtual_sink_port(
-                filter(  # Get all virtual sinks with a matching stream id
+                list(filter(  # Get all virtual sinks with a matching stream id
                     lambda vs: vs.get_param('stream_id').get_value() == vsp.get_parent().get_param('stream_id').get_value(),
-                    filter(  # Get all enabled blocks that are also virtual sinks
+                    list(filter(  # Get all enabled blocks that are also virtual sinks
                         lambda b: b.is_virtual_sink(),
                         vsp.get_parent().get_parent().get_enabled_blocks(),
-                    ),
-                )[0].get_sinks()[0]
+                    )),
+                ))[0].get_sinks()[0]
             ), traversed + [vsp],
         )
     except:
@@ -87,10 +90,10 @@ def _get_sink_from_virtual_sink_port(vsp, traversed=[]):
             _get_sink_from_virtual_source_port(
                 filter(  # Get all virtual source with a matching stream id
                     lambda vs: vs.get_param('stream_id').get_value() == vsp.get_parent().get_param('stream_id').get_value(),
-                    filter(  # Get all enabled blocks that are also virtual sinks
+                    list(filter(  # Get all enabled blocks that are also virtual sinks
                         lambda b: b.is_virtual_source(),
                         vsp.get_parent().get_parent().get_enabled_blocks(),
-                    ),
+                    )),
                 )[0].get_sources()[0]
             ), traversed + [vsp],
         )
@@ -113,10 +116,10 @@ class Port(Element):
         """
         self._n = n
         if n['type'] == 'message':
-            n['domain'] = GR_MESSAGE_DOMAIN
+            n['domain'] = Constants.GR_MESSAGE_DOMAIN
         if 'domain' not in n:
-            n['domain'] = DEFAULT_DOMAIN
-        elif n['domain'] == GR_MESSAGE_DOMAIN:
+            n['domain'] = Constants.DEFAULT_DOMAIN
+        elif n['domain'] == Constants.GR_MESSAGE_DOMAIN:
             n['key'] = n['name']
             n['type'] = 'message'  # For port color
         if n['type'] == 'msg':
@@ -147,7 +150,7 @@ class Port(Element):
             return 'Sink - {}({})'.format(self.get_name(), self.get_key())
 
     def get_types(self):
-        return Constants.TYPE_TO_SIZEOF.keys()
+        return list(Constants.TYPE_TO_SIZEOF.keys())
 
     def is_type_empty(self):
         return not self._n['type']
@@ -189,11 +192,11 @@ class Port(Element):
 
         # Update domain if was deduced from (dynamic) port type
         type_ = self.get_type()
-        if self._domain == GR_STREAM_DOMAIN and type_ == "message":
-            self._domain = GR_MESSAGE_DOMAIN
+        if self._domain == Constants.GR_STREAM_DOMAIN and type_ == "message":
+            self._domain = Constants.GR_MESSAGE_DOMAIN
             self._key = self._name
-        if self._domain == GR_MESSAGE_DOMAIN and type_ != "message":
-            self._domain = GR_STREAM_DOMAIN
+        if self._domain == Constants.GR_MESSAGE_DOMAIN and type_ != "message":
+            self._domain = Constants.GR_STREAM_DOMAIN
             self._key = '0'  # Is rectified in rewrite()
 
     def resolve_virtual_source(self):
@@ -341,7 +344,7 @@ class Port(Element):
     def get_name(self):
         number = ''
         if self.get_type() == 'bus':
-            busses = filter(lambda a: a._dir == self._dir, self.get_parent().get_ports_gui())
+            busses = [a for a in self.get_parent().get_ports_gui() if a._dir == self._dir]
             number = str(busses.index(self)) + '#' + str(len(self.get_associated_ports()))
         return self._name + number
 
@@ -373,7 +376,7 @@ class Port(Element):
             a list of connection objects
         """
         connections = self.get_parent().get_parent().connections
-        connections = filter(lambda c: c.get_source() is self or c.get_sink() is self, connections)
+        connections = [c for c in connections if c.get_source() is self or c.get_sink() is self]
         return connections
 
     def get_enabled_connections(self):
@@ -383,7 +386,7 @@ class Port(Element):
         Returns:
             a list of connection objects
         """
-        return filter(lambda c: c.get_enabled(), self.get_connections())
+        return [c for c in self.get_connections() if c.get_enabled()]
 
     def get_associated_ports(self):
         if not self.get_type() == 'bus':
@@ -400,5 +403,5 @@ class Port(Element):
             if bus_structure:
                 busses = [i for i in get_ports() if i.get_type() == 'bus']
                 bus_index = busses.index(self)
-                ports = filter(lambda a: ports.index(a) in bus_structure[bus_index], ports)
+                ports = [a for a in ports if ports.index(a) in bus_structure[bus_index]]
             return ports
diff --git a/grc/core/generator/FlowGraphProxy.py b/grc/core/generator/FlowGraphProxy.py
index 3723005576..c673c5b005 100644
--- a/grc/core/generator/FlowGraphProxy.py
+++ b/grc/core/generator/FlowGraphProxy.py
@@ -16,6 +16,10 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
 
+from __future__ import absolute_import
+from six.moves import range
+
+
 class FlowGraphProxy(object):
 
     def __init__(self, fg):
@@ -34,7 +38,7 @@ class FlowGraphProxy(object):
         Returns:
             a list of dicts with: type, label, vlen, size, optional
         """
-        return filter(lambda p: p['type'] != "message", self.get_hier_block_io(direction))
+        return [p for p in self.get_hier_block_io(direction) if p['type'] != "message"]
 
     def get_hier_block_message_io(self, direction):
         """
@@ -46,7 +50,7 @@ class FlowGraphProxy(object):
         Returns:
             a list of dicts with: type, label, vlen, size, optional
         """
-        return filter(lambda p: p['type'] == "message", self.get_hier_block_io(direction))
+        return [p for p in self.get_hier_block_io(direction) if p['type'] == "message"]
 
     def get_hier_block_io(self, direction):
         """
@@ -71,7 +75,7 @@ class FlowGraphProxy(object):
             }
             num_ports = pad.get_param('num_streams').get_evaluated()
             if num_ports > 1:
-                for i in xrange(num_ports):
+                for i in range(num_ports):
                     clone = master.copy()
                     clone['label'] += str(i)
                     ports.append(clone)
@@ -86,7 +90,7 @@ class FlowGraphProxy(object):
         Returns:
             a list of pad source blocks in this flow graph
         """
-        pads = filter(lambda b: b.get_key() == 'pad_source', self.get_enabled_blocks())
+        pads = [b for b in self.get_enabled_blocks() if b.get_key() == 'pad_source']
         return sorted(pads, lambda x, y: cmp(x.get_id(), y.get_id()))
 
     def get_pad_sinks(self):
@@ -96,7 +100,7 @@ class FlowGraphProxy(object):
         Returns:
             a list of pad sink blocks in this flow graph
         """
-        pads = filter(lambda b: b.get_key() == 'pad_sink', self.get_enabled_blocks())
+        pads = [b for b in self.get_enabled_blocks() if b.get_key() == 'pad_sink']
         return sorted(pads, lambda x, y: cmp(x.get_id(), y.get_id()))
 
     def get_pad_port_global_key(self, port):
@@ -123,4 +127,4 @@ class FlowGraphProxy(object):
                 # assuming we have either only sources or sinks
                 if not is_message_pad:
                     key_offset += len(pad.get_ports())
-        return -1
\ No newline at end of file
+        return -1
diff --git a/grc/core/generator/Generator.py b/grc/core/generator/Generator.py
index 0d0ca6f55f..c9b065372d 100644
--- a/grc/core/generator/Generator.py
+++ b/grc/core/generator/Generator.py
@@ -16,9 +16,11 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
 
+from __future__ import absolute_import
 import codecs
 import os
 import tempfile
+import operator
 
 from Cheetah.Template import Template
 
@@ -85,14 +87,15 @@ class TopBlockGenerator(object):
     def write(self):
         """generate output and write it to files"""
         # Do throttle warning
-        throttling_blocks = filter(lambda b: b.throtteling(), self._flow_graph.get_enabled_blocks())
+        throttling_blocks = [b for b in self._flow_graph.get_enabled_blocks()
+                             if b.throtteling()]
         if not throttling_blocks and not self._generate_options.startswith('hb'):
             Messages.send_warning("This flow graph may not have flow control: "
                                   "no audio or RF hardware blocks found. "
                                   "Add a Misc->Throttle block to your flow "
                                   "graph to avoid CPU congestion.")
         if len(throttling_blocks) > 1:
-            keys = set(map(lambda b: b.get_key(), throttling_blocks))
+            keys = set([b.get_key() for b in throttling_blocks])
             if len(keys) > 1 and 'blocks_throttle' in keys:
                 Messages.send_warning("This flow graph contains a throttle "
                                       "block and another rate limiting block, "
@@ -139,15 +142,15 @@ class TopBlockGenerator(object):
             return code
 
         blocks = expr_utils.sort_objects(
-            filter(lambda b: b.get_enabled() and not b.get_bypassed(), fg.blocks),
-            lambda b: b.get_id(), _get_block_sort_text
+            [b for b in fg.blocks if b.get_enabled() and not b.get_bypassed()],
+            operator.methodcaller('get_id'), _get_block_sort_text
         )
         deprecated_block_keys = set(block.get_name() for block in blocks if block.is_deprecated)
         for key in deprecated_block_keys:
             Messages.send_warning("The block {!r} is deprecated.".format(key))
 
         # List of regular blocks (all blocks minus the special ones)
-        blocks = filter(lambda b: b not in (imports + parameters), blocks)
+        blocks = [b for b in blocks if b not in imports and b not in parameters]
 
         for block in blocks:
             key = block.get_key()
@@ -162,10 +165,10 @@ class TopBlockGenerator(object):
         # Filter out virtual sink connections
         def cf(c):
             return not (c.is_bus() or c.is_msg() or c.get_sink().get_parent().is_virtual_sink())
-        connections = filter(cf, fg.get_enabled_connections())
+        connections = [con for con in fg.get_enabled_connections() if cf(con)]
 
         # Get the virtual blocks and resolve their connections
-        virtual = filter(lambda c: c.get_source().get_parent().is_virtual_source(), connections)
+        virtual = [c for c in connections if c.get_source().get_parent().is_virtual_source()]
         for connection in virtual:
             source = connection.get_source().resolve_virtual_source()
             sink = connection.get_sink()
@@ -183,7 +186,7 @@ class TopBlockGenerator(object):
         for block in bypassed_blocks:
             # Get the upstream connection (off of the sink ports)
             # Use *connections* not get_connections()
-            source_connection = filter(lambda c: c.get_sink() == block.get_sinks()[0], connections)
+            source_connection = [c for c in connections if c.get_sink() == block.get_sinks()[0]]
             # The source connection should never have more than one element.
             assert (len(source_connection) == 1)
 
@@ -191,7 +194,7 @@ class TopBlockGenerator(object):
             source_port = source_connection[0].get_source()
 
             # Loop through all the downstream connections
-            for sink in filter(lambda c: c.get_source() == block.get_sources()[0], connections):
+            for sink in (c for c in connections if c.get_source() == block.get_sources()[0]):
                 if not sink.get_enabled():
                     # Ignore disabled connections
                     continue
@@ -210,7 +213,8 @@ class TopBlockGenerator(object):
         ))
 
         connection_templates = fg.get_parent().connection_templates
-        msgs = filter(lambda c: c.is_msg(), fg.get_enabled_connections())
+        msgs = [c for c in fg.get_enabled_connections() if c.is_msg()]
+
         # List of variable names
         var_ids = [var.get_id() for var in parameters + variables]
         # Prepend self.
@@ -222,7 +226,7 @@ class TopBlockGenerator(object):
             ]
         # Map var id to callbacks
         var_id2cbs = dict([
-            (var_id, filter(lambda c: expr_utils.get_variable_dependencies(c, [var_id]), callbacks))
+            (var_id, [c for c in callbacks if expr_utils.get_variable_dependencies(c, [var_id])])
             for var_id in var_ids
         ])
         # Load the namespace
@@ -290,8 +294,8 @@ class HierBlockGenerator(TopBlockGenerator):
         parameters = self._flow_graph.get_parameters()
 
         def var_or_value(name):
-            if name in map(lambda p: p.get_id(), parameters):
-                return "$"+name
+            if name in (p.get_id() for p in parameters):
+                return "$" + name
             return name
 
         # Build the nested data
diff --git a/grc/core/generator/__init__.py b/grc/core/generator/__init__.py
index f44b94a85d..98f410c8d4 100644
--- a/grc/core/generator/__init__.py
+++ b/grc/core/generator/__init__.py
@@ -15,4 +15,5 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
-from Generator import Generator
+from __future__ import absolute_import
+from .Generator import Generator
diff --git a/grc/core/utils/__init__.py b/grc/core/utils/__init__.py
index 6b23da2723..0d84f7131d 100644
--- a/grc/core/utils/__init__.py
+++ b/grc/core/utils/__init__.py
@@ -15,8 +15,10 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
-import expr_utils
-import epy_block_io
-import extract_docs
+from __future__ import absolute_import
 
-from odict import odict
+from . import expr_utils
+from . import epy_block_io
+from . import extract_docs
+
+from .odict import odict
diff --git a/grc/core/utils/complexity.py b/grc/core/utils/complexity.py
index baa8040db4..d72db9b3dd 100644
--- a/grc/core/utils/complexity.py
+++ b/grc/core/utils/complexity.py
@@ -8,8 +8,8 @@ def calculate_flowgraph_complexity(flowgraph):
             continue
 
         # Don't worry about optional sinks?
-        sink_list = filter(lambda c: not c.get_optional(), block.get_sinks())
-        source_list = filter(lambda c: not c.get_optional(), block.get_sources())
+        sink_list = [c for c in block.get_sinks() if not c.get_optional()]
+        source_list = [c for c in block.get_sources() if not c.get_optional()]
         sinks = float(len(sink_list))
         sources = float(len(source_list))
         base = max(min(sinks, sources), 1)
@@ -22,14 +22,15 @@ def calculate_flowgraph_complexity(flowgraph):
             multi = 1
 
         # Connection ratio multiplier
-        sink_multi = max(float(sum(map(lambda c: len(c.get_connections()), sink_list)) / max(sinks, 1.0)), 1.0)
-        source_multi = max(float(sum(map(lambda c: len(c.get_connections()), source_list)) / max(sources, 1.0)), 1.0)
-        dbal = dbal + (base * multi * sink_multi * source_multi)
+        sink_multi = max(float(sum(len(c.get_connections()) for c in sink_list) / max(sinks, 1.0)), 1.0)
+        source_multi = max(float(sum(len(c.get_connections()) for c in source_list) / max(sources, 1.0)), 1.0)
+        dbal += base * multi * sink_multi * source_multi
 
     blocks = float(len(flowgraph.blocks))
     connections = float(len(flowgraph.connections))
     elements = blocks + connections
-    disabled_connections = len(filter(lambda c: not c.get_enabled(), flowgraph.connections))
+    disabled_connections = sum(not c.get_enabled() for c in flowgraph.connections)
+
     variables = elements - blocks - connections
     enabled = float(len(flowgraph.get_enabled_blocks()))
 
diff --git a/grc/core/utils/epy_block_io.py b/grc/core/utils/epy_block_io.py
index 76b50051db..7a2006a833 100644
--- a/grc/core/utils/epy_block_io.py
+++ b/grc/core/utils/epy_block_io.py
@@ -1,7 +1,12 @@
 
+from __future__ import absolute_import
+
 import inspect
 import collections
 
+import six
+from six.moves import zip
+
 
 TYPE_MAP = {
     'complex64': 'complex', 'complex': 'complex',
@@ -31,10 +36,10 @@ def _ports(sigs, msgs):
 def _find_block_class(source_code, cls):
     ns = {}
     try:
-        exec source_code in ns
+        exec(source_code, ns)
     except Exception as e:
         raise ValueError("Can't interpret source code: " + str(e))
-    for var in ns.itervalues():
+    for var in six.itervalues(ns):
         if inspect.isclass(var) and issubclass(var, cls):
             return var
     raise ValueError('No python block class found in code')
@@ -52,7 +57,7 @@ def extract(cls):
 
     spec = inspect.getargspec(cls.__init__)
     init_args = spec.args[1:]
-    defaults = map(repr, spec.defaults or ())
+    defaults = [repr(arg) for arg in (spec.defaults or ())]
     doc = cls.__doc__ or cls.__init__.__doc__ or ''
     cls_name = cls.__name__
 
diff --git a/grc/core/utils/expr_utils.py b/grc/core/utils/expr_utils.py
index 66911757d6..0577f06a75 100644
--- a/grc/core/utils/expr_utils.py
+++ b/grc/core/utils/expr_utils.py
@@ -17,7 +17,12 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import, print_function
+
 import string
+
+import six
+
 VAR_CHARS = string.letters + string.digits + '_'
 
 
@@ -50,7 +55,7 @@ class graph(object):
         self._graph[src_node_key].remove(dest_node_key)
 
     def get_nodes(self):
-        return self._graph.keys()
+        return list(self._graph.keys())
 
     def get_edges(self, node_key):
         return self._graph[node_key]
@@ -85,7 +90,7 @@ def expr_split(expr):
             toks.append(char)
             tok = ''
     toks.append(tok)
-    return filter(lambda t: t, toks)
+    return [t for t in toks if t]
 
 
 def expr_replace(expr, replace_dict):
@@ -101,7 +106,7 @@ def expr_replace(expr, replace_dict):
     """
     expr_splits = expr_split(expr)
     for i, es in enumerate(expr_splits):
-        if es in replace_dict.keys():
+        if es in list(replace_dict.keys()):
             expr_splits[i] = replace_dict[es]
     return ''.join(expr_splits)
 
@@ -118,7 +123,7 @@ def get_variable_dependencies(expr, vars):
         a subset of vars used in the expression
     """
     expr_toks = expr_split(expr)
-    return set(filter(lambda v: v in expr_toks, vars))
+    return set(v for v in vars if v in expr_toks)
 
 
 def get_graph(exprs):
@@ -131,12 +136,12 @@ def get_graph(exprs):
     Returns:
         a graph of variable deps
     """
-    vars = exprs.keys()
+    vars = list(exprs.keys())
     # Get dependencies for each expression, load into graph
     var_graph = graph()
     for var in vars:
         var_graph.add_node(var)
-    for var, expr in exprs.iteritems():
+    for var, expr in six.iteritems(exprs):
         for dep in get_variable_dependencies(expr, vars):
             if dep != var:
                 var_graph.add_edge(dep, var)
@@ -159,7 +164,7 @@ def sort_variables(exprs):
     # Determine dependency order
     while var_graph.get_nodes():
         # Get a list of nodes with no edges
-        indep_vars = filter(lambda var: not var_graph.get_edges(var), var_graph.get_nodes())
+        indep_vars = [var for var in var_graph.get_nodes() if not var_graph.get_edges(var)]
         if not indep_vars:
             raise Exception('circular dependency caught in sort_variables')
         # Add the indep vars to the end of the list
@@ -193,4 +198,4 @@ def sort_objects(objects, get_id, get_expr):
 
 if __name__ == '__main__':
     for i in sort_variables({'x': '1', 'y': 'x+1', 'a': 'x+y', 'b': 'y+1', 'c': 'a+b+x+y'}):
-        print i
+        print(i)
diff --git a/grc/core/utils/extract_docs.py b/grc/core/utils/extract_docs.py
index a6e0bc971e..cff8a81099 100644
--- a/grc/core/utils/extract_docs.py
+++ b/grc/core/utils/extract_docs.py
@@ -17,15 +17,19 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import, print_function
+
 import sys
 import re
 import subprocess
 import threading
 import json
-import Queue
 import random
 import itertools
 
+import six
+from six.moves import queue, filter, range
+
 
 ###############################################################################
 # The docstring extraction
@@ -124,7 +128,7 @@ class SubprocessLoader(object):
         self.callback_query_result = callback_query_result
         self.callback_finished = callback_finished or (lambda: None)
 
-        self._queue = Queue.Queue()
+        self._queue = queue.Queue()
         self._thread = None
         self._worker = None
         self._shutdown = threading.Event()
@@ -157,14 +161,14 @@ class SubprocessLoader(object):
                 cmd, args = self._last_cmd
                 if cmd == 'query':
                     msg += " (crashed while loading {0!r})".format(args[0])
-                print >> sys.stderr, msg
+                print(msg, file=sys.stderr)
                 continue  # restart
             else:
                 break  # normal termination, return
             finally:
                 self._worker.terminate()
         else:
-            print >> sys.stderr, "Warning: docstring loader crashed too often"
+            print("Warning: docstring loader crashed too often", file=sys.stderr)
         self._thread = None
         self._worker = None
         self.callback_finished()
@@ -203,9 +207,9 @@ class SubprocessLoader(object):
             key, docs = args
             self.callback_query_result(key, docs)
         elif cmd == 'error':
-            print args
+            print(args)
         else:
-            print >> sys.stderr, "Unknown response:", cmd, args
+            print("Unknown response:", cmd, args, file=sys.stderr)
 
     def query(self, key, imports=None, make=None):
         """ Request docstring extraction for a certain key """
@@ -270,12 +274,12 @@ if __name__ == '__worker__':
 
 elif __name__ == '__main__':
     def callback(key, docs):
-        print key
-        for match, doc in docs.iteritems():
-            print '-->', match
-            print doc.strip()
-            print
-        print
+        print(key)
+        for match, doc in six.iteritems(docs):
+            print('-->', match)
+            print(doc.strip())
+            print()
+        print()
 
     r = SubprocessLoader(callback)
 
diff --git a/grc/core/utils/odict.py b/grc/core/utils/odict.py
index 20970e947c..38f898a97f 100644
--- a/grc/core/utils/odict.py
+++ b/grc/core/utils/odict.py
@@ -17,6 +17,8 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
+
 from UserDict import DictMixin
 
 
diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py
index 492bf8de9c..9c3e9246d5 100644
--- a/grc/gui/ActionHandler.py
+++ b/grc/gui/ActionHandler.py
@@ -18,6 +18,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
 
+from __future__ import absolute_import, print_function
+
 import os
 import subprocess
 
@@ -114,7 +116,7 @@ class ActionHandler:
         ##################################################
         if action == Actions.APPLICATION_INITIALIZE:
             if not self.init_file_paths:
-                self.init_file_paths = filter(os.path.exists, Preferences.get_open_files())
+                self.init_file_paths = list(filter(os.path.exists, Preferences.get_open_files()))
             if not self.init_file_paths: self.init_file_paths = ['']
             for file_path in self.init_file_paths:
                 if file_path: main.new_page(file_path) #load pages from file paths
@@ -603,7 +605,7 @@ class ActionHandler:
                 try:
                     page.process.kill()
                 except:
-                    print "could not kill process: %d" % page.process.pid
+                    print("could not kill process: %d" % page.process.pid)
         elif action == Actions.PAGE_CHANGE:  # pass and run the global actions
             pass
         elif action == Actions.RELOAD_BLOCKS:
@@ -645,7 +647,7 @@ class ActionHandler:
                              shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
 
         else:
-            print '!!! Action "%s" not handled !!!' % action
+            print('!!! Action "%s" not handled !!!' % action)
         ##################################################
         # Global Actions for all States
         ##################################################
diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py
index d0e114293f..3a51e80918 100644
--- a/grc/gui/Actions.py
+++ b/grc/gui/Actions.py
@@ -17,13 +17,17 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
+
+import six
+
 import gi
 gi.require_version('Gtk', '3.0')
 from gi.repository import Gtk
 from gi.repository import Gdk
 from gi.repository import GObject
 
-import Preferences
+from . import Preferences
 
 NO_MODS_MASK = 0
 
@@ -47,7 +51,6 @@ def handle_key_press(event):
     Returns:
         true if handled
     """
-    _used_mods_mask = reduce(lambda x, y: x | y, [mod_mask for keyval, mod_mask in _actions_keypress_dict], NO_MODS_MASK)
     # extract the key value and the consumed modifiers
     _unused, keyval, egroup, level, consumed = _keymap.translate_keyboard_state(
         event.hardware_keycode, event.get_state(), event.group)
@@ -80,13 +83,16 @@ class _ActionBase(object):
     Register actions and keypresses with this module.
     """
     def __init__(self, label, keypresses):
+        global _used_mods_mask
+
         _all_actions_list.append(self)
         for i in range(len(keypresses)/2):
             keyval, mod_mask = keypresses[i*2:(i+1)*2]
             # register this keypress
-            if _actions_keypress_dict.has_key((keyval, mod_mask)):
+            if (keyval, mod_mask) in _actions_keypress_dict:
                 raise KeyError('keyval/mod_mask pair already registered "%s"' % str((keyval, mod_mask)))
             _actions_keypress_dict[(keyval, mod_mask)] = self
+            _used_mods_mask |= mod_mask
             # set the accelerator group, and accelerator path
             # register the key name and mod mask with the accelerator path
             if label is None:
@@ -102,7 +108,7 @@ class _ActionBase(object):
         The string representation should be the name of the action id.
         Try to find the action id for this action by searching this module.
         """
-        for name, value in globals().iteritems():
+        for name, value in six.iteritems(globals()):
             if value == self:
                 return name
         return self.get_name()
diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py
index c8631aa298..0c18836c4e 100644
--- a/grc/gui/Bars.py
+++ b/grc/gui/Bars.py
@@ -17,6 +17,7 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
 import gi
 gi.require_version('Gtk', '3.0')
 from gi.repository import Gtk
@@ -206,7 +207,7 @@ class SubMenuCreator(object):
 
     def _fill_flow_graph_recent_submenu(self, action):
         """menu showing recent flow-graphs"""
-        import Preferences
+        from . import Preferences
         menu = Gtk.Menu()
         recent_files = Preferences.get_recent_files()
         if len(recent_files) > 0:
diff --git a/grc/gui/Block.py b/grc/gui/Block.py
index 1b90cf69a0..a6c31cd473 100644
--- a/grc/gui/Block.py
+++ b/grc/gui/Block.py
@@ -17,6 +17,7 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
 import math
 import gi
 gi.require_version('Gtk', '3.0')
@@ -150,7 +151,7 @@ class Block(Element, _Block):
         W = self.label_width + 2 * BLOCK_LABEL_PADDING
 
         def get_min_height_for_ports():
-            visible_ports = filter(lambda p: not p.get_hide(), ports)
+            visible_ports = [p for p in ports if not p.get_hide()]
             min_height = 2*PORT_BORDER_SEPARATION + len(visible_ports) * PORT_SEPARATION
             if visible_ports:
                 min_height -= ports[0].H
diff --git a/grc/gui/BlockTreeWindow.py b/grc/gui/BlockTreeWindow.py
index 829ddfed68..26086f58e9 100644
--- a/grc/gui/BlockTreeWindow.py
+++ b/grc/gui/BlockTreeWindow.py
@@ -17,6 +17,9 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
+import six
+
 import gi
 gi.require_version('Gtk', '3.0')
 from gi.repository import Gtk
@@ -33,7 +36,7 @@ def _format_doc(doc):
     docs = []
     if doc.get(''):
         docs += doc.pop('').splitlines() + ['']
-    for block_name, docstring in doc.iteritems():
+    for block_name, docstring in six.iteritems(doc):
         docs.append('--- {0} ---'.format(block_name))
         docs += docstring.splitlines()
         docs.append('')
@@ -142,8 +145,9 @@ class BlockTreeWindow(Gtk.VBox):
         if categories is None:
             categories = self._categories
 
-        if isinstance(category, (str, unicode)): category = category.split('/')
-        category = tuple(filter(lambda x: x, category))  # tuple is hashable
+        if isinstance(category, (str, six.text_type)):
+            category = category.split('/')
+        category = tuple(x for x in category if x)  # tuple is hashable
         # add category and all sub categories
         for i, cat_name in enumerate(category):
             sub_category = category[:i+1]
@@ -210,8 +214,8 @@ class BlockTreeWindow(Gtk.VBox):
             self.treeview.set_model(self.treestore)
             self.treeview.collapse_all()
         else:
-            matching_blocks = filter(lambda b: key in b.get_key().lower() or key in b.get_name().lower(),
-                                     self.platform.blocks.values())
+            matching_blocks = [b for b in list(self.platform.blocks.values())
+                               if key in b.get_key().lower() or key in b.get_name().lower()]
 
             self.treestore_search.clear()
             self._categories_search = {tuple(): None}
diff --git a/grc/gui/Colors.py b/grc/gui/Colors.py
index a03a7bcade..b2ed55b711 100644
--- a/grc/gui/Colors.py
+++ b/grc/gui/Colors.py
@@ -17,6 +17,7 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
 
 
 def get_color(color_code):
diff --git a/grc/gui/Config.py b/grc/gui/Config.py
index 9b0c5d4afe..b6556ad724 100644
--- a/grc/gui/Config.py
+++ b/grc/gui/Config.py
@@ -17,8 +17,11 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import, print_function
+
 import sys
 import os
+
 from ..core.Config import Config as _Config
 from . import Constants
 
@@ -57,7 +60,7 @@ class Config(_Config):
                 raise Exception()
             return value
         except:
-            print >> sys.stderr, "Error: invalid 'canvas_default_size' setting."
+            print("Error: invalid 'canvas_default_size' setting.", file=sys.stderr)
             return Constants.DEFAULT_CANVAS_SIZE_DEFAULT
 
     @property
@@ -69,6 +72,6 @@ class Config(_Config):
                 raise Exception()
         except:
             font_size = Constants.DEFAULT_FONT_SIZE
-            print >> sys.stderr, "Error: invalid 'canvas_font_size' setting."
+            print("Error: invalid 'canvas_font_size' setting.", file=sys.stderr)
 
         return font_size
diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py
index 46414c94c8..8953ca0183 100644
--- a/grc/gui/Connection.py
+++ b/grc/gui/Connection.py
@@ -17,16 +17,14 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
-import gi
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk
-from gi.repository import Gdk
+from __future__ import absolute_import
 
+from six.moves import map
 
-import Colors
-import Utils
-from Constants import CONNECTOR_ARROW_BASE, CONNECTOR_ARROW_HEIGHT
-from Element import Element
+from . import Colors
+from . import Utils
+from .Constants import CONNECTOR_ARROW_BASE, CONNECTOR_ARROW_HEIGHT
+from .Element import Element
 
 from ..core.Constants import GR_MESSAGE_DOMAIN
 from ..core.Connection import Connection as _Connection
@@ -130,7 +128,7 @@ class Connection(Element, _Connection):
             #points[0][0] -> source connector should not be in the direction of source
             if Utils.get_angle_from_coordinates(points[0][0], (x1, y1)) == source.get_connector_direction(): points.reverse()
             #create 3-line connector
-            p1, p2 = map(int, points[0][0]), map(int, points[0][1])
+            p1, p2 = list(map(int, points[0][0])), list(map(int, points[0][1]))
             self.add_line((x1, y1), p1)
             self.add_line(p1, p2)
             self.add_line((x2, y2), p2)
diff --git a/grc/gui/Constants.py b/grc/gui/Constants.py
index 55739a7c03..8bb15acc09 100644
--- a/grc/gui/Constants.py
+++ b/grc/gui/Constants.py
@@ -17,6 +17,7 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
 import gi
 gi.require_version('Gtk', '3.0')
 from gi.repository import Gtk
diff --git a/grc/gui/Dialogs.py b/grc/gui/Dialogs.py
index 953373ee24..8f0f60d764 100644
--- a/grc/gui/Dialogs.py
+++ b/grc/gui/Dialogs.py
@@ -17,6 +17,7 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
 import gi
 gi.require_version('Gtk', '3.0')
 from gi.repository import Gtk
diff --git a/grc/gui/DrawingArea.py b/grc/gui/DrawingArea.py
index d1e0e78634..33c669c99f 100644
--- a/grc/gui/DrawingArea.py
+++ b/grc/gui/DrawingArea.py
@@ -17,6 +17,7 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
 from gi.repository import Gtk, Gdk, GObject
 
 
diff --git a/grc/gui/Element.py b/grc/gui/Element.py
index 30c0f5dba7..4e88df375f 100644
--- a/grc/gui/Element.py
+++ b/grc/gui/Element.py
@@ -17,10 +17,12 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
-from Constants import LINE_SELECT_SENSITIVITY
-from Constants import POSSIBLE_ROTATIONS
+from __future__ import absolute_import
+from .Constants import LINE_SELECT_SENSITIVITY
+from .Constants import POSSIBLE_ROTATIONS
 
 import gi
+from six.moves import zip
 gi.require_version('Gtk', '3.0')
 from gi.repository import Gtk
 from gi.repository import Gdk
diff --git a/grc/gui/Executor.py b/grc/gui/Executor.py
index 13a1cfd942..a8c67986e5 100644
--- a/grc/gui/Executor.py
+++ b/grc/gui/Executor.py
@@ -15,6 +15,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
+from __future__ import absolute_import
 import os
 import threading
 import shlex
diff --git a/grc/gui/FileDialogs.py b/grc/gui/FileDialogs.py
index 63d4a397a8..3ee715dac6 100644
--- a/grc/gui/FileDialogs.py
+++ b/grc/gui/FileDialogs.py
@@ -17,18 +17,19 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
 import gi
 gi.require_version('Gtk', '3.0')
 from gi.repository import Gtk
 from gi.repository import GObject
 
-from Dialogs import MessageDialogHelper
-from Constants import \
+from .Dialogs import MessageDialogHelper
+from .Constants import \
     DEFAULT_FILE_PATH, IMAGE_FILE_EXTENSION, TEXT_FILE_EXTENSION, \
     NEW_FLOGRAPH_TITLE
-import Preferences
+from . import Preferences
 from os import path
-import Utils
+from . import Utils
 
 ##################################################
 # Constants
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index 802c54f7a7..50e146b4db 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -17,16 +17,20 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
+
 import functools
 import random
 from distutils.spawn import find_executable
 from itertools import chain, count
 from operator import methodcaller
 
+import six
+from six.moves import filter
+
 from gi.repository import GObject
 
-from . import Actions, Colors, Constants, Utils, Bars, Dialogs
-from .Constants import SCROLL_PROXIMITY_SENSITIVITY, SCROLL_DISTANCE
+from . import Actions, Colors, Utils, Bars, Dialogs
 from .Element import Element
 from .external_editor import ExternalEditor
 
@@ -179,10 +183,10 @@ class FlowGraph(Element, _Flowgraph):
             x_min = min(x, x_min)
             y_min = min(y, y_min)
         #get connections between selected blocks
-        connections = filter(
+        connections = list(filter(
             lambda c: c.get_source().get_parent() in blocks and c.get_sink().get_parent() in blocks,
             self.connections,
-        )
+        ))
         clipboard = (
             (x_min, y_min),
             [block.export_data() for block in blocks],
@@ -222,7 +226,7 @@ class FlowGraph(Element, _Flowgraph):
                 block.get_param('_io_cache').set_value(params.pop('_io_cache'))
                 block.get_param('_source_code').set_value(params.pop('_source_code'))
                 block.rewrite()  # this creates the other params
-            for param_key, param_value in params.iteritems():
+            for param_key, param_value in six.iteritems(params):
                 #setup id parameter
                 if param_key == 'id':
                     old_id2block[param_value] = block
diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py
index ce16074c81..3236768969 100644
--- a/grc/gui/MainWindow.py
+++ b/grc/gui/MainWindow.py
@@ -17,6 +17,8 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
+
 import os
 
 import gi
@@ -270,7 +272,7 @@ class MainWindow(Gtk.Window):
         Returns:
             true if all closed
         """
-        open_files = filter(lambda file: file, self._get_files()) #filter blank files
+        open_files = [file for file in self._get_files() if file] #filter blank files
         open_file = self.current_page.file_path
         #close each page
         for page in sorted(self.get_pages(), key=lambda p: p.saved):
@@ -416,7 +418,7 @@ class MainWindow(Gtk.Window):
         Returns:
             list of file paths
         """
-        return map(lambda page: page.file_path, self.get_pages())
+        return [page.file_path for page in self.get_pages()]
 
     def get_pages(self):
         """
diff --git a/grc/gui/NotebookPage.py b/grc/gui/NotebookPage.py
index bcfb4d87fe..757dcbc0f8 100644
--- a/grc/gui/NotebookPage.py
+++ b/grc/gui/NotebookPage.py
@@ -17,6 +17,7 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
 import os
 
 from gi.repository import Gtk, Gdk, GObject
diff --git a/grc/gui/Param.py b/grc/gui/Param.py
index 0f88015256..137c5e057b 100644
--- a/grc/gui/Param.py
+++ b/grc/gui/Param.py
@@ -15,6 +15,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
+from __future__ import absolute_import
 from . import Utils, Constants
 
 from . import ParamWidgets
diff --git a/grc/gui/ParamWidgets.py b/grc/gui/ParamWidgets.py
index 2fd6ccd1dc..e0979e19f3 100644
--- a/grc/gui/ParamWidgets.py
+++ b/grc/gui/ParamWidgets.py
@@ -15,6 +15,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
+from __future__ import absolute_import
 import os
 
 from gi.repository import Gtk, Gdk
diff --git a/grc/gui/ParserErrorsDialog.py b/grc/gui/ParserErrorsDialog.py
index f49e6923e5..28cc8ece0c 100644
--- a/grc/gui/ParserErrorsDialog.py
+++ b/grc/gui/ParserErrorsDialog.py
@@ -17,12 +17,16 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
+
+import six
+
 import gi
 gi.require_version('Gtk', '3.0')
 from gi.repository import Gtk
 from gi.repository import GObject
 
-from Constants import MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT
+from .Constants import MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT
 
 
 class ParserErrorsDialog(Gtk.Dialog):
@@ -72,7 +76,7 @@ class ParserErrorsDialog(Gtk.Dialog):
         """set up data model"""
         self.tree_store.clear()
         self._error_logs = error_logs
-        for filename, errors in error_logs.iteritems():
+        for filename, errors in six.iteritems(error_logs):
             parent = self.tree_store.append(None, [str(filename)])
             try:
                 with open(filename, 'r') as fp:
diff --git a/grc/gui/Platform.py b/grc/gui/Platform.py
index 500df1cce4..997e96ab59 100644
--- a/grc/gui/Platform.py
+++ b/grc/gui/Platform.py
@@ -17,6 +17,8 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import, print_function
+
 import os
 import sys
 
@@ -58,7 +60,7 @@ class Platform(Element, _Platform):
                 import shutil
                 shutil.move(old_gui_prefs_file, gui_prefs_file)
             except Exception as e:
-                print >> sys.stderr, e
+                print(e, file=sys.stderr)
 
     ##############################################
     # Constructors
diff --git a/grc/gui/Port.py b/grc/gui/Port.py
index fb1cd678cd..0fa35573c1 100644
--- a/grc/gui/Port.py
+++ b/grc/gui/Port.py
@@ -17,6 +17,7 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
 import math
 import gi
 gi.require_version('Gtk', '3.0')
@@ -68,7 +69,7 @@ class Port(_Port, Element):
         #get all sibling ports
         ports = self.get_parent().get_sources_gui() \
             if self.is_source else self.get_parent().get_sinks_gui()
-        ports = filter(lambda p: not p.get_hide(), ports)
+        ports = [p for p in ports if not p.get_hide()]
         #get the max width
         self.W = max([port.W for port in ports] + [PORT_MIN_WIDTH])
         W = self.W if not self._label_hidden() else PORT_LABEL_HIDDEN_WIDTH
@@ -79,16 +80,15 @@ class Port(_Port, Element):
             if hasattr(self, '_connector_length'):
                 del self._connector_length
             return
-        length = len(filter(lambda p: not p.get_hide(), ports))
         #reverse the order of ports for these rotations
         if rotation in (180, 270):
-            index = length-index-1
+            index = len(ports)-index-1
 
         port_separation = PORT_SEPARATION \
             if not self.get_parent().has_busses[self.is_source] \
             else max([port.H for port in ports]) + PORT_SPACING
 
-        offset = (self.get_parent().H - (length-1)*port_separation - self.H)/2
+        offset = (self.get_parent().H - (len(ports)-1)*port_separation - self.H)/2
         #create areas and connector coordinates
         if (self.is_sink and rotation == 0) or (self.is_source and rotation == 180):
             x = -W
diff --git a/grc/gui/Preferences.py b/grc/gui/Preferences.py
index 5fbdfe927a..8756a7ab23 100644
--- a/grc/gui/Preferences.py
+++ b/grc/gui/Preferences.py
@@ -17,9 +17,12 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import, print_function
+
 import os
 import sys
-import ConfigParser
+
+from six.moves import configparser
 
 
 HEADER = """\
@@ -31,7 +34,7 @@ HEADER = """\
 """
 
 _platform = None
-_config_parser = ConfigParser.SafeConfigParser()
+_config_parser = configparser.SafeConfigParser()
 
 
 def file_extension():
@@ -45,12 +48,12 @@ def load(platform):
     for section in ['main', 'files_open', 'files_recent']:
         try:
             _config_parser.add_section(section)
-        except Exception, e:
-             print e
+        except Exception as e:
+            print(e)
     try:
         _config_parser.read(_platform.get_prefs_file())
     except Exception as err:
-        print >> sys.stderr, err
+        print(err, file=sys.stderr)
 
 
 def save():
@@ -59,7 +62,7 @@ def save():
             fp.write(HEADER)
             _config_parser.write(fp)
     except Exception as err:
-        print >> sys.stderr, err
+        print(err, file=sys.stderr)
 
 
 def entry(key, value=None, default=None):
@@ -74,7 +77,7 @@ def entry(key, value=None, default=None):
         }.get(_type, _config_parser.get)
         try:
             result = getter('main', key)
-        except ConfigParser.Error:
+        except configparser.Error:
             result = _type() if default is None else default
     return result
 
@@ -106,7 +109,7 @@ def get_file_list(key):
     try:
         files = [value for name, value in _config_parser.items(key)
                  if name.startswith('%s_' % key)]
-    except ConfigParser.Error:
+    except configparser.Error:
         files = []
     return files
 
@@ -121,7 +124,7 @@ def set_open_files(files):
 
 def get_recent_files():
     """ Gets recent files, removes any that do not exist and re-saves it """
-    files = filter(os.path.exists, get_file_list('files_recent'))
+    files = list(filter(os.path.exists, get_file_list('files_recent')))
     set_recent_files(files)
     return files
 
diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py
index d6b64944cc..f87ca6e7c1 100644
--- a/grc/gui/PropsDialog.py
+++ b/grc/gui/PropsDialog.py
@@ -17,10 +17,12 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
 from gi.repository import Gtk, Gdk, GObject, Pango
 
 from . import Actions, Utils, Constants
 from .Dialogs import SimpleTextDisplay
+import six
 
 
 class PropsDialog(Gtk.Dialog):
@@ -160,7 +162,7 @@ class PropsDialog(Gtk.Dialog):
                     # child.destroy()   # disabled because it throw errors...
                 # repopulate the params box
                 box_all_valid = True
-                for param in filter(lambda p: p.get_tab_label() == tab, self._block.get_params()):
+                for param in [p for p in self._block.get_params() if p.get_tab_label() == tab]:
                     # fixme: why do we even rebuild instead of really hiding params?
                     if param.get_hide() == 'all':
                         continue
@@ -212,7 +214,7 @@ class PropsDialog(Gtk.Dialog):
             docstrings = {block_class: docstrings[block_class]}
 
         # show docstring(s) extracted from python sources
-        for cls_name, docstring in docstrings.iteritems():
+        for cls_name, docstring in six.iteritems(docstrings):
             buf.insert_with_tags_by_name(pos, cls_name + '\n', 'b')
             buf.insert(pos, docstring + '\n\n')
         pos.backward_chars(2)
diff --git a/grc/gui/StateCache.py b/grc/gui/StateCache.py
index 3cdb5f30ce..b109a1281b 100644
--- a/grc/gui/StateCache.py
+++ b/grc/gui/StateCache.py
@@ -17,8 +17,9 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
-import Actions
-from Constants import STATE_CACHE_SIZE
+from __future__ import absolute_import
+from . import Actions
+from .Constants import STATE_CACHE_SIZE
 
 class StateCache(object):
     """
diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py
index 311b37f468..e5d4ccaa35 100644
--- a/grc/gui/Utils.py
+++ b/grc/gui/Utils.py
@@ -17,6 +17,8 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import
+
 import gi
 gi.require_version('Gtk', '3.0')
 from gi.repository import GLib
@@ -47,7 +49,7 @@ def get_rotated_coordinate(coor, rotation):
     return x * cos_r + y * sin_r, -x * sin_r + y * cos_r
 
 
-def get_angle_from_coordinates((x1, y1), (x2, y2)):
+def get_angle_from_coordinates(p1, p2):
     """
     Given two points, calculate the vector direction from point1 to point2, directions are multiples of 90 degrees.
 
@@ -58,6 +60,8 @@ def get_angle_from_coordinates((x1, y1), (x2, y2)):
     Returns:
         the direction in degrees
     """
+    (x1, y1) = p1
+    (x2, y2) = p2
     if y1 == y2:  # 0 or 180
         return 0 if x2 > x1 else 180
     else:  # 90 or 270
@@ -78,7 +82,7 @@ def align_to_grid(coor, mode=round):
     def align(value):
         return int(mode(value / (1.0 * CANVAS_GRID_SIZE)) * CANVAS_GRID_SIZE)
     try:
-        return map(align, coor)
+        return [align(c) for c in coor]
     except TypeError:
         x = coor
         return align(coor)
diff --git a/grc/gui/VariableEditor.py b/grc/gui/VariableEditor.py
index d34903e241..399e4ec475 100644
--- a/grc/gui/VariableEditor.py
+++ b/grc/gui/VariableEditor.py
@@ -17,7 +17,7 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
-from operator import attrgetter
+from __future__ import absolute_import
 
 import gi
 gi.require_version('Gtk', '3.0')
@@ -221,8 +221,8 @@ class VariableEditor(Gtk.VBox):
                 sp('foreground', 'red')
 
     def update_gui(self, blocks):
-        self._imports = filter(attrgetter('is_import'), blocks)
-        self._variables = filter(attrgetter('is_variable'), blocks)
+        self._imports = [block for block in blocks if block.is_import]
+        self._variables = [block for block in blocks if block.is_variable]
         self._rebuild()
         self.treeview.expand_all()
 
diff --git a/grc/gui/external_editor.py b/grc/gui/external_editor.py
index 76f21412b0..11d6fd7ebb 100644
--- a/grc/gui/external_editor.py
+++ b/grc/gui/external_editor.py
@@ -17,6 +17,8 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+from __future__ import absolute_import, print_function
+
 import os
 import sys
 import time
@@ -71,7 +73,7 @@ class ExternalEditor(threading.Thread):
                 time.sleep(1)
 
         except Exception as e:
-            print >> sys.stderr, "file monitor crashed:", str(e)
+            print("file monitor crashed:", str(e), file=sys.stderr)
         else:
             # print "file monitor: done with", filename
             pass
@@ -79,7 +81,7 @@ class ExternalEditor(threading.Thread):
 
 if __name__ == '__main__':
     def p(data):
-        print data
+        print(data)
 
     e = ExternalEditor('/usr/bin/gedit', "test", "content", p)
     e.open_editor()
diff --git a/grc/main.py b/grc/main.py
index cd9863739f..810ac7c66f 100755
--- a/grc/main.py
+++ b/grc/main.py
@@ -15,6 +15,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
+from __future__ import absolute_import
 import optparse
 
 import gi
-- 
cgit v1.2.3


From 963773b800655f2902998aedce8d46605d54e60f Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Fri, 3 Jun 2016 16:17:57 +0200
Subject: grc-refactor: remove odict

---
 grc/core/Block.py               |  69 ++++++++++++------------
 grc/core/Connection.py          |   5 +-
 grc/core/FlowGraph.py           |  52 +++++++++---------
 grc/core/Param.py               |  26 ++++-----
 grc/core/ParseXML.py            |  50 ++++++++++++------
 grc/core/Platform.py            |  55 ++++++++++---------
 grc/core/Port.py                |  17 +++---
 grc/core/generator/Generator.py |  26 +++++----
 grc/core/utils/__init__.py      |   1 -
 grc/core/utils/odict.py         | 113 ----------------------------------------
 grc/gui/FlowGraph.py            |  12 ++---
 11 files changed, 172 insertions(+), 254 deletions(-)
 delete mode 100644 grc/core/utils/odict.py

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/core/Block.py b/grc/core/Block.py
index 062598e9d1..b607f908fb 100644
--- a/grc/core/Block.py
+++ b/grc/core/Block.py
@@ -26,7 +26,7 @@ from six.moves import map, range
 
 from Cheetah.Template import Template
 
-from .utils import epy_block_io, odict
+from .utils import epy_block_io
 from . Constants import (
     BLOCK_FLAG_NEED_QT_GUI, BLOCK_FLAG_NEED_WX_GUI,
     ADVANCED_PARAM_TAB, DEFAULT_PARAM_TAB,
@@ -64,33 +64,34 @@ class Block(Element):
             block a new block
         """
         Element.__init__(self, flow_graph)
-        self._name = n.find('name')
-        self._key = n.find('key')
-        self._category = n.find('category') or ''
-        self._flags = n.find('flags') or ''
+
+        self._name = n['name']
+        self._key = n['key']
+        self._category = n.get('category', '')
+        self._flags = n.get('flags', '')
 
         # Backwards compatibility
-        if n.find('throttle') and BLOCK_FLAG_THROTTLE not in self._flags:
+        if n.get('throttle') and BLOCK_FLAG_THROTTLE not in self._flags:
             self._flags += BLOCK_FLAG_THROTTLE
 
-        self._doc = (n.find('doc') or '').strip('\n').replace('\\\n', '')
-        self._imports = [i.strip() for i in n.findall('import')]
-        self._make = n.find('make')
-        self._var_make = n.find('var_make')
-        self._var_value = n.find('var_value') or '$value'
-        self._checks = n.findall('check')
-        self._callbacks = n.findall('callback')
+        self._doc = n.get('doc', '').strip('\n').replace('\\\n', '')
+        self._imports = [i.strip() for i in n.get('import', [])]
+        self._make = n.get('make')
+        self._var_make = n.get('var_make')
+        self._var_value = n.get('var_value', '$value')
+        self._checks = n.get('check', [])
+        self._callbacks = n.get('callback', [])
 
-        self._grc_source = n.find('grc_source') or ''
-        self._block_wrapper_path = n.find('block_wrapper_path')
+        self._grc_source = n.get('grc_source', '')
+        self._block_wrapper_path = n.get('block_wrapper_path')
 
-        params_n = n.findall('param')
-        sources_n = n.findall('source')
-        sinks_n = n.findall('sink')
+        params_n = n.get('param', [])
+        sources_n = n.get('source', [])
+        sinks_n = n.get('sink', [])
 
         # Get list of param tabs
-        n_tabs = n.find('param_tab_order') or None
-        self._param_tab_labels = n_tabs.findall('tab') if n_tabs is not None else [DEFAULT_PARAM_TAB]
+        n_tabs = n.get('param_tab_order', {})
+        self._param_tab_labels = n.get('param_tab_order', {}).get('tab') or [DEFAULT_PARAM_TAB]
         self._params = []
         self._init_params(
             params_n=params_n,
@@ -108,17 +109,17 @@ class Block(Element):
         self.back_ofthe_bus(self._sources)
         self.back_ofthe_bus(self._sinks)
         self.current_bus_structure = {'source': '', 'sink': ''}
-        self._bus_structure_source = n.find('bus_structure_source') or ''
-        self._bus_structure_sink = n.find('bus_structure_sink') or ''
-        self._bussify_sink = n.find('bus_sink')
-        self._bussify_source = n.find('bus_source')
+        self._bus_structure_source = n.get('bus_structure_source', '')
+        self._bus_structure_sink = n.get('bus_structure_sink', '')
+        self._bussify_sink = n.get('bus_sink')
+        self._bussify_source = n.get('bus_source')
         if self._bussify_sink:
             self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
         if self._bussify_source:
             self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
 
     def _add_param(self, key, name, value='', type='raw', **kwargs):
-        n = odict({'key': key, 'name': name, 'value': value, 'type': type})
+        n = {'key': key, 'name': name, 'value': value, 'type': type}
         n.update(kwargs)
         param = self.get_parent().get_parent().Param(block=self, n=n)
         self._params.append(param)
@@ -366,7 +367,7 @@ class Block(Element):
                 param.set_default(value)
             except KeyError:  # need to make a new param
                 name = key.replace('_', ' ').title()
-                n = odict(dict(name=name, key=key, type='raw', value=value))
+                n = dict(name=name, key=key, type='raw', value=value)
                 param = platform.Param(block=self, n=n)
                 setattr(param, '__epy_param__', True)
             self._params.append(param)
@@ -386,7 +387,7 @@ class Block(Element):
                     ports_to_remove.remove(port_current)
                     port, port_current = port_current, next(iter_ports, None)
                 else:
-                    n = odict(dict(name=label + str(key), type=port_type, key=key))
+                    n = dict(name=label + str(key), type=port_type, key=key)
                     if port_type == 'message':
                         n['name'] = key
                         n['optional'] = '1'
@@ -684,7 +685,7 @@ class Block(Element):
         Returns:
             a nested data odict
         """
-        n = odict()
+        n = collections.OrderedDict()
         n['key'] = self.get_key()
         n['param'] = [p.export_data() for p in sorted(self.get_params(), key=str)]
         if 'bus' in [a.get_type() for a in self.get_sinks()]:
@@ -705,7 +706,7 @@ class Block(Element):
         Args:
             n: the nested data odict
         """
-        params_n = n.findall('param')
+        params_n = n.get('param', [])
         params = dict((param.get_key(), param) for param in self._params)
 
         def get_hash():
@@ -714,8 +715,8 @@ class Block(Element):
         my_hash = 0
         while get_hash() != my_hash:
             for param_n in params_n:
-                key = param_n.find('key')
-                value = param_n.find('value')
+                key = param_n['key']
+                value = param_n['value']
                 try:
                     params[key].set_value(value)
                 except KeyError:
@@ -755,13 +756,13 @@ class Block(Element):
         return buslist or ports
 
     def _import_bus_stuff(self, n):
-        bussinks = n.findall('bus_sink')
+        bussinks = n.get('bus_sink', [])
         if len(bussinks) > 0 and not self._bussify_sink:
             self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
         elif len(bussinks) > 0:
             self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
             self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
-        bussrcs = n.findall('bus_source')
+        bussrcs = n.get('bus_source', [])
         if len(bussrcs) > 0 and not self._bussify_source:
             self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
         elif len(bussrcs) > 0:
@@ -815,7 +816,7 @@ class Block(Element):
 
             for i in range(len(struct)):
                 n['key'] = str(len(get_p()))
-                n = odict(n)
+                n = dict(n)
                 port = self.get_parent().get_parent().Port(block=self, n=n, dir=direc)
                 get_p().append(port)
         elif 'bus' in [a.get_type() for a in get_p()]:
diff --git a/grc/core/Connection.py b/grc/core/Connection.py
index ddc6c0256f..9ae99debe7 100644
--- a/grc/core/Connection.py
+++ b/grc/core/Connection.py
@@ -19,11 +19,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
 from __future__ import absolute_import
 
+import collections
+
 from six.moves import range
 
 from . import Constants
 from .Element import Element
-from .utils import odict
 
 
 class Connection(Element):
@@ -157,7 +158,7 @@ class Connection(Element):
         Returns:
             a nested data odict
         """
-        n = odict()
+        n = collections.OrderedDict()
         n['source_block_id'] = self.get_source().get_parent().get_id()
         n['sink_block_id'] = self.get_sink().get_parent().get_id()
         n['source_key'] = self.get_source().get_key()
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index 9edd4f24d8..b0f52dbe74 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -22,13 +22,14 @@ import time
 import re
 from itertools import chain
 from operator import methodcaller, attrgetter
+import collections
 
 from six.moves import filter
 
 from . import Messages
 from .Constants import FLOW_GRAPH_FILE_FORMAT_VERSION
 from .Element import Element
-from .utils import odict, expr_utils
+from .utils import expr_utils
 
 _parameter_matcher = re.compile('^(parameter)$')
 _monitors_searcher = re.compile('(ctrlport_monitor)')
@@ -364,20 +365,19 @@ class FlowGraph(Element):
             str(b)
         ))
         connections = sorted(self.connections, key=str)
-        n = odict()
+        n = collections.OrderedDict()
         n['timestamp'] = self._timestamp
         n['block'] = [b.export_data() for b in blocks]
         n['connection'] = [c.export_data() for c in connections]
-        instructions = odict({
-            'created': '.'.join(self.get_parent().config.version_parts),
-            'format': FLOW_GRAPH_FILE_FORMAT_VERSION,
-        })
-        return odict({'flow_graph': n, '_instructions': instructions})
+        instructions = collections.OrderedDict()
+        instructions['created'] = '.'.join(self.get_parent().config.version_parts)
+        instructions['format'] = FLOW_GRAPH_FILE_FORMAT_VERSION
+        return {'flow_graph': n, '_instructions': instructions}
 
     def import_data(self, n):
         """
         Import blocks and connections into this flow graph.
-        Clear this flowgraph of all previous blocks and connections.
+        Clear this flow graph of all previous blocks and connections.
         Any blocks or connections in error will be ignored.
 
         Args:
@@ -388,18 +388,18 @@ class FlowGraph(Element):
         del self.connections[:]
         # set file format
         try:
-            instructions = n.find('_instructions') or {}
+            instructions = n.get('_instructions', {})
             file_format = int(instructions.get('format', '0')) or _guess_file_format_1(n)
         except:
             file_format = 0
 
-        fg_n = n and n.find('flow_graph') or odict()  # use blank data if none provided
-        self._timestamp = fg_n.find('timestamp') or time.ctime()
+        fg_n = n and n.get('flow_graph', {})  # use blank data if none provided
+        self._timestamp = fg_n.get('timestamp', time.ctime())
 
         # build the blocks
         self._options_block = self.new_block('options')
-        for block_n in fg_n.findall('block'):
-            key = block_n.find('key')
+        for block_n in fg_n.get('block', []):
+            key = block_n['key']
             block = self._options_block if key == 'options' else self.new_block(key)
 
             if not block:
@@ -442,12 +442,12 @@ class FlowGraph(Element):
             return port
 
         errors = False
-        for connection_n in fg_n.findall('connection'):
+        for connection_n in fg_n.get('connection', []):
             # get the block ids and port keys
-            source_block_id = connection_n.find('source_block_id')
-            sink_block_id = connection_n.find('sink_block_id')
-            source_key = connection_n.find('source_key')
-            sink_key = connection_n.find('sink_key')
+            source_block_id = connection_n.get('source_block_id')
+            sink_block_id = connection_n.get('sink_block_id')
+            source_key = connection_n.get('source_key')
+            sink_key = connection_n.get('sink_key')
             try:
                 source_block = self.get_block(source_block_id)
                 sink_block = self.get_block(sink_block_id)
@@ -502,7 +502,7 @@ class FlowGraph(Element):
 
                         for i in times:
                             n['key'] = str(len(get_p()))
-                            n = odict(n)
+                            n = dict(n)
                             port = block.get_parent().get_parent().Port(
                                 block=block, n=n, dir=direc)
                             get_p().append(port)
@@ -553,9 +553,9 @@ def _guess_file_format_1(n):
     """
     try:
         has_non_numeric_message_keys = any(not (
-            connection_n.find('source_key').isdigit() and
-            connection_n.find('sink_key').isdigit()
-        ) for connection_n in n.find('flow_graph').findall('connection'))
+            connection_n.get('source_key', '').isdigit() and
+            connection_n.get('sink_key', '').isdigit()
+        ) for connection_n in n.get('flow_graph', []).get('connection', []))
         if has_non_numeric_message_keys:
             return 1
     except:
@@ -569,20 +569,20 @@ def _initialize_dummy_block(block, block_n):
     Modify block object to get the behaviour for a missing block
     """
 
-    block._key = block_n.find('key')
+    block._key = block_n.get('key')
     block.is_dummy_block = lambda: True
     block.is_valid = lambda: False
     block.get_enabled = lambda: False
-    for param_n in block_n.findall('param'):
+    for param_n in block_n.get('param', []):
         if param_n['key'] not in block.get_param_keys():
-            new_param_n = odict({'key': param_n['key'], 'name': param_n['key'], 'type': 'string'})
+            new_param_n = {'key': param_n['key'], 'name': param_n['key'], 'type': 'string'}
             params = block.get_parent().get_parent().Param(block=block, n=new_param_n)
             block.get_params().append(params)
 
 
 def _dummy_block_add_port(block, key, dir):
     """ This is so ugly... Add a port to a dummy-field block """
-    port_n = odict({'name': '?', 'key': key, 'type': ''})
+    port_n = {'name': '?', 'key': key, 'type': ''}
     port = block.get_parent().get_parent().Port(block=block, n=port_n, dir=dir)
     if port.is_source:
         block.get_sources().append(port)
diff --git a/grc/core/Param.py b/grc/core/Param.py
index 45f0187d27..a9d495d5a4 100644
--- a/grc/core/Param.py
+++ b/grc/core/Param.py
@@ -22,12 +22,12 @@ from __future__ import absolute_import
 import ast
 import weakref
 import re
+import collections
 
 from six.moves import builtins, filter, map, range, zip
 
 from . import Constants
 from .Element import Element
-from .utils import odict
 
 # Blacklist certain ids, its not complete, but should help
 ID_BLACKLIST = ['self', 'options', 'gr', 'blks2', 'wxgui', 'wx', 'math', 'forms', 'firdes'] + dir(builtins)
@@ -79,10 +79,10 @@ class Option(Element):
 
     def __init__(self, param, n):
         Element.__init__(self, param)
-        self._name = n.find('name')
-        self._key = n.find('key')
+        self._name = n.get('name')
+        self._key = n.get('key')
         self._opts = dict()
-        opts = n.findall('opt')
+        opts = n.get('opt', [])
         # Test against opts when non enum
         if not self.get_parent().is_enum() and opts:
             raise Exception('Options for non-enum types cannot have sub-options')
@@ -155,7 +155,7 @@ class Param(Element):
             n: the nested odict
         """
         # If the base key is a valid param key, copy its data and overlay this params data
-        base_key = n.find('base_key')
+        base_key = n.get('base_key')
         if base_key and base_key in block.get_param_keys():
             n_expanded = block.get_param(base_key)._n.copy()
             n_expanded.update(n)
@@ -163,12 +163,12 @@ class Param(Element):
         # Save odict in case this param will be base for another
         self._n = n
         # Parse the data
-        self._name = n.find('name')
-        self._key = n.find('key')
-        value = n.find('value') or ''
-        self._type = n.find('type') or 'raw'
-        self._hide = n.find('hide') or ''
-        self._tab_label = n.find('tab') or block.get_param_tab_labels()[0]
+        self._name = n['name']
+        self._key = n['key']
+        value = n.get('value', '')
+        self._type = n.get('type', 'raw')
+        self._hide = n.get('hide', '')
+        self._tab_label = n.get('tab', block.get_param_tab_labels()[0])
         if self._tab_label not in block.get_param_tab_labels():
             block.get_param_tab_labels().append(self._tab_label)
         # Build the param
@@ -176,7 +176,7 @@ class Param(Element):
         # Create the Option objects from the n data
         self._options = list()
         self._evaluated = None
-        for o_n in n.findall('option'):
+        for o_n in n.get('option', []):
             option = Option(param=self, n=o_n)
             key = option.get_key()
             # Test against repeated keys
@@ -703,7 +703,7 @@ class Param(Element):
         Returns:
             a nested data odict
         """
-        n = odict()
+        n = collections.OrderedDict()
         n['key'] = self.get_key()
         n['value'] = self.get_value()
         return n
diff --git a/grc/core/ParseXML.py b/grc/core/ParseXML.py
index d1306fcab4..163289ba06 100644
--- a/grc/core/ParseXML.py
+++ b/grc/core/ParseXML.py
@@ -24,7 +24,6 @@ from lxml import etree
 import six
 from six.moves import map
 
-from .utils import odict
 
 xml_failures = {}
 etree.set_default_parser(etree.XMLParser(remove_comments=True))
@@ -80,19 +79,35 @@ def from_file(xml_file):
         the nested data with grc version information
     """
     xml = etree.parse(xml_file)
-    nested_data = _from_file(xml.getroot())
+
+    tag, nested_data = _from_file(xml.getroot())
+    nested_data = {tag: nested_data, '_instructions': {}}
 
     # Get the embedded instructions and build a dictionary item
-    nested_data['_instructions'] = {}
     xml_instructions = xml.xpath('/processing-instruction()')
     for inst in xml_instructions:
         if inst.target != 'grc':
             continue
-        nested_data['_instructions'] = odict(inst.attrib)
+        nested_data['_instructions'] = dict(inst.attrib)
     return nested_data
 
 
-def _from_file(xml):
+WANT_A_LIST = {
+    '/block': 'import callback param check sink source'.split(),
+    '/block/param_tab_order': 'tab'.split(),
+    '/block/param': 'option'.split(),
+    '/block/param/option': 'opt'.split(),
+    '/flow_graph': 'block connection'.split(),
+    '/flow_graph/block': 'param'.split(),
+    '/cat': 'cat block'.split(),
+    '/cat/cat': 'cat block'.split(),
+    '/cat/cat/cat': 'cat block'.split(),
+    '/cat/cat/cat/cat': 'cat block'.split(),
+    '/domain': 'connection'.split(),
+}
+
+
+def _from_file(xml, parent_tag=''):
     """
     Recursively parse the xml tree into nested data format.
 
@@ -103,21 +118,24 @@ def _from_file(xml):
         the nested data
     """
     tag = xml.tag
+    tag_path = parent_tag + '/' + tag
+
     if not len(xml):
-        return odict({tag: xml.text or ''})  # store empty tags (text is None) as empty string
-    nested_data = odict()
+        return tag, xml.text or ''  # store empty tags (text is None) as empty string
+
+    nested_data = {}
     for elem in xml:
-        key, value = list(_from_file(elem).items())[0]
-        if key in nested_data:
-            nested_data[key].append(value)
+        key, value = _from_file(elem, tag_path)
+
+        if key in WANT_A_LIST.get(tag_path, []):
+            try:
+                nested_data[key].append(value)
+            except KeyError:
+                nested_data[key] = [value]
         else:
-            nested_data[key] = [value]
-    # Delistify if the length of values is 1
-    for key, values in six.iteritems(nested_data):
-        if len(values) == 1:
-            nested_data[key] = values[0]
+            nested_data[key] = value
 
-    return odict({tag: nested_data})
+    return tag, nested_data
 
 
 def to_file(nested_data, xml_file):
diff --git a/grc/core/Platform.py b/grc/core/Platform.py
index 25f415639a..403c6c87b4 100644
--- a/grc/core/Platform.py
+++ b/grc/core/Platform.py
@@ -17,11 +17,14 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
-from __future__ import absolute_import
-from __future__ import print_function
+from __future__ import absolute_import, print_function
+
 import os
 import sys
 
+import six
+from six.moves import range
+
 from . import ParseXML, Messages, Constants
 
 from .Config import Config
@@ -33,10 +36,7 @@ from .Block import Block
 from .Port import Port
 from .Param import Param
 
-from .utils import odict, extract_docs
-import six
-from six.moves import map
-from six.moves import range
+from .utils import extract_docs
 
 
 class Platform(Element):
@@ -72,9 +72,9 @@ class Platform(Element):
         self._flow_graph = Element(self)
         self._flow_graph.connections = []
 
-        self.blocks = None
-        self._blocks_n = None
-        self._category_trees_n = None
+        self.blocks = {}
+        self._blocks_n = {}
+        self._category_trees_n = []
         self.domains = {}
         self.connection_templates = {}
 
@@ -141,13 +141,15 @@ class Platform(Element):
     def load_blocks(self):
         """load the blocks and block tree from the search paths"""
         self._docstring_extractor.start()
+
         # Reset
-        self.blocks = odict()
-        self._blocks_n = odict()
-        self._category_trees_n = list()
+        self.blocks.clear()
+        self._blocks_n.clear()
+        del self._category_trees_n[:]
         self.domains.clear()
         self.connection_templates.clear()
         ParseXML.xml_failures.clear()
+
         # Try to parse and load blocks
         for xml_file in self.iter_xml_files():
             try:
@@ -161,6 +163,7 @@ class Platform(Element):
                 # print >> sys.stderr, 'Warning: Block validation failed:\n\t%s\n\tIgnoring: %s' % (e, xml_file)
                 pass
             except Exception as e:
+                raise
                 print('Warning: XML parsing failed:\n\t%r\n\tIgnoring: %s' % (e, xml_file), file=sys.stderr)
 
         self._docstring_extractor.finish()
@@ -180,7 +183,7 @@ class Platform(Element):
         """Load block description from xml file"""
         # Validate and import
         ParseXML.validate_dtd(xml_file, self._block_dtd)
-        n = ParseXML.from_file(xml_file).find('block')
+        n = ParseXML.from_file(xml_file).get('block', {})
         n['block_wrapper_path'] = xml_file  # inject block wrapper path
         # Get block instance and add it to the list of blocks
         block = self.Block(self._flow_graph, n)
@@ -200,15 +203,15 @@ class Platform(Element):
     def load_category_tree_xml(self, xml_file):
         """Validate and parse category tree file and add it to list"""
         ParseXML.validate_dtd(xml_file, Constants.BLOCK_TREE_DTD)
-        n = ParseXML.from_file(xml_file).find('cat')
+        n = ParseXML.from_file(xml_file).get('cat', {})
         self._category_trees_n.append(n)
 
     def load_domain_xml(self, xml_file):
         """Load a domain properties and connection templates from XML"""
         ParseXML.validate_dtd(xml_file, Constants.DOMAIN_DTD)
-        n = ParseXML.from_file(xml_file).find('domain')
+        n = ParseXML.from_file(xml_file).get('domain')
 
-        key = n.find('key')
+        key = n.get('key')
         if not key:
             print('Warning: Domain with emtpy key.\n\tIgnoring: {}'.format(xml_file), file=sys.stderr)
             return
@@ -222,7 +225,7 @@ class Platform(Element):
                 return s.lower() not in ('false', 'off', '0', '')
             return d
 
-        color = n.find('color') or ''
+        color = n.get('color') or ''
         try:
             chars_per_color = 2 if len(color) > 4 else 1
             tuple(int(color[o:o + 2], 16) / 255.0 for o in range(1, 3 * chars_per_color, chars_per_color))
@@ -232,19 +235,19 @@ class Platform(Element):
                 color = None
 
         self.domains[key] = dict(
-            name=n.find('name') or key,
-            multiple_sinks=to_bool(n.find('multiple_sinks'), True),
-            multiple_sources=to_bool(n.find('multiple_sources'), False),
+            name=n.get('name') or key,
+            multiple_sinks=to_bool(n.get('multiple_sinks'), True),
+            multiple_sources=to_bool(n.get('multiple_sources'), False),
             color=color
         )
-        for connection_n in n.findall('connection'):
-            key = (connection_n.find('source_domain'), connection_n.find('sink_domain'))
+        for connection_n in n.get('connection', []):
+            key = (connection_n.get('source_domain'), connection_n.get('sink_domain'))
             if not all(key):
                 print('Warning: Empty domain key(s) in connection template.\n\t{}'.format(xml_file), file=sys.stderr)
             elif key in self.connection_templates:
                 print('Warning: Connection template "{}" already exists.\n\t{}'.format(key, xml_file), file=sys.stderr)
             else:
-                self.connection_templates[key] = connection_n.find('make') or ''
+                self.connection_templates[key] = connection_n.get('make') or ''
 
     def load_block_tree(self, block_tree):
         """
@@ -258,13 +261,13 @@ class Platform(Element):
         # Recursive function to load categories and blocks
         def load_category(cat_n, parent=None):
             # Add this category
-            parent = (parent or []) + [cat_n.find('name')]
+            parent = (parent or []) + [cat_n.get('name')]
             block_tree.add_block(parent)
             # Recursive call to load sub categories
-            for cat in cat_n.findall('cat'):
+            for cat in cat_n.get('cat', []):
                 load_category(cat, parent)
             # Add blocks in this category
-            for block_key in cat_n.findall('block'):
+            for block_key in cat_n.get('block', []):
                 if block_key not in self.blocks:
                     print('Warning: Block key "{}" not found when loading category tree.'.format(block_key), file=sys.stderr)
                     continue
diff --git a/grc/core/Port.py b/grc/core/Port.py
index a24262da6b..34edb8d0b4 100644
--- a/grc/core/Port.py
+++ b/grc/core/Port.py
@@ -124,23 +124,24 @@ class Port(Element):
             n['type'] = 'message'  # For port color
         if n['type'] == 'msg':
             n['key'] = 'msg'
-        if not n.find('key'):
-            n['key'] = str(next(block.port_counters[dir == 'source']))
+
+        n.setdefault('key', str(next(block.port_counters[dir == 'source'])))
 
         # Build the port
         Element.__init__(self, block)
         # Grab the data
         self._name = n['name']
         self._key = n['key']
-        self._type = n['type'] or ''
-        self._domain = n['domain']
-        self._hide = n.find('hide') or ''
+        self._type = n.get('type', '')
+        self._domain = n.get('domain')
+        self._hide = n.get('hide', '')
         self._dir = dir
         self._hide_evaluated = False  # Updated on rewrite()
 
-        self._nports = n.find('nports') or ''
-        self._vlen = n.find('vlen') or ''
-        self._optional = bool(n.find('optional'))
+        self._nports = n.get('nports', '')
+        self._vlen = n.get('vlen', '')
+        self._optional = bool(n.get('optional'))
+        self.di_optional = bool(n.get('optional'))
         self._clones = []  # References to cloned ports (for nports > 1)
 
     def __str__(self):
diff --git a/grc/core/generator/Generator.py b/grc/core/generator/Generator.py
index c9b065372d..c27e926c79 100644
--- a/grc/core/generator/Generator.py
+++ b/grc/core/generator/Generator.py
@@ -17,12 +17,15 @@
 
 
 from __future__ import absolute_import
+
 import codecs
 import os
 import tempfile
 import operator
+import collections
 
 from Cheetah.Template import Template
+import six
 
 from .FlowGraphProxy import FlowGraphProxy
 from .. import ParseXML, Messages
@@ -30,7 +33,7 @@ from ..Constants import (
     TOP_BLOCK_FILE_MODE, BLOCK_FLAG_NEED_QT_GUI,
     HIER_BLOCK_FILE_MODE, BLOCK_DTD
 )
-from ..utils import expr_utils, odict
+from ..utils import expr_utils
 
 DATA_DIR = os.path.dirname(__file__)
 FLOW_GRAPH_TEMPLATE = os.path.join(DATA_DIR, 'flow_graph.tmpl')
@@ -299,7 +302,7 @@ class HierBlockGenerator(TopBlockGenerator):
             return name
 
         # Build the nested data
-        block_n = odict()
+        block_n = collections.OrderedDict()
         block_n['name'] = self._flow_graph.get_option('title') or \
             self._flow_graph.get_option('id').replace('_', ' ').title()
         block_n['key'] = block_key
@@ -324,7 +327,7 @@ class HierBlockGenerator(TopBlockGenerator):
         # Parameters
         block_n['param'] = list()
         for param in parameters:
-            param_n = odict()
+            param_n = collections.OrderedDict()
             param_n['name'] = param.get_param('label').get_value() or param.get_id()
             param_n['key'] = param.get_id()
             param_n['value'] = param.get_param('value').get_value()
@@ -341,7 +344,7 @@ class HierBlockGenerator(TopBlockGenerator):
         for direction in ('sink', 'source'):
             block_n[direction] = list()
             for port in self._flow_graph.get_hier_block_io(direction):
-                port_n = odict()
+                port_n = collections.OrderedDict()
                 port_n['name'] = port['label']
                 port_n['type'] = port['type']
                 if port['type'] != "message":
@@ -374,14 +377,18 @@ class QtHierBlockGenerator(HierBlockGenerator):
 
     def _build_block_n_from_flow_graph_io(self):
         n = HierBlockGenerator._build_block_n_from_flow_graph_io(self)
-        block_n = n['block']
+        block_n = collections.OrderedDict()
+
+        # insert flags after category
+        for key, value in six.iteritems(n['block']):
+            block_n[key] = value
+            if key == 'category':
+                block_n['flags'] = BLOCK_FLAG_NEED_QT_GUI
 
         if not block_n['name'].upper().startswith('QT GUI'):
             block_n['name'] = 'QT GUI ' + block_n['name']
 
-        block_n.insert_after('category', 'flags', BLOCK_FLAG_NEED_QT_GUI)
-
-        gui_hint_param = odict()
+        gui_hint_param = collections.OrderedDict()
         gui_hint_param['name'] = 'GUI Hint'
         gui_hint_param['key'] = 'gui_hint'
         gui_hint_param['value'] = ''
@@ -393,4 +400,5 @@ class QtHierBlockGenerator(HierBlockGenerator):
             "\n#set $win = 'self.%s' % $id"
             "\n${gui_hint()($win)}"
         )
-        return n
+
+        return {'block': block_n}
diff --git a/grc/core/utils/__init__.py b/grc/core/utils/__init__.py
index 0d84f7131d..66393fdc1b 100644
--- a/grc/core/utils/__init__.py
+++ b/grc/core/utils/__init__.py
@@ -21,4 +21,3 @@ from . import expr_utils
 from . import epy_block_io
 from . import extract_docs
 
-from .odict import odict
diff --git a/grc/core/utils/odict.py b/grc/core/utils/odict.py
deleted file mode 100644
index 38f898a97f..0000000000
--- a/grc/core/utils/odict.py
+++ /dev/null
@@ -1,113 +0,0 @@
-"""
-Copyright 2008-2015 Free Software Foundation, Inc.
-This file is part of GNU Radio
-
-GNU Radio Companion is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-GNU Radio Companion is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
-"""
-
-from __future__ import absolute_import
-
-from UserDict import DictMixin
-
-
-class odict(DictMixin):
-
-    def __init__(self, d={}):
-        self._keys = list(d.keys())
-        self._data = dict(d.copy())
-
-    def __setitem__(self, key, value):
-        if key not in self._data:
-            self._keys.append(key)
-        self._data[key] = value
-
-    def __getitem__(self, key):
-        return self._data[key]
-
-    def __delitem__(self, key):
-        del self._data[key]
-        self._keys.remove(key)
-
-    def keys(self):
-        return list(self._keys)
-
-    def copy(self):
-        copy_dict = odict()
-        copy_dict._data = self._data.copy()
-        copy_dict._keys = list(self._keys)
-        return copy_dict
-
-    def insert_after(self, pos_key, key, val):
-        """
-        Insert the new key, value entry after the entry given by the position key.
-        If the positional key is None, insert at the end.
-
-        Args:
-            pos_key: the positional key
-            key: the key for the new entry
-            val: the value for the new entry
-        """
-        index = (pos_key is None) and len(self._keys) or self._keys.index(pos_key)
-        if key in self._keys:
-            raise KeyError('Cannot insert, key "{}" already exists'.format(str(key)))
-        self._keys.insert(index+1, key)
-        self._data[key] = val
-
-    def insert_before(self, pos_key, key, val):
-        """
-        Insert the new key, value entry before the entry given by the position key.
-        If the positional key is None, insert at the begining.
-
-        Args:
-            pos_key: the positional key
-            key: the key for the new entry
-            val: the value for the new entry
-        """
-        index = (pos_key is not None) and self._keys.index(pos_key) or 0
-        if key in self._keys:
-            raise KeyError('Cannot insert, key "{}" already exists'.format(str(key)))
-        self._keys.insert(index, key)
-        self._data[key] = val
-
-    def find(self, key):
-        """
-        Get the value for this key if exists.
-
-        Args:
-            key: the key to search for
-
-        Returns:
-            the value or None
-        """
-        if key in self:
-            return self[key]
-        return None
-
-    def findall(self, key):
-        """
-        Get a list of values for this key.
-
-        Args:
-            key: the key to search for
-
-        Returns:
-            a list of values or empty list
-        """
-        obj = self.find(key)
-        if obj is None:
-            obj = list()
-        if isinstance(obj, list):
-            return obj
-        return [obj]
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index 50e146b4db..8f35222d42 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -213,15 +213,15 @@ class FlowGraph(Element, _Flowgraph):
             x_off, y_off = 0, 0
         #create blocks
         for block_n in blocks_n:
-            block_key = block_n.find('key')
-            if block_key == 'options': continue
+            block_key = block_n.get('key')
+            if block_key == 'options':
+                continue
             block = self.new_block(block_key)
             if not block:
                 continue  # unknown block was pasted (e.g. dummy block)
             selected.add(block)
             #set params
-            params = dict((n.find('key'), n.find('value'))
-                          for n in block_n.findall('param'))
+            params = {n['key']: n['value'] for n in block_n.get('param', [])}
             if block_key == 'epy_block':
                 block.get_param('_io_cache').set_value(params.pop('_io_cache'))
                 block.get_param('_source_code').set_value(params.pop('_source_code'))
@@ -241,8 +241,8 @@ class FlowGraph(Element, _Flowgraph):
         self.update()
         #create connections
         for connection_n in connections_n:
-            source = old_id2block[connection_n.find('source_block_id')].get_source(connection_n.find('source_key'))
-            sink = old_id2block[connection_n.find('sink_block_id')].get_sink(connection_n.find('sink_key'))
+            source = old_id2block[connection_n.get('source_block_id')].get_source(connection_n.get('source_key'))
+            sink = old_id2block[connection_n.get('sink_block_id')].get_sink(connection_n.get('sink_key'))
             self.connect(source, sink)
         #set all pasted elements selected
         for block in selected: selected = selected.union(set(block.get_connections()))
-- 
cgit v1.2.3


From 6375ebf0eb2b619e1a31ec8b8babc3ad0f968dd2 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Fri, 3 Jun 2016 16:37:05 +0200
Subject: grc-refactor: Connections

---
 grc/core/Connection.py             | 106 ++++++++++++++++++++-----------------
 grc/core/FlowGraph.py              |   4 +-
 grc/core/Port.py                   |   6 +--
 grc/core/generator/Generator.py    |  20 +++----
 grc/core/generator/flow_graph.tmpl |   6 +--
 grc/gui/ActionHandler.py           |   8 +--
 grc/gui/Connection.py              |  24 ++++-----
 grc/gui/FlowGraph.py               |   2 +-
 8 files changed, 93 insertions(+), 83 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/core/Connection.py b/grc/core/Connection.py
index 9ae99debe7..a15fe6a2e8 100644
--- a/grc/core/Connection.py
+++ b/grc/core/Connection.py
@@ -45,6 +45,21 @@ class Connection(Element):
             a new connection
         """
         Element.__init__(self, flow_graph)
+
+        source, sink = self._get_sink_source(porta, portb)
+
+        self.source_port = source
+        self.sink_port = sink
+
+        # Ensure that this connection (source -> sink) is unique
+        for connection in flow_graph.connections:
+            if connection.source_port is source and connection.sink_port is sink:
+                raise LookupError('This connection between source and sink is not unique.')
+
+        self._make_bus_connect()
+
+    @staticmethod
+    def _get_sink_source(porta, portb):
         source = sink = None
         # Separate the source and sink
         for port in (porta, portb):
@@ -56,42 +71,18 @@ class Connection(Element):
             raise ValueError('Connection could not isolate source')
         if not sink:
             raise ValueError('Connection could not isolate sink')
-
-        if (source.get_type() == 'bus') != (sink.get_type() == 'bus'):
-            raise ValueError('busses must get with busses')
-
-        if not len(source.get_associated_ports()) == len(sink.get_associated_ports()):
-            raise ValueError('port connections must have same cardinality')
-        # Ensure that this connection (source -> sink) is unique
-        for connection in flow_graph.connections:
-            if connection.get_source() is source and connection.get_sink() is sink:
-                raise LookupError('This connection between source and sink is not unique.')
-        self._source = source
-        self._sink = sink
-        if source.get_type() == 'bus':
-
-            sources = source.get_associated_ports()
-            sinks = sink.get_associated_ports()
-
-            for i in range(len(sources)):
-                try:
-                    flow_graph.connect(sources[i], sinks[i])
-                except:
-                    pass
+        return source, sink
 
     def __str__(self):
         return 'Connection (\n\t{}\n\t\t{}\n\t{}\n\t\t{}\n)'.format(
-            self.get_source().get_parent(),
-            self.get_source(),
-            self.get_sink().get_parent(),
-            self.get_sink(),
+            self.source_block, self.source_port, self.sink_block, self.sink_port,
         )
 
     def is_msg(self):
-        return self.get_source().get_type() == self.get_sink().get_type() == 'msg'
+        return self.source_port.get_type() == self.sink_port.get_type() == 'msg'
 
     def is_bus(self):
-        return self.get_source().get_type() == self.get_sink().get_type() == 'bus'
+        return self.source_port.get_type() == self.sink_port.get_type() == 'bus'
 
     def validate(self):
         """
@@ -104,18 +95,20 @@ class Connection(Element):
         """
         Element.validate(self)
         platform = self.get_parent().get_parent()
-        source_domain = self.get_source().get_domain()
-        sink_domain = self.get_sink().get_domain()
+
+        source_domain = self.source_port.get_domain()
+        sink_domain = self.sink_port.get_domain()
+
         if (source_domain, sink_domain) not in platform.connection_templates:
             self.add_error_message('No connection known for domains "{}", "{}"'.format(
-                    source_domain, sink_domain))
+                source_domain, sink_domain))
         too_many_other_sinks = (
             not platform.domains.get(source_domain, []).get('multiple_sinks', False) and
-            len(self.get_source().get_enabled_connections()) > 1
+            len(self.source_port.get_enabled_connections()) > 1
         )
         too_many_other_sources = (
             not platform.domains.get(sink_domain, []).get('multiple_sources', False) and
-            len(self.get_sink().get_enabled_connections()) > 1
+            len(self.sink_port.get_enabled_connections()) > 1
         )
         if too_many_other_sinks:
             self.add_error_message(
@@ -124,8 +117,8 @@ class Connection(Element):
             self.add_error_message(
                 'Domain "{}" can have only one upstream block'.format(sink_domain))
 
-        source_size = Constants.TYPE_TO_SIZEOF[self.get_source().get_type()] * self.get_source().get_vlen()
-        sink_size = Constants.TYPE_TO_SIZEOF[self.get_sink().get_type()] * self.get_sink().get_vlen()
+        source_size = Constants.TYPE_TO_SIZEOF[self.source_port.get_type()] * self.source_port.get_vlen()
+        sink_size = Constants.TYPE_TO_SIZEOF[self.sink_port.get_type()] * self.sink_port.get_vlen()
         if source_size != sink_size:
             self.add_error_message('Source IO size "{}" does not match sink IO size "{}".'.format(source_size, sink_size))
 
@@ -136,17 +129,15 @@ class Connection(Element):
         Returns:
             true if source and sink blocks are enabled
         """
-        return self.get_source().get_parent().get_enabled() and \
-            self.get_sink().get_parent().get_enabled()
+        return self.source_block.get_enabled() and self.sink_block.get_enabled()
 
-    #############################
-    # Access Ports
-    #############################
-    def get_sink(self):
-        return self._sink
+    @property
+    def source_block(self):
+        return self.source_port.get_parent()
 
-    def get_source(self):
-        return self._source
+    @property
+    def sink_block(self):
+        return self.sink_port.get_parent()
 
     ##############################################
     # Import/Export Methods
@@ -159,8 +150,27 @@ class Connection(Element):
             a nested data odict
         """
         n = collections.OrderedDict()
-        n['source_block_id'] = self.get_source().get_parent().get_id()
-        n['sink_block_id'] = self.get_sink().get_parent().get_id()
-        n['source_key'] = self.get_source().get_key()
-        n['sink_key'] = self.get_sink().get_key()
+        n['source_block_id'] = self.source_block.get_id()
+        n['sink_block_id'] = self.sink_block.get_id()
+        n['source_key'] = self.source_port.get_key()
+        n['sink_key'] = self.sink_port.get_key()
         return n
+
+    def _make_bus_connect(self):
+        source, sink = self.source_port, self.sink_port
+
+        if (source.get_type() == 'bus') != (sink.get_type() == 'bus'):
+            raise ValueError('busses must get with busses')
+
+        if not len(source.get_associated_ports()) == len(sink.get_associated_ports()):
+            raise ValueError('port connections must have same cardinality')
+
+        if source.get_type() == 'bus':
+            sources = source.get_associated_ports()
+            sinks = sink.get_associated_ports()
+
+            for i in range(len(sources)):
+                try:
+                    self.get_parent().connect(sources[i], sinks[i])
+                except:
+                    pass
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index b0f52dbe74..a17d820539 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -342,7 +342,7 @@ class FlowGraph(Element):
 
         elif element in self.connections:
             if element.is_bus():
-                for port in element.get_source().get_associated_ports():
+                for port in element.source_port.get_associated_ports():
                     for connection in port.get_connections():
                         self.remove_element(connection)
             self.connections.remove(element)
@@ -516,7 +516,7 @@ class FlowGraph(Element):
 
                             for j in range(len(source.get_connections())):
                                 sink.append(
-                                    source.get_connections()[j].get_sink())
+                                    source.get_connections()[j].sink_port)
                             for elt in source.get_connections():
                                 self.remove_element(elt)
                             for j in sink:
diff --git a/grc/core/Port.py b/grc/core/Port.py
index 34edb8d0b4..b0753910e6 100644
--- a/grc/core/Port.py
+++ b/grc/core/Port.py
@@ -33,7 +33,7 @@ def _get_source_from_virtual_sink_port(vsp):
     """
     try:
         return _get_source_from_virtual_source_port(
-            vsp.get_enabled_connections()[0].get_source())
+            vsp.get_enabled_connections()[0].source_port)
     except:
         raise Exception('Could not resolve source for virtual sink port {}'.format(vsp))
 
@@ -71,7 +71,7 @@ def _get_sink_from_virtual_source_port(vsp):
     try:
         # Could have many connections, but use first
         return _get_sink_from_virtual_sink_port(
-            vsp.get_enabled_connections()[0].get_sink())
+            vsp.get_enabled_connections()[0].sink_port)
     except:
         raise Exception('Could not resolve source for virtual source port {}'.format(vsp))
 
@@ -377,7 +377,7 @@ class Port(Element):
             a list of connection objects
         """
         connections = self.get_parent().get_parent().connections
-        connections = [c for c in connections if c.get_source() is self or c.get_sink() is self]
+        connections = [c for c in connections if c.source_port is self or c.sink_port is self]
         return connections
 
     def get_enabled_connections(self):
diff --git a/grc/core/generator/Generator.py b/grc/core/generator/Generator.py
index c27e926c79..97729b3ada 100644
--- a/grc/core/generator/Generator.py
+++ b/grc/core/generator/Generator.py
@@ -167,14 +167,14 @@ class TopBlockGenerator(object):
 
         # Filter out virtual sink connections
         def cf(c):
-            return not (c.is_bus() or c.is_msg() or c.get_sink().get_parent().is_virtual_sink())
+            return not (c.is_bus() or c.is_msg() or c.sink_block.is_virtual_sink())
         connections = [con for con in fg.get_enabled_connections() if cf(con)]
 
         # Get the virtual blocks and resolve their connections
-        virtual = [c for c in connections if c.get_source().get_parent().is_virtual_source()]
+        virtual = [c for c in connections if c.source_block.is_virtual_source()]
         for connection in virtual:
-            source = connection.get_source().resolve_virtual_source()
-            sink = connection.get_sink()
+            source = connection.source.resolve_virtual_source()
+            sink = connection.sink_port
             resolved = fg.get_parent().Connection(flow_graph=fg, porta=source, portb=sink)
             connections.append(resolved)
             # Remove the virtual connection
@@ -189,19 +189,19 @@ class TopBlockGenerator(object):
         for block in bypassed_blocks:
             # Get the upstream connection (off of the sink ports)
             # Use *connections* not get_connections()
-            source_connection = [c for c in connections if c.get_sink() == block.get_sinks()[0]]
+            source_connection = [c for c in connections if c.sink_port == block.get_sinks()[0]]
             # The source connection should never have more than one element.
             assert (len(source_connection) == 1)
 
             # Get the source of the connection.
-            source_port = source_connection[0].get_source()
+            source_port = source_connection[0].source_port
 
             # Loop through all the downstream connections
-            for sink in (c for c in connections if c.get_source() == block.get_sources()[0]):
+            for sink in (c for c in connections if c.source_port == block.get_sources()[0]):
                 if not sink.get_enabled():
                     # Ignore disabled connections
                     continue
-                sink_port = sink.get_sink()
+                sink_port = sink.sink_port
                 connection = fg.get_parent().Connection(flow_graph=fg, porta=source_port, portb=sink_port)
                 connections.append(connection)
                 # Remove this sink connection
@@ -211,8 +211,8 @@ class TopBlockGenerator(object):
 
         # List of connections where each endpoint is enabled (sorted by domains, block names)
         connections.sort(key=lambda c: (
-            c.get_source().get_domain(), c.get_sink().get_domain(),
-            c.get_source().get_parent().get_id(), c.get_sink().get_parent().get_id()
+            c.source_port.get_domain(), c.sink_port.get_domain(),
+            c.source_block.get_id(), c.sink_block.get_id()
         ))
 
         connection_templates = fg.get_parent().connection_templates
diff --git a/grc/core/generator/flow_graph.tmpl b/grc/core/generator/flow_graph.tmpl
index ecdb89390e..21bcb601a2 100644
--- a/grc/core/generator/flow_graph.tmpl
+++ b/grc/core/generator/flow_graph.tmpl
@@ -205,7 +205,7 @@ gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))])
         $DIVIDER
 #end if
 #for $msg in $msgs
-        $(msg.get_source().get_parent().get_id())_msgq_out = $(msg.get_sink().get_parent().get_id())_msgq_in = gr.msg_queue(2)
+        $(msg.source_block.get_id())_msgq_out = $(msg.sink_block.get_id())_msgq_in = gr.msg_queue(2)
 #end for
 ########################################################
 ##Create Blocks
@@ -260,8 +260,8 @@ gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))])
         $DIVIDER
 #end if
 #for $con in $connections
-    #set global $source = $con.get_source()
-    #set global $sink = $con.get_sink()
+    #set global $source = $con.source_port
+    #set global $sink = $con.sink_port
     #include source=$connection_templates[($source.get_domain(), $sink.get_domain())]
 
 #end for
diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py
index 9c3e9246d5..3c6b57b482 100644
--- a/grc/gui/ActionHandler.py
+++ b/grc/gui/ActionHandler.py
@@ -240,15 +240,15 @@ class ActionHandler:
                             for connection in block.connections:
 
                                 # Get id of connected blocks
-                                source_id = connection.get_source().get_parent().get_id()
-                                sink_id = connection.get_sink().get_parent().get_id()
+                                source_id = connection.source_block.get_id()
+                                sink_id = connection.sink_block.get_id()
 
                                 # If connected block is not in the list of selected blocks create a pad for it
                                 if flow_graph.get_block(source_id) not in flow_graph.get_selected_blocks():
-                                    pads.append({'key': connection.get_sink().get_key(), 'coord': connection.get_source().get_coordinate(), 'block_id' : block.get_id(), 'direction': 'source'})
+                                    pads.append({'key': connection.sink_port.get_key(), 'coord': connection.source_port.get_coordinate(), 'block_id' : block.get_id(), 'direction': 'source'})
 
                                 if flow_graph.get_block(sink_id) not in flow_graph.get_selected_blocks():
-                                    pads.append({'key': connection.get_source().get_key(), 'coord': connection.get_sink().get_coordinate(), 'block_id' : block.get_id(), 'direction': 'sink'})
+                                    pads.append({'key': connection.source_port.get_key(), 'coord': connection.sink_port.get_coordinate(), 'block_id' : block.get_id(), 'direction': 'sink'})
 
 
                         # Copy the selected blocks and paste them into a new page
diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py
index 8953ca0183..3af6badaa0 100644
--- a/grc/gui/Connection.py
+++ b/grc/gui/Connection.py
@@ -75,20 +75,20 @@ class Connection(Element, _Connection):
         self._source_coor = None
         #get the source coordinate
         try:
-            connector_length = self.get_source().get_connector_length()
+            connector_length = self.source_port.get_connector_length()
         except:
             return
-        self.x1, self.y1 = Utils.get_rotated_coordinate((connector_length, 0), self.get_source().get_rotation())
+        self.x1, self.y1 = Utils.get_rotated_coordinate((connector_length, 0), self.source_port.get_rotation())
         #get the sink coordinate
-        connector_length = self.get_sink().get_connector_length() + CONNECTOR_ARROW_HEIGHT
-        self.x2, self.y2 = Utils.get_rotated_coordinate((-connector_length, 0), self.get_sink().get_rotation())
+        connector_length = self.sink_port.get_connector_length() + CONNECTOR_ARROW_HEIGHT
+        self.x2, self.y2 = Utils.get_rotated_coordinate((-connector_length, 0), self.sink_port.get_rotation())
         #build the arrow
         self.arrow = [(0, 0),
-            Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, -CONNECTOR_ARROW_BASE/2), self.get_sink().get_rotation()),
-            Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, CONNECTOR_ARROW_BASE/2), self.get_sink().get_rotation()),
+            Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, -CONNECTOR_ARROW_BASE/2), self.sink_port.get_rotation()),
+            Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, CONNECTOR_ARROW_BASE/2), self.sink_port.get_rotation()),
         ]
-        source_domain = self.get_source().get_domain()
-        sink_domain = self.get_sink().get_domain()
+        source_domain = self.source_port.get_domain()
+        sink_domain = self.sink_port.get_domain()
         # self.line_attributes[0] = 2 if source_domain != sink_domain else 0
         # self.line_attributes[1] = Gdk.LINE_DOUBLE_DASH \
         #     if not source_domain == sink_domain == GR_MESSAGE_DOMAIN \
@@ -105,12 +105,12 @@ class Connection(Element, _Connection):
         """Calculate coordinates."""
         self.clear() #FIXME do i want this here?
         #source connector
-        source = self.get_source()
+        source = self.source_port
         X, Y = source.get_connector_coordinate()
         x1, y1 = self.x1 + X, self.y1 + Y
         self.add_line((x1, y1), (X, Y))
         #sink connector
-        sink = self.get_sink()
+        sink = self.sink_port
         X, Y = sink.get_connector_coordinate()
         x2, y2 = self.x2 + X, self.y2 + Y
         self.add_line((x2, y2), (X, Y))
@@ -149,8 +149,8 @@ class Connection(Element, _Connection):
         """
         Draw the connection.
         """
-        sink = self.get_sink()
-        source = self.get_source()
+        sink = self.sink_port
+        source = self.source_port
         #check for changes
         if self._sink_rot != sink.get_rotation() or self._source_rot != source.get_rotation(): self.create_shapes()
         elif self._sink_coor != sink.get_coordinate() or self._source_coor != source.get_coordinate():
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index 8f35222d42..37a233f825 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -184,7 +184,7 @@ class FlowGraph(Element, _Flowgraph):
             y_min = min(y, y_min)
         #get connections between selected blocks
         connections = list(filter(
-            lambda c: c.get_source().get_parent() in blocks and c.get_sink().get_parent() in blocks,
+            lambda c: c.source_block in blocks and c.sink_block in blocks,
             self.connections,
         ))
         clipboard = (
-- 
cgit v1.2.3


From 435e2b16c903b4a9d16d40ffba649698c4ded190 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Thu, 9 Jun 2016 14:44:22 +0200
Subject: grc-refactor: rewrite tree-api in core

---
 grc/core/Block.py                    | 65 ++++++++++++++++++------------------
 grc/core/Connection.py               | 14 ++++----
 grc/core/Element.py                  | 54 ++++++++++++++++++++++++++++--
 grc/core/FlowGraph.py                | 56 ++++++++++++-------------------
 grc/core/Param.py                    | 34 +++++++++----------
 grc/core/Platform.py                 |  8 ++---
 grc/core/Port.py                     | 45 +++++++++++++------------
 grc/core/generator/FlowGraphProxy.py |  2 +-
 grc/core/generator/Generator.py      |  9 +++--
 grc/core/generator/flow_graph.tmpl   |  4 +--
 grc/gui/ActionHandler.py             |  5 ++-
 grc/gui/Block.py                     |  4 +--
 grc/gui/FlowGraph.py                 | 12 +++----
 grc/gui/NotebookPage.py              |  4 +--
 grc/gui/Param.py                     |  2 +-
 grc/gui/ParamWidgets.py              |  8 ++---
 grc/gui/Port.py                      | 26 +++++++--------
 17 files changed, 191 insertions(+), 161 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/core/Block.py b/grc/core/Block.py
index b607f908fb..ff7f041dc0 100644
--- a/grc/core/Block.py
+++ b/grc/core/Block.py
@@ -63,18 +63,18 @@ class Block(Element):
         Returns:
             block a new block
         """
-        Element.__init__(self, flow_graph)
+        Element.__init__(self, parent=flow_graph)
 
         self._name = n['name']
         self._key = n['key']
         self._category = n.get('category', '')
         self._flags = n.get('flags', '')
+        self._doc = n.get('doc', '').strip('\n').replace('\\\n', '')
 
         # Backwards compatibility
         if n.get('throttle') and BLOCK_FLAG_THROTTLE not in self._flags:
             self._flags += BLOCK_FLAG_THROTTLE
 
-        self._doc = n.get('doc', '').strip('\n').replace('\\\n', '')
         self._imports = [i.strip() for i in n.get('import', [])]
         self._make = n.get('make')
         self._var_make = n.get('var_make')
@@ -90,7 +90,6 @@ class Block(Element):
         sinks_n = n.get('sink', [])
 
         # Get list of param tabs
-        n_tabs = n.get('param_tab_order', {})
         self._param_tab_labels = n.get('param_tab_order', {}).get('tab') or [DEFAULT_PARAM_TAB]
         self._params = []
         self._init_params(
@@ -106,22 +105,12 @@ class Block(Element):
         self._epy_source_hash = -1  # for epy blocks
         self._epy_reload_error = None
 
-        self.back_ofthe_bus(self._sources)
-        self.back_ofthe_bus(self._sinks)
-        self.current_bus_structure = {'source': '', 'sink': ''}
-        self._bus_structure_source = n.get('bus_structure_source', '')
-        self._bus_structure_sink = n.get('bus_structure_sink', '')
-        self._bussify_sink = n.get('bus_sink')
-        self._bussify_source = n.get('bus_source')
-        if self._bussify_sink:
-            self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
-        if self._bussify_source:
-            self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
+        self._init_bus_ports(n)
 
     def _add_param(self, key, name, value='', type='raw', **kwargs):
         n = {'key': key, 'name': name, 'value': value, 'type': type}
         n.update(kwargs)
-        param = self.get_parent().get_parent().Param(block=self, n=n)
+        param = self.parent_platform.Param(block=self, n=n)
         self._params.append(param)
 
     def _init_params(self, params_n, has_sources, has_sinks):
@@ -157,7 +146,7 @@ class Block(Element):
 
         param_keys = set(param.get_key() for param in self._params)
         for param_n in params_n:
-            param = self.get_parent().get_parent().Param(block=self, n=param_n)
+            param = self.parent_platform.Param(block=self, n=param_n)
             key = param.get_key()
             if key in param_keys:
                 raise Exception('Key "{}" already exists in params'.format(key))
@@ -168,7 +157,7 @@ class Block(Element):
                         value='', tab=ADVANCED_PARAM_TAB)
 
     def _init_ports(self, ports_n, direction):
-        port_cls = self.get_parent().get_parent().Port
+        port_cls = self.parent_platform.Port
         ports = []
         port_keys = set()
         for port_n in ports_n:
@@ -191,7 +180,7 @@ class Block(Element):
         for check in self._checks:
             check_res = self.resolve_dependencies(check)
             try:
-                if not self.get_parent().evaluate(check_res):
+                if not self.parent.evaluate(check_res):
                     self.add_error_message('Check "{}" failed.'.format(check))
             except:
                 self.add_error_message('Check "{}" did not evaluate.'.format(check))
@@ -201,12 +190,12 @@ class Block(Element):
             value = self._var_value
             try:
                 value = self.get_var_value()
-                self.get_parent().evaluate(value)
+                self.parent.evaluate(value)
             except Exception as err:
                 self.add_error_message('Value "{}" cannot be evaluated:\n{}'.format(value, err))
 
         # check if this is a GUI block and matches the selected generate option
-        current_generate_option = self.get_parent().get_option('generate_options')
+        current_generate_option = self.parent.get_option('generate_options')
 
         def check_generate_mode(label, flag, valid_options):
             block_requires_mode = (
@@ -237,14 +226,14 @@ class Block(Element):
                 num_ports = 1 + len(master_port.get_clones())
                 if master_port.get_hide():
                     for connection in master_port.get_connections():
-                        self.get_parent().remove_element(connection)
+                        self.parent.remove_element(connection)
                 if not nports and num_ports == 1:  # Not a master port and no left-over clones
                     continue
                 # Remove excess cloned ports
                 for port in master_port.get_clones()[nports-1:]:
                     # Remove excess connections
                     for connection in port.get_connections():
-                        self.get_parent().remove_element(connection)
+                        self.parent.remove_element(connection)
                     master_port.remove_clone(port)
                     ports.remove(port)
                 # Add more cloned ports
@@ -261,8 +250,7 @@ class Block(Element):
                 domain_specific_port_index[domain] += 1
 
     def get_doc(self):
-        platform = self.get_parent().get_parent()
-        documentation = platform.block_docstrings.get(self._key, {})
+        documentation = self.parent_platform.block_docstrings.get(self._key, {})
         from_xml = self._doc.strip()
         if from_xml:
             documentation[''] = from_xml
@@ -280,8 +268,8 @@ class Block(Element):
         """
         if raw:
             return self._imports
-        return [i for i in sum([self.resolve_dependencies(i).split('\n')
-                                for i in self._imports], []) if i]
+        return [i for i in sum((self.resolve_dependencies(i).split('\n')
+                                for i in self._imports), []) if i]
 
     def get_make(self, raw=False):
         if raw:
@@ -319,8 +307,8 @@ class Block(Element):
     ###########################################################################
 
     def rewrite_epy_block(self):
-        flowgraph = self.get_parent()
-        platform = flowgraph.get_parent()
+        flowgraph = self.parent_flowgraph
+        platform = self.parent_block
         param_blk = self.get_param('_io_cache')
         param_src = self.get_param('_source_code')
 
@@ -743,7 +731,7 @@ class Block(Element):
             return ''  # TODO: Don't like empty strings. should change this to None eventually
 
         try:
-            clean_bus_structure = self.get_parent().evaluate(bus_structure)
+            clean_bus_structure = self.parent.evaluate(bus_structure)
             return clean_bus_structure
         except:
             return ''
@@ -798,15 +786,13 @@ class Block(Element):
         if direc == 'source':
             get_p = self.get_sources
             get_p_gui = self.get_sources_gui
-            bus_structure = self.get_bus_structure('source')
         else:
             get_p = self.get_sinks
             get_p_gui = self.get_sinks_gui
-            bus_structure = self.get_bus_structure('sink')
 
         for elt in get_p():
             for connect in elt.get_connections():
-                self.get_parent().remove_element(connect)
+                self.parent.remove_element(connect)
 
         if ('bus' not in [a.get_type() for a in get_p()]) and len(get_p()) > 0:
             struct = self.form_bus_structure(direc)
@@ -817,9 +803,22 @@ class Block(Element):
             for i in range(len(struct)):
                 n['key'] = str(len(get_p()))
                 n = dict(n)
-                port = self.get_parent().get_parent().Port(block=self, n=n, dir=direc)
+                port = self.parent.parent.Port(block=self, n=n, dir=direc)
                 get_p().append(port)
         elif 'bus' in [a.get_type() for a in get_p()]:
             for elt in get_p_gui():
                 get_p().remove(elt)
             self.current_bus_structure[direc] = ''
+
+    def _init_bus_ports(self, n):
+        self.back_ofthe_bus(self._sources)
+        self.back_ofthe_bus(self._sinks)
+        self.current_bus_structure = {'source': '', 'sink': ''}
+        self._bus_structure_source = n.get('bus_structure_source', '')
+        self._bus_structure_sink = n.get('bus_structure_sink', '')
+        self._bussify_sink = n.get('bus_sink')
+        self._bussify_source = n.get('bus_source')
+        if self._bussify_sink:
+            self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
+        if self._bussify_source:
+            self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
diff --git a/grc/core/Connection.py b/grc/core/Connection.py
index a15fe6a2e8..2309d159c8 100644
--- a/grc/core/Connection.py
+++ b/grc/core/Connection.py
@@ -24,7 +24,7 @@ import collections
 from six.moves import range
 
 from . import Constants
-from .Element import Element
+from .Element import Element, lazyproperty
 
 
 class Connection(Element):
@@ -94,7 +94,7 @@ class Connection(Element):
         The ports must match in type.
         """
         Element.validate(self)
-        platform = self.get_parent().get_parent()
+        platform = self.parent_platform
 
         source_domain = self.source_port.get_domain()
         sink_domain = self.sink_port.get_domain()
@@ -131,13 +131,13 @@ class Connection(Element):
         """
         return self.source_block.get_enabled() and self.sink_block.get_enabled()
 
-    @property
+    @lazyproperty
     def source_block(self):
-        return self.source_port.get_parent()
+        return self.source_port.parent_block
 
-    @property
+    @lazyproperty
     def sink_block(self):
-        return self.sink_port.get_parent()
+        return self.sink_port.parent_block
 
     ##############################################
     # Import/Export Methods
@@ -171,6 +171,6 @@ class Connection(Element):
 
             for i in range(len(sources)):
                 try:
-                    self.get_parent().connect(sources[i], sinks[i])
+                    self.parent_flowgraph.connect(sources[i], sinks[i])
                 except:
                     pass
diff --git a/grc/core/Element.py b/grc/core/Element.py
index e697d293fb..a046d6beb4 100644
--- a/grc/core/Element.py
+++ b/grc/core/Element.py
@@ -17,11 +17,26 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
+import weakref
+
+
+class lazyproperty(object):
+    def __init__(self, func):
+        self.func = func
+
+    def __get__(self, instance, cls):
+        if instance is None:
+            return self
+        else:
+            value = self.func(instance)
+            setattr(instance, self.func.__name__, value)
+            return value
+
 
 class Element(object):
 
     def __init__(self, parent=None):
-        self._parent = parent
+        self._parent = weakref.ref(parent) if parent else lambda: None
         self._error_messages = []
 
     ##################################################
@@ -33,6 +48,7 @@ class Element(object):
         Call this base method before adding error messages in the subclass.
         """
         del self._error_messages[:]
+
         for child in self.get_children():
             child.validate()
 
@@ -88,8 +104,40 @@ class Element(object):
     ##############################################
     # Tree-like API
     ##############################################
-    def get_parent(self):
-        return self._parent
+    @property
+    def parent(self):
+        return self._parent()
+
+    def get_parent_of_type(self, cls):
+        parent = self.parent
+        if parent is None:
+            return None
+        elif isinstance(parent, cls):
+            return parent
+        else:
+            return parent.get_parent_of_type(cls)
+
+    @lazyproperty
+    def parent_platform(self):
+        from .Platform import Platform
+        return self.get_parent_of_type(Platform)
+
+    @lazyproperty
+    def parent_flowgraph(self):
+        from .FlowGraph import FlowGraph
+        return self.get_parent_of_type(FlowGraph)
+
+    @lazyproperty
+    def parent_block(self):
+        from .Block import Block
+        return self.get_parent_of_type(Block)
+
+    def reset_parents(self):
+        """Reset all lazy properties"""
+        # todo: use case?
+        for name, obj in vars(Element):
+            if isinstance(obj, lazyproperty):
+                delattr(self, name)
 
     def get_children(self):
         return list()
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index a17d820539..16842595c8 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -54,25 +54,20 @@ class FlowGraph(Element):
             the flow graph object
         """
         Element.__init__(self, platform)
-        self._elements = []
         self._timestamp = time.ctime()
+        self._options_block = self.parent_platform.get_new_block(self, 'options')
 
-        self.platform = platform  # todo: make this a lazy prop
-        self.blocks = []
+        self.blocks = [self._options_block]
         self.connections = []
 
         self._eval_cache = {}
         self.namespace = {}
 
         self.grc_file_path = ''
-        self._options_block = self.new_block('options')
 
     def __str__(self):
         return 'FlowGraph - {}({})'.format(self.get_option('title'), self.get_option('id'))
 
-    ##############################################
-    # TODO: Move these to new generator package
-    ##############################################
     def get_imports(self):
         """
         Get a set of all import statements in this flow graph namespace.
@@ -199,19 +194,6 @@ class FlowGraph(Element):
         raise KeyError('No block with ID {!r}'.format(id))
 
     def get_elements(self):
-        """
-        Get a list of all the elements.
-        Always ensure that the options block is in the list (only once).
-
-        Returns:
-            the element list
-        """
-        options_block_count = self.blocks.count(self._options_block)
-        if not options_block_count:
-            self.blocks.append(self._options_block)
-        for _ in range(options_block_count-1):
-            self.blocks.remove(self._options_block)
-
         return self.blocks + self.connections
 
     get_children = get_elements
@@ -220,7 +202,6 @@ class FlowGraph(Element):
         """
         Flag the namespace to be renewed.
         """
-
         self.renew_namespace()
         for child in chain(self.blocks, self.connections):
             child.rewrite()
@@ -297,8 +278,10 @@ class FlowGraph(Element):
         Returns:
             the new block or None if not found
         """
+        if key == 'options':
+            return self._options_block
         try:
-            block = self.platform.get_new_block(self, key)
+            block = self.parent_platform.get_new_block(self, key)
             self.blocks.append(block)
         except KeyError:
             block = None
@@ -317,7 +300,7 @@ class FlowGraph(Element):
             the new connection
         """
 
-        connection = self.platform.Connection(
+        connection = self.parent_platform.Connection(
             flow_graph=self, porta=porta, portb=portb)
         self.connections.append(connection)
         return connection
@@ -329,9 +312,12 @@ class FlowGraph(Element):
         If the element is a block, remove its connections.
         If the element is a connection, just remove the connection.
         """
+        if element is self._options_block:
+            return
+
         if element.is_port:
             # Found a port, set to parent signal block
-            element = element.get_parent()
+            element = element.parent
 
         if element in self.blocks:
             # Remove block, remove all involved connections
@@ -370,7 +356,7 @@ class FlowGraph(Element):
         n['block'] = [b.export_data() for b in blocks]
         n['connection'] = [c.export_data() for c in connections]
         instructions = collections.OrderedDict()
-        instructions['created'] = '.'.join(self.get_parent().config.version_parts)
+        instructions['created'] = '.'.join(self.parent.config.version_parts)
         instructions['format'] = FLOW_GRAPH_FILE_FORMAT_VERSION
         return {'flow_graph': n, '_instructions': instructions}
 
@@ -397,22 +383,22 @@ class FlowGraph(Element):
         self._timestamp = fg_n.get('timestamp', time.ctime())
 
         # build the blocks
-        self._options_block = self.new_block('options')
+        self.blocks.append(self._options_block)
         for block_n in fg_n.get('block', []):
             key = block_n['key']
-            block = self._options_block if key == 'options' else self.new_block(key)
+            block = self.new_block(key)
 
             if not block:
                 # we're before the initial fg update(), so no evaluated values!
                 # --> use raw value instead
                 path_param = self._options_block.get_param('hier_block_src_path')
-                file_path = self.platform.find_file_in_paths(
+                file_path = self.parent_platform.find_file_in_paths(
                     filename=key + '.grc',
                     paths=path_param.get_value(),
                     cwd=self.grc_file_path
                 )
                 if file_path:  # grc file found. load and get block
-                    self.platform.load_and_generate_flow_graph(file_path)
+                    self.parent_platform.load_and_generate_flow_graph(file_path)
                     block = self.new_block(key)  # can be None
 
             if not block:  # looks like this block key cannot be found
@@ -488,22 +474,22 @@ class FlowGraph(Element):
 
                 if 'bus' in [a.get_type() for a in get_p_gui()]:
                     if len(get_p_gui()) > len(bus_structure):
-                        times = list(range(len(bus_structure), len(get_p_gui())))
+                        times = range(len(bus_structure), len(get_p_gui()))
                         for i in times:
                             for connect in get_p_gui()[-1].get_connections():
-                                block.get_parent().remove_element(connect)
+                                block.parent.remove_element(connect)
                             get_p().remove(get_p_gui()[-1])
                     elif len(get_p_gui()) < len(bus_structure):
                         n = {'name': 'bus', 'type': 'bus'}
                         if any(isinstance(a.get_nports(), int) for a in get_p()):
                             n['nports'] = str(1)
 
-                        times = list(range(len(get_p_gui()), len(bus_structure)))
+                        times = range(len(get_p_gui()), len(bus_structure))
 
                         for i in times:
                             n['key'] = str(len(get_p()))
                             n = dict(n)
-                            port = block.get_parent().get_parent().Port(
+                            port = block.parent.parent.Port(
                                 block=block, n=n, dir=direc)
                             get_p().append(port)
 
@@ -576,14 +562,14 @@ def _initialize_dummy_block(block, block_n):
     for param_n in block_n.get('param', []):
         if param_n['key'] not in block.get_param_keys():
             new_param_n = {'key': param_n['key'], 'name': param_n['key'], 'type': 'string'}
-            params = block.get_parent().get_parent().Param(block=block, n=new_param_n)
+            params = block.parent_platform.Param(block=block, n=new_param_n)
             block.get_params().append(params)
 
 
 def _dummy_block_add_port(block, key, dir):
     """ This is so ugly... Add a port to a dummy-field block """
     port_n = {'name': '?', 'key': key, 'type': ''}
-    port = block.get_parent().get_parent().Port(block=block, n=port_n, dir=dir)
+    port = block.parent_platform.Port(block=block, n=port_n, dir=dir)
     if port.is_source:
         block.get_sources().append(port)
     else:
diff --git a/grc/core/Param.py b/grc/core/Param.py
index a9d495d5a4..b21cbcddf1 100644
--- a/grc/core/Param.py
+++ b/grc/core/Param.py
@@ -84,7 +84,7 @@ class Option(Element):
         self._opts = dict()
         opts = n.get('opt', [])
         # Test against opts when non enum
-        if not self.get_parent().is_enum() and opts:
+        if not self.parent.is_enum() and opts:
             raise Exception('Options for non-enum types cannot have sub-options')
         # Extract opts
         for opt in opts:
@@ -304,17 +304,17 @@ class Param(Element):
         Returns:
             hide the hide property string
         """
-        hide = self.get_parent().resolve_dependencies(self._hide).strip()
+        hide = self.parent.resolve_dependencies(self._hide).strip()
         if hide:
             return hide
         # Hide ID in non variable blocks
-        if self.get_key() == 'id' and not _show_id_matcher.match(self.get_parent().get_key()):
+        if self.get_key() == 'id' and not _show_id_matcher.match(self.parent.get_key()):
             return 'part'
         # Hide port controllers for type and nports
-        if self.get_key() in ' '.join([' '.join([p._type, p._nports]) for p in self.get_parent().get_ports()]):
+        if self.get_key() in ' '.join([' '.join([p._type, p._nports]) for p in self.parent.get_ports()]):
             return 'part'
         # Hide port controllers for vlen, when == 1
-        if self.get_key() in ' '.join(p._vlen for p in self.get_parent().get_ports()):
+        if self.get_key() in ' '.join(p._vlen for p in self.parent.get_ports()):
             try:
                 if int(self.get_evaluated()) == 1:
                     return 'part'
@@ -369,7 +369,7 @@ class Param(Element):
         elif t in ('raw', 'complex', 'real', 'float', 'int', 'hex', 'bool'):
             # Raise exception if python cannot evaluate this value
             try:
-                e = self.get_parent().get_parent().evaluate(v)
+                e = self.parent_flowgraph.evaluate(v)
             except Exception as e:
                 raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e))
             # Raise an exception if the data is invalid
@@ -404,7 +404,7 @@ class Param(Element):
                 v = '()'
             # Raise exception if python cannot evaluate this value
             try:
-                e = self.get_parent().get_parent().evaluate(v)
+                e = self.parent.parent.evaluate(v)
             except Exception as e:
                 raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e))
             # Raise an exception if the data is invalid
@@ -435,7 +435,7 @@ class Param(Element):
         elif t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'):
             # Do not check if file/directory exists, that is a runtime issue
             try:
-                e = self.get_parent().get_parent().evaluate(v)
+                e = self.parent.parent.evaluate(v)
                 if not isinstance(e, str):
                     raise Exception()
             except:
@@ -466,16 +466,16 @@ class Param(Element):
         elif t == 'stream_id':
             # Get a list of all stream ids used in the virtual sinks
             ids = [param.get_value() for param in filter(
-                lambda p: p.get_parent().is_virtual_sink(),
+                lambda p: p.parent.is_virtual_sink(),
                 self.get_all_params(t),
             )]
             # Check that the virtual sink's stream id is unique
-            if self.get_parent().is_virtual_sink():
+            if self.parent.is_virtual_sink():
                 # Id should only appear once, or zero times if block is disabled
                 if ids.count(v) > 1:
                     raise Exception('Stream ID "{}" is not unique.'.format(v))
             # Check that the virtual source's steam id is found
-            if self.get_parent().is_virtual_source():
+            if self.parent.is_virtual_source():
                 if v not in ids:
                     raise Exception('Stream ID "{}" is not found.'.format(v))
             return v
@@ -523,7 +523,7 @@ class Param(Element):
             if not v:
                 # Allow for empty grid pos
                 return ''
-            e = self.get_parent().get_parent().evaluate(v)
+            e = self.parent_flowgraph.evaluate(v)
             if not isinstance(e, (list, tuple)) or len(e) != 4 or not all([isinstance(ei, int) for ei in e]):
                 raise Exception('A grid position must be a list of 4 integers.')
             row, col, row_span, col_span = e
@@ -535,7 +535,7 @@ class Param(Element):
                 raise Exception('Row and column span must be greater than zero.')
             # Get hostage cell parent
             try:
-                my_parent = self.get_parent().get_param('notebook').evaluate()
+                my_parent = self.parent.get_param('notebook').evaluate()
             except:
                 my_parent = ''
             # Calculate hostage cells
@@ -558,7 +558,7 @@ class Param(Element):
                 return ''
 
             # Get a list of all notebooks
-            notebook_blocks = [b for b in self.get_parent().get_parent().get_enabled_blocks() if b.get_key() == 'notebook']
+            notebook_blocks = [b for b in self.parent_flowgraph.get_enabled_blocks() if b.get_key() == 'notebook']
             # Check for notebook param syntax
             try:
                 notebook_id, page_index = map(str.strip, v.split(','))
@@ -634,7 +634,7 @@ class Param(Element):
             a list of params
         """
         params = []
-        for block in self.get_parent().get_parent().get_enabled_blocks():
+        for block in self.parent_flowgraph.get_enabled_blocks():
             params.extend(p for p in block.get_params() if p.get_type() == type)
         return params
 
@@ -658,13 +658,13 @@ class Param(Element):
         self._default = str(value)
 
     def get_type(self):
-        return self.get_parent().resolve_dependencies(self._type)
+        return self.parent.resolve_dependencies(self._type)
 
     def get_tab_label(self):
         return self._tab_label
 
     def get_name(self):
-        return self.get_parent().resolve_dependencies(self._name).strip()
+        return self.parent.resolve_dependencies(self._name).strip()
 
     def get_key(self):
         return self._key
diff --git a/grc/core/Platform.py b/grc/core/Platform.py
index 403c6c87b4..2c0b83dca4 100644
--- a/grc/core/Platform.py
+++ b/grc/core/Platform.py
@@ -68,10 +68,6 @@ class Platform(Element):
         self._block_dtd = Constants.BLOCK_DTD
         self._default_flow_graph = Constants.DEFAULT_FLOW_GRAPH
 
-        # Create a dummy flow graph for the blocks
-        self._flow_graph = Element(self)
-        self._flow_graph.connections = []
-
         self.blocks = {}
         self._blocks_n = {}
         self._category_trees_n = []
@@ -80,6 +76,10 @@ class Platform(Element):
 
         self._auto_hier_block_generate_chain = set()
 
+        # Create a dummy flow graph for the blocks
+        self._flow_graph = Element.__new__(FlowGraph)
+        Element.__init__(self._flow_graph, self)
+        self._flow_graph.connections = []
         self.load_blocks()
 
     def __str__(self):
diff --git a/grc/core/Port.py b/grc/core/Port.py
index b0753910e6..99b5a25508 100644
--- a/grc/core/Port.py
+++ b/grc/core/Port.py
@@ -43,7 +43,7 @@ def _get_source_from_virtual_source_port(vsp, traversed=[]):
     Recursively resolve source ports over the virtual connections.
     Keep track of traversed sources to avoid recursive loops.
     """
-    if not vsp.get_parent().is_virtual_source():
+    if not vsp.parent.is_virtual_source():
         return vsp
     if vsp in traversed:
         raise Exception('Loop found when resolving virtual source {}'.format(vsp))
@@ -51,10 +51,10 @@ def _get_source_from_virtual_source_port(vsp, traversed=[]):
         return _get_source_from_virtual_source_port(
             _get_source_from_virtual_sink_port(
                 list(filter(  # Get all virtual sinks with a matching stream id
-                    lambda vs: vs.get_param('stream_id').get_value() == vsp.get_parent().get_param('stream_id').get_value(),
+                    lambda vs: vs.get_param('stream_id').get_value() == vsp.parent.get_param('stream_id').get_value(),
                     list(filter(  # Get all enabled blocks that are also virtual sinks
                         lambda b: b.is_virtual_sink(),
-                        vsp.get_parent().get_parent().get_enabled_blocks(),
+                        vsp.parent.parent.get_enabled_blocks(),
                     )),
                 ))[0].get_sinks()[0]
             ), traversed + [vsp],
@@ -81,7 +81,7 @@ def _get_sink_from_virtual_sink_port(vsp, traversed=[]):
     Recursively resolve sink ports over the virtual connections.
     Keep track of traversed sinks to avoid recursive loops.
     """
-    if not vsp.get_parent().is_virtual_sink():
+    if not vsp.parent.is_virtual_sink():
         return vsp
     if vsp in traversed:
         raise Exception('Loop found when resolving virtual sink {}'.format(vsp))
@@ -89,10 +89,10 @@ def _get_sink_from_virtual_sink_port(vsp, traversed=[]):
         return _get_sink_from_virtual_sink_port(
             _get_sink_from_virtual_source_port(
                 filter(  # Get all virtual source with a matching stream id
-                    lambda vs: vs.get_param('stream_id').get_value() == vsp.get_parent().get_param('stream_id').get_value(),
+                    lambda vs: vs.get_param('stream_id').get_value() == vsp.parent.get_param('stream_id').get_value(),
                     list(filter(  # Get all enabled blocks that are also virtual sinks
                         lambda b: b.is_virtual_source(),
-                        vsp.get_parent().get_parent().get_enabled_blocks(),
+                        vsp.parent.parent.get_enabled_blocks(),
                     )),
                 )[0].get_sources()[0]
             ), traversed + [vsp],
@@ -160,7 +160,7 @@ class Port(Element):
         Element.validate(self)
         if self.get_type() not in self.get_types():
             self.add_error_message('Type "{}" is not a possible type.'.format(self.get_type()))
-        platform = self.get_parent().get_parent().get_parent()
+        platform = self.parent.parent.parent
         if self.get_domain() not in platform.domains:
             self.add_error_message('Domain key "{}" is not registered.'.format(self.get_domain()))
         if not self.get_enabled_connections() and not self.get_optional():
@@ -188,7 +188,7 @@ class Port(Element):
                 self._vlen = ''
 
         Element.rewrite(self)
-        hide = self.get_parent().resolve_dependencies(self._hide).strip().lower()
+        hide = self.parent.resolve_dependencies(self._hide).strip().lower()
         self._hide_evaluated = False if hide in ('false', 'off', '0') else bool(hide)
 
         # Update domain if was deduced from (dynamic) port type
@@ -201,9 +201,9 @@ class Port(Element):
             self._key = '0'  # Is rectified in rewrite()
 
     def resolve_virtual_source(self):
-        if self.get_parent().is_virtual_sink():
+        if self.parent.is_virtual_sink():
             return _get_source_from_virtual_sink_port(self)
-        if self.get_parent().is_virtual_source():
+        if self.parent.is_virtual_source():
             return _get_source_from_virtual_source_port(self)
 
     def resolve_empty_type(self):
@@ -236,9 +236,9 @@ class Port(Element):
         Returns:
             the vector length or 1
         """
-        vlen = self.get_parent().resolve_dependencies(self._vlen)
+        vlen = self.parent.resolve_dependencies(self._vlen)
         try:
-            return int(self.get_parent().get_parent().evaluate(vlen))
+            return int(self.parent.parent.evaluate(vlen))
         except:
             return 1
 
@@ -254,9 +254,9 @@ class Port(Element):
         if self._nports == '':
             return ''
 
-        nports = self.get_parent().resolve_dependencies(self._nports)
+        nports = self.parent.resolve_dependencies(self._nports)
         try:
-            return max(1, int(self.get_parent().get_parent().evaluate(nports)))
+            return max(1, int(self.parent.parent.evaluate(nports)))
         except:
             return 1
 
@@ -325,7 +325,7 @@ class Port(Element):
         n['key'] = '99999' if self._key.isdigit() else n['name']
 
         # Clone
-        port = self.__class__(self.get_parent(), n, self._dir)
+        port = self.__class__(self.parent, n, self._dir)
         self._clones.append(port)
         return port
 
@@ -345,7 +345,7 @@ class Port(Element):
     def get_name(self):
         number = ''
         if self.get_type() == 'bus':
-            busses = [a for a in self.get_parent().get_ports_gui() if a._dir == self._dir]
+            busses = [a for a in self.parent.get_ports_gui() if a._dir == self._dir]
             number = str(busses.index(self)) + '#' + str(len(self.get_associated_ports()))
         return self._name + number
 
@@ -361,7 +361,7 @@ class Port(Element):
         return self._dir == 'source'
 
     def get_type(self):
-        return self.get_parent().resolve_dependencies(self._type)
+        return self.parent_block.resolve_dependencies(self._type)
 
     def get_domain(self):
         return self._domain
@@ -376,7 +376,7 @@ class Port(Element):
         Returns:
             a list of connection objects
         """
-        connections = self.get_parent().get_parent().connections
+        connections = self.parent_flowgraph.connections
         connections = [c for c in connections if c.source_port is self or c.sink_port is self]
         return connections
 
@@ -393,12 +393,13 @@ class Port(Element):
         if not self.get_type() == 'bus':
             return [self]
         else:
+            flowgraph = self.parent_flowgraph
             if self.is_source:
-                get_ports = self.get_parent().get_sources
-                bus_structure = self.get_parent().current_bus_structure['source']
+                get_ports = flowgraph.get_sources
+                bus_structure = flowgraph.current_bus_structure['source']
             else:
-                get_ports = self.get_parent().get_sinks
-                bus_structure = self.get_parent().current_bus_structure['sink']
+                get_ports = flowgraph.get_sinks
+                bus_structure = flowgraph.current_bus_structure['sink']
 
             ports = [i for i in get_ports() if not i.get_type() == 'bus']
             if bus_structure:
diff --git a/grc/core/generator/FlowGraphProxy.py b/grc/core/generator/FlowGraphProxy.py
index c673c5b005..a23c6d84ab 100644
--- a/grc/core/generator/FlowGraphProxy.py
+++ b/grc/core/generator/FlowGraphProxy.py
@@ -117,7 +117,7 @@ class FlowGraphProxy(object):
             # using the block param 'type' instead of the port domain here
             # to emphasize that hier block generation is domain agnostic
             is_message_pad = pad.get_param('type').get_evaluated() == "message"
-            if port.get_parent() == pad:
+            if port.parent == pad:
                 if is_message_pad:
                     key = pad.get_param('label').get_value()
                 else:
diff --git a/grc/core/generator/Generator.py b/grc/core/generator/Generator.py
index 97729b3ada..c3308d6c32 100644
--- a/grc/core/generator/Generator.py
+++ b/grc/core/generator/Generator.py
@@ -175,7 +175,7 @@ class TopBlockGenerator(object):
         for connection in virtual:
             source = connection.source.resolve_virtual_source()
             sink = connection.sink_port
-            resolved = fg.get_parent().Connection(flow_graph=fg, porta=source, portb=sink)
+            resolved = fg.parent.Connection(flow_graph=fg, porta=source, portb=sink)
             connections.append(resolved)
             # Remove the virtual connection
             connections.remove(connection)
@@ -202,7 +202,7 @@ class TopBlockGenerator(object):
                     # Ignore disabled connections
                     continue
                 sink_port = sink.sink_port
-                connection = fg.get_parent().Connection(flow_graph=fg, porta=source_port, portb=sink_port)
+                connection = fg.parent.Connection(flow_graph=fg, porta=source_port, portb=sink_port)
                 connections.append(connection)
                 # Remove this sink connection
                 connections.remove(sink)
@@ -215,7 +215,7 @@ class TopBlockGenerator(object):
             c.source_block.get_id(), c.sink_block.get_id()
         ))
 
-        connection_templates = fg.get_parent().connection_templates
+        connection_templates = fg.parent.connection_templates
         msgs = [c for c in fg.get_enabled_connections() if c.is_msg()]
 
         # List of variable names
@@ -265,9 +265,8 @@ class HierBlockGenerator(TopBlockGenerator):
             file_path: where to write the py file (the xml goes into HIER_BLOCK_LIB_DIR)
         """
         TopBlockGenerator.__init__(self, flow_graph, file_path)
-        platform = flow_graph.get_parent()
 
-        hier_block_lib_dir = platform.config.hier_block_lib_dir
+        hier_block_lib_dir = flow_graph.parent_platform.config.hier_block_lib_dir
         if not os.path.exists(hier_block_lib_dir):
             os.mkdir(hier_block_lib_dir)
 
diff --git a/grc/core/generator/flow_graph.tmpl b/grc/core/generator/flow_graph.tmpl
index 21bcb601a2..c86808455b 100644
--- a/grc/core/generator/flow_graph.tmpl
+++ b/grc/core/generator/flow_graph.tmpl
@@ -241,11 +241,11 @@ gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))])
 ##  However, port names for IO pads should be self.
 ########################################################
 #def make_port_sig($port)
-    #if $port.get_parent().get_key() in ('pad_source', 'pad_sink')
+    #if $port.parent.get_key() in ('pad_source', 'pad_sink')
         #set block = 'self'
         #set key = $flow_graph.get_pad_port_global_key($port)
     #else
-        #set block = 'self.' + $port.get_parent().get_id()
+        #set block = 'self.' + $port.parent.get_id()
         #set key = $port.get_key()
     #end if
     #if not $key.isdigit()
diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py
index 3c6b57b482..8d4dc7841f 100644
--- a/grc/gui/ActionHandler.py
+++ b/grc/gui/ActionHandler.py
@@ -384,12 +384,11 @@ class ActionHandler:
         # Window stuff
         ##################################################
         elif action == Actions.ABOUT_WINDOW_DISPLAY:
-            platform = flow_graph.get_parent()
-            Dialogs.AboutDialog(platform.config)
+            Dialogs.AboutDialog(self.platform.config)
         elif action == Actions.HELP_WINDOW_DISPLAY:
             Dialogs.HelpDialog()
         elif action == Actions.TYPES_WINDOW_DISPLAY:
-            Dialogs.TypesDialog(flow_graph.get_parent())
+            Dialogs.TypesDialog(self.platform)
         elif action == Actions.ERRORS_WINDOW_DISPLAY:
             Dialogs.ErrorsDialog(flow_graph)
         elif action == Actions.TOGGLE_CONSOLE_WINDOW:
diff --git a/grc/gui/Block.py b/grc/gui/Block.py
index a6c31cd473..49bba4f3db 100644
--- a/grc/gui/Block.py
+++ b/grc/gui/Block.py
@@ -181,7 +181,7 @@ class Block(Element, _Block):
 
         # Show the flow graph complexity on the top block if enabled
         if Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY.get_active() and self.get_key() == "options":
-            complexity = calculate_flowgraph_complexity(self.get_parent())
+            complexity = calculate_flowgraph_complexity(self.parent)
             markups.append(
                 '<span foreground="#444" size="medium" font_desc="{font}">'
                 '<b>Complexity: {num}bal</b></span>'.format(num=num_to_str(complexity), font=BLOCK_FONT)
@@ -201,7 +201,7 @@ class Block(Element, _Block):
         width, height = layout.get_pixel_size()
         if width and height:
             padding = BLOCK_LABEL_PADDING
-            pixmap = self.get_parent().new_pixmap(width + 2 * padding,
+            pixmap = self.parent.new_pixmap(width + 2 * padding,
                                                   height + 2 * padding)
             gc = pixmap.new_gc()
             gc.set_foreground(Colors.COMMENT_BACKGROUND_COLOR)
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index 37a233f825..8e4a26ea7e 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -88,12 +88,12 @@ class FlowGraph(Element, _Flowgraph):
         return block_id
 
     def install_external_editor(self, param):
-        target = (param.get_parent().get_id(), param.get_key())
+        target = (param.parent_block.get_id(), param.get_key())
 
         if target in self._external_updaters:
             editor = self._external_updaters[target]
         else:
-            config = self.get_parent().config
+            config = self.parent_platform.config
             editor = (find_executable(config.editor) or
                       Dialogs.ChooseEditorDialog(config))
             if not editor:
@@ -112,7 +112,7 @@ class FlowGraph(Element, _Flowgraph):
             # Problem launching the editor. Need to select a new editor.
             Messages.send('>>> Error opening an external editor. Please select a different editor.\n')
             # Reset the editor to force the user to select a new one.
-            self.get_parent().config.editor = ''
+            self.parent_platform.config.editor = ''
 
     def handle_external_editor_change(self, new_value, target):
         try:
@@ -452,9 +452,9 @@ class FlowGraph(Element, _Flowgraph):
         for selected in selected_elements:
             if selected in elements: continue
             selected_elements.remove(selected)
-        if self._old_selected_port and self._old_selected_port.get_parent() not in elements:
+        if self._old_selected_port and self._old_selected_port.parent not in elements:
             self._old_selected_port = None
-        if self._new_selected_port and self._new_selected_port.get_parent() not in elements:
+        if self._new_selected_port and self._new_selected_port.parent not in elements:
             self._new_selected_port = None
         #update highlighting
         for element in elements:
@@ -532,7 +532,7 @@ class FlowGraph(Element, _Flowgraph):
             #update the selected port information
             if selected_element.is_port:
                 if not coor_m: selected_port = selected_element
-                selected_element = selected_element.get_parent()
+                selected_element = selected_element.parent_block
             selected.add(selected_element)
             #place at the end of the list
             self.get_elements().remove(element)
diff --git a/grc/gui/NotebookPage.py b/grc/gui/NotebookPage.py
index 757dcbc0f8..4745035aff 100644
--- a/grc/gui/NotebookPage.py
+++ b/grc/gui/NotebookPage.py
@@ -49,7 +49,7 @@ class NotebookPage(Gtk.HBox):
         self.saved = True
 
         # import the file
-        initial_state = flow_graph.get_parent().parse_flow_graph(file_path)
+        initial_state = flow_graph.parent_platform.parse_flow_graph(file_path)
         flow_graph.import_data(initial_state)
         self.state_cache = StateCache(initial_state)
 
@@ -97,7 +97,7 @@ class NotebookPage(Gtk.HBox):
         Returns:
             generator
         """
-        platform = self.flow_graph.get_parent()
+        platform = self.flow_graph.parent_platform
         return platform.Generator(self.flow_graph, self.file_path)
 
     def _handle_button(self, button):
diff --git a/grc/gui/Param.py b/grc/gui/Param.py
index 137c5e057b..a630f5faa3 100644
--- a/grc/gui/Param.py
+++ b/grc/gui/Param.py
@@ -62,7 +62,7 @@ class Param(Element, _Param):
         return input_widget_cls(self, *args, **kwargs)
 
     def format_label_markup(self, have_pending_changes=False):
-        block = self.get_parent()
+        block = self.parent
         # fixme: using non-public attribute here
         has_callback = \
             hasattr(block, 'get_callbacks') and \
diff --git a/grc/gui/ParamWidgets.py b/grc/gui/ParamWidgets.py
index e0979e19f3..fbbfa93d1d 100644
--- a/grc/gui/ParamWidgets.py
+++ b/grc/gui/ParamWidgets.py
@@ -173,8 +173,7 @@ class PythonEditorParam(InputParam):
         self.pack_start(button, True)
 
     def open_editor(self, widget=None):
-        flowgraph = self.param.get_parent().get_parent()
-        flowgraph.install_external_editor(self.param)
+        self.param.parent_flowgraph.install_external_editor(self.param)
 
     def get_text(self):
         pass  # we never update the value from here
@@ -274,9 +273,8 @@ class FileParam(EntryParam):
         if self.param.get_key() == 'qt_qss_theme':
             dirname = os.path.dirname(dirname)  # trim filename
             if not os.path.exists(dirname):
-               platform = self.param.get_parent().get_parent().get_parent()
-               dirname = os.path.join(platform.config.install_prefix,
-                                      '/share/gnuradio/themes')
+               config = self.param.parent_platform.config
+               dirname = os.path.join(config.install_prefix, '/share/gnuradio/themes')
         if not os.path.exists(dirname):
             dirname = os.getcwd()  # fix bad paths
 
diff --git a/grc/gui/Port.py b/grc/gui/Port.py
index 0fa35573c1..8c4500f960 100644
--- a/grc/gui/Port.py
+++ b/grc/gui/Port.py
@@ -67,8 +67,8 @@ class Port(_Port, Element):
         #get current rotation
         rotation = self.get_rotation()
         #get all sibling ports
-        ports = self.get_parent().get_sources_gui() \
-            if self.is_source else self.get_parent().get_sinks_gui()
+        ports = self.parent.get_sources_gui() \
+            if self.is_source else self.parent.get_sinks_gui()
         ports = [p for p in ports if not p.get_hide()]
         #get the max width
         self.W = max([port.W for port in ports] + [PORT_MIN_WIDTH])
@@ -85,10 +85,10 @@ class Port(_Port, Element):
             index = len(ports)-index-1
 
         port_separation = PORT_SEPARATION \
-            if not self.get_parent().has_busses[self.is_source] \
+            if not self.parent.has_busses[self.is_source] \
             else max([port.H for port in ports]) + PORT_SPACING
 
-        offset = (self.get_parent().H - (len(ports)-1)*port_separation - self.H)/2
+        offset = (self.parent.H - (len(ports)-1)*port_separation - self.H)/2
         #create areas and connector coordinates
         if (self.is_sink and rotation == 0) or (self.is_source and rotation == 180):
             x = -W
@@ -96,7 +96,7 @@ class Port(_Port, Element):
             self.add_area((x, y), (W, self.H))
             self._connector_coordinate = (x-1, y+self.H/2)
         elif (self.is_source and rotation == 0) or (self.is_sink and rotation == 180):
-            x = self.get_parent().W
+            x = self.parent.W
             y = port_separation*index+offset
             self.add_area((x, y), (W, self.H))
             self._connector_coordinate = (x+1+W, y+self.H/2)
@@ -106,7 +106,7 @@ class Port(_Port, Element):
             self.add_area((x, y), (self.H, W))
             self._connector_coordinate = (x+self.H/2, y-1)
         elif (self.is_sink and rotation == 90) or (self.is_source and rotation == 270):
-            y = self.get_parent().W
+            y = self.parent.W
             x = port_separation*index+offset
             self.add_area((x, y), (self.H, W))
             self._connector_coordinate = (x+self.H/2, y+1+W)
@@ -125,7 +125,7 @@ class Port(_Port, Element):
         """
         border_color = (
             Colors.HIGHLIGHT_COLOR if self.is_highlighted() else
-            Colors.MISSING_BLOCK_BORDER_COLOR if self.get_parent().is_dummy_block else
+            Colors.MISSING_BLOCK_BORDER_COLOR if self.parent.is_dummy_block else
             Colors.BORDER_COLOR
         )
         Element.draw(self, widget, cr, border_color, self._bg_color)
@@ -186,7 +186,7 @@ class Port(_Port, Element):
         Returns:
             the parent's rotation
         """
-        return self.get_parent().get_rotation()
+        return self.parent.get_rotation()
 
     def move(self, delta_coor):
         """
@@ -195,7 +195,7 @@ class Port(_Port, Element):
         Args:
             delta_corr: the (delta_x, delta_y) tuple
         """
-        self.get_parent().move(delta_coor)
+        self.parent.move(delta_coor)
 
     def rotate(self, direction):
         """
@@ -204,7 +204,7 @@ class Port(_Port, Element):
         Args:
             direction: degrees to rotate
         """
-        self.get_parent().rotate(direction)
+        self.parent.rotate(direction)
 
     def get_coordinate(self):
         """
@@ -213,7 +213,7 @@ class Port(_Port, Element):
         Returns:
             the parents coordinate
         """
-        return self.get_parent().get_coordinate()
+        return self.parent.get_coordinate()
 
     def set_highlighted(self, highlight):
         """
@@ -222,7 +222,7 @@ class Port(_Port, Element):
         Args:
             highlight: true to enable highlighting
         """
-        self.get_parent().set_highlighted(highlight)
+        self.parent.set_highlighted(highlight)
 
     def is_highlighted(self):
         """
@@ -231,7 +231,7 @@ class Port(_Port, Element):
         Returns:
             the parent's highlighting status
         """
-        return self.get_parent().is_highlighted()
+        return self.parent.is_highlighted()
 
     def _label_hidden(self):
         """
-- 
cgit v1.2.3


From 7ac7cf6246e4d984d36c64df10ba4d2b2f6b2204 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Thu, 9 Jun 2016 14:45:24 +0200
Subject: grc: gtk3: fix paste and domain color settings

---
 grc/core/Connection.py |  6 ++---
 grc/core/Element.py    | 68 +++++++++++++++++++++++++-------------------------
 grc/core/FlowGraph.py  |  2 +-
 grc/core/Param.py      |  2 +-
 grc/core/Platform.py   |  3 +--
 grc/core/Port.py       |  2 +-
 grc/gui/Colors.py      |  2 ++
 grc/gui/Connection.py  | 11 +++++---
 grc/gui/FlowGraph.py   |  6 ++---
 9 files changed, 52 insertions(+), 50 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/core/Connection.py b/grc/core/Connection.py
index 2309d159c8..52cba4257c 100644
--- a/grc/core/Connection.py
+++ b/grc/core/Connection.py
@@ -24,7 +24,7 @@ import collections
 from six.moves import range
 
 from . import Constants
-from .Element import Element, lazyproperty
+from .Element import Element, lazy_property
 
 
 class Connection(Element):
@@ -131,11 +131,11 @@ class Connection(Element):
         """
         return self.source_block.get_enabled() and self.sink_block.get_enabled()
 
-    @lazyproperty
+    @lazy_property
     def source_block(self):
         return self.source_port.parent_block
 
-    @lazyproperty
+    @lazy_property
     def sink_block(self):
         return self.sink_port.parent_block
 
diff --git a/grc/core/Element.py b/grc/core/Element.py
index a046d6beb4..f07bb113e1 100644
--- a/grc/core/Element.py
+++ b/grc/core/Element.py
@@ -1,36 +1,37 @@
-"""
-Copyright 2008, 2009, 2015 Free Software Foundation, Inc.
-This file is part of GNU Radio
-
-GNU Radio Companion is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-GNU Radio Companion is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
-"""
+# Copyright 2008, 2009, 2015, 2016 Free Software Foundation, Inc.
+# This file is part of GNU Radio
+#
+# GNU Radio Companion is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# GNU Radio Companion is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
 import weakref
+import functools
 
 
-class lazyproperty(object):
+class lazy_property(object):
+
     def __init__(self, func):
         self.func = func
+        functools.update_wrapper(self, func)
 
     def __get__(self, instance, cls):
         if instance is None:
             return self
-        else:
-            value = self.func(instance)
-            setattr(instance, self.func.__name__, value)
-            return value
+        value = self.func(instance)
+        weak_value = weakref.proxy(value) if not weakref.ProxyType else value
+        setattr(instance, self.func.__name__, weak_value)
+        return weak_value
 
 
 class Element(object):
@@ -108,35 +109,34 @@ class Element(object):
     def parent(self):
         return self._parent()
 
-    def get_parent_of_type(self, cls):
+    def get_parent_by_type(self, cls):
         parent = self.parent
         if parent is None:
             return None
         elif isinstance(parent, cls):
             return parent
         else:
-            return parent.get_parent_of_type(cls)
+            return parent.get_parent_by_type(cls)
 
-    @lazyproperty
+    @lazy_property
     def parent_platform(self):
         from .Platform import Platform
-        return self.get_parent_of_type(Platform)
+        return self.get_parent_by_type(Platform)
 
-    @lazyproperty
+    @lazy_property
     def parent_flowgraph(self):
         from .FlowGraph import FlowGraph
-        return self.get_parent_of_type(FlowGraph)
+        return self.get_parent_by_type(FlowGraph)
 
-    @lazyproperty
+    @lazy_property
     def parent_block(self):
         from .Block import Block
-        return self.get_parent_of_type(Block)
+        return self.get_parent_by_type(Block)
 
-    def reset_parents(self):
+    def reset_parents_by_type(self):
         """Reset all lazy properties"""
-        # todo: use case?
         for name, obj in vars(Element):
-            if isinstance(obj, lazyproperty):
+            if isinstance(obj, lazy_property):
                 delattr(self, name)
 
     def get_children(self):
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index 16842595c8..67e86f3e6e 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -53,7 +53,7 @@ class FlowGraph(Element):
         Returns:
             the flow graph object
         """
-        Element.__init__(self, platform)
+        Element.__init__(self, parent=platform)
         self._timestamp = time.ctime()
         self._options_block = self.parent_platform.get_new_block(self, 'options')
 
diff --git a/grc/core/Param.py b/grc/core/Param.py
index b21cbcddf1..35bb176744 100644
--- a/grc/core/Param.py
+++ b/grc/core/Param.py
@@ -172,7 +172,7 @@ class Param(Element):
         if self._tab_label not in block.get_param_tab_labels():
             block.get_param_tab_labels().append(self._tab_label)
         # Build the param
-        Element.__init__(self, block)
+        Element.__init__(self, parent=block)
         # Create the Option objects from the n data
         self._options = list()
         self._evaluated = None
diff --git a/grc/core/Platform.py b/grc/core/Platform.py
index 2c0b83dca4..069870d389 100644
--- a/grc/core/Platform.py
+++ b/grc/core/Platform.py
@@ -53,10 +53,9 @@ class Platform(Element):
 
     def __init__(self, *args, **kwargs):
         """ Make a platform for GNU Radio """
-        Element.__init__(self)
+        Element.__init__(self, parent=None)
 
         self.config = self.Config(*args, **kwargs)
-
         self.block_docstrings = {}
         self.block_docstrings_loaded_callback = lambda: None  # dummy to be replaced by BlockTreeWindow
 
diff --git a/grc/core/Port.py b/grc/core/Port.py
index 99b5a25508..9a33c5c506 100644
--- a/grc/core/Port.py
+++ b/grc/core/Port.py
@@ -128,7 +128,7 @@ class Port(Element):
         n.setdefault('key', str(next(block.port_counters[dir == 'source'])))
 
         # Build the port
-        Element.__init__(self, block)
+        Element.__init__(self, parent=block)
         # Grab the data
         self._name = n['name']
         self._key = n['key']
diff --git a/grc/gui/Colors.py b/grc/gui/Colors.py
index b2ed55b711..6ee31e5e18 100644
--- a/grc/gui/Colors.py
+++ b/grc/gui/Colors.py
@@ -47,6 +47,8 @@ CONNECTION_ENABLED_COLOR = get_color('#000000')
 CONNECTION_DISABLED_COLOR = get_color('#BBBBBB')
 CONNECTION_ERROR_COLOR = get_color('#FF0000')
 
+DEFAULT_DOMAIN_COLOR = get_color('#777777')
+
 #################################################################################
 # param box colors
 #################################################################################
diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py
index 3af6badaa0..d893060aa6 100644
--- a/grc/gui/Connection.py
+++ b/grc/gui/Connection.py
@@ -26,7 +26,6 @@ from . import Utils
 from .Constants import CONNECTOR_ARROW_BASE, CONNECTOR_ARROW_HEIGHT
 from .Element import Element
 
-from ..core.Constants import GR_MESSAGE_DOMAIN
 from ..core.Connection import Connection as _Connection
 
 
@@ -93,9 +92,13 @@ class Connection(Element, _Connection):
         # self.line_attributes[1] = Gdk.LINE_DOUBLE_DASH \
         #     if not source_domain == sink_domain == GR_MESSAGE_DOMAIN \
         #     else Gdk.LINE_ON_OFF_DASH
-        get_domain_color = lambda d: Colors.get_color((
-            self.get_parent().get_parent().domains.get(d, {})
-        ).get('color') or Colors.DEFAULT_DOMAIN_COLOR_CODE)
+
+        def get_domain_color(domain_name):
+            domain = self.parent_platform.domains.get(domain_name, {})
+            color_spec = domain.get('color')
+            return Colors.get_color(color_spec) if color_spec else \
+                Colors.DEFAULT_DOMAIN_COLOR
+
         self._color = get_domain_color(source_domain)
         self._bg_color = get_domain_color(sink_domain)
         self._arrow_color = self._bg_color if self.is_valid() else Colors.CONNECTION_ERROR_COLOR
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index 8e4a26ea7e..d7745a529d 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -131,8 +131,6 @@ class FlowGraph(Element, _Flowgraph):
     ###########################################################################
     def get_drawing_area(self): return self.drawing_area
     def queue_draw(self): self.get_drawing_area().queue_draw()
-    def get_size(self): return self.get_drawing_area().get_size_request()
-    def set_size(self, *args): self.get_drawing_area().set_size_request(*args)
     def get_scroll_pane(self): return self.drawing_area.get_parent().get_parent()
     def get_ctrl_mask(self): return self.drawing_area.ctrl_mask
     def get_mod1_mask(self): return self.drawing_area.mod1_mask
@@ -207,8 +205,8 @@ class FlowGraph(Element, _Flowgraph):
         #recalc the position
         h_adj = self.get_scroll_pane().get_hadjustment()
         v_adj = self.get_scroll_pane().get_vadjustment()
-        x_off = h_adj.get_value() - x_min + h_adj.page_size/4
-        y_off = v_adj.get_value() - y_min + v_adj.page_size/4
+        x_off = h_adj.get_value() - x_min + h_adj.get_page_size() / 4
+        y_off = v_adj.get_value() - y_min + v_adj.get_page_size() / 4
         if len(self.get_elements()) <= 1:
             x_off, y_off = 0, 0
         #create blocks
-- 
cgit v1.2.3


From 03c3d3f030b9dc20a005a00668ca6aa69cb75de0 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Wed, 15 Jun 2016 20:49:34 -0700
Subject: grc-refactor: Block: remove key getters

---
 grc/core/Block.py                    | 49 +++++++++++++++++-------------------
 grc/core/Connection.py               |  4 +--
 grc/core/FlowGraph.py                | 24 +++++++++---------
 grc/core/Param.py                    | 45 +++++++++++++--------------------
 grc/core/Platform.py                 |  4 +--
 grc/core/Port.py                     | 23 ++++++++---------
 grc/core/generator/FlowGraphProxy.py |  6 ++---
 grc/core/generator/Generator.py      |  4 +--
 grc/core/generator/flow_graph.tmpl   |  4 +--
 grc/core/utils/complexity.py         |  2 +-
 grc/gui/ActionHandler.py             |  4 +--
 grc/gui/Block.py                     |  4 +--
 grc/gui/BlockTreeWindow.py           |  4 +--
 grc/gui/FlowGraph.py                 |  2 +-
 grc/gui/MainWindow.py                |  2 +-
 grc/gui/Param.py                     |  4 +--
 grc/gui/ParamWidgets.py              |  2 +-
 grc/gui/PropsDialog.py               |  2 +-
 grc/gui/VariableEditor.py            |  8 +++---
 19 files changed, 90 insertions(+), 107 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/core/Block.py b/grc/core/Block.py
index 503f1e66d4..be685ad65b 100644
--- a/grc/core/Block.py
+++ b/grc/core/Block.py
@@ -38,7 +38,7 @@ from . Element import Element
 
 
 def _get_keys(lst):
-    return [elem.get_key() for elem in lst]
+    return [elem.key for elem in lst]
 
 
 def _get_elem(lst, key):
@@ -69,7 +69,7 @@ class Block(Element):
         Element.__init__(self, parent=flow_graph)
 
         self.name = n['name']
-        self._key = n['key']
+        self.key = n['key']
         self.category = [cat.strip() for cat in n.get('category', '').split('/') if cat.strip()]
         self._flags = n.get('flags', '')
         self._doc = n.get('doc', '').strip('\n').replace('\\\n', '')
@@ -124,16 +124,16 @@ class Block(Element):
         # indistinguishable from normal GR blocks. Make explicit
         # checks for them here since they have no work function or
         # buffers to manage.
-        self.is_virtual_or_pad = is_virtual_or_pad = self._key in (
+        self.is_virtual_or_pad = is_virtual_or_pad = self.key in (
             "virtual_source", "virtual_sink", "pad_source", "pad_sink")
-        self.is_variable = is_variable = self._key.startswith('variable')
-        self.is_import = (self._key == 'import')
+        self.is_variable = is_variable = self.key.startswith('variable')
+        self.is_import = (self.key == 'import')
 
         # Disable blocks that are virtual/pads or variables
         if self.is_virtual_or_pad or self.is_variable:
             self._flags += BLOCK_FLAG_DISABLE_BYPASS
 
-        if not (is_virtual_or_pad or is_variable or self._key == 'options'):
+        if not (is_virtual_or_pad or is_variable or self.key == 'options'):
             self._add_param(key='alias', name='Block Alias', type='string',
                             hide='part', tab=ADVANCED_PARAM_TAB)
 
@@ -147,10 +147,10 @@ class Block(Element):
             self._add_param(key='maxoutbuf', name='Max Output Buffer', type='int',
                             hide='part', value='0', tab=ADVANCED_PARAM_TAB)
 
-        param_keys = set(param.get_key() for param in self._params)
+        param_keys = set(param.key for param in self._params)
         for param_n in params_n:
             param = self.parent_platform.Param(block=self, n=param_n)
-            key = param.get_key()
+            key = param.key
             if key in param_keys:
                 raise Exception('Key "{}" already exists in params'.format(key))
             param_keys.add(key)
@@ -165,7 +165,7 @@ class Block(Element):
         port_keys = set()
         for port_n in ports_n:
             port = port_cls(block=self, n=port_n, dir=direction)
-            key = port.get_key()
+            key = port.key
             if key in port_keys:
                 raise Exception('Key "{}" already exists in {}'.format(key, direction))
             port_keys.add(key)
@@ -227,7 +227,7 @@ class Block(Element):
         """
         Element.rewrite(self)
         # Check and run any custom rewrite function for this block
-        getattr(self, 'rewrite_' + self._key, lambda: None)()
+        getattr(self, 'rewrite_' + self.key, lambda: None)()
 
         # Adjust nports, disconnect hidden ports
         for ports in (self.get_sources(), self.get_sinks()):
@@ -254,9 +254,9 @@ class Block(Element):
             self.back_ofthe_bus(ports)
             # Renumber non-message/message ports
             domain_specific_port_index = collections.defaultdict(int)
-            for port in [p for p in ports if p.get_key().isdigit()]:
+            for port in [p for p in ports if p.key.isdigit()]:
                 domain = port.get_domain()
-                port._key = str(domain_specific_port_index[domain])
+                port.key = str(domain_specific_port_index[domain])
                 domain_specific_port_index[domain] += 1
 
     def get_imports(self, raw=False):
@@ -300,10 +300,10 @@ class Block(Element):
         return [make_callback(c) for c in self._callbacks]
 
     def is_virtual_sink(self):
-        return self.get_key() == 'virtual_sink'
+        return self.key == 'virtual_sink'
 
     def is_virtual_source(self):
-        return self.get_key() == 'virtual_source'
+        return self.key == 'virtual_source'
 
     ###########################################################################
     # Custom rewrite functions
@@ -349,7 +349,7 @@ class Block(Element):
         params = {}
         for param in list(self._params):
             if hasattr(param, '__epy_param__'):
-                params[param.get_key()] = param
+                params[param.key] = param
                 self._params.remove(param)
 
         for key, value in blk_io.params:
@@ -372,7 +372,7 @@ class Block(Element):
                 reuse_port = (
                     port_current is not None and
                     port_current.get_type() == port_type and
-                    (key.isdigit() or port_current.get_key() == key)
+                    (key.isdigit() or port_current.key == key)
                 )
                 if reuse_port:
                     ports_to_remove.remove(port_current)
@@ -398,7 +398,7 @@ class Block(Element):
 
     @property
     def documentation(self):
-        documentation = self.parent_platform.block_docstrings.get(self.get_key(), {})
+        documentation = self.parent_platform.block_docstrings.get(self.key, {})
         from_xml = self._doc.strip()
         if from_xml:
             documentation[''] = from_xml
@@ -492,14 +492,11 @@ class Block(Element):
         return True
 
     def __str__(self):
-        return 'Block - {} - {}({})'.format(self.get_id(), self.name, self.get_key())
+        return 'Block - {} - {}({})'.format(self.get_id(), self.name, self.key)
 
     def get_id(self):
         return self.get_param('id').get_value()
 
-    def get_key(self):
-        return self._key
-
     def get_ports(self):
         return self.get_sources() + self.get_sinks()
 
@@ -597,7 +594,7 @@ class Block(Element):
         tmpl = str(tmpl)
         if '$' not in tmpl:
             return tmpl
-        n = dict((param.get_key(), param.template_arg)
+        n = dict((param.key, param.template_arg)
                  for param in self.get_params())  # TODO: cache that
         try:
             return str(Template(tmpl, n))
@@ -622,7 +619,7 @@ class Block(Element):
         for param in [p for p in self.get_params() if p.is_enum()]:
             children = self.get_ports() + self.get_params()
             # Priority to the type controller
-            if param.get_key() in ' '.join([p._type for p in children]): type_param = param
+            if param.key in ' '.join([p._type for p in children]): type_param = param
             # Use param if type param is unset
             if not type_param:
                 type_param = param
@@ -653,7 +650,7 @@ class Block(Element):
         nports_str = ' '.join([port._nports for port in self.get_ports()])
         # Modify all params whose keys appear in the nports string
         for param in self.get_params():
-            if param.is_enum() or param.get_key() not in nports_str:
+            if param.is_enum() or param.key not in nports_str:
                 continue
             # Try to increment the port controller by direction
             try:
@@ -677,7 +674,7 @@ class Block(Element):
             a nested data odict
         """
         n = collections.OrderedDict()
-        n['key'] = self.get_key()
+        n['key'] = self.key
         n['param'] = [p.export_data() for p in sorted(self.get_params(), key=str)]
         if 'bus' in [a.get_type() for a in self.get_sinks()]:
             n['bus_sink'] = str(1)
@@ -698,7 +695,7 @@ class Block(Element):
             n: the nested data odict
         """
         params_n = n.get('param', [])
-        params = dict((param.get_key(), param) for param in self._params)
+        params = dict((param.key, param) for param in self._params)
 
         def get_hash():
             return hash(tuple(map(hash, self._params)))
diff --git a/grc/core/Connection.py b/grc/core/Connection.py
index 52cba4257c..44e3b6b5d8 100644
--- a/grc/core/Connection.py
+++ b/grc/core/Connection.py
@@ -152,8 +152,8 @@ class Connection(Element):
         n = collections.OrderedDict()
         n['source_block_id'] = self.source_block.get_id()
         n['sink_block_id'] = self.sink_block.get_id()
-        n['source_key'] = self.source_port.get_key()
-        n['sink_key'] = self.sink_port.get_key()
+        n['source_key'] = self.source_port.key
+        n['sink_key'] = self.sink_port.key
         return n
 
     def _make_bus_connect(self):
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index 67e86f3e6e..03a08baacc 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -96,24 +96,24 @@ class FlowGraph(Element):
         Returns:
             a list of parameterized variables
         """
-        parameters = [b for b in self.iter_enabled_blocks() if _parameter_matcher.match(b.get_key())]
+        parameters = [b for b in self.iter_enabled_blocks() if _parameter_matcher.match(b.key)]
         return parameters
 
     def get_monitors(self):
         """
         Get a list of all ControlPort monitors
         """
-        monitors = [b for b in self.iter_enabled_blocks() if _monitors_searcher.search(b.get_key())]
+        monitors = [b for b in self.iter_enabled_blocks() if _monitors_searcher.search(b.key)]
         return monitors
 
     def get_python_modules(self):
         """Iterate over custom code block ID and Source"""
         for block in self.iter_enabled_blocks():
-            if block.get_key() == 'epy_module':
+            if block.key == 'epy_module':
                 yield block.get_id(), block.get_param('source_code').get_value()
 
     def get_bussink(self):
-        bussink = [b for b in self.get_enabled_blocks() if _bussink_searcher.search(b.get_key())]
+        bussink = [b for b in self.get_enabled_blocks() if _bussink_searcher.search(b.key)]
 
         for i in bussink:
             for j in i.get_params():
@@ -122,7 +122,7 @@ class FlowGraph(Element):
         return False
 
     def get_bussrc(self):
-        bussrc = [b for b in self.get_enabled_blocks() if _bussrc_searcher.search(b.get_key())]
+        bussrc = [b for b in self.get_enabled_blocks() if _bussrc_searcher.search(b.key)]
 
         for i in bussrc:
             for j in i.get_params():
@@ -131,11 +131,11 @@ class FlowGraph(Element):
         return False
 
     def get_bus_structure_sink(self):
-        bussink = [b for b in self.get_enabled_blocks() if _bus_struct_sink_searcher.search(b.get_key())]
+        bussink = [b for b in self.get_enabled_blocks() if _bus_struct_sink_searcher.search(b.key)]
         return bussink
 
     def get_bus_structure_src(self):
-        bussrc = [b for b in self.get_enabled_blocks() if _bus_struct_src_searcher.search(b.get_key())]
+        bussrc = [b for b in self.get_enabled_blocks() if _bus_struct_src_searcher.search(b.key)]
         return bussrc
 
     def iter_enabled_blocks(self):
@@ -346,8 +346,8 @@ class FlowGraph(Element):
         """
         # sort blocks and connections for nicer diffs
         blocks = sorted(self.blocks, key=lambda b: (
-            b.get_key() != 'options',  # options to the front
-            not b.get_key().startswith('variable'),  # then vars
+            b.key != 'options',  # options to the front
+            not b.key.startswith('variable'),  # then vars
             str(b)
         ))
         connections = sorted(self.connections, key=str)
@@ -416,7 +416,7 @@ class FlowGraph(Element):
         def verify_and_get_port(key, block, dir):
             ports = block.get_sinks() if dir == 'sink' else block.get_sources()
             for port in ports:
-                if key == port.get_key():
+                if key == port.key:
                     break
                 if not key.isdigit() and port.get_type() == '' and key == port.get_name():
                     break
@@ -527,7 +527,7 @@ def _update_old_message_port_keys(source_key, sink_key, source_block, sink_block
         source_port = source_block.get_sources()[int(source_key)]
         sink_port = sink_block.get_sinks()[int(sink_key)]
         if source_port.get_type() == "message" and sink_port.get_type() == "message":
-            source_key, sink_key = source_port.get_key(), sink_port.get_key()
+            source_key, sink_key = source_port.key, sink_port.key
     except (ValueError, IndexError):
         pass
     return source_key, sink_key  # do nothing
@@ -555,7 +555,7 @@ def _initialize_dummy_block(block, block_n):
     Modify block object to get the behaviour for a missing block
     """
 
-    block._key = block_n.get('key')
+    block.key = block_n.get('key')
     block.is_dummy_block = lambda: True
     block.is_valid = lambda: False
     block.get_enabled = lambda: False
diff --git a/grc/core/Param.py b/grc/core/Param.py
index 35bb176744..23e9daa656 100644
--- a/grc/core/Param.py
+++ b/grc/core/Param.py
@@ -41,17 +41,6 @@ _check_id_matcher = re.compile('^[a-z|A-Z]\w*$')
 _show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$')
 
 
-def _get_keys(lst):
-    return [elem.get_key() for elem in lst]
-
-
-def _get_elem(lst, key):
-    try:
-        return lst[_get_keys(lst).index(key)]
-    except ValueError:
-        raise ValueError('Key "{}" not found in {}.'.format(key, _get_keys(lst)))
-
-
 def num_to_str(num):
     """ Display logic for numbers """
     def eng_notation(value, fmt='g'):
@@ -80,7 +69,7 @@ class Option(Element):
     def __init__(self, param, n):
         Element.__init__(self, param)
         self._name = n.get('name')
-        self._key = n.get('key')
+        self.key = n.get('key')
         self._opts = dict()
         opts = n.get('opt', [])
         # Test against opts when non enum
@@ -100,13 +89,13 @@ class Option(Element):
             self._opts[key] = value
 
     def __str__(self):
-        return 'Option {}({})'.format(self.get_name(), self.get_key())
+        return 'Option {}({})'.format(self.get_name(), self.key)
 
     def get_name(self):
         return self._name
 
     def get_key(self):
-        return self._key
+        return self.key
 
     ##############################################
     # Access Opts
@@ -164,7 +153,7 @@ class Param(Element):
         self._n = n
         # Parse the data
         self._name = n['name']
-        self._key = n['key']
+        self.key = n['key']
         value = n.get('value', '')
         self._type = n.get('type', 'raw')
         self._hide = n.get('hide', '')
@@ -178,7 +167,7 @@ class Param(Element):
         self._evaluated = None
         for o_n in n.get('option', []):
             option = Option(param=self, n=o_n)
-            key = option.get_key()
+            key = option.key
             # Test against repeated keys
             if key in self.get_option_keys():
                 raise Exception('Key "{}" already exists in options'.format(key))
@@ -291,7 +280,7 @@ class Param(Element):
         return self.get_value()
 
     def __str__(self):
-        return 'Param - {}({})'.format(self.get_name(), self.get_key())
+        return 'Param - {}({})'.format(self.get_name(), self.key)
 
     def get_hide(self):
         """
@@ -308,20 +297,20 @@ class Param(Element):
         if hide:
             return hide
         # Hide ID in non variable blocks
-        if self.get_key() == 'id' and not _show_id_matcher.match(self.parent.get_key()):
+        if self.key == 'id' and not _show_id_matcher.match(self.parent.key):
             return 'part'
         # Hide port controllers for type and nports
-        if self.get_key() in ' '.join([' '.join([p._type, p._nports]) for p in self.parent.get_ports()]):
+        if self.key in ' '.join([' '.join([p._type, p._nports]) for p in self.parent.get_ports()]):
             return 'part'
         # Hide port controllers for vlen, when == 1
-        if self.get_key() in ' '.join(p._vlen for p in self.parent.get_ports()):
+        if self.key in ' '.join(p._vlen for p in self.parent.get_ports()):
             try:
                 if int(self.get_evaluated()) == 1:
                     return 'part'
             except:
                 pass
         # Hide empty grid positions
-        if self.get_key() in ('grid_pos', 'notebook') and not self.get_value():
+        if self.key in ('grid_pos', 'notebook') and not self.get_value():
             return 'part'
         return hide
 
@@ -558,7 +547,7 @@ class Param(Element):
                 return ''
 
             # Get a list of all notebooks
-            notebook_blocks = [b for b in self.parent_flowgraph.get_enabled_blocks() if b.get_key() == 'notebook']
+            notebook_blocks = [b for b in self.parent_flowgraph.get_enabled_blocks() if b.key == 'notebook']
             # Check for notebook param syntax
             try:
                 notebook_id, page_index = map(str.strip, v.split(','))
@@ -666,17 +655,17 @@ class Param(Element):
     def get_name(self):
         return self.parent.resolve_dependencies(self._name).strip()
 
-    def get_key(self):
-        return self._key
-
     ##############################################
     # Access Options
     ##############################################
     def get_option_keys(self):
-        return _get_keys(self.get_options())
+        return [elem.key for elem in self._options]
 
     def get_option(self, key):
-        return _get_elem(self.get_options(), key)
+        for option in self._options:
+            if option.key == key:
+                return option
+        return ValueError('Key "{}" not found in {}.'.format(key, self.get_option_keys()))
 
     def get_options(self):
         return self._options
@@ -704,6 +693,6 @@ class Param(Element):
             a nested data odict
         """
         n = collections.OrderedDict()
-        n['key'] = self.get_key()
+        n['key'] = self.key
         n['value'] = self.get_value()
         return n
diff --git a/grc/core/Platform.py b/grc/core/Platform.py
index 844693d14b..10e75d797d 100644
--- a/grc/core/Platform.py
+++ b/grc/core/Platform.py
@@ -199,7 +199,7 @@ class Platform(Element):
         n['block_wrapper_path'] = xml_file  # inject block wrapper path
         # Get block instance and add it to the list of blocks
         block = self.Block(self._flow_graph, n)
-        key = block.get_key()
+        key = block.key
         if key in self.blocks:
             print('Warning: Block with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file), file=sys.stderr)
         else:  # Store the block
@@ -207,7 +207,7 @@ class Platform(Element):
             self._blocks_n[key] = n
 
         self._docstring_extractor.query(
-            block.get_key(),
+            block.key,
             block.get_imports(raw=True),
             block.get_make(raw=True)
         )
diff --git a/grc/core/Port.py b/grc/core/Port.py
index 4ba516d5fe..28988cd51e 100644
--- a/grc/core/Port.py
+++ b/grc/core/Port.py
@@ -131,7 +131,7 @@ class Port(Element):
         Element.__init__(self, parent=block)
         # Grab the data
         self._name = n['name']
-        self._key = n['key']
+        self.key = n['key']
         self._type = n.get('type', '')
         self._domain = n.get('domain')
         self._hide = n.get('hide', '')
@@ -146,9 +146,9 @@ class Port(Element):
 
     def __str__(self):
         if self.is_source:
-            return 'Source - {}({})'.format(self.get_name(), self.get_key())
+            return 'Source - {}({})'.format(self.get_name(), self.key)
         if self.is_sink:
-            return 'Sink - {}({})'.format(self.get_name(), self.get_key())
+            return 'Sink - {}({})'.format(self.get_name(), self.key)
 
     def get_types(self):
         return list(Constants.TYPE_TO_SIZEOF.keys())
@@ -195,10 +195,10 @@ class Port(Element):
         type_ = self.get_type()
         if self._domain == Constants.GR_STREAM_DOMAIN and type_ == "message":
             self._domain = Constants.GR_MESSAGE_DOMAIN
-            self._key = self._name
+            self.key = self._name
         if self._domain == Constants.GR_MESSAGE_DOMAIN and type_ != "message":
             self._domain = Constants.GR_STREAM_DOMAIN
-            self._key = '0'  # Is rectified in rewrite()
+            self.key = '0'  # Is rectified in rewrite()
 
     def resolve_virtual_source(self):
         if self.parent.is_virtual_sink():
@@ -286,8 +286,8 @@ class Port(Element):
         if not self._clones:
             self._name = self._n['name'] + '0'
             # Also update key for none stream ports
-            if not self._key.isdigit():
-                self._key = self._name
+            if not self.key.isdigit():
+                self.key = self._name
 
         # Prepare a copy of the odict for the clone
         n = self._n.copy()
@@ -296,7 +296,7 @@ class Port(Element):
             n.pop('nports')
         n['name'] = self._n['name'] + str(len(self._clones) + 1)
         # Dummy value 99999 will be fixed later
-        n['key'] = '99999' if self._key.isdigit() else n['name']
+        n['key'] = '99999' if self.key.isdigit() else n['name']
 
         # Clone
         port = self.__class__(self.parent, n, self._dir)
@@ -313,8 +313,8 @@ class Port(Element):
         if not self._clones:
             self._name = self._n['name']
             # Also update key for none stream ports
-            if not self._key.isdigit():
-                self._key = self._name
+            if not self.key.isdigit():
+                self.key = self._name
 
     def get_name(self):
         number = ''
@@ -323,9 +323,6 @@ class Port(Element):
             number = str(busses.index(self)) + '#' + str(len(self.get_associated_ports()))
         return self._name + number
 
-    def get_key(self):
-        return self._key
-
     @property
     def is_sink(self):
         return self._dir == 'sink'
diff --git a/grc/core/generator/FlowGraphProxy.py b/grc/core/generator/FlowGraphProxy.py
index a23c6d84ab..d5d31c0dce 100644
--- a/grc/core/generator/FlowGraphProxy.py
+++ b/grc/core/generator/FlowGraphProxy.py
@@ -90,7 +90,7 @@ class FlowGraphProxy(object):
         Returns:
             a list of pad source blocks in this flow graph
         """
-        pads = [b for b in self.get_enabled_blocks() if b.get_key() == 'pad_source']
+        pads = [b for b in self.get_enabled_blocks() if b.key == 'pad_source']
         return sorted(pads, lambda x, y: cmp(x.get_id(), y.get_id()))
 
     def get_pad_sinks(self):
@@ -100,7 +100,7 @@ class FlowGraphProxy(object):
         Returns:
             a list of pad sink blocks in this flow graph
         """
-        pads = [b for b in self.get_enabled_blocks() if b.get_key() == 'pad_sink']
+        pads = [b for b in self.get_enabled_blocks() if b.key == 'pad_sink']
         return sorted(pads, lambda x, y: cmp(x.get_id(), y.get_id()))
 
     def get_pad_port_global_key(self, port):
@@ -121,7 +121,7 @@ class FlowGraphProxy(object):
                 if is_message_pad:
                     key = pad.get_param('label').get_value()
                 else:
-                    key = str(key_offset + int(port.get_key()))
+                    key = str(key_offset + int(port.key))
                 return key
             else:
                 # assuming we have either only sources or sinks
diff --git a/grc/core/generator/Generator.py b/grc/core/generator/Generator.py
index 9c7d07e76b..3664d083e9 100644
--- a/grc/core/generator/Generator.py
+++ b/grc/core/generator/Generator.py
@@ -98,7 +98,7 @@ class TopBlockGenerator(object):
                                   "Add a Misc->Throttle block to your flow "
                                   "graph to avoid CPU congestion.")
         if len(throttling_blocks) > 1:
-            keys = set([b.get_key() for b in throttling_blocks])
+            keys = set([b.key for b in throttling_blocks])
             if len(keys) > 1 and 'blocks_throttle' in keys:
                 Messages.send_warning("This flow graph contains a throttle "
                                       "block and another rate limiting block, "
@@ -156,7 +156,7 @@ class TopBlockGenerator(object):
         blocks = [b for b in blocks_all if b not in imports and b not in parameters]
 
         for block in blocks:
-            key = block.get_key()
+            key = block.key
             file_path = os.path.join(self._dirname, block.get_id() + '.py')
             if key == 'epy_block':
                 src = block.get_param('_source_code').get_value()
diff --git a/grc/core/generator/flow_graph.tmpl b/grc/core/generator/flow_graph.tmpl
index 5eef3d2042..34178ebc66 100644
--- a/grc/core/generator/flow_graph.tmpl
+++ b/grc/core/generator/flow_graph.tmpl
@@ -241,12 +241,12 @@ gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))])
 ##  However, port names for IO pads should be self.
 ########################################################
 #def make_port_sig($port)
-    #if $port.parent.get_key() in ('pad_source', 'pad_sink')
+    #if $port.parent.key in ('pad_source', 'pad_sink')
         #set block = 'self'
         #set key = $flow_graph.get_pad_port_global_key($port)
     #else
         #set block = 'self.' + $port.parent.get_id()
-        #set key = $port.get_key()
+        #set key = $port.key
     #end if
     #if not $key.isdigit()
         #set key = repr($key)
diff --git a/grc/core/utils/complexity.py b/grc/core/utils/complexity.py
index d72db9b3dd..6864603a0a 100644
--- a/grc/core/utils/complexity.py
+++ b/grc/core/utils/complexity.py
@@ -4,7 +4,7 @@ def calculate_flowgraph_complexity(flowgraph):
     dbal = 0
     for block in flowgraph.blocks:
         # Skip options block
-        if block.get_key() == 'options':
+        if block.key == 'options':
             continue
 
         # Don't worry about optional sinks?
diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py
index d188030c62..b135efdce9 100644
--- a/grc/gui/ActionHandler.py
+++ b/grc/gui/ActionHandler.py
@@ -245,10 +245,10 @@ class ActionHandler:
 
                                 # If connected block is not in the list of selected blocks create a pad for it
                                 if flow_graph.get_block(source_id) not in flow_graph.get_selected_blocks():
-                                    pads.append({'key': connection.sink_port.get_key(), 'coord': connection.source_port.get_coordinate(), 'block_id' : block.get_id(), 'direction': 'source'})
+                                    pads.append({'key': connection.sink_port.key, 'coord': connection.source_port.get_coordinate(), 'block_id' : block.get_id(), 'direction': 'source'})
 
                                 if flow_graph.get_block(sink_id) not in flow_graph.get_selected_blocks():
-                                    pads.append({'key': connection.source_port.get_key(), 'coord': connection.sink_port.get_coordinate(), 'block_id' : block.get_id(), 'direction': 'sink'})
+                                    pads.append({'key': connection.source_port.key, 'coord': connection.sink_port.get_coordinate(), 'block_id' : block.get_id(), 'direction': 'sink'})
 
 
                         # Copy the selected blocks and paste them into a new page
diff --git a/grc/gui/Block.py b/grc/gui/Block.py
index fd6612b8c2..0d9bd3e09b 100644
--- a/grc/gui/Block.py
+++ b/grc/gui/Block.py
@@ -134,7 +134,7 @@ class Block(Element, _Block):
         #display the params
         if self.is_dummy_block:
             markups = ['<span foreground="black" font_desc="{font}"><b>key: </b>{key}</span>'.format(
-                font=PARAM_FONT, key=self._key
+                font=PARAM_FONT, key=self.key
             )]
         else:
             markups = [param.format_block_surface_markup() for param in self.get_params() if param.get_hide() not in ('all', 'part')]
@@ -180,7 +180,7 @@ class Block(Element, _Block):
         markups = []
 
         # Show the flow graph complexity on the top block if enabled
-        if Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY.get_active() and self.get_key() == "options":
+        if Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY.get_active() and self.key == "options":
             complexity = calculate_flowgraph_complexity(self.parent)
             markups.append(
                 '<span foreground="#444" size="medium" font_desc="{font}">'
diff --git a/grc/gui/BlockTreeWindow.py b/grc/gui/BlockTreeWindow.py
index 8522390fc1..f0f81b3757 100644
--- a/grc/gui/BlockTreeWindow.py
+++ b/grc/gui/BlockTreeWindow.py
@@ -176,7 +176,7 @@ class BlockTreeWindow(Gtk.VBox):
                 categories[parent_category] = iter_
         # add block
         iter_ = treestore.insert_before(categories[category], None)
-        treestore.set_value(iter_, KEY_INDEX, block.get_key())
+        treestore.set_value(iter_, KEY_INDEX, block.key)
         treestore.set_value(iter_, NAME_INDEX, block.name)
         treestore.set_value(iter_, DOC_INDEX, _format_doc(block.documentation))
 
@@ -230,7 +230,7 @@ class BlockTreeWindow(Gtk.VBox):
             self.expand_module_in_tree()
         else:
             matching_blocks = [b for b in list(self.platform.blocks.values())
-                               if key in b.get_key().lower() or key in b.name.lower()]
+                               if key in b.key.lower() or key in b.name.lower()]
 
             self.treestore_search.clear()
             self._categories_search = {tuple(): None}
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index d7745a529d..1c4960fed4 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -88,7 +88,7 @@ class FlowGraph(Element, _Flowgraph):
         return block_id
 
     def install_external_editor(self, param):
-        target = (param.parent_block.get_id(), param.get_key())
+        target = (param.parent_block.get_id(), param.key)
 
         if target in self._external_updaters:
             editor = self._external_updaters[target]
diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py
index 3236768969..bd07a667d4 100644
--- a/grc/gui/MainWindow.py
+++ b/grc/gui/MainWindow.py
@@ -60,7 +60,7 @@ class MainWindow(Gtk.Window):
         gen_opts = platform.blocks['options'].get_param('generate_options')
         generate_mode_default = gen_opts.get_value()
         generate_modes = [
-            (o.get_key(), o.get_name(), o.get_key() == generate_mode_default)
+            (o.key, o.get_name(), o.key == generate_mode_default)
             for o in gen_opts.get_options()]
 
         # Load preferences
diff --git a/grc/gui/Param.py b/grc/gui/Param.py
index a630f5faa3..c888b9ebc0 100644
--- a/grc/gui/Param.py
+++ b/grc/gui/Param.py
@@ -66,7 +66,7 @@ class Param(Element, _Param):
         # fixme: using non-public attribute here
         has_callback = \
             hasattr(block, 'get_callbacks') and \
-            any(self.get_key() in callback for callback in block._callbacks)
+            any(self.key in callback for callback in block._callbacks)
 
         return '<span underline="{line}" foreground="{color}" font_desc="Sans 9">{label}</span>'.format(
             line='low' if has_callback else 'none',
@@ -78,7 +78,7 @@ class Param(Element, _Param):
 
     def format_tooltip_text(self):
         errors = self.get_error_messages()
-        tooltip_lines = ['Key: ' + self.get_key(), 'Type: ' + self.get_type()]
+        tooltip_lines = ['Key: ' + self.key, 'Type: ' + self.get_type()]
         if self.is_valid():
             value = str(self.get_evaluated())
             if len(value) > 100:
diff --git a/grc/gui/ParamWidgets.py b/grc/gui/ParamWidgets.py
index fbbfa93d1d..3ee4c7761d 100644
--- a/grc/gui/ParamWidgets.py
+++ b/grc/gui/ParamWidgets.py
@@ -270,7 +270,7 @@ class FileParam(EntryParam):
         file_path = self.param.is_valid() and self.param.get_evaluated() or ''
         (dirname, basename) = os.path.isfile(file_path) and os.path.split(file_path) or (file_path, '')
         # check for qss theme default directory
-        if self.param.get_key() == 'qt_qss_theme':
+        if self.param.key == 'qt_qss_theme':
             dirname = os.path.dirname(dirname)  # trim filename
             if not os.path.exists(dirname):
                config = self.param.parent_platform.config
diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py
index d7722adff7..1244db3537 100644
--- a/grc/gui/PropsDialog.py
+++ b/grc/gui/PropsDialog.py
@@ -226,7 +226,7 @@ class PropsDialog(Gtk.Dialog):
 
         buf = self._code_text_display.get_buffer()
         block = self._block
-        key = block.get_key()
+        key = block.key
 
         if key == 'epy_block':
             src = block.get_param('_source_code').get_value()
diff --git a/grc/gui/VariableEditor.py b/grc/gui/VariableEditor.py
index 399e4ec475..11284f5708 100644
--- a/grc/gui/VariableEditor.py
+++ b/grc/gui/VariableEditor.py
@@ -177,9 +177,9 @@ class VariableEditor(Gtk.VBox):
 
         # Block specific values
         if block:
-            if block.get_key() == 'import':
+            if block.key == 'import':
                 value = block.get_param('import').get_value()
-            elif block.get_key() != "variable":
+            elif block.key != "variable":
                 value = "<Open Properties>"
                 sp('editable', False)
                 sp('foreground', '#0D47A1')
@@ -195,7 +195,7 @@ class VariableEditor(Gtk.VBox):
                 self.set_tooltip_text(error_message[-1])
             else:
                 # Evaluate and show the value (if it is a variable)
-                if block.get_key() == "variable":
+                if block.key == "variable":
                     evaluated = str(block.get_param('value').evaluate())
                     self.set_tooltip_text(evaluated)
         # Always set the text value.
@@ -300,7 +300,7 @@ class VariableEditor(Gtk.VBox):
                 # Make sure this has a block (not the import/variable rows)
                 if self._block and event.type == Gdk.EventType._2BUTTON_PRESS:
                     # Open the advanced dialog if it is a gui variable
-                    if self._block.get_key() not in ("variable", "import"):
+                    if self._block.key not in ("variable", "import"):
                         self.handle_action(None, self.OPEN_PROPERTIES, event=event)
                         return True
                 if event.type == Gdk.EventType.BUTTON_PRESS:
-- 
cgit v1.2.3


From 4b60db7deaa89a998715b45e942fa0c69dd5be1a Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Sat, 18 Jun 2016 21:16:14 -0700
Subject: grc-refactor: the hopeless cause of bus ports...

---
 grc/core/Block.py     | 107 ++++++++++++++++++++++++--------------------------
 grc/core/FlowGraph.py |  81 +++++++++++++++++---------------------
 grc/core/Port.py      |  27 ++++++-------
 grc/gui/FlowGraph.py  |   4 +-
 4 files changed, 102 insertions(+), 117 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/core/Block.py b/grc/core/Block.py
index c6d743eaee..743c9de99f 100644
--- a/grc/core/Block.py
+++ b/grc/core/Block.py
@@ -578,26 +578,30 @@ class Block(Element):
         Returns:
             true for change
         """
-        changed = False
+        type_templates = ' '.join(p._type for p in self.get_children())
         type_param = None
         for key, param in six.iteritems(self.params):
-            children = self.get_children()
+            if not param.is_enum():
+                continue
             # Priority to the type controller
-            if param.key in ' '.join([p._type for p in children]): type_param = param
+            if param.key in type_templates:
+                type_param = param
+                break
             # Use param if type param is unset
             if not type_param:
                 type_param = param
-        if type_param:
-            # Try to increment the enum by direction
-            try:
-                keys = type_param.get_option_keys()
-                old_index = keys.index(type_param.get_value())
-                new_index = (old_index + direction + len(keys)) % len(keys)
-                type_param.set_value(keys[new_index])
-                changed = True
-            except:
-                pass
-        return changed
+        if not type_param:
+            return False
+
+        # Try to increment the enum by direction
+        try:
+            keys = type_param.get_option_keys()
+            old_index = keys.index(type_param.get_value())
+            new_index = (old_index + direction + len(keys)) % len(keys)
+            type_param.set_value(keys[new_index])
+            return True
+        except:
+            return False
 
     def port_controller_modify(self, direction):
         """
@@ -611,7 +615,7 @@ class Block(Element):
         """
         changed = False
         # Concat the nports string from the private nports settings of all ports
-        nports_str = ' '.join([port._nports for port in self.get_ports()])
+        nports_str = ' '.join(port._nports for port in self.get_ports())
         # Modify all params whose keys appear in the nports string
         for key, param in six.iteritems(self.params):
             if param.is_enum() or param.key not in nports_str:
@@ -682,12 +686,9 @@ class Block(Element):
     ##############################################
 
     def get_bus_structure(self, direction):
-        if direction == 'source':
-            bus_structure = self._bus_structure_source
-        else:
-            bus_structure = self._bus_structure_sink
-
-        bus_structure = self.resolve_dependencies(bus_structure)
+        bus_structure = self.resolve_dependencies(
+            self._bus_structure_source if direction == 'source' else
+            self._bus_structure_sink)
 
         if not bus_structure:
             return ''  # TODO: Don't like empty strings. should change this to None eventually
@@ -706,70 +707,66 @@ class Block(Element):
         return buslist or ports
 
     def _import_bus_stuff(self, n):
-        bussinks = n.get('bus_sink', [])
-        if len(bussinks) > 0 and not self._bussify_sink:
+        bus_sinks = n.get('bus_sink', [])
+        if len(bus_sinks) > 0 and not self._bussify_sink:
             self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
-        elif len(bussinks) > 0:
+        elif len(bus_sinks) > 0:
             self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
             self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
-        bussrcs = n.get('bus_source', [])
-        if len(bussrcs) > 0 and not self._bussify_source:
+        bus_sources = n.get('bus_source', [])
+        if len(bus_sources) > 0 and not self._bussify_source:
             self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
-        elif len(bussrcs) > 0:
+        elif len(bus_sources) > 0:
             self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
             self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
 
     def form_bus_structure(self, direc):
-        if direc == 'source':
-            get_p = self.get_sources
-            get_p_gui = self.get_sources_gui
-            bus_structure = self.get_bus_structure('source')
-        else:
-            get_p = self.get_sinks
-            get_p_gui = self.get_sinks_gui
-            bus_structure = self.get_bus_structure('sink')
+        ports = self.sources if direc == 'source' else self.sinks
+        struct = self.get_bus_structure(direc)
 
-        struct = [list(range(len(get_p())))]
-        if True in [isinstance(a.get_nports(), int) for a in get_p()]:
-            structlet = []
-            last = 0
-            for j in [i.get_nports() for i in get_p() if isinstance(i.get_nports(), int)]:
-                structlet.extend([a+last for a in range(j)])
-                last = structlet[-1] + 1
-                struct = [structlet]
-        if bus_structure:
+        if not struct:
+            struct = [list(range(len(ports)))]
 
-            struct = bus_structure
+        elif any(isinstance(p.get_nports(), int) for p in ports):
+            last = 0
+            structlet = []
+            for port in ports:
+                nports = port.get_nports()
+                if not isinstance(nports, int):
+                    continue
+                structlet.extend(a + last for a in range(nports))
+                last += nports
+            struct = [structlet]
 
         self.current_bus_structure[direc] = struct
         return struct
 
     def bussify(self, n, direc):
         if direc == 'source':
-            get_p = self.get_sources
             get_p_gui = self.get_sources_gui
         else:
-            get_p = self.get_sinks
             get_p_gui = self.get_sinks_gui
 
-        for elt in get_p():
+        ports = self.sources if direc == 'source' else self.sinks
+
+        for elt in ports:
             for connect in elt.get_connections():
                 self.parent.remove_element(connect)
 
-        if ('bus' not in [a.get_type() for a in get_p()]) and len(get_p()) > 0:
+        if ports and all('bus' != p.get_type() for p in ports):
             struct = self.form_bus_structure(direc)
             self.current_bus_structure[direc] = struct
-            if get_p()[0].get_nports():
-                n['nports'] = str(1)
+            if ports[0].get_nports():
+                n['nports'] = '1'
 
             for i in range(len(struct)):
-                n['key'] = str(len(get_p()))
+                n['key'] = str(len(ports))
                 n = dict(n)
                 port = self.parent.parent.Port(block=self, n=n, dir=direc)
-                get_p().append(port)
-        elif 'bus' in [a.get_type() for a in get_p()]:
+                ports.append(port)
+        elif any('bus' == p.get_type() for p in ports):
             for elt in get_p_gui():
-                get_p().remove(elt)
+                ports.remove(elt)
             self.current_bus_structure[direc] = ''
 
     def _init_bus_ports(self, n):
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index 53c4b783b7..16bcb3b9f6 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -461,52 +461,41 @@ class FlowGraph(Element):
     ##############################################
     def bus_ports_rewrite(self):
         # todo: move to block.rewrite()
-        for block in self.blocks:
-            for direc in ['source', 'sink']:
-                if direc == 'source':
-                    get_p = block.get_sources
-                    get_p_gui = block.get_sources_gui
-                    bus_structure = block.form_bus_structure('source')
-                else:
-                    get_p = block.get_sinks
-                    get_p_gui = block.get_sinks_gui
-                    bus_structure = block.form_bus_structure('sink')
-
-                if 'bus' in [a.get_type() for a in get_p_gui()]:
-                    if len(get_p_gui()) > len(bus_structure):
-                        times = range(len(bus_structure), len(get_p_gui()))
-                        for i in times:
-                            for connect in get_p_gui()[-1].get_connections():
-                                block.parent.remove_element(connect)
-                            get_p().remove(get_p_gui()[-1])
-                    elif len(get_p_gui()) < len(bus_structure):
-                        n = {'name': 'bus', 'type': 'bus'}
-                        if any(isinstance(a.get_nports(), int) for a in get_p()):
-                            n['nports'] = str(1)
-
-                        times = range(len(get_p_gui()), len(bus_structure))
-
-                        for i in times:
-                            n['key'] = str(len(get_p()))
-                            n = dict(n)
-                            port = block.parent.parent.Port(
-                                block=block, n=n, dir=direc)
-                            get_p().append(port)
-
-                if 'bus' in [a.get_type() for a in block.get_sources_gui()]:
-                    for i in range(len(block.get_sources_gui())):
-                        if len(block.get_sources_gui()[
-                                   i].get_connections()) > 0:
-                            source = block.get_sources_gui()[i]
-                            sink = []
-
-                            for j in range(len(source.get_connections())):
-                                sink.append(
-                                    source.get_connections()[j].sink_port)
-                            for elt in source.get_connections():
-                                self.remove_element(elt)
-                            for j in sink:
-                                self.connect(source, j)
+        def doit(block, ports, ports_gui, direc):
+            bus_structure = block.form_bus_structure(direc)
+
+            if any('bus' == a.get_type() for a in ports_gui):
+                if len(ports_gui) > len(bus_structure):
+                    for _ in range(len(bus_structure), len(ports_gui)):
+                        for connect in ports_gui[-1].get_connections():
+                            block.parent.remove_element(connect)
+                        ports.remove(ports_gui[-1])
+                elif len(ports_gui) < len(bus_structure):
+                    n = {'name': 'bus', 'type': 'bus'}
+                    if any(isinstance(a.get_nports(), int) for a in ports):
+                        n['nports'] = str(1)
+                    for _ in range(len(ports_gui), len(bus_structure)):
+                        n['key'] = str(len(ports))
+                        port = block.parent.parent.Port(block=block, n=dict(n), dir=direc)
+                        ports.append(port)
+
+            if 'bus' in [a.get_type() for a in block.get_sources_gui()]:
+                for i in range(len(block.get_sources_gui())):
+                    if not block.get_sources_gui()[i].get_connections():
+                        continue
+                    source = block.get_sources_gui()[i]
+                    sink = []
+
+                    for j in range(len(source.get_connections())):
+                        sink.append(source.get_connections()[j].sink_port)
+                    for elt in source.get_connections():
+                        self.remove_element(elt)
+                    for j in sink:
+                        self.connect(source, j)
+
+        for blk in self.blocks:
+            doit(blk, blk.sources, blk.get_sources_gui(), 'source')
+            doit(blk, blk.sinks, blk.get_sinks_gui(), 'sinks')
 
 
 def _update_old_message_port_keys(source_key, sink_key, source_block, sink_block):
diff --git a/grc/core/Port.py b/grc/core/Port.py
index d0362ffd78..bb1e1fa9f2 100644
--- a/grc/core/Port.py
+++ b/grc/core/Port.py
@@ -363,18 +363,17 @@ class Port(Element):
     def get_associated_ports(self):
         if not self.get_type() == 'bus':
             return [self]
+
+        block = self.parent_block
+        if self.is_source:
+            block_ports = block.sources
+            bus_structure = block.current_bus_structure['source']
         else:
-            flowgraph = self.parent_flowgraph
-            if self.is_source:
-                get_ports = flowgraph.get_sources
-                bus_structure = flowgraph.current_bus_structure['source']
-            else:
-                get_ports = flowgraph.get_sinks
-                bus_structure = flowgraph.current_bus_structure['sink']
-
-            ports = [i for i in get_ports() if not i.get_type() == 'bus']
-            if bus_structure:
-                busses = [i for i in get_ports() if i.get_type() == 'bus']
-                bus_index = busses.index(self)
-                ports = [a for a in ports if ports.index(a) in bus_structure[bus_index]]
-            return ports
+            block_ports = block.sinks
+            bus_structure = block.current_bus_structure['sink']
+
+        ports = [i for i in block_ports if not i.get_type() == 'bus']
+        if bus_structure:
+            bus_index = [i for i in block_ports if i.get_type() == 'bus'].index(self)
+            ports = [p for i, p in enumerate(ports) if i in bus_structure[bus_index]]
+        return ports
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index 1c4960fed4..f6a57ef367 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -259,7 +259,7 @@ class FlowGraph(Element, _Flowgraph):
         Returns:
             true for change
         """
-        return any([sb.type_controller_modify(direction) for sb in self.get_selected_blocks()])
+        return any(sb.type_controller_modify(direction) for sb in self.get_selected_blocks())
 
     def port_controller_modify_selected(self, direction):
         """
@@ -271,7 +271,7 @@ class FlowGraph(Element, _Flowgraph):
         Returns:
             true for changed
         """
-        return any([sb.port_controller_modify(direction) for sb in self.get_selected_blocks()])
+        return any(sb.port_controller_modify(direction) for sb in self.get_selected_blocks())
 
     def enable_selected(self, enable):
         """
-- 
cgit v1.2.3


From 74e7af6cd0eb5e2f3892b42f08b0089ed2f2fec0 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Tue, 5 Jul 2016 17:17:54 +0200
Subject: grc: gtk3: enable block comments

---
 grc/gui/Block.py     | 49 ++++++++++++++++++++-----------------------------
 grc/gui/FlowGraph.py |  3 +--
 2 files changed, 21 insertions(+), 31 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/gui/Block.py b/grc/gui/Block.py
index d3659dd94a..225881fec8 100644
--- a/grc/gui/Block.py
+++ b/grc/gui/Block.py
@@ -52,10 +52,10 @@ class Block(Element, _Block):
                         hide='all')
 
         Element.__init__(self)  # needs the params
-        self._comment_pixmap = None
+        self._param_layouts = []
+        self._comment_layout = None
         self._bg_color = Colors.BLOCK_ENABLED_COLOR
         self.has_busses = [False, False]  # source, sink
-        self.layouts = []
 
     def get_coordinate(self):
         """
@@ -123,14 +123,14 @@ class Block(Element, _Block):
                          Colors.BLOCK_BYPASSED_COLOR if self.get_bypassed() else \
                          Colors.BLOCK_ENABLED_COLOR if self.get_enabled() else \
                          Colors.BLOCK_DISABLED_COLOR
-        del self.layouts[:]
+        del self._param_layouts[:]
         #create the main layout
         layout = Gtk.DrawingArea().create_pango_layout('')
         layout.set_markup('<span foreground="{foreground}" font_desc="{font}"><b>{name}</b></span>'.format(
             foreground='black' if self.is_valid() else 'red', font=BLOCK_FONT, name=Utils.encode(self.name)
         ))
         self.label_width, self.label_height = layout.get_pixel_size()
-        self.layouts.append(layout)
+        self._param_layouts.append(layout)
         #display the params
         if self.is_dummy_block:
             markups = ['<span foreground="black" font_desc="{font}"><b>key: </b>{key}</span>'.format(
@@ -143,7 +143,7 @@ class Block(Element, _Block):
             layout = Gtk.DrawingArea().create_pango_layout('')
             layout.set_spacing(LABEL_SEPARATION*Pango.SCALE)
             layout.set_markup('\n'.join(markups))
-            self.layouts.append(layout)
+            self._param_layouts.append(layout)
             w, h = layout.get_pixel_size()
             self.label_width = max(w, self.label_width)
             self.label_height += h + LABEL_SEPARATION
@@ -175,9 +175,9 @@ class Block(Element, _Block):
             any(port.get_type() == 'bus' for port in ports)
             for ports in (self.get_sources_gui(), self.get_sinks_gui())
         ]
-        self.create_comment_label()
+        self.create_comment_layout()
 
-    def create_comment_label(self):
+    def create_comment_layout(self):
         markups = []
 
         # Show the flow graph complexity on the top block if enabled
@@ -195,23 +195,11 @@ class Block(Element, _Block):
             markups.append('<span foreground="{foreground}" font_desc="{font}">{comment}</span>'.format(
                 foreground='#444' if self.get_enabled() else '#888', font=BLOCK_FONT, comment=Utils.encode(comment)
             ))
-        layout = Gtk.DrawingArea().create_pango_layout('')
-        layout.set_markup(''.join(markups))
-
-        # Setup the pixel map. Make sure that layout not empty
-        width, height = layout.get_pixel_size()
-        if width and height:
-            padding = BLOCK_LABEL_PADDING
-            pixmap = self.parent.new_pixmap(width + 2 * padding,
-                                                  height + 2 * padding)
-            gc = pixmap.new_gc()
-            gc.set_foreground(Colors.COMMENT_BACKGROUND_COLOR)
-            pixmap.draw_rectangle(
-                gc, True, 0, 0, width + 2 * padding, height + 2 * padding)
-            pixmap.draw_layout(gc, padding, padding, layout)
-            self._comment_pixmap = pixmap
+        if markups:
+            layout = self._comment_layout = Gtk.DrawingArea().create_pango_layout('')
+            layout.set_markup(''.join(markups))
         else:
-            self._comment_pixmap = None
+            self._comment_layout = None
 
     def draw(self, widget, cr):
         """
@@ -230,7 +218,6 @@ class Block(Element, _Block):
         x, y = self.get_coordinate()
         # create the image surface
         width = self.label_width
-        height = self.label_height
         cr.set_source_rgb(*self._bg_color)
         cr.save()
         if self.is_horizontal():
@@ -245,7 +232,7 @@ class Block(Element, _Block):
 
         # draw the layouts
         h_off = 0
-        for i, layout in enumerate(self.layouts):
+        for i, layout in enumerate(self._param_layouts):
             w, h = layout.get_pixel_size()
             if i == 0:
                 w_off = (width - w) / 2
@@ -255,7 +242,7 @@ class Block(Element, _Block):
             PangoCairo.update_layout(cr, layout)
             PangoCairo.show_layout(cr, layout)
             cr.translate(-w_off, -h_off)
-            h_off = h + h_off + LABEL_SEPARATION
+            h_off += h + LABEL_SEPARATION
         cr.restore()
 
     def what_is_selected(self, coor, coor_m=None):
@@ -274,8 +261,8 @@ class Block(Element, _Block):
             if port_selected: return port_selected
         return Element.what_is_selected(self, coor, coor_m)
 
-    def draw_comment(self, gc, window):
-        if not self._comment_pixmap:
+    def draw_comment(self, widget, cr):
+        if not self._comment_layout:
             return
         x, y = self.get_coordinate()
 
@@ -284,4 +271,8 @@ class Block(Element, _Block):
         else:
             x += self.H + BLOCK_LABEL_PADDING
 
-        window.draw_drawable(gc, self._comment_pixmap, 0, 0, x, y, -1, -1)
+        cr.save()
+        cr.translate(x, y)
+        PangoCairo.update_layout(cr, self._comment_layout)
+        PangoCairo.show_layout(cr, self._comment_layout)
+        cr.restore()
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index f6a57ef367..77615f13b0 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -408,8 +408,7 @@ class FlowGraph(Element, _Flowgraph):
         if Actions.TOGGLE_SHOW_BLOCK_COMMENTS.get_active():
             for block in self.blocks:
                 if block.get_enabled():
-                    # block.draw_comment(widget, cr)
-                    pass
+                    block.draw_comment(widget, cr)
         # draw multi select rectangle
         if self.mouse_pressed and (not self.get_selected_elements() or self.get_ctrl_mask()):
             x1, y1 = self.press_coor
-- 
cgit v1.2.3


From 5cef62d03e3a750bce7f180930ceb777f7dba6b2 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Thu, 7 Jul 2016 16:30:33 +0200
Subject: grc: gtk3: refactor and update draw code WIP

---
 grc/gui/Block.py      | 32 +++++++++++-----------
 grc/gui/Connection.py | 26 ++++++++----------
 grc/gui/Element.py    | 76 ++++++++++++++++++++++-----------------------------
 grc/gui/FlowGraph.py  | 76 ++++++++++++++++++++++++++++++---------------------
 grc/gui/Port.py       | 26 +++++++-----------
 5 files changed, 115 insertions(+), 121 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/gui/Block.py b/grc/gui/Block.py
index 225881fec8..a38e4cc59a 100644
--- a/grc/gui/Block.py
+++ b/grc/gui/Block.py
@@ -41,7 +41,7 @@ class Block(Element, _Block):
 
     def __init__(self, flow_graph, n):
         """
-        Block contructor.
+        Block constructor.
         Add graphics related params to the block.
         """
         _Block.__init__(self, flow_graph, n)
@@ -113,8 +113,10 @@ class Block(Element, _Block):
     def create_shapes(self):
         """Update the block, parameters, and ports when a change occurs."""
         Element.create_shapes(self)
-        if self.is_horizontal(): self.add_area((0, 0), (self.W, self.H))
-        elif self.is_vertical(): self.add_area((0, 0), (self.H, self.W))
+        if self.is_horizontal():
+            self.add_area(0, 0, self.W, self.H)
+        elif self.is_vertical():
+            self.add_area(0, 0, self.H, self.W)
 
     def create_labels(self):
         """Create the labels for the signal block."""
@@ -205,31 +207,30 @@ class Block(Element, _Block):
         """
         Draw the signal block with label and inputs/outputs.
         """
-        # draw ports
-        for port in self.get_ports_gui():
-            port.draw(widget, cr)
         # draw main block
+        bg_color = self._bg_color
         border_color = (
             Colors.HIGHLIGHT_COLOR if self.is_highlighted() else
             Colors.MISSING_BLOCK_BORDER_COLOR if self.is_dummy_block else
             Colors.BORDER_COLOR
         )
-        Element.draw(self, widget, cr, border_color, self._bg_color)
-        x, y = self.get_coordinate()
-        # create the image surface
+
+        for port in self.get_ports_gui():
+            cr.save()
+            port.draw(widget, cr, border_color, bg_color)
+            cr.restore()
+        Element.draw(self, widget, cr, border_color, bg_color)
+
+        # create the param label
         width = self.label_width
         cr.set_source_rgb(*self._bg_color)
-        cr.save()
         if self.is_horizontal():
-            cr.translate(x + BLOCK_LABEL_PADDING, y + (self.H - self.label_height) / 2)
+            cr.translate(BLOCK_LABEL_PADDING, (self.H - self.label_height) / 2)
         elif self.is_vertical():
-            cr.translate(x + (self.H - self.label_height) / 2, y + BLOCK_LABEL_PADDING)
+            cr.translate((self.H - self.label_height) / 2, BLOCK_LABEL_PADDING)
             cr.rotate(-90 * math.pi / 180.)
             cr.translate(-width, 0)
 
-        # cr.rectangle(0, 0, width, height)
-        # cr.fill()
-
         # draw the layouts
         h_off = 0
         for i, layout in enumerate(self._param_layouts):
@@ -243,7 +244,6 @@ class Block(Element, _Block):
             PangoCairo.show_layout(cr, layout)
             cr.translate(-w_off, -h_off)
             h_off += h + LABEL_SEPARATION
-        cr.restore()
 
     def what_is_selected(self, coor, coor_m=None):
         """
diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py
index b1a22e3949..ce40a3b036 100644
--- a/grc/gui/Connection.py
+++ b/grc/gui/Connection.py
@@ -42,7 +42,6 @@ class Connection(Element, _Connection):
     def __init__(self, **kwargs):
         Element.__init__(self)
         _Connection.__init__(self, **kwargs)
-        # can't use Colors.CONNECTION_ENABLED_COLOR here, might not be defined (grcc)
         self._bg_color = self._arrow_color = self._color = None
 
         self._sink_rot = self._source_rot = None
@@ -112,16 +111,14 @@ class Connection(Element, _Connection):
         self.clear()
         #source connector
         source = self.source_port
-        X, Y = source.get_connector_coordinate()
-        x1, y1 = self.x1 + X, self.y1 + Y
-        self.add_line((x1, y1), (X, Y))
+        x0, y0 = p0 = source.get_connector_coordinate()
+        x1, y1 = p1 = self.x1 + x0, self.y1 + y0
         #sink connector
         sink = self.sink_port
-        X, Y = sink.get_connector_coordinate()
-        x2, y2 = self.x2 + X, self.y2 + Y
-        self.add_line((x2, y2), (X, Y))
+        x3, y3 = p3 = sink.get_connector_coordinate()
+        x2, y2 = p2 = self.x2 + x3, self.y2 + y3
         #adjust arrow
-        self._arrow = [(x+X, y+Y) for x, y in self.arrow]
+        self._arrow = [(x + x3, y + y3) for x, y in self.arrow]
         #add the horizontal and vertical lines in this connection
         if abs(source.get_connector_direction() - sink.get_connector_direction()) == 180:
             #2 possible point sets to create a 3-line connector
@@ -133,11 +130,9 @@ class Connection(Element, _Connection):
             if Utils.get_angle_from_coordinates(points[0][0], (x2, y2)) == sink.get_connector_direction(): points.reverse()
             #points[0][0] -> source connector should not be in the direction of source
             if Utils.get_angle_from_coordinates(points[0][0], (x1, y1)) == source.get_connector_direction(): points.reverse()
-            #create 3-line connector
-            p1, p2 = list(map(int, points[0][0])), list(map(int, points[0][1]))
-            self.add_line((x1, y1), p1)
-            self.add_line(p1, p2)
-            self.add_line((x2, y2), p2)
+            # create 3-line connector
+            i1, i2 = list(map(int, points[0][0])), list(map(int, points[0][1]))
+            self.add_line(p0, p1, i1, i2, p2, p3)
         else:
             #2 possible points to create a right-angled connector
             points = [(x1, y2), (x2, y1)]
@@ -148,8 +143,8 @@ class Connection(Element, _Connection):
             #points[0] -> source connector should not be in the direction of source
             if Utils.get_angle_from_coordinates(points[0], (x1, y1)) == source.get_connector_direction(): points.reverse()
             #create right-angled connector
-            self.add_line((x1, y1), points[0])
-            self.add_line((x2, y2), points[0])
+            i1 = points[0]
+            self.add_line(p0, p1, i1, p2, p3)
 
     def draw(self, widget, cr):
         """
@@ -176,6 +171,7 @@ class Connection(Element, _Connection):
             Colors.CONNECTION_DISABLED_COLOR if not self.get_enabled() else
             color
         )
+        cr.set_dash([5, 5], 0.0)
         Element.draw(self, widget, cr, mod_color(self._color), mod_color(self._bg_color))
         # draw arrow on sink port
         cr.set_source_rgb(*self._arrow_color)
diff --git a/grc/gui/Element.py b/grc/gui/Element.py
index 48fdf62543..c4d5b5e47d 100644
--- a/grc/gui/Element.py
+++ b/grc/gui/Element.py
@@ -42,7 +42,7 @@ class Element(object):
         self.set_rotation(POSSIBLE_ROTATIONS[0])
         self.set_coordinate((0, 0))
         self.clear()
-        self.set_highlighted(False)
+        self.highlighted = False
         self.line_attributes = []
         """ # No idea where this is in pygobject
            0, Gdk.LINE_SOLID, Gdk.CAP_BUTT, Gdk.JOIN_MITER
@@ -102,21 +102,22 @@ class Element(object):
             bg_color: the color for the inside of the rectangle
         """
         X, Y = self.get_coordinate()
-        # TODO: gc.set_line_attributes(*self.line_attributes)
-        for (rX, rY), (W, H) in self._areas_list:
-            aX = X + rX
-            aY = Y + rY
+        cr.translate(X, Y)
+        for area in self._areas_list:
+            # aX = X + rX
+            # aY = Y + rY
             cr.set_source_rgb(*bg_color)
-            cr.rectangle(aX, aY, W, H)
+            cr.rectangle(*area)
             cr.fill()
             cr.set_source_rgb(*border_color)
-            cr.rectangle(aX, aY, W, H)
+            cr.rectangle(*area)
             cr.stroke()
 
-        for (x1, y1), (x2, y2) in self._lines_list:
-            cr.set_source_rgb(*border_color)
-            cr.move_to(X + x1, Y + y1)
-            cr.line_to(X + x2, Y + y2)
+        cr.set_source_rgb(*border_color)
+        for line in self._lines_list:
+            cr.move_to(*line[0])
+            for point in line[1:]:
+                cr.line_to(*point)
             cr.stroke()
 
     def rotate(self, rotation):
@@ -142,15 +143,6 @@ class Element(object):
         """
         self.coor = coor
 
-    # def get_parent(self):
-    #     """
-    #     Get the parent of this element.
-    #
-    #     Returns:
-    #         the parent
-    #     """
-    #     return self.parent
-
     def set_highlighted(self, highlighted):
         """
         Set the highlight status.
@@ -188,7 +180,7 @@ class Element(object):
         X, Y = self.get_coordinate()
         self.set_coordinate((X+deltaX, Y+deltaY))
 
-    def add_area(self, rel_coor, area):
+    def add_area(self, x, y, w, h):
         """
         Add an area to the area list.
         An area is actually a coordinate relative to the main coordinate
@@ -196,25 +188,17 @@ class Element(object):
         A positive width is to the right of the coordinate.
         A positive height is above the coordinate.
         The area is associated with a rotation.
-
-        Args:
-            rel_coor: (x,y) offset from this element's coordinate
-            area: (width,height) tuple
         """
-        self._areas_list.append((rel_coor, area))
+        self._areas_list.append([x, y, w, h])
 
-    def add_line(self, rel_coor1, rel_coor2):
+    def add_line(self, *points):
         """
         Add a line to the line list.
-        A line is defined by 2 relative coordinates.
+        A line is defined by 2 or more relative coordinates.
         Lines must be horizontal or vertical.
         The line is associated with a rotation.
-
-        Args:
-            rel_coor1: relative (x1,y1) tuple
-            rel_coor2: relative (x2,y2) tuple
         """
-        self._lines_list.append((rel_coor1, rel_coor2))
+        self._lines_list.append(points)
 
     def what_is_selected(self, coor, coor_m=None):
         """
@@ -239,27 +223,33 @@ class Element(object):
         if coor_m:
             x_m, y_m = [a-b for a,b in zip(coor_m, self.get_coordinate())]
             #handle rectangular areas
-            for (x1,y1), (w,h) in self._areas_list:
+            for x1, y1, w, h in self._areas_list:
                 if in_between(x1, x, x_m) and in_between(y1, y, y_m) or \
                     in_between(x1+w, x, x_m) and in_between(y1, y, y_m) or \
                     in_between(x1, x, x_m) and in_between(y1+h, y, y_m) or \
                     in_between(x1+w, x, x_m) and in_between(y1+h, y, y_m):
                     return self
             #handle horizontal or vertical lines
-            for (x1, y1), (x2, y2) in self._lines_list:
-                if in_between(x1, x, x_m) and in_between(y1, y, y_m) or \
-                    in_between(x2, x, x_m) and in_between(y2, y, y_m):
-                    return self
+            for line in self._lines_list:
+                last_point = line[0]
+                for x2, y2 in line[1:]:
+                    (x1, y1), last_point = last_point, (x2, y2)
+                    if in_between(x1, x, x_m) and in_between(y1, y, y_m) or \
+                        in_between(x2, x, x_m) and in_between(y2, y, y_m):
+                        return self
             return None
         else:
             #handle rectangular areas
-            for (x1,y1), (w,h) in self._areas_list:
+            for x1, y1, w, h in self._areas_list:
                 if in_between(x, x1, x1+w) and in_between(y, y1, y1+h): return self
             #handle horizontal or vertical lines
-            for (x1, y1), (x2, y2) in self._lines_list:
-                if x1 == x2: x1, x2 = x1-LINE_SELECT_SENSITIVITY, x2+LINE_SELECT_SENSITIVITY
-                if y1 == y2: y1, y2 = y1-LINE_SELECT_SENSITIVITY, y2+LINE_SELECT_SENSITIVITY
-                if in_between(x, x1, x2) and in_between(y, y1, y2): return self
+            for line in self._lines_list:
+                last_point = line[0]
+                for x2, y2 in line[1:]:
+                    (x1, y1), last_point = last_point, (x2, y2)
+                    if x1 == x2: x1, x2 = x1-LINE_SELECT_SENSITIVITY, x2+LINE_SELECT_SENSITIVITY
+                    if y1 == y2: y1, y2 = y1-LINE_SELECT_SENSITIVITY, y2+LINE_SELECT_SENSITIVITY
+                    if in_between(x, x1, x2) and in_between(y, y1, y2): return self
             return None
 
     def get_rotation(self):
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index 77615f13b0..dc80a4c87e 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -22,14 +22,11 @@ from __future__ import absolute_import
 import functools
 import random
 from distutils.spawn import find_executable
-from itertools import chain, count
-from operator import methodcaller
+from itertools import count
 
 import six
 from six.moves import filter
 
-from gi.repository import GObject
-
 from . import Actions, Colors, Utils, Bars, Dialogs
 from .Element import Element
 from .external_editor import ExternalEditor
@@ -67,7 +64,9 @@ class FlowGraph(Element, _Flowgraph):
         #context menu
         self._context_menu = Bars.ContextMenu()
         self.get_context_menu = lambda: self._context_menu
+        self._elements_to_draw = []
 
+        self._elements_to_draw = []
         self._external_updaters = {}
 
     def _get_unique_id(self, base_id=''):
@@ -199,6 +198,7 @@ class FlowGraph(Element, _Flowgraph):
         Args:
             clipboard: the nested data of blocks, connections
         """
+        # todo: rewrite this...
         selected = set()
         (x_min, y_min), blocks_n, connections_n = clipboard
         old_id2block = dict()
@@ -400,44 +400,58 @@ class FlowGraph(Element, _Flowgraph):
             changed = True
         return changed
 
+    def update_elements_to_draw(self):
+        hide_disabled_blocks = Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active()
+        hide_variables = Actions.TOGGLE_HIDE_VARIABLES.get_active()
+
+        def draw_order(elem):
+            return elem.highlighted, elem.is_block, elem.get_enabled()
+
+        elements = sorted(self.get_elements(), key=draw_order)
+        del self._elements_to_draw[:]
+
+        for element in elements:
+            if hide_disabled_blocks and not element.get_enabled():
+                continue  # skip hidden disabled blocks and connections
+            if hide_variables and (element.is_variable or element.is_import):
+                continue  # skip hidden disabled blocks and connections
+            self._elements_to_draw.append(element)
+
+    def _drawables(self):
+        show_comments = Actions.TOGGLE_SHOW_BLOCK_COMMENTS.get_active()
+        for element in self._elements_to_draw:
+            if element.is_block and show_comments and element.get_enabled():
+                yield element.draw_comment
+        for element in self._elements_to_draw:
+            yield element.draw
+
     def draw(self, widget, cr):
-        """
-        Draw the background and grid if enabled.
-        """
-        # draw comments first
-        if Actions.TOGGLE_SHOW_BLOCK_COMMENTS.get_active():
-            for block in self.blocks:
-                if block.get_enabled():
-                    block.draw_comment(widget, cr)
+        """Draw blocks connections comment and select rectangle"""
+        # todo: only update if required, duplicate logic in
+        self.update_elements_to_draw()
+
+        for draw_element in self._drawables():
+            cr.save()
+            draw_element(widget, cr)
+            cr.restore()
+
         # draw multi select rectangle
-        if self.mouse_pressed and (not self.get_selected_elements() or self.get_ctrl_mask()):
+        if self.mouse_pressed and (not self.selected_elements or self.get_ctrl_mask()):
             x1, y1 = self.press_coor
             x2, y2 = self.get_coordinate()
             x, y = int(min(x1, x2)), int(min(y1, y2))
             w, h = int(abs(x1 - x2)), int(abs(y1 - y2))
-            cr.set_source_rgb(*Colors.HIGHLIGHT_COLOR)
+            cr.set_source_rgba(
+                Colors.HIGHLIGHT_COLOR[0],
+                Colors.HIGHLIGHT_COLOR[1],
+                Colors.HIGHLIGHT_COLOR[2],
+                0.5,
+            )
             cr.rectangle(x, y, w, h)
             cr.fill()
-            cr.set_source_rgb(*Colors.BORDER_COLOR)
             cr.rectangle(x, y, w, h)
             cr.stroke()
 
-        # draw blocks on top of connections
-        hide_disabled_blocks = Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active()
-        hide_variables = Actions.TOGGLE_HIDE_VARIABLES.get_active()
-        blocks = sorted(self.blocks, key=methodcaller('get_enabled'))
-
-        for element in chain(self.connections, blocks):
-            if hide_disabled_blocks and not element.get_enabled():
-                continue  # skip hidden disabled blocks and connections
-            if hide_variables and (element.is_variable or element.is_import):
-                continue  # skip hidden disabled blocks and connections
-            element.draw(widget, cr)
-
-        # draw selected blocks on top of selected connections
-        for selected_element in self.get_selected_connections() + self.get_selected_blocks():
-            selected_element.draw(widget, cr)
-
     def update_selected(self):
         """
         Remove deleted elements from the selected elements list.
diff --git a/grc/gui/Port.py b/grc/gui/Port.py
index 62086c76e4..8945aa8c28 100644
--- a/grc/gui/Port.py
+++ b/grc/gui/Port.py
@@ -106,22 +106,22 @@ class Port(_Port, Element):
         if (self.is_sink and rotation == 0) or (self.is_source and rotation == 180):
             x = -W
             y = port_separation*index+offset
-            self.add_area((x, y), (W, self.H))
+            self.add_area(x, y, W, self.H)
             self._connector_coordinate = (x-1, y+self.H/2)
         elif (self.is_source and rotation == 0) or (self.is_sink and rotation == 180):
             x = self.parent.W
             y = port_separation*index+offset
-            self.add_area((x, y), (W, self.H))
+            self.add_area(x, y, W, self.H)
             self._connector_coordinate = (x+1+W, y+self.H/2)
         elif (self.is_source and rotation == 90) or (self.is_sink and rotation == 270):
             y = -W
             x = port_separation*index+offset
-            self.add_area((x, y), (self.H, W))
+            self.add_area(x, y, self.H, W)
             self._connector_coordinate = (x+self.H/2, y-1)
         elif (self.is_sink and rotation == 90) or (self.is_source and rotation == 270):
             y = self.parent.W
             x = port_separation*index+offset
-            self.add_area((x, y), (self.H, W))
+            self.add_area(x, y, self.H, W)
             self._connector_coordinate = (x+self.H/2, y+1+W)
         #the connector length
         self.connector_length = Constants.CONNECTOR_EXTENSION_MINIMAL + Constants.CONNECTOR_EXTENSION_INCREMENT * index
@@ -132,32 +132,26 @@ class Port(_Port, Element):
             name=Utils.encode(self.get_name()), font=Constants.PORT_FONT
         ))
 
-    def draw(self, widget, cr):
+    def draw(self, widget, cr, border_color, bg_color):
         """
         Draw the socket with a label.
         """
-        border_color = (
-            Colors.HIGHLIGHT_COLOR if self.is_highlighted() else
-            Colors.MISSING_BLOCK_BORDER_COLOR if self.parent.is_dummy_block else
-            Colors.BORDER_COLOR
-        )
         Element.draw(self, widget, cr, border_color, self._bg_color)
 
         if not self._areas_list or self._label_hidden():
             return  # this port is either hidden (no areas) or folded (no label)
-        X, Y = self.get_coordinate()
-        (x, y), _ = self._areas_list[0]
+
+        x, y, _, __ = self._areas_list[0]
+
         cr.set_source_rgb(*self._bg_color)
-        cr.save()
         if self.is_horizontal():
-            cr.translate(x + X + (self.W - self.w) / 2, y + Y + (self.H - self.h) / 2)
+            cr.translate(x + (self.W - self.w) / 2, y + (self.H - self.h) / 2)
         elif self.is_vertical():
-            cr.translate(x + X + (self.H - self.h) / 2, y + Y + (self.W - self.w) / 2)
+            cr.translate(x + (self.H - self.h) / 2, y + (self.W - self.w) / 2)
             cr.rotate(-90 * math.pi / 180.)
             cr.translate(-self.w, 0)
         PangoCairo.update_layout(cr, self.layout)
         PangoCairo.show_layout(cr, self.layout)
-        cr.restore()
 
     def get_connector_coordinate(self):
         """
-- 
cgit v1.2.3


From 4f29b9ae0b518bcc41038d6d300429e5d656d8e0 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Fri, 8 Jul 2016 15:18:58 +0200
Subject: grc: refactor: block state handling

---
 grc/core/Block.py        | 25 +++++++++++++------------
 grc/gui/ActionHandler.py | 23 +++++++++--------------
 grc/gui/FlowGraph.py     | 25 +++++--------------------
 3 files changed, 27 insertions(+), 46 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/core/Block.py b/grc/core/Block.py
index 743c9de99f..ec65a1a494 100644
--- a/grc/core/Block.py
+++ b/grc/core/Block.py
@@ -50,8 +50,7 @@ class Block(Element):
 
     is_block = True
 
-    # block states
-    DISABLED, ENABLED, BYPASSED = range(3)
+    STATE_LABELS = ['disabled', 'enabled', 'bypassed']
 
     def __init__(self, flow_graph, n):
         """
@@ -410,9 +409,9 @@ class Block(Element):
             DISABLED - 2
         """
         try:
-            return int(self.params['_enabled'].get_value())
+            return self.STATE_LABELS[int(self.params['_enabled'].get_value())]
         except ValueError:
-            return self.ENABLED
+            return 'enabled'
 
     @state.setter
     def state(self, value):
@@ -424,9 +423,11 @@ class Block(Element):
             BYPASSED - 1
             DISABLED - 2
         """
-        if value not in [self.ENABLED, self.BYPASSED, self.DISABLED]:
-            value = self.ENABLED
-        self.params['_enabled'].set_value(str(value))
+        try:
+            encoded = str(self.STATE_LABELS.index(value))
+        except ValueError:
+            encoded = str(self.STATE_LABELS.index('enabled'))
+        self.params['_enabled'].set_value(encoded)
 
     # Enable/Disable Aliases
     def get_enabled(self):
@@ -436,7 +437,7 @@ class Block(Element):
         Returns:
             true for enabled
         """
-        return self.state != self.DISABLED
+        return self.state != 'disabled'
 
     def set_enabled(self, enabled):
         """
@@ -449,7 +450,7 @@ class Block(Element):
             True if block changed state
         """
         old_state = self.state
-        new_state = self.ENABLED if enabled else self.DISABLED
+        new_state = 'enabled' if enabled else 'disabled'
         self.state = new_state
         return old_state != new_state
 
@@ -458,7 +459,7 @@ class Block(Element):
         """
         Check if the block is bypassed
         """
-        return self.state == self.BYPASSED
+        return self.state == 'bypassed'
 
     def set_bypassed(self):
         """
@@ -467,8 +468,8 @@ class Block(Element):
         Returns:
             True if block chagnes state
         """
-        if self.state != self.BYPASSED and self.can_bypass():
-            self.state = self.BYPASSED
+        if self.state != 'bypassed' and self.can_bypass():
+            self.state = 'bypassed'
             return True
         return False
 
diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py
index 732c48f50f..bb2f488b07 100644
--- a/grc/gui/ActionHandler.py
+++ b/grc/gui/ActionHandler.py
@@ -172,18 +172,13 @@ class ActionHandler:
         ##################################################
         # Enable/Disable
         ##################################################
-        elif action == Actions.BLOCK_ENABLE:
-            if flow_graph.enable_selected(True):
-                flow_graph_update()
-                page.state_cache.save_new_state(flow_graph.export_data())
-                page.saved = False
-        elif action == Actions.BLOCK_DISABLE:
-            if flow_graph.enable_selected(False):
-                flow_graph_update()
-                page.state_cache.save_new_state(flow_graph.export_data())
-                page.saved = False
-        elif action == Actions.BLOCK_BYPASS:
-            if flow_graph.bypass_selected():
+        elif action in (Actions.BLOCK_ENABLE, Actions.BLOCK_DISABLE, Actions.BLOCK_BYPASS):
+            changed = flow_graph.change_state_selected(new_state={
+                Actions.BLOCK_ENABLE: 'enabled',
+                Actions.BLOCK_DISABLE: 'disabled',
+                Actions.BLOCK_BYPASS: 'bypassed',
+            }[action])
+            if changed:
                 flow_graph_update()
                 page.state_cache.save_new_state(flow_graph.export_data())
                 page.saved = False
@@ -670,9 +665,9 @@ class ActionHandler:
         Actions.BLOCK_COPY.set_sensitive(bool(selected_blocks))
         Actions.BLOCK_PASTE.set_sensitive(bool(self.clipboard))
         #update enable/disable/bypass
-        can_enable = any(block.state != block.ENABLED
+        can_enable = any(block.state != 'enabled'
                          for block in selected_blocks)
-        can_disable = any(block.state != block.DISABLED
+        can_disable = any(block.state != 'disabled'
                           for block in selected_blocks)
         can_bypass_all = (
             all(block.can_bypass() for block in selected_blocks) and
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index dc80a4c87e..87bd91d880 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -273,35 +273,20 @@ class FlowGraph(Element, _Flowgraph):
         """
         return any(sb.port_controller_modify(direction) for sb in self.get_selected_blocks())
 
-    def enable_selected(self, enable):
+    def change_state_selected(self, new_state):
         """
         Enable/disable the selected blocks.
 
         Args:
-            enable: true to enable
+            new_state: a block state
 
         Returns:
             true if changed
         """
         changed = False
-        for selected_block in self.get_selected_blocks():
-            if selected_block.set_enabled(enable):
-                changed = True
-        return changed
-
-    def bypass_selected(self):
-        """
-        Bypass the selected blocks.
-
-        Args:
-            None
-        Returns:
-            true if changed
-        """
-        changed = False
-        for selected_block in self.get_selected_blocks():
-            if selected_block.set_bypassed():
-                changed = True
+        for block in self.selected_blocks():
+            changed |= block.state != new_state
+            block.state = new_state
         return changed
 
     def move_selected(self, delta_coordinate):
-- 
cgit v1.2.3


From 020878e8468d848cebe1bfe5b0dc7c9b557214f7 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Sun, 19 Jun 2016 20:39:22 -0700
Subject: grc: refactor: block states are no longer hidden params

---
 grc/core/Block.py    | 49 ++++++++++++++++++++++++++++++-------------------
 grc/gui/Block.py     | 31 +++++++++----------------------
 grc/gui/FlowGraph.py | 14 ++++++++++----
 3 files changed, 49 insertions(+), 45 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/core/Block.py b/grc/core/Block.py
index ec65a1a494..013f2cdbd8 100644
--- a/grc/core/Block.py
+++ b/grc/core/Block.py
@@ -21,6 +21,7 @@ from __future__ import absolute_import
 
 import collections
 import itertools
+import ast
 
 import six
 from six.moves import map, range
@@ -31,7 +32,7 @@ from . import utils
 
 from . Constants import (
     BLOCK_FLAG_NEED_QT_GUI, BLOCK_FLAG_NEED_WX_GUI,
-    ADVANCED_PARAM_TAB, DEFAULT_PARAM_TAB,
+    ADVANCED_PARAM_TAB,
     BLOCK_FLAG_THROTTLE, BLOCK_FLAG_DISABLE_BYPASS,
     BLOCK_FLAG_DEPRECATED,
 )
@@ -101,6 +102,8 @@ class Block(Element):
         self.sources = self._init_ports(sources_n, direction='source')
         self.sinks = self._init_ports(sinks_n, direction='sink')
 
+        self.states = {'_enabled': True}
+
         self._epy_source_hash = -1  # for epy blocks
         self._epy_reload_error = None
 
@@ -113,7 +116,6 @@ class Block(Element):
 
     def _init_params(self, params_n, has_sources, has_sinks):
         self._add_param(key='id', name='ID', type='id')
-        self._add_param(key='_enabled', name='Enabled', value='True', type='raw', hide='all')
 
         # Virtual source/sink and pad source/sink blocks are
         # indistinguishable from normal GR blocks. Make explicit
@@ -409,7 +411,7 @@ class Block(Element):
             DISABLED - 2
         """
         try:
-            return self.STATE_LABELS[int(self.params['_enabled'].get_value())]
+            return self.STATE_LABELS[int(self.states['_enabled'])]
         except ValueError:
             return 'enabled'
 
@@ -424,10 +426,10 @@ class Block(Element):
             DISABLED - 2
         """
         try:
-            encoded = str(self.STATE_LABELS.index(value))
+            encoded = self.STATE_LABELS.index(value)
         except ValueError:
-            encoded = str(self.STATE_LABELS.index('enabled'))
-        self.params['_enabled'].set_value(encoded)
+            encoded = 1
+        self.states['_enabled'] = encoded
 
     # Enable/Disable Aliases
     def get_enabled(self):
@@ -643,11 +645,16 @@ class Block(Element):
         """
         n = collections.OrderedDict()
         n['key'] = self.key
-        n['param'] = [param.export_data() for _, param in sorted(six.iteritems(self.params))]
-        if 'bus' in [a.get_type() for a in self.sinks]:
-            n['bus_sink'] = str(1)
-        if 'bus' in [a.get_type() for a in self.sources]:
-            n['bus_source'] = str(1)
+
+        params = (param.export_data() for param in six.itervalues(self.params))
+        states = (collections.OrderedDict([('key', key), ('value', repr(value))])
+                  for key, value in six.iteritems(self.states))
+        n['param'] = sorted(itertools.chain(states, params), key=lambda p: p['key'])
+
+        if any('bus' in a.get_type() for a in self.sinks):
+            n['bus_sink'] = '1'
+        if any('bus' in a.get_type() for a in self.sources):
+            n['bus_source'] = '1'
         return n
 
     def import_data(self, n):
@@ -662,22 +669,26 @@ class Block(Element):
         Args:
             n: the nested data odict
         """
-        params_n = n.get('param', [])
+        param_data = {p['key']: p['value'] for p in n.get('param', [])}
+
+        for key in self.states:
+            try:
+                self.states[key] = ast.literal_eval(param_data.pop(key))
+            except (KeyError, SyntaxError, ValueError):
+                pass
 
         def get_hash():
-            return hash(tuple(map(hash, self.params.values())))
+            return hash(tuple(hash(v) for v in self.params.values()))
 
-        my_hash = 0
-        while get_hash() != my_hash:
-            for param_n in params_n:
-                key = param_n['key']
-                value = param_n['value']
+        pre_rewrite_hash = -1
+        while pre_rewrite_hash != get_hash():
+            for key, value in six.iteritems(param_data):
                 try:
                     self.params[key].set_value(value)
                 except KeyError:
                     continue
             # Store hash and call rewrite
-            my_hash = get_hash()
+            pre_rewrite_hash = get_hash()
             self.rewrite()
 
         self._import_bus_stuff(n)
diff --git a/grc/gui/Block.py b/grc/gui/Block.py
index a38e4cc59a..94887eeb89 100644
--- a/grc/gui/Block.py
+++ b/grc/gui/Block.py
@@ -45,13 +45,11 @@ class Block(Element, _Block):
         Add graphics related params to the block.
         """
         _Block.__init__(self, flow_graph, n)
-        self.W = self.H = 0
-        self._add_param(key='_coordinate', name='GUI Coordinate', value='(0, 0)',
-                        hide='all')
-        self._add_param(key='_rotation', name='GUI Rotation', value='0',
-                        hide='all')
 
-        Element.__init__(self)  # needs the params
+        self.states.update(_coordinate=(0, 0), _rotation=0)
+        Element.__init__(self)  # needs the states
+
+        self.W = self.H = 0
         self._param_layouts = []
         self._comment_layout = None
         self._bg_color = Colors.BLOCK_ENABLED_COLOR
@@ -64,13 +62,7 @@ class Block(Element, _Block):
         Returns:
             the coordinate tuple (x, y) or (0, 0) if failure
         """
-        try:
-            coor = self.params['_coordinate'].get_value()  # should evaluate to tuple
-            coor = tuple(int(x) for x in coor[1:-1].split(','))
-        except:
-            coor = 0, 0
-            self.set_coordinate(coor)
-        return coor
+        return self.states['_coordinate']
 
     def set_coordinate(self, coor):
         """
@@ -85,7 +77,7 @@ class Block(Element, _Block):
                 Utils.align_to_grid(coor[0] + offset_x) - offset_x,
                 Utils.align_to_grid(coor[1] + offset_y) - offset_y
             )
-        self.get_param('_coordinate').set_value(repr(coor))
+        self.states['_coordinate'] = coor
 
     def get_rotation(self):
         """
@@ -94,21 +86,16 @@ class Block(Element, _Block):
         Returns:
             the rotation in degrees or 0 if failure
         """
-        try: #should evaluate to dict
-            rotation = int(self.get_param('_rotation').get_value())
-        except:
-            rotation = POSSIBLE_ROTATIONS[0]
-            self.set_rotation(rotation)
-        return rotation
+        return self.states['_rotation']
 
     def set_rotation(self, rot):
         """
         Set the rotation into the position param.
-
+q
         Args:
             rot: the rotation in degrees
         """
-        self.get_param('_rotation').set_value(repr(int(rot)))
+        self.states['_rotation'] = rot
 
     def create_shapes(self):
         """Update the block, parameters, and ports when a change occurs."""
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index 87bd91d880..d592242e2e 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -20,6 +20,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 from __future__ import absolute_import
 
 import functools
+import ast
 import random
 from distutils.spawn import find_executable
 from itertools import count
@@ -219,12 +220,17 @@ class FlowGraph(Element, _Flowgraph):
                 continue  # unknown block was pasted (e.g. dummy block)
             selected.add(block)
             #set params
-            params = {n['key']: n['value'] for n in block_n.get('param', [])}
+            param_data = {n['key']: n['value'] for n in block_n.get('param', [])}
+            for key in block.states:
+                try:
+                    block.states[key] = ast.literal_eval(param_data.pop(key))
+                except (KeyError, SyntaxError, ValueError):
+                    pass
             if block_key == 'epy_block':
-                block.get_param('_io_cache').set_value(params.pop('_io_cache'))
-                block.get_param('_source_code').set_value(params.pop('_source_code'))
+                block.get_param('_io_cache').set_value(param_data.pop('_io_cache'))
+                block.get_param('_source_code').set_value(param_data.pop('_source_code'))
                 block.rewrite()  # this creates the other params
-            for param_key, param_value in six.iteritems(params):
+            for param_key, param_value in six.iteritems(param_data):
                 #setup id parameter
                 if param_key == 'id':
                     old_id2block[param_value] = block
-- 
cgit v1.2.3


From c2efab9967bfc0b3cf47836eb53bc4ece23b7641 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Fri, 8 Jul 2016 15:34:48 +0200
Subject: grc: refactor: selected blocks handling

---
 grc/gui/ActionHandler.py |  23 ++--
 grc/gui/FlowGraph.py     | 335 +++++++++++++++++++++++------------------------
 2 files changed, 171 insertions(+), 187 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py
index bb2f488b07..16fed00db5 100644
--- a/grc/gui/ActionHandler.py
+++ b/grc/gui/ActionHandler.py
@@ -202,7 +202,7 @@ class ActionHandler:
         elif action == Actions.BLOCK_CREATE_HIER:
 
                         # keeping track of coordinates for pasting later
-                        coords = flow_graph.get_selected_blocks()[0].get_coordinate()
+                        coords = flow_graph.selected_blocks()[0].get_coordinate()
                         x,y = coords
                         x_min = x
                         y_min = y
@@ -211,7 +211,7 @@ class ActionHandler:
                         params = [];
 
                         # Save the state of the leaf blocks
-                        for block in flow_graph.get_selected_blocks():
+                        for block in flow_graph.selected_blocks():
 
                             # Check for string variables within the blocks
                             for param in block.params.values():
@@ -239,10 +239,10 @@ class ActionHandler:
                                 sink_id = connection.sink_block.get_id()
 
                                 # If connected block is not in the list of selected blocks create a pad for it
-                                if flow_graph.get_block(source_id) not in flow_graph.get_selected_blocks():
+                                if flow_graph.get_block(source_id) not in flow_graph.selected_blocks():
                                     pads.append({'key': connection.sink_port.key, 'coord': connection.source_port.get_coordinate(), 'block_id' : block.get_id(), 'direction': 'source'})
 
-                                if flow_graph.get_block(sink_id) not in flow_graph.get_selected_blocks():
+                                if flow_graph.get_block(sink_id) not in flow_graph.selected_blocks():
                                     pads.append({'key': connection.source_port.key, 'coord': connection.sink_port.get_coordinate(), 'block_id' : block.get_id(), 'direction': 'sink'})
 
 
@@ -451,10 +451,7 @@ class ActionHandler:
         # Param Modifications
         ##################################################
         elif action == Actions.BLOCK_PARAM_MODIFY:
-            if action.args:
-                selected_block = action.args[0]
-            else:
-                selected_block = flow_graph.get_selected_block()
+            selected_block = action.args[0] if action.args else flow_graph.selected_block
             if selected_block:
                 self.dialog = PropsDialog(self.main_window, selected_block)
                 response = Gtk.ResponseType.APPLY
@@ -616,12 +613,12 @@ class ActionHandler:
             main.btwin.search_entry.show()
             main.btwin.search_entry.grab_focus()
         elif action == Actions.OPEN_HIER:
-            for b in flow_graph.get_selected_blocks():
+            for b in flow_graph.selected_blocks():
                 if b._grc_source:
                     main.new_page(b._grc_source, show=True)
         elif action == Actions.BUSSIFY_SOURCES:
             n = {'name':'bus', 'type':'bus'}
-            for b in flow_graph.get_selected_blocks():
+            for b in flow_graph.selected_blocks():
                 b.bussify(n, 'source')
             flow_graph._old_selected_port = None
             flow_graph._new_selected_port = None
@@ -629,7 +626,7 @@ class ActionHandler:
 
         elif action == Actions.BUSSIFY_SINKS:
             n = {'name':'bus', 'type':'bus'}
-            for b in flow_graph.get_selected_blocks():
+            for b in flow_graph.selected_blocks():
                 b.bussify(n, 'sink')
             flow_graph._old_selected_port = None
             flow_graph._new_selected_port = None
@@ -647,12 +644,12 @@ class ActionHandler:
         page = main.current_page  # page and flow graph might have changed
         flow_graph = page.flow_graph if page else None
 
-        selected_blocks = flow_graph.get_selected_blocks()
+        selected_blocks = list(flow_graph.selected_blocks())
         selected_block = selected_blocks[0] if selected_blocks else None
 
         #update general buttons
         Actions.ERRORS_WINDOW_DISPLAY.set_sensitive(not flow_graph.is_valid())
-        Actions.ELEMENT_DELETE.set_sensitive(bool(flow_graph.get_selected_elements()))
+        Actions.ELEMENT_DELETE.set_sensitive(bool(flow_graph.selected_elements))
         Actions.BLOCK_PARAM_MODIFY.set_sensitive(bool(selected_block))
         Actions.BLOCK_ROTATE_CCW.set_sensitive(bool(selected_blocks))
         Actions.BLOCK_ROTATE_CW.set_sensitive(bool(selected_blocks))
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index d592242e2e..7a0f2475ff 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -50,22 +50,19 @@ class FlowGraph(Element, _Flowgraph):
         """
         Element.__init__(self)
         _Flowgraph.__init__(self, **kwargs)
-        #when is the flow graph selected? (used by keyboard event handler)
-        self.is_selected = lambda: bool(self.get_selected_elements())
-        #important vars dealing with mouse event tracking
+        # important vars dealing with mouse event tracking
         self.element_moved = False
         self.mouse_pressed = False
-        self._selected_elements = []
         self.press_coor = (0, 0)
-        #selected ports
+        # selected
+        self.selected_elements = set()
         self._old_selected_port = None
         self._new_selected_port = None
         # current mouse hover element
         self.element_under_mouse = None
-        #context menu
+        # context menu
         self._context_menu = Bars.ContextMenu()
         self.get_context_menu = lambda: self._context_menu
-        self._elements_to_draw = []
 
         self._elements_to_draw = []
         self._external_updaters = {}
@@ -125,17 +122,6 @@ class FlowGraph(Element, _Flowgraph):
             return
         Actions.EXTERNAL_UPDATE()
 
-
-    ###########################################################################
-    # Access Drawing Area
-    ###########################################################################
-    def get_drawing_area(self): return self.drawing_area
-    def queue_draw(self): self.get_drawing_area().queue_draw()
-    def get_scroll_pane(self): return self.drawing_area.get_parent().get_parent()
-    def get_ctrl_mask(self): return self.drawing_area.ctrl_mask
-    def get_mod1_mask(self): return self.drawing_area.mod1_mask
-    def new_pixmap(self, *args): return self.get_drawing_area().new_pixmap(*args)
-
     def add_new_block(self, key, coor=None):
         """
         Add a block of the given key to this flow graph.
@@ -160,6 +146,55 @@ class FlowGraph(Element, _Flowgraph):
         Actions.ELEMENT_CREATE()
         return id
 
+    def make_connection(self):
+        """this selection and the last were ports, try to connect them"""
+        if self._old_selected_port and self._new_selected_port:
+            try:
+                self.connect(self._old_selected_port, self._new_selected_port)
+                Actions.ELEMENT_CREATE()
+            except:
+                Messages.send_fail_connection()
+            self._old_selected_port = None
+            self._new_selected_port = None
+            return True
+        return False
+
+    def update(self):
+        """
+        Call the top level rewrite and validate.
+        Call the top level create labels and shapes.
+        """
+        self.rewrite()
+        self.validate()
+        self.create_labels()
+        self.create_shapes()
+
+    def reload(self):
+        """
+        Reload flow-graph (with updated blocks)
+
+        Args:
+            page: the page to reload (None means current)
+        Returns:
+            False if some error occurred during import
+        """
+        success = False
+        data = self.export_data()
+        if data:
+            self.unselect()
+            success = self.import_data(data)
+            self.update()
+        return success
+
+    ###########################################################################
+    # Access Drawing Area
+    ###########################################################################
+    def get_drawing_area(self): return self.drawing_area
+    def queue_draw(self): self.get_drawing_area().queue_draw()
+    def get_scroll_pane(self): return self.drawing_area.get_parent().get_parent()
+    def get_ctrl_mask(self): return self.drawing_area.ctrl_mask
+    def get_mod1_mask(self): return self.drawing_area.mod1_mask
+
     ###########################################################################
     # Copy Paste
     ###########################################################################
@@ -171,7 +206,7 @@ class FlowGraph(Element, _Flowgraph):
             the clipboard
         """
         #get selected blocks
-        blocks = self.get_selected_blocks()
+        blocks = list(self.selected_blocks())
         if not blocks:
             return None
         #calc x and y min
@@ -249,8 +284,9 @@ class FlowGraph(Element, _Flowgraph):
             sink = old_id2block[connection_n.get('sink_block_id')].get_sink(connection_n.get('sink_key'))
             self.connect(source, sink)
         #set all pasted elements selected
-        for block in selected: selected = selected.union(set(block.get_connections()))
-        self._selected_elements = list(selected)
+        for block in selected:
+            selected = selected.union(set(block.get_connections()))
+        self.selected_elements = set(selected)
 
     ###########################################################################
     # Modify Selected
@@ -265,7 +301,7 @@ class FlowGraph(Element, _Flowgraph):
         Returns:
             true for change
         """
-        return any(sb.type_controller_modify(direction) for sb in self.get_selected_blocks())
+        return any(sb.type_controller_modify(direction) for sb in self.selected_blocks())
 
     def port_controller_modify_selected(self, direction):
         """
@@ -277,7 +313,7 @@ class FlowGraph(Element, _Flowgraph):
         Returns:
             true for changed
         """
-        return any(sb.port_controller_modify(direction) for sb in self.get_selected_blocks())
+        return any(sb.port_controller_modify(direction) for sb in self.selected_blocks())
 
     def change_state_selected(self, new_state):
         """
@@ -302,7 +338,7 @@ class FlowGraph(Element, _Flowgraph):
         Args:
             delta_coordinate: the change in coordinates
         """
-        for selected_block in self.get_selected_blocks():
+        for selected_block in self.selected_blocks():
             selected_block.move(delta_coordinate)
             self.element_moved = True
 
@@ -316,7 +352,7 @@ class FlowGraph(Element, _Flowgraph):
         Returns:
             True if changed, otherwise False
         """
-        blocks = self.get_selected_blocks()
+        blocks = list(self.selected_blocks())
         if calling_action is None or not blocks:
             return False
 
@@ -357,13 +393,13 @@ class FlowGraph(Element, _Flowgraph):
         Returns:
             true if changed, otherwise false.
         """
-        if not self.get_selected_blocks():
+        if not any(self.selected_blocks()):
             return False
         #initialize min and max coordinates
-        min_x, min_y = self.get_selected_block().get_coordinate()
-        max_x, max_y = self.get_selected_block().get_coordinate()
+        min_x, min_y = self.selected_block.get_coordinate()
+        max_x, max_y = self.selected_block.get_coordinate()
         #rotate each selected block, and find min/max coordinate
-        for selected_block in self.get_selected_blocks():
+        for selected_block in self.selected_blocks():
             selected_block.rotate(rotation)
             #update the min/max coordinate
             x, y = selected_block.get_coordinate()
@@ -372,7 +408,7 @@ class FlowGraph(Element, _Flowgraph):
         #calculate center point of slected blocks
         ctr_x, ctr_y = (max_x + min_x)/2, (max_y + min_y)/2
         #rotate the blocks around the center point
-        for selected_block in self.get_selected_blocks():
+        for selected_block in self.selected_blocks():
             x, y = selected_block.get_coordinate()
             x, y = Utils.get_rotated_coordinate((x - ctr_x, y - ctr_y), rotation)
             selected_block.set_coordinate((x + ctr_x, y + ctr_y))
@@ -386,11 +422,35 @@ class FlowGraph(Element, _Flowgraph):
             true if changed.
         """
         changed = False
-        for selected_element in self.get_selected_elements():
+        for selected_element in self.selected_elements:
             self.remove_element(selected_element)
             changed = True
         return changed
 
+    def update_selected(self):
+        """
+        Remove deleted elements from the selected elements list.
+        Update highlighting so only the selected are highlighted.
+        """
+        selected_elements = self.selected_elements
+        elements = self.get_elements()
+        # remove deleted elements
+        for selected in selected_elements:
+            if selected in elements:
+                continue
+            selected_elements.remove(selected)
+        if self._old_selected_port and self._old_selected_port.parent not in elements:
+            self._old_selected_port = None
+        if self._new_selected_port and self._new_selected_port.parent not in elements:
+            self._new_selected_port = None
+        # update highlighting
+        for element in elements:
+            element.highlighted = element in selected_elements
+
+    ###########################################################################
+    # Draw stuff
+    ###########################################################################
+
     def update_elements_to_draw(self):
         hide_disabled_blocks = Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active()
         hide_variables = Actions.TOGGLE_HIDE_VARIABLES.get_active()
@@ -443,64 +503,54 @@ class FlowGraph(Element, _Flowgraph):
             cr.rectangle(x, y, w, h)
             cr.stroke()
 
-    def update_selected(self):
+    ##########################################################################
+    # selection handling
+    ##########################################################################
+    def update_selected_elements(self):
         """
-        Remove deleted elements from the selected elements list.
-        Update highlighting so only the selected are highlighted.
+        Update the selected elements.
+        The update behavior depends on the state of the mouse button.
+        When the mouse button pressed the selection will change when
+        the control mask is set or the new selection is not in the current group.
+        When the mouse button is released the selection will change when
+        the mouse has moved and the control mask is set or the current group is empty.
+        Attempt to make a new connection if the old and ports are filled.
+        If the control mask is set, merge with the current elements.
         """
-        selected_elements = self.get_selected_elements()
-        elements = self.get_elements()
-        #remove deleted elements
-        for selected in selected_elements:
-            if selected in elements: continue
-            selected_elements.remove(selected)
-        if self._old_selected_port and self._old_selected_port.parent not in elements:
-            self._old_selected_port = None
-        if self._new_selected_port and self._new_selected_port.parent not in elements:
-            self._new_selected_port = None
-        #update highlighting
-        for element in elements:
-            element.set_highlighted(element in selected_elements)
+        selected_elements = None
+        if self.mouse_pressed:
+            new_selections = self.what_is_selected(self.get_coordinate())
+            # update the selections if the new selection is not in the current selections
+            # allows us to move entire selected groups of elements
+            if self.get_ctrl_mask() or new_selections not in self.selected_elements:
+                selected_elements = new_selections
 
-    def update(self):
-        """
-        Call the top level rewrite and validate.
-        Call the top level create labels and shapes.
-        """
-        self.rewrite()
-        self.validate()
-        self.create_labels()
-        self.create_shapes()
+            if self._old_selected_port:
+                self._old_selected_port.force_label_unhidden(False)
+                self.create_shapes()
+                self.queue_draw()
+            elif self._new_selected_port:
+                self._new_selected_port.force_label_unhidden()
 
-    def reload(self):
-        """
-        Reload flow-graph (with updated blocks)
+        else:  # called from a mouse release
+            if not self.element_moved and (not self.selected_elements or self.get_ctrl_mask()):
+                selected_elements = self.what_is_selected(self.get_coordinate(), self.press_coor)
 
-        Args:
-            page: the page to reload (None means current)
-        Returns:
-            False if some error occurred during import
-        """
-        success = False
-        data = self.export_data()
-        if data:
-            self.unselect()
-            success = self.import_data(data)
-            self.update()
-        return success
+        # this selection and the last were ports, try to connect them
+        if self.make_connection():
+            return
 
-    ##########################################################################
-    ## Get Selected
-    ##########################################################################
-    def unselect(self):
-        """
-        Set selected elements to an empty set.
-        """
-        self._selected_elements = []
+        # update selected elements
+        if selected_elements is None:
+            return
 
-    def select_all(self):
-        """Select all blocks in the flow graph"""
-        self._selected_elements = list(self.get_elements())
+        # if ctrl, set the selected elements to the union - intersection of old and new
+        if self.get_ctrl_mask():
+            self.selected_elements ^= selected_elements
+        else:
+            self.selected_elements.clear()
+            self.selected_elements.update(selected_elements)
+        Actions.ELEMENT_SELECT()
 
     def what_is_selected(self, coor, coor_m=None):
         """
@@ -520,68 +570,55 @@ class FlowGraph(Element, _Flowgraph):
         """
         selected_port = None
         selected = set()
-        #check the elements
-        hide_disabled_blocks = Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active()
-        hide_variables = Actions.TOGGLE_HIDE_VARIABLES.get_active()
-        for element in reversed(self.get_elements()):
-            if hide_disabled_blocks and not element.get_enabled():
-                continue  # skip hidden disabled blocks and connections
-            if hide_variables and (element.is_variable or element.is_import):
-                continue  # skip hidden disabled blocks and connections
+        # check the elements
+        for element in reversed(self._elements_to_draw):
             selected_element = element.what_is_selected(coor, coor_m)
             if not selected_element:
                 continue
-            #update the selected port information
+            # update the selected port information
             if selected_element.is_port:
-                if not coor_m: selected_port = selected_element
+                if not coor_m:
+                    selected_port = selected_element
                 selected_element = selected_element.parent_block
+
             selected.add(selected_element)
-            #place at the end of the list
-            self.get_elements().remove(element)
-            self.get_elements().append(element)
-            #single select mode, break
-            if not coor_m: break
-        #update selected ports
+            if not coor_m:
+                break
+        # update selected ports
         if selected_port is not self._new_selected_port:
             self._old_selected_port = self._new_selected_port
             self._new_selected_port = selected_port
-        return list(selected)
+        return selected
 
-    def get_selected_connections(self):
+    def unselect(self):
         """
-        Get a group of selected connections.
-
-        Returns:
-            sub set of connections in this flow graph
+        Set selected elements to an empty set.
         """
-        selected = set()
-        for selected_element in self.get_selected_elements():
-            if selected_element.is_connection:
-                selected.add(selected_element)
-        return list(selected)
+        self.selected_elements.clear()
+
+    def select_all(self):
+        """Select all blocks in the flow graph"""
+        self.selected_elements.clear()
+        self.selected_elements.update(self._elements_to_draw)
 
-    def get_selected_blocks(self):
+    def selected_blocks(self):
         """
         Get a group of selected blocks.
 
         Returns:
             sub set of blocks in this flow graph
         """
-        selected = set()
-        for selected_element in self.get_selected_elements():
-            if selected_element.is_block:
-                selected.add(selected_element)
-        return list(selected)
+        return (e for e in self.selected_elements if e.is_block)
 
-    def get_selected_block(self):
+    @property
+    def selected_block(self):
         """
         Get the selected block when a block or port is selected.
 
         Returns:
             a block or None
         """
-        selected_blocks = self.get_selected_blocks()
-        return selected_blocks[0] if selected_blocks else None
+        return next(self.selected_blocks(), None)
 
     def get_selected_elements(self):
         """
@@ -590,7 +627,7 @@ class FlowGraph(Element, _Flowgraph):
         Returns:
             sub set of elements in this flow graph
         """
-        return self._selected_elements
+        return self.selected_elements
 
     def get_selected_element(self):
         """
@@ -599,60 +636,10 @@ class FlowGraph(Element, _Flowgraph):
         Returns:
             a block, port, or connection or None
         """
-        selected_elements = self.get_selected_elements()
-        return selected_elements[0] if selected_elements else None
-
-    def update_selected_elements(self):
-        """
-        Update the selected elements.
-        The update behavior depends on the state of the mouse button.
-        When the mouse button pressed the selection will change when
-        the control mask is set or the new selection is not in the current group.
-        When the mouse button is released the selection will change when
-        the mouse has moved and the control mask is set or the current group is empty.
-        Attempt to make a new connection if the old and ports are filled.
-        If the control mask is set, merge with the current elements.
-        """
-        selected_elements = None
-        if self.mouse_pressed:
-            new_selections = self.what_is_selected(self.get_coordinate())
-            #update the selections if the new selection is not in the current selections
-            #allows us to move entire selected groups of elements
-            if self.get_ctrl_mask() or not (
-                new_selections and new_selections[0] in self.get_selected_elements()
-            ): selected_elements = new_selections
-            if self._old_selected_port:
-                self._old_selected_port.force_label_unhidden(False)
-                self.create_shapes()
-                self.queue_draw()
-            elif self._new_selected_port:
-                self._new_selected_port.force_label_unhidden()
-        else:  # called from a mouse release
-            if not self.element_moved and (not self.get_selected_elements() or self.get_ctrl_mask()):
-                selected_elements = self.what_is_selected(self.get_coordinate(), self.press_coor)
-        #this selection and the last were ports, try to connect them
-        if self._old_selected_port and self._new_selected_port:
-            try:
-                self.connect(self._old_selected_port, self._new_selected_port)
-                Actions.ELEMENT_CREATE()
-            except: Messages.send_fail_connection()
-            self._old_selected_port = None
-            self._new_selected_port = None
-            return
-        #update selected elements
-        if selected_elements is None: return
-        old_elements = set(self.get_selected_elements())
-        self._selected_elements = list(set(selected_elements))
-        new_elements = set(self.get_selected_elements())
-        #if ctrl, set the selected elements to the union - intersection of old and new
-        if self.get_ctrl_mask():
-            self._selected_elements = list(
-                set.union(old_elements, new_elements) - set.intersection(old_elements, new_elements)
-            )
-        Actions.ELEMENT_SELECT()
+        return next(iter(self.selected_elements), None)
 
     ##########################################################################
-    ## Event Handlers
+    # Event Handlers
     ##########################################################################
     def handle_mouse_context_press(self, coordinate, event):
         """
@@ -661,7 +648,7 @@ class FlowGraph(Element, _Flowgraph):
         Then, show the context menu at the mouse click location.
         """
         selections = self.what_is_selected(coordinate)
-        if not set(selections).intersection(self.get_selected_elements()):
+        if not selections.intersection(self.selected_elements):
             self.set_coordinate(coordinate)
             self.mouse_pressed = True
             self.update_selected_elements()
@@ -683,7 +670,7 @@ class FlowGraph(Element, _Flowgraph):
             self.unselect()
         self.update_selected_elements()
 
-        if double_click and self.get_selected_block():
+        if double_click and self.selected_block:
             self.mouse_pressed = False
             Actions.BLOCK_PARAM_MODIFY()
 
@@ -739,7 +726,7 @@ class FlowGraph(Element, _Flowgraph):
 
     def _handle_mouse_motion_drag(self, coordinate):
         # remove the connection if selected in drag event
-        if len(self.get_selected_elements()) == 1 and self.get_selected_element().is_connection:
+        if len(self.selected_elements) == 1 and self.get_selected_element().is_connection:
             Actions.ELEMENT_DELETE()
 
         # move the selected elements and record the new coordinate
-- 
cgit v1.2.3


From b479f54903c0b1b164393af2e844723aed8a1072 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Tue, 12 Jul 2016 17:43:11 +0200
Subject: grc: refactor: fixup selection code and core connection changes

---
 grc/core/Port.py                | 6 +++---
 grc/core/generator/Generator.py | 2 +-
 grc/gui/FlowGraph.py            | 4 +++-
 3 files changed, 7 insertions(+), 5 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/core/Port.py b/grc/core/Port.py
index 10d021b315..ef6a92c7b1 100644
--- a/grc/core/Port.py
+++ b/grc/core/Port.py
@@ -21,7 +21,7 @@ from __future__ import absolute_import
 
 from six.moves import filter
 
-from .Element import Element
+from .Element import Element, lazy_property
 
 from . import Constants
 
@@ -323,11 +323,11 @@ class Port(Element):
             number = str(busses.index(self)) + '#' + str(len(self.get_associated_ports()))
         return self._name + number
 
-    @property
+    @lazy_property
     def is_sink(self):
         return self._dir == 'sink'
 
-    @property
+    @lazy_property
     def is_source(self):
         return self._dir == 'source'
 
diff --git a/grc/core/generator/Generator.py b/grc/core/generator/Generator.py
index e3f6bf38c9..637cf9e9df 100644
--- a/grc/core/generator/Generator.py
+++ b/grc/core/generator/Generator.py
@@ -173,7 +173,7 @@ class TopBlockGenerator(object):
         # Get the virtual blocks and resolve their connections
         virtual = [c for c in connections if c.source_block.is_virtual_source()]
         for connection in virtual:
-            source = connection.source.resolve_virtual_source()
+            source = connection.source_port.resolve_virtual_source()
             sink = connection.sink_port
             resolved = fg.parent.Connection(flow_graph=fg, porta=source, portb=sink)
             connections.append(resolved)
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index 7a0f2475ff..7176594460 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -28,6 +28,8 @@ from itertools import count
 import six
 from six.moves import filter
 
+from gi.repository import GObject
+
 from . import Actions, Colors, Utils, Bars, Dialogs
 from .Element import Element
 from .external_editor import ExternalEditor
@@ -435,7 +437,7 @@ class FlowGraph(Element, _Flowgraph):
         selected_elements = self.selected_elements
         elements = self.get_elements()
         # remove deleted elements
-        for selected in selected_elements:
+        for selected in list(selected_elements):
             if selected in elements:
                 continue
             selected_elements.remove(selected)
-- 
cgit v1.2.3


From cf71e261f1438d51adf857fd8b926cfdda0a51ab Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Wed, 13 Jul 2016 10:31:12 +0200
Subject: grc: gtk3: update gui element class

---
 grc/core/Element.py   |  7 ++++
 grc/core/FlowGraph.py |  7 ++--
 grc/gui/Block.py      | 46 +++++++++++++------------
 grc/gui/Connection.py | 32 +++++++++---------
 grc/gui/Element.py    | 94 ++++++++++-----------------------------------------
 grc/gui/FlowGraph.py  | 11 +++---
 grc/gui/Port.py       | 80 ++++++++++++++++++-------------------------
 7 files changed, 107 insertions(+), 170 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/core/Element.py b/grc/core/Element.py
index f07bb113e1..cd0514beb6 100644
--- a/grc/core/Element.py
+++ b/grc/core/Element.py
@@ -34,6 +34,13 @@ class lazy_property(object):
         return weak_value
 
 
+def property_nop_write(func):
+    """Make this a property with a nop setter"""
+    def nop(self, value):
+        pass
+    return property(fget=func, fset=nop)
+
+
 class Element(object):
 
     def __init__(self, parent=None):
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index 16bcb3b9f6..c626f7b0cd 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -549,10 +549,11 @@ def _initialize_dummy_block(block, block_n):
     block.is_valid = lambda: False
     block.get_enabled = lambda: False
     for param_n in block_n.get('param', []):
-        if param_n['key'] not in block.params:
-            new_param_n = {'key': param_n['key'], 'name': param_n['key'], 'type': 'string'}
+        key = param_n['key']
+        if key not in block.params:
+            new_param_n = {'key': key, 'name': key, 'type': 'string'}
             param = block.parent_platform.Param(block=block, n=new_param_n)
-            block.params.append(param)
+            block.params[key] = param
 
 
 def _dummy_block_add_port(block, key, dir):
diff --git a/grc/gui/Block.py b/grc/gui/Block.py
index 7321a0495b..e0899136f3 100644
--- a/grc/gui/Block.py
+++ b/grc/gui/Block.py
@@ -47,7 +47,7 @@ class Block(_Block, Element):
         _Block.__init__(self, flow_graph, n)
 
         self.states.update(_coordinate=(0, 0), _rotation=0)
-        self.W = self.H = 0
+        self.width = self.height = 0
         Element.__init__(self)  # needs the states and initial sizes
 
         self._surface_layouts = [
@@ -77,14 +77,15 @@ class Block(_Block, Element):
             coor: the coordinate tuple (x, y)
         """
         if Actions.TOGGLE_SNAP_TO_GRID.get_active():
-            offset_x, offset_y = (0, self.H/2) if self.is_horizontal() else (self.H/2, 0)
+            offset_x, offset_y = (0, self.height / 2) if self.is_horizontal() else (self.height / 2, 0)
             coor = (
                 Utils.align_to_grid(coor[0] + offset_x) - offset_x,
                 Utils.align_to_grid(coor[1] + offset_y) - offset_y
             )
         self.states['_coordinate'] = coor
 
-    def get_rotation(self):
+    @property
+    def rotation(self):
         """
         Get the rotation from the position param.
 
@@ -93,7 +94,8 @@ class Block(_Block, Element):
         """
         return self.states['_rotation']
 
-    def set_rotation(self, rot):
+    @rotation.setter
+    def rotation(self, rot):
         """
         Set the rotation into the position param.
 
@@ -107,25 +109,25 @@ class Block(_Block, Element):
         self.clear()
 
         if self.is_horizontal():
-            self.add_area(0, 0, self.W, self.H)
+            self.areas.append([0, 0, self.width, self.height])
         elif self.is_vertical():
-            self.add_area(0, 0, self.H, self.W)
+            self.areas.append([0, 0, self.height, self.width])
 
         for ports, has_busses in zip((self.active_sources, self.active_sinks), self.has_busses):
             if not ports:
                 continue
-            port_separation = PORT_SEPARATION if not has_busses else ports[0].H + PORT_SPACING
-            offset = (self.H - (len(ports) - 1) * port_separation - ports[0].H) / 2
+            port_separation = PORT_SEPARATION if not has_busses else ports[0].height + PORT_SPACING
+            offset = (self.height - (len(ports) - 1) * port_separation - ports[0].height) / 2
             for index, port in enumerate(ports):
                 port.create_shapes()
 
                 port.set_coordinate({
-                    0: (+self.W, offset),
-                    90: (offset, -port.W),
-                    180: (-port.W, offset),
-                    270: (offset, +self.W),
+                    0: (+self.width, offset),
+                    90: (offset, -port.width),
+                    180: (-port.width, offset),
+                    270: (offset, +self.width),
                 }[port.get_connector_direction()])
-                offset += PORT_SEPARATION if not has_busses else port.H + PORT_SPACING
+                offset += PORT_SEPARATION if not has_busses else port.height + PORT_SPACING
 
                 port.connector_length = Constants.CONNECTOR_EXTENSION_MINIMAL + \
                                         Constants.CONNECTOR_EXTENSION_INCREMENT * index
@@ -176,7 +178,7 @@ class Block(_Block, Element):
         def get_min_height_for_ports():
             min_height = 2 * PORT_BORDER_SEPARATION + len(ports) * PORT_SEPARATION
             if ports:
-                min_height -= ports[-1].H
+                min_height -= ports[-1].height
             return min_height
         height = max(
             [  # labels
@@ -187,11 +189,11 @@ class Block(_Block, Element):
             ] +
             [  # bus ports only
                 2 * PORT_BORDER_SEPARATION +
-                sum([port.H + PORT_SPACING for port in ports if port.get_type() == 'bus']) - PORT_SPACING
+                sum([port.height + PORT_SPACING for port in ports if port.get_type() == 'bus']) - PORT_SPACING
                 for ports in (self.get_sources_gui(), self.get_sinks_gui())
             ]
         )
-        self.W, self.H = width, height = Utils.align_to_grid((width, height))
+        self.width, self.height = width, height = Utils.align_to_grid((width, height))
 
         self._surface_layout_offsets = [
             (width - label_width) / 2.0,
@@ -209,9 +211,9 @@ class Block(_Block, Element):
             max_width = 0
             for port in ports:
                 port.create_labels()
-                max_width = max(max_width, port.W)
+                max_width = max(max_width, port.width)
             for port in ports:
-                port.W = max_width
+                port.width = max_width
 
     def create_comment_layout(self):
         markups = []
@@ -243,7 +245,7 @@ class Block(_Block, Element):
         """
         bg_color = self._bg_color
         border_color = (
-            Colors.HIGHLIGHT_COLOR if self.is_highlighted() else
+            Colors.HIGHLIGHT_COLOR if self.highlighted else
             Colors.MISSING_BLOCK_BORDER_COLOR if self.is_dummy_block else
             Colors.BORDER_COLOR
         )
@@ -257,7 +259,7 @@ class Block(_Block, Element):
         # title and params label
         if self.is_vertical():
             cr.rotate(-math.pi / 2)
-            cr.translate(-self.W, 0)
+            cr.translate(-self.width, 0)
         cr.translate(*self._surface_layout_offsets)
 
         for layout in self._surface_layouts:
@@ -292,9 +294,9 @@ class Block(_Block, Element):
         x, y = self.get_coordinate()
 
         if self.is_horizontal():
-            y += self.H + BLOCK_LABEL_PADDING
+            y += self.height + BLOCK_LABEL_PADDING
         else:
-            x += self.H + BLOCK_LABEL_PADDING
+            x += self.height + BLOCK_LABEL_PADDING
 
         cr.save()
         cr.translate(x, y)
diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py
index 5e3353c5c1..5bd25fb2e6 100644
--- a/grc/gui/Connection.py
+++ b/grc/gui/Connection.py
@@ -19,13 +19,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
 from __future__ import absolute_import
 
-from six.moves import map
-
-from . import Colors
-from . import Utils
+from . import Colors, Utils
 from .Constants import CONNECTOR_ARROW_BASE, CONNECTOR_ARROW_HEIGHT
 from .Element import Element
 
+from ..core.Element import property_nop_write
 from ..core.Connection import Connection as _Connection
 
 
@@ -42,7 +40,8 @@ class Connection(Element, _Connection):
     def __init__(self, **kwargs):
         Element.__init__(self)
         _Connection.__init__(self, **kwargs)
-        self._color2 = self._arrow_color = self._color = None
+
+        self._color =self._color2 = self._arrow_color =  None
 
         self._sink_rot = self._source_rot = None
         self._sink_coor = self._source_coor = None
@@ -50,7 +49,8 @@ class Connection(Element, _Connection):
     def get_coordinate(self):
         return self.source_port.get_connector_coordinate()
 
-    def get_rotation(self):
+    @property_nop_write
+    def rotation(self):
         """
         Get the 0 degree rotation.
         Rotations are irrelevant in connection.
@@ -72,14 +72,14 @@ class Connection(Element, _Connection):
             connector_length = self.source_port.connector_length
         except:
             return  # todo: why?
-        self.x1, self.y1 = Utils.get_rotated_coordinate((connector_length, 0), self.source_port.get_rotation())
+        self.x1, self.y1 = Utils.get_rotated_coordinate((connector_length, 0), self.source_port.rotation)
         #get the sink coordinate
         connector_length = self.sink_port.connector_length + CONNECTOR_ARROW_HEIGHT
-        self.x2, self.y2 = Utils.get_rotated_coordinate((-connector_length, 0), self.sink_port.get_rotation())
+        self.x2, self.y2 = Utils.get_rotated_coordinate((-connector_length, 0), self.sink_port.rotation)
         #build the arrow
         self.arrow = [(0, 0),
-            Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, -CONNECTOR_ARROW_BASE/2), self.sink_port.get_rotation()),
-            Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, CONNECTOR_ARROW_BASE/2), self.sink_port.get_rotation()),
+            Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, -CONNECTOR_ARROW_BASE/2), self.sink_port.rotation),
+            Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, CONNECTOR_ARROW_BASE/2), self.sink_port.rotation),
         ]
         source_domain = self.source_port.domain
         sink_domain = self.sink_port.domain
@@ -133,7 +133,7 @@ class Connection(Element, _Connection):
                 points, alt = alt, points
             # create 3-line connector
             i1, i2 = points
-            self.add_line(p0, p1, i1, i2, p2, p3)
+            self.lines.append([p0, p1, i1, i2, p2, p3])
         else:
             # 2 possible points to create a right-angled connector
             point, alt = [(x1, y2), (x2, y1)]
@@ -147,7 +147,7 @@ class Connection(Element, _Connection):
             if Utils.get_angle_from_coordinates(point, p1) == source_dir:
                 point, alt = alt, point
             # create right-angled connector
-            self.add_line(p0, p1, point, p2, p3)
+            self.lines.append([p0, p1, point, p2, p3])
 
     def draw(self, widget, cr):
         """
@@ -156,10 +156,10 @@ class Connection(Element, _Connection):
         sink = self.sink_port
         source = self.source_port
         #check for changes
-        if self._sink_rot != sink.get_rotation() or self._source_rot != source.get_rotation():
+        if self._sink_rot != sink.rotation or self._source_rot != source.rotation:
             self.create_shapes()
-            self._sink_rot = sink.get_rotation()
-            self._source_rot = source.get_rotation()
+            self._sink_rot = sink.rotation
+            self._source_rot = source.rotation
 
         elif self._sink_coor != sink.parent_block.get_coordinate() or self._source_coor != source.parent_block.get_coordinate():
             self._update_after_move()
@@ -167,7 +167,7 @@ class Connection(Element, _Connection):
             self._source_coor = source.parent_block.get_coordinate()
         # draw
         color1, color2 = (
-            Colors.HIGHLIGHT_COLOR if self.is_highlighted() else
+            Colors.HIGHLIGHT_COLOR if self.highlighted else
             Colors.CONNECTION_DISABLED_COLOR if not self.get_enabled() else
             color for color in (self._color, self._color2))
 
diff --git a/grc/gui/Element.py b/grc/gui/Element.py
index 9513b44dc6..50cb4aaa97 100644
--- a/grc/gui/Element.py
+++ b/grc/gui/Element.py
@@ -19,7 +19,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
 from __future__ import absolute_import
 from .Constants import LINE_SELECT_SENSITIVITY
-from .Constants import POSSIBLE_ROTATIONS
 
 from six.moves import zip
 
@@ -35,12 +34,17 @@ class Element(object):
         """
         Make a new list of rectangular areas and lines, and set the coordinate and the rotation.
         """
-        self.set_rotation(POSSIBLE_ROTATIONS[0])
         self.set_coordinate((0, 0))
         self.highlighted = False
+        self.rotation = 0
 
-        self._areas_list = []
-        self._lines_list = []
+        self.areas = []
+        self.lines = []
+
+    def clear(self):
+        """Empty the lines and areas."""
+        del self.areas[:]
+        del self.lines[:]
 
     def is_horizontal(self, rotation=None):
         """
@@ -53,7 +57,7 @@ class Element(object):
         Returns:
             true if rotation is horizontal
         """
-        rotation = rotation or self.get_rotation()
+        rotation = rotation or self.rotation
         return rotation in (0, 180)
 
     def is_vertical(self, rotation=None):
@@ -67,7 +71,7 @@ class Element(object):
         Returns:
             true if rotation is vertical
         """
-        rotation = rotation or self.get_rotation()
+        rotation = rotation or self.rotation
         return rotation in (90, 270)
 
     def create_labels(self):
@@ -99,7 +103,7 @@ class Element(object):
         """
         X, Y = self.get_coordinate()
         cr.translate(X, Y)
-        for area in self._areas_list:
+        for area in self.areas:
             # aX = X + rX
             # aY = Y + rY
             cr.set_source_rgb(*bg_color)
@@ -110,7 +114,7 @@ class Element(object):
             cr.stroke()
 
         cr.set_source_rgb(*border_color)
-        for line in self._lines_list:
+        for line in self.lines:
             cr.move_to(*line[0])
             for point in line[1:]:
                 cr.line_to(*point)
@@ -123,12 +127,7 @@ class Element(object):
         Args:
             rotation: multiple of 90 degrees
         """
-        self.set_rotation((self.get_rotation() + rotation)%360)
-
-    def clear(self):
-        """Empty the lines and areas."""
-        del self._areas_list[:]
-        del self._lines_list[:]
+        self.rotation = (self.rotation + rotation) % 360
 
     def set_coordinate(self, coor):
         """
@@ -139,24 +138,6 @@ class Element(object):
         """
         self.coor = coor
 
-    def set_highlighted(self, highlighted):
-        """
-        Set the highlight status.
-
-        Args:
-            highlighted: true to enable highlighting
-        """
-        self.highlighted = highlighted
-
-    def is_highlighted(self):
-        """
-        Get the highlight status.
-
-        Returns:
-            true if highlighted
-        """
-        return self.highlighted
-
     def get_coordinate(self):
         """Get the coordinate.
 
@@ -174,27 +155,7 @@ class Element(object):
         """
         deltaX, deltaY = delta_coor
         X, Y = self.get_coordinate()
-        self.set_coordinate((X+deltaX, Y+deltaY))
-
-    def add_area(self, x, y, w, h):
-        """
-        Add an area to the area list.
-        An area is actually a coordinate relative to the main coordinate
-        with a width/height pair relative to the area coordinate.
-        A positive width is to the right of the coordinate.
-        A positive height is above the coordinate.
-        The area is associated with a rotation.
-        """
-        self._areas_list.append([x, y, w, h])
-
-    def add_line(self, *points):
-        """
-        Add a line to the line list.
-        A line is defined by 2 or more relative coordinates.
-        Lines must be horizontal or vertical.
-        The line is associated with a rotation.
-        """
-        self._lines_list.append(points)
+        self.set_coordinate((X + deltaX, Y + deltaY))
 
     def what_is_selected(self, coor, coor_m=None):
         """
@@ -219,14 +180,14 @@ class Element(object):
         if coor_m:
             x_m, y_m = [a-b for a,b in zip(coor_m, self.get_coordinate())]
             #handle rectangular areas
-            for x1, y1, w, h in self._areas_list:
+            for x1, y1, w, h in self.areas:
                 if in_between(x1, x, x_m) and in_between(y1, y, y_m) or \
                     in_between(x1+w, x, x_m) and in_between(y1, y, y_m) or \
                     in_between(x1, x, x_m) and in_between(y1+h, y, y_m) or \
                     in_between(x1+w, x, x_m) and in_between(y1+h, y, y_m):
                     return self
             #handle horizontal or vertical lines
-            for line in self._lines_list:
+            for line in self.lines:
                 last_point = line[0]
                 for x2, y2 in line[1:]:
                     (x1, y1), last_point = last_point, (x2, y2)
@@ -236,10 +197,10 @@ class Element(object):
             return None
         else:
             #handle rectangular areas
-            for x1, y1, w, h in self._areas_list:
+            for x1, y1, w, h in self.areas:
                 if in_between(x, x1, x1+w) and in_between(y, y1, y1+h): return self
             #handle horizontal or vertical lines
-            for line in self._lines_list:
+            for line in self.lines:
                 last_point = line[0]
                 for x2, y2 in line[1:]:
                     (x1, y1), last_point = last_point, (x2, y2)
@@ -248,25 +209,6 @@ class Element(object):
                     if in_between(x, x1, x2) and in_between(y, y1, y2): return self
             return None
 
-    def get_rotation(self):
-        """
-        Get the rotation in degrees.
-
-        Returns:
-            the rotation
-        """
-        return self.rotation
-
-    def set_rotation(self, rotation):
-        """
-        Set the rotation in degrees.
-
-        Args:
-            rotation: the rotation"""
-        if rotation not in POSSIBLE_ROTATIONS:
-            raise Exception('"%s" is not one of the possible rotations: (%s)'%(rotation, POSSIBLE_ROTATIONS))
-        self.rotation = rotation
-
     def mouse_over(self):
         pass
 
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index 7176594460..5de38877c1 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -143,7 +143,6 @@ class FlowGraph(Element, _Flowgraph):
         #get the new block
         block = self.new_block(key)
         block.set_coordinate(coor)
-        block.set_rotation(0)
         block.get_param('id').set_value(id)
         Actions.ELEMENT_CREATE()
         return id
@@ -363,8 +362,8 @@ class FlowGraph(Element, _Flowgraph):
         for selected_block in blocks:
             x, y = selected_block.get_coordinate()
             min_x, min_y = min(min_x, x), min(min_y, y)
-            x += selected_block.W
-            y += selected_block.H
+            x += selected_block.width
+            y += selected_block.height
             max_x, max_y = max(max_x, x), max(max_y, y)
         ctr_x, ctr_y = (max_x + min_x)/2, (max_y + min_y)/2
 
@@ -380,7 +379,7 @@ class FlowGraph(Element, _Flowgraph):
 
         for selected_block in blocks:
             x, y = selected_block.get_coordinate()
-            w, h = selected_block.W, selected_block.H
+            w, h = selected_block.width, selected_block.height
             selected_block.set_coordinate(transform(x, y, w, h))
 
         return True
@@ -747,6 +746,6 @@ class FlowGraph(Element, _Flowgraph):
         w, h = initial
         for block in self.blocks:
             x, y = block.get_coordinate()
-            w = max(w, x + block.W)
-            h = max(h, y + block.H)
+            w = max(w, x + block.width)
+            h = max(h, y + block.height)
         return w, h
diff --git a/grc/gui/Port.py b/grc/gui/Port.py
index 298b22e19f..24dde67a01 100644
--- a/grc/gui/Port.py
+++ b/grc/gui/Port.py
@@ -25,6 +25,7 @@ from gi.repository import Gtk, PangoCairo, Pango
 from . import Actions, Colors, Utils, Constants
 from .Element import Element
 
+from ..core.Element import property_nop_write
 from ..core.Port import Port as _Port
 
 
@@ -44,19 +45,19 @@ class Port(_Port, Element):
         self._bg_color = (0, 0, 0)
         self._line_width_factor = 1.0
 
-        self._W = self.H = 0
+        self._width = self.height = 0
         self.connector_length = 0
 
         self.label_layout = Gtk.DrawingArea().create_pango_layout('')
         self.label_layout.set_alignment(Pango.Alignment.CENTER)
 
     @property
-    def W(self):
-        return self._W if not self._label_hidden() else Constants.PORT_LABEL_HIDDEN_WIDTH
+    def width(self):
+        return self._width if not self._label_hidden() else Constants.PORT_LABEL_HIDDEN_WIDTH
 
-    @W.setter
-    def W(self, value):
-        self._W = value
+    @width.setter
+    def width(self, value):
+        self._width = value
         self.label_layout.set_width(value * Pango.SCALE)
 
     def _get_color(self):
@@ -79,15 +80,15 @@ class Port(_Port, Element):
         self.clear()
 
         if self.is_horizontal():
-            self.add_area(0, 0, self.W, self.H)
+            self.areas.append([0, 0, self.width, self.height])
         elif self.is_vertical():
-            self.add_area(0, 0, self.H, self.W)
+            self.areas.append([0, 0, self.height, self.width])
 
         self._connector_coordinate = {
-            0:   (self.W, self.H / 2),
-            90:  (self.H / 2, 0),
-            180: (0, self.H / 2),
-            270: (self.H / 2, self.W)
+            0:   (self.width, self.height / 2),
+            90:  (self.height / 2, 0),
+            180: (0, self.height / 2),
+            270: (self.height / 2, self.width)
         }[self.get_connector_direction()]
 
     def create_labels(self):
@@ -106,11 +107,11 @@ class Port(_Port, Element):
         ))
         label_width, label_height = self.label_layout.get_pixel_size()
 
-        self.W = 2 * Constants.PORT_LABEL_PADDING + label_width
-        self.H = 2 * Constants.PORT_LABEL_PADDING + label_height
+        self.width = 2 * Constants.PORT_LABEL_PADDING + label_width
+        self.height = 2 * Constants.PORT_LABEL_PADDING + label_height
         if self.get_type() == 'bus':
-            self.H += 2 * label_height
-        self.H += self.H % 2  # uneven height
+            self.height += 2 * label_height
+        self.height += self.height % 2  # uneven height
 
     def draw(self, widget, cr, border_color, bg_color):
         """
@@ -124,7 +125,7 @@ class Port(_Port, Element):
 
         if self.is_vertical():
             cr.rotate(-math.pi / 2)
-            cr.translate(-self.W, 0)
+            cr.translate(-self.width, 0)
         cr.translate(0, Constants.PORT_LABEL_PADDING)
 
         PangoCairo.update_layout(cr, self.label_layout)
@@ -150,27 +151,13 @@ class Port(_Port, Element):
             the direction in degrees
         """
         if self.is_source:
-            return self.get_rotation()
+            return self.rotation
         elif self.is_sink:
-            return (self.get_rotation() + 180) % 360
+            return (self.rotation + 180) % 360
 
-    def get_rotation(self):
-        """
-        Get the parent's rotation rather than self.
-
-        Returns:
-            the parent's rotation
-        """
-        return self.parent.get_rotation()
-
-    def move(self, delta_coor):
-        """
-        Move the parent rather than self.
-
-        Args:
-            delta_corr: the (delta_x, delta_y) tuple
-        """
-        self.parent.move(delta_coor)
+    @property_nop_write
+    def rotation(self):
+        return self.parent_block.rotation
 
     def rotate(self, direction):
         """
@@ -181,23 +168,22 @@ class Port(_Port, Element):
         """
         self.parent.rotate(direction)
 
-    def set_highlighted(self, highlight):
+    def move(self, delta_coor):
         """
-        Set the parent highlight rather than self.
+        Move the parent rather than self.
 
         Args:
-            highlight: true to enable highlighting
+            delta_corr: the (delta_x, delta_y) tuple
         """
-        self.parent.set_highlighted(highlight)
+        self.parent.move(delta_coor)
 
-    def is_highlighted(self):
-        """
-        Get the parent's is highlight rather than self.
+    @property
+    def highlighted(self):
+        return self.parent_block.highlighted
 
-        Returns:
-            the parent's highlighting status
-        """
-        return self.parent.is_highlighted()
+    @highlighted.setter
+    def highlighted(self, value):
+        self.parent_block.highlighted = value
 
     def _label_hidden(self):
         """
-- 
cgit v1.2.3


From 47ddd4c48916308817ff39919c1e39b0da3f9c3d Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Wed, 13 Jul 2016 10:53:09 +0200
Subject: grc: gtk3: remove coordinate getter/setter

---
 grc/gui/ActionHandler.py |  8 ++---
 grc/gui/Block.py         | 16 +++++-----
 grc/gui/Connection.py    | 13 ++++----
 grc/gui/Element.py       | 80 +++++++++++++++++++++---------------------------
 grc/gui/FlowGraph.py     | 43 +++++++++++++-------------
 grc/gui/Port.py          |  4 +--
 6 files changed, 78 insertions(+), 86 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py
index 923d8d6d9a..938f8872a3 100644
--- a/grc/gui/ActionHandler.py
+++ b/grc/gui/ActionHandler.py
@@ -202,7 +202,7 @@ class ActionHandler:
         elif action == Actions.BLOCK_CREATE_HIER:
 
                         # keeping track of coordinates for pasting later
-                        coords = flow_graph.selected_blocks()[0].get_coordinate()
+                        coords = flow_graph.selected_blocks()[0].coordinate
                         x,y = coords
                         x_min = x
                         y_min = y
@@ -226,7 +226,7 @@ class ActionHandler:
 
 
                             # keep track of x,y mins for pasting later
-                            (x,y) = block.get_coordinate()
+                            (x,y) = block.coordinate
                             if x < x_min:
                                 x_min = x
                             if y < y_min:
@@ -240,10 +240,10 @@ class ActionHandler:
 
                                 # If connected block is not in the list of selected blocks create a pad for it
                                 if flow_graph.get_block(source_id) not in flow_graph.selected_blocks():
-                                    pads.append({'key': connection.sink_port.key, 'coord': connection.source_port.get_coordinate(), 'block_id' : block.get_id(), 'direction': 'source'})
+                                    pads.append({'key': connection.sink_port.key, 'coord': connection.source_port.coordinate, 'block_id' : block.get_id(), 'direction': 'source'})
 
                                 if flow_graph.get_block(sink_id) not in flow_graph.selected_blocks():
-                                    pads.append({'key': connection.source_port.key, 'coord': connection.sink_port.get_coordinate(), 'block_id' : block.get_id(), 'direction': 'sink'})
+                                    pads.append({'key': connection.source_port.key, 'coord': connection.sink_port.coordinate, 'block_id' : block.get_id(), 'direction': 'sink'})
 
 
                         # Copy the selected blocks and paste them into a new page
diff --git a/grc/gui/Block.py b/grc/gui/Block.py
index e0899136f3..5efb571456 100644
--- a/grc/gui/Block.py
+++ b/grc/gui/Block.py
@@ -60,7 +60,8 @@ class Block(_Block, Element):
         self._bg_color = Colors.BLOCK_ENABLED_COLOR
         self.has_busses = [False, False]  # source, sink
 
-    def get_coordinate(self):
+    @property
+    def coordinate(self):
         """
         Get the coordinate from the position param.
 
@@ -69,7 +70,8 @@ class Block(_Block, Element):
         """
         return self.states['_coordinate']
 
-    def set_coordinate(self, coor):
+    @coordinate.setter
+    def coordinate(self, coor):
         """
         Set the coordinate into the position param.
 
@@ -121,12 +123,12 @@ class Block(_Block, Element):
             for index, port in enumerate(ports):
                 port.create_shapes()
 
-                port.set_coordinate({
+                port.coordinate = {
                     0: (+self.width, offset),
                     90: (offset, -port.width),
                     180: (-port.width, offset),
                     270: (offset, +self.width),
-                }[port.get_connector_direction()])
+                }[port.get_connector_direction()]
                 offset += PORT_SEPARATION if not has_busses else port.height + PORT_SPACING
 
                 port.connector_length = Constants.CONNECTOR_EXTENSION_MINIMAL + \
@@ -281,8 +283,8 @@ class Block(_Block, Element):
         """
         for port in self.active_ports():
             port_selected = port.what_is_selected(
-                coor=[a - b for a, b in zip(coor, self.get_coordinate())],
-                coor_m=[a - b for a, b in zip(coor, self.get_coordinate())] if coor_m is not None else None
+                coor=[a - b for a, b in zip(coor, self.coordinate)],
+                coor_m=[a - b for a, b in zip(coor, self.coordinate)] if coor_m is not None else None
             )
             if port_selected:
                 return port_selected
@@ -291,7 +293,7 @@ class Block(_Block, Element):
     def draw_comment(self, widget, cr):
         if not self._comment_layout:
             return
-        x, y = self.get_coordinate()
+        x, y = self.coordinate
 
         if self.is_horizontal():
             y += self.height + BLOCK_LABEL_PADDING
diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py
index 5bd25fb2e6..33fe10483e 100644
--- a/grc/gui/Connection.py
+++ b/grc/gui/Connection.py
@@ -46,7 +46,8 @@ class Connection(Element, _Connection):
         self._sink_rot = self._source_rot = None
         self._sink_coor = self._source_coor = None
 
-    def get_coordinate(self):
+    @property_nop_write
+    def coordinate(self):
         return self.source_port.get_connector_coordinate()
 
     @property_nop_write
@@ -107,7 +108,7 @@ class Connection(Element, _Connection):
         source_dir = source.get_connector_direction()
         sink_dir = sink.get_connector_direction()
 
-        x_pos, y_pos = self.get_coordinate()
+        x_pos, y_pos = self.coordinate
         x_start, y_start = source.get_connector_coordinate()
         x_end, y_end = sink.get_connector_coordinate()
 
@@ -161,10 +162,10 @@ class Connection(Element, _Connection):
             self._sink_rot = sink.rotation
             self._source_rot = source.rotation
 
-        elif self._sink_coor != sink.parent_block.get_coordinate() or self._source_coor != source.parent_block.get_coordinate():
+        elif self._sink_coor != sink.parent_block.coordinate or self._source_coor != source.parent_block.coordinate:
             self._update_after_move()
-            self._sink_coor = sink.parent_block.get_coordinate()
-            self._source_coor = source.parent_block.get_coordinate()
+            self._sink_coor = sink.parent_block.coordinate
+            self._source_coor = source.parent_block.coordinate
         # draw
         color1, color2 = (
             Colors.HIGHLIGHT_COLOR if self.highlighted else
@@ -175,7 +176,7 @@ class Connection(Element, _Connection):
 
         if color1 != color2:
             cr.save()
-            x_pos, y_pos = self.get_coordinate()
+            x_pos, y_pos = self.coordinate
             cr.translate(-x_pos, -y_pos)
             cr.set_dash([5.0, 5.0], 5.0)
             Element.draw(self, widget, cr, color2, Colors.FLOWGRAPH_BACKGROUND_COLOR)
diff --git a/grc/gui/Element.py b/grc/gui/Element.py
index 50cb4aaa97..3b077dcc3e 100644
--- a/grc/gui/Element.py
+++ b/grc/gui/Element.py
@@ -34,9 +34,9 @@ class Element(object):
         """
         Make a new list of rectangular areas and lines, and set the coordinate and the rotation.
         """
-        self.set_coordinate((0, 0))
-        self.highlighted = False
+        self.coordinate = (0, 0)
         self.rotation = 0
+        self.highlighted = False
 
         self.areas = []
         self.lines = []
@@ -101,11 +101,8 @@ class Element(object):
             border_color: the color for lines and rectangle borders
             bg_color: the color for the inside of the rectangle
         """
-        X, Y = self.get_coordinate()
-        cr.translate(X, Y)
+        cr.translate(*self.coordinate)
         for area in self.areas:
-            # aX = X + rX
-            # aY = Y + rY
             cr.set_source_rgb(*bg_color)
             cr.rectangle(*area)
             cr.fill()
@@ -129,23 +126,6 @@ class Element(object):
         """
         self.rotation = (self.rotation + rotation) % 360
 
-    def set_coordinate(self, coor):
-        """
-        Set the reference coordinate.
-
-        Args:
-            coor: the coordinate tuple (x,y)
-        """
-        self.coor = coor
-
-    def get_coordinate(self):
-        """Get the coordinate.
-
-        Returns:
-            the coordinate tuple (x,y)
-        """
-        return self.coor
-
     def move(self, delta_coor):
         """
         Move the element by adding the delta_coor to the current coordinate.
@@ -153,9 +133,9 @@ class Element(object):
         Args:
             delta_coor: (delta_x,delta_y) tuple
         """
-        deltaX, deltaY = delta_coor
-        X, Y = self.get_coordinate()
-        self.set_coordinate((X + deltaX, Y + deltaY))
+        x, y = self.coordinate
+        dx, dy = delta_coor
+        self.coordinate = (x + dx, y + dy)
 
     def what_is_selected(self, coor, coor_m=None):
         """
@@ -173,40 +153,50 @@ class Element(object):
         Returns:
             self if one of the areas/lines encompasses coor, else None.
         """
-        #function to test if p is between a and b (inclusive)
-        in_between = lambda p, a, b: p >= min(a, b) and p <= max(a, b)
-        #relative coordinate
-        x, y = [a-b for a,b in zip(coor, self.get_coordinate())]
+        # function to test if p is between a and b (inclusive)
+        def in_between(p, a, b):
+            # return min(a, b) <= p <= max(a, b)
+            return a <= p <= b or b <= p <= a
+        # relative coordinate
+        x, y = [a - b for a, b in zip(coor, self.coordinate)]
         if coor_m:
-            x_m, y_m = [a-b for a,b in zip(coor_m, self.get_coordinate())]
-            #handle rectangular areas
+            x_m, y_m = [a - b for a, b in zip(coor_m, self.coordinate)]
+            # handle rectangular areas
             for x1, y1, w, h in self.areas:
-                if in_between(x1, x, x_m) and in_between(y1, y, y_m) or \
-                    in_between(x1+w, x, x_m) and in_between(y1, y, y_m) or \
-                    in_between(x1, x, x_m) and in_between(y1+h, y, y_m) or \
-                    in_between(x1+w, x, x_m) and in_between(y1+h, y, y_m):
+                if (
+                    in_between(x1,     x, x_m) and in_between(y1,     y, y_m) or
+                    in_between(x1 + w, x, x_m) and in_between(y1,     y, y_m) or
+                    in_between(x1,     x, x_m) and in_between(y1 + h, y, y_m) or
+                    in_between(x1 + w, x, x_m) and in_between(y1 + h, y, y_m)
+                ):
                     return self
-            #handle horizontal or vertical lines
+            # handle horizontal or vertical lines
             for line in self.lines:
                 last_point = line[0]
                 for x2, y2 in line[1:]:
                     (x1, y1), last_point = last_point, (x2, y2)
-                    if in_between(x1, x, x_m) and in_between(y1, y, y_m) or \
-                        in_between(x2, x, x_m) and in_between(y2, y, y_m):
+                    if (
+                        in_between(x1, x, x_m) and in_between(y1, y, y_m) or
+                        in_between(x2, x, x_m) and in_between(y2, y, y_m)
+                    ):
                         return self
             return None
         else:
-            #handle rectangular areas
+            # handle rectangular areas
             for x1, y1, w, h in self.areas:
-                if in_between(x, x1, x1+w) and in_between(y, y1, y1+h): return self
-            #handle horizontal or vertical lines
+                if in_between(x, x1, x1+w) and in_between(y, y1, y1+h):
+                    return self
+            # handle horizontal or vertical lines
             for line in self.lines:
                 last_point = line[0]
                 for x2, y2 in line[1:]:
                     (x1, y1), last_point = last_point, (x2, y2)
-                    if x1 == x2: x1, x2 = x1-LINE_SELECT_SENSITIVITY, x2+LINE_SELECT_SENSITIVITY
-                    if y1 == y2: y1, y2 = y1-LINE_SELECT_SENSITIVITY, y2+LINE_SELECT_SENSITIVITY
-                    if in_between(x, x1, x2) and in_between(y, y1, y2): return self
+                    if x1 == x2:
+                        x1, x2 = x1 - LINE_SELECT_SENSITIVITY, x2 + LINE_SELECT_SENSITIVITY
+                    if y1 == y2:
+                        y1, y2 = y1 - LINE_SELECT_SENSITIVITY, y2 + LINE_SELECT_SENSITIVITY
+                    if in_between(x, x1, x2) and in_between(y, y1, y2):
+                        return self
             return None
 
     def mouse_over(self):
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index 5de38877c1..c3571231fb 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -142,7 +142,7 @@ class FlowGraph(Element, _Flowgraph):
         )
         #get the new block
         block = self.new_block(key)
-        block.set_coordinate(coor)
+        block.coordinate = coor
         block.get_param('id').set_value(id)
         Actions.ELEMENT_CREATE()
         return id
@@ -211,9 +211,9 @@ class FlowGraph(Element, _Flowgraph):
         if not blocks:
             return None
         #calc x and y min
-        x_min, y_min = blocks[0].get_coordinate()
+        x_min, y_min = blocks[0].coordinate
         for block in blocks:
-            x, y = block.get_coordinate()
+            x, y = block.coordinate
             x_min = min(x, x_min)
             y_min = min(y, y_min)
         #get connections between selected blocks
@@ -358,9 +358,9 @@ class FlowGraph(Element, _Flowgraph):
             return False
 
         # compute common boundary of selected objects
-        min_x, min_y = max_x, max_y = blocks[0].get_coordinate()
+        min_x, min_y = max_x, max_y = blocks[0].coordinate
         for selected_block in blocks:
-            x, y = selected_block.get_coordinate()
+            x, y = selected_block.coordinate
             min_x, min_y = min(min_x, x), min(min_y, y)
             x += selected_block.width
             y += selected_block.height
@@ -378,9 +378,9 @@ class FlowGraph(Element, _Flowgraph):
         }.get(calling_action, lambda *args: args)
 
         for selected_block in blocks:
-            x, y = selected_block.get_coordinate()
+            x, y = selected_block.coordinate
             w, h = selected_block.width, selected_block.height
-            selected_block.set_coordinate(transform(x, y, w, h))
+            selected_block.coordinate = transform(x, y, w, h)
 
         return True
 
@@ -397,22 +397,21 @@ class FlowGraph(Element, _Flowgraph):
         if not any(self.selected_blocks()):
             return False
         #initialize min and max coordinates
-        min_x, min_y = self.selected_block.get_coordinate()
-        max_x, max_y = self.selected_block.get_coordinate()
-        #rotate each selected block, and find min/max coordinate
+        min_x, min_y = max_x, max_y = self.selected_block.coordinate
+        # rotate each selected block, and find min/max coordinate
         for selected_block in self.selected_blocks():
             selected_block.rotate(rotation)
             #update the min/max coordinate
-            x, y = selected_block.get_coordinate()
+            x, y = selected_block.coordinate
             min_x, min_y = min(min_x, x), min(min_y, y)
             max_x, max_y = max(max_x, x), max(max_y, y)
         #calculate center point of slected blocks
         ctr_x, ctr_y = (max_x + min_x)/2, (max_y + min_y)/2
         #rotate the blocks around the center point
         for selected_block in self.selected_blocks():
-            x, y = selected_block.get_coordinate()
+            x, y = selected_block.coordinate
             x, y = Utils.get_rotated_coordinate((x - ctr_x, y - ctr_y), rotation)
-            selected_block.set_coordinate((x + ctr_x, y + ctr_y))
+            selected_block.coordinate = (x + ctr_x, y + ctr_y)
         return True
 
     def remove_selected(self):
@@ -490,7 +489,7 @@ class FlowGraph(Element, _Flowgraph):
         # draw multi select rectangle
         if self.mouse_pressed and (not self.selected_elements or self.get_ctrl_mask()):
             x1, y1 = self.press_coor
-            x2, y2 = self.get_coordinate()
+            x2, y2 = self.coordinate
             x, y = int(min(x1, x2)), int(min(y1, y2))
             w, h = int(abs(x1 - x2)), int(abs(y1 - y2))
             cr.set_source_rgba(
@@ -520,7 +519,7 @@ class FlowGraph(Element, _Flowgraph):
         """
         selected_elements = None
         if self.mouse_pressed:
-            new_selections = self.what_is_selected(self.get_coordinate())
+            new_selections = self.what_is_selected(self.coordinate)
             # update the selections if the new selection is not in the current selections
             # allows us to move entire selected groups of elements
             if self.get_ctrl_mask() or new_selections not in self.selected_elements:
@@ -535,7 +534,7 @@ class FlowGraph(Element, _Flowgraph):
 
         else:  # called from a mouse release
             if not self.element_moved and (not self.selected_elements or self.get_ctrl_mask()):
-                selected_elements = self.what_is_selected(self.get_coordinate(), self.press_coor)
+                selected_elements = self.what_is_selected(self.coordinate, self.press_coor)
 
         # this selection and the last were ports, try to connect them
         if self.make_connection():
@@ -650,7 +649,7 @@ class FlowGraph(Element, _Flowgraph):
         """
         selections = self.what_is_selected(coordinate)
         if not selections.intersection(self.selected_elements):
-            self.set_coordinate(coordinate)
+            self.coordinate = coordinate
             self.mouse_pressed = True
             self.update_selected_elements()
             self.mouse_pressed = False
@@ -664,7 +663,7 @@ class FlowGraph(Element, _Flowgraph):
         Update the selection state of the flow graph.
         """
         self.press_coor = coordinate
-        self.set_coordinate(coordinate)
+        self.coordinate = coordinate
         self.mouse_pressed = True
 
         if double_click:
@@ -681,7 +680,7 @@ class FlowGraph(Element, _Flowgraph):
         Update the state, handle motion (dragging).
         And update the selected flowgraph elements.
         """
-        self.set_coordinate(coordinate)
+        self.coordinate = coordinate
         self.mouse_pressed = False
         if self.element_moved:
             Actions.BLOCK_MOVE()
@@ -733,19 +732,19 @@ class FlowGraph(Element, _Flowgraph):
         # move the selected elements and record the new coordinate
         x, y = coordinate
         if not self.get_ctrl_mask():
-            X, Y = self.get_coordinate()
+            X, Y = self.coordinate
             dX, dY = int(x - X), int(y - Y)
             active = Actions.TOGGLE_SNAP_TO_GRID.get_active() or self.get_mod1_mask()
             if not active or abs(dX) >= Utils.CANVAS_GRID_SIZE or abs(dY) >= Utils.CANVAS_GRID_SIZE:
                 self.move_selected((dX, dY))
-                self.set_coordinate((x, y))
+                self.coordinate = (x, y)
         # queue draw for animation
         self.queue_draw()
 
     def get_max_coords(self, initial=(0, 0)):
         w, h = initial
         for block in self.blocks:
-            x, y = block.get_coordinate()
+            x, y = block.coordinate
             w = max(w, x + block.width)
             h = max(h, y + block.height)
         return w, h
diff --git a/grc/gui/Port.py b/grc/gui/Port.py
index 24dde67a01..d8a180b7c8 100644
--- a/grc/gui/Port.py
+++ b/grc/gui/Port.py
@@ -138,8 +138,8 @@ class Port(_Port, Element):
         Returns:
             the connector coordinate (x, y) tuple
         """
-        return [sum(c) for c in zip(self._connector_coordinate, self.get_coordinate(),
-                                    self.parent_block.get_coordinate())]
+        return [sum(c) for c in zip(self._connector_coordinate, self.coordinate,
+                                    self.parent_block.coordinate)]
 
     def get_connector_direction(self):
         """
-- 
cgit v1.2.3


From dbcc062b337ba7fa24fa98f26984ee61d484d834 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Wed, 20 Jul 2016 15:06:12 +0200
Subject: grc: gtk3: fix port label hiding

---
 grc/gui/Block.py     |  2 +-
 grc/gui/FlowGraph.py |  6 +++---
 grc/gui/Port.py      | 36 +++++++++++++++---------------------
 3 files changed, 19 insertions(+), 25 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/gui/Block.py b/grc/gui/Block.py
index 616396c747..e7dc03345b 100644
--- a/grc/gui/Block.py
+++ b/grc/gui/Block.py
@@ -214,7 +214,7 @@ class Block(CoreBlock, Element):
             max_width = 0
             for port in ports:
                 port.create_labels()
-                max_width = max(max_width, port.width)
+                max_width = max(max_width, port.width_with_label)
             for port in ports:
                 port.width = max_width
 
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index c3571231fb..da88636e1b 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -526,11 +526,11 @@ class FlowGraph(Element, _Flowgraph):
                 selected_elements = new_selections
 
             if self._old_selected_port:
-                self._old_selected_port.force_label_unhidden(False)
+                self._old_selected_port.force_show_label = False
                 self.create_shapes()
                 self.queue_draw()
             elif self._new_selected_port:
-                self._new_selected_port.force_label_unhidden()
+                self._new_selected_port.force_show_label = True
 
         else:  # called from a mouse release
             if not self.element_moved and (not self.selected_elements or self.get_ctrl_mask()):
@@ -705,7 +705,7 @@ class FlowGraph(Element, _Flowgraph):
         if not Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active():
             return
         redraw = False
-        for element in reversed(self.get_elements()):
+        for element in self._elements_to_draw:
             over_element = element.what_is_selected(coordinate)
             if not over_element:
                 continue
diff --git a/grc/gui/Port.py b/grc/gui/Port.py
index db3ab9da23..6ac216244d 100644
--- a/grc/gui/Port.py
+++ b/grc/gui/Port.py
@@ -40,12 +40,12 @@ class Port(_Port, Element):
         super(self.__class__, self).__init__(parent, direction, **n)
         Element.__init__(self)
         self._connector_coordinate = (0, 0)
-        self._hovering = True
-        self._force_label_unhidden = False
+        self._hovering = False
+        self.force_show_label = False
         self._bg_color = (0, 0, 0)
         self._line_width_factor = 1.0
 
-        self._width = self.height = 0
+        self.width_with_label = self.height = 0
         self.connector_length = 0
 
         self.label_layout = Gtk.DrawingArea().create_pango_layout('')
@@ -53,11 +53,11 @@ class Port(_Port, Element):
 
     @property
     def width(self):
-        return self._width if not self._label_hidden() else Constants.PORT_LABEL_HIDDEN_WIDTH
+        return self.width_with_label if self._show_label else Constants.PORT_LABEL_HIDDEN_WIDTH
 
     @width.setter
     def width(self, value):
-        self._width = value
+        self.width_with_label = value
         self.label_layout.set_width(value * Pango.SCALE)
 
     def _get_color(self):
@@ -120,7 +120,7 @@ class Port(_Port, Element):
         cr.set_line_width(self._line_width_factor * cr.get_line_width())
         Element.draw(self, widget, cr, border_color, self._bg_color)
 
-        if self._label_hidden():
+        if not self._show_label:
             return  # this port is folded (no label)
 
         if self.is_vertical():
@@ -186,34 +186,28 @@ class Port(_Port, Element):
     def highlighted(self, value):
         self.parent_block.highlighted = value
 
-    def _label_hidden(self):
+    @property
+    def _show_label(self):
         """
         Figure out if the label should be hidden
 
         Returns:
             true if the label should not be shown
         """
-        return self._hovering and not self._force_label_unhidden and Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active()
-
-    def force_label_unhidden(self, enable=True):
-        """
-        Disable showing the label on mouse-over for this port
-
-        Args:
-            enable: true to override the mouse-over behaviour
-        """
-        self._force_label_unhidden = enable
+        return self._hovering or self.force_show_label or not Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active()
 
     def mouse_over(self):
         """
         Called from flow graph on mouse-over
         """
-        self._hovering = False
-        return Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active()  # only redraw if necessary
+        changed = not self._show_label
+        self._hovering = True
+        return changed
 
     def mouse_out(self):
         """
         Called from flow graph on mouse-out
         """
-        self._hovering = True
-        return Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active()  # only redraw if necessary
+        label_was_shown = self._show_label
+        self._hovering = False
+        return label_was_shown != self._show_label
-- 
cgit v1.2.3


From 6b99b6fded94ae1ed8421c624246362e7925fb08 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Thu, 21 Jul 2016 11:30:42 +0200
Subject: grc: refactor: replace get_enabled by prop

---
 grc/core/Block.py               | 28 +++++-----------------------
 grc/core/Connection.py          | 35 ++++++++++++++++++-----------------
 grc/core/Element.py             |  9 +++++----
 grc/core/Element.pyi            | 25 ++++++++++---------------
 grc/core/FlowGraph.py           | 12 +++++-------
 grc/core/Port.py                |  2 +-
 grc/core/generator/Generator.py |  4 ++--
 grc/core/utils/_complexity.py   |  2 +-
 grc/gui/Block.py                |  4 ++--
 grc/gui/Connection.py           |  2 +-
 grc/gui/FlowGraph.py            |  6 +++---
 grc/gui/VariableEditor.py       | 12 ++++++------
 12 files changed, 59 insertions(+), 82 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/core/Block.py b/grc/core/Block.py
index 7042ba7702..9572982bf7 100644
--- a/grc/core/Block.py
+++ b/grc/core/Block.py
@@ -332,30 +332,11 @@ class Block(Element):
         self.states['_enabled'] = encoded
 
     # Enable/Disable Aliases
-    def get_enabled(self):
-        """
-        Get the enabled state of the block.
-
-        Returns:
-            true for enabled
-        """
+    @property
+    def enabled(self):
+        """Get the enabled state of the block"""
         return self.state != 'disabled'
 
-    def set_enabled(self, enabled):
-        """
-        Set the enabled state of the block.
-
-        Args:
-            enabled: true for enabled
-
-        Returns:
-            True if block changed state
-        """
-        old_state = self.state
-        new_state = 'enabled' if enabled else 'disabled'
-        self.state = new_state
-        return old_state != new_state
-
     # Block bypassing
     def get_bypassed(self):
         """
@@ -739,7 +720,8 @@ class DummyBlock(Block):
     def is_valid(self):
         return False
 
-    def get_enabled(self):
+    @property
+    def enabled(self):
         return False
 
     def add_missing_port(self, key, dir):
diff --git a/grc/core/Connection.py b/grc/core/Connection.py
index aec7a217b3..6be1ccb2aa 100644
--- a/grc/core/Connection.py
+++ b/grc/core/Connection.py
@@ -76,6 +76,24 @@ class Connection(Element):
             raise ValueError('Connection could not isolate sink')
         return source, sink
 
+    @lazy_property
+    def source_block(self):
+        return self.source_port.parent_block
+
+    @lazy_property
+    def sink_block(self):
+        return self.sink_port.parent_block
+
+    @property
+    def enabled(self):
+        """
+        Get the enabled state of this connection.
+
+        Returns:
+            true if source and sink blocks are enabled
+        """
+        return self.source_block.enabled and self.sink_block.enabled
+
     def __str__(self):
         return 'Connection (\n\t{}\n\t\t{}\n\t{}\n\t\t{}\n)'.format(
             self.source_block, self.source_port, self.sink_block, self.sink_port,
@@ -125,23 +143,6 @@ class Connection(Element):
         if source_size != sink_size:
             self.add_error_message('Source IO size "{}" does not match sink IO size "{}".'.format(source_size, sink_size))
 
-    def get_enabled(self):
-        """
-        Get the enabled state of this connection.
-
-        Returns:
-            true if source and sink blocks are enabled
-        """
-        return self.source_block.get_enabled() and self.sink_block.get_enabled()
-
-    @lazy_property
-    def source_block(self):
-        return self.source_port.parent_block
-
-    @lazy_property
-    def sink_block(self):
-        return self.sink_port.parent_block
-
     ##############################################
     # Import/Export Methods
     ##############################################
diff --git a/grc/core/Element.py b/grc/core/Element.py
index 415b086402..32afabbed7 100644
--- a/grc/core/Element.py
+++ b/grc/core/Element.py
@@ -66,7 +66,7 @@ class Element(object):
         Returns:
             true when the element is enabled and has no error messages or is bypassed
         """
-        return (not self.get_error_messages() or not self.get_enabled()) or self.get_bypassed()
+        return (not self.get_error_messages() or not self.enabled) or self.get_bypassed()
 
     def add_error_message(self, msg):
         """
@@ -88,7 +88,7 @@ class Element(object):
         """
         error_messages = list(self._error_messages)  # Make a copy
         for child in self.get_children():
-            if not child.get_enabled() or child.get_bypassed():
+            if not child.enabled or child.get_bypassed():
                 continue
             for msg in child.get_error_messages():
                 error_messages.append("{}:\n\t{}".format(child, msg.replace("\n", "\n\t")))
@@ -102,7 +102,8 @@ class Element(object):
         for child in self.get_children():
             child.rewrite()
 
-    def get_enabled(self):
+    @property
+    def enabled(self):
         return True
 
     def get_bypassed(self):
@@ -141,7 +142,7 @@ class Element(object):
 
     def reset_parents_by_type(self):
         """Reset all lazy properties"""
-        for name, obj in vars(Element):
+        for name, obj in vars(Element):  # explicitly only in Element, not subclasses
             if isinstance(obj, lazy_property):
                 delattr(self, name)
 
diff --git a/grc/core/Element.pyi b/grc/core/Element.pyi
index 46c6d3480d..2a2aed401c 100644
--- a/grc/core/Element.pyi
+++ b/grc/core/Element.pyi
@@ -15,32 +15,27 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
+from typing import Union
+
 from . import Platform, FlowGraph, Block
 
-def lazy_property(func):
-    return func
+lazy_property = property  # fixme: descriptors don't seems to be supported
 
 
 class Element(object):
 
-    def __init__(self, parent=None):
-        ...
+    def __init__(self, parent: Union[None, 'Element'] = None): ...
 
-    @property
-    def parent(self): -> 'Element'
-        ...
+    @lazy_property
+    def parent(self) -> 'Element': ...
 
-    def get_parent_by_type(self, cls): -> 'Element'
-        ...
+    def get_parent_by_type(self, cls) -> Union[None, 'Element']: ...
 
     @lazy_property
-    def parent_platform(self): -> Platform.Platform
-        ...
+    def parent_platform(self) -> Platform.Platform: ...
 
     @lazy_property
-    def parent_flowgraph(self): -> FlowGraph.FlowGraph
-        ...
+    def parent_flowgraph(self) -> FlowGraph.FlowGraph: ...
 
     @lazy_property
-    def parent_block(self): -> Block.Block
-        ...
+    def parent_block(self) -> Block.Block: ...
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index cb2a56ce7d..97a4c37353 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -21,11 +21,9 @@ import imp
 import time
 import re
 from itertools import chain
-from operator import methodcaller, attrgetter
+from operator import methodcaller
 import collections
 
-from six.moves import filter
-
 from . import Messages
 from .Constants import FLOW_GRAPH_FILE_FORMAT_VERSION
 from .Element import Element
@@ -86,7 +84,7 @@ class FlowGraph(Element):
         Returns:
             a sorted list of variable blocks in order of dependency (indep -> dep)
         """
-        variables = list(filter(attrgetter('is_variable'), self.iter_enabled_blocks()))
+        variables = [block for block in self.iter_enabled_blocks() if block.is_variable]
         return expr_utils.sort_objects(variables, methodcaller('get_id'), methodcaller('get_var_make'))
 
     def get_parameters(self):
@@ -142,7 +140,7 @@ class FlowGraph(Element):
         """
         Get an iterator of all blocks that are enabled and not bypassed.
         """
-        return filter(methodcaller('get_enabled'), self.blocks)
+        return (block for block in self.blocks if block.enabled)
 
     def get_enabled_blocks(self):
         """
@@ -160,7 +158,7 @@ class FlowGraph(Element):
         Returns:
             a list of blocks
         """
-        return list(filter(methodcaller('get_bypassed'), self.blocks))
+        return [block for block in self.blocks if block.get_bypassed()]
 
     def get_enabled_connections(self):
         """
@@ -169,7 +167,7 @@ class FlowGraph(Element):
         Returns:
             a list of connections
         """
-        return list(filter(methodcaller('get_enabled'), self.connections))
+        return [connection for connection in self.connections if connection.enabled]
 
     def get_option(self, key):
         """
diff --git a/grc/core/Port.py b/grc/core/Port.py
index f7046ad8c8..0d9298fb05 100644
--- a/grc/core/Port.py
+++ b/grc/core/Port.py
@@ -332,7 +332,7 @@ class Port(Element):
         Returns:
             a list of connection objects
         """
-        return [c for c in self.get_connections() if c.get_enabled()]
+        return [c for c in self.get_connections() if c.enabled]
 
     def get_associated_ports(self):
         if not self.get_type() == 'bus':
diff --git a/grc/core/generator/Generator.py b/grc/core/generator/Generator.py
index dcbf767835..7c9a1eca73 100644
--- a/grc/core/generator/Generator.py
+++ b/grc/core/generator/Generator.py
@@ -145,7 +145,7 @@ class TopBlockGenerator(object):
             return code
 
         blocks_all = expr_utils.sort_objects(
-            [b for b in fg.blocks if b.get_enabled() and not b.get_bypassed()],
+            [b for b in fg.blocks if b.enabled and not b.get_bypassed()],
             operator.methodcaller('get_id'), _get_block_sort_text
         )
         deprecated_block_keys = set(b.name for b in blocks_all if b.is_deprecated)
@@ -198,7 +198,7 @@ class TopBlockGenerator(object):
 
             # Loop through all the downstream connections
             for sink in (c for c in connections if c.source_port == block.sources[0]):
-                if not sink.get_enabled():
+                if not sink.enabled:
                     # Ignore disabled connections
                     continue
                 sink_port = sink.sink_port
diff --git a/grc/core/utils/_complexity.py b/grc/core/utils/_complexity.py
index 6da16eb28d..c0f3ae9de4 100644
--- a/grc/core/utils/_complexity.py
+++ b/grc/core/utils/_complexity.py
@@ -29,7 +29,7 @@ def calculate_flowgraph_complexity(flowgraph):
     blocks = float(len(flowgraph.blocks))
     connections = float(len(flowgraph.connections))
     elements = blocks + connections
-    disabled_connections = sum(not c.get_enabled() for c in flowgraph.connections)
+    disabled_connections = sum(not c.enabled for c in flowgraph.connections)
 
     variables = elements - blocks - connections
     enabled = float(len(flowgraph.get_enabled_blocks()))
diff --git a/grc/gui/Block.py b/grc/gui/Block.py
index e7dc03345b..db147738b6 100644
--- a/grc/gui/Block.py
+++ b/grc/gui/Block.py
@@ -137,7 +137,7 @@ class Block(CoreBlock, Element):
         self._bg_color = (
             Colors.MISSING_BLOCK_BACKGROUND_COLOR if self.is_dummy_block else
             Colors.BLOCK_BYPASSED_COLOR if self.get_bypassed() else
-            Colors.BLOCK_ENABLED_COLOR if self.get_enabled() else
+            Colors.BLOCK_ENABLED_COLOR if self.enabled else
             Colors.BLOCK_DISABLED_COLOR
         )
 
@@ -234,7 +234,7 @@ class Block(CoreBlock, Element):
                 markups.append('<span></span>')
 
             markups.append('<span foreground="{foreground}" font_desc="{font}">{comment}</span>'.format(
-                foreground='#444' if self.get_enabled() else '#888', font=BLOCK_FONT, comment=Utils.encode(comment)
+                foreground='#444' if self.enabled else '#888', font=BLOCK_FONT, comment=Utils.encode(comment)
             ))
         if markups:
             layout = self._comment_layout = Gtk.DrawingArea().create_pango_layout('')
diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py
index 87dd97a520..b6e84f8c89 100644
--- a/grc/gui/Connection.py
+++ b/grc/gui/Connection.py
@@ -171,7 +171,7 @@ class Connection(Element, _Connection):
         # draw
         color1, color2 = (
             Colors.HIGHLIGHT_COLOR if self.highlighted else
-            Colors.CONNECTION_DISABLED_COLOR if not self.get_enabled() else
+            Colors.CONNECTION_DISABLED_COLOR if not self.enabled else
             color for color in (self._color, self._color2))
 
         Element.draw(self, widget, cr, color1, Colors.FLOWGRAPH_BACKGROUND_COLOR)
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index da88636e1b..a3dd379074 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -456,13 +456,13 @@ class FlowGraph(Element, _Flowgraph):
         hide_variables = Actions.TOGGLE_HIDE_VARIABLES.get_active()
 
         def draw_order(elem):
-            return elem.highlighted, elem.is_block, elem.get_enabled()
+            return elem.highlighted, elem.is_block, elem.enabled
 
         elements = sorted(self.get_elements(), key=draw_order)
         del self._elements_to_draw[:]
 
         for element in elements:
-            if hide_disabled_blocks and not element.get_enabled():
+            if hide_disabled_blocks and not element.enabled:
                 continue  # skip hidden disabled blocks and connections
             if hide_variables and (element.is_variable or element.is_import):
                 continue  # skip hidden disabled blocks and connections
@@ -471,7 +471,7 @@ class FlowGraph(Element, _Flowgraph):
     def _drawables(self):
         show_comments = Actions.TOGGLE_SHOW_BLOCK_COMMENTS.get_active()
         for element in self._elements_to_draw:
-            if element.is_block and show_comments and element.get_enabled():
+            if element.is_block and show_comments and element.enabled:
                 yield element.draw_comment
         for element in self._elements_to_draw:
             yield element.draw
diff --git a/grc/gui/VariableEditor.py b/grc/gui/VariableEditor.py
index 11284f5708..09fe629195 100644
--- a/grc/gui/VariableEditor.py
+++ b/grc/gui/VariableEditor.py
@@ -211,7 +211,7 @@ class VariableEditor(Gtk.VBox):
 
         # Block specific changes
         if block:
-            if not block.get_enabled():
+            if not block.enabled:
                 # Disabled block. But, this should still be editable
                 sp('editable', True)
                 sp('foreground', 'gray')
@@ -274,9 +274,9 @@ class VariableEditor(Gtk.VBox):
             else:
                 self.handle_action(None, self.DELETE_BLOCK, None)
         elif key == self.ENABLE_BLOCK:
-            self._block.set_enabled(True)
+            self._block.state = 'enabled'
         elif key == self.DISABLE_BLOCK:
-            self._block.set_enabled(False)
+            self._block.state = 'disabled'
         Actions.VARIABLE_EDITOR_UPDATE()
 
     def _handle_mouse_button_press(self, widget, event):
@@ -318,7 +318,7 @@ class VariableEditor(Gtk.VBox):
                         return True
             elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS:
                 if self._block:
-                    self._context_menu.update_sensitive(True, enabled=self._block.get_enabled())
+                    self._context_menu.update_sensitive(True, enabled=self._block.enabled)
                 else:
                     self._context_menu.update_sensitive(False)
                 self._context_menu.popup(None, None, None, None, event.button, event.time)
@@ -341,10 +341,10 @@ class VariableEditor(Gtk.VBox):
     def _handle_key_button_press(self, widget, event):
         model, path = self.treeview.get_selection().get_selected_rows()
         if path and self._block:
-            if self._block.get_enabled() and event.string == "d":
+            if self._block.enabled and event.string == "d":
                 self.handle_action(None, self.DISABLE_BLOCK, None)
                 return True
-            elif not self._block.get_enabled() and event.string == "e":
+            elif not self._block.enabled and event.string == "e":
                 self.handle_action(None, self.ENABLE_BLOCK, None)
                 return True
         return False
-- 
cgit v1.2.3


From 6f067a5029baaf4be8fcb39c6b22729a0a9e946b Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Fri, 22 Jul 2016 10:08:54 +0200
Subject: grc: various clean-ups and fixes

---
 grc/core/Block.py      | 151 ++++++++++++++++++++++++++++++++-----------------
 grc/core/Connection.py |  22 ++++---
 grc/core/FlowGraph.py  |  49 +---------------
 grc/core/Messages.py   |   4 +-
 grc/gui/Block.py       |  51 ++++++++---------
 grc/gui/Connection.py  |   2 +-
 grc/gui/Constants.py   |   1 +
 grc/gui/FlowGraph.py   |   4 +-
 grc/gui/Port.py        |  20 ++++---
 9 files changed, 152 insertions(+), 152 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/core/Block.py b/grc/core/Block.py
index 9572982bf7..e7e4a8215a 100644
--- a/grc/core/Block.py
+++ b/grc/core/Block.py
@@ -178,17 +178,20 @@ class Block(Element):
                 port.key = str(domain_specific_port_index[domain])
                 domain_specific_port_index[domain] += 1
 
-        # Adjust nports, disconnect hidden ports
+        # Adjust nports
         for ports in (self.sources, self.sinks):
             self._rewrite_nports(ports)
             self.back_ofthe_bus(ports)
             rekey(ports)
 
+        self._rewrite_bus_ports()
+
         # disconnect hidden ports
         for port in itertools.chain(self.sources, self.sinks):
             if port.get_hide():
                 for connection in port.get_connections():
-                    self.parent.remove_element(connection)
+                    self.parent_flowgraph.remove_element(connection)
+
 
         self.active_sources = [p for p in self.get_sources_gui() if not p.get_hide()]
         self.active_sinks = [p for p in self.get_sinks_gui() if not p.get_hide()]
@@ -201,7 +204,7 @@ class Block(Element):
             for clone in port.clones[nports-1:]:
                 # Remove excess connections
                 for connection in clone.get_connections():
-                    self.parent.remove_element(connection)
+                    self.parent_flowgraph.remove_element(connection)
                 port.remove_clone(clone)
                 ports.remove(clone)
             # Add more cloned ports
@@ -256,8 +259,56 @@ class Block(Element):
                 self.add_error_message('Value "{}" cannot be evaluated:\n{}'.format(value, err))
 
     ##############################################
-    # Getters
+    # props
+    ##############################################
+
+    @lazy_property
+    def is_throtteling(self):
+        return BLOCK_FLAG_THROTTLE in self.flags
+
+    @lazy_property
+    def is_deprecated(self):
+        return BLOCK_FLAG_DEPRECATED in self.flags
+
+    @property
+    def documentation(self):
+        documentation = self.parent_platform.block_docstrings.get(self.key, {})
+        from_xml = self._doc.strip()
+        if from_xml:
+            documentation[''] = from_xml
+        return documentation
+
+    @property
+    def comment(self):
+        return self.params['comment'].get_value()
+
+    @property
+    def state(self):
+        """Gets the block's current state."""
+        try:
+            return self.STATE_LABELS[int(self.states['_enabled'])]
+        except ValueError:
+            return 'enabled'
+
+    @state.setter
+    def state(self, value):
+        """Sets the state for the block."""
+        try:
+            encoded = self.STATE_LABELS.index(value)
+        except ValueError:
+            encoded = 1
+        self.states['_enabled'] = encoded
+
+    # Enable/Disable Aliases
+    @property
+    def enabled(self):
+        """Get the enabled state of the block"""
+        return self.state != 'disabled'
+
+    ##############################################
+    # Getters (old)
     ##############################################
+
     def get_imports(self, raw=False):
         """
         Resolve all import statements.
@@ -304,39 +355,6 @@ class Block(Element):
     def is_virtual_source(self):
         return self.key == 'virtual_source'
 
-    @property
-    def documentation(self):
-        documentation = self.parent_platform.block_docstrings.get(self.key, {})
-        from_xml = self._doc.strip()
-        if from_xml:
-            documentation[''] = from_xml
-        return documentation
-
-    # Main functions to get and set the block state
-    # Also kept get_enabled and set_enabled to keep compatibility
-    @property
-    def state(self):
-        """Gets the block's current state."""
-        try:
-            return self.STATE_LABELS[int(self.states['_enabled'])]
-        except ValueError:
-            return 'enabled'
-
-    @state.setter
-    def state(self, value):
-        """Sets the state for the block."""
-        try:
-            encoded = self.STATE_LABELS.index(value)
-        except ValueError:
-            encoded = 1
-        self.states['_enabled'] = encoded
-
-    # Enable/Disable Aliases
-    @property
-    def enabled(self):
-        """Get the enabled state of the block"""
-        return self.state != 'disabled'
-
     # Block bypassing
     def get_bypassed(self):
         """
@@ -389,17 +407,6 @@ class Block(Element):
     def get_children_gui(self):
         return self.get_ports_gui() + self.params.values()
 
-    def get_comment(self):
-        return self.params['comment'].get_value()
-
-    @lazy_property
-    def is_throtteling(self):
-        return BLOCK_FLAG_THROTTLE in self.flags
-
-    @lazy_property
-    def is_deprecated(self):
-        return BLOCK_FLAG_DEPRECATED in self.flags
-
     ##############################################
     # Access
     ##############################################
@@ -597,6 +604,44 @@ class Block(Element):
         if self._bussify_source:
             self.bussify('source')
 
+    def _rewrite_bus_ports(self):
+        return  # fixme: probably broken
+
+        def doit(ports, ports_gui, direc):
+            if not self.current_bus_structure[direc]:
+                return
+
+            bus_structure = self.form_bus_structure(direc)
+            for port in ports_gui[len(bus_structure):]:
+                for connect in port.get_connections():
+                    self.parent_flowgraph.remove_element(connect)
+                ports.remove(port)
+
+            port_factory = self.parent_platform.get_new_port
+
+            if len(ports_gui) < len(bus_structure):
+                for i in range(len(ports_gui), len(bus_structure)):
+                    port = port_factory(self, direction=direc, key=str(1 + i),
+                                        name='bus', type='bus')
+                    ports.append(port)
+
+        doit(self.sources, self.get_sources_gui(), 'source')
+        doit(self.sinks, self.get_sinks_gui(), 'sink')
+
+        if 'bus' in [a.get_type() for a in self.get_sources_gui()]:
+            for i in range(len(self.get_sources_gui())):
+                if not self.get_sources_gui()[i].get_connections():
+                    continue
+                source = self.get_sources_gui()[i]
+                sink = []
+
+                for j in range(len(source.get_connections())):
+                    sink.append(source.get_connections()[j].sink_port)
+                for elt in source.get_connections():
+                    self.parent_flowgraph.remove_element(elt)
+                for j in sink:
+                    self.parent_flowgraph.connect(source, j)
+
 
 class EPyBlock(Block):
 
@@ -711,11 +756,11 @@ class DummyBlock(Block):
     build_in_param_keys = 'id alias affinity minoutbuf maxoutbuf comment'
 
     def __init__(self, parent, key, missing_key, params_n):
-        params = [{'key': p['key'], 'name': p['key'], 'type': 'string'}
-                  for p in params_n if p['key'] not in self.build_in_param_keys]
-        super(DummyBlock, self).__init__(
-            parent=parent, key=missing_key, name='Missing Block', param=params,
-        )
+        super(DummyBlock, self).__init__(parent=parent, key=missing_key, name='Missing Block')
+        param_factory = self.parent_platform.get_new_param
+        for param_n in params_n:
+            key = param_n['key']
+            self.params.setdefault(key, param_factory(self, key=key, name=key, type='string'))
 
     def is_valid(self):
         return False
diff --git a/grc/core/Connection.py b/grc/core/Connection.py
index 6be1ccb2aa..63c6a94571 100644
--- a/grc/core/Connection.py
+++ b/grc/core/Connection.py
@@ -31,7 +31,7 @@ class Connection(Element):
 
     is_connection = True
 
-    def __init__(self, flow_graph, porta, portb):
+    def __init__(self, parent, porta, portb):
         """
         Make a new connection given the parent and 2 ports.
 
@@ -44,7 +44,7 @@ class Connection(Element):
         Returns:
             a new connection
         """
-        Element.__init__(self, flow_graph)
+        Element.__init__(self, parent)
 
         source, sink = self._get_sink_source(porta, portb)
 
@@ -52,14 +52,16 @@ class Connection(Element):
         self.sink_port = sink
 
         # Ensure that this connection (source -> sink) is unique
-        for connection in flow_graph.connections:
-            if connection.source_port is source and connection.sink_port is sink:
-                raise LookupError('This connection between source and sink is not unique.')
+        if self in self.parent_flowgraph.connections:
+            raise LookupError('This connection between source and sink is not unique.')
 
         if self.is_bus():
             self._make_bus_connect()
-        else:
-            self.parent_flowgraph.connect(source, sink)
+
+    def __eq__(self, other):
+        if not isinstance(other, self.__class__):
+            return NotImplemented
+        return self.source_port == other.source_port and self.sink_port == other.sink_port
 
     @staticmethod
     def _get_sink_source(porta, portb):
@@ -68,7 +70,7 @@ class Connection(Element):
         for port in (porta, portb):
             if port.is_source:
                 source = port
-            else:
+            if port.is_sink:
                 sink = port
         if not source:
             raise ValueError('Connection could not isolate source')
@@ -110,10 +112,6 @@ class Connection(Element):
         Validate the connections.
         The ports must match in io size.
         """
-        """
-        Validate the connections.
-        The ports must match in type.
-        """
         Element.validate(self)
         platform = self.parent_platform
 
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index 97a4c37353..8246d86f44 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -20,7 +20,6 @@ from __future__ import absolute_import, print_function
 import imp
 import time
 import re
-from itertools import chain
 from operator import methodcaller
 import collections
 
@@ -201,10 +200,7 @@ class FlowGraph(Element):
         Flag the namespace to be renewed.
         """
         self.renew_namespace()
-        for child in chain(self.blocks, self.connections):
-            child.rewrite()
-
-        self.bus_ports_rewrite()
+        Element.rewrite(self)
 
     def renew_namespace(self):
         namespace = {}
@@ -299,7 +295,7 @@ class FlowGraph(Element):
         """
 
         connection = self.parent_platform.Connection(
-            flow_graph=self, porta=porta, portb=portb)
+            parent=self, porta=porta, portb=portb)
         self.connections.append(connection)
         return connection
 
@@ -453,47 +449,6 @@ class FlowGraph(Element):
         self.rewrite()  # global rewrite
         return errors
 
-    ##############################################
-    # Needs to go
-    ##############################################
-    def bus_ports_rewrite(self):
-        # todo: move to block.rewrite()
-        def doit(block, ports, ports_gui, direc):
-            bus_structure = block.form_bus_structure(direc)
-
-            if any('bus' == a.get_type() for a in ports_gui):
-                if len(ports_gui) > len(bus_structure):
-                    for _ in range(len(bus_structure), len(ports_gui)):
-                        for connect in ports_gui[-1].get_connections():
-                            block.parent.remove_element(connect)
-                        ports.remove(ports_gui[-1])
-                elif len(ports_gui) < len(bus_structure):
-                    n = {'name': 'bus', 'type': 'bus'}
-                    if any(isinstance(a.get_nports(), int) for a in ports):
-                        n['nports'] = str(1)
-                    for _ in range(len(ports_gui), len(bus_structure)):
-                        n['key'] = str(len(ports))
-                        port = block.parent.parent.Port(block=block, n=dict(n), dir=direc)
-                        ports.append(port)
-
-            if 'bus' in [a.get_type() for a in block.get_sources_gui()]:
-                for i in range(len(block.get_sources_gui())):
-                    if not block.get_sources_gui()[i].get_connections():
-                        continue
-                    source = block.get_sources_gui()[i]
-                    sink = []
-
-                    for j in range(len(source.get_connections())):
-                        sink.append(source.get_connections()[j].sink_port)
-                    for elt in source.get_connections():
-                        self.remove_element(elt)
-                    for j in sink:
-                        self.connect(source, j)
-
-        for blk in self.blocks:
-            doit(blk, blk.sources, blk.get_sources_gui(), 'source')
-            doit(blk, blk.sinks, blk.get_sinks_gui(), 'sink')
-
 
 def _update_old_message_port_keys(source_key, sink_key, source_block, sink_block):
     """
diff --git a/grc/core/Messages.py b/grc/core/Messages.py
index 596b6197d8..c2d216ef61 100644
--- a/grc/core/Messages.py
+++ b/grc/core/Messages.py
@@ -125,8 +125,8 @@ def send_fail_save(file_path):
     send('>>> Error: Cannot save: %s\n' % file_path)
 
 
-def send_fail_connection():
-    send('>>> Error: Cannot create connection.\n')
+def send_fail_connection(msg=''):
+    send('>>> Error: Cannot create connection.\n' + ('\t' + str(msg) if msg else ''))
 
 
 def send_fail_load_preferences(prefs_file_path):
diff --git a/grc/gui/Block.py b/grc/gui/Block.py
index db147738b6..d1f67d6586 100644
--- a/grc/gui/Block.py
+++ b/grc/gui/Block.py
@@ -55,8 +55,9 @@ class Block(CoreBlock, Element):
         self._surface_layout_offsets = 0, 0
         self._comment_layout = None
 
+        self._border_color = (Colors.MISSING_BLOCK_BORDER_COLOR if self.is_dummy_block else
+                              Colors.BORDER_COLOR)
         self._bg_color = Colors.BLOCK_ENABLED_COLOR
-        self.has_busses = [False, False]  # source, sink
 
     @property
     def coordinate(self):
@@ -113,7 +114,8 @@ class Block(CoreBlock, Element):
         elif self.is_vertical():
             self.areas.append([0, 0, self.height, self.width])
 
-        for ports, has_busses in zip((self.active_sources, self.active_sinks), self.has_busses):
+        bussified = self.current_bus_structure['source'], self.current_bus_structure['sink']
+        for ports, has_busses in zip((self.active_sources, self.active_sinks), bussified):
             if not ports:
                 continue
             port_separation = PORT_SEPARATION if not has_busses else ports[0].height + PORT_SPACING
@@ -177,25 +179,26 @@ class Block(CoreBlock, Element):
 
         self.create_port_labels()
 
-        def get_min_height_for_ports():
+        def get_min_height_for_ports(ports):
             min_height = 2 * PORT_BORDER_SEPARATION + len(ports) * PORT_SEPARATION
             if ports:
                 min_height -= ports[-1].height
             return min_height
 
-        height = max(
-            [  # labels
-                height
-            ] +
-            [  # ports
-                get_min_height_for_ports() for ports in (self.active_sources, self.active_sinks)
-            ] +
-            [  # bus ports only
-                2 * PORT_BORDER_SEPARATION +
-                sum([port.height + PORT_SPACING for port in ports if port.get_type() == 'bus']) - PORT_SPACING
-                for ports in (self.get_sources_gui(), self.get_sinks_gui())
-            ]
-        )
+        height = max(height,
+                     get_min_height_for_ports(self.active_sinks),
+                     get_min_height_for_ports(self.active_sources))
+
+        def get_min_height_for_bus_ports(ports):
+            return 2 * PORT_BORDER_SEPARATION + sum(
+                port.height + PORT_SPACING for port in ports if port.get_type() == 'bus'
+            ) - PORT_SPACING
+
+        if self.current_bus_structure['sink']:
+            height = max(height, get_min_height_for_bus_ports(self.active_sinks))
+        if self.current_bus_structure['source']:
+            height = max(height, get_min_height_for_bus_ports(self.active_sources))
+
         self.width, self.height = width, height = Utils.align_to_grid((width, height))
 
         self._surface_layout_offsets = [
@@ -203,10 +206,6 @@ class Block(CoreBlock, Element):
             (height - label_height) / 2.0
         ]
 
-        self.has_busses = [
-            any(port.get_type() == 'bus' for port in ports)
-            for ports in (self.get_sources_gui(), self.get_sinks_gui())
-        ]
         self.create_comment_layout()
 
     def create_port_labels(self):
@@ -226,9 +225,9 @@ class Block(CoreBlock, Element):
             complexity = utils.calculate_flowgraph_complexity(self.parent)
             markups.append(
                 '<span foreground="#444" size="medium" font_desc="{font}">'
-                '<b>Complexity: {num}bal</b></span>'.format(num=utils.num_to_str(complexity), font=BLOCK_FONT)
+                '<b>Complexity: {num}bal</b></span>'.format(num=Utils.num_to_str(complexity), font=BLOCK_FONT)
             )
-        comment = self.get_comment()  # Returns None if there are no comments
+        comment = self.comment  # Returns None if there are no comments
         if comment:
             if markups:
                 markups.append('<span></span>')
@@ -242,16 +241,12 @@ class Block(CoreBlock, Element):
         else:
             self._comment_layout = None
 
-    def draw(self, widget, cr):
+    def draw(self, widget, cr, border_color=None, bg_color=None):
         """
         Draw the signal block with label and inputs/outputs.
         """
         bg_color = self._bg_color
-        border_color = (
-            Colors.HIGHLIGHT_COLOR if self.highlighted else
-            Colors.MISSING_BLOCK_BORDER_COLOR if self.is_dummy_block else
-            Colors.BORDER_COLOR
-        )
+        border_color = Colors.HIGHLIGHT_COLOR if self.highlighted else self._border_color
         # draw main block
         Element.draw(self, widget, cr, border_color, bg_color)
         for port in self.active_ports():
diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py
index b6e84f8c89..9b483383ac 100644
--- a/grc/gui/Connection.py
+++ b/grc/gui/Connection.py
@@ -152,7 +152,7 @@ class Connection(Element, _Connection):
             # create right-angled connector
             self.lines.append([p0, p1, point, p2, p3])
 
-    def draw(self, widget, cr):
+    def draw(self, widget, cr, border_color=None, bg_color=None):
         """
         Draw the connection.
         """
diff --git a/grc/gui/Constants.py b/grc/gui/Constants.py
index 5c55c4180e..516aaf92f4 100644
--- a/grc/gui/Constants.py
+++ b/grc/gui/Constants.py
@@ -72,6 +72,7 @@ PORT_SEPARATION = 32
 
 PORT_MIN_WIDTH = 20
 PORT_LABEL_HIDDEN_WIDTH = 10
+PORT_EXTRA_BUS_HEIGHT = 40
 
 # minimal length of connector
 CONNECTOR_EXTENSION_MINIMAL = 11
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index a3dd379074..6ff4507df2 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -153,8 +153,8 @@ class FlowGraph(Element, _Flowgraph):
             try:
                 self.connect(self._old_selected_port, self._new_selected_port)
                 Actions.ELEMENT_CREATE()
-            except:
-                Messages.send_fail_connection()
+            except Exception as e:
+                Messages.send_fail_connection(e)
             self._old_selected_port = None
             self._new_selected_port = None
             return True
diff --git a/grc/gui/Port.py b/grc/gui/Port.py
index 991036cb99..6776963c63 100644
--- a/grc/gui/Port.py
+++ b/grc/gui/Port.py
@@ -42,8 +42,9 @@ class Port(_Port, Element):
         self._connector_coordinate = (0, 0)
         self._hovering = False
         self.force_show_label = False
-        self._bg_color = (0, 0, 0)
+        self._bg_color = 0, 0, 0
         self._line_width_factor = 1.0
+        self._label_layout_offsets = 0, 0
 
         self.width_with_label = self.height = 0
         self.connector_length = 0
@@ -109,8 +110,10 @@ class Port(_Port, Element):
 
         self.width = 2 * Constants.PORT_LABEL_PADDING + label_width
         self.height = 2 * Constants.PORT_LABEL_PADDING + label_height
+        self._label_layout_offsets = [0, Constants.PORT_LABEL_PADDING]
         if self.get_type() == 'bus':
-            self.height += 2 * label_height
+            self.height += Constants.PORT_EXTRA_BUS_HEIGHT
+            self._label_layout_offsets[1] += Constants.PORT_EXTRA_BUS_HEIGHT / 2
         self.height += self.height % 2  # uneven height
 
     def draw(self, widget, cr, border_color, bg_color):
@@ -126,7 +129,7 @@ class Port(_Port, Element):
         if self.is_vertical():
             cr.rotate(-math.pi / 2)
             cr.translate(-self.width, 0)
-        cr.translate(0, Constants.PORT_LABEL_PADDING)
+        cr.translate(*self._label_layout_offsets)
 
         PangoCairo.update_layout(cr, self.label_layout)
         PangoCairo.show_layout(cr, self.label_layout)
@@ -138,8 +141,11 @@ class Port(_Port, Element):
         Returns:
             the connector coordinate (x, y) tuple
         """
-        return [sum(c) for c in zip(self._connector_coordinate, self.coordinate,
-                                    self.parent_block.coordinate)]
+        return [sum(c) for c in zip(
+            self._connector_coordinate,   # relative to port
+            self.coordinate,              # relative to block
+            self.parent_block.coordinate  # abs
+        )]
 
     def get_connector_direction(self):
         """
@@ -167,7 +173,7 @@ class Port(_Port, Element):
         Args:
             direction: degrees to rotate
         """
-        self.parent.rotate(direction)
+        self.parent_block.rotate(direction)
 
     def move(self, delta_coor):
         """
@@ -176,7 +182,7 @@ class Port(_Port, Element):
         Args:
             delta_corr: the (delta_x, delta_y) tuple
         """
-        self.parent.move(delta_coor)
+        self.parent_block.move(delta_coor)
 
     @property
     def highlighted(self):
-- 
cgit v1.2.3


From 2cd2b5b781bc656afb333c8fd8142244e92b19de Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Fri, 29 Jul 2016 16:09:24 +0200
Subject: grc: gtk3: fix bug in mouse motion handling

---
 grc/gui/DrawingArea.py | 5 +++--
 grc/gui/FlowGraph.py   | 4 ++--
 2 files changed, 5 insertions(+), 4 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/gui/DrawingArea.py b/grc/gui/DrawingArea.py
index 33c669c99f..cc82c70c57 100644
--- a/grc/gui/DrawingArea.py
+++ b/grc/gui/DrawingArea.py
@@ -117,8 +117,10 @@ class DrawingArea(Gtk.DrawingArea):
         self.button_state[event.button] = True
 
         if event.button == 1:
+            double_click = (event.type == Gdk.EventType._2BUTTON_PRESS)
+            self.button_state[1] = not double_click
             self._flow_graph.handle_mouse_selector_press(
-                double_click=(event.type == Gdk.EventType._2BUTTON_PRESS),
+                double_click=double_click,
                 coordinate=self._translate_event_coords(event),
             )
         elif event.button == 3:
@@ -151,7 +153,6 @@ class DrawingArea(Gtk.DrawingArea):
 
         self._flow_graph.handle_mouse_motion(
             coordinate=self._translate_event_coords(event),
-            button1_pressed=self.button_state[1]
         )
 
     def _auto_scroll(self, event):
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index 6ff4507df2..d57323988f 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -687,7 +687,7 @@ class FlowGraph(Element, _Flowgraph):
             self.element_moved = False
         self.update_selected_elements()
 
-    def handle_mouse_motion(self, coordinate, button1_pressed):
+    def handle_mouse_motion(self, coordinate):
         """
         The mouse has moved, respond to mouse dragging or notify elements
         Move a selected element to the new coordinate.
@@ -695,7 +695,7 @@ class FlowGraph(Element, _Flowgraph):
         """
         # to perform a movement, the mouse must be pressed
         # (no longer checking pending events via Gtk.events_pending() - always true in Windows)
-        if not button1_pressed:
+        if not self.mouse_pressed:
             self._handle_mouse_motion_move(coordinate)
         else:
             self._handle_mouse_motion_drag(coordinate)
-- 
cgit v1.2.3


From 52dadbf46f16b682348a6969a782ff64a129d9f8 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Sun, 31 Jul 2016 21:13:17 +0200
Subject: grc: refactor: handle flowgraph and connection super init same as in
 block

---
 grc/core/FlowGraph.py | 6 +++---
 grc/core/Platform.py  | 2 +-
 grc/gui/Connection.py | 8 ++++----
 grc/gui/FlowGraph.py  | 9 +++++----
 4 files changed, 13 insertions(+), 12 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index 8246d86f44..18a5778015 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -40,17 +40,17 @@ class FlowGraph(Element):
 
     is_flow_graph = True
 
-    def __init__(self, platform):
+    def __init__(self, parent):
         """
         Make a flow graph from the arguments.
 
         Args:
-            platform: a platforms with blocks and contrcutors
+            parent: a platforms with blocks and element factories
 
         Returns:
             the flow graph object
         """
-        Element.__init__(self, parent=platform)
+        Element.__init__(self, parent)
         self._timestamp = time.ctime()
         self._options_block = self.parent_platform.get_new_block(self, 'options')
 
diff --git a/grc/core/Platform.py b/grc/core/Platform.py
index 2a8764ad0d..9956391055 100644
--- a/grc/core/Platform.py
+++ b/grc/core/Platform.py
@@ -326,7 +326,7 @@ class Platform(Element):
     }
 
     def get_new_flow_graph(self):
-        return self.FlowGraph(platform=self)
+        return self.FlowGraph(parent=self)
 
     def get_new_block(self, parent, key, **kwargs):
         cls = self.block_classes.get(key, self.block_classes[None])
diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py
index b1ae32ddcc..949840401e 100644
--- a/grc/gui/Connection.py
+++ b/grc/gui/Connection.py
@@ -24,10 +24,10 @@ from .Constants import CONNECTOR_ARROW_BASE, CONNECTOR_ARROW_HEIGHT
 from .Element import Element
 
 from ..core.Element import nop_write
-from ..core.Connection import Connection as _Connection
+from ..core.Connection import Connection as CoreConnection
 
 
-class Connection(Element, _Connection):
+class Connection(CoreConnection, Element):
     """
     A graphical connection for ports.
     The connection has 2 parts, the arrow and the wire.
@@ -38,8 +38,8 @@ class Connection(Element, _Connection):
     """
 
     def __init__(self, *args, **kwargs):
+        super(self.__class__, self).__init__(*args, **kwargs)
         Element.__init__(self)
-        _Connection.__init__(self, *args, **kwargs)
 
         self._color = self._color2 = self._arrow_color = None
 
@@ -64,7 +64,7 @@ class Connection(Element, _Connection):
         return 0
 
     def create_shapes(self):
-        """Precalculate relative coordinates."""
+        """Pre-calculate relative coordinates."""
         Element.create_shapes(self)
         self._sink_rot = None
         self._source_rot = None
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index d57323988f..5cd575dabe 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -34,24 +34,25 @@ from . import Actions, Colors, Utils, Bars, Dialogs
 from .Element import Element
 from .external_editor import ExternalEditor
 
-from ..core.FlowGraph import FlowGraph as _Flowgraph
+from ..core.FlowGraph import FlowGraph as CoreFlowgraph
 from ..core import Messages
 
 
-class FlowGraph(Element, _Flowgraph):
+class FlowGraph(CoreFlowgraph, Element):
     """
     FlowGraph is the data structure to store graphical signal blocks,
     graphical inputs and outputs,
     and the connections between inputs and outputs.
     """
 
-    def __init__(self, **kwargs):
+    def __init__(self, parent, **kwargs):
         """
         FlowGraph constructor.
         Create a list for signal blocks and connections. Connect mouse handlers.
         """
+        super(self.__class__, self).__init__(parent, **kwargs)
         Element.__init__(self)
-        _Flowgraph.__init__(self, **kwargs)
+        self.drawing_area = None
         # important vars dealing with mouse event tracking
         self.element_moved = False
         self.mouse_pressed = False
-- 
cgit v1.2.3


From 979cab9bf5d5986c3df3ea97d0082ed41d313190 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Mon, 1 Aug 2016 17:27:09 +0200
Subject: grc: gtk3: better lables/shapes handling during flowgraph update

---
 grc/gui/Element.py   | 34 +++++++++++++++-------------------
 grc/gui/FlowGraph.py | 14 ++++++++++----
 2 files changed, 25 insertions(+), 23 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/gui/Element.py b/grc/gui/Element.py
index 73be7b8c92..81a5cbfc40 100644
--- a/grc/gui/Element.py
+++ b/grc/gui/Element.py
@@ -76,25 +76,6 @@ class Element(object):
         rotation = rotation or self.rotation
         return rotation in (90, 270)
 
-    def create_labels(self):
-        """
-        Create labels (if applicable) and call on all children.
-        Call this base method before creating labels in the element.
-        """
-        for child in self.get_children():
-            child.create_labels()
-
-    def create_shapes(self):
-        """
-        Create shapes (if applicable) and call on all children.
-        Call this base method before creating shapes in the element.
-        """
-        for child in self.get_children():
-            child.create_shapes()
-
-    def draw(self, widget, cr):
-        raise NotImplementedError()
-
     def rotate(self, rotation):
         """
         Rotate all of the areas by 90 degrees.
@@ -115,6 +96,21 @@ class Element(object):
         dx, dy = delta_coor
         self.coordinate = (x + dx, y + dy)
 
+    def create_labels(self):
+        """
+        Create labels (if applicable) and call on all children.
+        Call this base method before creating labels in the element.
+        """
+
+    def create_shapes(self):
+        """
+        Create shapes (if applicable) and call on all children.
+        Call this base method before creating shapes in the element.
+        """
+
+    def draw(self, widget, cr):
+        raise NotImplementedError()
+
     def bounds_from_area(self, area):
         x1, y1, w, h = area
         x2 = x1 + w
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index 5cd575dabe..83796f35fd 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -1,5 +1,5 @@
 """
-Copyright 2007-2011 Free Software Foundation, Inc.
+Copyright 2007-2011, 2016q Free Software Foundation, Inc.
 This file is part of GNU Radio
 
 GNU Radio Companion is free software; you can redistribute it and/or
@@ -168,6 +168,7 @@ class FlowGraph(CoreFlowgraph, Element):
         """
         self.rewrite()
         self.validate()
+        self.update_elements_to_draw()
         self.create_labels()
         self.create_shapes()
 
@@ -469,6 +470,14 @@ class FlowGraph(CoreFlowgraph, Element):
                 continue  # skip hidden disabled blocks and connections
             self._elements_to_draw.append(element)
 
+    def create_labels(self):
+        for element in self._elements_to_draw:
+            element.create_labels()
+
+    def create_shapes(self):
+        for element in self._elements_to_draw:
+            element.create_shapes()
+
     def _drawables(self):
         show_comments = Actions.TOGGLE_SHOW_BLOCK_COMMENTS.get_active()
         for element in self._elements_to_draw:
@@ -479,9 +488,6 @@ class FlowGraph(CoreFlowgraph, Element):
 
     def draw(self, widget, cr):
         """Draw blocks connections comment and select rectangle"""
-        # todo: only update if required, duplicate logic in
-        self.update_elements_to_draw()
-
         for draw_element in self._drawables():
             cr.save()
             draw_element(widget, cr)
-- 
cgit v1.2.3


From cbe1e43b0f0d1ee0d356b7110700400578855ac6 Mon Sep 17 00:00:00 2001
From: Glenn Richardson <glenn.richardson@live.com>
Date: Tue, 2 Aug 2016 22:45:02 +0200
Subject: grc: gtk3: fixup dialogs

---
 grc/gui/ActionHandler.py |  30 ++--
 grc/gui/Dialogs.py       | 401 +++++++++++++++++++++++++++++------------------
 grc/gui/FileDialogs.py   | 101 ++++++------
 grc/gui/FlowGraph.py     |   6 +-
 grc/gui/MainWindow.py    |   8 +-
 grc/gui/ParamWidgets.py  |   2 +-
 grc/gui/PropsDialog.py   |   1 +
 7 files changed, 326 insertions(+), 223 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py
index 237dd6c84c..9e57565772 100644
--- a/grc/gui/ActionHandler.py
+++ b/grc/gui/ActionHandler.py
@@ -26,10 +26,7 @@ import subprocess
 from gi.repository import Gtk
 from gi.repository import GObject
 
-from . import Dialogs, Preferences, Actions, Executor, Constants
-from .FileDialogs import (OpenFlowGraphFileDialog, SaveFlowGraphFileDialog,
-                          SaveConsoleFileDialog, SaveScreenShotDialog,
-                          OpenQSSFileDialog)
+from . import Dialogs, Preferences, Actions, Executor, Constants, FileDialogs
 from .MainWindow import MainWindow
 from .ParserErrorsDialog import ParserErrorsDialog
 from .PropsDialog import PropsDialog
@@ -379,13 +376,13 @@ class ActionHandler:
         # Window stuff
         ##################################################
         elif action == Actions.ABOUT_WINDOW_DISPLAY:
-            Dialogs.AboutDialog(self.platform.config)
+            Dialogs.show_about(main, self.platform.config)
         elif action == Actions.HELP_WINDOW_DISPLAY:
-            Dialogs.HelpDialog()
+            Dialogs.show_help(main)
         elif action == Actions.TYPES_WINDOW_DISPLAY:
-            Dialogs.TypesDialog(self.platform)
+            Dialogs.show_types(main)
         elif action == Actions.ERRORS_WINDOW_DISPLAY:
-            Dialogs.ErrorsDialog(flow_graph)
+            Dialogs.ErrorsDialog(main, flow_graph).run_and_destroy()
         elif action == Actions.TOGGLE_CONSOLE_WINDOW:
             main.update_panel_visibility(main.CONSOLE, action.get_active())
             action.save_to_preferences()
@@ -401,7 +398,7 @@ class ActionHandler:
         elif action == Actions.CLEAR_CONSOLE:
             main.text_display.clear()
         elif action == Actions.SAVE_CONSOLE:
-            file_path = SaveConsoleFileDialog(page.file_path).run()
+            file_path = FileDialogs.SaveConsole(main, page.file_path).run()
             if file_path is not None:
                 main.text_display.save(file_path)
         elif action == Actions.TOGGLE_HIDE_DISABLED_BLOCKS:
@@ -437,7 +434,7 @@ class ActionHandler:
                     # Leave it enabled
                     action.set_sensitive(True)
                     action.set_active(True)
-            #Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR.set_sensitive(action.get_active())
+            # Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR.set_sensitive(action.get_active())
             action.save_to_preferences()
         elif action == Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR:
             if self.init:
@@ -511,7 +508,7 @@ class ActionHandler:
                 flow_graph._options_block.get_param('generate_options').set_value(args[0])
                 flow_graph_update()
         elif action == Actions.FLOW_GRAPH_OPEN:
-            file_paths = args if args else OpenFlowGraphFileDialog(page.file_path).run()
+            file_paths = args if args else FileDialogs.OpenFlowGraph(main, page.file_path).run()
             if file_paths: # Open a new page for each file, show only the first
                 for i,file_path in enumerate(file_paths):
                     main.new_page(file_path, show=(i==0))
@@ -519,8 +516,8 @@ class ActionHandler:
                     main.tool_bar.refresh_submenus()
                     main.menu_bar.refresh_submenus()
         elif action == Actions.FLOW_GRAPH_OPEN_QSS_THEME:
-            file_paths = OpenQSSFileDialog(self.platform.config.install_prefix +
-                                           '/share/gnuradio/themes/').run()
+            file_paths = FileDialogs.OpenQSS(main, self.platform.config.install_prefix +
+                                             '/share/gnuradio/themes/').run()
             if file_paths:
                 try:
                     prefs = self.platform.config.prefs
@@ -544,7 +541,7 @@ class ActionHandler:
                     Messages.send_fail_save(page.file_path)
                     page.saved = False
         elif action == Actions.FLOW_GRAPH_SAVE_AS:
-            file_path = SaveFlowGraphFileDialog(page.file_path).run()
+            file_path = FileDialogs.SaveFlowGraph(main, page.file_path).run()
             if file_path is not None:
                 page.file_path = os.path.abspath(file_path)
                 Actions.FLOW_GRAPH_SAVE()
@@ -552,7 +549,7 @@ class ActionHandler:
                 main.tool_bar.refresh_submenus()
                 main.menu_bar.refresh_submenus()
         elif action == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
-            file_path, background_transparent = SaveScreenShotDialog(page.file_path).run()
+            file_path, background_transparent = FileDialogs.SaveScreenShot(main, page.file_path).run()
             if file_path is not None:
                 pixbuf = flow_graph.get_drawing_area().get_screenshot(background_transparent)
                 pixbuf.save(file_path, Constants.IMAGE_FILE_EXTENSION[1:])
@@ -576,9 +573,10 @@ class ActionHandler:
             if not page.process:
                 Actions.FLOW_GRAPH_GEN()
                 xterm = self.platform.config.xterm_executable
+                Dialogs.show_missing_xterm(main, xterm)
                 if Preferences.xterm_missing() != xterm:
                     if not os.path.exists(xterm):
-                        Dialogs.MissingXTermDialog(xterm)
+                        Dialogs.show_missing_xterm(main, xterm)
                     Preferences.xterm_missing(xterm)
                 if page.saved and page.file_path:
                     Executor.ExecFlowGraphThread(
diff --git a/grc/gui/Dialogs.py b/grc/gui/Dialogs.py
index da271fe46e..da4b11a3b6 100644
--- a/grc/gui/Dialogs.py
+++ b/grc/gui/Dialogs.py
@@ -1,38 +1,36 @@
-"""
-Copyright 2008, 2009 Free Software Foundation, Inc.
-This file is part of GNU Radio
-
-GNU Radio Companion is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-GNU Radio Companion is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
-"""
+# Copyright 2008, 2009, 2016 Free Software Foundation, Inc.
+# This file is part of GNU Radio
+#
+# GNU Radio Companion is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# GNU Radio Companion is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
 from __future__ import absolute_import
-import gi
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GObject
 
 import sys
+import textwrap
 from distutils.spawn import find_executable
 
+from gi.repository import Gtk
+
 from . import Utils, Actions, Constants
 from ..core import Messages
 
 
 class SimpleTextDisplay(Gtk.TextView):
-    """A non editable gtk text view."""
+    """
+    A non user-editable gtk text view.
+    """
 
     def __init__(self, text=''):
         """
@@ -41,16 +39,18 @@ class SimpleTextDisplay(Gtk.TextView):
         Args:
             text: the text to display (string)
         """
-        text_buffer = Gtk.TextBuffer()
-        text_buffer.set_text(text)
-        self.set_text = text_buffer.set_text
-        GObject.GObject.__init__(self)
+        Gtk.TextView.__init__(self)
+        self.set_text = self.get_buffer().set_text
+        self.set_text(text)
         self.set_editable(False)
         self.set_cursor_visible(False)
         self.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
 
 
 class TextDisplay(SimpleTextDisplay):
+    """
+    A non user-editable scrollable text view with popup menu.
+    """
 
     def __init__(self, text=''):
         """
@@ -64,54 +64,77 @@ class TextDisplay(SimpleTextDisplay):
         self.connect("populate-popup", self.populate_popup)
 
     def insert(self, line):
-        # make backspaces work
+        """
+        Append text after handling backspaces and auto-scroll.
+
+        Args:
+            line: the text to append (string)
+        """
         line = self._consume_backspaces(line)
-        # add the remaining text to buffer
         self.get_buffer().insert(self.get_buffer().get_end_iter(), line)
-        # Automatically scroll on insert
         self.scroll_to_end()
 
     def _consume_backspaces(self, line):
-        """removes text from the buffer if line starts with \b*"""
-        if not line: return
+        """
+        Removes text from the buffer if line starts with '\b'
+
+        Args:
+            line: a string which may contain backspaces
+
+        Returns:
+            The string that remains from 'line' with leading '\b's removed.
+        """
+        if not line:
+            return
+
         # for each \b delete one char from the buffer
         back_count = 0
         start_iter = self.get_buffer().get_end_iter()
         while line[back_count] == '\b':
             # stop at the beginning of a line
-            if not start_iter.starts_line(): start_iter.backward_char()
+            if not start_iter.starts_line():
+                start_iter.backward_char()
             back_count += 1
-        # remove chars
+        # remove chars from buffer
         self.get_buffer().delete(start_iter, self.get_buffer().get_end_iter())
-        # return remaining text
         return line[back_count:]
 
     def scroll_to_end(self):
+        """ Update view's scroll position. """
         if self.scroll_lock:
-            buffer = self.get_buffer()
-            buffer.move_mark(buffer.get_insert(), buffer.get_end_iter())
+            buf = self.get_buffer()
+            buf.move_mark(buf.get_insert(), buf.get_end_iter())
             # TODO: Fix later
             #self.scroll_to_mark(buffer.get_insert(), 0.0)
 
     def clear(self):
-        buffer = self.get_buffer()
-        buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
+        """ Clear all text from buffer. """
+        buf = self.get_buffer()
+        buf.delete(buf.get_start_iter(), buf.get_end_iter())
 
     def save(self, file_path):
-        console_file = open(file_path, 'w')
-        buffer = self.get_buffer()
-        console_file.write(buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), True))
-        console_file.close()
+        """
+        Save context of buffer to the given file.
+
+        Args:
+            file_path: location to save buffer contents
+        """
+        with open(file_path, 'w') as logfile:
+            buf = self.get_buffer()
+            logfile.write(buf.get_text(buf.get_start_iter(),
+                                       buf.get_end_iter(), True))
 
-    # Callback functions to handle the scrolling lock and clear context menus options
     # Action functions are set by the ActionHandler's init function
     def clear_cb(self, menu_item, web_view):
+        """ Callback function to clear the text buffer """
         Actions.CLEAR_CONSOLE()
 
     def scroll_back_cb(self, menu_item, web_view):
+        """ Callback function to toggle scroll lock """
         Actions.TOGGLE_SCROLL_LOCK()
 
     def save_cb(self, menu_item, web_view):
+        """ Callback function to save the buffer """
         Actions.SAVE_CONSOLE()
 
     def populate_popup(self, view, menu):
@@ -134,79 +157,151 @@ class TextDisplay(SimpleTextDisplay):
         return False
 
 
-def MessageDialogHelper(type, buttons, title=None, markup=None, default_response=None, extra_buttons=None):
-    """
-    Create a modal message dialog and run it.
+class MessageDialogWrapper(Gtk.MessageDialog):
+    """ Run a message dialog. """
 
-    Args:
-        type: the type of message: Gtk.MessageType.INFO, Gtk.MessageType.WARNING, Gtk.MessageType.QUESTION or Gtk.MessageType.ERROR
-        buttons: the predefined set of buttons to use:
-        Gtk.ButtonsType.NONE, Gtk.ButtonsType.OK, Gtk.ButtonsType.CLOSE, Gtk.ButtonsType.CANCEL, Gtk.ButtonsType.YES_NO, Gtk.ButtonsType.OK_CANCEL
-
-    Args:
-        title: the title of the window (string)
-        markup: the message text with pango markup
-        default_response: if set, determines which button is highlighted by default
-        extra_buttons: a tuple containing pairs of values; each value is the button's text and the button's return value
-
-    Returns:
-        the gtk response from run()
-    """
-    message_dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, type, buttons)
-    if title: message_dialog.set_title(title)
-    if markup: message_dialog.set_markup(markup)
-    if extra_buttons: message_dialog.add_buttons(*extra_buttons)
-    if default_response: message_dialog.set_default_response(default_response)
-    response = message_dialog.run()
-    message_dialog.destroy()
-    return response
-
-
-def ErrorsDialog(flowgraph):
-    MessageDialogHelper(
-        type=Gtk.MessageType.ERROR,
-        buttons=Gtk.ButtonsType.CLOSE,
-        title='Flow Graph Errors',
-        markup='\n\n'.join(
-            '<b>Error {num}:</b>\n{msg}'.format(num=i, msg=Utils.encode(msg.replace('\t', '  ')))
-            for i, msg in enumerate(flowgraph.get_error_messages())
-        ),
-    )
-
-
-class AboutDialog(Gtk.AboutDialog):
-    """A cute little about dialog."""
-
-    def __init__(self, config):
-        """AboutDialog constructor."""
-        GObject.GObject.__init__(self)
-        self.set_name(config.name)
-        self.set_version(config.version)
-        self.set_license(config.license)
-        self.set_copyright(config.license.splitlines()[0])
-        self.set_website(config.website)
-        self.run()
-        self.destroy()
-
-
-def HelpDialog(): MessageDialogHelper(
-    type=Gtk.MessageType.INFO,
-    buttons=Gtk.ButtonsType.CLOSE,
-    title='Help',
-    markup="""\
-<b>Usage Tips</b>
-
-<u>Add block</u>: drag and drop or double click a block in the block selection window.
-<u>Rotate block</u>: Select a block, press left/right on the keyboard.
-<u>Change type</u>: Select a block, press up/down on the keyboard.
-<u>Edit parameters</u>: double click on a block in the flow graph.
-<u>Make connection</u>: click on the source port of one block, then click on the sink port of another block.
-<u>Remove connection</u>: select the connection and press delete, or drag the connection.
-
-* See the menu for other keyboard shortcuts.""")
+    def __init__(self, parent, message_type, buttons, title=None, markup=None,
+                 default_response=None, extra_buttons=None):
+        """
+        Create a modal message dialog.
 
+        Args:
+            message_type: the type of message may be one of:
+                            Gtk.MessageType.INFO
+                            Gtk.MessageType.WARNING
+                            Gtk.MessageType.QUESTION or Gtk.MessageType.ERROR
+            buttons: the predefined set of buttons to use:
+                            Gtk.ButtonsType.NONE
+                            Gtk.ButtonsType.OK
+                            Gtk.ButtonsType.CLOSE
+                            Gtk.ButtonsType.CANCEL
+                            Gtk.ButtonsType.YES_NO
+                            Gtk.ButtonsType.OK_CANCEL
+            title: the title of the window (string)
+            markup: the message text with pango markup
+            default_response: if set, determines which button is highlighted by default
+            extra_buttons: a tuple containing pairs of values:
+                            each value is the button's text and the button's return value
 
-def TypesDialog(platform):
+        """
+        Gtk.MessageDialog.__init__(
+            self, transient_for=parent, modal=True, destroy_with_parent=True,
+            message_type=message_type, buttons=buttons
+        )
+        if title:
+            self.set_title(title)
+        if markup:
+            self.set_markup(markup)
+        if extra_buttons:
+            self.add_buttons(*extra_buttons)
+        if default_response:
+            self.set_default_response(default_response)
+
+    def run_and_destroy(self):
+        response = self.run()
+        self.hide()
+        return response
+
+
+class ErrorsDialog(Gtk.Dialog):
+    """ Display flowgraph errors. """
+
+    def __init__(self, parent, flowgraph):
+        """Create a listview of errors"""
+        Gtk.Dialog.__init__(
+            self,
+            title='Errors and Warnings',
+            transient_for=parent,
+            modal=True,
+            destroy_with_parent=True,
+        )
+        self.add_buttons(Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT)
+        self.set_size_request(750, Constants.MIN_DIALOG_HEIGHT)
+        self.set_border_width(10)
+
+        self.store = Gtk.ListStore(str, str, str)
+        self.update(flowgraph)
+
+        self.treeview = Gtk.TreeView(model=self.store)
+        for i, column_title in enumerate(["Block", "Aspect", "Message"]):
+            renderer = Gtk.CellRendererText()
+            column = Gtk.TreeViewColumn(column_title, renderer, text=i)
+            column.set_sort_column_id(i)  # liststore id matches treeview id
+            column.set_resizable(True)
+            self.treeview.append_column(column)
+
+        self.scrollable = Gtk.ScrolledWindow()
+        self.scrollable.set_vexpand(True)
+        self.scrollable.add(self.treeview)
+
+        self.vbox.pack_start(self.scrollable, True, True, 0)
+        self.show_all()
+
+    def update(self, flowgraph):
+        self.store.clear()
+        for element, message in flowgraph.iter_error_messages():
+            if element.is_block:
+                src, aspect = element.get_id(), ''
+            elif element.is_connection:
+                src = element.source_block.get_id()
+                aspect = "Connection to '{}'".format(element.sink_block.get_id())
+            elif element.is_port:
+                src = element.parent_block.get_id()
+                aspect = "{} '{}'".format('Sink' if element.is_sink else 'Source', element.name)
+            elif element.is_param:
+                src = element.parent_block.get_id()
+                aspect = "Param '{}'".format(element.name)
+            else:
+                src = aspect = ''
+            self.store.append([src, aspect, message])
+
+    def run_and_destroy(self):
+        response = self.run()
+        self.hide()
+        return response
+
+
+def show_about(parent, config):
+    ad = Gtk.AboutDialog(transient_for=parent)
+    ad.set_program_name(config.name)
+    ad.set_name('')
+    ad.set_version(config.version)
+    ad.set_license(config.license)
+
+    try:
+        ad.set_logo(Gtk.IconTheme().load_icon('gnuradio-grc', 64, 0))
+    except:
+        pass
+
+    ad.set_copyright(config.license.splitlines()[0])
+    ad.set_website(config.website)
+
+    ad.connect("response", lambda action, param: action.hide())
+    ad.show()
+
+
+def show_help(parent):
+    """ Display basic usage tips. """
+    markup = textwrap.dedent("""\
+        <b>Usage Tips</b>
+        \n\
+        <u>Add block</u>: drag and drop or double click a block in the block selection window.
+        <u>Rotate block</u>: Select a block, press left/right on the keyboard.
+        <u>Change type</u>: Select a block, press up/down on the keyboard.
+        <u>Edit parameters</u>: double click on a block in the flow graph.
+        <u>Make connection</u>: click on the source port of one block, then click on the sink port of another block.
+        <u>Remove connection</u>: select the connection and press delete, or drag the connection.
+        \n\
+        * See the menu for other keyboard shortcuts.\
+    """)
+
+    MessageDialogWrapper(
+        parent, Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, title='Help', markup=markup
+    ).run_and_destroy()
+
+
+def show_types(parent):
+    """ Display information about standard data types. """
     colors = [(name, color) for name, key, sizeof, color in Constants.CORE_TYPES]
     max_len = 10 + max(len(name) for name, code in colors)
 
@@ -215,62 +310,65 @@ def TypesDialog(platform):
         ''.format(color=color, name=Utils.encode(name).center(max_len))
         for name, color in colors
     )
-    MessageDialogHelper(
-        type=Gtk.MessageType.INFO,
-        buttons=Gtk.ButtonsType.CLOSE,
-        title='Types - Color Mapping',
-        markup=message
-    )
 
+    MessageDialogWrapper(
+        parent, Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, title='Types - Color Mapping', markup=message
+    ).run_and_destroy()
 
-def MissingXTermDialog(xterm):
-    MessageDialogHelper(
-        type=Gtk.MessageType.WARNING,
-        buttons=Gtk.ButtonsType.OK,
-        title='Warning: missing xterm executable',
-        markup=("The xterm executable {0!r} is missing.\n\n"
-                "You can change this setting in your gnuradio.conf, in "
-                "section [grc], 'xterm_executable'.\n"
-                "\n"
-                "(This message is shown only once)").format(xterm)
-    )
 
+def show_missing_xterm(parent, xterm):
+    markup = textwrap.dedent("""\
+        The xterm executable {0!r} is missing.
+        You can change this setting in your gnurado.conf, in section [grc], 'xterm_executable'.
+        \n\
+        (This message is shown only once)\
+    """).format(xterm)
+
+    MessageDialogWrapper(
+        parent, message_type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.OK,
+        title='Warning: missing xterm executable', markup=markup
+    ).run_and_destroy()
+
+
+def choose_editor(parent, config):
+    """
+    Give the option to either choose an editor or use the default.
+    """
+    if config.editor and find_executable(config.editor):
+        return config.editor
 
-def ChooseEditorDialog(config):
-    # Give the option to either choose an editor or use the default
-    # Always return true/false so the caller knows it was successful
     buttons = (
         'Choose Editor', Gtk.ResponseType.YES,
         'Use Default', Gtk.ResponseType.NO,
         Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL
     )
-    response = MessageDialogHelper(
-        Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, 'Choose Editor',
-        'Would you like to choose the editor to use?', Gtk.ResponseType.YES, buttons
-    )
+    response = MessageDialogWrapper(
+        parent, message_type=Gtk.MessageType.QUESTION, buttons=Gtk.ButtonsType.NONE,
+        title='Choose Editor', markup='Would you like to choose the editor to use?',
+        default_response=Gtk.ResponseType.YES, extra_buttons=buttons
+    ).run_and_destroy()
 
-    # Handle the inital default/choose/cancel response
+    # Handle the initial default/choose/cancel response
     # User wants to choose the editor to use
+    editor = ''
     if response == Gtk.ResponseType.YES:
         file_dialog = Gtk.FileChooserDialog(
             'Select an Editor...', None,
             Gtk.FileChooserAction.OPEN,
-            ('gtk-cancel', Gtk.ResponseType.CANCEL, 'gtk-open', Gtk.ResponseType.OK)
+            ('gtk-cancel', Gtk.ResponseType.CANCEL, 'gtk-open', Gtk.ResponseType.OK),
+            transient_for=parent
         )
         file_dialog.set_select_multiple(False)
         file_dialog.set_local_only(True)
         file_dialog.set_current_folder('/usr/bin')
         try:
             if file_dialog.run() == Gtk.ResponseType.OK:
-                config.editor = file_path = file_dialog.get_filename()
-                file_dialog.destroy()
-                return file_path
+                editor = file_dialog.get_filename()
         finally:
-            file_dialog.destroy()
+            file_dialog.hide()
 
     # Go with the default editor
     elif response == Gtk.ResponseType.NO:
-        # Determine the platform
         try:
             process = None
             if sys.platform.startswith('linux'):
@@ -280,13 +378,10 @@ def ChooseEditorDialog(config):
             if process is None:
                 raise ValueError("Can't find default editor executable")
             # Save
-            config.editor = process
-            return process
+            editor = config.editor = process
         except Exception:
             Messages.send('>>> Unable to load the default editor. Please choose an editor.\n')
-            # Just reset of the constant and force the user to select an editor the next time
-            config.editor = ''
-            return
 
-    Messages.send('>>> No editor selected.\n')
-    return
+    if editor == '':
+        Messages.send('>>> No editor selected.\n')
+    return editor
diff --git a/grc/gui/FileDialogs.py b/grc/gui/FileDialogs.py
index 23fd7e7900..afd41af58c 100644
--- a/grc/gui/FileDialogs.py
+++ b/grc/gui/FileDialogs.py
@@ -18,18 +18,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
 from __future__ import absolute_import
-import gi
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk
-from gi.repository import GObject
 
-from .Dialogs import MessageDialogHelper
-from .Constants import \
-    DEFAULT_FILE_PATH, IMAGE_FILE_EXTENSION, TEXT_FILE_EXTENSION, \
-    NEW_FLOGRAPH_TITLE
-from . import Preferences
 from os import path
-from . import Utils
+
+from gi.repository import Gtk
+
+from . import Constants, Preferences, Utils
+from .Dialogs import MessageDialogWrapper
+
 
 ##################################################
 # Constants
@@ -52,14 +48,14 @@ def get_flow_graph_files_filter():
 def get_text_files_filter():
     filter = Gtk.FileFilter()
     filter.set_name('Text Files')
-    filter.add_pattern('*'+TEXT_FILE_EXTENSION)
+    filter.add_pattern('*' + Constants.TEXT_FILE_EXTENSION)
     return filter
 
 
 def get_image_files_filter():
     filter = Gtk.FileFilter()
     filter.set_name('Image Files')
-    filter.add_pattern('*'+IMAGE_FILE_EXTENSION)
+    filter.add_pattern('*' + Constants.IMAGE_FILE_EXTENSION)
     return filter
 
 
@@ -84,7 +80,7 @@ class FileDialogHelper(Gtk.FileChooserDialog):
     Implement a file chooser dialog with only necessary parameters.
     """
 
-    def __init__(self, action, title):
+    def __init__(self, parent, action, title):
         """
         FileDialogHelper contructor.
         Create a save or open dialog with cancel and ok buttons.
@@ -94,8 +90,16 @@ class FileDialogHelper(Gtk.FileChooserDialog):
             action: Gtk.FileChooserAction.OPEN or Gtk.FileChooserAction.SAVE
             title: the title of the dialog (string)
         """
-        ok_stock = {Gtk.FileChooserAction.OPEN : 'gtk-open', Gtk.FileChooserAction.SAVE : 'gtk-save'}[action]
-        Gtk.FileChooserDialog.__init__(self, title, None, action, ('gtk-cancel', Gtk.ResponseType.CANCEL, ok_stock, Gtk.ResponseType.OK))
+        ok_stock = {
+            Gtk.FileChooserAction.OPEN: 'gtk-open',
+            Gtk.FileChooserAction.SAVE: 'gtk-save'
+        }[action]
+
+        Gtk.FileChooserDialog.__init__(
+            self, title=title, action=action,
+            transient_for=parent
+        )
+        self.add_buttons('gtk-cancel', Gtk.ResponseType.CANCEL, ok_stock, Gtk.ResponseType.OK)
         self.set_select_multiple(False)
         self.set_local_only(True)
         self.add_filter(get_all_files_filter())
@@ -104,37 +108,39 @@ class FileDialogHelper(Gtk.FileChooserDialog):
 class FileDialog(FileDialogHelper):
     """A dialog box to save or open flow graph files. This is a base class, do not use."""
 
-    def __init__(self, current_file_path=''):
+    def __init__(self, parent, current_file_path=''):
         """
         FileDialog constructor.
 
         Args:
             current_file_path: the current directory or path to the open flow graph
         """
-        if not current_file_path: current_file_path = path.join(DEFAULT_FILE_PATH, NEW_FLOGRAPH_TITLE + Preferences.file_extension())
+        if not current_file_path:
+            current_file_path = path.join(Constants.DEFAULT_FILE_PATH,
+                                          Constants.NEW_FLOGRAPH_TITLE + Preferences.file_extension())
         if self.type == OPEN_FLOW_GRAPH:
-            FileDialogHelper.__init__(self, Gtk.FileChooserAction.OPEN, 'Open a Flow Graph from a File...')
+            FileDialogHelper.__init__(self, parent, Gtk.FileChooserAction.OPEN, 'Open a Flow Graph from a File...')
             self.add_and_set_filter(get_flow_graph_files_filter())
             self.set_select_multiple(True)
         elif self.type == SAVE_FLOW_GRAPH:
-            FileDialogHelper.__init__(self, Gtk.FileChooserAction.SAVE, 'Save a Flow Graph to a File...')
+            FileDialogHelper.__init__(self, parent, Gtk.FileChooserAction.SAVE, 'Save a Flow Graph to a File...')
             self.add_and_set_filter(get_flow_graph_files_filter())
             self.set_current_name(path.basename(current_file_path))
         elif self.type == SAVE_CONSOLE:
-            FileDialogHelper.__init__(self, Gtk.FileChooserAction.SAVE, 'Save Console to a File...')
+            FileDialogHelper.__init__(self, parent, Gtk.FileChooserAction.SAVE, 'Save Console to a File...')
             self.add_and_set_filter(get_text_files_filter())
             file_path = path.splitext(path.basename(current_file_path))[0]
-            self.set_current_name(file_path) #show the current filename
+            self.set_current_name(file_path)  # show the current filename
         elif self.type == SAVE_IMAGE:
-            FileDialogHelper.__init__(self, Gtk.FileChooserAction.SAVE, 'Save a Flow Graph Screen Shot...')
+            FileDialogHelper.__init__(self, parent, Gtk.FileChooserAction.SAVE, 'Save a Flow Graph Screen Shot...')
             self.add_and_set_filter(get_image_files_filter())
-            current_file_path = current_file_path + IMAGE_FILE_EXTENSION
-            self.set_current_name(path.basename(current_file_path)) #show the current filename
+            current_file_path = current_file_path + Constants.IMAGE_FILE_EXTENSION
+            self.set_current_name(path.basename(current_file_path))  # show the current filename
         elif self.type == OPEN_QSS_THEME:
-            FileDialogHelper.__init__(self, Gtk.FileChooserAction.OPEN, 'Open a QSS theme...')
+            FileDialogHelper.__init__(self, parent, Gtk.FileChooserAction.OPEN, 'Open a QSS theme...')
             self.add_and_set_filter(get_qss_themes_filter())
             self.set_select_multiple(False)
-        self.set_current_folder(path.dirname(current_file_path)) #current directory
+        self.set_current_folder(path.dirname(current_file_path))  # current directory
 
     def add_and_set_filter(self, filter):
         """
@@ -156,7 +162,7 @@ class FileDialog(FileDialogHelper):
         Returns:
             the complete file path
         """
-        if Gtk.FileChooserDialog.run(self) != Gtk.ResponseType.OK: return None #response was cancel
+        if Gtk.FileChooserDialog.run(self) != Gtk.ResponseType.OK: return None  # response was cancel
         #############################################
         # Handle Save Dialogs
         #############################################
@@ -164,18 +170,21 @@ class FileDialog(FileDialogHelper):
             filename = self.get_filename()
             extension = {
                 SAVE_FLOW_GRAPH: Preferences.file_extension(),
-                SAVE_CONSOLE: TEXT_FILE_EXTENSION,
-                SAVE_IMAGE: IMAGE_FILE_EXTENSION,
+                SAVE_CONSOLE: Constants.TEXT_FILE_EXTENSION,
+                SAVE_IMAGE: Constants.IMAGE_FILE_EXTENSION,
             }[self.type]
-            #append the missing file extension if the filter matches
-            if path.splitext(filename)[1].lower() != extension: filename += extension
-            self.set_current_name(path.basename(filename)) #show the filename with extension
-            if path.exists(filename): #ask the user to confirm overwrite
-                if MessageDialogHelper(
+            # append the missing file extension if the filter matches
+            if path.splitext(filename)[1].lower() != extension:
+                filename += extension
+            self.set_current_name(path.basename(filename))  # show the filename with extension
+            if path.exists(filename):  # ask the user to confirm overwrite
+                response = MessageDialogWrapper(
                     Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, 'Confirm Overwrite!',
                     'File <b>{filename}</b> Exists!\nWould you like to overwrite the existing file?'
                     ''.format(filename=Utils.encode(filename)),
-                ) == Gtk.ResponseType.NO: return self.get_rectified_filename()
+                ).run_and_destroy()
+                if response == Gtk.ResponseType.NO:
+                    return self.get_rectified_filename()
             return filename
         #############################################
         # Handle Open Dialogs
@@ -183,11 +192,11 @@ class FileDialog(FileDialogHelper):
         elif self.type in (OPEN_FLOW_GRAPH, OPEN_QSS_THEME):
             filenames = self.get_filenames()
             for filename in filenames:
-                if not path.exists(filename): #show a warning and re-run
-                    MessageDialogHelper(
+                if not path.exists(filename):  # show a warning and re-run
+                    MessageDialogWrapper(
                         Gtk.MessageType.WARNING, Gtk.ButtonsType.CLOSE, 'Cannot Open!',
                         'File <b>{filename}</b> Does not Exist!'.format(filename=Utils.encode(filename)),
-                    )
+                    ).run_and_destroy()
                     return self.get_rectified_filename()
             return filenames
 
@@ -203,19 +212,19 @@ class FileDialog(FileDialogHelper):
         return filename
 
 
-class OpenFlowGraphFileDialog(FileDialog):
+class OpenFlowGraph(FileDialog):
     type = OPEN_FLOW_GRAPH
 
 
-class SaveFlowGraphFileDialog(FileDialog):
+class SaveFlowGraph(FileDialog):
     type = SAVE_FLOW_GRAPH
 
 
-class OpenQSSFileDialog(FileDialog):
+class OpenQSS(FileDialog):
     type = OPEN_QSS_THEME
 
 
-class SaveConsoleFileDialog(FileDialog):
+class SaveConsole(FileDialog):
     type = SAVE_CONSOLE
 
 
@@ -223,11 +232,11 @@ class SaveImageFileDialog(FileDialog):
     type = SAVE_IMAGE
 
 
-class SaveScreenShotDialog(SaveImageFileDialog):
+class SaveScreenShot(SaveImageFileDialog):
 
-    def __init__(self, current_file_path=''):
-        SaveImageFileDialog.__init__(self, current_file_path)
-        self._button = button = Gtk.CheckButton('_Background transparent')
+    def __init__(self, parent, current_file_path=''):
+        SaveImageFileDialog.__init__(self, parent, current_file_path)
+        self._button = button = Gtk.CheckButton(label='Background transparent')
         self._button.set_active(Preferences.screen_shot_background_transparent())
         self.set_extra_widget(button)
 
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index 83796f35fd..7333519a4f 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -28,7 +28,7 @@ from itertools import count
 import six
 from six.moves import filter
 
-from gi.repository import GObject
+from gi.repository import GLib
 
 from . import Actions, Colors, Utils, Bars, Dialogs
 from .Element import Element
@@ -95,7 +95,7 @@ class FlowGraph(CoreFlowgraph, Element):
         else:
             config = self.parent_platform.config
             editor = (find_executable(config.editor) or
-                      Dialogs.ChooseEditorDialog(config))
+                      Dialogs.choose_editor(None, config))  # todo: pass in parent
             if not editor:
                 return
             updater = functools.partial(
@@ -103,7 +103,7 @@ class FlowGraph(CoreFlowgraph, Element):
             editor = self._external_updaters[target] = ExternalEditor(
                 editor=editor,
                 name=target[0], value=param.get_value(),
-                callback=functools.partial(GObject.idle_add, updater)
+                callback=functools.partial(GLib.idle_add, updater)
             )
             editor.start()
         try:
diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py
index 97f9033974..efa8573c3b 100644
--- a/grc/gui/MainWindow.py
+++ b/grc/gui/MainWindow.py
@@ -33,7 +33,7 @@ from .BlockTreeWindow import BlockTreeWindow
 from .VariableEditor import VariableEditor
 from .Constants import \
     NEW_FLOGRAPH_TITLE, DEFAULT_CONSOLE_WINDOW_WIDTH
-from .Dialogs import TextDisplay, MessageDialogHelper
+from .Dialogs import TextDisplay, MessageDialogWrapper
 from .NotebookPage import NotebookPage
 
 from ..core import Messages
@@ -398,10 +398,10 @@ class MainWindow(Gtk.Window):
             Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
             Gtk.STOCK_SAVE, Gtk.ResponseType.OK
         )
-        return MessageDialogHelper(
-            Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, 'Unsaved Changes!',
+        return MessageDialogWrapper(
+            self, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, 'Unsaved Changes!',
             'Would you like to save changes before closing?', Gtk.ResponseType.OK, buttons
-        )
+        ).run_and_destroy()
 
     def _get_files(self):
         """
diff --git a/grc/gui/ParamWidgets.py b/grc/gui/ParamWidgets.py
index 4ab7da6d9a..e5657c288e 100644
--- a/grc/gui/ParamWidgets.py
+++ b/grc/gui/ParamWidgets.py
@@ -168,7 +168,7 @@ class PythonEditorParam(InputParam):
 
     def __init__(self, *args, **kwargs):
         InputParam.__init__(self, *args, **kwargs)
-        button = self._button = Gtk.Button('Open in Editor')
+        button = self._button = Gtk.Button(label='Open in Editor')
         button.connect('clicked', self.open_editor)
         self.pack_start(button, True, True, True)
 
diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py
index 9543a62094..3a0f6ae6de 100644
--- a/grc/gui/PropsDialog.py
+++ b/grc/gui/PropsDialog.py
@@ -43,6 +43,7 @@ class PropsDialog(Gtk.Dialog):
             title='Properties: ' + block.name,
             transient_for=parent,
             modal=True,
+            destroy_with_parent=True,
         )
         self.add_buttons(
             Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT,
-- 
cgit v1.2.3


From f85df8b8d9bf2a88a6b87b91d0b55fdcb8161f46 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Tue, 30 Aug 2016 16:26:13 +0200
Subject: grc: gtk3: minor gui flowgraph cleanup

---
 grc/gui/ActionHandler.py |  2 +-
 grc/gui/Block.py         |  7 ++++---
 grc/gui/Connection.py    |  2 +-
 grc/gui/DrawingArea.py   |  2 +-
 grc/gui/Element.py       |  2 +-
 grc/gui/FlowGraph.py     | 47 ++++++++++++++++++++---------------------------
 grc/gui/MainWindow.py    |  4 ++--
 grc/gui/Port.py          |  2 +-
 8 files changed, 31 insertions(+), 37 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py
index ca16a7e7fd..85b68a9629 100644
--- a/grc/gui/ActionHandler.py
+++ b/grc/gui/ActionHandler.py
@@ -675,7 +675,7 @@ class ActionHandler:
         main.update()
 
         flow_graph.update_selected()
-        flow_graph.queue_draw()
+        page.drawing_area.queue_draw()
 
         return True  # action was handled
 
diff --git a/grc/gui/Block.py b/grc/gui/Block.py
index 6cbfa5f4f5..4b6c5b8e9e 100644
--- a/grc/gui/Block.py
+++ b/grc/gui/Block.py
@@ -244,7 +244,7 @@ class Block(CoreBlock, Element):
         else:
             self._comment_layout = None
 
-    def draw(self, widget, cr):
+    def draw(self, cr):
         """
         Draw the signal block with label and inputs/outputs.
         """
@@ -253,7 +253,7 @@ class Block(CoreBlock, Element):
 
         for port in self.active_ports():  # ports first
             cr.save()
-            port.draw(widget, cr)
+            port.draw(cr)
             cr.restore()
 
         cr.rectangle(*self._area)
@@ -295,7 +295,7 @@ class Block(CoreBlock, Element):
                 return port_selected
         return Element.what_is_selected(self, coor, coor_m)
 
-    def draw_comment(self, widget, cr):
+    def draw_comment(self, cr):
         if not self._comment_layout:
             return
         x, y = self.coordinate
@@ -375,3 +375,4 @@ class Block(CoreBlock, Element):
             except:
                 pass
         return changed
+
diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py
index b5238bc2a6..9862328d6a 100644
--- a/grc/gui/Connection.py
+++ b/grc/gui/Connection.py
@@ -156,7 +156,7 @@ class Connection(CoreConnection, Element):
             self._line = [p0, p1, point, p2, p3]
         self.bounds_from_line(self._line)
 
-    def draw(self, widget, cr):
+    def draw(self, cr):
         """
         Draw the connection.
         """
diff --git a/grc/gui/DrawingArea.py b/grc/gui/DrawingArea.py
index c729bbad24..f279d50557 100644
--- a/grc/gui/DrawingArea.py
+++ b/grc/gui/DrawingArea.py
@@ -194,7 +194,7 @@ class DrawingArea(Gtk.DrawingArea):
         cr.scale(self.zoom_factor, self.zoom_factor)
         cr.fill()
 
-        self._flow_graph.draw(widget, cr)
+        self._flow_graph.draw(cr)
 
     def _translate_event_coords(self, event):
         return event.x / self.zoom_factor, event.y / self.zoom_factor
diff --git a/grc/gui/Element.py b/grc/gui/Element.py
index 81a5cbfc40..8418bef0cc 100644
--- a/grc/gui/Element.py
+++ b/grc/gui/Element.py
@@ -108,7 +108,7 @@ class Element(object):
         Call this base method before creating shapes in the element.
         """
 
-    def draw(self, widget, cr):
+    def draw(self, cr):
         raise NotImplementedError()
 
     def bounds_from_area(self, area):
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index 7333519a4f..f082e4f7f0 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -134,14 +134,15 @@ class FlowGraph(CoreFlowgraph, Element):
             coor: an optional coordinate or None for random
         """
         id = self._get_unique_id(key)
-        #calculate the position coordinate
-        h_adj = self.get_scroll_pane().get_hadjustment()
-        v_adj = self.get_scroll_pane().get_vadjustment()
+        scroll_pane = self.drawing_area.get_parent().get_parent()
+        # calculate the position coordinate
+        h_adj = scroll_pane.get_hadjustment()
+        v_adj = scroll_pane.get_vadjustment()
         if coor is None: coor = (
             int(random.uniform(.25, .75)*h_adj.get_page_size() + h_adj.get_value()),
             int(random.uniform(.25, .75)*v_adj.get_page_size() + v_adj.get_value()),
         )
-        #get the new block
+        # get the new block
         block = self.new_block(key)
         block.coordinate = coor
         block.get_param('id').set_value(id)
@@ -189,15 +190,6 @@ class FlowGraph(CoreFlowgraph, Element):
             self.update()
         return success
 
-    ###########################################################################
-    # Access Drawing Area
-    ###########################################################################
-    def get_drawing_area(self): return self.drawing_area
-    def queue_draw(self): self.get_drawing_area().queue_draw()
-    def get_scroll_pane(self): return self.drawing_area.get_parent().get_parent()
-    def get_ctrl_mask(self): return self.drawing_area.ctrl_mask
-    def get_mod1_mask(self): return self.drawing_area.mod1_mask
-
     ###########################################################################
     # Copy Paste
     ###########################################################################
@@ -241,9 +233,10 @@ class FlowGraph(CoreFlowgraph, Element):
         selected = set()
         (x_min, y_min), blocks_n, connections_n = clipboard
         old_id2block = dict()
-        #recalc the position
-        h_adj = self.get_scroll_pane().get_hadjustment()
-        v_adj = self.get_scroll_pane().get_vadjustment()
+        # recalc the position
+        scroll_pane = self.drawing_area.get_parent().get_parent()
+        h_adj = scroll_pane.get_hadjustment()
+        v_adj = scroll_pane.get_vadjustment()
         x_off = h_adj.get_value() - x_min + h_adj.get_page_size() / 4
         y_off = v_adj.get_value() - y_min + v_adj.get_page_size() / 4
         if len(self.get_elements()) <= 1:
@@ -486,15 +479,15 @@ class FlowGraph(CoreFlowgraph, Element):
         for element in self._elements_to_draw:
             yield element.draw
 
-    def draw(self, widget, cr):
+    def draw(self, cr):
         """Draw blocks connections comment and select rectangle"""
         for draw_element in self._drawables():
             cr.save()
-            draw_element(widget, cr)
+            draw_element(cr)
             cr.restore()
 
         # draw multi select rectangle
-        if self.mouse_pressed and (not self.selected_elements or self.get_ctrl_mask()):
+        if self.mouse_pressed and (not self.selected_elements or self.drawing_area.ctrl_mask):
             x1, y1 = self.press_coor
             x2, y2 = self.coordinate
             x, y = int(min(x1, x2)), int(min(y1, y2))
@@ -529,18 +522,18 @@ class FlowGraph(CoreFlowgraph, Element):
             new_selections = self.what_is_selected(self.coordinate)
             # update the selections if the new selection is not in the current selections
             # allows us to move entire selected groups of elements
-            if self.get_ctrl_mask() or new_selections not in self.selected_elements:
+            if self.drawing_area.ctrl_mask or new_selections not in self.selected_elements:
                 selected_elements = new_selections
 
             if self._old_selected_port:
                 self._old_selected_port.force_show_label = False
                 self.create_shapes()
-                self.queue_draw()
+                self.drawing_area.queue_draw()
             elif self._new_selected_port:
                 self._new_selected_port.force_show_label = True
 
         else:  # called from a mouse release
-            if not self.element_moved and (not self.selected_elements or self.get_ctrl_mask()):
+            if not self.element_moved and (not self.selected_elements or self.drawing_area.ctrl_mask):
                 selected_elements = self.what_is_selected(self.coordinate, self.press_coor)
 
         # this selection and the last were ports, try to connect them
@@ -552,7 +545,7 @@ class FlowGraph(CoreFlowgraph, Element):
             return
 
         # if ctrl, set the selected elements to the union - intersection of old and new
-        if self.get_ctrl_mask():
+        if self.drawing_area.ctrl_mask:
             self.selected_elements ^= selected_elements
         else:
             self.selected_elements.clear()
@@ -729,7 +722,7 @@ class FlowGraph(CoreFlowgraph, Element):
         if redraw:
             # self.create_labels()
             self.create_shapes()
-            self.queue_draw()
+            self.drawing_area.queue_draw()
 
     def _handle_mouse_motion_drag(self, coordinate):
         # remove the connection if selected in drag event
@@ -738,15 +731,15 @@ class FlowGraph(CoreFlowgraph, Element):
 
         # move the selected elements and record the new coordinate
         x, y = coordinate
-        if not self.get_ctrl_mask():
+        if not self.drawing_area.ctrl_mask:
             X, Y = self.coordinate
             dX, dY = int(x - X), int(y - Y)
-            active = Actions.TOGGLE_SNAP_TO_GRID.get_active() or self.get_mod1_mask()
+            active = Actions.TOGGLE_SNAP_TO_GRID.get_active() or self.drawing_area.mod1_mask
             if not active or abs(dX) >= Utils.CANVAS_GRID_SIZE or abs(dY) >= Utils.CANVAS_GRID_SIZE:
                 self.move_selected((dX, dY))
                 self.coordinate = (x, y)
         # queue draw for animation
-        self.queue_draw()
+        self.drawing_area.queue_draw()
 
     def get_max_coords(self, initial=(0, 0)):
         w, h = initial
diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py
index efa8573c3b..61144386b4 100644
--- a/grc/gui/MainWindow.py
+++ b/grc/gui/MainWindow.py
@@ -77,7 +77,7 @@ class MainWindow(Gtk.Window):
         # Create the notebook
         self.notebook = Gtk.Notebook()
         self.page_to_be_closed = None
-        self.current_page = None
+        self.current_page = None  # type: NotebookPage
         self.notebook.set_show_border(False)
         self.notebook.set_scrollable(True)  # scroll arrows for page tabs
         self.notebook.connect('switch-page', self._handle_page_change)
@@ -370,7 +370,7 @@ class MainWindow(Gtk.Window):
         Returns:
             the focus flag
         """
-        return self.current_page.get_drawing_area().get_focus_flag()
+        return self.current_page.drawing_area.get_focus_flag()
 
     ############################################################
     # Helpers
diff --git a/grc/gui/Port.py b/grc/gui/Port.py
index 258ae244ab..8ac32dc25f 100644
--- a/grc/gui/Port.py
+++ b/grc/gui/Port.py
@@ -125,7 +125,7 @@ class Port(_Port, Element):
             self._label_layout_offsets[1] += Constants.PORT_EXTRA_BUS_HEIGHT / 2
         self.height += self.height % 2  # uneven height
 
-    def draw(self, widget, cr):
+    def draw(self, cr):
         """
         Draw the socket with a label.
         """
-- 
cgit v1.2.3


From eeea99b45c0a0dccd935dc5203b71e0adf1430fe Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Tue, 30 Aug 2016 16:27:31 +0200
Subject: grc: gtk3: calculate flowgraph canvas size

---
 grc/gui/Block.py     | 10 ++++++++++
 grc/gui/Element.py   |  9 +++++++++
 grc/gui/FlowGraph.py | 16 ++++++++++------
 3 files changed, 29 insertions(+), 6 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/gui/Block.py b/grc/gui/Block.py
index 4b6c5b8e9e..b37bec6dfa 100644
--- a/grc/gui/Block.py
+++ b/grc/gui/Block.py
@@ -311,6 +311,16 @@ class Block(CoreBlock, Element):
         PangoCairo.show_layout(cr, self._comment_layout)
         cr.restore()
 
+    @property
+    def extend(self):
+        extend = Element.extend.fget(self)
+        x, y = self.coordinate
+        for port in self.active_ports():
+            extend = (min_or_max(xy, offset + p_xy) for offset, min_or_max, xy, p_xy in zip(
+                (x, y, x, y), (min, min, max, max), extend, port.extend
+            ))
+        return tuple(extend)
+
     ##############################################
     # Controller Modify
     ##############################################
diff --git a/grc/gui/Element.py b/grc/gui/Element.py
index 8418bef0cc..17cd6ddd92 100644
--- a/grc/gui/Element.py
+++ b/grc/gui/Element.py
@@ -168,6 +168,15 @@ class Element(object):
                 if x <= x1 <= x_m and y <= y1 <= y_m:
                     return self
 
+    @property
+    def extend(self):
+        x_min, y_min = x_max, y_max = self.coordinate
+        x_min += min(x for x, y in self._bounding_points)
+        y_min += min(y for x, y in self._bounding_points)
+        x_max += max(x for x, y in self._bounding_points)
+        y_max += max(y for x, y in self._bounding_points)
+        return x_min, y_min, x_max, y_max
+
     def mouse_over(self):
         pass
 
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index f082e4f7f0..df8e668eed 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -742,9 +742,13 @@ class FlowGraph(CoreFlowgraph, Element):
         self.drawing_area.queue_draw()
 
     def get_max_coords(self, initial=(0, 0)):
-        w, h = initial
-        for block in self.blocks:
-            x, y = block.coordinate
-            w = max(w, x + block.width)
-            h = max(h, y + block.height)
-        return w, h
+        return tuple(max(i, e) for i, e in zip(initial, self.extend[2:]))
+
+    @property
+    def extend(self):
+        extend = 100000, 100000, 0, 0
+        for element in self._elements_to_draw:
+            extend = (min_or_max(xy, e_xy) for min_or_max, xy, e_xy in zip(
+                (min, min, max, max), extend, element.extend
+            ))
+        return tuple(extend)
-- 
cgit v1.2.3


From a867a290194228d09ba93f0f46e3a4e4523f5396 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Tue, 30 Aug 2016 16:28:24 +0200
Subject: grc: gtk3: make screnshots as png, pdf and svg

---
 grc/gui/ActionHandler.py |  9 +++++----
 grc/gui/Actions.py       |  2 +-
 grc/gui/FlowGraph.py     |  4 ++--
 grc/gui/Utils.py         | 44 ++++++++++++++++++++++++++++++++++++++++----
 4 files changed, 48 insertions(+), 11 deletions(-)

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py
index 85b68a9629..913eb5a8d1 100644
--- a/grc/gui/ActionHandler.py
+++ b/grc/gui/ActionHandler.py
@@ -24,9 +24,8 @@ import os
 import subprocess
 
 from gi.repository import Gtk
-from gi.repository import GObject
 
-from . import Dialogs, Preferences, Actions, Executor, Constants, FileDialogs
+from . import Dialogs, Preferences, Actions, Executor, FileDialogs, Utils
 from .MainWindow import MainWindow
 from .ParserErrorsDialog import ParserErrorsDialog
 from .PropsDialog import PropsDialog
@@ -548,8 +547,10 @@ class ActionHandler:
         elif action == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
             file_path, background_transparent = FileDialogs.SaveScreenShot(main, page.file_path).run()
             if file_path is not None:
-                pixbuf = flow_graph.get_drawing_area().get_screenshot(background_transparent)
-                pixbuf.save(file_path, Constants.IMAGE_FILE_EXTENSION[1:])
+                try:
+                    Utils.make_screenshot(flow_graph, file_path, background_transparent)
+                except ValueError:
+                    Messages.send('Failed to generate screen shot\n')
         ##################################################
         # Gen/Exec/Stop
         ##################################################
diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py
index 3a51e80918..4759e56294 100644
--- a/grc/gui/Actions.py
+++ b/grc/gui/Actions.py
@@ -466,7 +466,7 @@ FLOW_GRAPH_SCREEN_CAPTURE = Action(
     label='Screen Ca_pture',
     tooltip='Create a screen capture of the flow graph',
     stock_id=Gtk.STOCK_PRINT,
-    keypresses=(Gdk.KEY_Print, NO_MODS_MASK),
+    keypresses=(Gdk.KEY_p, Gdk.ModifierType.CONTROL_MASK),
 )
 PORT_CONTROLLER_DEC = Action(
     keypresses=(Gdk.KEY_minus, NO_MODS_MASK, Gdk.KEY_KP_Subtract, NO_MODS_MASK),
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index df8e668eed..f04383f32c 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -30,7 +30,7 @@ from six.moves import filter
 
 from gi.repository import GLib
 
-from . import Actions, Colors, Utils, Bars, Dialogs
+from . import Actions, Colors, Constants, Utils, Bars, Dialogs
 from .Element import Element
 from .external_editor import ExternalEditor
 
@@ -735,7 +735,7 @@ class FlowGraph(CoreFlowgraph, Element):
             X, Y = self.coordinate
             dX, dY = int(x - X), int(y - Y)
             active = Actions.TOGGLE_SNAP_TO_GRID.get_active() or self.drawing_area.mod1_mask
-            if not active or abs(dX) >= Utils.CANVAS_GRID_SIZE or abs(dY) >= Utils.CANVAS_GRID_SIZE:
+            if not active or abs(dX) >= Constants.CANVAS_GRID_SIZE or abs(dY) >= Constants.CANVAS_GRID_SIZE:
                 self.move_selected((dX, dY))
                 self.coordinate = (x, y)
         # queue draw for animation
diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py
index d474c66f19..782a7e3a01 100644
--- a/grc/gui/Utils.py
+++ b/grc/gui/Utils.py
@@ -20,8 +20,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 from __future__ import absolute_import
 
 from gi.repository import GLib
+import cairo
 
-from .Constants import POSSIBLE_ROTATIONS, CANVAS_GRID_SIZE, COMPLEX_TYPES
+from . import Colors, Constants
 
 
 def get_rotated_coordinate(coor, rotation):
@@ -37,7 +38,7 @@ def get_rotated_coordinate(coor, rotation):
     """
     # handles negative angles
     rotation = (rotation + 360) % 360
-    if rotation not in POSSIBLE_ROTATIONS:
+    if rotation not in Constants.POSSIBLE_ROTATIONS:
         raise ValueError('unusable rotation angle "%s"'%str(rotation))
     # determine the number of degrees to rotate
     cos_r, sin_r = {
@@ -68,7 +69,7 @@ def get_angle_from_coordinates(p1, p2):
 
 def align_to_grid(coor, mode=round):
     def align(value):
-        return int(mode(value / (1.0 * CANVAS_GRID_SIZE)) * CANVAS_GRID_SIZE)
+        return int(mode(value / (1.0 * Constants.CANVAS_GRID_SIZE)) * Constants.CANVAS_GRID_SIZE)
     try:
         return [align(c) for c in coor]
     except TypeError:
@@ -88,7 +89,7 @@ def num_to_str(num):
                 return template.format(value / factor, symbol.strip())
         return template.format(value, '')
 
-    if isinstance(num, COMPLEX_TYPES):
+    if isinstance(num, Constants.COMPLEX_TYPES):
         num = complex(num)  # Cast to python complex
         if num == 0:
             return '0'
@@ -107,3 +108,38 @@ def encode(value):
     """
     valid_utf8 = value.decode('utf-8', errors='replace').encode('utf-8')
     return GLib.markup_escape_text(valid_utf8)
+
+
+def make_screenshot(flow_graph, file_path, transparent_bg=False):
+    if not file_path:
+        return
+
+    x_min, y_min, x_max, y_max = flow_graph.extend
+    padding = Constants.CANVAS_GRID_SIZE
+    width = x_max - x_min + 2 * padding
+    height = y_max - y_min + 2 * padding
+
+    if file_path.endswith('.png'):
+        psurf = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
+    elif file_path.endswith('.pdf'):
+        psurf = cairo.PDFSurface(file_path, width, height)
+    elif file_path.endswith('.svg'):
+        psurf = cairo.SVGSurface(file_path, width, height)
+    else:
+        raise ValueError('Unknown file format')
+
+    cr = cairo.Context(psurf)
+
+    if not transparent_bg:
+        cr.set_source_rgba(*Colors.FLOWGRAPH_BACKGROUND_COLOR)
+        cr.rectangle(0, 0, width, height)
+        cr.fill()
+
+    cr.translate(padding - x_min, padding - y_min)
+    flow_graph.draw(cr)
+
+    if file_path.endswith('.png'):
+        psurf.write_to_png(file_path)
+    if file_path.endswith('.pdf') or file_path.endswith('.svg'):
+        cr.show_page()
+    psurf.finish()
-- 
cgit v1.2.3


From bc8ee049aefb7818b82adfc24de22590ee00b23f Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Tue, 13 Sep 2016 11:54:56 -0600
Subject: grc: refactor: move drawables in subpackage

---
 grc/gui/Block.py             | 388 ----------------------
 grc/gui/Connection.py        | 207 ------------
 grc/gui/Element.py           | 184 -----------
 grc/gui/FlowGraph.py         | 754 -------------------------------------------
 grc/gui/Param.py             | 162 ----------
 grc/gui/Platform.py          |  20 +-
 grc/gui/Port.py              | 236 --------------
 grc/gui/canvas/__init__.py   |  22 ++
 grc/gui/canvas/block.py      | 390 ++++++++++++++++++++++
 grc/gui/canvas/connection.py | 208 ++++++++++++
 grc/gui/canvas/drawable.py   | 184 +++++++++++
 grc/gui/canvas/flowgraph.py  | 754 +++++++++++++++++++++++++++++++++++++++++++
 grc/gui/canvas/param.py      | 162 ++++++++++
 grc/gui/canvas/port.py       | 233 +++++++++++++
 14 files changed, 1961 insertions(+), 1943 deletions(-)
 delete mode 100644 grc/gui/Block.py
 delete mode 100644 grc/gui/Connection.py
 delete mode 100644 grc/gui/Element.py
 delete mode 100644 grc/gui/FlowGraph.py
 delete mode 100644 grc/gui/Param.py
 delete mode 100644 grc/gui/Port.py
 create mode 100644 grc/gui/canvas/__init__.py
 create mode 100644 grc/gui/canvas/block.py
 create mode 100644 grc/gui/canvas/connection.py
 create mode 100644 grc/gui/canvas/drawable.py
 create mode 100644 grc/gui/canvas/flowgraph.py
 create mode 100644 grc/gui/canvas/param.py
 create mode 100644 grc/gui/canvas/port.py

(limited to 'grc/gui/FlowGraph.py')

diff --git a/grc/gui/Block.py b/grc/gui/Block.py
deleted file mode 100644
index b37bec6dfa..0000000000
--- a/grc/gui/Block.py
+++ /dev/null
@@ -1,388 +0,0 @@
-"""
-Copyright 2007, 2008, 2009 Free Software Foundation, Inc.
-This file is part of GNU Radio
-
-GNU Radio Companion is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-GNU Radio Companion is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
-"""
-
-from __future__ import absolute_import
-import math
-
-import six
-from gi.repository import Gtk, Pango, PangoCairo
-
-from . import Actions, Colors, Utils, Constants
-
-from .Constants import (
-    BLOCK_LABEL_PADDING, PORT_SPACING, PORT_SEPARATION, LABEL_SEPARATION,
-    PORT_BORDER_SEPARATION, BLOCK_FONT, PARAM_FONT
-)
-from . Element import Element
-from ..core import utils
-from ..core.Block import Block as CoreBlock
-
-
-class Block(CoreBlock, Element):
-    """The graphical signal block."""
-
-    def __init__(self, parent, **n):
-        """
-        Block constructor.
-        Add graphics related params to the block.
-        """
-        super(self.__class__, self).__init__(parent, **n)
-
-        self.states.update(_coordinate=(0, 0), _rotation=0)
-        self.width = self.height = 0
-        Element.__init__(self)  # needs the states and initial sizes
-
-        self._surface_layouts = [
-            Gtk.DrawingArea().create_pango_layout(''),  # title
-            Gtk.DrawingArea().create_pango_layout(''),  # params
-        ]
-        self._surface_layout_offsets = 0, 0
-        self._comment_layout = None
-
-        self._area = []
-        self._border_color = self._bg_color = Colors.BLOCK_ENABLED_COLOR
-        self._font_color = list(Colors.FONT_COLOR)
-
-    @property
-    def coordinate(self):
-        """
-        Get the coordinate from the position param.
-
-        Returns:
-            the coordinate tuple (x, y) or (0, 0) if failure
-        """
-        return self.states['_coordinate']
-
-    @coordinate.setter
-    def coordinate(self, coor):
-        """
-        Set the coordinate into the position param.
-
-        Args:
-            coor: the coordinate tuple (x, y)
-        """
-        if Actions.TOGGLE_SNAP_TO_GRID.get_active():
-            offset_x, offset_y = (0, self.height / 2) if self.is_horizontal() else (self.height / 2, 0)
-            coor = (
-                Utils.align_to_grid(coor[0] + offset_x) - offset_x,
-                Utils.align_to_grid(coor[1] + offset_y) - offset_y
-            )
-        self.states['_coordinate'] = coor
-
-    @property
-    def rotation(self):
-        """
-        Get the rotation from the position param.
-
-        Returns:
-            the rotation in degrees or 0 if failure
-        """
-        return self.states['_rotation']
-
-    @rotation.setter
-    def rotation(self, rot):
-        """
-        Set the rotation into the position param.
-
-        Args:
-            rot: the rotation in degrees
-        """
-        self.states['_rotation'] = rot
-
-    def _update_colors(self):
-        self._bg_color = (
-            Colors.MISSING_BLOCK_BACKGROUND_COLOR if self.is_dummy_block else
-            Colors.BLOCK_BYPASSED_COLOR if self.state == 'bypassed' else
-            Colors.BLOCK_ENABLED_COLOR if self.state == 'enabled' else
-            Colors.BLOCK_DISABLED_COLOR
-        )
-        self._font_color[-1] = 1.0 if self.state == 'enabled' else 0.4
-        self._border_color = (
-            Colors.MISSING_BLOCK_BORDER_COLOR if self.is_dummy_block else
-            Colors.BORDER_COLOR_DISABLED if not self.state == 'enabled' else Colors.BORDER_COLOR
-        )
-
-    def create_shapes(self):
-        """Update the block, parameters, and ports when a change occurs."""
-        if self.is_horizontal():
-            self._area = (0, 0, self.width, self.height)
-        elif self.is_vertical():
-            self._area = (0, 0, self.height, self.width)
-        self.bounds_from_area(self._area)
-
-        bussified = self.current_bus_structure['source'], self.current_bus_structure['sink']
-        for ports, has_busses in zip((self.active_sources, self.active_sinks), bussified):
-            if not ports:
-                continue
-            port_separation = PORT_SEPARATION if not has_busses else ports[0].height + PORT_SPACING
-            offset = (self.height - (len(ports) - 1) * port_separation - ports[0].height) / 2
-            for index, port in enumerate(ports):
-                port.create_shapes()
-
-                port.coordinate = {
-                    0: (+self.width, offset),
-                    90: (offset, -port.width),
-                    180: (-port.width, offset),
-                    270: (offset, +self.width),
-                }[port.get_connector_direction()]
-                offset += PORT_SEPARATION if not has_busses else port.height + PORT_SPACING
-
-                port.connector_length = Constants.CONNECTOR_EXTENSION_MINIMAL + \
-                                        Constants.CONNECTOR_EXTENSION_INCREMENT * index
-
-    def create_labels(self):
-        """Create the labels for the signal block."""
-        title_layout, params_layout = self._surface_layouts
-
-        title_layout.set_markup(
-            '<span {foreground} font_desc="{font}"><b>{name}</b></span>'.format(
-                foreground='foreground="red"' if not self.is_valid() else '', font=BLOCK_FONT,
-                name=Utils.encode(self.name)
-            )
-        )
-        title_width, title_height = title_layout.get_pixel_size()
-
-        # update the params layout
-        if not self.is_dummy_block:
-            markups = [param.format_block_surface_markup()
-                       for param in self.params.values() if param.get_hide() not in ('all', 'part')]
-        else:
-            markups = ['<span font_desc="{font}"><b>key: </b>{key}</span>'.format(font=PARAM_FONT, key=self.key)]
-
-        params_layout.set_spacing(LABEL_SEPARATION * Pango.SCALE)
-        params_layout.set_markup('\n'.join(markups))
-        params_width, params_height = params_layout.get_pixel_size() if markups else (0, 0)
-
-        label_width = max(title_width, params_width)
-        label_height = title_height + LABEL_SEPARATION + params_height
-
-        title_layout.set_width(label_width * Pango.SCALE)
-        title_layout.set_alignment(Pango.Alignment.CENTER)
-
-        # calculate width and height needed
-        width = label_width + 2 * BLOCK_LABEL_PADDING
-        height = label_height + 2 * BLOCK_LABEL_PADDING
-
-        self._update_colors()
-        self.create_port_labels()
-
-        def get_min_height_for_ports(ports):
-            min_height = 2 * PORT_BORDER_SEPARATION + len(ports) * PORT_SEPARATION
-            if ports:
-                min_height -= ports[-1].height
-            return min_height
-
-        height = max(height,
-                     get_min_height_for_ports(self.active_sinks),
-                     get_min_height_for_ports(self.active_sources))
-
-        def get_min_height_for_bus_ports(ports):
-            return 2 * PORT_BORDER_SEPARATION + sum(
-                port.height + PORT_SPACING for port in ports if port.get_type() == 'bus'
-            ) - PORT_SPACING
-
-        if self.current_bus_structure['sink']:
-            height = max(height, get_min_height_for_bus_ports(self.active_sinks))
-        if self.current_bus_structure['source']:
-            height = max(height, get_min_height_for_bus_ports(self.active_sources))
-
-        self.width, self.height = width, height = Utils.align_to_grid((width, height))
-
-        self._surface_layout_offsets = [
-            (width - label_width) / 2.0,
-            (height - label_height) / 2.0
-        ]
-
-        self.create_comment_layout()
-
-    def create_port_labels(self):
-        for ports in (self.active_sinks, self.active_sources):
-            max_width = 0
-            for port in ports:
-                port.create_labels()
-                max_width = max(max_width, port.width_with_label)
-            for port in ports:
-                port.width = max_width
-
-    def create_comment_layout(self):
-        markups = []
-
-        # Show the flow graph complexity on the top block if enabled
-        if Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY.get_active() and self.key == "options":
-            complexity = utils.calculate_flowgraph_complexity(self.parent)
-            markups.append(
-                '<span foreground="#444" size="medium" font_desc="{font}">'
-                '<b>Complexity: {num}bal</b></span>'.format(num=Utils.num_to_str(complexity), font=BLOCK_FONT)
-            )
-        comment = self.comment  # Returns None if there are no comments
-        if comment:
-            if markups:
-                markups.append('<span></span>')
-
-            markups.append('<span foreground="{foreground}" font_desc="{font}">{comment}</span>'.format(
-                foreground='#444' if self.enabled else '#888', font=BLOCK_FONT, comment=Utils.encode(comment)
-            ))
-        if markups:
-            layout = self._comment_layout = Gtk.DrawingArea().create_pango_layout('')
-            layout.set_markup(''.join(markups))
-        else:
-            self._comment_layout = None
-
-    def draw(self, cr):
-        """
-        Draw the signal block with label and inputs/outputs.
-        """
-        border_color = Colors.HIGHLIGHT_COLOR if self.highlighted else self._border_color
-        cr.translate(*self.coordinate)
-
-        for port in self.active_ports():  # ports first
-            cr.save()
-            port.draw(cr)
-            cr.restore()
-
-        cr.rectangle(*self._area)
-        cr.set_source_rgba(*self._bg_color)
-        cr.fill_preserve()
-        cr.set_source_rgba(*border_color)
-        cr.stroke()
-
-        # title and params label
-        if self.is_vertical():
-            cr.rotate(-math.pi / 2)
-            cr.translate(-self.width, 0)
-        cr.translate(*self._surface_layout_offsets)
-
-        cr.set_source_rgba(*self._font_color)
-        for layout in self._surface_layouts:
-            PangoCairo.update_layout(cr, layout)
-            PangoCairo.show_layout(cr, layout)
-            _, h = layout.get_pixel_size()
-            cr.translate(0, h + LABEL_SEPARATION)
-
-    def what_is_selected(self, coor, coor_m=None):
-        """
-        Get the element that is selected.
-
-        Args:
-            coor: the (x,y) tuple
-            coor_m: the (x_m, y_m) tuple
-
-        Returns:
-            this block, a port, or None
-        """
-        for port in self.active_ports():
-            port_selected = port.what_is_selected(
-                coor=[a - b for a, b in zip(coor, self.coordinate)],
-                coor_m=[a - b for a, b in zip(coor, self.coordinate)] if coor_m is not None else None
-            )
-            if port_selected:
-                return port_selected
-        return Element.what_is_selected(self, coor, coor_m)
-
-    def draw_comment(self, cr):
-        if not self._comment_layout:
-            return
-        x, y = self.coordinate
-
-        if self.is_horizontal():
-            y += self.height + BLOCK_LABEL_PADDING
-        else:
-            x += self.height + BLOCK_LABEL_PADDING
-
-        cr.save()
-        cr.translate(x, y)
-        PangoCairo.update_layout(cr, self._comment_layout)
-        PangoCairo.show_layout(cr, self._comment_layout)
-        cr.restore()
-
-    @property
-    def extend(self):
-        extend = Element.extend.fget(self)
-        x, y = self.coordinate
-        for port in self.active_ports():
-            extend = (min_or_max(xy, offset + p_xy) for offset, min_or_max, xy, p_xy in zip(
-                (x, y, x, y), (min, min, max, max), extend, port.extend
-            ))
-        return tuple(extend)
-
-    ##############################################
-    # Controller Modify
-    ##############################################
-    def type_controller_modify(self, direction):
-        """
-        Change the type controller.
-
-        Args:
-            direction: +1 or -1
-
-        Returns:
-            true for change
-        """
-        type_templates = ' '.join(p._type for p in self.get_children())
-        type_param = None
-        for key, param in six.iteritems(self.params):
-            if not param.is_enum():
-                continue
-            # Priority to the type controller
-            if param.key in type_templates:
-                type_param = param
-                break
-            # Use param if type param is unset
-            if not type_param:
-                type_param = param
-        if not type_param:
-            return False
-
-        # Try to increment the enum by direction
-        try:
-            keys = list(type_param.options)
-            old_index = keys.index(type_param.get_value())
-            new_index = (old_index + direction + len(keys)) % len(keys)
-            type_param.set_value(keys[new_index])
-            return True
-        except:
-            return False
-
-    def port_controller_modify(self, direction):
-        """
-        Change the port controller.
-
-        Args:
-            direction: +1 or -1
-
-        Returns:
-            true for change
-        """
-        changed = False
-        # Concat the nports string from the private nports settings of all ports
-        nports_str = ' '.join(port._nports for port in self.get_ports())
-        # Modify all params whose keys appear in the nports string
-        for key, param in six.iteritems(self.params):
-            if param.is_enum() or param.key not in nports_str:
-                continue
-            # Try to increment the port controller by direction
-            try:
-                value = param.get_evaluated() + direction
-                if value > 0:
-                    param.set_value(value)
-                    changed = True
-            except:
-                pass
-        return changed
-
diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py
deleted file mode 100644
index 9862328d6a..0000000000
--- a/grc/gui/Connection.py
+++ /dev/null
@@ -1,207 +0,0 @@
-"""
-Copyright 2007, 2008, 2009 Free Software Foundation, Inc.
-This file is part of GNU Radio
-
-GNU Radio Companion is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-GNU Radio Companion is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
-"""
-
-from __future__ import absolute_import
-
-from . import Colors, Utils
-from .Constants import CONNECTOR_ARROW_BASE, CONNECTOR_ARROW_HEIGHT, GR_MESSAGE_DOMAIN
-from .Element import Element
-
-from ..core.Element import nop_write
-from ..core.Connection import Connection as CoreConnection
-
-
-class Connection(CoreConnection, Element):
-    """
-    A graphical connection for ports.
-    The connection has 2 parts, the arrow and the wire.
-    The coloring of the arrow and wire exposes the status of 3 states:
-        enabled/disabled, valid/invalid, highlighted/non-highlighted.
-    The wire coloring exposes the enabled and highlighted states.
-    The arrow coloring exposes the enabled and valid states.
-    """
-
-    def __init__(self, *args, **kwargs):
-        super(self.__class__, self).__init__(*args, **kwargs)
-        Element.__init__(self)
-
-        self._line = []
-        self._line_width_factor = 1.0
-        self._color = self._color2 = self._arrow_color = None
-
-        self._sink_rot = self._source_rot = None
-        self._sink_coor = self._source_coor = None
-
-    @nop_write
-    @property
-    def coordinate(self):
-        return self.source_port.get_connector_coordinate()
-
-    @nop_write
-    @property
-    def rotation(self):
-        """
-        Get the 0 degree rotation.
-        Rotations are irrelevant in connection.
-
-        Returns:
-            0
-        """
-        return 0
-
-    def create_shapes(self):
-        """Pre-calculate relative coordinates."""
-        self._sink_rot = None
-        self._source_rot = None
-        self._sink_coor = None
-        self._source_coor = None
-        #get the source coordinate
-        try:
-            connector_length = self.source_port.connector_length
-        except:
-            return  # todo: why?
-        self.x1, self.y1 = Utils.get_rotated_coordinate((connector_length, 0), self.source_port.rotation)
-        #get the sink coordinate
-        connector_length = self.sink_port.connector_length + CONNECTOR_ARROW_HEIGHT
-        self.x2, self.y2 = Utils.get_rotated_coordinate((-connector_length, 0), self.sink_port.rotation)
-        #build the arrow
-        self._arrow_base = [
-            (0, 0),
-            Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, -CONNECTOR_ARROW_BASE/2), self.sink_port.rotation),
-            Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, CONNECTOR_ARROW_BASE/2), self.sink_port.rotation),
-        ] if self.sink_block.state != 'bypassed' else []
-        source_domain = self.source_port.domain
-        sink_domain = self.sink_port.domain
-
-        def get_domain_color(domain_name):
-            domain = self.parent_platform.domains.get(domain_name, {})
-            color_spec = domain.get('color')
-            return Colors.get_color(color_spec) if color_spec else Colors.DEFAULT_DOMAIN_COLOR
-
-        if source_domain == GR_MESSAGE_DOMAIN:
-            self._line_width_factor = 1.0
-            self._color = None
-            self._color2 = Colors.CONNECTION_ENABLED_COLOR
-        else:
-            if source_domain != sink_domain:
-                self._line_width_factor = 2.0
-            self._color = get_domain_color(source_domain)
-            self._color2 = get_domain_color(sink_domain)
-        self._arrow_color = self._color2 if self.is_valid() else Colors.CONNECTION_ERROR_COLOR
-        self._update_after_move()
-
-    def _update_after_move(self):
-        """Calculate coordinates."""
-        source = self.source_port
-        sink = self.sink_port
-        source_dir = source.get_connector_direction()
-        sink_dir = sink.get_connector_direction()
-
-        x_pos, y_pos = self.coordinate
-        x_start, y_start = source.get_connector_coordinate()
-        x_end, y_end = sink.get_connector_coordinate()
-
-        p3 = x3, y3 = x_end - x_pos, y_end - y_pos
-        p2 = x2, y2 = self.x2 + x3, self.y2 + y3
-        p1 = x1, y1 = self.x1, self.y1
-        p0 = x_start - x_pos, y_start - y_pos
-        self._arrow = [(x + x3, y + y3) for x, y in self._arrow_base]
-
-        if abs(source_dir - sink.get_connector_direction()) == 180:
-            # 2 possible point sets to create a 3-line connector
-            mid_x, mid_y = (x1 + x2) / 2.0, (y1 + y2) / 2.0
-            points = ((mid_x, y1), (mid_x, y2))
-            alt = ((x1, mid_y), (x2, mid_y))
-            # source connector -> points[0][0] should be in the direction of source (if possible)
-            if Utils.get_angle_from_coordinates(p1, points[0]) != source_dir:
-                points, alt = alt, points
-            # points[0] -> sink connector should not be in the direction of sink
-            if Utils.get_angle_from_coordinates(points[0], p2) == sink_dir:
-                points, alt = alt, points
-            # points[0] -> source connector should not be in the direction of source
-            if Utils.get_angle_from_coordinates(points[0], p1) == source_dir:
-                points, alt = alt, points
-            # create 3-line connector
-            i1, i2 = points
-            self._line = [p0, p1, i1, i2, p2, p3]
-        else:
-            # 2 possible points to create a right-angled connector
-            point, alt = [(x1, y2), (x2, y1)]
-            # source connector -> point should be in the direction of source (if possible)
-            if Utils.get_angle_from_coordinates(p1, point) != source_dir:
-                point, alt = alt, point
-            # point -> sink connector should not be in the direction of sink
-            if Utils.get_angle_from_coordinates(point, p2) == sink_dir:
-                point, alt = alt, point
-            # point -> source connector should not be in the direction of source
-            if Utils.get_angle_from_coordinates(point, p1) == source_dir:
-                point, alt = alt, point
-            # create right-angled connector
-            self._line = [p0, p1, point, p2, p3]
-        self.bounds_from_line(self._line)
-
-    def draw(self, cr):
-        """
-        Draw the connection.
-        """
-        sink = self.sink_port
-        source = self.source_port
-        # check for changes
-        if self._sink_rot != sink.rotation or self._source_rot != source.rotation:
-            self.create_shapes()
-            self._sink_rot = sink.rotation
-            self._source_rot = source.rotation
-
-        elif self._sink_coor != sink.parent_block.coordinate or self._source_coor != source.parent_block.coordinate:
-            self._update_after_move()
-            self._sink_coor = sink.parent_block.coordinate
-            self._source_coor = source.parent_block.coordinate
-        # draw
-        color1, color2, arrow_color = (
-            None if color is None else
-            Colors.HIGHLIGHT_COLOR if self.highlighted else
-            Colors.CONNECTION_DISABLED_COLOR if not self.enabled else color
-            for color in (self._color, self._color2, self._arrow_color)
-        )
-
-        cr.translate(*self.coordinate)
-        cr.set_line_width(self._line_width_factor * cr.get_line_width())
-        for point in self._line:
-            cr.line_to(*point)
-
-        if color1:  # not a message connection
-            cr.set_source_rgba(*color1)
-            cr.stroke_preserve()
-
-        if color1 != color2:
-            cr.save()
-            cr.set_dash([5.0, 5.0], 5.0 if color1 else 0.0)
-            cr.set_source_rgba(*color2)
-            cr.stroke()
-            cr.restore()
-        else:
-            cr.new_path()
-
-        if self._arrow:
-            cr.set_source_rgba(*arrow_color)
-            cr.move_to(*self._arrow[0])
-            cr.line_to(*self._arrow[1])
-            cr.line_to(*self._arrow[2])
-            cr.close_path()
-            cr.fill()
diff --git a/grc/gui/Element.py b/grc/gui/Element.py
deleted file mode 100644
index 17cd6ddd92..0000000000
--- a/grc/gui/Element.py
+++ /dev/null
@@ -1,184 +0,0 @@
-"""
-Copyright 2007, 2008, 2009, 2016 Free Software Foundation, Inc.
-This file is part of GNU Radio
-
-GNU Radio Companion is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-GNU Radio Companion is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
-"""
-
-from __future__ import absolute_import
-from .Constants import LINE_SELECT_SENSITIVITY
-
-from six.moves import zip
-
-
-class Element(object):
-    """
-    GraphicalElement is the base class for all graphical elements.
-    It contains an X,Y coordinate, a list of rectangular areas that the element occupies,
-    and methods to detect selection of those areas.
-    """
-
-    @classmethod
-    def make_cls_with_base(cls, super_cls):
-        name = super_cls.__name__
-        bases = (super_cls,) + cls.__bases__[1:]
-        namespace = cls.__dict__.copy()
-        return type(name, bases, namespace)
-
-    def __init__(self):
-        """
-        Make a new list of rectangular areas and lines, and set the coordinate and the rotation.
-        """
-        self.coordinate = (0, 0)
-        self.rotation = 0
-        self.highlighted = False
-
-        self._bounding_rects = []
-        self._bounding_points = []
-
-    def is_horizontal(self, rotation=None):
-        """
-        Is this element horizontal?
-        If rotation is None, use this element's rotation.
-
-        Args:
-            rotation: the optional rotation
-
-        Returns:
-            true if rotation is horizontal
-        """
-        rotation = rotation or self.rotation
-        return rotation in (0, 180)
-
-    def is_vertical(self, rotation=None):
-        """
-        Is this element vertical?
-        If rotation is None, use this element's rotation.
-
-        Args:
-            rotation: the optional rotation
-
-        Returns:
-            true if rotation is vertical
-        """
-        rotation = rotation or self.rotation
-        return rotation in (90, 270)
-
-    def rotate(self, rotation):
-        """
-        Rotate all of the areas by 90 degrees.
-
-        Args:
-            rotation: multiple of 90 degrees
-        """
-        self.rotation = (self.rotation + rotation) % 360
-
-    def move(self, delta_coor):
-        """
-        Move the element by adding the delta_coor to the current coordinate.
-
-        Args:
-            delta_coor: (delta_x,delta_y) tuple
-        """
-        x, y = self.coordinate
-        dx, dy = delta_coor
-        self.coordinate = (x + dx, y + dy)
-
-    def create_labels(self):
-        """
-        Create labels (if applicable) and call on all children.
-        Call this base method before creating labels in the element.
-        """
-
-    def create_shapes(self):
-        """
-        Create shapes (if applicable) and call on all children.
-        Call this base method before creating shapes in the element.
-        """
-
-    def draw(self, cr):
-        raise NotImplementedError()
-
-    def bounds_from_area(self, area):
-        x1, y1, w, h = area
-        x2 = x1 + w
-        y2 = y1 + h
-        self._bounding_rects = [(x1, y1, x2, y2)]
-        self._bounding_points = [(x1, y1), (x2, y1), (x1, y2), (x2, y2)]
-
-    def bounds_from_line(self, line):
-        self._bounding_rects = rects = []
-        self._bounding_points = list(line)
-        last_point = line[0]
-        for x2, y2 in line[1:]:
-            (x1, y1), last_point = last_point, (x2, y2)
-            if x1 == x2:
-                x1, x2 = x1 - LINE_SELECT_SENSITIVITY, x2 + LINE_SELECT_SENSITIVITY
-                if y2 < y1:
-                    y1, y2 = y2, y1
-            elif y1 == y2:
-                y1, y2 = y1 - LINE_SELECT_SENSITIVITY, y2 + LINE_SELECT_SENSITIVITY
-                if x2 < x1:
-                    x1, x2 = x2, x1
-
-            rects.append((x1, y1, x2, y2))
-
-    def what_is_selected(self, coor, coor_m=None):
-        """
-        One coordinate specified:
-            Is this element selected at given coordinate?
-            ie: is the coordinate encompassed by one of the areas or lines?
-        Both coordinates specified:
-            Is this element within the rectangular region defined by both coordinates?
-            ie: do any area corners or line endpoints fall within the region?
-
-        Args:
-            coor: the selection coordinate, tuple x, y
-            coor_m: an additional selection coordinate.
-
-        Returns:
-            self if one of the areas/lines encompasses coor, else None.
-        """
-        x, y = [a - b for a, b in zip(coor, self.coordinate)]
-
-        if not coor_m:
-            for x1, y1, x2, y2 in self._bounding_rects:
-                if x1 <= x <= x2 and y1 <= y <= y2:
-                    return self
-        else:
-            x_m, y_m = [a - b for a, b in zip(coor_m, self.coordinate)]
-            if y_m < y:
-                y, y_m = y_m, y
-            if x_m < x:
-                x, x_m = x_m, x
-
-            for x1, y1 in self._bounding_points:
-                if x <= x1 <= x_m and y <= y1 <= y_m:
-                    return self
-
-    @property
-    def extend(self):
-        x_min, y_min = x_max, y_max = self.coordinate
-        x_min += min(x for x, y in self._bounding_points)
-        y_min += min(y for x, y in self._bounding_points)
-        x_max += max(x for x, y in self._bounding_points)
-        y_max += max(y for x, y in self._bounding_points)
-        return x_min, y_min, x_max, y_max
-
-    def mouse_over(self):
-        pass
-
-    def mouse_out(self):
-        pass
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
deleted file mode 100644
index f04383f32c..0000000000
--- a/grc/gui/FlowGraph.py
+++ /dev/null
@@ -1,754 +0,0 @@
-"""
-Copyright 2007-2011, 2016q Free Software Foundation, Inc.
-This file is part of GNU Radio
-
-GNU Radio Companion is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-GNU Radio Companion is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
-"""
-
-from __future__ import absolute_import
-
-import functools
-import ast
-import random
-from distutils.spawn import find_executable
-from itertools import count
-
-import six
-from six.moves import filter
-
-from gi.repository import GLib
-
-from . import Actions, Colors, Constants, Utils, Bars, Dialogs
-from .Element import Element
-from .external_editor import ExternalEditor
-
-from ..core.FlowGraph import FlowGraph as CoreFlowgraph
-from ..core import Messages
-
-
-class FlowGraph(CoreFlowgraph, Element):
-    """
-    FlowGraph is the data structure to store graphical signal blocks,
-    graphical inputs and outputs,
-    and the connections between inputs and outputs.
-    """
-
-    def __init__(self, parent, **kwargs):
-        """
-        FlowGraph constructor.
-        Create a list for signal blocks and connections. Connect mouse handlers.
-        """
-        super(self.__class__, self).__init__(parent, **kwargs)
-        Element.__init__(self)
-        self.drawing_area = None
-        # important vars dealing with mouse event tracking
-        self.element_moved = False
-        self.mouse_pressed = False
-        self.press_coor = (0, 0)
-        # selected
-        self.selected_elements = set()
-        self._old_selected_port = None
-        self._new_selected_port = None
-        # current mouse hover element
-        self.element_under_mouse = None
-        # context menu
-        self._context_menu = Bars.ContextMenu()
-        self.get_context_menu = lambda: self._context_menu
-
-        self._elements_to_draw = []
-        self._external_updaters = {}
-
-    def _get_unique_id(self, base_id=''):
-        """
-        Get a unique id starting with the base id.
-
-        Args:
-            base_id: the id starts with this and appends a count
-
-        Returns:
-            a unique id
-        """
-        block_ids = set(b.get_id() for b in self.blocks)
-        for index in count():
-            block_id = '{}_{}'.format(base_id, index)
-            if block_id not in block_ids:
-                break
-        return block_id
-
-    def install_external_editor(self, param):
-        target = (param.parent_block.get_id(), param.key)
-
-        if target in self._external_updaters:
-            editor = self._external_updaters[target]
-        else:
-            config = self.parent_platform.config
-            editor = (find_executable(config.editor) or
-                      Dialogs.choose_editor(None, config))  # todo: pass in parent
-            if not editor:
-                return
-            updater = functools.partial(
-                self.handle_external_editor_change, target=target)
-            editor = self._external_updaters[target] = ExternalEditor(
-                editor=editor,
-                name=target[0], value=param.get_value(),
-                callback=functools.partial(GLib.idle_add, updater)
-            )
-            editor.start()
-        try:
-            editor.open_editor()
-        except Exception as e:
-            # Problem launching the editor. Need to select a new editor.
-            Messages.send('>>> Error opening an external editor. Please select a different editor.\n')
-            # Reset the editor to force the user to select a new one.
-            self.parent_platform.config.editor = ''
-
-    def handle_external_editor_change(self, new_value, target):
-        try:
-            block_id, param_key = target
-            self.get_block(block_id).get_param(param_key).set_value(new_value)
-
-        except (IndexError, ValueError):  # block no longer exists
-            self._external_updaters[target].stop()
-            del self._external_updaters[target]
-            return
-        Actions.EXTERNAL_UPDATE()
-
-    def add_new_block(self, key, coor=None):
-        """
-        Add a block of the given key to this flow graph.
-
-        Args:
-            key: the block key
-            coor: an optional coordinate or None for random
-        """
-        id = self._get_unique_id(key)
-        scroll_pane = self.drawing_area.get_parent().get_parent()
-        # calculate the position coordinate
-        h_adj = scroll_pane.get_hadjustment()
-        v_adj = scroll_pane.get_vadjustment()
-        if coor is None: coor = (
-            int(random.uniform(.25, .75)*h_adj.get_page_size() + h_adj.get_value()),
-            int(random.uniform(.25, .75)*v_adj.get_page_size() + v_adj.get_value()),
-        )
-        # get the new block
-        block = self.new_block(key)
-        block.coordinate = coor
-        block.get_param('id').set_value(id)
-        Actions.ELEMENT_CREATE()
-        return id
-
-    def make_connection(self):
-        """this selection and the last were ports, try to connect them"""
-        if self._old_selected_port and self._new_selected_port:
-            try:
-                self.connect(self._old_selected_port, self._new_selected_port)
-                Actions.ELEMENT_CREATE()
-            except Exception as e:
-                Messages.send_fail_connection(e)
-            self._old_selected_port = None
-            self._new_selected_port = None
-            return True
-        return False
-
-    def update(self):
-        """
-        Call the top level rewrite and validate.
-        Call the top level create labels and shapes.
-        """
-        self.rewrite()
-        self.validate()
-        self.update_elements_to_draw()
-        self.create_labels()
-        self.create_shapes()
-
-    def reload(self):
-        """
-        Reload flow-graph (with updated blocks)
-
-        Args:
-            page: the page to reload (None means current)
-        Returns:
-            False if some error occurred during import
-        """
-        success = False
-        data = self.export_data()
-        if data:
-            self.unselect()
-            success = self.import_data(data)
-            self.update()
-        return success
-
-    ###########################################################################
-    # Copy Paste
-    ###########################################################################
-    def copy_to_clipboard(self):
-        """
-        Copy the selected blocks and connections into the clipboard.
-
-        Returns:
-            the clipboard
-        """
-        #get selected blocks
-        blocks = list(self.selected_blocks())
-        if not blocks:
-            return None
-        #calc x and y min
-        x_min, y_min = blocks[0].coordinate
-        for block in blocks:
-            x, y = block.coordinate
-            x_min = min(x, x_min)
-            y_min = min(y, y_min)
-        #get connections between selected blocks
-        connections = list(filter(
-            lambda c: c.source_block in blocks and c.sink_block in blocks,
-            self.connections,
-        ))
-        clipboard = (
-            (x_min, y_min),
-            [block.export_data() for block in blocks],
-            [connection.export_data() for connection in connections],
-        )
-        return clipboard
-
-    def paste_from_clipboard(self, clipboard):
-        """
-        Paste the blocks and connections from the clipboard.
-
-        Args:
-            clipboard: the nested data of blocks, connections
-        """
-        # todo: rewrite this...
-        selected = set()
-        (x_min, y_min), blocks_n, connections_n = clipboard
-        old_id2block = dict()
-        # recalc the position
-        scroll_pane = self.drawing_area.get_parent().get_parent()
-        h_adj = scroll_pane.get_hadjustment()
-        v_adj = scroll_pane.get_vadjustment()
-        x_off = h_adj.get_value() - x_min + h_adj.get_page_size() / 4
-        y_off = v_adj.get_value() - y_min + v_adj.get_page_size() / 4
-        if len(self.get_elements()) <= 1:
-            x_off, y_off = 0, 0
-        #create blocks
-        for block_n in blocks_n:
-            block_key = block_n.get('key')
-            if block_key == 'options':
-                continue
-            block = self.new_block(block_key)
-            if not block:
-                continue  # unknown block was pasted (e.g. dummy block)
-            selected.add(block)
-            #set params
-            param_data = {n['key']: n['value'] for n in block_n.get('param', [])}
-            for key in block.states:
-                try:
-                    block.states[key] = ast.literal_eval(param_data.pop(key))
-                except (KeyError, SyntaxError, ValueError):
-                    pass
-            if block_key == 'epy_block':
-                block.get_param('_io_cache').set_value(param_data.pop('_io_cache'))
-                block.get_param('_source_code').set_value(param_data.pop('_source_code'))
-                block.rewrite()  # this creates the other params
-            for param_key, param_value in six.iteritems(param_data):
-                #setup id parameter
-                if param_key == 'id':
-                    old_id2block[param_value] = block
-                    #if the block id is not unique, get a new block id
-                    if param_value in (blk.get_id() for blk in self.blocks):
-                        param_value = self._get_unique_id(param_value)
-                #set value to key
-                block.get_param(param_key).set_value(param_value)
-            #move block to offset coordinate
-            block.move((x_off, y_off))
-        #update before creating connections
-        self.update()
-        #create connections
-        for connection_n in connections_n:
-            source = old_id2block[connection_n.get('source_block_id')].get_source(connection_n.get('source_key'))
-            sink = old_id2block[connection_n.get('sink_block_id')].get_sink(connection_n.get('sink_key'))
-            self.connect(source, sink)
-        #set all pasted elements selected
-        for block in selected:
-            selected = selected.union(set(block.get_connections()))
-        self.selected_elements = set(selected)
-
-    ###########################################################################
-    # Modify Selected
-    ###########################################################################
-    def type_controller_modify_selected(self, direction):
-        """
-        Change the registered type controller for the selected signal blocks.
-
-        Args:
-            direction: +1 or -1
-
-        Returns:
-            true for change
-        """
-        return any(sb.type_controller_modify(direction) for sb in self.selected_blocks())
-
-    def port_controller_modify_selected(self, direction):
-        """
-        Change port controller for the selected signal blocks.
-
-        Args:
-            direction: +1 or -1
-
-        Returns:
-            true for changed
-        """
-        return any(sb.port_controller_modify(direction) for sb in self.selected_blocks())
-
-    def change_state_selected(self, new_state):
-        """
-        Enable/disable the selected blocks.
-
-        Args:
-            new_state: a block state
-
-        Returns:
-            true if changed
-        """
-        changed = False
-        for block in self.selected_blocks():
-            changed |= block.state != new_state
-            block.state = new_state
-        return changed
-
-    def move_selected(self, delta_coordinate):
-        """
-        Move the element and by the change in coordinates.
-
-        Args:
-            delta_coordinate: the change in coordinates
-        """
-        for selected_block in self.selected_blocks():
-            selected_block.move(delta_coordinate)
-            self.element_moved = True
-
-    def align_selected(self, calling_action=None):
-        """
-        Align the selected blocks.
-
-        Args:
-            calling_action: the action initiating the alignment
-
-        Returns:
-            True if changed, otherwise False
-        """
-        blocks = list(self.selected_blocks())
-        if calling_action is None or not blocks:
-            return False
-
-        # compute common boundary of selected objects
-        min_x, min_y = max_x, max_y = blocks[0].coordinate
-        for selected_block in blocks:
-            x, y = selected_block.coordinate
-            min_x, min_y = min(min_x, x), min(min_y, y)
-            x += selected_block.width
-            y += selected_block.height
-            max_x, max_y = max(max_x, x), max(max_y, y)
-        ctr_x, ctr_y = (max_x + min_x)/2, (max_y + min_y)/2
-
-        # align the blocks as requested
-        transform = {
-                Actions.BLOCK_VALIGN_TOP: lambda x, y, w, h: (x, min_y),
-                Actions.BLOCK_VALIGN_MIDDLE: lambda x, y, w, h: (x, ctr_y - h/2),
-                Actions.BLOCK_VALIGN_BOTTOM: lambda x, y, w, h: (x, max_y - h),
-                Actions.BLOCK_HALIGN_LEFT: lambda x, y, w, h: (min_x, y),
-                Actions.BLOCK_HALIGN_CENTER: lambda x, y, w, h: (ctr_x-w/2, y),
-                Actions.BLOCK_HALIGN_RIGHT: lambda x, y, w, h: (max_x - w, y),
-        }.get(calling_action, lambda *args: args)
-
-        for selected_block in blocks:
-            x, y = selected_block.coordinate
-            w, h = selected_block.width, selected_block.height
-            selected_block.coordinate = transform(x, y, w, h)
-
-        return True
-
-    def rotate_selected(self, rotation):
-        """
-        Rotate the selected blocks by multiples of 90 degrees.
-
-        Args:
-            rotation: the rotation in degrees
-
-        Returns:
-            true if changed, otherwise false.
-        """
-        if not any(self.selected_blocks()):
-            return False
-        #initialize min and max coordinates
-        min_x, min_y = max_x, max_y = self.selected_block.coordinate
-        # rotate each selected block, and find min/max coordinate
-        for selected_block in self.selected_blocks():
-            selected_block.rotate(rotation)
-            #update the min/max coordinate
-            x, y = selected_block.coordinate
-            min_x, min_y = min(min_x, x), min(min_y, y)
-            max_x, max_y = max(max_x, x), max(max_y, y)
-        #calculate center point of slected blocks
-        ctr_x, ctr_y = (max_x + min_x)/2, (max_y + min_y)/2
-        #rotate the blocks around the center point
-        for selected_block in self.selected_blocks():
-            x, y = selected_block.coordinate
-            x, y = Utils.get_rotated_coordinate((x - ctr_x, y - ctr_y), rotation)
-            selected_block.coordinate = (x + ctr_x, y + ctr_y)
-        return True
-
-    def remove_selected(self):
-        """
-        Remove selected elements
-
-        Returns:
-            true if changed.
-        """
-        changed = False
-        for selected_element in self.selected_elements:
-            self.remove_element(selected_element)
-            changed = True
-        return changed
-
-    def update_selected(self):
-        """
-        Remove deleted elements from the selected elements list.
-        Update highlighting so only the selected are highlighted.
-        """
-        selected_elements = self.selected_elements
-        elements = self.get_elements()
-        # remove deleted elements
-        for selected in list(selected_elements):
-            if selected in elements:
-                continue
-            selected_elements.remove(selected)
-        if self._old_selected_port and self._old_selected_port.parent not in elements:
-            self._old_selected_port = None
-        if self._new_selected_port and self._new_selected_port.parent not in elements:
-            self._new_selected_port = None
-        # update highlighting
-        for element in elements:
-            element.highlighted = element in selected_elements
-
-    ###########################################################################
-    # Draw stuff
-    ###########################################################################
-
-    def update_elements_to_draw(self):
-        hide_disabled_blocks = Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active()
-        hide_variables = Actions.TOGGLE_HIDE_VARIABLES.get_active()
-
-        def draw_order(elem):
-            return elem.highlighted, elem.is_block, elem.enabled
-
-        elements = sorted(self.get_elements(), key=draw_order)
-        del self._elements_to_draw[:]
-
-        for element in elements:
-            if hide_disabled_blocks and not element.enabled:
-                continue  # skip hidden disabled blocks and connections
-            if hide_variables and (element.is_variable or element.is_import):
-                continue  # skip hidden disabled blocks and connections
-            self._elements_to_draw.append(element)
-
-    def create_labels(self):
-        for element in self._elements_to_draw:
-            element.create_labels()
-
-    def create_shapes(self):
-        for element in self._elements_to_draw:
-            element.create_shapes()
-
-    def _drawables(self):
-        show_comments = Actions.TOGGLE_SHOW_BLOCK_COMMENTS.get_active()
-        for element in self._elements_to_draw:
-            if element.is_block and show_comments and element.enabled:
-                yield element.draw_comment
-        for element in self._elements_to_draw:
-            yield element.draw
-
-    def draw(self, cr):
-        """Draw blocks connections comment and select rectangle"""
-        for draw_element in self._drawables():
-            cr.save()
-            draw_element(cr)
-            cr.restore()
-
-        # draw multi select rectangle
-        if self.mouse_pressed and (not self.selected_elements or self.drawing_area.ctrl_mask):
-            x1, y1 = self.press_coor
-            x2, y2 = self.coordinate
-            x, y = int(min(x1, x2)), int(min(y1, y2))
-            w, h = int(abs(x1 - x2)), int(abs(y1 - y2))
-            cr.set_source_rgba(
-                Colors.HIGHLIGHT_COLOR[0],
-                Colors.HIGHLIGHT_COLOR[1],
-                Colors.HIGHLIGHT_COLOR[2],
-                0.5,
-            )
-            cr.rectangle(x, y, w, h)
-            cr.fill()
-            cr.rectangle(x, y, w, h)
-            cr.stroke()
-
-    ##########################################################################
-    # selection handling
-    ##########################################################################
-    def update_selected_elements(self):
-        """
-        Update the selected elements.
-        The update behavior depends on the state of the mouse button.
-        When the mouse button pressed the selection will change when
-        the control mask is set or the new selection is not in the current group.
-        When the mouse button is released the selection will change when
-        the mouse has moved and the control mask is set or the current group is empty.
-        Attempt to make a new connection if the old and ports are filled.
-        If the control mask is set, merge with the current elements.
-        """
-        selected_elements = None
-        if self.mouse_pressed:
-            new_selections = self.what_is_selected(self.coordinate)
-            # update the selections if the new selection is not in the current selections
-            # allows us to move entire selected groups of elements
-            if self.drawing_area.ctrl_mask or new_selections not in self.selected_elements:
-                selected_elements = new_selections
-
-            if self._old_selected_port:
-                self._old_selected_port.force_show_label = False
-                self.create_shapes()
-                self.drawing_area.queue_draw()
-            elif self._new_selected_port:
-                self._new_selected_port.force_show_label = True
-
-        else:  # called from a mouse release
-            if not self.element_moved and (not self.selected_elements or self.drawing_area.ctrl_mask):
-                selected_elements = self.what_is_selected(self.coordinate, self.press_coor)
-
-        # this selection and the last were ports, try to connect them
-        if self.make_connection():
-            return
-
-        # update selected elements
-        if selected_elements is None:
-            return
-
-        # if ctrl, set the selected elements to the union - intersection of old and new
-        if self.drawing_area.ctrl_mask:
-            self.selected_elements ^= selected_elements
-        else:
-            self.selected_elements.clear()
-            self.selected_elements.update(selected_elements)
-        Actions.ELEMENT_SELECT()
-
-    def what_is_selected(self, coor, coor_m=None):
-        """
-        What is selected?
-        At the given coordinate, return the elements found to be selected.
-        If coor_m is unspecified, return a list of only the first element found to be selected:
-        Iterate though the elements backwards since top elements are at the end of the list.
-        If an element is selected, place it at the end of the list so that is is drawn last,
-        and hence on top. Update the selected port information.
-
-        Args:
-            coor: the coordinate of the mouse click
-            coor_m: the coordinate for multi select
-
-        Returns:
-            the selected blocks and connections or an empty list
-        """
-        selected_port = None
-        selected = set()
-        # check the elements
-        for element in reversed(self._elements_to_draw):
-            selected_element = element.what_is_selected(coor, coor_m)
-            if not selected_element:
-                continue
-            # update the selected port information
-            if selected_element.is_port:
-                if not coor_m:
-                    selected_port = selected_element
-                selected_element = selected_element.parent_block
-
-            selected.add(selected_element)
-            if not coor_m:
-                break
-        # update selected ports
-        if selected_port is not self._new_selected_port:
-            self._old_selected_port = self._new_selected_port
-            self._new_selected_port = selected_port
-        return selected
-
-    def unselect(self):
-        """
-        Set selected elements to an empty set.
-        """
-        self.selected_elements.clear()
-
-    def select_all(self):
-        """Select all blocks in the flow graph"""
-        self.selected_elements.clear()
-        self.selected_elements.update(self._elements_to_draw)
-
-    def selected_blocks(self):
-        """
-        Get a group of selected blocks.
-
-        Returns:
-            sub set of blocks in this flow graph
-        """
-        return (e for e in self.selected_elements if e.is_block)
-
-    @property
-    def selected_block(self):
-        """
-        Get the selected block when a block or port is selected.
-
-        Returns:
-            a block or None
-        """
-        return next(self.selected_blocks(), None)
-
-    def get_selected_elements(self):
-        """
-        Get the group of selected elements.
-
-        Returns:
-            sub set of elements in this flow graph
-        """
-        return self.selected_elements
-
-    def get_selected_element(self):
-        """
-        Get the selected element.
-
-        Returns:
-            a block, port, or connection or None
-        """
-        return next(iter(self.selected_elements), None)
-
-    ##########################################################################
-    # Event Handlers
-    ##########################################################################
-    def handle_mouse_context_press(self, coordinate, event):
-        """
-        The context mouse button was pressed:
-        If no elements were selected, perform re-selection at this coordinate.
-        Then, show the context menu at the mouse click location.
-        """
-        selections = self.what_is_selected(coordinate)
-        if not selections.intersection(self.selected_elements):
-            self.coordinate = coordinate
-            self.mouse_pressed = True
-            self.update_selected_elements()
-            self.mouse_pressed = False
-        self._context_menu.popup(None, None, None, None, event.button, event.time)
-
-    def handle_mouse_selector_press(self, double_click, coordinate):
-        """
-        The selector mouse button was pressed:
-        Find the selected element. Attempt a new connection if possible.
-        Open the block params window on a double click.
-        Update the selection state of the flow graph.
-        """
-        self.press_coor = coordinate
-        self.coordinate = coordinate
-        self.mouse_pressed = True
-
-        if double_click:
-            self.unselect()
-        self.update_selected_elements()
-
-        if double_click and self.selected_block:
-            self.mouse_pressed = False
-            Actions.BLOCK_PARAM_MODIFY()
-
-    def handle_mouse_selector_release(self, coordinate):
-        """
-        The selector mouse button was released:
-        Update the state, handle motion (dragging).
-        And update the selected flowgraph elements.
-        """
-        self.coordinate = coordinate
-        self.mouse_pressed = False
-        if self.element_moved:
-            Actions.BLOCK_MOVE()
-            self.element_moved = False
-        self.update_selected_elements()
-
-    def handle_mouse_motion(self, coordinate):
-        """
-        The mouse has moved, respond to mouse dragging or notify elements
-        Move a selected element to the new coordinate.
-        Auto-scroll the scroll bars at the boundaries.
-        """
-        # to perform a movement, the mouse must be pressed
-        # (no longer checking pending events via Gtk.events_pending() - always true in Windows)
-        if not self.mouse_pressed:
-            self._handle_mouse_motion_move(coordinate)
-        else:
-            self._handle_mouse_motion_drag(coordinate)
-
-    def _handle_mouse_motion_move(self, coordinate):
-        # only continue if mouse-over stuff is enabled (just the auto-hide port label stuff for now)
-        if not Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active():
-            return
-        redraw = False
-        for element in self._elements_to_draw:
-            over_element = element.what_is_selected(coordinate)
-            if not over_element:
-                continue
-            if over_element != self.element_under_mouse:  # over sth new
-                if self.element_under_mouse:
-                    redraw |= self.element_under_mouse.mouse_out() or False
-                self.element_under_mouse = over_element
-                redraw |= over_element.mouse_over() or False
-            break
-        else:
-            if self.element_under_mouse:
-                redraw |= self.element_under_mouse.mouse_out() or False
-                self.element_under_mouse = None
-        if redraw:
-            # self.create_labels()
-            self.create_shapes()
-            self.drawing_area.queue_draw()
-
-    def _handle_mouse_motion_drag(self, coordinate):
-        # remove the connection if selected in drag event
-        if len(self.selected_elements) == 1 and self.get_selected_element().is_connection:
-            Actions.ELEMENT_DELETE()
-
-        # move the selected elements and record the new coordinate
-        x, y = coordinate
-        if not self.drawing_area.ctrl_mask:
-            X, Y = self.coordinate
-            dX, dY = int(x - X), int(y - Y)
-            active = Actions.TOGGLE_SNAP_TO_GRID.get_active() or self.drawing_area.mod1_mask
-            if not active or abs(dX) >= Constants.CANVAS_GRID_SIZE or abs(dY) >= Constants.CANVAS_GRID_SIZE:
-                self.move_selected((dX, dY))
-                self.coordinate = (x, y)
-        # queue draw for animation
-        self.drawing_area.queue_draw()
-
-    def get_max_coords(self, initial=(0, 0)):
-        return tuple(max(i, e) for i, e in zip(initial, self.extend[2:]))
-
-    @property
-    def extend(self):
-        extend = 100000, 100000, 0, 0
-        for element in self._elements_to_draw:
-            extend = (min_or_max(xy, e_xy) for min_or_max, xy, e_xy in zip(
-                (min, min, max, max), extend, element.extend
-            ))
-        return tuple(extend)
diff --git a/grc/gui/Param.py b/grc/gui/Param.py
deleted file mode 100644
index ed5257ae69..0000000000
--- a/grc/gui/Param.py
+++ /dev/null
@@ -1,162 +0,0 @@
-# Copyright 2007-2016 Free Software Foundation, Inc.
-# This file is part of GNU Radio
-#
-# GNU Radio Companion is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# GNU Radio Companion is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
-
-from __future__ import absolute_import
-from . import Utils, Constants
-
-from . import ParamWidgets
-from .Element import Element
-
-from ..core.Param import Param as _Param
-
-
-class Param(_Param):
-    """The graphical parameter."""
-
-    make_cls_with_base = classmethod(Element.make_cls_with_base.__func__)
-
-    def get_input(self, *args, **kwargs):
-        """
-        Get the graphical gtk class to represent this parameter.
-        An enum requires and combo parameter.
-        A non-enum with options gets a combined entry/combo parameter.
-        All others get a standard entry parameter.
-
-        Returns:
-            gtk input class
-        """
-        type_ = self.get_type()
-        if type_ in ('file_open', 'file_save'):
-            input_widget_cls = ParamWidgets.FileParam
-
-        elif self.is_enum():
-            input_widget_cls = ParamWidgets.EnumParam
-
-        elif self.options:
-            input_widget_cls = ParamWidgets.EnumEntryParam
-
-        elif type_ == '_multiline':
-            input_widget_cls = ParamWidgets.MultiLineEntryParam
-
-        elif type_ == '_multiline_python_external':
-            input_widget_cls = ParamWidgets.PythonEditorParam
-
-        else:
-            input_widget_cls = ParamWidgets.EntryParam
-
-        return input_widget_cls(self, *args, **kwargs)
-
-    def format_label_markup(self, have_pending_changes=False):
-        block = self.parent
-        # fixme: using non-public attribute here
-        has_callback = \
-            hasattr(block, 'get_callbacks') and \
-            any(self.key in callback for callback in block._callbacks)
-
-        return '<span {underline} {foreground} font_desc="Sans 9">{label}</span>'.format(
-            underline='underline="low"' if has_callback else '',
-            foreground='foreground="blue"' if have_pending_changes else
-                       'foreground="red"' if not self.is_valid() else '',
-            label=Utils.encode(self.name)
-        )
-
-    def format_tooltip_text(self):
-        errors = self.get_error_messages()
-        tooltip_lines = ['Key: ' + self.key, 'Type: ' + self.get_type()]
-        if self.is_valid():
-            value = str(self.get_evaluated())
-            if len(value) > 100:
-                value = '{}...{}'.format(value[:50], value[-50:])
-            tooltip_lines.append('Value: ' + value)
-        elif len(errors) == 1:
-            tooltip_lines.append('Error: ' + errors[0])
-        elif len(errors) > 1:
-            tooltip_lines.append('Error:')
-            tooltip_lines.extend(' * ' + msg for msg in errors)
-        return '\n'.join(tooltip_lines)
-
-    def pretty_print(self):
-        """
-        Get the repr (nice string format) for this param.
-
-        Returns:
-            the string representation
-        """
-        ##################################################
-        # Truncate helper method
-        ##################################################
-        def _truncate(string, style=0):
-            max_len = max(27 - len(self.name), 3)
-            if len(string) > max_len:
-                if style < 0:  # Front truncate
-                    string = '...' + string[3-max_len:]
-                elif style == 0:  # Center truncate
-                    string = string[:max_len/2 - 3] + '...' + string[-max_len/2:]
-                elif style > 0:  # Rear truncate
-                    string = string[:max_len-3] + '...'
-            return string
-
-        ##################################################
-        # Simple conditions
-        ##################################################
-        value = self.get_value()
-        if not self.is_valid():
-            return _truncate(value)
-        if value in self.options:
-            return self.options_names[self.options.index(value)]
-
-        ##################################################
-        # Split up formatting by type
-        ##################################################
-        # Default center truncate
-        truncate = 0
-        e = self.get_evaluated()
-        t = self.get_type()
-        if isinstance(e, bool):
-            return str(e)
-        elif isinstance(e, Constants.COMPLEX_TYPES):
-            dt_str = Utils.num_to_str(e)
-        elif isinstance(e, Constants.VECTOR_TYPES):
-            # Vector types
-            if len(e) > 8:
-                # Large vectors use code
-                dt_str = self.get_value()
-                truncate = 1
-            else:
-                # Small vectors use eval
-                dt_str = ', '.join(map(Utils.num_to_str, e))
-        elif t in ('file_open', 'file_save'):
-            dt_str = self.get_value()
-            truncate = -1
-        else:
-            # Other types
-            dt_str = str(e)
-
-        # Done
-        return _truncate(dt_str, truncate)
-
-    def format_block_surface_markup(self):
-        """
-        Get the markup for this param.
-
-        Returns:
-            a pango markup string
-        """
-        return '<span {foreground} font_desc="{font}"><b>{label}:</b> {value}</span>'.format(
-            foreground='foreground="red"' if not self.is_valid() else '', font=Constants.PARAM_FONT,
-            label=Utils.encode(self.name), value=Utils.encode(self.pretty_print().replace('\n', ' '))
-        )
diff --git a/grc/gui/Platform.py b/grc/gui/Platform.py
index 6a2a13f644..44380c579f 100644
--- a/grc/gui/Platform.py
+++ b/grc/gui/Platform.py
@@ -19,17 +19,13 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
 from __future__ import absolute_import, print_function
 
-import os
 import sys
 
-from ..core.Platform import Platform as CorePlatform
+import os
 
 from .Config import Config
-from .Block import Block
-from .Connection import Connection
-from .FlowGraph import FlowGraph
-from .Param import Param
-from .Port import Port
+from . import canvas
+from ..core.Platform import Platform as CorePlatform
 
 
 class Platform(CorePlatform):
@@ -64,11 +60,11 @@ class Platform(CorePlatform):
     # Factories
     ##############################################
     Config = Config
-    FlowGraph = FlowGraph
-    Connection = Connection
-    block_classes = {key: Block.make_cls_with_base(cls)
+    FlowGraph = canvas.FlowGraph
+    Connection = canvas.Connection
+    block_classes = {key: canvas.Block.make_cls_with_base(cls)
                      for key, cls in CorePlatform.block_classes.items()}
-    port_classes = {key: Port.make_cls_with_base(cls)
+    port_classes = {key: canvas.Port.make_cls_with_base(cls)
                     for key, cls in CorePlatform.port_classes.items()}
-    param_classes = {key: Param.make_cls_with_base(cls)
+    param_classes = {key: canvas.Param.make_cls_with_base(cls)
                      for key, cls in CorePlatform.param_classes.items()}
diff --git a/grc/gui/Port.py b/grc/gui/Port.py
deleted file mode 100644
index 8ac32dc25f..0000000000
--- a/grc/gui/Port.py
+++ /dev/null
@@ -1,236 +0,0 @@
-"""
-Copyright 2007, 2008, 2009 Free Software Foundation, Inc.
-This file is part of GNU Radio
-
-GNU Radio Companion is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-GNU Radio Companion is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
-"""
-
-from __future__ import absolute_import
-import math
-
-from gi.repository import Gtk, PangoCairo, Pango
-
-from . import Actions, Colors, Utils, Constants
-from .Element import Element
-
-from ..core.Element import nop_write
-from ..core.Port import Port as _Port
-
-
-class Port(_Port, Element):
-    """The graphical port."""
-
-    def __init__(self, parent, direction, **n):
-        """
-        Port constructor.
-        Create list of connector coordinates.
-        """
-        super(self.__class__, self).__init__(parent, direction, **n)
-        Element.__init__(self)
-        self._connector_coordinate = (0, 0)
-        self._hovering = False
-        self.force_show_label = False
-
-        self._area = []
-        self._bg_color = self._border_color = 0, 0, 0, 0
-        self._font_color = list(Colors.FONT_COLOR)
-
-        self._line_width_factor = 1.0
-        self._label_layout_offsets = 0, 0
-
-        self.width_with_label = self.height = 0
-        self.connector_length = 0
-
-        self.label_layout = Gtk.DrawingArea().create_pango_layout('')
-        self.label_layout.set_alignment(Pango.Alignment.CENTER)
-
-    @property
-    def width(self):
-        return self.width_with_label if self._show_label else Constants.PORT_LABEL_HIDDEN_WIDTH
-
-    @width.setter
-    def width(self, value):
-        self.width_with_label = value
-        self.label_layout.set_width(value * Pango.SCALE)
-
-    def _update_colors(self):
-        """
-        Get the color that represents this port's type.
-        Codes differ for ports where the vec length is 1 or greater than 1.
-
-        Returns:
-            a hex color code.
-        """
-        if not self.parent_block.enabled:
-            self._font_color[-1] = 0.4
-            color = Colors.BLOCK_DISABLED_COLOR
-        else:
-            self._font_color[-1] = 1.0
-            color = Colors.PORT_TYPE_TO_COLOR.get(self.get_type()) or Colors.PORT_TYPE_TO_COLOR.get('')
-            vlen = self.get_vlen()
-            if vlen > 1:
-                dark = (0, 0, 30 / 255.0, 50 / 255.0, 70 / 255.0)[min(4, vlen)]
-                color = tuple(max(c - dark, 0) for c in color)
-        self._bg_color = color
-        self._border_color = tuple(max(c - 0.3, 0) for c in color)
-
-    def create_shapes(self):
-        """Create new areas and labels for the port."""
-        if self.is_horizontal():
-            self._area = (0, 0, self.width, self.height)
-        elif self.is_vertical():
-            self._area = (0, 0, self.height, self.width)
-        self.bounds_from_area(self._area)
-
-        self._connector_coordinate = {
-            0:   (self.width, self.height / 2),
-            90:  (self.height / 2, 0),
-            180: (0, self.height / 2),
-            270: (self.height / 2, self.width)
-        }[self.get_connector_direction()]
-
-    def create_labels(self):
-        """Create the labels for the socket."""
-
-        if self.domain in (Constants.GR_MESSAGE_DOMAIN, Constants.DEFAULT_DOMAIN):
-            self._line_width_factor = 1.0
-        else:
-            self._line_width_factor = 2.0
-
-        self._update_colors()
-
-        layout = self.label_layout
-        layout.set_markup('<span font_desc="{font}">{name}</span>'.format(
-            name=Utils.encode(self.name), font=Constants.PORT_FONT
-        ))
-        label_width, label_height = self.label_layout.get_pixel_size()
-
-        self.width = 2 * Constants.PORT_LABEL_PADDING + label_width
-        self.height = 2 * Constants.PORT_LABEL_PADDING + label_height
-        self._label_layout_offsets = [0, Constants.PORT_LABEL_PADDING]
-        if self.get_type() == 'bus':
-            self.height += Constants.PORT_EXTRA_BUS_HEIGHT
-            self._label_layout_offsets[1] += Constants.PORT_EXTRA_BUS_HEIGHT / 2
-        self.height += self.height % 2  # uneven height
-
-    def draw(self, cr):
-        """
-        Draw the socket with a label.
-        """
-        border_color = self._border_color
-        cr.set_line_width(self._line_width_factor * cr.get_line_width())
-        cr.translate(*self.coordinate)
-
-        cr.rectangle(*self._area)
-        cr.set_source_rgba(*self._bg_color)
-        cr.fill_preserve()
-        cr.set_source_rgba(*border_color)
-        cr.stroke()
-
-        if not self._show_label:
-            return  # this port is folded (no label)
-
-        if self.is_vertical():
-            cr.rotate(-math.pi / 2)
-            cr.translate(-self.width, 0)
-        cr.translate(*self._label_layout_offsets)
-
-        cr.set_source_rgba(*self._font_color)
-        PangoCairo.update_layout(cr, self.label_layout)
-        PangoCairo.show_layout(cr, self.label_layout)
-
-    def get_connector_coordinate(self):
-        """
-        Get the coordinate where connections may attach to.
-
-        Returns:
-            the connector coordinate (x, y) tuple
-        """
-        return [sum(c) for c in zip(
-            self._connector_coordinate,   # relative to port
-            self.coordinate,              # relative to block
-            self.parent_block.coordinate  # abs
-        )]
-
-    def get_connector_direction(self):
-        """
-        Get the direction that the socket points: 0,90,180,270.
-        This is the rotation degree if the socket is an output or
-        the rotation degree + 180 if the socket is an input.
-
-        Returns:
-            the direction in degrees
-        """
-        if self.is_source:
-            return self.rotation
-        elif self.is_sink:
-            return (self.rotation + 180) % 360
-
-    @nop_write
-    @property
-    def rotation(self):
-        return self.parent_block.rotation
-
-    def rotate(self, direction):
-        """
-        Rotate the parent rather than self.
-
-        Args:
-            direction: degrees to rotate
-        """
-        self.parent_block.rotate(direction)
-
-    def move(self, delta_coor):
-        """
-        Move the parent rather than self.
-
-        Args:
-            delta_corr: the (delta_x, delta_y) tuple
-        """
-        self.parent_block.move(delta_coor)
-
-    @property
-    def highlighted(self):
-        return self.parent_block.highlighted
-
-    @highlighted.setter
-    def highlighted(self, value):
-        self.parent_block.highlighted = value
-
-    @property
-    def _show_label(self):
-        """
-        Figure out if the label should be hidden
-
-        Returns:
-            true if the label should not be shown
-        """
-        return self._hovering or self.force_show_label or not Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active()
-
-    def mouse_over(self):
-        """
-        Called from flow graph on mouse-over
-        """
-        changed = not self._show_label
-        self._hovering = True
-        return changed
-
-    def mouse_out(self):
-        """
-        Called from flow graph on mouse-out
-        """
-        label_was_shown = self._show_label
-        self._hovering = False
-        return label_was_shown != self._show_label
diff --git a/grc/gui/canvas/__init__.py b/grc/gui/canvas/__init__.py
new file mode 100644
index 0000000000..f90d10c4e6
--- /dev/null
+++ b/grc/gui/canvas/__init__.py
@@ -0,0 +1,22 @@
+# Copyright 2016 Free Software Foundation, Inc.
+# This file is part of GNU Radio
+#
+# GNU Radio Companion is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# GNU Radio Companion is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+
+from .block import Block
+from .connection import Connection
+from .flowgraph import FlowGraph
+from .param import Param
+from .port import Port
diff --git a/grc/gui/canvas/block.py b/grc/gui/canvas/block.py
new file mode 100644
index 0000000000..7e28a21fc2
--- /dev/null
+++ b/grc/gui/canvas/block.py
@@ -0,0 +1,390 @@
+"""
+Copyright 2007, 2008, 2009 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+"""
+
+from __future__ import absolute_import
+
+import math
+
+import six
+from gi.repository import Gtk, Pango, PangoCairo
+
+from .drawable import Drawable
+
+from .. import Actions, Colors, Utils, Constants
+from ..Constants import (
+    BLOCK_LABEL_PADDING, PORT_SPACING, PORT_SEPARATION, LABEL_SEPARATION,
+    PORT_BORDER_SEPARATION, BLOCK_FONT, PARAM_FONT
+)
+
+from ...core import utils
+from ...core.Block import Block as CoreBlock
+
+
+class Block(CoreBlock, Drawable):
+    """The graphical signal block."""
+
+    def __init__(self, parent, **n):
+        """
+        Block constructor.
+        Add graphics related params to the block.
+        """
+        super(self.__class__, self).__init__(parent, **n)
+
+        self.states.update(_coordinate=(0, 0), _rotation=0)
+        self.width = self.height = 0
+        Drawable.__init__(self)  # needs the states and initial sizes
+
+        self._surface_layouts = [
+            Gtk.DrawingArea().create_pango_layout(''),  # title
+            Gtk.DrawingArea().create_pango_layout(''),  # params
+        ]
+        self._surface_layout_offsets = 0, 0
+        self._comment_layout = None
+
+        self._area = []
+        self._border_color = self._bg_color = Colors.BLOCK_ENABLED_COLOR
+        self._font_color = list(Colors.FONT_COLOR)
+
+    @property
+    def coordinate(self):
+        """
+        Get the coordinate from the position param.
+
+        Returns:
+            the coordinate tuple (x, y) or (0, 0) if failure
+        """
+        return self.states['_coordinate']
+
+    @coordinate.setter
+    def coordinate(self, coor):
+        """
+        Set the coordinate into the position param.
+
+        Args:
+            coor: the coordinate tuple (x, y)
+        """
+        if Actions.TOGGLE_SNAP_TO_GRID.get_active():
+            offset_x, offset_y = (0, self.height / 2) if self.is_horizontal() else (self.height / 2, 0)
+            coor = (
+                Utils.align_to_grid(coor[0] + offset_x) - offset_x,
+                Utils.align_to_grid(coor[1] + offset_y) - offset_y
+            )
+        self.states['_coordinate'] = coor
+
+    @property
+    def rotation(self):
+        """
+        Get the rotation from the position param.
+
+        Returns:
+            the rotation in degrees or 0 if failure
+        """
+        return self.states['_rotation']
+
+    @rotation.setter
+    def rotation(self, rot):
+        """
+        Set the rotation into the position param.
+
+        Args:
+            rot: the rotation in degrees
+        """
+        self.states['_rotation'] = rot
+
+    def _update_colors(self):
+        self._bg_color = (
+            Colors.MISSING_BLOCK_BACKGROUND_COLOR if self.is_dummy_block else
+            Colors.BLOCK_BYPASSED_COLOR if self.state == 'bypassed' else
+            Colors.BLOCK_ENABLED_COLOR if self.state == 'enabled' else
+            Colors.BLOCK_DISABLED_COLOR
+        )
+        self._font_color[-1] = 1.0 if self.state == 'enabled' else 0.4
+        self._border_color = (
+            Colors.MISSING_BLOCK_BORDER_COLOR if self.is_dummy_block else
+            Colors.BORDER_COLOR_DISABLED if not self.state == 'enabled' else Colors.BORDER_COLOR
+        )
+
+    def create_shapes(self):
+        """Update the block, parameters, and ports when a change occurs."""
+        if self.is_horizontal():
+            self._area = (0, 0, self.width, self.height)
+        elif self.is_vertical():
+            self._area = (0, 0, self.height, self.width)
+        self.bounds_from_area(self._area)
+
+        bussified = self.current_bus_structure['source'], self.current_bus_structure['sink']
+        for ports, has_busses in zip((self.active_sources, self.active_sinks), bussified):
+            if not ports:
+                continue
+            port_separation = PORT_SEPARATION if not has_busses else ports[0].height + PORT_SPACING
+            offset = (self.height - (len(ports) - 1) * port_separation - ports[0].height) / 2
+            for index, port in enumerate(ports):
+                port.create_shapes()
+
+                port.coordinate = {
+                    0: (+self.width, offset),
+                    90: (offset, -port.width),
+                    180: (-port.width, offset),
+                    270: (offset, +self.width),
+                }[port.get_connector_direction()]
+                offset += PORT_SEPARATION if not has_busses else port.height + PORT_SPACING
+
+                port.connector_length = Constants.CONNECTOR_EXTENSION_MINIMAL + \
+                                        Constants.CONNECTOR_EXTENSION_INCREMENT * index
+
+    def create_labels(self):
+        """Create the labels for the signal block."""
+        title_layout, params_layout = self._surface_layouts
+
+        title_layout.set_markup(
+            '<span {foreground} font_desc="{font}"><b>{name}</b></span>'.format(
+                foreground='foreground="red"' if not self.is_valid() else '', font=BLOCK_FONT,
+                name=Utils.encode(self.name)
+            )
+        )
+        title_width, title_height = title_layout.get_pixel_size()
+
+        # update the params layout
+        if not self.is_dummy_block:
+            markups = [param.format_block_surface_markup()
+                       for param in self.params.values() if param.get_hide() not in ('all', 'part')]
+        else:
+            markups = ['<span font_desc="{font}"><b>key: </b>{key}</span>'.format(font=PARAM_FONT, key=self.key)]
+
+        params_layout.set_spacing(LABEL_SEPARATION * Pango.SCALE)
+        params_layout.set_markup('\n'.join(markups))
+        params_width, params_height = params_layout.get_pixel_size() if markups else (0, 0)
+
+        label_width = max(title_width, params_width)
+        label_height = title_height + LABEL_SEPARATION + params_height
+
+        title_layout.set_width(label_width * Pango.SCALE)
+        title_layout.set_alignment(Pango.Alignment.CENTER)
+
+        # calculate width and height needed
+        width = label_width + 2 * BLOCK_LABEL_PADDING
+        height = label_height + 2 * BLOCK_LABEL_PADDING
+
+        self._update_colors()
+        self.create_port_labels()
+
+        def get_min_height_for_ports(ports):
+            min_height = 2 * PORT_BORDER_SEPARATION + len(ports) * PORT_SEPARATION
+            if ports:
+                min_height -= ports[-1].height
+            return min_height
+
+        height = max(height,
+                     get_min_height_for_ports(self.active_sinks),
+                     get_min_height_for_ports(self.active_sources))
+
+        def get_min_height_for_bus_ports(ports):
+            return 2 * PORT_BORDER_SEPARATION + sum(
+                port.height + PORT_SPACING for port in ports if port.get_type() == 'bus'
+            ) - PORT_SPACING
+
+        if self.current_bus_structure['sink']:
+            height = max(height, get_min_height_for_bus_ports(self.active_sinks))
+        if self.current_bus_structure['source']:
+            height = max(height, get_min_height_for_bus_ports(self.active_sources))
+
+        self.width, self.height = width, height = Utils.align_to_grid((width, height))
+
+        self._surface_layout_offsets = [
+            (width - label_width) / 2.0,
+            (height - label_height) / 2.0
+        ]
+
+        self.create_comment_layout()
+
+    def create_port_labels(self):
+        for ports in (self.active_sinks, self.active_sources):
+            max_width = 0
+            for port in ports:
+                port.create_labels()
+                max_width = max(max_width, port.width_with_label)
+            for port in ports:
+                port.width = max_width
+
+    def create_comment_layout(self):
+        markups = []
+
+        # Show the flow graph complexity on the top block if enabled
+        if Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY.get_active() and self.key == "options":
+            complexity = utils.calculate_flowgraph_complexity(self.parent)
+            markups.append(
+                '<span foreground="#444" size="medium" font_desc="{font}">'
+                '<b>Complexity: {num}bal</b></span>'.format(num=Utils.num_to_str(complexity), font=BLOCK_FONT)
+            )
+        comment = self.comment  # Returns None if there are no comments
+        if comment:
+            if markups:
+                markups.append('<span></span>')
+
+            markups.append('<span foreground="{foreground}" font_desc="{font}">{comment}</span>'.format(
+                foreground='#444' if self.enabled else '#888', font=BLOCK_FONT, comment=Utils.encode(comment)
+            ))
+        if markups:
+            layout = self._comment_layout = Gtk.DrawingArea().create_pango_layout('')
+            layout.set_markup(''.join(markups))
+        else:
+            self._comment_layout = None
+
+    def draw(self, cr):
+        """
+        Draw the signal block with label and inputs/outputs.
+        """
+        border_color = Colors.HIGHLIGHT_COLOR if self.highlighted else self._border_color
+        cr.translate(*self.coordinate)
+
+        for port in self.active_ports():  # ports first
+            cr.save()
+            port.draw(cr)
+            cr.restore()
+
+        cr.rectangle(*self._area)
+        cr.set_source_rgba(*self._bg_color)
+        cr.fill_preserve()
+        cr.set_source_rgba(*border_color)
+        cr.stroke()
+
+        # title and params label
+        if self.is_vertical():
+            cr.rotate(-math.pi / 2)
+            cr.translate(-self.width, 0)
+        cr.translate(*self._surface_layout_offsets)
+
+        cr.set_source_rgba(*self._font_color)
+        for layout in self._surface_layouts:
+            PangoCairo.update_layout(cr, layout)
+            PangoCairo.show_layout(cr, layout)
+            _, h = layout.get_pixel_size()
+            cr.translate(0, h + LABEL_SEPARATION)
+
+    def what_is_selected(self, coor, coor_m=None):
+        """
+        Get the element that is selected.
+
+        Args:
+            coor: the (x,y) tuple
+            coor_m: the (x_m, y_m) tuple
+
+        Returns:
+            this block, a port, or None
+        """
+        for port in self.active_ports():
+            port_selected = port.what_is_selected(
+                coor=[a - b for a, b in zip(coor, self.coordinate)],
+                coor_m=[a - b for a, b in zip(coor, self.coordinate)] if coor_m is not None else None
+            )
+            if port_selected:
+                return port_selected
+        return Drawable.what_is_selected(self, coor, coor_m)
+
+    def draw_comment(self, cr):
+        if not self._comment_layout:
+            return
+        x, y = self.coordinate
+
+        if self.is_horizontal():
+            y += self.height + BLOCK_LABEL_PADDING
+        else:
+            x += self.height + BLOCK_LABEL_PADDING
+
+        cr.save()
+        cr.translate(x, y)
+        PangoCairo.update_layout(cr, self._comment_layout)
+        PangoCairo.show_layout(cr, self._comment_layout)
+        cr.restore()
+
+    @property
+    def extend(self):
+        extend = Drawable.extend.fget(self)
+        x, y = self.coordinate
+        for port in self.active_ports():
+            extend = (min_or_max(xy, offset + p_xy) for offset, min_or_max, xy, p_xy in zip(
+                (x, y, x, y), (min, min, max, max), extend, port.extend
+            ))
+        return tuple(extend)
+
+    ##############################################
+    # Controller Modify
+    ##############################################
+    def type_controller_modify(self, direction):
+        """
+        Change the type controller.
+
+        Args:
+            direction: +1 or -1
+
+        Returns:
+            true for change
+        """
+        type_templates = ' '.join(p._type for p in self.get_children())
+        type_param = None
+        for key, param in six.iteritems(self.params):
+            if not param.is_enum():
+                continue
+            # Priority to the type controller
+            if param.key in type_templates:
+                type_param = param
+                break
+            # Use param if type param is unset
+            if not type_param:
+                type_param = param
+        if not type_param:
+            return False
+
+        # Try to increment the enum by direction
+        try:
+            keys = list(type_param.options)
+            old_index = keys.index(type_param.get_value())
+            new_index = (old_index + direction + len(keys)) % len(keys)
+            type_param.set_value(keys[new_index])
+            return True
+        except:
+            return False
+
+    def port_controller_modify(self, direction):
+        """
+        Change the port controller.
+
+        Args:
+            direction: +1 or -1
+
+        Returns:
+            true for change
+        """
+        changed = False
+        # Concat the nports string from the private nports settings of all ports
+        nports_str = ' '.join(port._nports for port in self.get_ports())
+        # Modify all params whose keys appear in the nports string
+        for key, param in six.iteritems(self.params):
+            if param.is_enum() or param.key not in nports_str:
+                continue
+            # Try to increment the port controller by direction
+            try:
+                value = param.get_evaluated() + direction
+                if value > 0:
+                    param.set_value(value)
+                    changed = True
+            except:
+                pass
+        return changed
+
diff --git a/grc/gui/canvas/connection.py b/grc/gui/canvas/connection.py
new file mode 100644
index 0000000000..14bd0c9280
--- /dev/null
+++ b/grc/gui/canvas/connection.py
@@ -0,0 +1,208 @@
+"""
+Copyright 2007, 2008, 2009 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+"""
+
+from __future__ import absolute_import
+
+from .drawable import Drawable
+
+from .. import Colors, Utils
+from ..Constants import CONNECTOR_ARROW_BASE, CONNECTOR_ARROW_HEIGHT, GR_MESSAGE_DOMAIN
+
+from ...core.Connection import Connection as CoreConnection
+from ...core.Element import nop_write
+
+
+class Connection(CoreConnection, Drawable):
+    """
+    A graphical connection for ports.
+    The connection has 2 parts, the arrow and the wire.
+    The coloring of the arrow and wire exposes the status of 3 states:
+        enabled/disabled, valid/invalid, highlighted/non-highlighted.
+    The wire coloring exposes the enabled and highlighted states.
+    The arrow coloring exposes the enabled and valid states.
+    """
+
+    def __init__(self, *args, **kwargs):
+        super(self.__class__, self).__init__(*args, **kwargs)
+        Drawable.__init__(self)
+
+        self._line = []
+        self._line_width_factor = 1.0
+        self._color = self._color2 = self._arrow_color = None
+
+        self._sink_rot = self._source_rot = None
+        self._sink_coor = self._source_coor = None
+
+    @nop_write
+    @property
+    def coordinate(self):
+        return self.source_port.get_connector_coordinate()
+
+    @nop_write
+    @property
+    def rotation(self):
+        """
+        Get the 0 degree rotation.
+        Rotations are irrelevant in connection.
+
+        Returns:
+            0
+        """
+        return 0
+
+    def create_shapes(self):
+        """Pre-calculate relative coordinates."""
+        self._sink_rot = None
+        self._source_rot = None
+        self._sink_coor = None
+        self._source_coor = None
+        #get the source coordinate
+        try:
+            connector_length = self.source_port.connector_length
+        except:
+            return  # todo: why?
+        self.x1, self.y1 = Utils.get_rotated_coordinate((connector_length, 0), self.source_port.rotation)
+        #get the sink coordinate
+        connector_length = self.sink_port.connector_length + CONNECTOR_ARROW_HEIGHT
+        self.x2, self.y2 = Utils.get_rotated_coordinate((-connector_length, 0), self.sink_port.rotation)
+        #build the arrow
+        self._arrow_base = [
+            (0, 0),
+            Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, -CONNECTOR_ARROW_BASE/2), self.sink_port.rotation),
+            Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, CONNECTOR_ARROW_BASE/2), self.sink_port.rotation),
+        ] if self.sink_block.state != 'bypassed' else []
+        source_domain = self.source_port.domain
+        sink_domain = self.sink_port.domain
+
+        def get_domain_color(domain_name):
+            domain = self.parent_platform.domains.get(domain_name, {})
+            color_spec = domain.get('color')
+            return Colors.get_color(color_spec) if color_spec else Colors.DEFAULT_DOMAIN_COLOR
+
+        if source_domain == GR_MESSAGE_DOMAIN:
+            self._line_width_factor = 1.0
+            self._color = None
+            self._color2 = Colors.CONNECTION_ENABLED_COLOR
+        else:
+            if source_domain != sink_domain:
+                self._line_width_factor = 2.0
+            self._color = get_domain_color(source_domain)
+            self._color2 = get_domain_color(sink_domain)
+        self._arrow_color = self._color2 if self.is_valid() else Colors.CONNECTION_ERROR_COLOR
+        self._update_after_move()
+
+    def _update_after_move(self):
+        """Calculate coordinates."""
+        source = self.source_port
+        sink = self.sink_port
+        source_dir = source.get_connector_direction()
+        sink_dir = sink.get_connector_direction()
+
+        x_pos, y_pos = self.coordinate
+        x_start, y_start = source.get_connector_coordinate()
+        x_end, y_end = sink.get_connector_coordinate()
+
+        p3 = x3, y3 = x_end - x_pos, y_end - y_pos
+        p2 = x2, y2 = self.x2 + x3, self.y2 + y3
+        p1 = x1, y1 = self.x1, self.y1
+        p0 = x_start - x_pos, y_start - y_pos
+        self._arrow = [(x + x3, y + y3) for x, y in self._arrow_base]
+
+        if abs(source_dir - sink.get_connector_direction()) == 180:
+            # 2 possible point sets to create a 3-line connector
+            mid_x, mid_y = (x1 + x2) / 2.0, (y1 + y2) / 2.0
+            points = ((mid_x, y1), (mid_x, y2))
+            alt = ((x1, mid_y), (x2, mid_y))
+            # source connector -> points[0][0] should be in the direction of source (if possible)
+            if Utils.get_angle_from_coordinates(p1, points[0]) != source_dir:
+                points, alt = alt, points
+            # points[0] -> sink connector should not be in the direction of sink
+            if Utils.get_angle_from_coordinates(points[0], p2) == sink_dir:
+                points, alt = alt, points
+            # points[0] -> source connector should not be in the direction of source
+            if Utils.get_angle_from_coordinates(points[0], p1) == source_dir:
+                points, alt = alt, points
+            # create 3-line connector
+            i1, i2 = points
+            self._line = [p0, p1, i1, i2, p2, p3]
+        else:
+            # 2 possible points to create a right-angled connector
+            point, alt = [(x1, y2), (x2, y1)]
+            # source connector -> point should be in the direction of source (if possible)
+            if Utils.get_angle_from_coordinates(p1, point) != source_dir:
+                point, alt = alt, point
+            # point -> sink connector should not be in the direction of sink
+            if Utils.get_angle_from_coordinates(point, p2) == sink_dir:
+                point, alt = alt, point
+            # point -> source connector should not be in the direction of source
+            if Utils.get_angle_from_coordinates(point, p1) == source_dir:
+                point, alt = alt, point
+            # create right-angled connector
+            self._line = [p0, p1, point, p2, p3]
+        self.bounds_from_line(self._line)
+
+    def draw(self, cr):
+        """
+        Draw the connection.
+        """
+        sink = self.sink_port
+        source = self.source_port
+        # check for changes
+        if self._sink_rot != sink.rotation or self._source_rot != source.rotation:
+            self.create_shapes()
+            self._sink_rot = sink.rotation
+            self._source_rot = source.rotation
+
+        elif self._sink_coor != sink.parent_block.coordinate or self._source_coor != source.parent_block.coordinate:
+            self._update_after_move()
+            self._sink_coor = sink.parent_block.coordinate
+            self._source_coor = source.parent_block.coordinate
+        # draw
+        color1, color2, arrow_color = (
+            None if color is None else
+            Colors.HIGHLIGHT_COLOR if self.highlighted else
+            Colors.CONNECTION_DISABLED_COLOR if not self.enabled else color
+            for color in (self._color, self._color2, self._arrow_color)
+        )
+
+        cr.translate(*self.coordinate)
+        cr.set_line_width(self._line_width_factor * cr.get_line_width())
+        for point in self._line:
+            cr.line_to(*point)
+
+        if color1:  # not a message connection
+            cr.set_source_rgba(*color1)
+            cr.stroke_preserve()
+
+        if color1 != color2:
+            cr.save()
+            cr.set_dash([5.0, 5.0], 5.0 if color1 else 0.0)
+            cr.set_source_rgba(*color2)
+            cr.stroke()
+            cr.restore()
+        else:
+            cr.new_path()
+
+        if self._arrow:
+            cr.set_source_rgba(*arrow_color)
+            cr.move_to(*self._arrow[0])
+            cr.line_to(*self._arrow[1])
+            cr.line_to(*self._arrow[2])
+            cr.close_path()
+            cr.fill()
diff --git a/grc/gui/canvas/drawable.py b/grc/gui/canvas/drawable.py
new file mode 100644
index 0000000000..d1a6f42667
--- /dev/null
+++ b/grc/gui/canvas/drawable.py
@@ -0,0 +1,184 @@
+"""
+Copyright 2007, 2008, 2009, 2016 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+"""
+
+from __future__ import absolute_import
+from ..Constants import LINE_SELECT_SENSITIVITY
+
+from six.moves import zip
+
+
+class Drawable(object):
+    """
+    GraphicalElement is the base class for all graphical elements.
+    It contains an X,Y coordinate, a list of rectangular areas that the element occupies,
+    and methods to detect selection of those areas.
+    """
+
+    @classmethod
+    def make_cls_with_base(cls, super_cls):
+        name = super_cls.__name__
+        bases = (super_cls,) + cls.__bases__[1:]
+        namespace = cls.__dict__.copy()
+        return type(name, bases, namespace)
+
+    def __init__(self):
+        """
+        Make a new list of rectangular areas and lines, and set the coordinate and the rotation.
+        """
+        self.coordinate = (0, 0)
+        self.rotation = 0
+        self.highlighted = False
+
+        self._bounding_rects = []
+        self._bounding_points = []
+
+    def is_horizontal(self, rotation=None):
+        """
+        Is this element horizontal?
+        If rotation is None, use this element's rotation.
+
+        Args:
+            rotation: the optional rotation
+
+        Returns:
+            true if rotation is horizontal
+        """
+        rotation = rotation or self.rotation
+        return rotation in (0, 180)
+
+    def is_vertical(self, rotation=None):
+        """
+        Is this element vertical?
+        If rotation is None, use this element's rotation.
+
+        Args:
+            rotation: the optional rotation
+
+        Returns:
+            true if rotation is vertical
+        """
+        rotation = rotation or self.rotation
+        return rotation in (90, 270)
+
+    def rotate(self, rotation):
+        """
+        Rotate all of the areas by 90 degrees.
+
+        Args:
+            rotation: multiple of 90 degrees
+        """
+        self.rotation = (self.rotation + rotation) % 360
+
+    def move(self, delta_coor):
+        """
+        Move the element by adding the delta_coor to the current coordinate.
+
+        Args:
+            delta_coor: (delta_x,delta_y) tuple
+        """
+        x, y = self.coordinate
+        dx, dy = delta_coor
+        self.coordinate = (x + dx, y + dy)
+
+    def create_labels(self):
+        """
+        Create labels (if applicable) and call on all children.
+        Call this base method before creating labels in the element.
+        """
+
+    def create_shapes(self):
+        """
+        Create shapes (if applicable) and call on all children.
+        Call this base method before creating shapes in the element.
+        """
+
+    def draw(self, cr):
+        raise NotImplementedError()
+
+    def bounds_from_area(self, area):
+        x1, y1, w, h = area
+        x2 = x1 + w
+        y2 = y1 + h
+        self._bounding_rects = [(x1, y1, x2, y2)]
+        self._bounding_points = [(x1, y1), (x2, y1), (x1, y2), (x2, y2)]
+
+    def bounds_from_line(self, line):
+        self._bounding_rects = rects = []
+        self._bounding_points = list(line)
+        last_point = line[0]
+        for x2, y2 in line[1:]:
+            (x1, y1), last_point = last_point, (x2, y2)
+            if x1 == x2:
+                x1, x2 = x1 - LINE_SELECT_SENSITIVITY, x2 + LINE_SELECT_SENSITIVITY
+                if y2 < y1:
+                    y1, y2 = y2, y1
+            elif y1 == y2:
+                y1, y2 = y1 - LINE_SELECT_SENSITIVITY, y2 + LINE_SELECT_SENSITIVITY
+                if x2 < x1:
+                    x1, x2 = x2, x1
+
+            rects.append((x1, y1, x2, y2))
+
+    def what_is_selected(self, coor, coor_m=None):
+        """
+        One coordinate specified:
+            Is this element selected at given coordinate?
+            ie: is the coordinate encompassed by one of the areas or lines?
+        Both coordinates specified:
+            Is this element within the rectangular region defined by both coordinates?
+            ie: do any area corners or line endpoints fall within the region?
+
+        Args:
+            coor: the selection coordinate, tuple x, y
+            coor_m: an additional selection coordinate.
+
+        Returns:
+            self if one of the areas/lines encompasses coor, else None.
+        """
+        x, y = [a - b for a, b in zip(coor, self.coordinate)]
+
+        if not coor_m:
+            for x1, y1, x2, y2 in self._bounding_rects:
+                if x1 <= x <= x2 and y1 <= y <= y2:
+                    return self
+        else:
+            x_m, y_m = [a - b for a, b in zip(coor_m, self.coordinate)]
+            if y_m < y:
+                y, y_m = y_m, y
+            if x_m < x:
+                x, x_m = x_m, x
+
+            for x1, y1 in self._bounding_points:
+                if x <= x1 <= x_m and y <= y1 <= y_m:
+                    return self
+
+    @property
+    def extend(self):
+        x_min, y_min = x_max, y_max = self.coordinate
+        x_min += min(x for x, y in self._bounding_points)
+        y_min += min(y for x, y in self._bounding_points)
+        x_max += max(x for x, y in self._bounding_points)
+        y_max += max(y for x, y in self._bounding_points)
+        return x_min, y_min, x_max, y_max
+
+    def mouse_over(self):
+        pass
+
+    def mouse_out(self):
+        pass
diff --git a/grc/gui/canvas/flowgraph.py b/grc/gui/canvas/flowgraph.py
new file mode 100644
index 0000000000..5969e00a43
--- /dev/null
+++ b/grc/gui/canvas/flowgraph.py
@@ -0,0 +1,754 @@
+"""
+Copyright 2007-2011, 2016q Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+"""
+
+from __future__ import absolute_import
+
+import ast
+import functools
+import random
+from distutils.spawn import find_executable
+from itertools import count
+
+import six
+from gi.repository import GLib
+from six.moves import filter
+
+from .drawable import Drawable
+
+from .. import Actions, Colors, Constants, Utils, Bars, Dialogs
+from ..external_editor import ExternalEditor
+
+from ...core import Messages
+from ...core.FlowGraph import FlowGraph as CoreFlowgraph
+
+
+class FlowGraph(CoreFlowgraph, Drawable):
+    """
+    FlowGraph is the data structure to store graphical signal blocks,
+    graphical inputs and outputs,
+    and the connections between inputs and outputs.
+    """
+
+    def __init__(self, parent, **kwargs):
+        """
+        FlowGraph constructor.
+        Create a list for signal blocks and connections. Connect mouse handlers.
+        """
+        super(self.__class__, self).__init__(parent, **kwargs)
+        Drawable.__init__(self)
+        self.drawing_area = None
+        # important vars dealing with mouse event tracking
+        self.element_moved = False
+        self.mouse_pressed = False
+        self.press_coor = (0, 0)
+        # selected
+        self.selected_elements = set()
+        self._old_selected_port = None
+        self._new_selected_port = None
+        # current mouse hover element
+        self.element_under_mouse = None
+        # context menu
+        self._context_menu = Bars.ContextMenu()
+        self.get_context_menu = lambda: self._context_menu
+
+        self._elements_to_draw = []
+        self._external_updaters = {}
+
+    def _get_unique_id(self, base_id=''):
+        """
+        Get a unique id starting with the base id.
+
+        Args:
+            base_id: the id starts with this and appends a count
+
+        Returns:
+            a unique id
+        """
+        block_ids = set(b.get_id() for b in self.blocks)
+        for index in count():
+            block_id = '{}_{}'.format(base_id, index)
+            if block_id not in block_ids:
+                break
+        return block_id
+
+    def install_external_editor(self, param):
+        target = (param.parent_block.get_id(), param.key)
+
+        if target in self._external_updaters:
+            editor = self._external_updaters[target]
+        else:
+            config = self.parent_platform.config
+            editor = (find_executable(config.editor) or
+                      Dialogs.choose_editor(None, config))  # todo: pass in parent
+            if not editor:
+                return
+            updater = functools.partial(
+                self.handle_external_editor_change, target=target)
+            editor = self._external_updaters[target] = ExternalEditor(
+                editor=editor,
+                name=target[0], value=param.get_value(),
+                callback=functools.partial(GLib.idle_add, updater)
+            )
+            editor.start()
+        try:
+            editor.open_editor()
+        except Exception as e:
+            # Problem launching the editor. Need to select a new editor.
+            Messages.send('>>> Error opening an external editor. Please select a different editor.\n')
+            # Reset the editor to force the user to select a new one.
+            self.parent_platform.config.editor = ''
+
+    def handle_external_editor_change(self, new_value, target):
+        try:
+            block_id, param_key = target
+            self.get_block(block_id).get_param(param_key).set_value(new_value)
+
+        except (IndexError, ValueError):  # block no longer exists
+            self._external_updaters[target].stop()
+            del self._external_updaters[target]
+            return
+        Actions.EXTERNAL_UPDATE()
+
+    def add_new_block(self, key, coor=None):
+        """
+        Add a block of the given key to this flow graph.
+
+        Args:
+            key: the block key
+            coor: an optional coordinate or None for random
+        """
+        id = self._get_unique_id(key)
+        scroll_pane = self.drawing_area.get_parent().get_parent()
+        # calculate the position coordinate
+        h_adj = scroll_pane.get_hadjustment()
+        v_adj = scroll_pane.get_vadjustment()
+        if coor is None: coor = (
+            int(random.uniform(.25, .75)*h_adj.get_page_size() + h_adj.get_value()),
+            int(random.uniform(.25, .75)*v_adj.get_page_size() + v_adj.get_value()),
+        )
+        # get the new block
+        block = self.new_block(key)
+        block.coordinate = coor
+        block.get_param('id').set_value(id)
+        Actions.ELEMENT_CREATE()
+        return id
+
+    def make_connection(self):
+        """this selection and the last were ports, try to connect them"""
+        if self._old_selected_port and self._new_selected_port:
+            try:
+                self.connect(self._old_selected_port, self._new_selected_port)
+                Actions.ELEMENT_CREATE()
+            except Exception as e:
+                Messages.send_fail_connection(e)
+            self._old_selected_port = None
+            self._new_selected_port = None
+            return True
+        return False
+
+    def update(self):
+        """
+        Call the top level rewrite and validate.
+        Call the top level create labels and shapes.
+        """
+        self.rewrite()
+        self.validate()
+        self.update_elements_to_draw()
+        self.create_labels()
+        self.create_shapes()
+
+    def reload(self):
+        """
+        Reload flow-graph (with updated blocks)
+
+        Args:
+            page: the page to reload (None means current)
+        Returns:
+            False if some error occurred during import
+        """
+        success = False
+        data = self.export_data()
+        if data:
+            self.unselect()
+            success = self.import_data(data)
+            self.update()
+        return success
+
+    ###########################################################################
+    # Copy Paste
+    ###########################################################################
+    def copy_to_clipboard(self):
+        """
+        Copy the selected blocks and connections into the clipboard.
+
+        Returns:
+            the clipboard
+        """
+        #get selected blocks
+        blocks = list(self.selected_blocks())
+        if not blocks:
+            return None
+        #calc x and y min
+        x_min, y_min = blocks[0].coordinate
+        for block in blocks:
+            x, y = block.coordinate
+            x_min = min(x, x_min)
+            y_min = min(y, y_min)
+        #get connections between selected blocks
+        connections = list(filter(
+            lambda c: c.source_block in blocks and c.sink_block in blocks,
+            self.connections,
+        ))
+        clipboard = (
+            (x_min, y_min),
+            [block.export_data() for block in blocks],
+            [connection.export_data() for connection in connections],
+        )
+        return clipboard
+
+    def paste_from_clipboard(self, clipboard):
+        """
+        Paste the blocks and connections from the clipboard.
+
+        Args:
+            clipboard: the nested data of blocks, connections
+        """
+        # todo: rewrite this...
+        selected = set()
+        (x_min, y_min), blocks_n, connections_n = clipboard
+        old_id2block = dict()
+        # recalc the position
+        scroll_pane = self.drawing_area.get_parent().get_parent()
+        h_adj = scroll_pane.get_hadjustment()
+        v_adj = scroll_pane.get_vadjustment()
+        x_off = h_adj.get_value() - x_min + h_adj.get_page_size() / 4
+        y_off = v_adj.get_value() - y_min + v_adj.get_page_size() / 4
+        if len(self.get_elements()) <= 1:
+            x_off, y_off = 0, 0
+        #create blocks
+        for block_n in blocks_n:
+            block_key = block_n.get('key')
+            if block_key == 'options':
+                continue
+            block = self.new_block(block_key)
+            if not block:
+                continue  # unknown block was pasted (e.g. dummy block)
+            selected.add(block)
+            #set params
+            param_data = {n['key']: n['value'] for n in block_n.get('param', [])}
+            for key in block.states:
+                try:
+                    block.states[key] = ast.literal_eval(param_data.pop(key))
+                except (KeyError, SyntaxError, ValueError):
+                    pass
+            if block_key == 'epy_block':
+                block.get_param('_io_cache').set_value(param_data.pop('_io_cache'))
+                block.get_param('_source_code').set_value(param_data.pop('_source_code'))
+                block.rewrite()  # this creates the other params
+            for param_key, param_value in six.iteritems(param_data):
+                #setup id parameter
+                if param_key == 'id':
+                    old_id2block[param_value] = block
+                    #if the block id is not unique, get a new block id
+                    if param_value in (blk.get_id() for blk in self.blocks):
+                        param_value = self._get_unique_id(param_value)
+                #set value to key
+                block.get_param(param_key).set_value(param_value)
+            #move block to offset coordinate
+            block.move((x_off, y_off))
+        #update before creating connections
+        self.update()
+        #create connections
+        for connection_n in connections_n:
+            source = old_id2block[connection_n.get('source_block_id')].get_source(connection_n.get('source_key'))
+            sink = old_id2block[connection_n.get('sink_block_id')].get_sink(connection_n.get('sink_key'))
+            self.connect(source, sink)
+        #set all pasted elements selected
+        for block in selected:
+            selected = selected.union(set(block.get_connections()))
+        self.selected_elements = set(selected)
+
+    ###########################################################################
+    # Modify Selected
+    ###########################################################################
+    def type_controller_modify_selected(self, direction):
+        """
+        Change the registered type controller for the selected signal blocks.
+
+        Args:
+            direction: +1 or -1
+
+        Returns:
+            true for change
+        """
+        return any(sb.type_controller_modify(direction) for sb in self.selected_blocks())
+
+    def port_controller_modify_selected(self, direction):
+        """
+        Change port controller for the selected signal blocks.
+
+        Args:
+            direction: +1 or -1
+
+        Returns:
+            true for changed
+        """
+        return any(sb.port_controller_modify(direction) for sb in self.selected_blocks())
+
+    def change_state_selected(self, new_state):
+        """
+        Enable/disable the selected blocks.
+
+        Args:
+            new_state: a block state
+
+        Returns:
+            true if changed
+        """
+        changed = False
+        for block in self.selected_blocks():
+            changed |= block.state != new_state
+            block.state = new_state
+        return changed
+
+    def move_selected(self, delta_coordinate):
+        """
+        Move the element and by the change in coordinates.
+
+        Args:
+            delta_coordinate: the change in coordinates
+        """
+        for selected_block in self.selected_blocks():
+            selected_block.move(delta_coordinate)
+            self.element_moved = True
+
+    def align_selected(self, calling_action=None):
+        """
+        Align the selected blocks.
+
+        Args:
+            calling_action: the action initiating the alignment
+
+        Returns:
+            True if changed, otherwise False
+        """
+        blocks = list(self.selected_blocks())
+        if calling_action is None or not blocks:
+            return False
+
+        # compute common boundary of selected objects
+        min_x, min_y = max_x, max_y = blocks[0].coordinate
+        for selected_block in blocks:
+            x, y = selected_block.coordinate
+            min_x, min_y = min(min_x, x), min(min_y, y)
+            x += selected_block.width
+            y += selected_block.height
+            max_x, max_y = max(max_x, x), max(max_y, y)
+        ctr_x, ctr_y = (max_x + min_x)/2, (max_y + min_y)/2
+
+        # align the blocks as requested
+        transform = {
+                Actions.BLOCK_VALIGN_TOP: lambda x, y, w, h: (x, min_y),
+                Actions.BLOCK_VALIGN_MIDDLE: lambda x, y, w, h: (x, ctr_y - h/2),
+                Actions.BLOCK_VALIGN_BOTTOM: lambda x, y, w, h: (x, max_y - h),
+                Actions.BLOCK_HALIGN_LEFT: lambda x, y, w, h: (min_x, y),
+                Actions.BLOCK_HALIGN_CENTER: lambda x, y, w, h: (ctr_x-w/2, y),
+                Actions.BLOCK_HALIGN_RIGHT: lambda x, y, w, h: (max_x - w, y),
+        }.get(calling_action, lambda *args: args)
+
+        for selected_block in blocks:
+            x, y = selected_block.coordinate
+            w, h = selected_block.width, selected_block.height
+            selected_block.coordinate = transform(x, y, w, h)
+
+        return True
+
+    def rotate_selected(self, rotation):
+        """
+        Rotate the selected blocks by multiples of 90 degrees.
+
+        Args:
+            rotation: the rotation in degrees
+
+        Returns:
+            true if changed, otherwise false.
+        """
+        if not any(self.selected_blocks()):
+            return False
+        #initialize min and max coordinates
+        min_x, min_y = max_x, max_y = self.selected_block.coordinate
+        # rotate each selected block, and find min/max coordinate
+        for selected_block in self.selected_blocks():
+            selected_block.rotate(rotation)
+            #update the min/max coordinate
+            x, y = selected_block.coordinate
+            min_x, min_y = min(min_x, x), min(min_y, y)
+            max_x, max_y = max(max_x, x), max(max_y, y)
+        #calculate center point of slected blocks
+        ctr_x, ctr_y = (max_x + min_x)/2, (max_y + min_y)/2
+        #rotate the blocks around the center point
+        for selected_block in self.selected_blocks():
+            x, y = selected_block.coordinate
+            x, y = Utils.get_rotated_coordinate((x - ctr_x, y - ctr_y), rotation)
+            selected_block.coordinate = (x + ctr_x, y + ctr_y)
+        return True
+
+    def remove_selected(self):
+        """
+        Remove selected elements
+
+        Returns:
+            true if changed.
+        """
+        changed = False
+        for selected_element in self.selected_elements:
+            self.remove_element(selected_element)
+            changed = True
+        return changed
+
+    def update_selected(self):
+        """
+        Remove deleted elements from the selected elements list.
+        Update highlighting so only the selected are highlighted.
+        """
+        selected_elements = self.selected_elements
+        elements = self.get_elements()
+        # remove deleted elements
+        for selected in list(selected_elements):
+            if selected in elements:
+                continue
+            selected_elements.remove(selected)
+        if self._old_selected_port and self._old_selected_port.parent not in elements:
+            self._old_selected_port = None
+        if self._new_selected_port and self._new_selected_port.parent not in elements:
+            self._new_selected_port = None
+        # update highlighting
+        for element in elements:
+            element.highlighted = element in selected_elements
+
+    ###########################################################################
+    # Draw stuff
+    ###########################################################################
+
+    def update_elements_to_draw(self):
+        hide_disabled_blocks = Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active()
+        hide_variables = Actions.TOGGLE_HIDE_VARIABLES.get_active()
+
+        def draw_order(elem):
+            return elem.highlighted, elem.is_block, elem.enabled
+
+        elements = sorted(self.get_elements(), key=draw_order)
+        del self._elements_to_draw[:]
+
+        for element in elements:
+            if hide_disabled_blocks and not element.enabled:
+                continue  # skip hidden disabled blocks and connections
+            if hide_variables and (element.is_variable or element.is_import):
+                continue  # skip hidden disabled blocks and connections
+            self._elements_to_draw.append(element)
+
+    def create_labels(self):
+        for element in self._elements_to_draw:
+            element.create_labels()
+
+    def create_shapes(self):
+        for element in self._elements_to_draw:
+            element.create_shapes()
+
+    def _drawables(self):
+        show_comments = Actions.TOGGLE_SHOW_BLOCK_COMMENTS.get_active()
+        for element in self._elements_to_draw:
+            if element.is_block and show_comments and element.enabled:
+                yield element.draw_comment
+        for element in self._elements_to_draw:
+            yield element.draw
+
+    def draw(self, cr):
+        """Draw blocks connections comment and select rectangle"""
+        for draw_element in self._drawables():
+            cr.save()
+            draw_element(cr)
+            cr.restore()
+
+        # draw multi select rectangle
+        if self.mouse_pressed and (not self.selected_elements or self.drawing_area.ctrl_mask):
+            x1, y1 = self.press_coor
+            x2, y2 = self.coordinate
+            x, y = int(min(x1, x2)), int(min(y1, y2))
+            w, h = int(abs(x1 - x2)), int(abs(y1 - y2))
+            cr.set_source_rgba(
+                Colors.HIGHLIGHT_COLOR[0],
+                Colors.HIGHLIGHT_COLOR[1],
+                Colors.HIGHLIGHT_COLOR[2],
+                0.5,
+            )
+            cr.rectangle(x, y, w, h)
+            cr.fill()
+            cr.rectangle(x, y, w, h)
+            cr.stroke()
+
+    ##########################################################################
+    # selection handling
+    ##########################################################################
+    def update_selected_elements(self):
+        """
+        Update the selected elements.
+        The update behavior depends on the state of the mouse button.
+        When the mouse button pressed the selection will change when
+        the control mask is set or the new selection is not in the current group.
+        When the mouse button is released the selection will change when
+        the mouse has moved and the control mask is set or the current group is empty.
+        Attempt to make a new connection if the old and ports are filled.
+        If the control mask is set, merge with the current elements.
+        """
+        selected_elements = None
+        if self.mouse_pressed:
+            new_selections = self.what_is_selected(self.coordinate)
+            # update the selections if the new selection is not in the current selections
+            # allows us to move entire selected groups of elements
+            if self.drawing_area.ctrl_mask or new_selections not in self.selected_elements:
+                selected_elements = new_selections
+
+            if self._old_selected_port:
+                self._old_selected_port.force_show_label = False
+                self.create_shapes()
+                self.drawing_area.queue_draw()
+            elif self._new_selected_port:
+                self._new_selected_port.force_show_label = True
+
+        else:  # called from a mouse release
+            if not self.element_moved and (not self.selected_elements or self.drawing_area.ctrl_mask):
+                selected_elements = self.what_is_selected(self.coordinate, self.press_coor)
+
+        # this selection and the last were ports, try to connect them
+        if self.make_connection():
+            return
+
+        # update selected elements
+        if selected_elements is None:
+            return
+
+        # if ctrl, set the selected elements to the union - intersection of old and new
+        if self.drawing_area.ctrl_mask:
+            self.selected_elements ^= selected_elements
+        else:
+            self.selected_elements.clear()
+            self.selected_elements.update(selected_elements)
+        Actions.ELEMENT_SELECT()
+
+    def what_is_selected(self, coor, coor_m=None):
+        """
+        What is selected?
+        At the given coordinate, return the elements found to be selected.
+        If coor_m is unspecified, return a list of only the first element found to be selected:
+        Iterate though the elements backwards since top elements are at the end of the list.
+        If an element is selected, place it at the end of the list so that is is drawn last,
+        and hence on top. Update the selected port information.
+
+        Args:
+            coor: the coordinate of the mouse click
+            coor_m: the coordinate for multi select
+
+        Returns:
+            the selected blocks and connections or an empty list
+        """
+        selected_port = None
+        selected = set()
+        # check the elements
+        for element in reversed(self._elements_to_draw):
+            selected_element = element.what_is_selected(coor, coor_m)
+            if not selected_element:
+                continue
+            # update the selected port information
+            if selected_element.is_port:
+                if not coor_m:
+                    selected_port = selected_element
+                selected_element = selected_element.parent_block
+
+            selected.add(selected_element)
+            if not coor_m:
+                break
+        # update selected ports
+        if selected_port is not self._new_selected_port:
+            self._old_selected_port = self._new_selected_port
+            self._new_selected_port = selected_port
+        return selected
+
+    def unselect(self):
+        """
+        Set selected elements to an empty set.
+        """
+        self.selected_elements.clear()
+
+    def select_all(self):
+        """Select all blocks in the flow graph"""
+        self.selected_elements.clear()
+        self.selected_elements.update(self._elements_to_draw)
+
+    def selected_blocks(self):
+        """
+        Get a group of selected blocks.
+
+        Returns:
+            sub set of blocks in this flow graph
+        """
+        return (e for e in self.selected_elements if e.is_block)
+
+    @property
+    def selected_block(self):
+        """
+        Get the selected block when a block or port is selected.
+
+        Returns:
+            a block or None
+        """
+        return next(self.selected_blocks(), None)
+
+    def get_selected_elements(self):
+        """
+        Get the group of selected elements.
+
+        Returns:
+            sub set of elements in this flow graph
+        """
+        return self.selected_elements
+
+    def get_selected_element(self):
+        """
+        Get the selected element.
+
+        Returns:
+            a block, port, or connection or None
+        """
+        return next(iter(self.selected_elements), None)
+
+    ##########################################################################
+    # Event Handlers
+    ##########################################################################
+    def handle_mouse_context_press(self, coordinate, event):
+        """
+        The context mouse button was pressed:
+        If no elements were selected, perform re-selection at this coordinate.
+        Then, show the context menu at the mouse click location.
+        """
+        selections = self.what_is_selected(coordinate)
+        if not selections.intersection(self.selected_elements):
+            self.coordinate = coordinate
+            self.mouse_pressed = True
+            self.update_selected_elements()
+            self.mouse_pressed = False
+        self._context_menu.popup(None, None, None, None, event.button, event.time)
+
+    def handle_mouse_selector_press(self, double_click, coordinate):
+        """
+        The selector mouse button was pressed:
+        Find the selected element. Attempt a new connection if possible.
+        Open the block params window on a double click.
+        Update the selection state of the flow graph.
+        """
+        self.press_coor = coordinate
+        self.coordinate = coordinate
+        self.mouse_pressed = True
+
+        if double_click:
+            self.unselect()
+        self.update_selected_elements()
+
+        if double_click and self.selected_block:
+            self.mouse_pressed = False
+            Actions.BLOCK_PARAM_MODIFY()
+
+    def handle_mouse_selector_release(self, coordinate):
+        """
+        The selector mouse button was released:
+        Update the state, handle motion (dragging).
+        And update the selected flowgraph elements.
+        """
+        self.coordinate = coordinate
+        self.mouse_pressed = False
+        if self.element_moved:
+            Actions.BLOCK_MOVE()
+            self.element_moved = False
+        self.update_selected_elements()
+
+    def handle_mouse_motion(self, coordinate):
+        """
+        The mouse has moved, respond to mouse dragging or notify elements
+        Move a selected element to the new coordinate.
+        Auto-scroll the scroll bars at the boundaries.
+        """
+        # to perform a movement, the mouse must be pressed
+        # (no longer checking pending events via Gtk.events_pending() - always true in Windows)
+        if not self.mouse_pressed:
+            self._handle_mouse_motion_move(coordinate)
+        else:
+            self._handle_mouse_motion_drag(coordinate)
+
+    def _handle_mouse_motion_move(self, coordinate):
+        # only continue if mouse-over stuff is enabled (just the auto-hide port label stuff for now)
+        if not Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active():
+            return
+        redraw = False
+        for element in self._elements_to_draw:
+            over_element = element.what_is_selected(coordinate)
+            if not over_element:
+                continue
+            if over_element != self.element_under_mouse:  # over sth new
+                if self.element_under_mouse:
+                    redraw |= self.element_under_mouse.mouse_out() or False
+                self.element_under_mouse = over_element
+                redraw |= over_element.mouse_over() or False
+            break
+        else:
+            if self.element_under_mouse:
+                redraw |= self.element_under_mouse.mouse_out() or False
+                self.element_under_mouse = None
+        if redraw:
+            # self.create_labels()
+            self.create_shapes()
+            self.drawing_area.queue_draw()
+
+    def _handle_mouse_motion_drag(self, coordinate):
+        # remove the connection if selected in drag event
+        if len(self.selected_elements) == 1 and self.get_selected_element().is_connection:
+            Actions.ELEMENT_DELETE()
+
+        # move the selected elements and record the new coordinate
+        x, y = coordinate
+        if not self.drawing_area.ctrl_mask:
+            X, Y = self.coordinate
+            dX, dY = int(x - X), int(y - Y)
+            active = Actions.TOGGLE_SNAP_TO_GRID.get_active() or self.drawing_area.mod1_mask
+            if not active or abs(dX) >= Constants.CANVAS_GRID_SIZE or abs(dY) >= Constants.CANVAS_GRID_SIZE:
+                self.move_selected((dX, dY))
+                self.coordinate = (x, y)
+        # queue draw for animation
+        self.drawing_area.queue_draw()
+
+    def get_max_coords(self, initial=(0, 0)):
+        return tuple(max(i, e) for i, e in zip(initial, self.extend[2:]))
+
+    @property
+    def extend(self):
+        extend = 100000, 100000, 0, 0
+        for element in self._elements_to_draw:
+            extend = (min_or_max(xy, e_xy) for min_or_max, xy, e_xy in zip(
+                (min, min, max, max), extend, element.extend
+            ))
+        return tuple(extend)
diff --git a/grc/gui/canvas/param.py b/grc/gui/canvas/param.py
new file mode 100644
index 0000000000..2ec99e70d8
--- /dev/null
+++ b/grc/gui/canvas/param.py
@@ -0,0 +1,162 @@
+# Copyright 2007-2016 Free Software Foundation, Inc.
+# This file is part of GNU Radio
+#
+# GNU Radio Companion is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# GNU Radio Companion is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+
+from __future__ import absolute_import
+
+from .drawable import Drawable
+
+from .. import ParamWidgets, Utils, Constants
+
+from ...core.Param import Param as CoreParam
+
+
+class Param(CoreParam):
+    """The graphical parameter."""
+
+    make_cls_with_base = classmethod(Drawable.make_cls_with_base.__func__)
+
+    def get_input(self, *args, **kwargs):
+        """
+        Get the graphical gtk class to represent this parameter.
+        An enum requires and combo parameter.
+        A non-enum with options gets a combined entry/combo parameter.
+        All others get a standard entry parameter.
+
+        Returns:
+            gtk input class
+        """
+        type_ = self.get_type()
+        if type_ in ('file_open', 'file_save'):
+            input_widget_cls = ParamWidgets.FileParam
+
+        elif self.is_enum():
+            input_widget_cls = ParamWidgets.EnumParam
+
+        elif self.options:
+            input_widget_cls = ParamWidgets.EnumEntryParam
+
+        elif type_ == '_multiline':
+            input_widget_cls = ParamWidgets.MultiLineEntryParam
+
+        elif type_ == '_multiline_python_external':
+            input_widget_cls = ParamWidgets.PythonEditorParam
+
+        else:
+            input_widget_cls = ParamWidgets.EntryParam
+
+        return input_widget_cls(self, *args, **kwargs)
+
+    def format_label_markup(self, have_pending_changes=False):
+        block = self.parent
+        # fixme: using non-public attribute here
+        has_callback = \
+            hasattr(block, 'get_callbacks') and \
+            any(self.key in callback for callback in block._callbacks)
+
+        return '<span {underline} {foreground} font_desc="Sans 9">{label}</span>'.format(
+            underline='underline="low"' if has_callback else '',
+            foreground='foreground="blue"' if have_pending_changes else
+                       'foreground="red"' if not self.is_valid() else '',
+            label=Utils.encode(self.name)
+        )
+
+    def format_tooltip_text(self):
+        errors = self.get_error_messages()
+        tooltip_lines = ['Key: ' + self.key, 'Type: ' + self.get_type()]
+        if self.is_valid():
+            value = str(self.get_evaluated())
+            if len(value) > 100:
+                value = '{}...{}'.format(value[:50], value[-50:])
+            tooltip_lines.append('Value: ' + value)
+        elif len(errors) == 1:
+            tooltip_lines.append('Error: ' + errors[0])
+        elif len(errors) > 1:
+            tooltip_lines.append('Error:')
+            tooltip_lines.extend(' * ' + msg for msg in errors)
+        return '\n'.join(tooltip_lines)
+
+    def pretty_print(self):
+        """
+        Get the repr (nice string format) for this param.
+
+        Returns:
+            the string representation
+        """
+        ##################################################
+        # Truncate helper method
+        ##################################################
+        def _truncate(string, style=0):
+            max_len = max(27 - len(self.name), 3)
+            if len(string) > max_len:
+                if style < 0:  # Front truncate
+                    string = '...' + string[3-max_len:]
+                elif style == 0:  # Center truncate
+                    string = string[:max_len/2 - 3] + '...' + string[-max_len/2:]
+                elif style > 0:  # Rear truncate
+                    string = string[:max_len-3] + '...'
+            return string
+
+        ##################################################
+        # Simple conditions
+        ##################################################
+        value = self.get_value()
+        if not self.is_valid():
+            return _truncate(value)
+        if value in self.options:
+            return self.options_names[self.options.index(value)]
+
+        ##################################################
+        # Split up formatting by type
+        ##################################################
+        # Default center truncate
+        truncate = 0
+        e = self.get_evaluated()
+        t = self.get_type()
+        if isinstance(e, bool):
+            return str(e)
+        elif isinstance(e, Constants.COMPLEX_TYPES):
+            dt_str = Utils.num_to_str(e)
+        elif isinstance(e, Constants.VECTOR_TYPES):
+            # Vector types
+            if len(e) > 8:
+                # Large vectors use code
+                dt_str = self.get_value()
+                truncate = 1
+            else:
+                # Small vectors use eval
+                dt_str = ', '.join(map(Utils.num_to_str, e))
+        elif t in ('file_open', 'file_save'):
+            dt_str = self.get_value()
+            truncate = -1
+        else:
+            # Other types
+            dt_str = str(e)
+
+        # Done
+        return _truncate(dt_str, truncate)
+
+    def format_block_surface_markup(self):
+        """
+        Get the markup for this param.
+
+        Returns:
+            a pango markup string
+        """
+        return '<span {foreground} font_desc="{font}"><b>{label}:</b> {value}</span>'.format(
+            foreground='foreground="red"' if not self.is_valid() else '', font=Constants.PARAM_FONT,
+            label=Utils.encode(self.name), value=Utils.encode(self.pretty_print().replace('\n', ' '))
+        )
diff --git a/grc/gui/canvas/port.py b/grc/gui/canvas/port.py
new file mode 100644
index 0000000000..bc40c9c56c
--- /dev/null
+++ b/grc/gui/canvas/port.py
@@ -0,0 +1,233 @@
+"""
+Copyright 2007, 2008, 2009 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+"""
+
+from __future__ import absolute_import
+
+import math
+
+from gi.repository import Gtk, PangoCairo, Pango
+
+from .drawable import Drawable
+
+from .. import Actions, Colors, Utils, Constants
+
+from ...core.Element import nop_write
+from ...core.Port import Port as CorePort
+
+
+class Port(CorePort, Drawable):
+    """The graphical port."""
+
+    def __init__(self, parent, direction, **n):
+        """
+        Port constructor.
+        Create list of connector coordinates.
+        """
+        super(self.__class__, self).__init__(parent, direction, **n)
+        Drawable.__init__(self)
+        self._connector_coordinate = (0, 0)
+        self._hovering = False
+        self.force_show_label = False
+
+        self._area = []
+        self._bg_color = self._border_color = 0, 0, 0, 0
+        self._font_color = list(Colors.FONT_COLOR)
+
+        self._line_width_factor = 1.0
+        self._label_layout_offsets = 0, 0
+
+        self.width_with_label = self.height = 0
+        self.connector_length = 0
+
+        self.label_layout = Gtk.DrawingArea().create_pango_layout('')
+        self.label_layout.set_alignment(Pango.Alignment.CENTER)
+
+    @property
+    def width(self):
+        return self.width_with_label if self._show_label else Constants.PORT_LABEL_HIDDEN_WIDTH
+
+    @width.setter
+    def width(self, value):
+        self.width_with_label = value
+        self.label_layout.set_width(value * Pango.SCALE)
+
+    def _update_colors(self):
+        """
+        Get the color that represents this port's type.
+        Codes differ for ports where the vec length is 1 or greater than 1.
+
+        Returns:
+            a hex color code.
+        """
+        if not self.parent_block.enabled:
+            self._font_color[-1] = 0.4
+            color = Colors.BLOCK_DISABLED_COLOR
+        else:
+            self._font_color[-1] = 1.0
+            color = Colors.PORT_TYPE_TO_COLOR.get(self.get_type()) or Colors.PORT_TYPE_TO_COLOR.get('')
+            vlen = self.get_vlen()
+            if vlen > 1:
+                dark = (0, 0, 30 / 255.0, 50 / 255.0, 70 / 255.0)[min(4, vlen)]
+                color = tuple(max(c - dark, 0) for c in color)
+        self._bg_color = color
+        self._border_color = tuple(max(c - 0.3, 0) for c in color)
+
+    def create_shapes(self):
+        """Create new areas and labels for the port."""
+        if self.is_horizontal():
+            self._area = (0, 0, self.width, self.height)
+        elif self.is_vertical():
+            self._area = (0, 0, self.height, self.width)
+        self.bounds_from_area(self._area)
+
+        self._connector_coordinate = {
+            0:   (self.width, self.height / 2),
+            90:  (self.height / 2, 0),
+            180: (0, self.height / 2),
+            270: (self.height / 2, self.width)
+        }[self.get_connector_direction()]
+
+    def create_labels(self):
+        """Create the labels for the socket."""
+
+        if self.domain in (Constants.GR_MESSAGE_DOMAIN, Constants.DEFAULT_DOMAIN):
+            self._line_width_factor = 1.0
+        else:
+            self._line_width_factor = 2.0
+
+        self._update_colors()
+
+        layout = self.label_layout
+        layout.set_markup('<span font_desc="{font}">{name}</span>'.format(
+            name=Utils.encode(self.name), font=Constants.PORT_FONT
+        ))
+        label_width, label_height = self.label_layout.get_pixel_size()
+
+        self.width = 2 * Constants.PORT_LABEL_PADDING + label_width
+        self.height = 2 * Constants.PORT_LABEL_PADDING + label_height
+        self._label_layout_offsets = [0, Constants.PORT_LABEL_PADDING]
+        if self.get_type() == 'bus':
+            self.height += Constants.PORT_EXTRA_BUS_HEIGHT
+            self._label_layout_offsets[1] += Constants.PORT_EXTRA_BUS_HEIGHT / 2
+        self.height += self.height % 2  # uneven height
+
+    def draw(self, cr):
+        """
+        Draw the socket with a label.
+        """
+        border_color = self._border_color
+        cr.set_line_width(self._line_width_factor * cr.get_line_width())
+        cr.translate(*self.coordinate)
+
+        cr.rectangle(*self._area)
+        cr.set_source_rgba(*self._bg_color)
+        cr.fill_preserve()
+        cr.set_source_rgba(*border_color)
+        cr.stroke()
+
+        if not self._show_label:
+            return  # this port is folded (no label)
+
+        if self.is_vertical():
+            cr.rotate(-math.pi / 2)
+            cr.translate(-self.width, 0)
+        cr.translate(*self._label_layout_offsets)
+
+        cr.set_source_rgba(*self._font_color)
+        PangoCairo.update_layout(cr, self.label_layout)
+        PangoCairo.show_layout(cr, self.label_layout)
+
+    def get_connector_coordinate(self):
+        """
+        Get the coordinate where connections may attach to.
+
+        Returns:
+            the connector coordinate (x, y) tuple
+        """
+        return [sum(c) for c in zip(
+            self._connector_coordinate,   # relative to port
+            self.coordinate,              # relative to block
+            self.parent_block.coordinate  # abs
+        )]
+
+    def get_connector_direction(self):
+        """
+        Get the direction that the socket points: 0,90,180,270.
+        This is the rotation degree if the socket is an output or
+        the rotation degree + 180 if the socket is an input.
+
+        Returns:
+            the direction in degrees
+        """
+        if self.is_source:
+            return self.rotation
+        elif self.is_sink:
+            return (self.rotation + 180) % 360
+
+    @nop_write
+    @property
+    def rotation(self):
+        return self.parent_block.rotation
+
+    def rotate(self, direction):
+        """
+        Rotate the parent rather than self.
+
+        Args:
+            direction: degrees to rotate
+        """
+        self.parent_block.rotate(direction)
+
+    def move(self, delta_coor):
+        """Move the parent rather than self."""
+        self.parent_block.move(delta_coor)
+
+    @property
+    def highlighted(self):
+        return self.parent_block.highlighted
+
+    @highlighted.setter
+    def highlighted(self, value):
+        self.parent_block.highlighted = value
+
+    @property
+    def _show_label(self):
+        """
+        Figure out if the label should be hidden
+
+        Returns:
+            true if the label should not be shown
+        """
+        return self._hovering or self.force_show_label or not Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active()
+
+    def mouse_over(self):
+        """
+        Called from flow graph on mouse-over
+        """
+        changed = not self._show_label
+        self._hovering = True
+        return changed
+
+    def mouse_out(self):
+        """
+        Called from flow graph on mouse-out
+        """
+        label_was_shown = self._show_label
+        self._hovering = False
+        return label_was_shown != self._show_label
-- 
cgit v1.2.3