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