Statistics
| Branch: | Tag: | Revision:

root / grc / gui / ActionHandler.py @ ab55c4a6

History | View | Annotate | Download (17.6 kB)

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_actions)
56
                #setup the main window
57
                self.main_window = MainWindow(self.handle_states, 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
                self.handle_states(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
                keyval, egroup, level, consumed = \
83
                        gtk.gdk.keymap_get_default().translate_keyboard_state(
84
                        event.hardware_keycode, event.state, event.group)
85
                #extract action name from this key press
86
                action_name = Actions.get_action_name_from_key_press(keyval, event.state & ~consumed)
87
                #handle the action if flow graph is in focus
88
                if action_name and self.get_focus_flag():
89
                        self.handle_states(action_name)
90
                        return True #handled by this method
91
                return False #let gtk handle the key press
92
93
        def _quit(self, window, event):
94
                """
95
                Handle the delete event from the main window.
96
                Generated by pressing X to close, alt+f4, or right click+close.
97
                This method in turns calls the state handler to quit.
98
                @return true
99
                """
100
                self.handle_states(Actions.APPLICATION_QUIT)
101
                return True
102
103
        def _handle_actions(self, event):
104
                """
105
                Handle all of the activate signals from the gtk actions.
106
                The action signals derive from clicking on a toolbar or menu bar button.
107
                Forward the action to the state handler.
108
                """
109
                self.handle_states(event.get_name())
110
111
        def handle_states(self, state=''):
112
                """
113
                Handle the state changes in the GUI.
114
                Handle all of the state changes that arise from the action handler or other gui and
115
                inputs in the application. The state passed to the handle_states method is a string descriping
116
                the change. A series of if/elif statements handle the state by greying out action buttons, causing
117
                changes in the flow graph, saving/opening files... The handle_states method is passed to the
118
                contructors of many of the classes used in this application enabling them to report any state change.
119
                @param state a string describing the state change
120
                """
121
                #print state
122
                ##################################################
123
                # Initalize/Quit
124
                ##################################################
125
                if state == Actions.APPLICATION_INITIALIZE:
126
                        for action in Actions.get_all_actions(): action.set_sensitive(False) #set all actions disabled
127
                        # enable a select few actions
128
                        for action in (
129
                                Actions.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW,
130
                                Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS,
131
                                Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY,
132
                                Actions.FLOW_GRAPH_SCREEN_CAPTURE, Actions.HELP_WINDOW_DISPLAY,
133
                                Actions.TYPES_WINDOW_DISPLAY,
134
                        ): Actions.get_action_from_name(action).set_sensitive(True)
135
                        if not self.init_file_paths:
136
                                self.init_file_paths = Preferences.files_open()
137
                        if not self.init_file_paths: self.init_file_paths = ['']
138
                        for file_path in self.init_file_paths:
139
                                if file_path: self.main_window.new_page(file_path) #load pages from file paths
140
                        if Preferences.file_open() in self.init_file_paths:
141
                                self.main_window.new_page(Preferences.file_open(), show=True)
142
                        if not self.get_page():        self.main_window.new_page() #ensure that at least a blank page exists
143
                elif state == Actions.APPLICATION_QUIT:
144
                        if self.main_window.close_pages():
145
                                gtk.main_quit()
146
                                exit(0)
147
                ##################################################
148
                # Selections
149
                ##################################################
150
                elif state == Actions.ELEMENT_SELECT:
151
                        pass #do nothing, update routines below
152
                elif state == Actions.NOTHING_SELECT:
153
                        self.get_flow_graph().unselect()
154
                ##################################################
155
                # Enable/Disable
156
                ##################################################
157
                elif state == Actions.BLOCK_ENABLE:
158
                        if self.get_flow_graph().enable_selected(True):
159
                                self.get_flow_graph().update()
160
                                self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
161
                                self.get_page().set_saved(False)
162
                elif state == Actions.BLOCK_DISABLE:
163
                        if self.get_flow_graph().enable_selected(False):
164
                                self.get_flow_graph().update()
165
                                self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
166
                                self.get_page().set_saved(False)
167
                ##################################################
168
                # Cut/Copy/Paste
169
                ##################################################
170
                elif state == Actions.BLOCK_CUT:
171
                        self.handle_states(Actions.BLOCK_COPY)
172
                        self.handle_states(Actions.ELEMENT_DELETE)
173
                elif state == Actions.BLOCK_COPY:
174
                        self.clipboard = self.get_flow_graph().copy_to_clipboard()
175
                elif state == Actions.BLOCK_PASTE:
176
                        if self.clipboard:
177
                                self.get_flow_graph().paste_from_clipboard(self.clipboard)
178
                                self.get_flow_graph().update()
179
                                self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
180
                                self.get_page().set_saved(False)
181
                ##################################################
182
                # Move/Rotate/Delete/Create
183
                ##################################################
184
                elif state == Actions.BLOCK_MOVE:
185
                        self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
186
                        self.get_page().set_saved(False)
187
                elif state == Actions.BLOCK_ROTATE_CCW:
188
                        if self.get_flow_graph().rotate_selected(90):
189
                                self.get_flow_graph().update()
190
                                self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
191
                                self.get_page().set_saved(False)
192
                elif state == Actions.BLOCK_ROTATE_CW:
193
                        if self.get_flow_graph().rotate_selected(-90):
194
                                self.get_flow_graph().update()
195
                                self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
196
                                self.get_page().set_saved(False)
197
                elif state == Actions.ELEMENT_DELETE:
198
                        if self.get_flow_graph().remove_selected():
199
                                self.get_flow_graph().update()
200
                                self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
201
                                self.handle_states(Actions.NOTHING_SELECT)
202
                                self.get_page().set_saved(False)
203
                elif state == Actions.ELEMENT_CREATE:
204
                        self.get_flow_graph().update()
205
                        self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
206
                        self.handle_states(Actions.NOTHING_SELECT)
207
                        self.get_page().set_saved(False)
208
                elif state == Actions.BLOCK_INC_TYPE:
209
                        if self.get_flow_graph().type_controller_modify_selected(1):
210
                                self.get_flow_graph().update()
211
                                self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
212
                                self.get_page().set_saved(False)
213
                elif state == Actions.BLOCK_DEC_TYPE:
214
                        if self.get_flow_graph().type_controller_modify_selected(-1):
215
                                self.get_flow_graph().update()
216
                                self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
217
                                self.get_page().set_saved(False)
218
                elif state == Actions.PORT_CONTROLLER_INC:
219
                        if self.get_flow_graph().port_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.PORT_CONTROLLER_DEC:
224
                        if self.get_flow_graph().port_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
                ##################################################
229
                # Window stuff
230
                ##################################################
231
                elif state == Actions.ABOUT_WINDOW_DISPLAY:
232
                        Dialogs.AboutDialog(self.get_flow_graph().get_parent())
233
                elif state == Actions.HELP_WINDOW_DISPLAY:
234
                        Dialogs.HelpDialog()
235
                elif state == Actions.TYPES_WINDOW_DISPLAY:
236
                        Dialogs.TypesDialog(self.get_flow_graph().get_parent())
237
                ##################################################
238
                # Param Modifications
239
                ##################################################
240
                elif state == Actions.BLOCK_PARAM_MODIFY:
241
                        selected_block = self.get_flow_graph().get_selected_block()
242
                        if selected_block:
243
                                if PropsDialog(selected_block).run():
244
                                        #save the new state
245
                                        self.get_flow_graph().update()
246
                                        self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
247
                                        self.get_page().set_saved(False)
248
                                else:
249
                                        #restore the current state
250
                                        n = self.get_page().get_state_cache().get_current_state()
251
                                        self.get_flow_graph().import_data(n)
252
                                        self.get_flow_graph().update()
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
                                pixbuf = self.get_flow_graph().get_drawing_area().get_pixbuf()
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_CCW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
337
                Actions.get_action_from_name(Actions.BLOCK_ROTATE_CW).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
                try: #set the size of the flow graph area (if changed)
351
                        new_size = self.get_flow_graph().get_option('window_size')
352
                        if self.get_flow_graph().get_size() != tuple(new_size):
353
                                self.get_flow_graph().set_size(*new_size)
354
                except: pass
355
                #draw the flow graph
356
                self.get_flow_graph().update_selected()
357
                self.get_flow_graph().queue_draw()
358
359
        def update_exec_stop(self):
360
                """
361
                Update the exec and stop buttons.
362
                Lock and unlock the mutex for race conditions with exec flow graph threads.
363
                """
364
                sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid()
365
                Actions.get_action_from_name(Actions.FLOW_GRAPH_GEN).set_sensitive(sensitive)
366
                Actions.get_action_from_name(Actions.FLOW_GRAPH_EXEC).set_sensitive(sensitive)
367
                Actions.get_action_from_name(Actions.FLOW_GRAPH_KILL).set_sensitive(self.get_page().get_pid() != None)
368
369
class ExecFlowGraphThread(Thread):
370
        """Execute the flow graph as a new process and wait on it to finish."""
371
372
        def __init__ (self, action_handler):
373
                """
374
                ExecFlowGraphThread constructor.
375
                @param action_handler an instance of an ActionHandler
376
                """
377
                Thread.__init__(self)
378
                self.update_exec_stop = action_handler.update_exec_stop
379
                self.flow_graph = action_handler.get_flow_graph()
380
                #store page and dont use main window calls in run
381
                self.page = action_handler.get_page()
382
                Messages.send_start_exec(self.page.get_generator().get_file_path())
383
                #get the popen
384
                try:
385
                        self.p = self.page.get_generator().get_popen()
386
                        self.page.set_pid(self.p.pid)
387
                        #update
388
                        self.update_exec_stop()
389
                        self.start()
390
                except Exception, e:
391
                        Messages.send_verbose_exec(str(e))
392
                        Messages.send_end_exec()
393
394
        def run(self):
395
                """
396
                Wait on the executing process by reading from its stdout.
397
                Use gobject.idle_add when calling functions that modify gtk objects.
398
                """
399
                #handle completion
400
                r = "\n"
401
                while(r):
402
                        gobject.idle_add(Messages.send_verbose_exec, r)
403
                        r = os.read(self.p.stdout.fileno(), 1024)
404
                gobject.idle_add(self.done)
405
406
        def done(self):
407
                """Perform end of execution tasks."""
408
                Messages.send_end_exec()
409
                self.page.set_pid(None)
410
                self.update_exec_stop()