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.py1079
1 files changed, 534 insertions, 545 deletions
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index a238ed166a..4dff675afb 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -29,548 +29,537 @@ import random
import Messages
class FlowGraph(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):
- """
- FlowGraph contructor.
- Create a list for signal blocks and connections. Connect mouse handlers.
- """
- Element.__init__(self)
- #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.element_moved = False
- self.mouse_pressed = False
- self.unselect()
- self.press_coor = (0, 0)
- #selected ports
- self._old_selected_port = None
- self._new_selected_port = None
- #context menu
- self._context_menu = gtk.Menu()
- for action in [
- Actions.BLOCK_CUT,
- Actions.BLOCK_COPY,
- Actions.BLOCK_PASTE,
- Actions.ELEMENT_DELETE,
- Actions.BLOCK_ROTATE_CCW,
- Actions.BLOCK_ROTATE_CW,
- Actions.BLOCK_ENABLE,
- Actions.BLOCK_DISABLE,
- Actions.BLOCK_PARAM_MODIFY,
- Actions.BLOCK_CREATE_HIER,
- Actions.OPEN_HIER,
- Actions.BUSSIFY_SOURCES,
- Actions.BUSSIFY_SINKS,
- ]: self._context_menu.append(action.create_menu_item())
-
- ###########################################################################
- # 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 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.
-
- Args:
- key: the block key
- 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()
- 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()),
- )
- #get the new block
- block = self.get_new_block(key)
- block.set_coordinate(coor)
- block.set_rotation(0)
- block.get_param('id').set_value(id)
- Actions.ELEMENT_CREATE()
-
- return id
-
- ###########################################################################
- # Copy Paste
- ###########################################################################
- def copy_to_clipboard(self):
- """
- Copy the selected blocks and connections into the clipboard.
-
- Returns:
- the clipboard
- """
- #get selected blocks
- blocks = self.get_selected_blocks()
- if not blocks: return None
- #calc x and y min
- x_min, y_min = blocks[0].get_coordinate()
- for block in blocks:
- x, y = block.get_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,
- self.get_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
- """
-
- 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
- #create blocks
- for block_n in blocks_n:
- block_key = block_n.find('key')
- if block_key == 'options': continue
- block = self.get_new_block(block_key)
- selected.add(block)
- #set params
- params_n = block_n.findall('param')
- for param_n in params_n:
-
- param_key = param_n.find('key')
- param_value = param_n.find('value')
- #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 [bluck.get_id() for bluck in self.get_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.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'))
- 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)
-
- ###########################################################################
- # 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.get_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.get_selected_blocks()])
-
- def enable_selected(self, enable):
- """
- 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.get_enabled() != enable:
- selected_block.set_enabled(enable)
- changed = True
- 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.get_selected_blocks():
- selected_block.move(delta_coordinate)
- self.element_moved = 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 self.get_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():
- selected_block.rotate(rotation)
- #update the min/max coordinate
- x, y = selected_block.get_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()
- x, y = Utils.get_rotated_coordinate((x - ctr_x, y - ctr_y), rotation)
- selected_block.set_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.get_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()
- #draw the background
- gc.set_foreground(Colors.FLOWGRAPH_BACKGROUND_COLOR)
- window.draw_rectangle(gc, True, 0, 0, W, H)
- #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
- for element in self.get_connections() + self.get_blocks():
- 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()
- 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.get_parent() not in elements:
- self._old_selected_port = None
- if self._new_selected_port and self._new_selected_port.get_parent() not in elements:
- self._new_selected_port = None
- #update highlighting
- for element in elements:
- element.set_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()
-
- ##########################################################################
- ## Get Selected
- ##########################################################################
- def unselect(self):
- """
- Set selected elements to an empty set.
- """
- self._selected_elements = []
-
- 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.get_elements()):
- 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.get_parent()
- 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
- self._old_selected_port = self._new_selected_port
- self._new_selected_port = selected_port
- return list(selected)
-
- def get_selected_connections(self):
- """
- Get a group of selected connections.
-
- Returns:
- sub set of connections in this flow graph
- """
- selected = set()
- for selected_element in self.get_selected_elements():
- if selected_element.is_connection(): selected.add(selected_element)
- return list(selected)
-
- def get_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)
-
- def get_selected_block(self):
- """
- Get the selected block when a block or port is selected.
-
- Returns:
- a block or None
- """
- return self.get_selected_blocks() and self.get_selected_blocks()[0] or 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 self.get_selected_elements() and self.get_selected_elements()[0] or 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
- 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 and \
- self._old_selected_port is not 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()
-
- ##########################################################################
- ## 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 set(selections).intersection(self.get_selected_elements()):
- self.set_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)
-
- 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.set_coordinate(coordinate)
- self.time = 0
- self.mouse_pressed = True
- 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()
-
- 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.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):
- """
- The mouse has moved, respond to mouse dragging.
- 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: return
- #perform autoscrolling
- 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
- X, Y = self.get_coordinate()
- if not self.get_ctrl_mask(): self.move_selected((int(x - X), int(y - Y)))
- self.set_coordinate((x, y))
- #queue draw for animation
- self.queue_draw()
+ """
+ FlowGraph is the data structure to store graphical signal blocks,
+ graphical inputs and outputs,
+ and the connections between inputs and outputs.
+ """
+
+ def __init__(self):
+ """
+ FlowGraph contructor.
+ Create a list for signal blocks and connections. Connect mouse handlers.
+ """
+ Element.__init__(self)
+ #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.element_moved = False
+ self.mouse_pressed = False
+ self.unselect()
+ self.press_coor = (0, 0)
+ #selected ports
+ self._old_selected_port = None
+ self._new_selected_port = None
+ #context menu
+ self._context_menu = gtk.Menu()
+ for action in [
+ Actions.BLOCK_CUT,
+ Actions.BLOCK_COPY,
+ Actions.BLOCK_PASTE,
+ Actions.ELEMENT_DELETE,
+ Actions.BLOCK_ROTATE_CCW,
+ Actions.BLOCK_ROTATE_CW,
+ Actions.BLOCK_ENABLE,
+ Actions.BLOCK_DISABLE,
+ Actions.BLOCK_PARAM_MODIFY,
+ Actions.BLOCK_CREATE_HIER,
+ Actions.OPEN_HIER,
+ Actions.BUSSIFY_SOURCES,
+ Actions.BUSSIFY_SINKS,
+ ]: self._context_menu.append(action.create_menu_item())
+
+ ###########################################################################
+ # 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 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.
+
+ Args:
+ key: the block key
+ 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()
+ 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()),
+ )
+ #get the new block
+ block = self.get_new_block(key)
+ block.set_coordinate(coor)
+ block.set_rotation(0)
+ block.get_param('id').set_value(id)
+ Actions.ELEMENT_CREATE()
+ return id
+
+ ###########################################################################
+ # Copy Paste
+ ###########################################################################
+ def copy_to_clipboard(self):
+ """
+ Copy the selected blocks and connections into the clipboard.
+
+ Returns:
+ the clipboard
+ """
+ #get selected blocks
+ blocks = self.get_selected_blocks()
+ if not blocks: return None
+ #calc x and y min
+ x_min, y_min = blocks[0].get_coordinate()
+ for block in blocks:
+ x, y = block.get_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,
+ self.get_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
+ """
+ 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
+ #create blocks
+ for block_n in blocks_n:
+ block_key = block_n.find('key')
+ if block_key == 'options': continue
+ block = self.get_new_block(block_key)
+ selected.add(block)
+ #set params
+ params_n = block_n.findall('param')
+ for param_n in params_n:
+ param_key = param_n.find('key')
+ param_value = param_n.find('value')
+ #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 [bluck.get_id() for bluck in self.get_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.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'))
+ 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)
+
+ ###########################################################################
+ # 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.get_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.get_selected_blocks()])
+
+ def enable_selected(self, enable):
+ """
+ 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.get_enabled() != enable:
+ selected_block.set_enabled(enable)
+ changed = True
+ 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.get_selected_blocks():
+ selected_block.move(delta_coordinate)
+ self.element_moved = 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 self.get_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():
+ selected_block.rotate(rotation)
+ #update the min/max coordinate
+ x, y = selected_block.get_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()
+ x, y = Utils.get_rotated_coordinate((x - ctr_x, y - ctr_y), rotation)
+ selected_block.set_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.get_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()
+ #draw the background
+ gc.set_foreground(Colors.FLOWGRAPH_BACKGROUND_COLOR)
+ window.draw_rectangle(gc, True, 0, 0, W, H)
+ #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
+ for element in self.get_connections() + self.get_blocks():
+ 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()
+ 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.get_parent() not in elements:
+ self._old_selected_port = None
+ if self._new_selected_port and self._new_selected_port.get_parent() not in elements:
+ self._new_selected_port = None
+ #update highlighting
+ for element in elements:
+ element.set_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()
+
+ ##########################################################################
+ ## Get Selected
+ ##########################################################################
+ def unselect(self):
+ """
+ Set selected elements to an empty set.
+ """
+ self._selected_elements = []
+
+ 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.get_elements()):
+ 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.get_parent()
+ 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
+ self._old_selected_port = self._new_selected_port
+ self._new_selected_port = selected_port
+ return list(selected)
+
+ def get_selected_connections(self):
+ """
+ Get a group of selected connections.
+
+ Returns:
+ sub set of connections in this flow graph
+ """
+ selected = set()
+ for selected_element in self.get_selected_elements():
+ if selected_element.is_connection(): selected.add(selected_element)
+ return list(selected)
+
+ def get_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)
+
+ def get_selected_block(self):
+ """
+ Get the selected block when a block or port is selected.
+
+ Returns:
+ a block or None
+ """
+ return self.get_selected_blocks() and self.get_selected_blocks()[0] or 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 self.get_selected_elements() and self.get_selected_elements()[0] or 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
+ 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 and \
+ self._old_selected_port is not 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()
+
+ ##########################################################################
+ ## 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 set(selections).intersection(self.get_selected_elements()):
+ self.set_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)
+
+ 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.set_coordinate(coordinate)
+ self.time = 0
+ self.mouse_pressed = True
+ 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()
+
+ 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.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):
+ """
+ The mouse has moved, respond to mouse dragging.
+ 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: return
+ #perform autoscrolling
+ 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
+ X, Y = self.get_coordinate()
+ if not self.get_ctrl_mask(): self.move_selected((int(x - X), int(y - Y)))
+ self.set_coordinate((x, y))
+ #queue draw for animation
+ self.queue_draw()