Created skeleton wxgui term window component
[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                 return Actions.handle_key_press(event)
85
86         def _quit(self, window, event):
87                 """
88                 Handle the delete event from the main window.
89                 Generated by pressing X to close, alt+f4, or right click+close.
90                 This method in turns calls the state handler to quit.
91                 @return true
92                 """
93                 Actions.APPLICATION_QUIT()
94                 return True
95
96         def _handle_action(self, action):
97                 #print action
98                 ##################################################
99                 # Initalize/Quit
100                 ##################################################
101                 if action == Actions.APPLICATION_INITIALIZE:
102                         for action in Actions.get_all_actions(): action.set_sensitive(False) #set all actions disabled
103                         #enable a select few actions
104                         for action in (
105                                 Actions.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW,
106                                 Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS,
107                                 Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY,
108                                 Actions.FLOW_GRAPH_SCREEN_CAPTURE, Actions.HELP_WINDOW_DISPLAY,
109                                 Actions.TYPES_WINDOW_DISPLAY,
110                         ): action.set_sensitive(True)
111                         if not self.init_file_paths:
112                                 self.init_file_paths = Preferences.files_open()
113                         if not self.init_file_paths: self.init_file_paths = ['']
114                         for file_path in self.init_file_paths:
115                                 if file_path: self.main_window.new_page(file_path) #load pages from file paths
116                         if Preferences.file_open() in self.init_file_paths:
117                                 self.main_window.new_page(Preferences.file_open(), show=True)
118                         if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists
119                 elif action == Actions.APPLICATION_QUIT:
120                         if self.main_window.close_pages():
121                                 gtk.main_quit()
122                                 exit(0)
123                 ##################################################
124                 # Selections
125                 ##################################################
126                 elif action == Actions.ELEMENT_SELECT:
127                         pass #do nothing, update routines below
128                 elif action == Actions.NOTHING_SELECT:
129                         self.get_flow_graph().unselect()
130                 ##################################################
131                 # Enable/Disable
132                 ##################################################
133                 elif action == Actions.BLOCK_ENABLE:
134                         if self.get_flow_graph().enable_selected(True):
135                                 self.get_flow_graph().update()
136                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
137                                 self.get_page().set_saved(False)
138                 elif action == Actions.BLOCK_DISABLE:
139                         if self.get_flow_graph().enable_selected(False):
140                                 self.get_flow_graph().update()
141                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
142                                 self.get_page().set_saved(False)
143                 ##################################################
144                 # Cut/Copy/Paste
145                 ##################################################
146                 elif action == Actions.BLOCK_CUT:
147                         Actions.BLOCK_COPY()
148                         Actions.ELEMENT_DELETE()
149                 elif action == Actions.BLOCK_COPY:
150                         self.clipboard = self.get_flow_graph().copy_to_clipboard()
151                 elif action == Actions.BLOCK_PASTE:
152                         if self.clipboard:
153                                 self.get_flow_graph().paste_from_clipboard(self.clipboard)
154                                 self.get_flow_graph().update()
155                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
156                                 self.get_page().set_saved(False)
157                 ##################################################
158                 # Move/Rotate/Delete/Create
159                 ##################################################
160                 elif action == Actions.BLOCK_MOVE:
161                         self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
162                         self.get_page().set_saved(False)
163                 elif action == Actions.BLOCK_ROTATE_CCW:
164                         if self.get_flow_graph().rotate_selected(90):
165                                 self.get_flow_graph().update()
166                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
167                                 self.get_page().set_saved(False)
168                 elif action == Actions.BLOCK_ROTATE_CW:
169                         if self.get_flow_graph().rotate_selected(-90):
170                                 self.get_flow_graph().update()
171                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
172                                 self.get_page().set_saved(False)
173                 elif action == Actions.ELEMENT_DELETE:
174                         if self.get_flow_graph().remove_selected():
175                                 self.get_flow_graph().update()
176                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
177                                 Actions.NOTHING_SELECT()
178                                 self.get_page().set_saved(False)
179                 elif action == Actions.ELEMENT_CREATE:
180                         self.get_flow_graph().update()
181                         self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
182                         Actions.NOTHING_SELECT()
183                         self.get_page().set_saved(False)
184                 elif action == Actions.BLOCK_INC_TYPE:
185                         if self.get_flow_graph().type_controller_modify_selected(1):
186                                 self.get_flow_graph().update()
187                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
188                                 self.get_page().set_saved(False)
189                 elif action == Actions.BLOCK_DEC_TYPE:
190                         if self.get_flow_graph().type_controller_modify_selected(-1):
191                                 self.get_flow_graph().update()
192                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
193                                 self.get_page().set_saved(False)
194                 elif action == Actions.PORT_CONTROLLER_INC:
195                         if self.get_flow_graph().port_controller_modify_selected(1):
196                                 self.get_flow_graph().update()
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 action == Actions.PORT_CONTROLLER_DEC:
200                         if self.get_flow_graph().port_controller_modify_selected(-1):
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                 ##################################################
205                 # Window stuff
206                 ##################################################
207                 elif action == Actions.ABOUT_WINDOW_DISPLAY:
208                         Dialogs.AboutDialog(self.get_flow_graph().get_parent())
209                 elif action == Actions.HELP_WINDOW_DISPLAY:
210                         Dialogs.HelpDialog()
211                 elif action == Actions.TYPES_WINDOW_DISPLAY:
212                         Dialogs.TypesDialog(self.get_flow_graph().get_parent())
213                 elif action == Actions.ERRORS_WINDOW_DISPLAY:
214                         Dialogs.ErrorsDialog(self.get_flow_graph())
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.ERRORS_WINDOW_DISPLAY.set_sensitive(not self.get_flow_graph().is_valid())
313                 Actions.ELEMENT_DELETE.set_sensitive(bool(self.get_flow_graph().get_selected_elements()))
314                 Actions.BLOCK_PARAM_MODIFY.set_sensitive(bool(self.get_flow_graph().get_selected_block()))
315                 Actions.BLOCK_ROTATE_CCW.set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
316                 Actions.BLOCK_ROTATE_CW.set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
317                 #update cut/copy/paste
318                 Actions.BLOCK_CUT.set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
319                 Actions.BLOCK_COPY.set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
320                 Actions.BLOCK_PASTE.set_sensitive(bool(self.clipboard))
321                 #update enable/disable
322                 Actions.BLOCK_ENABLE.set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
323                 Actions.BLOCK_DISABLE.set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
324                 #set the exec and stop buttons
325                 self.update_exec_stop()
326                 #saved status
327                 Actions.FLOW_GRAPH_SAVE.set_sensitive(not self.get_page().get_saved())
328                 self.main_window.update()
329                 try: #set the size of the flow graph area (if changed)
330                         new_size = self.get_flow_graph().get_option('window_size')
331                         if self.get_flow_graph().get_size() != tuple(new_size):
332                                 self.get_flow_graph().set_size(*new_size)
333                 except: pass
334                 #draw the flow graph
335                 self.get_flow_graph().update_selected()
336                 self.get_flow_graph().queue_draw()
337                 return True #action was handled
338
339         def update_exec_stop(self):
340                 """
341                 Update the exec and stop buttons.
342                 Lock and unlock the mutex for race conditions with exec flow graph threads.
343                 """
344                 sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid()
345                 Actions.FLOW_GRAPH_GEN.set_sensitive(sensitive)
346                 Actions.FLOW_GRAPH_EXEC.set_sensitive(sensitive)
347                 Actions.FLOW_GRAPH_KILL.set_sensitive(self.get_page().get_pid() != None)
348
349 class ExecFlowGraphThread(Thread):
350         """Execute the flow graph as a new process and wait on it to finish."""
351
352         def __init__ (self, action_handler):
353                 """
354                 ExecFlowGraphThread constructor.
355                 @param action_handler an instance of an ActionHandler
356                 """
357                 Thread.__init__(self)
358                 self.update_exec_stop = action_handler.update_exec_stop
359                 self.flow_graph = action_handler.get_flow_graph()
360                 #store page and dont use main window calls in run
361                 self.page = action_handler.get_page()
362                 Messages.send_start_exec(self.page.get_generator().get_file_path())
363                 #get the popen
364                 try:
365                         self.p = self.page.get_generator().get_popen()
366                         self.page.set_pid(self.p.pid)
367                         #update
368                         self.update_exec_stop()
369                         self.start()
370                 except Exception, e:
371                         Messages.send_verbose_exec(str(e))
372                         Messages.send_end_exec()
373
374         def run(self):
375                 """
376                 Wait on the executing process by reading from its stdout.
377                 Use gobject.idle_add when calling functions that modify gtk objects.
378                 """
379                 #handle completion
380                 r = "\n"
381                 while(r):
382                         gobject.idle_add(Messages.send_verbose_exec, r)
383                         r = os.read(self.p.stdout.fileno(), 1024)
384                 gobject.idle_add(self.done)
385
386         def done(self):
387                 """Perform end of execution tasks."""
388                 Messages.send_end_exec()
389                 self.page.set_pid(None)
390                 self.update_exec_stop()