Reworked actions api and actions objects:
[debian/gnuradio] / grc / gui / ActionHandler.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 import os
21 import signal 
22 from Constants import 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 .. base import ParseXML
32 import random
33 from MainWindow import MainWindow
34 from PropsDialog import PropsDialog
35 import Dialogs
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                 for action in Actions.get_all_actions(): action.connect('activate', self._handle_action)
56                 #setup the main window
57                 self.main_window = MainWindow(platform)
58                 self.main_window.connect('delete-event', self._quit)
59                 self.main_window.connect('key-press-event', self._handle_key_press)
60                 self.get_page = self.main_window.get_page
61                 self.get_flow_graph = self.main_window.get_flow_graph
62                 self.get_focus_flag = self.main_window.get_focus_flag
63                 #setup the messages
64                 Messages.register_messenger(self.main_window.add_report_line)
65                 Messages.send_init(platform)
66                 #initialize
67                 self.init_file_paths = file_paths
68                 Actions.APPLICATION_INITIALIZE()
69                 #enter the mainloop
70                 gtk.main()
71
72         def _handle_key_press(self, widget, event):
73                 """
74                 Handle key presses from the keyboard and translate key combinations into actions.
75                 This key press handler is called prior to the gtk key press handler.
76                 This handler bypasses built in accelerator key handling when in focus because
77                 * some keys are ignored by the accelerators like the direction keys,
78                 * some keys are not registered to any accelerators but are still used.
79                 When not in focus, gtk and the accelerators handle the the key press.
80                 @return false to let gtk handle the key action
81                 """
82                 try: assert self.get_focus_flag()
83                 except AssertionError: return False
84                 try: Actions.get_action_from_key_press(event)()
85                 except KeyError: return False
86                 return True #handled by this method
87
88         def _quit(self, window, event):
89                 """
90                 Handle the delete event from the main window.
91                 Generated by pressing X to close, alt+f4, or right click+close.
92                 This method in turns calls the state handler to quit.
93                 @return true
94                 """
95                 Actions.APPLICATION_QUIT()
96                 return True
97
98         def _handle_action(self, action):
99                 #print action
100                 ##################################################
101                 # Initalize/Quit
102                 ##################################################
103                 if action == Actions.APPLICATION_INITIALIZE:
104                         for action in Actions.get_all_actions(): action.set_sensitive(False) #set all actions disabled
105                         #enable a select few actions
106                         for action in (
107                                 Actions.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW,
108                                 Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS,
109                                 Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY,
110                                 Actions.FLOW_GRAPH_SCREEN_CAPTURE, Actions.HELP_WINDOW_DISPLAY,
111                                 Actions.TYPES_WINDOW_DISPLAY,
112                         ): action.set_sensitive(True)
113                         if not self.init_file_paths:
114                                 self.init_file_paths = Preferences.files_open()
115                         if not self.init_file_paths: self.init_file_paths = ['']
116                         for file_path in self.init_file_paths:
117                                 if file_path: self.main_window.new_page(file_path) #load pages from file paths
118                         if Preferences.file_open() in self.init_file_paths:
119                                 self.main_window.new_page(Preferences.file_open(), show=True)
120                         if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists
121                 elif action == Actions.APPLICATION_QUIT:
122                         if self.main_window.close_pages():
123                                 gtk.main_quit()
124                                 exit(0)
125                 ##################################################
126                 # Selections
127                 ##################################################
128                 elif action == Actions.ELEMENT_SELECT:
129                         pass #do nothing, update routines below
130                 elif action == Actions.NOTHING_SELECT:
131                         self.get_flow_graph().unselect()
132                 ##################################################
133                 # Enable/Disable
134                 ##################################################
135                 elif action == Actions.BLOCK_ENABLE:
136                         if self.get_flow_graph().enable_selected(True):
137                                 self.get_flow_graph().update()
138                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
139                                 self.get_page().set_saved(False)
140                 elif action == Actions.BLOCK_DISABLE:
141                         if self.get_flow_graph().enable_selected(False):
142                                 self.get_flow_graph().update()
143                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
144                                 self.get_page().set_saved(False)
145                 ##################################################
146                 # Cut/Copy/Paste
147                 ##################################################
148                 elif action == Actions.BLOCK_CUT:
149                         Actions.BLOCK_COPY()
150                         Actions.ELEMENT_DELETE()
151                 elif action == Actions.BLOCK_COPY:
152                         self.clipboard = self.get_flow_graph().copy_to_clipboard()
153                 elif action == Actions.BLOCK_PASTE:
154                         if self.clipboard:
155                                 self.get_flow_graph().paste_from_clipboard(self.clipboard)
156                                 self.get_flow_graph().update()
157                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
158                                 self.get_page().set_saved(False)
159                 ##################################################
160                 # Move/Rotate/Delete/Create
161                 ##################################################
162                 elif action == Actions.BLOCK_MOVE:
163                         self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
164                         self.get_page().set_saved(False)
165                 elif action == Actions.BLOCK_ROTATE_CCW:
166                         if self.get_flow_graph().rotate_selected(90):
167                                 self.get_flow_graph().update()
168                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
169                                 self.get_page().set_saved(False)
170                 elif action == Actions.BLOCK_ROTATE_CW:
171                         if self.get_flow_graph().rotate_selected(-90):
172                                 self.get_flow_graph().update()
173                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
174                                 self.get_page().set_saved(False)
175                 elif action == Actions.ELEMENT_DELETE:
176                         if self.get_flow_graph().remove_selected():
177                                 self.get_flow_graph().update()
178                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
179                                 Actions.NOTHING_SELECT()
180                                 self.get_page().set_saved(False)
181                 elif action == Actions.ELEMENT_CREATE:
182                         self.get_flow_graph().update()
183                         self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
184                         Actions.NOTHING_SELECT()
185                         self.get_page().set_saved(False)
186                 elif action == Actions.BLOCK_INC_TYPE:
187                         if self.get_flow_graph().type_controller_modify_selected(1):
188                                 self.get_flow_graph().update()
189                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
190                                 self.get_page().set_saved(False)
191                 elif action == Actions.BLOCK_DEC_TYPE:
192                         if self.get_flow_graph().type_controller_modify_selected(-1):
193                                 self.get_flow_graph().update()
194                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
195                                 self.get_page().set_saved(False)
196                 elif action == Actions.PORT_CONTROLLER_INC:
197                         if self.get_flow_graph().port_controller_modify_selected(1):
198                                 self.get_flow_graph().update()
199                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
200                                 self.get_page().set_saved(False)
201                 elif action == Actions.PORT_CONTROLLER_DEC:
202                         if self.get_flow_graph().port_controller_modify_selected(-1):
203                                 self.get_flow_graph().update()
204                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
205                                 self.get_page().set_saved(False)
206                 ##################################################
207                 # Window stuff
208                 ##################################################
209                 elif action == Actions.ABOUT_WINDOW_DISPLAY:
210                         Dialogs.AboutDialog(self.get_flow_graph().get_parent())
211                 elif action == Actions.HELP_WINDOW_DISPLAY:
212                         Dialogs.HelpDialog()
213                 elif action == Actions.TYPES_WINDOW_DISPLAY:
214                         Dialogs.TypesDialog(self.get_flow_graph().get_parent())
215                 ##################################################
216                 # Param Modifications
217                 ##################################################
218                 elif action == Actions.BLOCK_PARAM_MODIFY:
219                         selected_block = self.get_flow_graph().get_selected_block()
220                         if selected_block:
221                                 if PropsDialog(selected_block).run():
222                                         #save the new state
223                                         self.get_flow_graph().update()
224                                         self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
225                                         self.get_page().set_saved(False)
226                                 else:
227                                         #restore the current state
228                                         n = self.get_page().get_state_cache().get_current_state()
229                                         self.get_flow_graph().import_data(n)
230                                         self.get_flow_graph().update()
231                 ##################################################
232                 # Undo/Redo
233                 ##################################################
234                 elif action == Actions.FLOW_GRAPH_UNDO:
235                         n = self.get_page().get_state_cache().get_prev_state()
236                         if n:
237                                 self.get_flow_graph().unselect()
238                                 self.get_flow_graph().import_data(n)
239                                 self.get_flow_graph().update()
240                                 self.get_page().set_saved(False)
241                 elif action == Actions.FLOW_GRAPH_REDO:
242                         n = self.get_page().get_state_cache().get_next_state()
243                         if n:
244                                 self.get_flow_graph().unselect()
245                                 self.get_flow_graph().import_data(n)
246                                 self.get_flow_graph().update()
247                                 self.get_page().set_saved(False)
248                 ##################################################
249                 # New/Open/Save/Close
250                 ##################################################
251                 elif action == Actions.FLOW_GRAPH_NEW:
252                         self.main_window.new_page()
253                 elif action == Actions.FLOW_GRAPH_OPEN:
254                         file_paths = OpenFlowGraphFileDialog(self.get_page().get_file_path()).run()
255                         if file_paths: #open a new page for each file, show only the first
256                                 for i,file_path in enumerate(file_paths):
257                                         self.main_window.new_page(file_path, show=(i==0))
258                 elif action == Actions.FLOW_GRAPH_CLOSE:
259                         self.main_window.close_page()
260                 elif action == Actions.FLOW_GRAPH_SAVE:
261                         #read-only or undefined file path, do save-as
262                         if self.get_page().get_read_only() or not self.get_page().get_file_path():
263                                 Actions.FLOW_GRAPH_SAVE_AS()
264                         #otherwise try to save
265                         else:
266                                 try:
267                                         ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
268                                         self.get_page().set_saved(True)
269                                 except IOError:
270                                         Messages.send_fail_save(self.get_page().get_file_path())
271                                         self.get_page().set_saved(False)
272                 elif action == Actions.FLOW_GRAPH_SAVE_AS:
273                         file_path = SaveFlowGraphFileDialog(self.get_page().get_file_path()).run()
274                         if file_path is not None:
275                                 self.get_page().set_file_path(file_path)
276                                 Actions.FLOW_GRAPH_SAVE()
277                 elif action == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
278                         file_path = SaveImageFileDialog(self.get_page().get_file_path()).run()
279                         if file_path is not None:
280                                 pixbuf = self.get_flow_graph().get_drawing_area().get_pixbuf()
281                                 pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:])
282                 ##################################################
283                 # Gen/Exec/Stop
284                 ##################################################
285                 elif action == Actions.FLOW_GRAPH_GEN:
286                         if not self.get_page().get_pid():
287                                 if not self.get_page().get_saved() or not self.get_page().get_file_path():
288                                         Actions.FLOW_GRAPH_SAVE() #only save if file path missing or not saved
289                                 if self.get_page().get_saved() and self.get_page().get_file_path():
290                                         generator = self.get_page().get_generator()
291                                         try:
292                                                 Messages.send_start_gen(generator.get_file_path())
293                                                 generator.write()
294                                         except Exception,e: Messages.send_fail_gen(e)
295                                 else: self.generator = None
296                 elif action == Actions.FLOW_GRAPH_EXEC:
297                         if not self.get_page().get_pid():
298                                 Actions.FLOW_GRAPH_GEN()
299                                 if self.get_page().get_saved() and self.get_page().get_file_path():
300                                         ExecFlowGraphThread(self)
301                 elif action == Actions.FLOW_GRAPH_KILL:
302                         if self.get_page().get_pid():
303                                 try: os.kill(self.get_page().get_pid(), signal.SIGKILL)
304                                 except: print "could not kill pid: %s"%self.get_page().get_pid()
305                 elif action == Actions.PAGE_CHANGE: #pass and run the global actions
306                         pass
307                 else: print '!!! Action "%s" not handled !!!'%action
308                 ##################################################
309                 # Global Actions for all States
310                 ##################################################
311                 #update general buttons
312                 Actions.ELEMENT_DELETE.set_sensitive(bool(self.get_flow_graph().get_selected_elements()))
313                 Actions.BLOCK_PARAM_MODIFY.set_sensitive(bool(self.get_flow_graph().get_selected_block()))
314                 Actions.BLOCK_ROTATE_CCW.set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
315                 Actions.BLOCK_ROTATE_CW.set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
316                 #update cut/copy/paste
317                 Actions.BLOCK_CUT.set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
318                 Actions.BLOCK_COPY.set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
319                 Actions.BLOCK_PASTE.set_sensitive(bool(self.clipboard))
320                 #update enable/disable
321                 Actions.BLOCK_ENABLE.set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
322                 Actions.BLOCK_DISABLE.set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
323                 #set the exec and stop buttons
324                 self.update_exec_stop()
325                 #saved status
326                 Actions.FLOW_GRAPH_SAVE.set_sensitive(not self.get_page().get_saved())
327                 self.main_window.update()
328                 try: #set the size of the flow graph area (if changed)
329                         new_size = self.get_flow_graph().get_option('window_size')
330                         if self.get_flow_graph().get_size() != tuple(new_size):
331                                 self.get_flow_graph().set_size(*new_size)
332                 except: pass
333                 #draw the flow graph
334                 self.get_flow_graph().update_selected()
335                 self.get_flow_graph().queue_draw()
336                 return True #action was handled
337
338         def update_exec_stop(self):
339                 """
340                 Update the exec and stop buttons.
341                 Lock and unlock the mutex for race conditions with exec flow graph threads.
342                 """
343                 sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid()
344                 Actions.FLOW_GRAPH_GEN.set_sensitive(sensitive)
345                 Actions.FLOW_GRAPH_EXEC.set_sensitive(sensitive)
346                 Actions.FLOW_GRAPH_KILL.set_sensitive(self.get_page().get_pid() != None)
347
348 class ExecFlowGraphThread(Thread):
349         """Execute the flow graph as a new process and wait on it to finish."""
350
351         def __init__ (self, action_handler):
352                 """
353                 ExecFlowGraphThread constructor.
354                 @param action_handler an instance of an ActionHandler
355                 """
356                 Thread.__init__(self)
357                 self.update_exec_stop = action_handler.update_exec_stop
358                 self.flow_graph = action_handler.get_flow_graph()
359                 #store page and dont use main window calls in run
360                 self.page = action_handler.get_page()
361                 Messages.send_start_exec(self.page.get_generator().get_file_path())
362                 #get the popen
363                 try:
364                         self.p = self.page.get_generator().get_popen()
365                         self.page.set_pid(self.p.pid)
366                         #update
367                         self.update_exec_stop()
368                         self.start()
369                 except Exception, e:
370                         Messages.send_verbose_exec(str(e))
371                         Messages.send_end_exec()
372
373         def run(self):
374                 """
375                 Wait on the executing process by reading from its stdout.
376                 Use gobject.idle_add when calling functions that modify gtk objects.
377                 """
378                 #handle completion
379                 r = "\n"
380                 while(r):
381                         gobject.idle_add(Messages.send_verbose_exec, r)
382                         r = os.read(self.p.stdout.fileno(), 1024)
383                 gobject.idle_add(self.done)
384
385         def done(self):
386                 """Perform end of execution tasks."""
387                 Messages.send_end_exec()
388                 self.page.set_pid(None)
389                 self.update_exec_stop()