Removing PowerLine since we weren't using it and don't have plans to.
[debian/gnuradio] / grc / gui / BlockTreeWindow.py
1 """
2 Copyright 2007, 2008, 2009 Free Software Foundation, Inc.
3 This file is part of GNU Radio
4
5 GNU Radio Companion is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 GNU Radio Companion is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18 """
19
20 from Constants import DEFAULT_BLOCKS_WINDOW_WIDTH, DND_TARGETS
21 import Utils
22 import pygtk
23 pygtk.require('2.0')
24 import gtk
25 import gobject
26
27 NAME_INDEX = 0
28 KEY_INDEX = 1
29 DOC_INDEX = 2
30
31 DOC_MARKUP_TMPL="""\
32 #if $doc
33 $encode($doc)#slurp
34 #else
35 undocumented#slurp
36 #end if"""
37
38 CAT_MARKUP_TMPL="""Category: $cat"""
39
40 class BlockTreeWindow(gtk.VBox):
41         """The block selection panel."""
42
43         def __init__(self, platform, get_flow_graph):
44                 """
45                 BlockTreeWindow constructor.
46                 Create a tree view of the possible blocks in the platform.
47                 The tree view nodes will be category names, the leaves will be block names.
48                 A mouse double click or button press action will trigger the add block event.
49                 @param platform the particular platform will all block prototypes
50                 @param get_flow_graph get the selected flow graph
51                 """
52                 gtk.VBox.__init__(self)
53                 self.platform = platform
54                 self.get_flow_graph = get_flow_graph
55                 #make the tree model for holding blocks
56                 self.treestore = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
57                 self.treeview = gtk.TreeView(self.treestore)
58                 self.treeview.set_enable_search(False) #disable pop up search box
59                 self.treeview.add_events(gtk.gdk.BUTTON_PRESS_MASK)
60                 self.treeview.connect('button-press-event', self._handle_mouse_button_press)
61                 selection = self.treeview.get_selection()
62                 selection.set_mode('single')
63                 selection.connect('changed', self._handle_selection_change)
64                 renderer = gtk.CellRendererText()
65                 column = gtk.TreeViewColumn('Blocks', renderer, text=NAME_INDEX)
66                 self.treeview.append_column(column)
67                 #setup the search
68                 self.treeview.set_enable_search(True)
69                 self.treeview.set_search_equal_func(self._handle_search)
70                 #try to enable the tooltips (available in pygtk 2.12 and above) 
71                 try: self.treeview.set_tooltip_column(DOC_INDEX)
72                 except: pass
73                 #setup drag and drop
74                 self.treeview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, DND_TARGETS, gtk.gdk.ACTION_COPY)
75                 self.treeview.connect('drag-data-get', self._handle_drag_get_data)
76                 #make the scrolled window to hold the tree view
77                 scrolled_window = gtk.ScrolledWindow()
78                 scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
79                 scrolled_window.add_with_viewport(self.treeview)
80                 scrolled_window.set_size_request(DEFAULT_BLOCKS_WINDOW_WIDTH, -1)
81                 self.pack_start(scrolled_window)
82                 #add button
83                 self.add_button = gtk.Button(None, gtk.STOCK_ADD)
84                 self.add_button.connect('clicked', self._handle_add_button)
85                 self.pack_start(self.add_button, False)
86                 #map categories to iters, automatic mapping for root
87                 self._categories = {tuple(): None}
88                 #add blocks and categories
89                 self.platform.load_block_tree(self)
90                 #initialize
91                 self._update_add_button()
92
93         ############################################################
94         ## Block Tree Methods
95         ############################################################
96         def add_block(self, category, block=None):
97                 """
98                 Add a block with category to this selection window.
99                 Add only the category when block is None.
100                 @param category the category list or path string
101                 @param block the block object or None
102                 """
103                 if isinstance(category, str): category = category.split('/')
104                 category = tuple(filter(lambda x: x, category)) #tuple is hashable
105                 #add category and all sub categories
106                 for i, cat_name in enumerate(category):
107                         sub_category = category[:i+1]
108                         if sub_category not in self._categories:
109                                 iter = self.treestore.insert_before(self._categories[sub_category[:-1]], None)
110                                 self.treestore.set_value(iter, NAME_INDEX, '[ %s ]'%cat_name)
111                                 self.treestore.set_value(iter, KEY_INDEX, '')
112                                 self.treestore.set_value(iter, DOC_INDEX, Utils.parse_template(CAT_MARKUP_TMPL, cat=cat_name))
113                                 self._categories[sub_category] = iter
114                 #add block
115                 if block is None: return
116                 iter = self.treestore.insert_before(self._categories[category], None)
117                 self.treestore.set_value(iter, NAME_INDEX, block.get_name())
118                 self.treestore.set_value(iter, KEY_INDEX, block.get_key())
119                 self.treestore.set_value(iter, DOC_INDEX, Utils.parse_template(DOC_MARKUP_TMPL, doc=block.get_doc()))
120
121         ############################################################
122         ## Helper Methods
123         ############################################################
124         def _get_selected_block_key(self):
125                 """
126                 Get the currently selected block key.
127                 @return the key of the selected block or a empty string
128                 """
129                 selection = self.treeview.get_selection()
130                 treestore, iter = selection.get_selected()
131                 return iter and treestore.get_value(iter, KEY_INDEX) or ''
132
133         def _update_add_button(self):
134                 """
135                 Update the add button's sensitivity.
136                 The button should be active only if a block is selected.
137                 """
138                 key = self._get_selected_block_key()
139                 self.add_button.set_sensitive(bool(key))
140
141         def _add_selected_block(self):
142                 """
143                 Add the selected block with the given key to the flow graph.
144                 """
145                 key = self._get_selected_block_key()
146                 if key: self.get_flow_graph().add_new_block(key)
147
148         ############################################################
149         ## Event Handlers
150         ############################################################
151         def _handle_search(self, model, column, key, iter):
152                 #determine which blocks match the search key
153                 blocks = self.get_flow_graph().get_parent().get_blocks()
154                 matching_blocks = filter(lambda b: key in b.get_key() or key in b.get_name().lower(), blocks)
155                 #remove the old search category
156                 try: self.treestore.remove(self._categories.pop((self._search_category, )))
157                 except (KeyError, AttributeError): pass #nothing to remove
158                 #create a search category
159                 if not matching_blocks: return
160                 self._search_category = 'Search: %s'%key
161                 for block in matching_blocks: self.add_block(self._search_category, block)
162                 #expand the search category
163                 path = self.treestore.get_path(self._categories[(self._search_category, )])
164                 self.treeview.collapse_all()
165                 self.treeview.expand_row(path, open_all=False)
166
167         def _handle_drag_get_data(self, widget, drag_context, selection_data, info, time):
168                 """
169                 Handle a drag and drop by setting the key to the selection object.
170                 This will call the destination handler for drag and drop.
171                 Only call set when the key is valid to ignore DND from categories.
172                 """
173                 key = self._get_selected_block_key()
174                 if key: selection_data.set(selection_data.target, 8, key)
175
176         def _handle_mouse_button_press(self, widget, event):
177                 """
178                 Handle the mouse button press.
179                 If a left double click is detected, call add selected block.
180                 """
181                 if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
182                         self._add_selected_block()
183
184         def _handle_selection_change(self, selection):
185                 """
186                 Handle a selection change in the tree view.
187                 If a selection changes, set the add button sensitive.
188                 """
189                 self._update_add_button()
190
191         def _handle_add_button(self, widget):
192                 """
193                 Handle the add button clicked signal.
194                 Call add selected block.
195                 """
196                 self._add_selected_block()