summaryrefslogtreecommitdiff
path: root/grc/gui/FlowGraph.py
diff options
context:
space:
mode:
Diffstat (limited to 'grc/gui/FlowGraph.py')
-rw-r--r--grc/gui/FlowGraph.py679
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)