From 510a96615da4b2511e08da2d7a16e77e1b58a1b5 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Tue, 15 Oct 2013 21:57:29 +0200
Subject: filter-based block tree search

---
 grc/gui/BlockTreeWindow.py | 142 +++++++++++++++++++++++++++++++--------------
 1 file changed, 97 insertions(+), 45 deletions(-)

(limited to 'grc/gui/BlockTreeWindow.py')

diff --git a/grc/gui/BlockTreeWindow.py b/grc/gui/BlockTreeWindow.py
index ced6429c62..013809cdea 100644
--- a/grc/gui/BlockTreeWindow.py
+++ b/grc/gui/BlockTreeWindow.py
@@ -18,6 +18,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 """
 
 from Constants import DEFAULT_BLOCKS_WINDOW_WIDTH, DND_TARGETS
+import Actions
 import Utils
 import pygtk
 pygtk.require('2.0')
@@ -46,7 +47,7 @@ class BlockTreeWindow(gtk.VBox):
         Create a tree view of the possible blocks in the platform.
         The tree view nodes will be category names, the leaves will be block names.
         A mouse double click or button press action will trigger the add block event.
-        
+
         Args:
             platform: the particular platform will all block prototypes
             get_flow_graph: get the selected flow graph
@@ -54,21 +55,33 @@ class BlockTreeWindow(gtk.VBox):
         gtk.VBox.__init__(self)
         self.platform = platform
         self.get_flow_graph = get_flow_graph
-        #make the tree model for holding blocks
+
+        # search entry
+        self.search_entry = gtk.Entry()
+        self.search_entry.set_icon_from_stock(gtk.ENTRY_ICON_PRIMARY, gtk.STOCK_FIND)
+        self.search_entry.set_icon_activatable(gtk.ENTRY_ICON_PRIMARY, False)
+        self.search_entry.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, gtk.STOCK_CLOSE)
+        self.search_entry.connect('icon-release', self._handle_icon_event)
+        self.search_entry.connect('changed', self._update_search_tree)
+        self.search_entry.connect('key-press-event', self._handle_search_key_press)
+        self.pack_start(self.search_entry, False)
+
+        #make the tree model for holding blocks and a temporary one for search results
         self.treestore = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
+        self.treestore_search = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
+
         self.treeview = gtk.TreeView(self.treestore)
         self.treeview.set_enable_search(False) #disable pop up search box
+        self.treeview.set_search_column(-1) # really disable search
+        self.treeview.set_headers_visible(False)
         self.treeview.add_events(gtk.gdk.BUTTON_PRESS_MASK)
         self.treeview.connect('button-press-event', self._handle_mouse_button_press)
-        selection = self.treeview.get_selection()
-        selection.set_mode('single')
-        selection.connect('changed', self._handle_selection_change)
+        self.treeview.connect('key-press-event', self._handle_search_key_press)
+
+        self.treeview.get_selection().set_mode('single')
         renderer = gtk.CellRendererText()
         column = gtk.TreeViewColumn('Blocks', renderer, text=NAME_INDEX)
         self.treeview.append_column(column)
-        #setup the search
-        self.treeview.set_enable_search(True)
-        self.treeview.set_search_equal_func(self._handle_search)
         #try to enable the tooltips (available in pygtk 2.12 and above)
         try: self.treeview.set_tooltip_column(DOC_INDEX)
         except: pass
@@ -90,6 +103,7 @@ class BlockTreeWindow(gtk.VBox):
         self.pack_start(self.add_button, False)
         #map categories to iters, automatic mapping for root
         self._categories = {tuple(): None}
+        self._categories_search = {tuple(): None}
         #add blocks and categories
         self.platform.load_block_tree(self)
         #initialize
@@ -99,36 +113,38 @@ class BlockTreeWindow(gtk.VBox):
         self.treestore.clear();
         self._categories = {tuple(): None}
 
-
     ############################################################
     ## Block Tree Methods
     ############################################################
-    def add_block(self, category, block=None):
+    def add_block(self, category, block=None, treestore=None, categories=None):
         """
         Add a block with category to this selection window.
         Add only the category when block is None.
-        
+
         Args:
             category: the category list or path string
             block: the block object or None
         """
+        if treestore is None: treestore = self.treestore
+        if categories is None: categories = self._categories
+
         if isinstance(category, str): category = category.split('/')
         category = tuple(filter(lambda x: x, category)) #tuple is hashable
         #add category and all sub categories
         for i, cat_name in enumerate(category):
             sub_category = category[:i+1]
-            if sub_category not in self._categories:
-                iter = self.treestore.insert_before(self._categories[sub_category[:-1]], None)
-                self.treestore.set_value(iter, NAME_INDEX, '[ %s ]'%cat_name)
-                self.treestore.set_value(iter, KEY_INDEX, '')
-                self.treestore.set_value(iter, DOC_INDEX, Utils.parse_template(CAT_MARKUP_TMPL, cat=cat_name))
-                self._categories[sub_category] = iter
+            if sub_category not in categories:
+                iter = treestore.insert_before(categories[sub_category[:-1]], None)
+                treestore.set_value(iter, NAME_INDEX, '[ %s ]'%cat_name)
+                treestore.set_value(iter, KEY_INDEX, '')
+                treestore.set_value(iter, DOC_INDEX, Utils.parse_template(CAT_MARKUP_TMPL, cat=cat_name))
+                categories[sub_category] = iter
         #add block
         if block is None: return
-        iter = self.treestore.insert_before(self._categories[category], None)
-        self.treestore.set_value(iter, NAME_INDEX, block.get_name())
-        self.treestore.set_value(iter, KEY_INDEX, block.get_key())
-        self.treestore.set_value(iter, DOC_INDEX, Utils.parse_template(DOC_MARKUP_TMPL, doc=block.get_doc()))
+        iter = treestore.insert_before(categories[category], None)
+        treestore.set_value(iter, NAME_INDEX, block.get_name())
+        treestore.set_value(iter, KEY_INDEX, block.get_key())
+        treestore.set_value(iter, DOC_INDEX, Utils.parse_template(DOC_MARKUP_TMPL, doc=block.get_doc()))
 
     ############################################################
     ## Helper Methods
@@ -136,7 +152,7 @@ class BlockTreeWindow(gtk.VBox):
     def _get_selected_block_key(self):
         """
         Get the currently selected block key.
-        
+
         Returns:
             the key of the selected block or a empty string
         """
@@ -157,26 +173,69 @@ class BlockTreeWindow(gtk.VBox):
         Add the selected block with the given key to the flow graph.
         """
         key = self._get_selected_block_key()
-        if key: self.get_flow_graph().add_new_block(key)
+        if key:
+            self.get_flow_graph().add_new_block(key)
+            return True
+        return False
+
+    def _expand_category(self):
+        treestore, iter = self.treeview.get_selection().get_selected()
+        if iter and treestore.iter_has_child(iter):
+            path = self.treestore.get_path(iter)
+            self.treeview.expand_to_path(path)
 
     ############################################################
     ## Event Handlers
     ############################################################
-    def _handle_search(self, model, column, key, iter):
-        #determine which blocks match the search key
-        blocks = self.get_flow_graph().get_parent().get_blocks()
-        matching_blocks = filter(lambda b: key in b.get_key() or key in b.get_name().lower(), blocks)
-        #remove the old search category
-        try: self.treestore.remove(self._categories.pop((self._search_category, )))
-        except (KeyError, AttributeError): pass #nothing to remove
-        #create a search category
-        if not matching_blocks: return
-        self._search_category = 'Search: %s'%key
-        for block in matching_blocks: self.add_block(self._search_category, block)
-        #expand the search category
-        path = self.treestore.get_path(self._categories[(self._search_category, )])
-        self.treeview.collapse_all()
-        self.treeview.expand_row(path, open_all=False)
+    def _handle_icon_event(self, widget, icon, event):
+        if icon == gtk.ENTRY_ICON_PRIMARY:
+            pass
+        elif icon == gtk.ENTRY_ICON_SECONDARY:
+            widget.set_text('')
+
+    def _update_search_tree(self, widget):
+        key = widget.get_text().lower()
+        if not key:
+            self.treeview.set_model(self.treestore)
+            self.treeview.collapse_all()
+        else:
+            blocks = self.get_flow_graph().get_parent().get_blocks()
+            matching_blocks = filter(lambda b: key in b.get_key().lower() or key in b.get_name().lower(), blocks)
+
+            self.treestore_search.clear()
+            self._categories_search = {tuple(): None}
+            for block in matching_blocks:
+                self.add_block(block.get_category() or 'None', block,
+                               self.treestore_search, self._categories_search)
+            self.treeview.set_model(self.treestore_search)
+            self.treeview.expand_all()
+
+    def _handle_search_key_press(self, widget, event):
+        """Handle Return and Escape key events in search entry and treeview"""
+        if event.keyval == gtk.keysyms.Return:
+            # add block on enter
+
+            if widget == self.search_entry:
+                #  Get the first block in the search tree and add it
+                selected = self.treestore_search.get_iter_first()
+                while self.treestore_search.iter_children(selected):
+                    selected = self.treestore_search.iter_children(selected)
+                if selected is not None:
+                    key = self.treestore_search.get_value(selected, KEY_INDEX)
+                    if key: self.get_flow_graph().add_new_block(key)
+            elif widget == self.treeview:
+                self._add_selected_block() or self._expand_category()
+            else:
+                return False  # propagate event
+
+        elif event.keyval == gtk.keysyms.Escape:
+            # reset the search
+            self.search_entry.set_text('')
+
+        else:
+            return False # propagate event
+
+        return True
 
     def _handle_drag_get_data(self, widget, drag_context, selection_data, info, time):
         """
@@ -195,13 +254,6 @@ class BlockTreeWindow(gtk.VBox):
         if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
             self._add_selected_block()
 
-    def _handle_selection_change(self, selection):
-        """
-        Handle a selection change in the tree view.
-        If a selection changes, set the add button sensitive.
-        """
-        self._update_add_button()
-
     def _handle_add_button(self, widget):
         """
         Handle the add button clicked signal.
-- 
cgit v1.2.3