diff options
Diffstat (limited to 'grc/gui/FlowGraph.py')
-rw-r--r-- | grc/gui/FlowGraph.py | 679 |
1 files changed, 334 insertions, 345 deletions
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index f98aec41d5..f04383f32c 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 @@ -17,53 +17,57 @@ 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 chain, count -from operator import methodcaller +from itertools import count + +import six +from six.moves import filter -import gobject +from gi.repository import GLib from . import Actions, Colors, Constants, Utils, Bars, Dialogs -from .Constants import SCROLL_PROXIMITY_SENSITIVITY, SCROLL_DISTANCE 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) - #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 + self.drawing_area = None + # 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._external_updaters = {} def _get_unique_id(self, base_id=''): @@ -76,21 +80,22 @@ 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 def install_external_editor(self, param): - target = (param.get_parent().get_id(), param.get_key()) + target = (param.parent_block.get_id(), param.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)) + Dialogs.choose_editor(None, config)) # todo: pass in parent if not editor: return updater = functools.partial( @@ -98,7 +103,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(GLib.idle_add, updater) ) editor.start() try: @@ -107,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: @@ -120,19 +125,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_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_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. @@ -142,24 +134,62 @@ class FlowGraph(Element, _Flowgraph): coor: an optional coordinate or None for random """ id = self._get_unique_id(key) - #calculate the position coordinate - W, H = self.get_size() - 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) * min(h_adj.page_size, W) + - h_adj.get_value()), - int(random.uniform(.25, .75) * min(v_adj.page_size, H) + - 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 + # get the new block block = self.new_block(key) - block.set_coordinate(coor) - block.set_rotation(0) + 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 ########################################################################### @@ -171,19 +201,20 @@ class FlowGraph(Element, _Flowgraph): the clipboard """ #get selected blocks - blocks = self.get_selected_blocks() - if not blocks: return None + blocks = list(self.selected_blocks()) + 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 - connections = filter( - lambda c: c.get_source().get_parent() in blocks and c.get_sink().get_parent() in 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], @@ -198,32 +229,39 @@ 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() - #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 + # 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.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')) + 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 params.iteritems(): + for param_key, param_value in six.iteritems(param_data): #setup id parameter if param_key == 'id': old_id2block[param_value] = block @@ -238,12 +276,13 @@ 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())) - self._selected_elements = list(selected) + for block in selected: + selected = selected.union(set(block.get_connections())) + self.selected_elements = set(selected) ########################################################################### # Modify Selected @@ -258,7 +297,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): """ @@ -270,35 +309,22 @@ 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 enable_selected(self, enable): + def change_state_selected(self, new_state): """ Enable/disable the selected blocks. Args: - enable: true to enable - - 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. + new_state: a block state - 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): @@ -308,10 +334,7 @@ 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(): + for selected_block in self.selected_blocks(): selected_block.move(delta_coordinate) self.element_moved = True @@ -325,17 +348,17 @@ 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 # 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.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 @@ -350,9 +373,9 @@ class FlowGraph(Element, _Flowgraph): }.get(calling_action, lambda *args: args) for selected_block in blocks: - x, y = selected_block.get_coordinate() - w, h = selected_block.W, selected_block.H - selected_block.set_coordinate(transform(x, y, w, h)) + x, y = selected_block.coordinate + w, h = selected_block.width, selected_block.height + selected_block.coordinate = transform(x, y, w, h) return True @@ -366,25 +389,24 @@ 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() - #rotate each selected block, and find min/max coordinate - for selected_block in self.get_selected_blocks(): + 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.get_selected_blocks(): - x, y = selected_block.get_coordinate() + 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.set_coordinate((x + ctr_x, y + ctr_y)) + selected_block.coordinate = (x + ctr_x, y + ctr_y) return True def remove_selected(self): @@ -395,116 +417,140 @@ 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 draw(self, gc, window): - """ - 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() - hide_disabled_blocks = Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active() - hide_variables = Actions.TOGGLE_HIDE_VARIABLES.get_active() - - #draw the background - gc.set_foreground(Colors.FLOWGRAPH_BACKGROUND_COLOR) - window.draw_rectangle(gc, True, 0, 0, W, H) - - # draw comments first - if Actions.TOGGLE_SHOW_BLOCK_COMMENTS.get_active(): - for block in self.blocks: - if hide_variables and (block.is_variable or block.is_import): - continue # skip hidden disabled blocks and connections - if block.get_enabled(): - block.draw_comment(gc, window) - #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 - 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) - #draw blocks on top of connections - 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(gc, window) - #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) - def update_selected(self): """ Remove deleted elements from the selected elements list. Update highlighting so only the selected are highlighted. """ - selected_elements = self.get_selected_elements() + selected_elements = self.selected_elements elements = self.get_elements() - #remove deleted elements - for selected in selected_elements: - if selected in elements: continue + # 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.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 + # update highlighting for element in elements: - element.set_highlighted(element in selected_elements) + element.highlighted = element in selected_elements - 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() + ########################################################################### + # Draw stuff + ########################################################################### - def reload(self): - """ - Reload flow-graph (with updated blocks) + def update_elements_to_draw(self): + hide_disabled_blocks = Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active() + hide_variables = Actions.TOGGLE_HIDE_VARIABLES.get_active() - 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 + 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() ########################################################################## - ## Get Selected + # selection handling ########################################################################## - def unselect(self): + def update_selected_elements(self): """ - Set selected elements to an empty set. + 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. """ - self._selected_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 - def select_all(self): - """Select all blocks in the flow graph""" - self._selected_elements = list(self.get_elements()) + 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): """ @@ -524,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 - selected_element = selected_element.get_parent() + 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): """ @@ -594,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): """ @@ -603,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): """ @@ -665,12 +648,12 @@ 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()): - self.set_coordinate(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, 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): """ @@ -680,13 +663,14 @@ class FlowGraph(Element, _Flowgraph): Update the selection state of the flow graph. """ self.press_coor = coordinate - self.set_coordinate(coordinate) - self.time = 0 + self.coordinate = coordinate 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(): + + if double_click and self.selected_block: self.mouse_pressed = False Actions.BLOCK_PARAM_MODIFY() @@ -696,8 +680,7 @@ class FlowGraph(Element, _Flowgraph): Update the state, handle motion (dragging). And update the selected flowgraph elements. """ - self.set_coordinate(coordinate) - self.time = 0 + self.coordinate = coordinate self.mouse_pressed = False if self.element_moved: Actions.BLOCK_MOVE() @@ -710,56 +693,62 @@ class FlowGraph(Element, _Flowgraph): 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) + # 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: + 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 = 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 - self.queue_draw() + 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) |