summaryrefslogtreecommitdiff
path: root/grc/gui/VariableEditor.py
diff options
context:
space:
mode:
Diffstat (limited to 'grc/gui/VariableEditor.py')
-rw-r--r--grc/gui/VariableEditor.py283
1 files changed, 283 insertions, 0 deletions
diff --git a/grc/gui/VariableEditor.py b/grc/gui/VariableEditor.py
new file mode 100644
index 0000000000..fcb1f4d2e9
--- /dev/null
+++ b/grc/gui/VariableEditor.py
@@ -0,0 +1,283 @@
+"""
+Copyright 2015 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+"""
+
+from itertools import chain
+from operator import attrgetter
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+
+from . import Actions
+from . import Preferences
+from .Constants import DEFAULT_BLOCKS_WINDOW_WIDTH
+
+BLOCK_INDEX = 0
+ID_INDEX = 1
+
+
+class VariableEditor(gtk.VBox):
+
+ # Actions that are handled by the editor
+ ADD_IMPORT = 0
+ ADD_VARIABLE = 1
+ OPEN_PROPERTIES = 2
+ DELETE_BLOCK = 3
+ DELETE_CONFIRM = 4
+ ENABLE_BLOCK = 5
+ DISABLE_BLOCK = 6
+
+ def __init__(self, platform, get_flow_graph):
+ gtk.VBox.__init__(self)
+ self.platform = platform
+ self.get_flow_graph = get_flow_graph
+ self._block = None
+
+ # Only use the model to store the block reference and name.
+ # Generate everything else dynamically
+ self.treestore = gtk.TreeStore(gobject.TYPE_PYOBJECT, # Block reference
+ gobject.TYPE_STRING) # Category and block name
+ self.treeview = gtk.TreeView(self.treestore)
+ self.treeview.set_enable_search(True)
+ self.treeview.set_search_column(ID_INDEX)
+ self.treeview.get_selection().set_mode('single')
+ self.treeview.set_headers_visible(True)
+ self.treeview.connect('button-press-event', self._handle_mouse_button_press)
+ self.treeview.connect('key-press-event', self._handle_key_button_press)
+
+ # Block Name or Category
+ self.id_cell = gtk.CellRendererText()
+ self.id_cell.connect('edited', self._handle_name_edited_cb)
+ id_column = gtk.TreeViewColumn("Id", self.id_cell, text=ID_INDEX)
+ id_column.set_name("id")
+ id_column.set_resizable(True)
+ id_column.set_max_width(300)
+ id_column.set_min_width(80)
+ id_column.set_fixed_width(100)
+ id_column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
+ id_column.set_cell_data_func(self.id_cell, self.set_properties)
+ self.treeview.append_column(id_column)
+ self.treestore.set_sort_column_id(ID_INDEX, gtk.SORT_ASCENDING)
+
+ # Block Value
+ self.value_cell = gtk.CellRendererText()
+ self.value_cell.connect('edited', self._handle_value_edited_cb)
+ value_column = gtk.TreeViewColumn("Value", self.value_cell)
+ value_column.set_name("value")
+ value_column.set_resizable(False)
+ value_column.set_expand(True)
+ value_column.set_min_width(100)
+ value_column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
+ value_column.set_cell_data_func(self.value_cell, self.set_value)
+ self.treeview.append_column(value_column)
+
+ # Block Actions (Add, Remove)
+ self.action_cell = gtk.CellRendererPixbuf()
+ value_column.pack_start(self.action_cell, False)
+ value_column.set_cell_data_func(self.action_cell, self.set_icon)
+
+ # Make the scrolled window to hold the tree view
+ scrolled_window = gtk.ScrolledWindow()
+ scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled_window.add_with_viewport(self.treeview)
+ scrolled_window.set_size_request(DEFAULT_BLOCKS_WINDOW_WIDTH, -1)
+ self.pack_start(scrolled_window)
+
+ # Context menus
+ self._confirm_delete = Preferences.variable_editor_confirm_delete()
+
+ # Sets cell contents
+ def set_icon(self, col, cell, model, iter):
+ block = model.get_value(iter, BLOCK_INDEX)
+ if block:
+ pb = self.treeview.render_icon(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU, None)
+ else:
+ pb = self.treeview.render_icon(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU, None)
+ cell.set_property('pixbuf', pb)
+
+ def set_value(self, col, cell, model, iter):
+ sp = cell.set_property
+ block = model.get_value(iter, BLOCK_INDEX)
+
+ # Set the default properties for this column first.
+ # Some set in set_properties() may be overridden (editable for advanced variable blocks)
+ self.set_properties(col, cell, model, iter)
+
+ # Set defaults
+ value = None
+ evaluated = None
+ self.set_tooltip_text(None)
+
+ # Block specific values
+ if block:
+ if block.get_key() == 'import':
+ value = block.get_param('import').get_value()
+ else:
+ if block.get_key() != "variable":
+ value = "<Open Properties>"
+ sp('editable', False)
+ else:
+ value = block.get_param('value').get_value()
+
+ # Check if there are errors in the blocks.
+ # Show the block error as a tooltip
+ error_message = block.get_error_messages()
+ if len(error_message) > 0:
+ # Set the error message to the last error in the list.
+ # This should be the first message generated
+ self.set_tooltip_text(error_message[-1])
+ else:
+ # Evaluate and show the value (if it is a variable)
+ if block.get_key() == "variable":
+ evaluated = str(block.get_param('value').evaluate())
+ self.set_tooltip_text(evaluated)
+ # Always set the text value.
+ sp('text', value)
+
+ def set_properties(self, col, cell, model, iter):
+ sp = cell.set_property
+ block = model.get_value(iter, BLOCK_INDEX)
+ # Set defaults
+ sp('sensitive', True)
+ sp('editable', False)
+ sp('foreground', None)
+
+ # Block specific changes
+ if block:
+ if not block.get_enabled():
+ # Disabled block. But, this should still be editable
+ sp('editable', True)
+ sp('foreground', 'gray')
+ else:
+ sp('editable', True)
+ if block.get_error_messages():
+ sp('foreground', 'red')
+
+ def update_gui(self):
+ if not self.get_flow_graph():
+ return
+ self._update_blocks()
+ self._rebuild()
+ self.treeview.expand_all()
+
+ def _update_blocks(self):
+ self._imports = filter(attrgetter('is_import'),
+ self.get_flow_graph().blocks)
+ self._variables = filter(attrgetter('is_variable'),
+ self.get_flow_graph().blocks)
+
+ def _rebuild(self, *args):
+ self.treestore.clear()
+ imports = self.treestore.append(None, [None, 'Imports'])
+ variables = self.treestore.append(None, [None, 'Variables'])
+ for block in self._imports:
+ self.treestore.append(imports, [block, block.get_param('id').get_value()])
+ for block in sorted(self._variables, key=lambda v: v.get_id()):
+ self.treestore.append(variables, [block, block.get_param('id').get_value()])
+
+ def _handle_name_edited_cb(self, cell, path, new_text):
+ block = self.treestore[path][BLOCK_INDEX]
+ block.get_param('id').set_value(new_text)
+ Actions.VARIABLE_EDITOR_UPDATE()
+
+ def _handle_value_edited_cb(self, cell, path, new_text):
+ block = self.treestore[path][BLOCK_INDEX]
+ if block.is_import:
+ block.get_param('import').set_value(new_text)
+ else:
+ block.get_param('value').set_value(new_text)
+ Actions.VARIABLE_EDITOR_UPDATE()
+
+ def _handle_action(self, item, key, event=None):
+ """
+ Single handler for the different actions that can be triggered by the context menu,
+ key presses or mouse clicks. Also triggers an update of the flowgraph and editor.
+ """
+ if key == self.ADD_IMPORT:
+ self.get_flow_graph().add_new_block('import')
+ elif key == self.ADD_VARIABLE:
+ self.get_flow_graph().add_new_block('variable')
+ elif key == self.OPEN_PROPERTIES:
+ Actions.BLOCK_PARAM_MODIFY(self._block)
+ elif key == self.DELETE_BLOCK:
+ self.get_flow_graph().remove_element(self._block)
+ elif key == self.DELETE_CONFIRM:
+ if self._confirm_delete:
+ # Create a context menu to confirm the delete operation
+ confirmation_menu = gtk.Menu()
+ block_id = self._block.get_param('id').get_value().replace("_", "__")
+ confirm = gtk.MenuItem("Delete {}".format(block_id))
+ confirm.connect('activate', self._handle_action, self.DELETE_BLOCK)
+ confirmation_menu.add(confirm)
+ confirmation_menu.show_all()
+ confirmation_menu.popup(None, None, None, event.button, event.time)
+ else:
+ self._handle_action(None, self.DELETE_BLOCK, None)
+ elif key == self.ENABLE_BLOCK:
+ self._block.set_enabled(True)
+ elif key == self.DISABLE_BLOCK:
+ self._block.set_enabled(False)
+ Actions.VARIABLE_EDITOR_UPDATE()
+
+ def _handle_mouse_button_press(self, widget, event):
+ """
+ Handles mouse button for several different events:
+ - Doublc Click to open properties for advanced blocks
+ - Click to add/remove blocks
+ """
+ path = widget.get_path_at_pos(int(event.x), int(event.y))
+ if path:
+ # If there is a valid path, then get the row, column and block selected.
+ row = self.treestore[path[0]]
+ col = path[1]
+ self._block = row[BLOCK_INDEX]
+
+ if event.button == 1 and col.get_name() == "value":
+ # Make sure this has a block (not the import/variable rows)
+ if self._block and event.type == gtk.gdk._2BUTTON_PRESS:
+ # Open the advanced dialog if it is a gui variable
+ if self._block.get_key() not in ("variable", "import"):
+ self._handle_action(None, self.OPEN_PROPERTIES, event=event)
+ return True
+ if event.type == gtk.gdk.BUTTON_PRESS:
+ # User is adding/removing blocks
+ # Make sure this is the action cell (Add/Remove Icons)
+ if path[2] > col.cell_get_position(self.action_cell)[0]:
+ if row[1] == "Imports":
+ # Add a new import block.
+ self._handle_action(None, self.ADD_IMPORT, event=event)
+ elif row[1] == "Variables":
+ # Add a new variable block
+ self._handle_action(None, self.ADD_VARIABLE, event=event)
+ else:
+ self._handle_action(None, self.DELETE_CONFIRM, event=event)
+ return True
+ return False
+
+ def _handle_key_button_press(self, widget, event):
+ model, path = self.treeview.get_selection().get_selected_rows()
+ if path and self._block:
+ if self._block.get_enabled() and event.string == "d":
+ self._handle_action(None, self.DISABLE_BLOCK, None)
+ return True
+ elif not self._block.get_enabled() and event.string == "e":
+ self._handle_action(None, self.ENABLE_BLOCK, None)
+ return True
+ return False