new preferences
[debian/gnuradio] / grc / src / gui / ActionHandler.py
1 """
2 Copyright 2007 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 import os
21 import signal 
22 from Constants import DIR_LEFT, DIR_RIGHT, IMAGE_FILE_EXTENSION
23 import Actions
24 import pygtk
25 pygtk.require('2.0')
26 import gtk
27 import gobject
28 import Preferences
29 from threading import Thread
30 import Messages
31 from .. utils import ParseXML
32 import random
33 from .. platforms.gui.Platform import Platform
34 from MainWindow import MainWindow
35 from Dialogs import AboutDialog
36 from FileDialogs import OpenFlowGraphFileDialog, SaveFlowGraphFileDialog, SaveImageFileDialog
37
38 gobject.threads_init()
39
40 class ActionHandler:
41         """
42         The action handler will setup all the major window components,
43         and handle button presses and flow graph operations from the GUI.
44         """
45
46         def __init__(self, file_paths, platform):
47                 """
48                 ActionHandler constructor.
49                 Create the main window, setup the message handler, import the preferences,
50                 and connect all of the action handlers. Finally, enter the gtk main loop and block.
51                 @param file_paths a list of flow graph file passed from command line
52                 @param platform platform module
53                 """
54                 self.clipboard = None
55                 platform = Platform(platform)
56                 #setup icon using icon theme
57                 try: gtk.window_set_default_icon(gtk.IconTheme().load_icon('gnuradio-grc', 256, 0))
58                 except: pass
59                 for action in Actions.get_all_actions(): action.connect('activate', self._handle_actions)
60                 #setup the main window
61                 self.main_window = MainWindow(self.handle_states, platform)
62                 self.main_window.connect('delete_event', self._quit)
63                 self.main_window.connect('key_press_event', self._handle_key_press)
64                 self.get_page = self.main_window.get_page
65                 self.get_flow_graph = self.main_window.get_flow_graph
66                 self.get_focus_flag = self.main_window.drawing_area.get_focus_flag
67                 #setup the messages
68                 Messages.register_messenger(self.main_window.add_report_line)
69                 Messages.send_init()
70                 #initialize
71                 self.init_file_paths = file_paths
72                 self.handle_states(Actions.APPLICATION_INITIALIZE)
73                 #enter the mainloop
74                 gtk.main()
75
76         def _handle_key_press(self, widget, event):
77                 """
78                 Handle key presses from the keyboard and translate key combos into actions.
79                 This key press handler is called before the gtk accelerators kick in.
80                 This handler ensures that key presses without a mod mask,
81                 only pass to the accelerators if the flow graph is in focus.
82                 This function also handles keys that accelerators refuse to handle: left/right,
83                 and keys that are not registered with an accelerator: +/-.
84                 @return false to let the accelerators handle the key action
85                 """
86                 if self.get_focus_flag():
87                         try:
88                                 self.handle_states({
89                                         'Left': Actions.BLOCK_ROTATE_LEFT,
90                                         'Right': Actions.BLOCK_ROTATE_RIGHT,
91                                         'Up': Actions.BLOCK_DEC_TYPE,
92                                         'Down': Actions.BLOCK_INC_TYPE,
93                                         'equal': Actions.PORT_CONTROLLER_INC,
94                                         'plus': Actions.PORT_CONTROLLER_INC,
95                                         'KP_Add': Actions.PORT_CONTROLLER_INC,
96                                         'minus': Actions.PORT_CONTROLLER_DEC,
97                                         'KP_Subtract': Actions.PORT_CONTROLLER_DEC,
98                                 }[gtk.gdk.keyval_name(event.keyval)])
99                                 return True
100                         #focus: always return false for accelerator to handle
101                         except: return False
102                 #no focus: only allow accelerator to handle when a mod is used
103                 return not event.state
104
105         def _quit(self, window, event):
106                 """
107                 Handle the delete event from the main window.
108                 Generated by pressing X to close, alt+f4, or right click+close.
109                 This method in turns calls the state handler to quit.
110                 @return true
111                 """
112                 self.handle_states(Actions.APPLICATION_QUIT)
113                 return True
114
115         def _handle_actions(self, event):
116                 """
117                 Handle all of the activate signals from the gtk actions.
118                 The action signals derive from clicking on a toolbar or menu bar button.
119                 Forward the action to the state handler.
120                 """
121                 self.handle_states(event.get_name())
122
123         def handle_states(self, state=''):
124                 """
125                 Handle the state changes in the GUI.
126                 Handle all of the state changes that arise from the action handler or other gui and
127                 inputs in the application. The state passed to the handle_states method is a string descriping
128                 the change. A series of if/elif statements handle the state by greying out action buttons, causing
129                 changes in the flow graph, saving/opening files... The handle_states method is passed to the
130                 contructors of many of the classes used in this application enabling them to report any state change.
131                 @param state a string describing the state change
132                 """
133                 #print state
134                 ##################################################
135                 # Initalize/Quit
136                 ##################################################
137                 if state == Actions.APPLICATION_INITIALIZE:
138                         for action in Actions.get_all_actions(): action.set_sensitive(False) #set all actions disabled
139                         # enable a select few actions
140                         for action in (
141                                 Actions.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW,
142                                 Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS,
143                                 Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY,
144                                 Actions.FLOW_GRAPH_SCREEN_CAPTURE,
145                         ): Actions.get_action_from_name(action).set_sensitive(True)
146                         if not self.init_file_paths:
147                                 self.init_file_paths = Preferences.files_open()
148                         if not self.init_file_paths: self.init_file_paths = ['']
149                         for file_path in self.init_file_paths:
150                                 if file_path: self.main_window.new_page(file_path) #load pages from file paths
151                         if Preferences.file_open() in self.init_file_paths:
152                                 self.main_window.new_page(Preferences.file_open(), show=True)
153                         if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists
154                 elif state == Actions.APPLICATION_QUIT:
155                         if self.main_window.close_pages():
156                                 gtk.main_quit()
157                                 exit(0)
158                 ##################################################
159                 # Selections
160                 ##################################################
161                 elif state == Actions.ELEMENT_SELECT:
162                         self.get_flow_graph().update()
163                 elif state == Actions.NOTHING_SELECT:
164                         self.get_flow_graph().unselect()
165                         self.get_flow_graph().update()
166                 ##################################################
167                 # Enable/Disable
168                 ##################################################
169                 elif state == Actions.BLOCK_ENABLE:
170                         if self.get_flow_graph().enable_selected(True):
171                                 self.get_flow_graph().update()
172                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
173                                 self.get_page().set_saved(False)
174                 elif state == Actions.BLOCK_DISABLE:
175                         if self.get_flow_graph().enable_selected(False):
176                                 self.get_flow_graph().update()
177                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
178                                 self.get_page().set_saved(False)
179                 ##################################################
180                 # Cut/Copy/Paste
181                 ##################################################
182                 elif state == Actions.BLOCK_CUT:
183                         self.handle_states(BLOCK_COPY)
184                         self.handle_states(ELEMENT_DELETE)
185                 elif state == Actions.BLOCK_COPY:
186                         self.clipboard = self.get_flow_graph().copy_to_clipboard()
187                 elif state == Actions.BLOCK_PASTE:
188                         if self.clipboard:
189                                 self.get_flow_graph().paste_from_clipboard(self.clipboard)
190                                 self.get_flow_graph().update()
191                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
192                                 self.get_page().set_saved(False)
193                 ##################################################
194                 # Move/Rotate/Delete/Create
195                 ##################################################
196                 elif state == Actions.BLOCK_MOVE:
197                         self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
198                         self.get_page().set_saved(False)
199                 elif state == Actions.BLOCK_ROTATE_LEFT:
200                         if self.get_flow_graph().rotate_selected(DIR_LEFT):
201                                 self.get_flow_graph().update()
202                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
203                                 self.get_page().set_saved(False)
204                 elif state == Actions.BLOCK_ROTATE_RIGHT:
205                         if self.get_flow_graph().rotate_selected(DIR_RIGHT):
206                                 self.get_flow_graph().update()
207                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
208                                 self.get_page().set_saved(False)
209                 elif state == Actions.ELEMENT_DELETE:
210                         if self.get_flow_graph().remove_selected():
211                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
212                                 self.handle_states(Actions.NOTHING_SELECT)
213                                 self.get_page().set_saved(False)
214                 elif state == Actions.ELEMENT_CREATE:
215                         self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
216                         self.handle_states(Actions.NOTHING_SELECT)
217                         self.get_page().set_saved(False)
218                 elif state == Actions.BLOCK_INC_TYPE:
219                         if self.get_flow_graph().type_controller_modify_selected(1):
220                                 self.get_flow_graph().update()
221                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
222                                 self.get_page().set_saved(False)
223                 elif state == Actions.BLOCK_DEC_TYPE:
224                         if self.get_flow_graph().type_controller_modify_selected(-1):
225                                 self.get_flow_graph().update()
226                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
227                                 self.get_page().set_saved(False)
228                 elif state == Actions.PORT_CONTROLLER_INC:
229                         if self.get_flow_graph().port_controller_modify_selected(1):
230                                 self.get_flow_graph().update()
231                                 self.get_flow_graph().update() #2 times
232                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
233                                 self.get_page().set_saved(False)
234                 elif state == Actions.PORT_CONTROLLER_DEC:
235                         if self.get_flow_graph().port_controller_modify_selected(-1):
236                                 self.get_flow_graph().update()
237                                 self.get_flow_graph().update() #2 times
238                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
239                                 self.get_page().set_saved(False)
240                 ##################################################
241                 # Window stuff
242                 ##################################################
243                 elif state == Actions.ABOUT_WINDOW_DISPLAY:
244                         AboutDialog()
245                 ##################################################
246                 # Param Modifications
247                 ##################################################
248                 elif state == Actions.BLOCK_PARAM_MODIFY:
249                         if self.get_flow_graph().param_modify_selected():
250                                 self.get_flow_graph().update()
251                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
252                                 self.get_page().set_saved(False)
253                 ##################################################
254                 # Undo/Redo
255                 ##################################################
256                 elif state == Actions.FLOW_GRAPH_UNDO:
257                         n = self.get_page().get_state_cache().get_prev_state()
258                         if n:
259                                 self.get_flow_graph().unselect()
260                                 self.get_flow_graph().import_data(n)
261                                 self.get_flow_graph().update()
262                                 self.get_page().set_saved(False)
263                 elif state == Actions.FLOW_GRAPH_REDO:
264                         n = self.get_page().get_state_cache().get_next_state()
265                         if n:
266                                 self.get_flow_graph().unselect()
267                                 self.get_flow_graph().import_data(n)
268                                 self.get_flow_graph().update()
269                                 self.get_page().set_saved(False)
270                 ##################################################
271                 # New/Open/Save/Close
272                 ##################################################
273                 elif state == Actions.FLOW_GRAPH_NEW:
274                         self.main_window.new_page()
275                 elif state == Actions.FLOW_GRAPH_OPEN:
276                         file_paths = OpenFlowGraphFileDialog(self.get_page().get_file_path()).run()
277                         if file_paths: #open a new page for each file, show only the first
278                                 for i,file_path in enumerate(file_paths):
279                                         self.main_window.new_page(file_path, show=(i==0))
280                 elif state == Actions.FLOW_GRAPH_CLOSE:
281                         self.main_window.close_page()
282                 elif state == Actions.FLOW_GRAPH_SAVE:
283                         #read-only or undefined file path, do save-as
284                         if self.get_page().get_read_only() or not self.get_page().get_file_path():
285                                 self.handle_states(Actions.FLOW_GRAPH_SAVE_AS)
286                         #otherwise try to save
287                         else:
288                                 try:
289                                         ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
290                                         self.get_page().set_saved(True)
291                                 except IOError:
292                                         Messages.send_fail_save(self.get_page().get_file_path())
293                                         self.get_page().set_saved(False)
294                 elif state == Actions.FLOW_GRAPH_SAVE_AS:
295                         file_path = SaveFlowGraphFileDialog(self.get_page().get_file_path()).run()
296                         if file_path is not None:
297                                 self.get_page().set_file_path(file_path)
298                                 self.handle_states(Actions.FLOW_GRAPH_SAVE)
299                 elif state == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
300                         file_path = SaveImageFileDialog(self.get_page().get_file_path()).run()
301                         if file_path is not None:
302                                 pixmap = self.get_flow_graph().get_drawing_area().pixmap
303                                 width, height = pixmap.get_size()
304                                 pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, width, height)
305                                 pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, width, height)
306                                 pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:])
307                 ##################################################
308                 # Gen/Exec/Stop
309                 ##################################################
310                 elif state == Actions.FLOW_GRAPH_GEN:
311                         if not self.get_page().get_pid():
312                                 if not self.get_page().get_saved() or not self.get_page().get_file_path():
313                                         self.handle_states(Actions.FLOW_GRAPH_SAVE) #only save if file path missing or not saved
314                                 if self.get_page().get_saved() and self.get_page().get_file_path():
315                                         generator = self.get_page().get_generator()
316                                         try:
317                                                 Messages.send_start_gen(generator.get_file_path())
318                                                 generator.write()
319                                         except Exception,e: Messages.send_fail_gen(e)
320                                 else: self.generator = None
321                 elif state == Actions.FLOW_GRAPH_EXEC:
322                         if not self.get_page().get_pid():
323                                 self.handle_states(Actions.FLOW_GRAPH_GEN)
324                                 if self.get_page().get_saved() and self.get_page().get_file_path():
325                                         ExecFlowGraphThread(self)
326                 elif state == Actions.FLOW_GRAPH_KILL:
327                         if self.get_page().get_pid():
328                                 try: os.kill(self.get_page().get_pid(), signal.SIGKILL)
329                                 except: print "could not kill pid: %s"%self.get_page().get_pid()
330                 elif state == '': #pass and run the global actions
331                         pass
332                 else: print '!!! State "%s" not handled !!!'%state
333                 ##################################################
334                 # Global Actions for all States
335                 ##################################################
336                 #update general buttons
337                 Actions.get_action_from_name(Actions.ELEMENT_DELETE).set_sensitive(bool(self.get_flow_graph().get_selected_elements()))
338                 Actions.get_action_from_name(Actions.BLOCK_PARAM_MODIFY).set_sensitive(bool(self.get_flow_graph().get_selected_block()))
339                 Actions.get_action_from_name(Actions.BLOCK_ROTATE_RIGHT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
340                 Actions.get_action_from_name(Actions.BLOCK_ROTATE_LEFT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
341                 #update cut/copy/paste
342                 Actions.get_action_from_name(Actions.BLOCK_CUT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
343                 Actions.get_action_from_name(Actions.BLOCK_COPY).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
344                 Actions.get_action_from_name(Actions.BLOCK_PASTE).set_sensitive(bool(self.clipboard))
345                 #update enable/disable
346                 Actions.get_action_from_name(Actions.BLOCK_ENABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
347                 Actions.get_action_from_name(Actions.BLOCK_DISABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
348                 #set the exec and stop buttons
349                 self.update_exec_stop()
350                 #saved status
351                 Actions.get_action_from_name(Actions.FLOW_GRAPH_SAVE).set_sensitive(not self.get_page().get_saved())
352                 self.main_window.update()
353                 #draw the flow graph
354                 self.get_flow_graph().draw()
355
356         def update_exec_stop(self):
357                 """
358                 Update the exec and stop buttons.
359                 Lock and unlock the mutex for race conditions with exec flow graph threads.
360                 """
361                 sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid()
362                 Actions.get_action_from_name(Actions.FLOW_GRAPH_GEN).set_sensitive(sensitive)
363                 Actions.get_action_from_name(Actions.FLOW_GRAPH_EXEC).set_sensitive(sensitive)
364                 Actions.get_action_from_name(Actions.FLOW_GRAPH_KILL).set_sensitive(self.get_page().get_pid() != None)
365
366 class ExecFlowGraphThread(Thread):
367         """Execute the flow graph as a new process and wait on it to finish."""
368
369         def __init__ (self, action_handler):
370                 """
371                 ExecFlowGraphThread constructor.
372                 @param action_handler an instance of an ActionHandler
373                 """
374                 Thread.__init__(self)
375                 self.update_exec_stop = action_handler.update_exec_stop
376                 self.flow_graph = action_handler.get_flow_graph()
377                 #store page and dont use main window calls in run
378                 self.page = action_handler.get_page()
379                 Messages.send_start_exec(self.page.get_generator().get_file_path())
380                 #get the popen
381                 try:
382                         self.p = self.page.get_generator().get_popen()
383                         self.page.set_pid(self.p.pid)
384                         #update
385                         self.update_exec_stop()
386                         self.start()
387                 except Exception, e:
388                         Messages.send_verbose_exec(str(e))
389                         Messages.send_end_exec()
390
391         def run(self):
392                 """
393                 Wait on the executing process by reading from its stdout.
394                 Use gobject.idle_add when calling functions that modify gtk objects.
395                 """
396                 #handle completion
397                 r = "\n"
398                 while(r):
399                         gobject.idle_add(Messages.send_verbose_exec, r)
400                         r = os.read(self.p.stdout.fileno(), 1024)
401                 gobject.idle_add(self.done)
402
403         def done(self):
404                 """Perform end of execution tasks."""
405                 Messages.send_end_exec()
406                 self.page.set_pid(None)
407                 self.update_exec_stop()