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