From 9988664127b367fa8fee4409f8460673d6f265e1 Mon Sep 17 00:00:00 2001 From: jblum Date: Tue, 23 Jun 2009 20:38:18 +0000 Subject: Merging r11186:11273 from grc branch. Fixes, features, and reorganization for grc. Minor fixes and features for wxgui forms. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11274 221aa14e-8319-0410-a670-987f0aec2ac5 --- grc/gui/ActionHandler.py | 411 +++++++++++++++++++++++++++++++++++++ grc/gui/Actions.py | 169 ++++++++++++++++ grc/gui/Bars.py | 138 +++++++++++++ grc/gui/Block.py | 199 ++++++++++++++++++ grc/gui/BlockTreeWindow.py | 168 ++++++++++++++++ grc/gui/Colors.py | 37 ++++ grc/gui/Connection.py | 140 +++++++++++++ grc/gui/Constants.py | 83 ++++++++ grc/gui/Dialogs.py | 105 ++++++++++ grc/gui/DrawingArea.py | 126 ++++++++++++ grc/gui/Element.py | 226 +++++++++++++++++++++ grc/gui/FileDialogs.py | 175 ++++++++++++++++ grc/gui/FlowGraph.py | 489 +++++++++++++++++++++++++++++++++++++++++++++ grc/gui/MainWindow.py | 324 ++++++++++++++++++++++++++++++ grc/gui/Makefile.am | 50 +++++ grc/gui/Messages.py | 104 ++++++++++ grc/gui/NotebookPage.py | 188 +++++++++++++++++ grc/gui/Param.py | 104 ++++++++++ grc/gui/ParamsDialog.py | 145 ++++++++++++++ grc/gui/Platform.py | 48 +++++ grc/gui/Port.py | 190 ++++++++++++++++++ grc/gui/Preferences.py | 86 ++++++++ grc/gui/StateCache.py | 92 +++++++++ grc/gui/Utils.py | 82 ++++++++ grc/gui/__init__.py | 1 + 25 files changed, 3880 insertions(+) create mode 100644 grc/gui/ActionHandler.py create mode 100644 grc/gui/Actions.py create mode 100644 grc/gui/Bars.py create mode 100644 grc/gui/Block.py create mode 100644 grc/gui/BlockTreeWindow.py create mode 100644 grc/gui/Colors.py create mode 100644 grc/gui/Connection.py create mode 100644 grc/gui/Constants.py create mode 100644 grc/gui/Dialogs.py create mode 100644 grc/gui/DrawingArea.py create mode 100644 grc/gui/Element.py create mode 100644 grc/gui/FileDialogs.py create mode 100644 grc/gui/FlowGraph.py create mode 100644 grc/gui/MainWindow.py create mode 100644 grc/gui/Makefile.am create mode 100644 grc/gui/Messages.py create mode 100644 grc/gui/NotebookPage.py create mode 100644 grc/gui/Param.py create mode 100644 grc/gui/ParamsDialog.py create mode 100644 grc/gui/Platform.py create mode 100644 grc/gui/Port.py create mode 100644 grc/gui/Preferences.py create mode 100644 grc/gui/StateCache.py create mode 100644 grc/gui/Utils.py create mode 100644 grc/gui/__init__.py (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py new file mode 100644 index 000000000..2c411a175 --- /dev/null +++ b/grc/gui/ActionHandler.py @@ -0,0 +1,411 @@ +""" +Copyright 2007, 2008, 2009 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import os +import signal +from Constants import IMAGE_FILE_EXTENSION +import Actions +import pygtk +pygtk.require('2.0') +import gtk +import gobject +import Preferences +from threading import Thread +import Messages +from .. base import ParseXML +import random +from Platform import Platform +from MainWindow import MainWindow +from ParamsDialog import ParamsDialog +import Dialogs +from FileDialogs import OpenFlowGraphFileDialog, SaveFlowGraphFileDialog, SaveImageFileDialog + +gobject.threads_init() + +class ActionHandler: + """ + The action handler will setup all the major window components, + and handle button presses and flow graph operations from the GUI. + """ + + def __init__(self, file_paths, platform): + """ + ActionHandler constructor. + Create the main window, setup the message handler, import the preferences, + and connect all of the action handlers. Finally, enter the gtk main loop and block. + @param file_paths a list of flow graph file passed from command line + @param platform platform module + """ + self.clipboard = None + platform = Platform(platform) + for action in Actions.get_all_actions(): action.connect('activate', self._handle_actions) + #setup the main window + self.main_window = MainWindow(self.handle_states, platform) + self.main_window.connect('delete_event', self._quit) + self.main_window.connect('key-press-event', self._handle_key_press) + self.get_page = self.main_window.get_page + self.get_flow_graph = self.main_window.get_flow_graph + self.get_focus_flag = self.main_window.get_focus_flag + #setup the messages + Messages.register_messenger(self.main_window.add_report_line) + Messages.send_init(platform) + #initialize + self.init_file_paths = file_paths + self.handle_states(Actions.APPLICATION_INITIALIZE) + #enter the mainloop + gtk.main() + + def _handle_key_press(self, widget, event): + """ + Handle key presses from the keyboard and translate key combinations into actions. + This key press handler is called prior to the gtk key press handler. + This handler bypasses built in accelerator key handling when in focus because + * some keys are ignored by the accelerators like the direction keys, + * some keys are not registered to any accelerators but are still used. + When not in focus, gtk and the accelerators handle the the key press. + @return false to let gtk handle the key action + """ + #dont allow key presses to queue up + if gtk.events_pending(): return True + #extract action name from this key press + key_name = gtk.gdk.keyval_name(event.keyval) + mod_mask = event.state + action_name = Actions.get_action_name_from_key_name(key_name, mod_mask) + #handle the action if flow graph is in focus + if action_name and self.get_focus_flag(): + self.handle_states(action_name) + return True #handled by this method + return False #let gtk handle the key press + + def _quit(self, window, event): + """ + Handle the delete event from the main window. + Generated by pressing X to close, alt+f4, or right click+close. + This method in turns calls the state handler to quit. + @return true + """ + self.handle_states(Actions.APPLICATION_QUIT) + return True + + def _handle_actions(self, event): + """ + Handle all of the activate signals from the gtk actions. + The action signals derive from clicking on a toolbar or menu bar button. + Forward the action to the state handler. + """ + self.handle_states(event.get_name()) + + def handle_states(self, state=''): + """ + Handle the state changes in the GUI. + Handle all of the state changes that arise from the action handler or other gui and + inputs in the application. The state passed to the handle_states method is a string descriping + the change. A series of if/elif statements handle the state by greying out action buttons, causing + changes in the flow graph, saving/opening files... The handle_states method is passed to the + contructors of many of the classes used in this application enabling them to report any state change. + @param state a string describing the state change + """ + #print state + ################################################## + # Initalize/Quit + ################################################## + if state == Actions.APPLICATION_INITIALIZE: + for action in Actions.get_all_actions(): action.set_sensitive(False) #set all actions disabled + # enable a select few actions + for action in ( + Actions.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW, + Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS, + Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY, + Actions.FLOW_GRAPH_SCREEN_CAPTURE, Actions.HELP_WINDOW_DISPLAY, + Actions.COLORS_WINDOW_DISPLAY, + ): Actions.get_action_from_name(action).set_sensitive(True) + if not self.init_file_paths: + self.init_file_paths = Preferences.files_open() + if not self.init_file_paths: self.init_file_paths = [''] + for file_path in self.init_file_paths: + if file_path: self.main_window.new_page(file_path) #load pages from file paths + if Preferences.file_open() in self.init_file_paths: + self.main_window.new_page(Preferences.file_open(), show=True) + if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists + elif state == Actions.APPLICATION_QUIT: + if self.main_window.close_pages(): + gtk.main_quit() + exit(0) + ################################################## + # Selections + ################################################## + elif state == Actions.ELEMENT_SELECT: + pass #do nothing, update routines below + elif state == Actions.NOTHING_SELECT: + self.get_flow_graph().unselect() + ################################################## + # Enable/Disable + ################################################## + elif state == Actions.BLOCK_ENABLE: + if self.get_flow_graph().enable_selected(True): + self.get_flow_graph().update() + self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + self.get_page().set_saved(False) + elif state == Actions.BLOCK_DISABLE: + if self.get_flow_graph().enable_selected(False): + self.get_flow_graph().update() + self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + self.get_page().set_saved(False) + ################################################## + # Cut/Copy/Paste + ################################################## + elif state == Actions.BLOCK_CUT: + self.handle_states(Actions.BLOCK_COPY) + self.handle_states(Actions.ELEMENT_DELETE) + elif state == Actions.BLOCK_COPY: + self.clipboard = self.get_flow_graph().copy_to_clipboard() + elif state == Actions.BLOCK_PASTE: + if self.clipboard: + self.get_flow_graph().paste_from_clipboard(self.clipboard) + self.get_flow_graph().update() + self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + self.get_page().set_saved(False) + ################################################## + # Move/Rotate/Delete/Create + ################################################## + elif state == Actions.BLOCK_MOVE: + self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + self.get_page().set_saved(False) + elif state == Actions.BLOCK_ROTATE_CCW: + if self.get_flow_graph().rotate_selected(90): + self.get_flow_graph().update() + self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + self.get_page().set_saved(False) + elif state == Actions.BLOCK_ROTATE_CW: + if self.get_flow_graph().rotate_selected(-90): + self.get_flow_graph().update() + self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + self.get_page().set_saved(False) + elif state == Actions.ELEMENT_DELETE: + if self.get_flow_graph().remove_selected(): + self.get_flow_graph().update() + self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + self.handle_states(Actions.NOTHING_SELECT) + self.get_page().set_saved(False) + elif state == Actions.ELEMENT_CREATE: + self.get_flow_graph().update() + self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + self.handle_states(Actions.NOTHING_SELECT) + self.get_page().set_saved(False) + elif state == Actions.BLOCK_INC_TYPE: + if self.get_flow_graph().type_controller_modify_selected(1): + self.get_flow_graph().update() + self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + self.get_page().set_saved(False) + elif state == Actions.BLOCK_DEC_TYPE: + if self.get_flow_graph().type_controller_modify_selected(-1): + self.get_flow_graph().update() + self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + self.get_page().set_saved(False) + elif state == Actions.PORT_CONTROLLER_INC: + if self.get_flow_graph().port_controller_modify_selected(1): + self.get_flow_graph().update() + self.get_flow_graph().update() #2 times + self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + self.get_page().set_saved(False) + elif state == Actions.PORT_CONTROLLER_DEC: + if self.get_flow_graph().port_controller_modify_selected(-1): + self.get_flow_graph().update() + self.get_flow_graph().update() #2 times + self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + self.get_page().set_saved(False) + ################################################## + # Window stuff + ################################################## + elif state == Actions.ABOUT_WINDOW_DISPLAY: + Dialogs.AboutDialog(self.get_flow_graph().get_parent()) + elif state == Actions.HELP_WINDOW_DISPLAY: + Dialogs.HelpDialog() + elif state == Actions.COLORS_WINDOW_DISPLAY: + Dialogs.ColorsDialog(self.get_flow_graph().get_parent()) + ################################################## + # Param Modifications + ################################################## + elif state == Actions.BLOCK_PARAM_MODIFY: + selected_block = self.get_flow_graph().get_selected_block() + if selected_block and ParamsDialog(selected_block).run(): + self.get_flow_graph().update() + self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + self.get_page().set_saved(False) + ################################################## + # Undo/Redo + ################################################## + elif state == Actions.FLOW_GRAPH_UNDO: + n = self.get_page().get_state_cache().get_prev_state() + if n: + self.get_flow_graph().unselect() + self.get_flow_graph().import_data(n) + self.get_flow_graph().update() + self.get_page().set_saved(False) + elif state == Actions.FLOW_GRAPH_REDO: + n = self.get_page().get_state_cache().get_next_state() + if n: + self.get_flow_graph().unselect() + self.get_flow_graph().import_data(n) + self.get_flow_graph().update() + self.get_page().set_saved(False) + ################################################## + # New/Open/Save/Close + ################################################## + elif state == Actions.FLOW_GRAPH_NEW: + self.main_window.new_page() + elif state == Actions.FLOW_GRAPH_OPEN: + file_paths = OpenFlowGraphFileDialog(self.get_page().get_file_path()).run() + if file_paths: #open a new page for each file, show only the first + for i,file_path in enumerate(file_paths): + self.main_window.new_page(file_path, show=(i==0)) + elif state == Actions.FLOW_GRAPH_CLOSE: + self.main_window.close_page() + elif state == Actions.FLOW_GRAPH_SAVE: + #read-only or undefined file path, do save-as + if self.get_page().get_read_only() or not self.get_page().get_file_path(): + self.handle_states(Actions.FLOW_GRAPH_SAVE_AS) + #otherwise try to save + else: + try: + ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path()) + self.get_page().set_saved(True) + except IOError: + Messages.send_fail_save(self.get_page().get_file_path()) + self.get_page().set_saved(False) + elif state == Actions.FLOW_GRAPH_SAVE_AS: + file_path = SaveFlowGraphFileDialog(self.get_page().get_file_path()).run() + if file_path is not None: + self.get_page().set_file_path(file_path) + self.handle_states(Actions.FLOW_GRAPH_SAVE) + elif state == Actions.FLOW_GRAPH_SCREEN_CAPTURE: + file_path = SaveImageFileDialog(self.get_page().get_file_path()).run() + if file_path is not None: + pixmap = self.get_flow_graph().get_pixmap() + width, height = pixmap.get_size() + pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, width, height) + pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, width, height) + pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:]) + ################################################## + # Gen/Exec/Stop + ################################################## + elif state == Actions.FLOW_GRAPH_GEN: + if not self.get_page().get_pid(): + if not self.get_page().get_saved() or not self.get_page().get_file_path(): + self.handle_states(Actions.FLOW_GRAPH_SAVE) #only save if file path missing or not saved + if self.get_page().get_saved() and self.get_page().get_file_path(): + generator = self.get_page().get_generator() + try: + Messages.send_start_gen(generator.get_file_path()) + generator.write() + except Exception,e: Messages.send_fail_gen(e) + else: self.generator = None + elif state == Actions.FLOW_GRAPH_EXEC: + if not self.get_page().get_pid(): + self.handle_states(Actions.FLOW_GRAPH_GEN) + if self.get_page().get_saved() and self.get_page().get_file_path(): + ExecFlowGraphThread(self) + elif state == Actions.FLOW_GRAPH_KILL: + if self.get_page().get_pid(): + try: os.kill(self.get_page().get_pid(), signal.SIGKILL) + except: print "could not kill pid: %s"%self.get_page().get_pid() + elif state == '': #pass and run the global actions + pass + else: print '!!! State "%s" not handled !!!'%state + ################################################## + # Global Actions for all States + ################################################## + #update general buttons + Actions.get_action_from_name(Actions.ELEMENT_DELETE).set_sensitive(bool(self.get_flow_graph().get_selected_elements())) + Actions.get_action_from_name(Actions.BLOCK_PARAM_MODIFY).set_sensitive(bool(self.get_flow_graph().get_selected_block())) + Actions.get_action_from_name(Actions.BLOCK_ROTATE_CCW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) + Actions.get_action_from_name(Actions.BLOCK_ROTATE_CW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) + #update cut/copy/paste + Actions.get_action_from_name(Actions.BLOCK_CUT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) + Actions.get_action_from_name(Actions.BLOCK_COPY).set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) + Actions.get_action_from_name(Actions.BLOCK_PASTE).set_sensitive(bool(self.clipboard)) + #update enable/disable + Actions.get_action_from_name(Actions.BLOCK_ENABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) + Actions.get_action_from_name(Actions.BLOCK_DISABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) + #set the exec and stop buttons + self.update_exec_stop() + #saved status + Actions.get_action_from_name(Actions.FLOW_GRAPH_SAVE).set_sensitive(not self.get_page().get_saved()) + self.main_window.update() + try: #set the size of the flow graph area (if changed) + new_size = self.get_flow_graph().get_option('window_size') + if self.get_flow_graph().get_size() != tuple(new_size): + self.get_flow_graph().set_size(*new_size) + except: pass + #draw the flow graph + self.get_flow_graph().update_selected() + self.get_flow_graph().queue_draw() + + def update_exec_stop(self): + """ + Update the exec and stop buttons. + Lock and unlock the mutex for race conditions with exec flow graph threads. + """ + sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid() + Actions.get_action_from_name(Actions.FLOW_GRAPH_GEN).set_sensitive(sensitive) + Actions.get_action_from_name(Actions.FLOW_GRAPH_EXEC).set_sensitive(sensitive) + Actions.get_action_from_name(Actions.FLOW_GRAPH_KILL).set_sensitive(self.get_page().get_pid() != None) + +class ExecFlowGraphThread(Thread): + """Execute the flow graph as a new process and wait on it to finish.""" + + def __init__ (self, action_handler): + """ + ExecFlowGraphThread constructor. + @param action_handler an instance of an ActionHandler + """ + Thread.__init__(self) + self.update_exec_stop = action_handler.update_exec_stop + self.flow_graph = action_handler.get_flow_graph() + #store page and dont use main window calls in run + self.page = action_handler.get_page() + Messages.send_start_exec(self.page.get_generator().get_file_path()) + #get the popen + try: + self.p = self.page.get_generator().get_popen() + self.page.set_pid(self.p.pid) + #update + self.update_exec_stop() + self.start() + except Exception, e: + Messages.send_verbose_exec(str(e)) + Messages.send_end_exec() + + def run(self): + """ + Wait on the executing process by reading from its stdout. + Use gobject.idle_add when calling functions that modify gtk objects. + """ + #handle completion + r = "\n" + while(r): + gobject.idle_add(Messages.send_verbose_exec, r) + r = os.read(self.p.stdout.fileno(), 1024) + gobject.idle_add(self.done) + + def done(self): + """Perform end of execution tasks.""" + Messages.send_end_exec() + self.page.set_pid(None) + self.update_exec_stop() diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py new file mode 100644 index 000000000..3695e09ef --- /dev/null +++ b/grc/gui/Actions.py @@ -0,0 +1,169 @@ +""" +Copyright 2007, 2008, 2009 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import pygtk +pygtk.require('2.0') +import gtk + +###################################################################################################### +# Action Names +###################################################################################################### +APPLICATION_INITIALIZE = 'app init' +APPLICATION_QUIT = 'app quit' +PARAM_MODIFY = 'param modify' +BLOCK_MOVE = 'block move' +BLOCK_ROTATE_CCW = 'block rotate ccw' +BLOCK_ROTATE_CW = 'block rotate cw' +BLOCK_PARAM_MODIFY = 'block param modify' +BLOCK_INC_TYPE = 'block increment type' +BLOCK_DEC_TYPE = 'block decrement type' +BLOCK_ENABLE = 'block enable' +BLOCK_DISABLE = 'block disable' +BLOCK_CUT = 'block cut' +BLOCK_COPY = 'block copy' +BLOCK_PASTE = 'block paste' +PORT_CONTROLLER_INC = 'port controller increment' +PORT_CONTROLLER_DEC = 'port controller decrement' +ELEMENT_CREATE = 'element create' +ELEMENT_DELETE = 'element delete' +ELEMENT_SELECT = 'element select' +NOTHING_SELECT = 'nothing select' +FLOW_GRAPH_OPEN = 'flow graph open' +FLOW_GRAPH_UNDO = 'flow graph undo' +FLOW_GRAPH_REDO = 'flow graph redo' +FLOW_GRAPH_SAVE = 'flow graph save' +FLOW_GRAPH_SAVE_AS = 'flow graph save as' +FLOW_GRAPH_CLOSE = 'flow graph close' +FLOW_GRAPH_NEW = 'flow graph new' +FLOW_GRAPH_GEN = 'flow graph gen' +FLOW_GRAPH_EXEC = 'flow graph exec' +FLOW_GRAPH_KILL = 'flow graph kill' +FLOW_GRAPH_SCREEN_CAPTURE = 'flow graph screen capture' +ABOUT_WINDOW_DISPLAY = 'about window display' +HELP_WINDOW_DISPLAY = 'help window display' +COLORS_WINDOW_DISPLAY = 'colors window display' + +###################################################################################################### +# Action Key Map +###################################################################################################### +_actions_key_list = ( + #action name, key name, mod mask + (FLOW_GRAPH_NEW, 'n', gtk.gdk.CONTROL_MASK), + (FLOW_GRAPH_OPEN, 'o', gtk.gdk.CONTROL_MASK), + (FLOW_GRAPH_SAVE, 's', gtk.gdk.CONTROL_MASK), + (FLOW_GRAPH_SAVE_AS, 's', gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK), + (FLOW_GRAPH_CLOSE, 'w', gtk.gdk.CONTROL_MASK), + (APPLICATION_QUIT, 'q', gtk.gdk.CONTROL_MASK), + (FLOW_GRAPH_UNDO, 'z', gtk.gdk.CONTROL_MASK), + (FLOW_GRAPH_REDO, 'y', gtk.gdk.CONTROL_MASK), + (ELEMENT_DELETE, 'Delete', 0), + (BLOCK_ROTATE_CCW, 'Left', 0), + (BLOCK_ROTATE_CW, 'Right', 0), + (BLOCK_DEC_TYPE, 'Up', 0), + (BLOCK_INC_TYPE, 'Down', 0), + (BLOCK_PARAM_MODIFY, 'Return', 0), + (BLOCK_ENABLE, 'e', 0), + (BLOCK_DISABLE, 'd', 0), + (BLOCK_CUT, 'x', gtk.gdk.CONTROL_MASK), + (BLOCK_COPY, 'c', gtk.gdk.CONTROL_MASK), + (BLOCK_PASTE, 'v', gtk.gdk.CONTROL_MASK), + (FLOW_GRAPH_GEN, 'F5', 0), + (FLOW_GRAPH_EXEC, 'F6', 0), + (FLOW_GRAPH_KILL, 'F7', 0), + (FLOW_GRAPH_SCREEN_CAPTURE, 'Print', 0), + (HELP_WINDOW_DISPLAY, 'F1', 0), + #the following have no associated gtk.Action + (PORT_CONTROLLER_INC, 'equal', 0), + (PORT_CONTROLLER_INC, 'plus', 0), + (PORT_CONTROLLER_INC, 'KP_Add', 0), + (PORT_CONTROLLER_DEC, 'minus', 0), + (PORT_CONTROLLER_DEC, 'KP_Subtract', 0), +) + +_actions_key_dict = dict(((key_name, mod_mask), action_name) for action_name, key_name, mod_mask in _actions_key_list) +def get_action_name_from_key_name(key_name, mod_mask=0): + """ + Get the action name associated with the key name and mask. + Both keyname and mask have to match. + @param key_name the name of the key + @param mod_mask the key press mask (shift, ctrl) 0 for none + @return the action name or blank string + """ + key_name_mod_mask = (key_name, mod_mask) + if key_name_mod_mask in _actions_key_dict: return _actions_key_dict[key_name_mod_mask] + return '' + +###################################################################################################### +# Actions +###################################################################################################### +_actions_list = ( + gtk.Action(FLOW_GRAPH_NEW, '_New', 'Create a new flow graph', gtk.STOCK_NEW), + gtk.Action(FLOW_GRAPH_OPEN, '_Open', 'Open an existing flow graph', gtk.STOCK_OPEN), + gtk.Action(FLOW_GRAPH_SAVE, '_Save', 'Save the current flow graph', gtk.STOCK_SAVE), + gtk.Action(FLOW_GRAPH_SAVE_AS, 'Save _As', 'Save the current flow graph as...', gtk.STOCK_SAVE_AS), + gtk.Action(FLOW_GRAPH_CLOSE, '_Close', 'Close the current flow graph', gtk.STOCK_CLOSE), + gtk.Action(APPLICATION_QUIT, '_Quit', 'Quit program', gtk.STOCK_QUIT), + gtk.Action(FLOW_GRAPH_UNDO, '_Undo', 'Undo a change to the flow graph', gtk.STOCK_UNDO), + gtk.Action(FLOW_GRAPH_REDO, '_Redo', 'Redo a change to the flow graph', gtk.STOCK_REDO), + gtk.Action(ELEMENT_DELETE, '_Delete', 'Delete the selected blocks', gtk.STOCK_DELETE), + gtk.Action(BLOCK_ROTATE_CCW, 'Rotate Counterclockwise', 'Rotate the selected blocks 90 degrees to the left', gtk.STOCK_GO_BACK), + gtk.Action(BLOCK_ROTATE_CW, 'Rotate Clockwise', 'Rotate the selected blocks 90 degrees to the right', gtk.STOCK_GO_FORWARD), + gtk.Action(BLOCK_PARAM_MODIFY, '_Properties', 'Modify params for the selected block', gtk.STOCK_PROPERTIES), + gtk.Action(BLOCK_ENABLE, 'E_nable', 'Enable the selected blocks', gtk.STOCK_CONNECT), + gtk.Action(BLOCK_DISABLE, 'D_isable', 'Disable the selected blocks', gtk.STOCK_DISCONNECT), + gtk.Action(BLOCK_CUT, 'Cu_t', 'Cut', gtk.STOCK_CUT), + gtk.Action(BLOCK_COPY, '_Copy', 'Copy', gtk.STOCK_COPY), + gtk.Action(BLOCK_PASTE, '_Paste', 'Paste', gtk.STOCK_PASTE), + gtk.Action(ABOUT_WINDOW_DISPLAY, '_About', 'About this program', gtk.STOCK_ABOUT), + gtk.Action(HELP_WINDOW_DISPLAY, '_Help', 'Usage Tips', gtk.STOCK_HELP), + gtk.Action(COLORS_WINDOW_DISPLAY, '_Colors', 'Color Mapping', gtk.STOCK_DIALOG_INFO), + gtk.Action(FLOW_GRAPH_GEN, '_Generate', 'Generate the flow graph', gtk.STOCK_CONVERT), + gtk.Action(FLOW_GRAPH_EXEC, '_Execute', 'Execute the flow graph', gtk.STOCK_EXECUTE), + gtk.Action(FLOW_GRAPH_KILL, '_Kill', 'Kill the flow graph', gtk.STOCK_STOP), + gtk.Action(FLOW_GRAPH_SCREEN_CAPTURE, 'S_creen Capture', 'Create a screen capture of the flow graph', gtk.STOCK_PRINT), +) +def get_all_actions(): return _actions_list + +_actions_dict = dict((action.get_name(), action) for action in _actions_list) +def get_action_from_name(action_name): + """ + Retrieve the action from the action list. + Search the list and find an action with said name. + @param action_name the action name(string) + @throw KeyError bad action name + @return a gtk action object + """ + if action_name in _actions_dict: return _actions_dict[action_name] + raise KeyError('Action Name: "%s" does not exist'%action_name) + +###################################################################################################### +# Accelerators +###################################################################################################### +_accel_group = gtk.AccelGroup() +def get_accel_group(): return _accel_group + +#set the accelerator group, and accelerator path +#register the key name and mod mask with the accelerator path +for action_name, key_name, mod_mask in _actions_key_list: + try: + accel_path = '
/'+action_name + get_action_from_name(action_name).set_accel_group(get_accel_group()) + get_action_from_name(action_name).set_accel_path(accel_path) + gtk.accel_map_add_entry(accel_path, gtk.gdk.keyval_from_name(key_name), mod_mask) + except KeyError: pass #no action was created for this action name diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py new file mode 100644 index 000000000..e0c547eba --- /dev/null +++ b/grc/gui/Bars.py @@ -0,0 +1,138 @@ +""" +Copyright 2007, 2008, 2009 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import Actions +import pygtk +pygtk.require('2.0') +import gtk + +##The list of actions for the toolbar. +TOOLBAR_LIST = ( + Actions.FLOW_GRAPH_NEW, + Actions.FLOW_GRAPH_OPEN, + Actions.FLOW_GRAPH_SAVE, + Actions.FLOW_GRAPH_CLOSE, + None, + Actions.FLOW_GRAPH_SCREEN_CAPTURE, + None, + Actions.BLOCK_CUT, + Actions.BLOCK_COPY, + Actions.BLOCK_PASTE, + Actions.ELEMENT_DELETE, + None, + Actions.FLOW_GRAPH_UNDO, + Actions.FLOW_GRAPH_REDO, + None, + Actions.FLOW_GRAPH_GEN, + Actions.FLOW_GRAPH_EXEC, + Actions.FLOW_GRAPH_KILL, + None, + Actions.BLOCK_ROTATE_CCW, + Actions.BLOCK_ROTATE_CW, + None, + Actions.BLOCK_ENABLE, + Actions.BLOCK_DISABLE, +) + +##The list of actions and categories for the menu bar. +MENU_BAR_LIST = ( + (gtk.Action('File', '_File', None, None), [ + Actions.FLOW_GRAPH_NEW, + Actions.FLOW_GRAPH_OPEN, + None, + Actions.FLOW_GRAPH_SAVE, + Actions.FLOW_GRAPH_SAVE_AS, + None, + Actions.FLOW_GRAPH_SCREEN_CAPTURE, + None, + Actions.FLOW_GRAPH_CLOSE, + Actions.APPLICATION_QUIT, + ]), + (gtk.Action('Edit', '_Edit', None, None), [ + Actions.FLOW_GRAPH_UNDO, + Actions.FLOW_GRAPH_REDO, + None, + Actions.BLOCK_CUT, + Actions.BLOCK_COPY, + Actions.BLOCK_PASTE, + Actions.ELEMENT_DELETE, + None, + Actions.BLOCK_ROTATE_CCW, + Actions.BLOCK_ROTATE_CW, + None, + Actions.BLOCK_ENABLE, + Actions.BLOCK_DISABLE, + None, + Actions.BLOCK_PARAM_MODIFY, + ]), + (gtk.Action('Build', '_Build', None, None), [ + Actions.FLOW_GRAPH_GEN, + Actions.FLOW_GRAPH_EXEC, + Actions.FLOW_GRAPH_KILL, + ]), + (gtk.Action('Help', '_Help', None, None), [ + Actions.HELP_WINDOW_DISPLAY, + Actions.COLORS_WINDOW_DISPLAY, + None, + Actions.ABOUT_WINDOW_DISPLAY, + ]), +) + +class Toolbar(gtk.Toolbar): + """The gtk toolbar with actions added from the toolbar list.""" + + def __init__(self): + """ + Parse the list of action names in the toolbar list. + Look up the action for each name in the action list and add it to the toolbar. + """ + gtk.Toolbar.__init__(self) + self.set_style(gtk.TOOLBAR_ICONS) + for action_name in TOOLBAR_LIST: + if action_name: #add a tool item + action = Actions.get_action_from_name(action_name) + self.add(action.create_tool_item()) + #this reset of the tooltip property is required (after creating the tool item) for the tooltip to show + action.set_property('tooltip', action.get_property('tooltip')) + else: self.add(gtk.SeparatorToolItem()) + +class MenuBar(gtk.MenuBar): + """The gtk menu bar with actions added from the menu bar list.""" + + def __init__(self): + """ + Parse the list of submenus from the menubar list. + For each submenu, get a list of action names. + Look up the action for each name in the action list and add it to the submenu. + Add the submenu to the menu bar. + """ + gtk.MenuBar.__init__(self) + for main_action,action_names in MENU_BAR_LIST: + #create the main menu item + main_menu_item = main_action.create_menu_item() + self.append(main_menu_item) + #create the menu + main_menu = gtk.Menu() + main_menu_item.set_submenu(main_menu) + for action_name in action_names: + if action_name: #append a menu item + action = Actions.get_action_from_name(action_name) + main_menu.append(action.create_menu_item()) + else: main_menu.append(gtk.SeparatorMenuItem()) + main_menu.show_all() #this show all is required for the separators to show diff --git a/grc/gui/Block.py b/grc/gui/Block.py new file mode 100644 index 000000000..0496f0a28 --- /dev/null +++ b/grc/gui/Block.py @@ -0,0 +1,199 @@ +""" +Copyright 2007, 2008, 2009 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +from Element import Element +import Utils +import Colors +from .. base import odict +from Constants import BORDER_PROXIMITY_SENSITIVITY +from Constants import \ + BLOCK_LABEL_PADDING, \ + PORT_SEPARATION, LABEL_SEPARATION, \ + PORT_BORDER_SEPARATION, POSSIBLE_ROTATIONS +import pygtk +pygtk.require('2.0') +import gtk + +BLOCK_MARKUP_TMPL="""\ +#set $foreground = $block.is_valid() and 'black' or 'red' +$encode($block.get_name())""" + +class Block(Element): + """The graphical signal block.""" + + def __init__(self, *args, **kwargs): + """ + Block contructor. + Add graphics related params to the block. + """ + #add the position param + self._params['_coordinate'] = self.get_parent().get_parent().Param( + self, + odict({ + 'name': 'GUI Coordinate', + 'key': '_coordinate', + 'type': 'raw', + 'value': '(0, 0)', + 'hide': 'all', + }) + ) + self._params['_rotation'] = self.get_parent().get_parent().Param( + self, + odict({ + 'name': 'GUI Rotation', + 'key': '_rotation', + 'type': 'raw', + 'value': '0', + 'hide': 'all', + }) + ) + Element.__init__(self) + + def get_coordinate(self): + """ + Get the coordinate from the position param. + @return the coordinate tuple (x, y) or (0, 0) if failure + """ + try: #should evaluate to tuple + coor = eval(self.get_param('_coordinate').get_value()) + x, y = map(int, coor) + fgW,fgH = self.get_parent().get_size() + if x <= 0: + x = 0 + elif x >= fgW - BORDER_PROXIMITY_SENSITIVITY: + x = fgW - BORDER_PROXIMITY_SENSITIVITY + if y <= 0: + y = 0 + elif y >= fgH - BORDER_PROXIMITY_SENSITIVITY: + y = fgH - BORDER_PROXIMITY_SENSITIVITY + return (x, y) + except: + self.set_coordinate((0, 0)) + return (0, 0) + + def set_coordinate(self, coor): + """ + Set the coordinate into the position param. + @param coor the coordinate tuple (x, y) + """ + self.get_param('_coordinate').set_value(str(coor)) + + def get_rotation(self): + """ + Get the rotation from the position param. + @return the rotation in degrees or 0 if failure + """ + try: #should evaluate to dict + rotation = eval(self.get_param('_rotation').get_value()) + return int(rotation) + except: + self.set_rotation(POSSIBLE_ROTATIONS[0]) + return POSSIBLE_ROTATIONS[0] + + def set_rotation(self, rot): + """ + Set the rotation into the position param. + @param rot the rotation in degrees + """ + self.get_param('_rotation').set_value(str(rot)) + + def update(self): + """Update the block, parameters, and ports when a change occurs.""" + self._bg_color = self.get_enabled() and Colors.BLOCK_ENABLED_COLOR or Colors.BLOCK_DISABLED_COLOR + self.clear() + self._create_labels() + self.W = self.label_width + 2*BLOCK_LABEL_PADDING + self.H = max(*( + [self.label_height+2*BLOCK_LABEL_PADDING] + [2*PORT_BORDER_SEPARATION + \ + sum([port.H + PORT_SEPARATION for port in ports]) - PORT_SEPARATION + for ports in (self.get_sources(), self.get_sinks())] + )) + if self.is_horizontal(): self.add_area((0, 0), (self.W, self.H)) + elif self.is_vertical(): self.add_area((0, 0), (self.H, self.W)) + map(lambda p: p.update(), self.get_ports()) + + def _create_labels(self): + """Create the labels for the signal block.""" + layouts = list() + #create the main layout + layout = gtk.DrawingArea().create_pango_layout('') + layouts.append(layout) + layout.set_markup(Utils.parse_template(BLOCK_MARKUP_TMPL, block=self)) + self.label_width, self.label_height = layout.get_pixel_size() + #display the params + for param in filter(lambda p: p.get_hide() not in ('all', 'part'), self.get_params()): + layout = param.get_layout() + layouts.append(layout) + w,h = layout.get_pixel_size() + self.label_width = max(w, self.label_width) + self.label_height = self.label_height + h + LABEL_SEPARATION + width = self.label_width + height = self.label_height + #setup the pixmap + pixmap = self.get_parent().new_pixmap(width, height) + gc = pixmap.new_gc() + gc.set_foreground(self._bg_color) + pixmap.draw_rectangle(gc, True, 0, 0, width, height) + #draw the layouts + h_off = 0 + for i,layout in enumerate(layouts): + w,h = layout.get_pixel_size() + if i == 0: w_off = (width-w)/2 + else: w_off = 0 + pixmap.draw_layout(gc, w_off, h_off, layout) + h_off = h + h_off + LABEL_SEPARATION + #create vertical and horizontal images + self.horizontal_label = image = pixmap.get_image(0, 0, width, height) + if self.is_vertical(): + self.vertical_label = vimage = gtk.gdk.Image(gtk.gdk.IMAGE_NORMAL, pixmap.get_visual(), height, width) + for i in range(width): + for j in range(height): vimage.put_pixel(j, width-i-1, image.get_pixel(i, j)) + map(lambda p: p._create_labels(), self.get_ports()) + + def draw(self, gc, window): + """ + Draw the signal block with label and inputs/outputs. + @param gc the graphics context + @param window the gtk window to draw on + """ + x, y = self.get_coordinate() + #draw main block + Element.draw( + self, gc, window, bg_color=self._bg_color, + border_color=self.is_highlighted() and Colors.HIGHLIGHT_COLOR or Colors.BORDER_COLOR, + ) + #draw label image + if self.is_horizontal(): + window.draw_image(gc, self.horizontal_label, 0, 0, x+BLOCK_LABEL_PADDING, y+(self.H-self.label_height)/2, -1, -1) + elif self.is_vertical(): + window.draw_image(gc, self.vertical_label, 0, 0, x+(self.H-self.label_height)/2, y+BLOCK_LABEL_PADDING, -1, -1) + #draw ports + for port in self.get_ports(): port.draw(gc, window) + + def what_is_selected(self, coor, coor_m=None): + """ + Get the element that is selected. + @param coor the (x,y) tuple + @param coor_m the (x_m, y_m) tuple + @return this block, a port, or None + """ + for port in self.get_ports(): + port_selected = port.what_is_selected(coor, coor_m) + if port_selected: return port_selected + return Element.what_is_selected(self, coor, coor_m) diff --git a/grc/gui/BlockTreeWindow.py b/grc/gui/BlockTreeWindow.py new file mode 100644 index 000000000..379c4a6a2 --- /dev/null +++ b/grc/gui/BlockTreeWindow.py @@ -0,0 +1,168 @@ +""" +Copyright 2007, 2008, 2009 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +from Constants import DEFAULT_BLOCKS_WINDOW_WIDTH, DND_TARGETS +import Utils +import pygtk +pygtk.require('2.0') +import gtk +import gobject + +NAME_INDEX = 0 +KEY_INDEX = 1 +DOC_INDEX = 2 + +class BlockTreeWindow(gtk.VBox): + """The block selection panel.""" + + def __init__(self, platform, get_flow_graph): + """ + BlockTreeWindow constructor. + Create a tree view of the possible blocks in the platform. + The tree view nodes will be category names, the leaves will be block names. + A mouse double click or button press action will trigger the add block event. + @param platform the particular platform will all block prototypes + @param get_flow_graph get the selected flow graph + """ + gtk.VBox.__init__(self) + self.platform = platform + self.get_flow_graph = get_flow_graph + #make the tree model for holding blocks + self.treestore = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING) + self.treeview = gtk.TreeView(self.treestore) + self.treeview.set_enable_search(False) #disable pop up search box + self.treeview.add_events(gtk.gdk.BUTTON_PRESS_MASK) + self.treeview.connect('button_press_event', self._handle_mouse_button_press) + selection = self.treeview.get_selection() + selection.set_mode('single') + selection.connect('changed', self._handle_selection_change) + renderer = gtk.CellRendererText() + column = gtk.TreeViewColumn('Blocks', renderer, text=NAME_INDEX) + self.treeview.append_column(column) + #try to enable the tooltips (available in pygtk 2.12 and above) + try: self.treeview.set_tooltip_column(DOC_INDEX) + except: pass + #setup drag and drop + self.treeview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, DND_TARGETS, gtk.gdk.ACTION_COPY) + self.treeview.connect('drag-data-get', self._handle_drag_get_data) + #make the scrolled window to hold the tree view + scrolled_window = gtk.ScrolledWindow() + scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolled_window.add_with_viewport(self.treeview) + scrolled_window.set_size_request(DEFAULT_BLOCKS_WINDOW_WIDTH, -1) + self.pack_start(scrolled_window) + #add button + self.add_button = gtk.Button(None, gtk.STOCK_ADD) + self.add_button.connect('clicked', self._handle_add_button) + self.pack_start(self.add_button, False) + #map categories to iters, automatic mapping for root + self._categories = {tuple(): None} + #add blocks and categories + self.platform.load_block_tree(self) + #initialize + self._update_add_button() + + ############################################################ + ## Block Tree Methods + ############################################################ + def add_block(self, category, block=None): + """ + Add a block with category to this selection window. + Add only the category when block is None. + @param category the category list or path string + @param block the block object or None + """ + if isinstance(category, str): category = category.split('/') + category = tuple(filter(lambda x: x, category)) #tuple is hashable + #add category and all sub categories + for i, cat_name in enumerate(category): + sub_category = category[:i+1] + if sub_category not in self._categories: + iter = self.treestore.insert_before(self._categories[sub_category[:-1]], None) + self.treestore.set_value(iter, NAME_INDEX, '[ %s ]'%cat_name) + self.treestore.set_value(iter, KEY_INDEX, '') + self.treestore.set_value(iter, DOC_INDEX, Utils.xml_encode('Category: %s'%cat_name)) + self._categories[sub_category] = iter + #add block + if block is None: return + iter = self.treestore.insert_before(self._categories[category], None) + self.treestore.set_value(iter, NAME_INDEX, block.get_name()) + self.treestore.set_value(iter, KEY_INDEX, block.get_key()) + self.treestore.set_value(iter, DOC_INDEX, Utils.xml_encode(block.get_doc() or 'undocumented')) + + ############################################################ + ## Helper Methods + ############################################################ + def _get_selected_block_key(self): + """ + Get the currently selected block key. + @return the key of the selected block or a empty string + """ + selection = self.treeview.get_selection() + treestore, iter = selection.get_selected() + return iter and treestore.get_value(iter, KEY_INDEX) or '' + + def _update_add_button(self): + """ + Update the add button's sensitivity. + The button should be active only if a block is selected. + """ + key = self._get_selected_block_key() + self.add_button.set_sensitive(bool(key)) + + def _add_selected_block(self): + """ + Add the selected block with the given key to the flow graph. + """ + key = self._get_selected_block_key() + if key: self.get_flow_graph().add_new_block(key) + + ############################################################ + ## Event Handlers + ############################################################ + def _handle_drag_get_data(self, widget, drag_context, selection_data, info, time): + """ + Handle a drag and drop by setting the key to the selection object. + This will call the destination handler for drag and drop. + Only call set when the key is valid to ignore DND from categories. + """ + key = self._get_selected_block_key() + if key: selection_data.set(selection_data.target, 8, key) + + def _handle_mouse_button_press(self, widget, event): + """ + Handle the mouse button press. + If a left double click is detected, call add selected block. + """ + if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS: + self._add_selected_block() + + def _handle_selection_change(self, selection): + """ + Handle a selection change in the tree view. + If a selection changes, set the add button sensitive. + """ + self._update_add_button() + + def _handle_add_button(self, widget): + """ + Handle the add button clicked signal. + Call add selected block. + """ + self._add_selected_block() diff --git a/grc/gui/Colors.py b/grc/gui/Colors.py new file mode 100644 index 000000000..f0b989b37 --- /dev/null +++ b/grc/gui/Colors.py @@ -0,0 +1,37 @@ +""" +Copyright 2008 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import pygtk +pygtk.require('2.0') +import gtk + +_COLORMAP = gtk.gdk.colormap_get_system() #create all of the colors +def get_color(color_code): return _COLORMAP.alloc_color(color_code, True, True) + +HIGHLIGHT_COLOR = get_color('#00FFFF') +BORDER_COLOR = get_color('black') +#flow graph color constants +FLOWGRAPH_BACKGROUND_COLOR = get_color('#FFF9FF') +#block color constants +BLOCK_ENABLED_COLOR = get_color('#F1ECFF') +BLOCK_DISABLED_COLOR = get_color('#CCCCCC') +#connection color constants +CONNECTION_ENABLED_COLOR = get_color('black') +CONNECTION_DISABLED_COLOR = get_color('#999999') +CONNECTION_ERROR_COLOR = get_color('red') diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py new file mode 100644 index 000000000..013bcb00f --- /dev/null +++ b/grc/gui/Connection.py @@ -0,0 +1,140 @@ +""" +Copyright 2007, 2008 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import Utils +from Element import Element +import Colors +from Constants import CONNECTOR_ARROW_BASE, CONNECTOR_ARROW_HEIGHT + +class Connection(Element): + """ + A graphical connection for ports. + The connection has 2 parts, the arrow and the wire. + The coloring of the arrow and wire exposes the status of 3 states: + enabled/disabled, valid/invalid, highlighted/non-highlighted. + The wire coloring exposes the enabled and highlighted states. + The arrow coloring exposes the enabled and valid states. + """ + + def get_coordinate(self): + """ + Get the 0,0 coordinate. + Coordinates are irrelevant in connection. + @return 0, 0 + """ + return (0, 0) + + def get_rotation(self): + """ + Get the 0 degree rotation. + Rotations are irrelevant in connection. + @return 0 + """ + return 0 + + def update(self): + """Precalculate relative coordinates.""" + self._sink_rot = None + self._source_rot = None + self._sink_coor = None + self._source_coor = None + #get the source coordinate + connector_length = self.get_source().get_connector_length() + self.x1, self.y1 = Utils.get_rotated_coordinate((connector_length, 0), self.get_source().get_rotation()) + #get the sink coordinate + connector_length = self.get_sink().get_connector_length() + CONNECTOR_ARROW_HEIGHT + self.x2, self.y2 = Utils.get_rotated_coordinate((-connector_length, 0), self.get_sink().get_rotation()) + #build the arrow + self.arrow = [(0, 0), + Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, -CONNECTOR_ARROW_BASE/2), self.get_sink().get_rotation()), + Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, CONNECTOR_ARROW_BASE/2), self.get_sink().get_rotation()), + ] + self._update_after_move() + if not self.get_enabled(): self._arrow_color = Colors.CONNECTION_DISABLED_COLOR + elif not self.is_valid(): self._arrow_color = Colors.CONNECTION_ERROR_COLOR + else: self._arrow_color = Colors.CONNECTION_ENABLED_COLOR + + def _update_after_move(self): + """Calculate coordinates.""" + self.clear() + #source connector + source = self.get_source() + X, Y = source.get_connector_coordinate() + x1, y1 = self.x1 + X, self.y1 + Y + self.add_line((x1, y1), (X, Y)) + #sink connector + sink = self.get_sink() + X, Y = sink.get_connector_coordinate() + x2, y2 = self.x2 + X, self.y2 + Y + self.add_line((x2, y2), (X, Y)) + #adjust arrow + self._arrow = [(x+X, y+Y) for x,y in self.arrow] + #add the horizontal and vertical lines in this connection + if abs(source.get_connector_direction() - sink.get_connector_direction()) == 180: + #2 possible point sets to create a 3-line connector + mid_x, mid_y = (x1 + x2)/2.0, (y1 + y2)/2.0 + points = [((mid_x, y1), (mid_x, y2)), ((x1, mid_y), (x2, mid_y))] + #source connector -> points[0][0] should be in the direction of source (if possible) + if Utils.get_angle_from_coordinates((x1, y1), points[0][0]) != source.get_connector_direction(): points.reverse() + #points[0][0] -> sink connector should not be in the direction of sink + if Utils.get_angle_from_coordinates(points[0][0], (x2, y2)) == sink.get_connector_direction(): points.reverse() + #points[0][0] -> source connector should not be in the direction of source + if Utils.get_angle_from_coordinates(points[0][0], (x1, y1)) == source.get_connector_direction(): points.reverse() + #create 3-line connector + p1, p2 = map(int, points[0][0]), map(int, points[0][1]) + self.add_line((x1, y1), p1) + self.add_line(p1, p2) + self.add_line((x2, y2), p2) + else: + #2 possible points to create a right-angled connector + points = [(x1, y2), (x2, y1)] + #source connector -> points[0] should be in the direction of source (if possible) + if Utils.get_angle_from_coordinates((x1, y1), points[0]) != source.get_connector_direction(): points.reverse() + #points[0] -> sink connector should not be in the direction of sink + if Utils.get_angle_from_coordinates(points[0], (x2, y2)) == sink.get_connector_direction(): points.reverse() + #points[0] -> source connector should not be in the direction of source + if Utils.get_angle_from_coordinates(points[0], (x1, y1)) == source.get_connector_direction(): points.reverse() + #create right-angled connector + self.add_line((x1, y1), points[0]) + self.add_line((x2, y2), points[0]) + + def draw(self, gc, window): + """ + Draw the connection. + @param gc the graphics context + @param window the gtk window to draw on + """ + sink = self.get_sink() + source = self.get_source() + #check for changes + if self._sink_rot != sink.get_rotation() or self._source_rot != source.get_rotation(): self.update() + elif self._sink_coor != sink.get_coordinate() or self._source_coor != source.get_coordinate(): self._update_after_move() + #cache values + self._sink_rot = sink.get_rotation() + self._source_rot = source.get_rotation() + self._sink_coor = sink.get_coordinate() + self._source_coor = source.get_coordinate() + #draw + if self.is_highlighted(): border_color = Colors.HIGHLIGHT_COLOR + elif self.get_enabled(): border_color = Colors.CONNECTION_ENABLED_COLOR + else: border_color = Colors.CONNECTION_DISABLED_COLOR + Element.draw(self, gc, window, bg_color=None, border_color=border_color) + #draw arrow on sink port + gc.set_foreground(self._arrow_color) + window.draw_polygon(gc, True, self._arrow) diff --git a/grc/gui/Constants.py b/grc/gui/Constants.py new file mode 100644 index 000000000..7fabcfc0a --- /dev/null +++ b/grc/gui/Constants.py @@ -0,0 +1,83 @@ +""" +Copyright 2008, 2009 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import pygtk +pygtk.require('2.0') +import gtk +import os + +##default path for the open/save dialogs +DEFAULT_FILE_PATH = os.getcwd() + +##file extensions +IMAGE_FILE_EXTENSION = '.png' + +##name for new/unsaved flow graphs +NEW_FLOGRAPH_TITLE = 'untitled' + +##main window constraints +MIN_WINDOW_WIDTH = 600 +MIN_WINDOW_HEIGHT = 400 +##dialog constraints +MIN_DIALOG_WIDTH = 500 +MIN_DIALOG_HEIGHT = 500 +##default sizes +DEFAULT_BLOCKS_WINDOW_WIDTH = 100 +DEFAULT_REPORTS_WINDOW_WIDTH = 100 + +##The size of the state saving cache in the flow graph (for undo/redo functionality) +STATE_CACHE_SIZE = 42 + +##Shared targets for drag and drop of blocks +DND_TARGETS = [('STRING', gtk.TARGET_SAME_APP, 0)] + +#label constraint dimensions +LABEL_SEPARATION = 3 +BLOCK_LABEL_PADDING = 7 +PORT_LABEL_PADDING = 2 + +#port constraint dimensions +PORT_SEPARATION = 17 +PORT_BORDER_SEPARATION = 9 +PORT_MIN_WIDTH = 20 + +#minimal length of connector +CONNECTOR_EXTENSION_MINIMAL = 11 + +#increment length for connector +CONNECTOR_EXTENSION_INCREMENT = 11 + +#connection arrow dimensions +CONNECTOR_ARROW_BASE = 13 +CONNECTOR_ARROW_HEIGHT = 17 + +#possible rotations in degrees +POSSIBLE_ROTATIONS = (0, 90, 180, 270) + +#How close can the mouse get to the window border before mouse events are ignored. +BORDER_PROXIMITY_SENSITIVITY = 50 + +#How close the mouse can get to the edge of the visible window before scrolling is invoked. +SCROLL_PROXIMITY_SENSITIVITY = 30 + +#When the window has to be scrolled, move it this distance in the required direction. +SCROLL_DISTANCE = 15 + +#How close the mouse click can be to a line and register a connection select. +LINE_SELECT_SENSITIVITY = 5 diff --git a/grc/gui/Dialogs.py b/grc/gui/Dialogs.py new file mode 100644 index 000000000..8d764e28e --- /dev/null +++ b/grc/gui/Dialogs.py @@ -0,0 +1,105 @@ +""" +Copyright 2008, 2009 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import pygtk +pygtk.require('2.0') +import gtk +import Preferences +import Utils + +class TextDisplay(gtk.TextView): + """A non editable gtk text view.""" + + def __init__(self, text=''): + """ + TextDisplay constructor. + @param text the text to display (string) + """ + text_buffer = gtk.TextBuffer() + text_buffer.set_text(text) + self.set_text = text_buffer.set_text + self.insert = lambda line: text_buffer.insert(text_buffer.get_end_iter(), line) + gtk.TextView.__init__(self, text_buffer) + self.set_editable(False) + self.set_cursor_visible(False) + self.set_wrap_mode(gtk.WRAP_WORD_CHAR) + +def MessageDialogHelper(type, buttons, title=None, markup=None): + """ + Create a modal message dialog and run it. + @param type the type of message: gtk.MESSAGE_INFO, gtk.MESSAGE_WARNING, gtk.MESSAGE_QUESTION or gtk.MESSAGE_ERROR + @param buttons the predefined set of buttons to use: + gtk.BUTTONS_NONE, gtk.BUTTONS_OK, gtk.BUTTONS_CLOSE, gtk.BUTTONS_CANCEL, gtk.BUTTONS_YES_NO, gtk.BUTTONS_OK_CANCEL + @param tittle the title of the window (string) + @param markup the message text with pango markup + @return the gtk response from run() + """ + message_dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, type, buttons) + if title: message_dialog.set_title(title) + if markup: message_dialog.set_markup(markup) + response = message_dialog.run() + message_dialog.destroy() + return response + +class AboutDialog(gtk.AboutDialog): + """A cute little about dialog.""" + + def __init__(self, platform): + """AboutDialog constructor.""" + gtk.AboutDialog.__init__(self) + self.set_name(platform.get_name()) + self.set_version(platform.get_version()) + self.set_license(platform.get_license()) + self.set_copyright(platform.get_license().splitlines()[0]) + self.set_website(platform.get_website()) + self.run() + self.destroy() + +def HelpDialog(): MessageDialogHelper( + type=gtk.MESSAGE_INFO, + buttons=gtk.BUTTONS_CLOSE, + title='Help', + markup="""\ +Usage Tips + +Add block: drag and drop or double click a block in the block selection window. +Rotate block: Select a block, press left/right on the keyboard. +Change type: Select a block, press up/down on the keyboard. +Edit parameters: double click on a block in the flow graph. +Make connection: click on the source port of one block, then click on the sink port of another block. +Remove connection: select the connection and press delete, or drag the connection. + +* See the menu for other keyboard shortcuts.""") + +COLORS_DIALOG_MARKUP_TMPL = """\ +Color Mapping + +#if $colors + #set $max_len = max([len(color[0]) for color in $colors]) + 10 + #for $title, $color_spec in $colors +$($encode($title).center($max_len)) + #end for +#end if +""" + +def ColorsDialog(platform): MessageDialogHelper( + type=gtk.MESSAGE_INFO, + buttons=gtk.BUTTONS_CLOSE, + title='Colors', + markup=Utils.parse_template(COLORS_DIALOG_MARKUP_TMPL, colors=platform.get_colors())) diff --git a/grc/gui/DrawingArea.py b/grc/gui/DrawingArea.py new file mode 100644 index 000000000..6f90049c5 --- /dev/null +++ b/grc/gui/DrawingArea.py @@ -0,0 +1,126 @@ +""" +Copyright 2007, 2008, 2009 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import pygtk +pygtk.require('2.0') +import gtk +from Constants import MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT, DND_TARGETS + +class DrawingArea(gtk.DrawingArea): + """ + DrawingArea is the gtk pixel map that graphical elements may draw themselves on. + The drawing area also responds to mouse and key events. + """ + + def __init__(self, flow_graph): + """ + DrawingArea contructor. + Connect event handlers. + @param main_window the main_window containing all flow graphs + """ + self.ctrl_mask = False + self._flow_graph = flow_graph + gtk.DrawingArea.__init__(self) + self.set_size_request(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT) + self.connect('realize', self._handle_window_realize) + self.connect('configure-event', self._handle_window_configure) + self.connect('expose-event', self._handle_window_expose) + self.connect('motion-notify-event', self._handle_mouse_motion) + self.connect('button-press-event', self._handle_mouse_button_press) + self.connect('button-release-event', self._handle_mouse_button_release) + self.add_events( + gtk.gdk.BUTTON_PRESS_MASK | \ + gtk.gdk.POINTER_MOTION_MASK | \ + gtk.gdk.BUTTON_RELEASE_MASK | \ + gtk.gdk.LEAVE_NOTIFY_MASK | \ + gtk.gdk.ENTER_NOTIFY_MASK + ) + #setup drag and drop + self.drag_dest_set(gtk.DEST_DEFAULT_ALL, DND_TARGETS, gtk.gdk.ACTION_COPY) + self.connect('drag-data-received', self._handle_drag_data_received) + #setup the focus flag + self._focus_flag = False + self.get_focus_flag = lambda: self._focus_flag + def _handle_focus_event(widget, event, focus_flag): self._focus_flag = focus_flag + self.connect('leave-notify-event', _handle_focus_event, False) + self.connect('enter-notify-event', _handle_focus_event, True) + + def new_pixmap(self, width, height): return gtk.gdk.Pixmap(self.window, width, height, -1) + + ########################################################################## + ## Handlers + ########################################################################## + def _handle_drag_data_received(self, widget, drag_context, x, y, selection_data, info, time): + """ + Handle a drag and drop by adding a block at the given coordinate. + """ + self._flow_graph.add_new_block(selection_data.data, (x, y)) + + def _handle_mouse_button_press(self, widget, event): + """ + Forward button click information to the flow graph. + """ + self.ctrl_mask = event.state & gtk.gdk.CONTROL_MASK + self._flow_graph.handle_mouse_button_press( + left_click=(event.button == 1), + double_click=(event.type == gtk.gdk._2BUTTON_PRESS), + coordinate=(event.x, event.y), + ) + + def _handle_mouse_button_release(self, widget, event): + """ + Forward button release information to the flow graph. + """ + self.ctrl_mask = event.state & gtk.gdk.CONTROL_MASK + self._flow_graph.handle_mouse_button_release( + left_click=(event.button == 1), + coordinate=(event.x, event.y), + ) + + def _handle_mouse_motion(self, widget, event): + """ + Forward mouse motion information to the flow graph. + """ + self.ctrl_mask = event.state & gtk.gdk.CONTROL_MASK + self._flow_graph.handle_mouse_motion( + coordinate=(event.x, event.y), + ) + + def _handle_window_realize(self, widget): + """ + Called when the window is realized. + Update the flowgraph, which calls new pixmap. + """ + self._flow_graph.update() + + def _handle_window_configure(self, widget, event): + """ + Called when the window is resized. + Create a new pixmap for background buffer. + """ + self._pixmap = self.new_pixmap(*self.get_size_request()) + + def _handle_window_expose(self, widget, event): + """ + Called when window is exposed, or queue_draw is called. + Double buffering: draw to pixmap, then draw pixmap to window. + """ + gc = self.window.new_gc() + self._flow_graph.draw(gc, self._pixmap) + self.window.draw_drawable(gc, self._pixmap, 0, 0, 0, 0, -1, -1) diff --git a/grc/gui/Element.py b/grc/gui/Element.py new file mode 100644 index 000000000..315191723 --- /dev/null +++ b/grc/gui/Element.py @@ -0,0 +1,226 @@ +""" +Copyright 2007 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import Colors +import pygtk +pygtk.require('2.0') +import gtk +import pango +from Constants import LINE_SELECT_SENSITIVITY +from Constants import POSSIBLE_ROTATIONS + +class Element(object): + """ + GraphicalElement is the base class for all graphical elements. + It contains an X,Y coordinate, a list of rectangular areas that the element occupies, + and methods to detect selection of those areas. + """ + + def __init__(self, *args, **kwargs): + """ + Make a new list of rectangular areas and lines, and set the coordinate and the rotation. + """ + self.set_rotation(POSSIBLE_ROTATIONS[0]) + self.set_coordinate((0, 0)) + self.clear() + self.set_highlighted(False) + + def is_horizontal(self, rotation=None): + """ + Is this element horizontal? + If rotation is None, use this element's rotation. + @param rotation the optional rotation + @return true if rotation is horizontal + """ + rotation = rotation or self.get_rotation() + return rotation in (0, 180) + + def is_vertical(self, rotation=None): + """ + Is this element vertical? + If rotation is None, use this element's rotation. + @param rotation the optional rotation + @return true if rotation is vertical + """ + rotation = rotation or self.get_rotation() + return rotation in (90, 270) + + def draw(self, gc, window, border_color, bg_color): + """ + Draw in the given window. + @param gc the graphics context + @param window the gtk window to draw on + @param border_color the color for lines and rectangle borders + @param bg_color the color for the inside of the rectangle + """ + X,Y = self.get_coordinate() + for (rX,rY),(W,H) in self.areas_dict[self.get_rotation()]: + aX = X + rX + aY = Y + rY + gc.set_foreground(bg_color) + window.draw_rectangle(gc, True, aX, aY, W, H) + gc.set_foreground(border_color) + window.draw_rectangle(gc, False, aX, aY, W, H) + for (x1, y1),(x2, y2) in self.lines_dict[self.get_rotation()]: + gc.set_foreground(border_color) + window.draw_line(gc, X+x1, Y+y1, X+x2, Y+y2) + + def rotate(self, rotation): + """ + Rotate all of the areas by 90 degrees. + @param rotation multiple of 90 degrees + """ + self.set_rotation((self.get_rotation() + rotation)%360) + + def clear(self): + """Empty the lines and areas.""" + self.areas_dict = dict((rotation, list()) for rotation in POSSIBLE_ROTATIONS) + self.lines_dict = dict((rotation, list()) for rotation in POSSIBLE_ROTATIONS) + + def set_coordinate(self, coor): + """ + Set the reference coordinate. + @param coor the coordinate tuple (x,y) + """ + self.coor = coor + + def get_parent(self): + """ + Get the parent of this element. + @return the parent + """ + return self.parent + + def set_highlighted(self, highlighted): + """ + Set the highlight status. + @param highlighted true to enable highlighting + """ + self.highlighted = highlighted + + def is_highlighted(self): + """ + Get the highlight status. + @return true if highlighted + """ + return self.highlighted + + def get_coordinate(self): + """Get the coordinate. + @return the coordinate tuple (x,y) + """ + return self.coor + + def move(self, delta_coor): + """ + Move the element by adding the delta_coor to the current coordinate. + @param delta_coor (delta_x,delta_y) tuple + """ + deltaX, deltaY = delta_coor + X, Y = self.get_coordinate() + self.set_coordinate((X+deltaX, Y+deltaY)) + + def add_area(self, rel_coor, area, rotation=None): + """ + Add an area to the area list. + An area is actually a coordinate relative to the main coordinate + with a width/height pair relative to the area coordinate. + A positive width is to the right of the coordinate. + A positive height is above the coordinate. + The area is associated with a rotation. + If rotation is not specified, the element's current rotation is used. + @param rel_coor (x,y) offset from this element's coordinate + @param area (width,height) tuple + @param rotation rotation in degrees + """ + self.areas_dict[rotation or self.get_rotation()].append((rel_coor, area)) + + def add_line(self, rel_coor1, rel_coor2, rotation=None): + """ + Add a line to the line list. + A line is defined by 2 relative coordinates. + Lines must be horizontal or vertical. + The line is associated with a rotation. + If rotation is not specified, the element's current rotation is used. + @param rel_coor1 relative (x1,y1) tuple + @param rel_coor2 relative (x2,y2) tuple + @param rotation rotation in degrees + """ + self.lines_dict[rotation or self.get_rotation()].append((rel_coor1, rel_coor2)) + + def what_is_selected(self, coor, coor_m=None): + """ + One coordinate specified: + Is this element selected at given coordinate? + ie: is the coordinate encompassed by one of the areas or lines? + Both coordinates specified: + Is this element within the rectangular region defined by both coordinates? + ie: do any area corners or line endpoints fall within the region? + @param coor the selection coordinate, tuple x, y + @param coor_m an additional selection coordinate. + @return self if one of the areas/lines encompasses coor, else None. + """ + #function to test if p is between a and b (inclusive) + in_between = lambda p, a, b: p >= min(a, b) and p <= max(a, b) + #relative coordinate + x, y = [a-b for a,b in zip(coor, self.get_coordinate())] + if coor_m: + x_m, y_m = [a-b for a,b in zip(coor_m, self.get_coordinate())] + #handle rectangular areas + for (x1,y1), (w,h) in self.areas_dict[self.get_rotation()]: + if in_between(x1, x, x_m) and in_between(y1, y, y_m) or \ + in_between(x1+w, x, x_m) and in_between(y1, y, y_m) or \ + in_between(x1, x, x_m) and in_between(y1+h, y, y_m) or \ + in_between(x1+w, x, x_m) and in_between(y1+h, y, y_m): + return self + #handle horizontal or vertical lines + for (x1, y1), (x2, y2) in self.lines_dict[self.get_rotation()]: + if in_between(x1, x, x_m) and in_between(y1, y, y_m) or \ + in_between(x2, x, x_m) and in_between(y2, y, y_m): + return self + return None + else: + #handle rectangular areas + for (x1,y1), (w,h) in self.areas_dict[self.get_rotation()]: + if in_between(x, x1, x1+w) and in_between(y, y1, y1+h): return self + #handle horizontal or vertical lines + for (x1, y1), (x2, y2) in self.lines_dict[self.get_rotation()]: + if x1 == x2: x1, x2 = x1-LINE_SELECT_SENSITIVITY, x2+LINE_SELECT_SENSITIVITY + if y1 == y2: y1, y2 = y1-LINE_SELECT_SENSITIVITY, y2+LINE_SELECT_SENSITIVITY + if in_between(x, x1, x2) and in_between(y, y1, y2): return self + return None + + def get_rotation(self): + """ + Get the rotation in degrees. + @return the rotation + """ + return self.rotation + + def set_rotation(self, rotation): + """ + Set the rotation in degrees. + @param rotation the rotation""" + if rotation not in POSSIBLE_ROTATIONS: + raise Exception('"%s" is not one of the possible rotations: (%s)'%(rotation, POSSIBLE_ROTATIONS)) + self.rotation = rotation + + def update(self): + """Do nothing for the update. Dummy method.""" + pass diff --git a/grc/gui/FileDialogs.py b/grc/gui/FileDialogs.py new file mode 100644 index 000000000..3b210c33f --- /dev/null +++ b/grc/gui/FileDialogs.py @@ -0,0 +1,175 @@ +""" +Copyright 2007 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import pygtk +pygtk.require('2.0') +import gtk +from Dialogs import MessageDialogHelper +from Constants import \ + DEFAULT_FILE_PATH, IMAGE_FILE_EXTENSION, \ + NEW_FLOGRAPH_TITLE +import Preferences +from os import path +import Utils + +################################################## +# Constants +################################################## +OPEN_FLOW_GRAPH = 'open flow graph' +SAVE_FLOW_GRAPH = 'save flow graph' +SAVE_IMAGE = 'save image' + +FILE_OVERWRITE_MARKUP_TMPL="""\ +File $encode($filename) Exists!\nWould you like to overwrite the existing file?""" + +FILE_DNE_MARKUP_TMPL="""\ +File $encode($filename) Does not Exist!""" + +################################################## +# File Filters +################################################## +##the filter for flow graph files +def get_flow_graph_files_filter(): + filter = gtk.FileFilter() + filter.set_name('Flow Graph Files') + filter.add_pattern('*'+Preferences.file_extension()) + return filter + +##the filter for image files +def get_image_files_filter(): + filter = gtk.FileFilter() + filter.set_name('Image Files') + filter.add_pattern('*'+IMAGE_FILE_EXTENSION) + return filter + +##the filter for all files +def get_all_files_filter(): + filter = gtk.FileFilter() + filter.set_name('All Files') + filter.add_pattern('*') + return filter + +################################################## +# File Dialogs +################################################## +class FileDialogHelper(gtk.FileChooserDialog): + """ + A wrapper class for the gtk file chooser dialog. + Implement a file chooser dialog with only necessary parameters. + """ + + def __init__(self, action, title): + """ + FileDialogHelper contructor. + Create a save or open dialog with cancel and ok buttons. + Use standard settings: no multiple selection, local files only, and the * filter. + @param action gtk.FILE_CHOOSER_ACTION_OPEN or gtk.FILE_CHOOSER_ACTION_SAVE + @param title the title of the dialog (string) + """ + ok_stock = {gtk.FILE_CHOOSER_ACTION_OPEN : 'gtk-open', gtk.FILE_CHOOSER_ACTION_SAVE : 'gtk-save'}[action] + gtk.FileChooserDialog.__init__(self, title, None, action, ('gtk-cancel', gtk.RESPONSE_CANCEL, ok_stock, gtk.RESPONSE_OK)) + self.set_select_multiple(False) + self.set_local_only(True) + self.add_filter(get_all_files_filter()) + +class FileDialog(FileDialogHelper): + """A dialog box to save or open flow graph files. This is a base class, do not use.""" + + def __init__(self, current_file_path=''): + """ + FileDialog constructor. + @param current_file_path the current directory or path to the open flow graph + """ + if not current_file_path: current_file_path = path.join(DEFAULT_FILE_PATH, NEW_FLOGRAPH_TITLE + Preferences.file_extension()) + if self.type == OPEN_FLOW_GRAPH: + FileDialogHelper.__init__(self, gtk.FILE_CHOOSER_ACTION_OPEN, 'Open a Flow Graph from a File...') + self.add_and_set_filter(get_flow_graph_files_filter()) + self.set_select_multiple(True) + elif self.type == SAVE_FLOW_GRAPH: + FileDialogHelper.__init__(self, gtk.FILE_CHOOSER_ACTION_SAVE, 'Save a Flow Graph to a File...') + self.add_and_set_filter(get_flow_graph_files_filter()) + self.set_current_name(path.basename(current_file_path)) #show the current filename + elif self.type == SAVE_IMAGE: + FileDialogHelper.__init__(self, gtk.FILE_CHOOSER_ACTION_SAVE, 'Save a Flow Graph Screen Shot...') + self.add_and_set_filter(get_image_files_filter()) + current_file_path = current_file_path + IMAGE_FILE_EXTENSION + self.set_current_name(path.basename(current_file_path)) #show the current filename + self.set_current_folder(path.dirname(current_file_path)) #current directory + + def add_and_set_filter(self, filter): + """ + Add the gtk file filter to the list of filters and set it as the default file filter. + @param filter a gtk file filter. + """ + self.add_filter(filter) + self.set_filter(filter) + + def get_rectified_filename(self): + """ + Run the dialog and get the filename. + If this is a save dialog and the file name is missing the extension, append the file extension. + If the file name with the extension already exists, show a overwrite dialog. + If this is an open dialog, return a list of filenames. + @return the complete file path + """ + if gtk.FileChooserDialog.run(self) != gtk.RESPONSE_OK: return None #response was cancel + ############################################# + # Handle Save Dialogs + ############################################# + if self.type in (SAVE_FLOW_GRAPH, SAVE_IMAGE): + filename = self.get_filename() + extension = { + SAVE_FLOW_GRAPH: Preferences.file_extension(), + SAVE_IMAGE: IMAGE_FILE_EXTENSION, + }[self.type] + #append the missing file extension if the filter matches + if path.splitext(filename)[1].lower() != extension: filename += extension + self.set_current_name(path.basename(filename)) #show the filename with extension + if path.exists(filename): #ask the user to confirm overwrite + if MessageDialogHelper( + gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, 'Confirm Overwrite!', + Utils.parse_template(FILE_OVERWRITE_MARKUP_TMPL, filename=filename), + ) == gtk.RESPONSE_NO: return self.get_rectified_filename() + return filename + ############################################# + # Handle Open Dialogs + ############################################# + elif self.type in (OPEN_FLOW_GRAPH,): + filenames = self.get_filenames() + for filename in filenames: + if not path.exists(filename): #show a warning and re-run + MessageDialogHelper( + gtk.MESSAGE_WARNING, gtk.BUTTONS_CLOSE, 'Cannot Open!', + Utils.parse_template(FILE_DNE_MARKUP_TMPL, filename=filename), + ) + return self.get_rectified_filename() + return filenames + + def run(self): + """ + Get the filename and destroy the dialog. + @return the filename or None if a close/cancel occured. + """ + filename = self.get_rectified_filename() + self.destroy() + return filename + +class OpenFlowGraphFileDialog(FileDialog): type = OPEN_FLOW_GRAPH +class SaveFlowGraphFileDialog(FileDialog): type = SAVE_FLOW_GRAPH +class SaveImageFileDialog(FileDialog): type = SAVE_IMAGE diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py new file mode 100644 index 000000000..26544faab --- /dev/null +++ b/grc/gui/FlowGraph.py @@ -0,0 +1,489 @@ +""" +Copyright 2007, 2008, 2009 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +from Constants import SCROLL_PROXIMITY_SENSITIVITY, SCROLL_DISTANCE +from Actions import \ + ELEMENT_CREATE, ELEMENT_SELECT, \ + BLOCK_PARAM_MODIFY, BLOCK_MOVE, \ + ELEMENT_DELETE +import Colors +import Utils +from Element import Element +from .. base import FlowGraph as _FlowGraph +import pygtk +pygtk.require('2.0') +import gtk +import random +import Messages + +class FlowGraph(Element): + """ + FlowGraph is the data structure to store graphical signal blocks, + graphical inputs and outputs, + and the connections between inputs and outputs. + """ + + def __init__(self, *args, **kwargs): + """ + FlowGraph contructor. + Create a list for signal blocks and connections. Connect mouse handlers. + """ + Element.__init__(self) + #when is the flow graph selected? (used by keyboard event handler) + self.is_selected = lambda: bool(self.get_selected_elements()) + #important vars dealing with mouse event tracking + self.element_moved = False + self.mouse_pressed = False + self.unselect() + self.press_coor = (0, 0) + #selected ports + self._old_selected_port = None + self._new_selected_port = None + + ########################################################################### + # Access Drawing Area + ########################################################################### + def get_drawing_area(self): return self.drawing_area + def queue_draw(self): self.get_drawing_area().queue_draw() + def get_size(self): return self.get_drawing_area().get_size_request() + def set_size(self, *args): self.get_drawing_area().set_size_request(*args) + def get_scroll_pane(self): return self.drawing_area.get_parent() + def get_ctrl_mask(self): return self.drawing_area.ctrl_mask + def new_pixmap(self, *args): return self.get_drawing_area().new_pixmap(*args) + + def add_new_block(self, key, coor=None): + """ + Add a block of the given key to this flow graph. + @param key the block key + @param coor an optional coordinate or None for random + """ + id = self._get_unique_id(key) + #calculate the position coordinate + h_adj = self.get_scroll_pane().get_hadjustment() + v_adj = self.get_scroll_pane().get_vadjustment() + if coor is None: coor = ( + int(random.uniform(.25, .75)*h_adj.page_size + h_adj.get_value()), + int(random.uniform(.25, .75)*v_adj.page_size + v_adj.get_value()), + ) + #get the new block + block = self.get_new_block(key) + block.set_coordinate(coor) + block.set_rotation(0) + block.get_param('id').set_value(id) + self.handle_states(ELEMENT_CREATE) + + ########################################################################### + # Copy Paste + ########################################################################### + def copy_to_clipboard(self): + """ + Copy the selected blocks and connections into the clipboard. + @return the clipboard + """ + #get selected blocks + blocks = self.get_selected_blocks() + if not blocks: return None + #calc x and y min + x_min, y_min = blocks[0].get_coordinate() + for block in blocks: + x, y = block.get_coordinate() + x_min = min(x, x_min) + y_min = min(y, y_min) + #get connections between selected blocks + connections = filter( + lambda c: c.get_source().get_parent() in blocks and c.get_sink().get_parent() in blocks, + self.get_connections(), + ) + clipboard = ( + (x_min, y_min), + [block.export_data() for block in blocks], + [connection.export_data() for connection in connections], + ) + return clipboard + + def paste_from_clipboard(self, clipboard): + """ + Paste the blocks and connections from the clipboard. + @param clipboard the nested data of blocks, connections + """ + selected = set() + (x_min, y_min), blocks_n, connections_n = clipboard + old_id2block = dict() + #recalc the position + h_adj = self.get_scroll_pane().get_hadjustment() + v_adj = self.get_scroll_pane().get_vadjustment() + x_off = h_adj.get_value() - x_min + h_adj.page_size/4 + y_off = v_adj.get_value() - y_min + v_adj.page_size/4 + #create blocks + for block_n in blocks_n: + block_key = block_n.find('key') + if block_key == 'options': continue + block = self.get_new_block(block_key) + selected.add(block) + #set params + params_n = block_n.findall('param') + for param_n in params_n: + param_key = param_n.find('key') + param_value = param_n.find('value') + #setup id parameter + if param_key == 'id': + old_id2block[param_value] = block + #if the block id is not unique, get a new block id + if param_value in [block.get_id() for block in self.get_blocks()]: + param_value = self._get_unique_id(param_value) + #set value to key + block.get_param(param_key).set_value(param_value) + #move block to offset coordinate + block.move((x_off, y_off)) + #update before creating connections + self.update() + #create connections + for connection_n in connections_n: + source = old_id2block[connection_n.find('source_block_id')].get_source(connection_n.find('source_key')) + sink = old_id2block[connection_n.find('sink_block_id')].get_sink(connection_n.find('sink_key')) + self.connect(source, sink) + #set all pasted elements selected + for block in selected: selected = selected.union(set(block.get_connections())) + self._selected_elements = list(selected) + + ########################################################################### + # Modify Selected + ########################################################################### + def type_controller_modify_selected(self, direction): + """ + Change the registered type controller for the selected signal blocks. + @param direction +1 or -1 + @return true for change + """ + return any([sb.type_controller_modify(direction) for sb in self.get_selected_blocks()]) + + def port_controller_modify_selected(self, direction): + """ + Change port controller for the selected signal blocks. + @param direction +1 or -1 + @return true for changed + """ + return any([sb.port_controller_modify(direction) for sb in self.get_selected_blocks()]) + + def enable_selected(self, enable): + """ + Enable/disable the selected blocks. + @param enable true to enable + @return true if changed + """ + changed = False + for selected_block in self.get_selected_blocks(): + if selected_block.get_enabled() != enable: + selected_block.set_enabled(enable) + changed = True + return changed + + def move_selected(self, delta_coordinate): + """ + Move the element and by the change in coordinates. + @param delta_coordinate the change in coordinates + """ + for selected_block in self.get_selected_blocks(): + selected_block.move(delta_coordinate) + self.element_moved = True + + def rotate_selected(self, rotation): + """ + Rotate the selected blocks by multiples of 90 degrees. + @param rotation the rotation in degrees + @return true if changed, otherwise false. + """ + if not self.get_selected_blocks(): return False + #initialize min and max coordinates + min_x, min_y = self.get_selected_block().get_coordinate() + max_x, max_y = self.get_selected_block().get_coordinate() + #rotate each selected block, and find min/max coordinate + for selected_block in self.get_selected_blocks(): + selected_block.rotate(rotation) + #update the min/max coordinate + x, y = selected_block.get_coordinate() + min_x, min_y = min(min_x, x), min(min_y, y) + max_x, max_y = max(max_x, x), max(max_y, y) + #calculate center point of slected blocks + ctr_x, ctr_y = (max_x + min_x)/2, (max_y + min_y)/2 + #rotate the blocks around the center point + for selected_block in self.get_selected_blocks(): + x, y = selected_block.get_coordinate() + x, y = Utils.get_rotated_coordinate((x - ctr_x, y - ctr_y), rotation) + selected_block.set_coordinate((x + ctr_x, y + ctr_y)) + return True + + def remove_selected(self): + """ + Remove selected elements + @return true if changed. + """ + changed = False + for selected_element in self.get_selected_elements(): + self.remove_element(selected_element) + changed = True + return changed + + def draw(self, gc, window): + """ + Draw the background and grid if enabled. + Draw all of the elements in this flow graph onto the pixmap. + Draw the pixmap to the drawable window of this flow graph. + """ + W,H = self.get_size() + #draw the background + gc.set_foreground(Colors.FLOWGRAPH_BACKGROUND_COLOR) + window.draw_rectangle(gc, True, 0, 0, W, H) + #draw multi select rectangle + if self.mouse_pressed and (not self.get_selected_elements() or self.get_ctrl_mask()): + #coordinates + x1, y1 = self.press_coor + x2, y2 = self.get_coordinate() + #calculate top-left coordinate and width/height + x, y = int(min(x1, x2)), int(min(y1, y2)) + w, h = int(abs(x1 - x2)), int(abs(y1 - y2)) + #draw + gc.set_foreground(Colors.HIGHLIGHT_COLOR) + window.draw_rectangle(gc, True, x, y, w, h) + gc.set_foreground(Colors.BORDER_COLOR) + window.draw_rectangle(gc, False, x, y, w, h) + #draw blocks on top of connections + for element in self.get_connections() + self.get_blocks(): + element.draw(gc, window) + #draw selected blocks on top of selected connections + for selected_element in self.get_selected_connections() + self.get_selected_blocks(): + selected_element.draw(gc, window) + + def update_selected(self): + """ + Remove deleted elements from the selected elements list. + Update highlighting so only the selected are highlighted. + """ + selected_elements = self.get_selected_elements() + elements = self.get_elements() + #remove deleted elements + for selected in selected_elements: + if selected in elements: continue + selected_elements.remove(selected) + #update highlighting + for element in elements: + element.set_highlighted(element in selected_elements) + + def update(self): + """ + Call update on all elements. + """ + self.validate() + for element in self.get_elements(): element.update() + + ########################################################################## + ## Get Selected + ########################################################################## + def unselect(self): + """ + Set selected elements to an empty set. + """ + self._selected_elements = [] + + def what_is_selected(self, coor, coor_m=None): + """ + What is selected? + At the given coordinate, return the elements found to be selected. + If coor_m is unspecified, return a list of only the first element found to be selected: + Iterate though the elements backwards since top elements are at the end of the list. + If an element is selected, place it at the end of the list so that is is drawn last, + and hence on top. Update the selected port information. + @param coor the coordinate of the mouse click + @param coor_m the coordinate for multi select + @return the selected blocks and connections or an empty list + """ + selected_port = None + selected = set() + #check the elements + for element in reversed(self.get_elements()): + selected_element = element.what_is_selected(coor, coor_m) + if not selected_element: continue + #update the selected port information + if selected_element.is_port(): + if not coor_m: selected_port = selected_element + selected_element = selected_element.get_parent() + selected.add(selected_element) + #place at the end of the list + self.get_elements().remove(element) + self.get_elements().append(element) + #single select mode, break + if not coor_m: break + #update selected ports + self._old_selected_port = self._new_selected_port + self._new_selected_port = selected_port + return list(selected) + + def get_selected_connections(self): + """ + Get a group of selected connections. + @return sub set of connections in this flow graph + """ + selected = set() + for selected_element in self.get_selected_elements(): + if selected_element.is_connection(): selected.add(selected_element) + return list(selected) + + def get_selected_blocks(self): + """ + Get a group of selected blocks. + @return sub set of blocks in this flow graph + """ + selected = set() + for selected_element in self.get_selected_elements(): + if selected_element.is_block(): selected.add(selected_element) + return list(selected) + + def get_selected_block(self): + """ + Get the selected block when a block or port is selected. + @return a block or None + """ + return self.get_selected_blocks() and self.get_selected_blocks()[0] or None + + def get_selected_elements(self): + """ + Get the group of selected elements. + @return sub set of elements in this flow graph + """ + return self._selected_elements + + def get_selected_element(self): + """ + Get the selected element. + @return a block, port, or connection or None + """ + return self.get_selected_elements() and self.get_selected_elements()[0] or None + + def update_selected_elements(self): + """ + Update the selected elements. + The update behavior depends on the state of the mouse button. + When the mouse button pressed the selection will change when + the control mask is set or the new selection is not in the current group. + When the mouse button is released the selection will change when + the mouse has moved and the control mask is set or the current group is empty. + Attempt to make a new connection if the old and ports are filled. + If the control mask is set, merge with the current elements. + """ + selected_elements = None + if self.mouse_pressed: + new_selections = self.what_is_selected(self.get_coordinate()) + #update the selections if the new selection is not in the current selections + #allows us to move entire selected groups of elements + if self.get_ctrl_mask() or not ( + new_selections and new_selections[0] in self.get_selected_elements() + ): selected_elements = new_selections + else: #called from a mouse release + if not self.element_moved and (not self.get_selected_elements() or self.get_ctrl_mask()): + selected_elements = self.what_is_selected(self.get_coordinate(), self.press_coor) + #this selection and the last were ports, try to connect them + if self._old_selected_port and self._new_selected_port and \ + self._old_selected_port is not self._new_selected_port: + try: + self.connect(self._old_selected_port, self._new_selected_port) + self.handle_states(ELEMENT_CREATE) + except: Messages.send_fail_connection() + self._old_selected_port = None + self._new_selected_port = None + return + #update selected elements + if selected_elements is None: return + old_elements = set(self.get_selected_elements()) + self._selected_elements = list(set(selected_elements)) + new_elements = set(self.get_selected_elements()) + #if ctrl, set the selected elements to the union - intersection of old and new + if self.get_ctrl_mask(): + self._selected_elements = list( + set.union(old_elements, new_elements) - set.intersection(old_elements, new_elements) + ) + self.handle_states(ELEMENT_SELECT) + + ########################################################################## + ## Event Handlers + ########################################################################## + def handle_mouse_button_press(self, left_click, double_click, coordinate): + """ + A mouse button is pressed, only respond to left clicks. + Find the selected element. Attempt a new connection if possible. + Open the block params window on a double click. + Update the selection state of the flow graph. + """ + if not left_click: return + self.press_coor = coordinate + self.set_coordinate(coordinate) + self.time = 0 + self.mouse_pressed = True + if double_click: self.unselect() + self.update_selected_elements() + #double click detected, bring up params dialog if possible + if double_click and self.get_selected_block(): + self.mouse_pressed = False + self.handle_states(BLOCK_PARAM_MODIFY) + + def handle_mouse_button_release(self, left_click, coordinate): + """ + A mouse button is released, record the state. + """ + if not left_click: return + self.set_coordinate(coordinate) + self.time = 0 + self.mouse_pressed = False + if self.element_moved: + self.handle_states(BLOCK_MOVE) + self.element_moved = False + self.update_selected_elements() + + def handle_mouse_motion(self, coordinate): + """ + The mouse has moved, respond to mouse dragging. + Move a selected element to the new coordinate. + Auto-scroll the scroll bars at the boundaries. + """ + #to perform a movement, the mouse must be pressed, no pending events + if gtk.events_pending() or not self.mouse_pressed: return + #perform autoscrolling + width, height = self.get_size() + x, y = coordinate + h_adj = self.get_scroll_pane().get_hadjustment() + v_adj = self.get_scroll_pane().get_vadjustment() + for pos, length, adj, adj_val, adj_len in ( + (x, width, h_adj, h_adj.get_value(), h_adj.page_size), + (y, height, v_adj, v_adj.get_value(), v_adj.page_size), + ): + #scroll if we moved near the border + if pos-adj_val > adj_len-SCROLL_PROXIMITY_SENSITIVITY and adj_val+SCROLL_DISTANCE < length-adj_len: + adj.set_value(adj_val+SCROLL_DISTANCE) + adj.emit('changed') + elif pos-adj_val < SCROLL_PROXIMITY_SENSITIVITY: + adj.set_value(adj_val-SCROLL_DISTANCE) + adj.emit('changed') + #remove the connection if selected in drag event + if len(self.get_selected_elements()) == 1 and self.get_selected_element().is_connection(): + self.handle_states(ELEMENT_DELETE) + #move the selected elements and record the new coordinate + X, Y = self.get_coordinate() + if not self.get_ctrl_mask(): self.move_selected((int(x - X), int(y - Y))) + self.set_coordinate((x, y)) + #queue draw for animation + self.queue_draw() diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py new file mode 100644 index 000000000..6d36f4cf7 --- /dev/null +++ b/grc/gui/MainWindow.py @@ -0,0 +1,324 @@ +""" +Copyright 2008 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +from Constants import \ + NEW_FLOGRAPH_TITLE, DEFAULT_REPORTS_WINDOW_WIDTH +from Actions import \ + APPLICATION_QUIT, FLOW_GRAPH_KILL, \ + FLOW_GRAPH_SAVE, get_accel_group +import pygtk +pygtk.require('2.0') +import gtk +import Bars +from BlockTreeWindow import BlockTreeWindow +from Dialogs import TextDisplay, MessageDialogHelper +from NotebookPage import NotebookPage +import Preferences +import Messages +import Utils +import os + +MAIN_WINDOW_TITLE_TMPL = """\ +#if not $saved +*#slurp +#end if +#if $basename +$basename#slurp +#else +$new_flowgraph_title#slurp +#end if +#if $read_only + (read only)#slurp +#end if +#if $dirname + - $dirname#slurp +#end if + - $platform_name#slurp +""" + +PAGE_TITLE_MARKUP_TMPL = """\ +#set $foreground = $saved and 'black' or 'red' +$encode($title or $new_flowgraph_title)#slurp +#if $read_only + (ro)#slurp +#end if +""" + +############################################################ +# Main window +############################################################ + +class MainWindow(gtk.Window): + """The topmost window with menus, the tool bar, and other major windows.""" + + def __init__(self, handle_states, platform): + """ + MainWindow contructor. + @param handle_states the callback function + """ + self._platform = platform + #setup window + self.handle_states = handle_states + gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) + vbox = gtk.VBox() + self.hpaned = gtk.HPaned() + self.add(vbox) + #create the menu bar and toolbar + self.add_accel_group(get_accel_group()) + vbox.pack_start(Bars.MenuBar(), False) + vbox.pack_start(Bars.Toolbar(), False) + vbox.pack_start(self.hpaned) + #create the notebook + self.notebook = gtk.Notebook() + self.page_to_be_closed = None + self.current_page = None + self.notebook.set_show_border(False) + self.notebook.set_scrollable(True) #scroll arrows for page tabs + self.notebook.connect('switch-page', self._handle_page_change) + #setup containers + self.flow_graph_vpaned = gtk.VPaned() + #flow_graph_box.pack_start(self.scrolled_window) + self.flow_graph_vpaned.pack1(self.notebook) + self.hpaned.pack1(self.flow_graph_vpaned) + self.hpaned.pack2(BlockTreeWindow(platform, self.get_flow_graph), False) #dont allow resize + #create the reports window + self.text_display = TextDisplay() + #house the reports in a scrolled window + self.reports_scrolled_window = gtk.ScrolledWindow() + self.reports_scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.reports_scrolled_window.add_with_viewport(self.text_display) + self.reports_scrolled_window.set_size_request(-1, DEFAULT_REPORTS_WINDOW_WIDTH) + self.flow_graph_vpaned.pack2(self.reports_scrolled_window, False) #dont allow resize + #load preferences and show the main window + Preferences.load(platform) + self.resize(*Preferences.main_window_size()) + self.flow_graph_vpaned.set_position(Preferences.reports_window_position()) + self.hpaned.set_position(Preferences.blocks_window_position()) + self.show_all() + + ############################################################ + # Event Handlers + ############################################################ + + def _quit(self, window, event): + """ + Handle the delete event from the main window. + Generated by pressing X to close, alt+f4, or right click+close. + This method in turns calls the state handler to quit. + @return true + """ + self.handle_states(APPLICATION_QUIT) + return True + + def _handle_page_change(self, notebook, page, page_num): + """ + Handle a page change. When the user clicks on a new tab, + reload the flow graph to update the vars window and + call handle states (select nothing) to update the buttons. + @param notebook the notebook + @param page new page + @param page_num new page number + """ + self.current_page = self.notebook.get_nth_page(page_num) + Messages.send_page_switch(self.current_page.get_file_path()) + self.handle_states() + + ############################################################ + # Report Window + ############################################################ + + def add_report_line(self, line): + """ + Place line at the end of the text buffer, then scroll its window all the way down. + @param line the new text + """ + self.text_display.insert(line) + vadj = self.reports_scrolled_window.get_vadjustment() + vadj.set_value(vadj.upper) + vadj.emit('changed') + + ############################################################ + # Pages: create and close + ############################################################ + + def new_page(self, file_path='', show=False): + """ + Create a new notebook page. + Set the tab to be selected. + @param file_path optional file to load into the flow graph + @param show true if the page should be shown after loading + """ + #if the file is already open, show the open page and return + if file_path and file_path in self._get_files(): #already open + page = self.notebook.get_nth_page(self._get_files().index(file_path)) + self._set_page(page) + return + try: #try to load from file + if file_path: Messages.send_start_load(file_path) + flow_graph = self._platform.get_new_flow_graph() + page = NotebookPage( + self, + flow_graph=flow_graph, + file_path=file_path, + ) + if file_path: Messages.send_end_load() + except Exception, e: #return on failure + Messages.send_fail_load(e) + return + #add this page to the notebook + self.notebook.append_page(page, page.get_tab()) + try: self.notebook.set_tab_reorderable(page, True) + except: pass #gtk too old + self.notebook.set_tab_label_packing(page, False, False, gtk.PACK_START) + #only show if blank or manual + if not file_path or show: self._set_page(page) + + def close_pages(self): + """ + Close all the pages in this notebook. + @return true if all closed + """ + open_files = filter(lambda file: file, self._get_files()) #filter blank files + open_file = self.get_page().get_file_path() + #close each page + for page in self._get_pages(): + self.page_to_be_closed = page + self.close_page(False) + if self.notebook.get_n_pages(): return False + #save state before closing + Preferences.files_open(open_files) + Preferences.file_open(open_file) + Preferences.main_window_size(self.get_size()) + Preferences.reports_window_position(self.flow_graph_vpaned.get_position()) + Preferences.blocks_window_position(self.hpaned.get_position()) + Preferences.save() + return True + + def close_page(self, ensure=True): + """ + Close the current page. + If the notebook becomes empty, and ensure is true, + call new page upon exit to ensure that at least one page exists. + @param ensure boolean + """ + if not self.page_to_be_closed: self.page_to_be_closed = self.get_page() + #show the page if it has an executing flow graph or is unsaved + if self.page_to_be_closed.get_pid() or not self.page_to_be_closed.get_saved(): + self._set_page(self.page_to_be_closed) + #unsaved? ask the user + if not self.page_to_be_closed.get_saved() and self._save_changes(): + self.handle_states(FLOW_GRAPH_SAVE) #try to save + if not self.page_to_be_closed.get_saved(): #still unsaved? + self.page_to_be_closed = None #set the page to be closed back to None + return + #stop the flow graph if executing + if self.page_to_be_closed.get_pid(): self.handle_states(FLOW_GRAPH_KILL) + #remove the page + self.notebook.remove_page(self.notebook.page_num(self.page_to_be_closed)) + if ensure and self.notebook.get_n_pages() == 0: self.new_page() #no pages, make a new one + self.page_to_be_closed = None #set the page to be closed back to None + + ############################################################ + # Misc + ############################################################ + + def update(self): + """ + Set the title of the main window. + Set the titles on the page tabs. + Show/hide the reports window. + @param title the window title + """ + gtk.Window.set_title(self, Utils.parse_template(MAIN_WINDOW_TITLE_TMPL, + basename=os.path.basename(self.get_page().get_file_path()), + dirname=os.path.dirname(self.get_page().get_file_path()), + new_flowgraph_title=NEW_FLOGRAPH_TITLE, + read_only=self.get_page().get_read_only(), + saved=self.get_page().get_saved(), + platform_name=self._platform.get_name(), + ) + ) + #set tab titles + for page in self._get_pages(): page.set_markup( + Utils.parse_template(PAGE_TITLE_MARKUP_TMPL, + #get filename and strip out file extension + title=os.path.splitext(os.path.basename(page.get_file_path()))[0], + read_only=page.get_read_only(), saved=page.get_saved(), + new_flowgraph_title=NEW_FLOGRAPH_TITLE, + ) + ) + #show/hide notebook tabs + self.notebook.set_show_tabs(len(self._get_pages()) > 1) + + def get_page(self): + """ + Get the selected page. + @return the selected page + """ + return self.current_page + + def get_flow_graph(self): + """ + Get the selected flow graph. + @return the selected flow graph + """ + return self.get_page().get_flow_graph() + + def get_focus_flag(self): + """ + Get the focus flag from the current page. + @return the focus flag + """ + return self.get_page().get_drawing_area().get_focus_flag() + + ############################################################ + # Helpers + ############################################################ + + def _set_page(self, page): + """ + Set the current page. + @param page the page widget + """ + self.current_page = page + self.notebook.set_current_page(self.notebook.page_num(self.current_page)) + + def _save_changes(self): + """ + Save changes to flow graph? + @return true if yes + """ + return MessageDialogHelper( + gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, 'Unsaved Changes!', + 'Would you like to save changes before closing?' + ) == gtk.RESPONSE_YES + + def _get_files(self): + """ + Get the file names for all the pages, in order. + @return list of file paths + """ + return map(lambda page: page.get_file_path(), self._get_pages()) + + def _get_pages(self): + """ + Get a list of all pages in the notebook. + @return list of pages + """ + return [self.notebook.get_nth_page(page_num) for page_num in range(self.notebook.get_n_pages())] diff --git a/grc/gui/Makefile.am b/grc/gui/Makefile.am new file mode 100644 index 000000000..c31bc5f61 --- /dev/null +++ b/grc/gui/Makefile.am @@ -0,0 +1,50 @@ +# +# Copyright 2008, 2009 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +include $(top_srcdir)/grc/Makefile.inc + +ourpythondir = $(grc_src_prefix)/gui +ourpython_PYTHON = \ + Block.py \ + Colors.py \ + Constants.py \ + Connection.py \ + Element.py \ + FlowGraph.py \ + Param.py \ + Platform.py \ + Port.py \ + Utils.py \ + ActionHandler.py \ + Actions.py \ + Bars.py \ + BlockTreeWindow.py \ + Constants.py \ + Dialogs.py \ + DrawingArea.py \ + FileDialogs.py \ + MainWindow.py \ + Messages.py \ + NotebookPage.py \ + ParamsDialog.py \ + Preferences.py \ + StateCache.py \ + __init__.py diff --git a/grc/gui/Messages.py b/grc/gui/Messages.py new file mode 100644 index 000000000..80057e0ba --- /dev/null +++ b/grc/gui/Messages.py @@ -0,0 +1,104 @@ +""" +Copyright 2007 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import traceback +import sys + +## A list of functions that can receive a message. +MESSENGERS_LIST = list() + +def register_messenger(messenger): + """ + Append the given messenger to the list of messengers. + @param messenger a method thats takes a string + """ + MESSENGERS_LIST.append(messenger) + +def send(message): + """ + Give the message to each of the messengers. + @param message a message string + """ + for messenger in MESSENGERS_LIST: messenger(message) + +#register stdout by default +register_messenger(sys.stdout.write) + +########################################################################### +# Special functions for specific program functionalities +########################################################################### +def send_init(platform): + send("""<<< Welcome to %s %s >>>\n"""%(platform.get_name(), platform.get_version())) + +def send_page_switch(file_path): + send('\nShowing: "%s"\n'%file_path) + +################# functions for loading flow graphs ######################################## +def send_start_load(file_path): + send('\nLoading: "%s"'%file_path + '\n') + +def send_error_load(error): + send('>>> Error: %s\n'%error) + traceback.print_exc() + +def send_end_load(): + send('>>> Done\n') + +def send_fail_load(error): + send('Error: %s\n'%error) + send('>>> Failue\n') + traceback.print_exc() + +################# functions for generating flow graphs ######################################## +def send_start_gen(file_path): + send('\nGenerating: "%s"'%file_path + '\n') + +def send_fail_gen(error): + send('Generate Error: %s\n'%error) + send('>>> Failue\n') + traceback.print_exc() + +################# functions for executing flow graphs ######################################## +def send_start_exec(file_path): + send('\nExecuting: "%s"'%file_path + '\n') + +def send_verbose_exec(verbose): + send(verbose) + +def send_end_exec(): + send('\n>>> Done\n') + +################# functions for saving flow graphs ######################################## +def send_fail_save(file_path): + send('>>> Error: Cannot save: %s\n'%file_path) + +################# functions for connections ######################################## +def send_fail_connection(): + send('>>> Error: Cannot create connection.\n') + +################# functions for preferences ######################################## +def send_fail_load_preferences(prefs_file_path): + send('>>> Error: Cannot load preferences file: "%s"\n'%prefs_file_path) + +def send_fail_save_preferences(prefs_file_path): + send('>>> Error: Cannot save preferences file: "%s"\n'%prefs_file_path) + +################# functions for warning ######################################## +def send_warning(warning): + send('>>> Warning: %s\n'%warning) diff --git a/grc/gui/NotebookPage.py b/grc/gui/NotebookPage.py new file mode 100644 index 000000000..cb6b7ed30 --- /dev/null +++ b/grc/gui/NotebookPage.py @@ -0,0 +1,188 @@ +""" +Copyright 2008, 2009 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +from Actions import FLOW_GRAPH_CLOSE +import pygtk +pygtk.require('2.0') +import gtk +from StateCache import StateCache +from Constants import MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT +from DrawingArea import DrawingArea +import os + +############################################################ +## Notebook Page +############################################################ + +class NotebookPage(gtk.HBox): + """A page in the notebook.""" + + def __init__(self, main_window, flow_graph, file_path=''): + """ + Page constructor. + @param main_window main window + @param file_path path to a flow graph file + """ + self._flow_graph = flow_graph + self.set_pid(None) + #import the file + self.main_window = main_window + self.set_file_path(file_path) + initial_state = flow_graph.get_parent().parse_flow_graph(file_path) + self.state_cache = StateCache(initial_state) + self.set_saved(True) + #import the data to the flow graph + self.get_flow_graph().import_data(initial_state) + #initialize page gui + gtk.HBox.__init__(self, False, 0) + self.show() + #tab box to hold label and close button + self.tab = gtk.HBox(False, 0) + #setup tab label + self.label = gtk.Label() + self.tab.pack_start(self.label, False) + #setup button image + image = gtk.Image() + image.set_from_stock('gtk-close', gtk.ICON_SIZE_MENU) + #setup image box + image_box = gtk.HBox(False, 0) + image_box.pack_start(image, True, False, 0) + #setup the button + button = gtk.Button() + button.connect("clicked", self._handle_button) + button.set_relief(gtk.RELIEF_NONE) + button.add(image_box) + #button size + w, h = gtk.icon_size_lookup_for_settings(button.get_settings(), gtk.ICON_SIZE_MENU) + button.set_size_request(w+6, h+6) + self.tab.pack_start(button, False) + self.tab.show_all() + #setup scroll window and drawing area + self.scrolled_window = gtk.ScrolledWindow() + self.scrolled_window.set_size_request(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT) + self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.drawing_area = DrawingArea(self.get_flow_graph()) + self.scrolled_window.add_with_viewport(self.get_drawing_area()) + self.pack_start(self.scrolled_window) + #inject drawing area and handle states into flow graph + self.get_flow_graph().drawing_area = self.get_drawing_area() + self.get_flow_graph().handle_states = main_window.handle_states + self.show_all() + + def get_drawing_area(self): return self.drawing_area + + def get_generator(self): + """ + Get the generator object for this flow graph. + @return generator + """ + return self.get_flow_graph().get_parent().get_generator()( + self.get_flow_graph(), + self.get_file_path(), + ) + + def _handle_button(self, button): + """ + The button was clicked. + Make the current page selected, then close. + @param the button + """ + self.main_window.page_to_be_closed = self + self.main_window.handle_states(FLOW_GRAPH_CLOSE) + + def set_markup(self, markup): + """ + Set the markup in this label. + @param markup the new markup text + """ + self.label.set_markup(markup) + + def get_tab(self): + """ + Get the gtk widget for this page's tab. + @return gtk widget + """ + return self.tab + + def get_pid(self): + """ + Get the pid for the flow graph. + @return the pid number + """ + return self.pid + + def set_pid(self, pid): + """ + Set the pid number. + @param pid the new pid number + """ + self.pid = pid + + def get_flow_graph(self): + """ + Get the flow graph. + @return the flow graph + """ + return self._flow_graph + + def get_read_only(self): + """ + Get the read-only state of the file. + Always false for empty path. + @return true for read-only + """ + if not self.get_file_path(): return False + return os.path.exists(self.get_file_path()) and \ + not os.access(self.get_file_path(), os.W_OK) + + def get_file_path(self): + """ + Get the file path for the flow graph. + @return the file path or '' + """ + return self.file_path + + def set_file_path(self, file_path=''): + """ + Set the file path, '' for no file path. + @param file_path file path string + """ + if file_path: self.file_path = os.path.abspath(file_path) + else: self.file_path = '' + + def get_saved(self): + """ + Get the saved status for the flow graph. + @return true if saved + """ + return self.saved + + def set_saved(self, saved=True): + """ + Set the saved status. + @param saved boolean status + """ + self.saved = saved + + def get_state_cache(self): + """ + Get the state cache for the flow graph. + @return the state cache + """ + return self.state_cache diff --git a/grc/gui/Param.py b/grc/gui/Param.py new file mode 100644 index 000000000..3029569b4 --- /dev/null +++ b/grc/gui/Param.py @@ -0,0 +1,104 @@ +""" +Copyright 2007, 2008, 2009 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import Utils +from Element import Element +import pygtk +pygtk.require('2.0') +import gtk + +PARAM_MARKUP_TMPL="""\ +#set $foreground = $param.is_valid() and 'black' or 'red' +#set $value = not $param.is_valid() and 'error' or repr($param) +$encode($param.get_name()): $encode($value)""" + +PARAM_LABEL_MARKUP_TMPL="""\ +#set $foreground = $param.is_valid() and 'black' or 'red' +#set $underline = $has_cb and 'low' or 'none' +$encode($param.get_name())""" + +TIP_MARKUP_TMPL="""\ +Key: $param.get_key() +Type: $param.get_type() +#if $param.is_valid() +Value: $param.get_evaluated() +#elif len($param.get_error_messages()) == 1 +Error: $(param.get_error_messages()[0]) +#else +Error: + #for $error_msg in $param.get_error_messages() + * $error_msg + #end for +#end if""" + +class Param(Element): + """The graphical parameter.""" + + def update(self): + """ + Called when an external change occurs. + Update the graphical input by calling the change handler. + """ + if hasattr(self, '_input'): self._handle_changed() + + def get_input_object(self, callback=None): + """ + Get the graphical gtk object to represent this parameter. + Create the input object with this data type and the handle changed method. + @param callback a function of one argument(this param) to be called from the change handler + @return gtk input object + """ + self._callback = callback + self._input = self.get_input_class()(self, self._handle_changed) + if not self._callback: self.update() + return self._input + + def _handle_changed(self, widget=None): + """ + When the input changes, write the inputs to the data type. + Finish by calling the exteral callback. + """ + self.set_value(self._input.get_text()) + self.validate() + #is param is involved in a callback? #FIXME: messy + has_cb = \ + hasattr(self.get_parent(), 'get_callbacks') and \ + filter(lambda c: self.get_key() in c, self.get_parent()._callbacks) + self._input.set_markup(Utils.parse_template(PARAM_LABEL_MARKUP_TMPL, param=self, has_cb=has_cb)) + #hide/show + if self.get_hide() == 'all': self._input.hide_all() + else: self._input.show_all() + #set the color + self._input.set_color(self.get_color()) + #set the tooltip + if self._input.tp: self._input.tp.set_tip( + self._input.entry, + Utils.parse_template(TIP_MARKUP_TMPL, param=self).strip(), + ) + #execute the external callback + if self._callback: self._callback(self) + + def get_layout(self): + """ + Create a layout based on the current markup. + @return the pango layout + """ + layout = gtk.DrawingArea().create_pango_layout('') + layout.set_markup(Utils.parse_template(PARAM_MARKUP_TMPL, param=self)) + return layout diff --git a/grc/gui/ParamsDialog.py b/grc/gui/ParamsDialog.py new file mode 100644 index 000000000..ccf19d1a2 --- /dev/null +++ b/grc/gui/ParamsDialog.py @@ -0,0 +1,145 @@ +""" +Copyright 2007, 2008, 2009 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import pygtk +pygtk.require('2.0') +import gtk + +from Dialogs import TextDisplay +from Constants import MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT + +def get_title_label(title): + """ + Get a title label for the params window. + The title will be bold, underlined, and left justified. + @param title the text of the title + @return a gtk object + """ + label = gtk.Label() + label.set_markup('\n%s:\n'%title) + hbox = gtk.HBox() + hbox.pack_start(label, False, False, padding=11) + return hbox + +class ParamsDialog(gtk.Dialog): + """A dialog box to set block parameters.""" + + def __init__(self, block): + """ + SignalBlockParamsDialog contructor. + @param block the signal block + """ + gtk.Dialog.__init__(self, + title='Properties: %s'%block.get_name(), + buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE), + ) + self.block = block + self.set_size_request(MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT) + vbox = gtk.VBox() + #Add the title label + vbox.pack_start(get_title_label('Parameters'), False) + #Create the scrolled window to hold all the parameters + scrolled_window = gtk.ScrolledWindow() + scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolled_window.add_with_viewport(vbox) + self.vbox.pack_start(scrolled_window, True) + #Error Messages for the block + self._error_box = gtk.VBox() + self._error_messages_text_display = TextDisplay() + self._error_box.pack_start(gtk.Label(), False, False, 7) #spacing + self._error_box.pack_start(get_title_label('Error Messages'), False) + self._error_box.pack_start(self._error_messages_text_display, False) + #Docs for the block + self._docs_box = err_box = gtk.VBox() + self._docs_text_display = TextDisplay() + self._docs_box.pack_start(gtk.Label(), False, False, 7) #spacing + self._docs_box.pack_start(get_title_label('Documentation'), False) + self._docs_box.pack_start(self._docs_text_display, False) + #Add all the parameters + for param in self.block.get_params(): + vbox.pack_start(param.get_input_object(self._handle_changed), False) + #Add the error and docs box + vbox.pack_start(self._error_box, False) + vbox.pack_start(self._docs_box, False) + #connect and show + self.connect('key_press_event', self._handle_key_press) + self.show_all() + #initial update + for param in self.block.get_params(): param.update() + self._update() + + def _update(self): + """ + Update the error messages box. + Hide the box if there are no errors. + Update the documentation block. + Hide the box if there are no docs. + """ + self.block.validate() + #update the errors box + if self.block.is_valid(): self._error_box.hide() + else: self._error_box.show() + messages = '\n\n'.join(self.block.get_error_messages()) + self._error_messages_text_display.set_text(messages) + #update the docs box + if self.block.get_doc(): self._docs_box.show() + else: self._docs_box.hide() + self._docs_text_display.set_text(self.block.get_doc()) + + def _handle_key_press(self, widget, event): + """ + Handle key presses from the keyboard. + Call the ok response when enter is pressed. + @return false to forward the keypress + """ + keyname = gtk.gdk.keyval_name(event.keyval) + if keyname == 'Return': self.response(gtk.RESPONSE_OK) + return False #forward the keypress + + def _handle_changed(self, param): + """ + A change occured, update any dependent parameters: + The enum inside the variable type may have changed and, + the variable param will need an external update. + @param param the graphical parameter that initiated the callback + """ + #update dependent params + if param.is_enum(): + for other_param in param.get_parent().get_params(): + if param.get_key() is not other_param.get_key() and ( + param.get_key() in other_param._type or \ + param.get_key() in other_param._hide): other_param.update() + #update + self._update() + return True + + def run(self): + """ + Call run(). + @return true if a change occured. + """ + original_data = list() + for param in self.block.get_params(): + original_data.append(param.get_value()) + gtk.Dialog.run(self) + self.destroy() + new_data = list() + for param in self.block.get_params(): + new_data.append(param.get_value()) + return original_data != new_data diff --git a/grc/gui/Platform.py b/grc/gui/Platform.py new file mode 100644 index 000000000..a32b0209f --- /dev/null +++ b/grc/gui/Platform.py @@ -0,0 +1,48 @@ +""" +Copyright 2008 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +from FlowGraph import FlowGraph +from Connection import Connection +from Block import Block +from Port import Port +from Param import Param + +def conjoin_classes(name, c1, c2): + exec(""" +class %s(c1, c2): + def __init__(self, *args, **kwargs): + c1.__init__(self, *args, **kwargs) + c2.__init__(self, *args, **kwargs) +"""%name, locals()) + return locals()[name] + +def Platform(platform): + #combine with gui class + for attr, value in ( + ('FlowGraph', FlowGraph), + ('Connection', Connection), + ('Block', Block), + ('Source', Port), + ('Sink', Port), + ('Param', Param), + ): + old_value = getattr(platform, attr) + c = conjoin_classes(attr, old_value, value) + setattr(platform, attr, c) + return platform diff --git a/grc/gui/Port.py b/grc/gui/Port.py new file mode 100644 index 000000000..d1f36f8b9 --- /dev/null +++ b/grc/gui/Port.py @@ -0,0 +1,190 @@ +""" +Copyright 2007 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +from Element import Element +from Constants import \ + PORT_SEPARATION, CONNECTOR_EXTENSION_MINIMAL, \ + CONNECTOR_EXTENSION_INCREMENT, \ + PORT_LABEL_PADDING, PORT_MIN_WIDTH +import Utils +import Colors +import pygtk +pygtk.require('2.0') +import gtk + +PORT_MARKUP_TMPL="""\ +$encode($port.get_name())""" + +class Port(Element): + """The graphical port.""" + + def __init__(self, *args, **kwargs): + """ + Port contructor. + Create list of connector coordinates. + """ + Element.__init__(self) + self.connector_coordinates = dict() + + def update(self): + """Create new areas and labels for the port.""" + self.clear() + #get current rotation + rotation = self.get_rotation() + #get all sibling ports + if self.is_source(): ports = self.get_parent().get_sources() + elif self.is_sink(): ports = self.get_parent().get_sinks() + #get the max width + self.W = max([port.W for port in ports] + [PORT_MIN_WIDTH]) + #get a numeric index for this port relative to its sibling ports + index = ports.index(self) + length = len(ports) + #reverse the order of ports for these rotations + if rotation in (180, 270): index = length-index-1 + offset = (self.get_parent().H - length*self.H - (length-1)*PORT_SEPARATION)/2 + #create areas and connector coordinates + if (self.is_sink() and rotation == 0) or (self.is_source() and rotation == 180): + x = -1*self.W + y = (PORT_SEPARATION+self.H)*index+offset + self.add_area((x, y), (self.W, self.H)) + self._connector_coordinate = (x-1, y+self.H/2) + elif (self.is_source() and rotation == 0) or (self.is_sink() and rotation == 180): + x = self.get_parent().W + y = (PORT_SEPARATION+self.H)*index+offset + self.add_area((x, y), (self.W, self.H)) + self._connector_coordinate = (x+1+self.W, y+self.H/2) + elif (self.is_source() and rotation == 90) or (self.is_sink() and rotation == 270): + y = -1*self.W + x = (PORT_SEPARATION+self.H)*index+offset + self.add_area((x, y), (self.H, self.W)) + self._connector_coordinate = (x+self.H/2, y-1) + elif (self.is_sink() and rotation == 90) or (self.is_source() and rotation == 270): + y = self.get_parent().W + x = (PORT_SEPARATION+self.H)*index+offset + self.add_area((x, y), (self.H, self.W)) + self._connector_coordinate = (x+self.H/2, y+1+self.W) + #the connector length + self._connector_length = CONNECTOR_EXTENSION_MINIMAL + CONNECTOR_EXTENSION_INCREMENT*index + + def _create_labels(self): + """Create the labels for the socket.""" + self._bg_color = Colors.get_color(self.get_color()) + #create the layout + layout = gtk.DrawingArea().create_pango_layout('') + layout.set_markup(Utils.parse_template(PORT_MARKUP_TMPL, port=self)) + self.w, self.h = layout.get_pixel_size() + self.W, self.H = 2*PORT_LABEL_PADDING+self.w, 2*PORT_LABEL_PADDING+self.h + #create the pixmap + pixmap = self.get_parent().get_parent().new_pixmap(self.w, self.h) + gc = pixmap.new_gc() + gc.set_foreground(self._bg_color) + pixmap.draw_rectangle(gc, True, 0, 0, self.w, self.h) + pixmap.draw_layout(gc, 0, 0, layout) + #create the images + self.horizontal_label = image = pixmap.get_image(0, 0, self.w, self.h) + if self.is_vertical(): + self.vertical_label = vimage = gtk.gdk.Image(gtk.gdk.IMAGE_NORMAL, pixmap.get_visual(), self.h, self.w) + for i in range(self.w): + for j in range(self.h): vimage.put_pixel(j, self.w-i-1, image.get_pixel(i, j)) + + def draw(self, gc, window): + """ + Draw the socket with a label. + @param gc the graphics context + @param window the gtk window to draw on + """ + Element.draw( + self, gc, window, bg_color=self._bg_color, + border_color=self.is_highlighted() and Colors.HIGHLIGHT_COLOR or Colors.BORDER_COLOR, + ) + X,Y = self.get_coordinate() + (x,y),(w,h) = self.areas_dict[self.get_rotation()][0] #use the first area's sizes to place the labels + if self.is_horizontal(): + window.draw_image(gc, self.horizontal_label, 0, 0, x+X+(self.W-self.w)/2, y+Y+(self.H-self.h)/2, -1, -1) + elif self.is_vertical(): + window.draw_image(gc, self.vertical_label, 0, 0, x+X+(self.H-self.h)/2, y+Y+(self.W-self.w)/2, -1, -1) + + def get_connector_coordinate(self): + """ + Get the coordinate where connections may attach to. + @return the connector coordinate (x, y) tuple + """ + x,y = self._connector_coordinate + X,Y = self.get_coordinate() + return (x+X, y+Y) + + def get_connector_direction(self): + """ + Get the direction that the socket points: 0,90,180,270. + This is the rotation degree if the socket is an output or + the rotation degree + 180 if the socket is an input. + @return the direction in degrees + """ + if self.is_source(): return self.get_rotation() + elif self.is_sink(): return (self.get_rotation() + 180)%360 + + def get_connector_length(self): + """ + Get the length of the connector. + The connector length increases as the port index changes. + @return the length in pixels + """ + return self._connector_length + + def get_rotation(self): + """ + Get the parent's rotation rather than self. + @return the parent's rotation + """ + return self.get_parent().get_rotation() + + def move(self, delta_coor): + """ + Move the parent rather than self. + @param delta_corr the (delta_x, delta_y) tuple + """ + self.get_parent().move(delta_coor) + + def rotate(self, direction): + """ + Rotate the parent rather than self. + @param direction degrees to rotate + """ + self.get_parent().rotate(direction) + + def get_coordinate(self): + """ + Get the parent's coordinate rather than self. + @return the parents coordinate + """ + return self.get_parent().get_coordinate() + + def set_highlighted(self, highlight): + """ + Set the parent highlight rather than self. + @param highlight true to enable highlighting + """ + self.get_parent().set_highlighted(highlight) + + def is_highlighted(self): + """ + Get the parent's is highlight rather than self. + @return the parent's highlighting status + """ + return self.get_parent().is_highlighted() diff --git a/grc/gui/Preferences.py b/grc/gui/Preferences.py new file mode 100644 index 000000000..1d89920dd --- /dev/null +++ b/grc/gui/Preferences.py @@ -0,0 +1,86 @@ +""" +Copyright 2008 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import ConfigParser +import os + +_platform = None +_config_parser = ConfigParser.ConfigParser() + +def file_extension(): return '.'+_platform.get_key() +def _prefs_file(): return os.path.join(os.path.expanduser('~'), file_extension()) + +def load(platform): + global _platform + _platform = platform + #create sections + _config_parser.add_section('main') + _config_parser.add_section('files_open') + try: _config_parser.read(_prefs_file()) + except: pass +def save(): + try: _config_parser.write(open(_prefs_file(), 'w')) + except: pass + +########################################################################### +# Special methods for specific program functionalities +########################################################################### + +def main_window_size(size=None): + if size is not None: + _config_parser.set('main', 'main_window_width', size[0]) + _config_parser.set('main', 'main_window_height', size[1]) + else: + try: return ( + _config_parser.getint('main', 'main_window_width'), + _config_parser.getint('main', 'main_window_height'), + ) + except: return (1, 1) + +def file_open(file=None): + if file is not None: _config_parser.set('main', 'file_open', file) + else: + try: return _config_parser.get('main', 'file_open') + except: return '' + +def files_open(files=None): + if files is not None: + _config_parser.remove_section('files_open') #clear section + _config_parser.add_section('files_open') + for i, file in enumerate(files): + _config_parser.set('files_open', 'file_open_%d'%i, file) + else: + files = list() + i = 0 + while True: + try: files.append(_config_parser.get('files_open', 'file_open_%d'%i)) + except: return files + i = i + 1 + +def reports_window_position(pos=None): + if pos is not None: _config_parser.set('main', 'reports_window_position', pos) + else: + try: return _config_parser.getint('main', 'reports_window_position') or 1 #greater than 0 + except: return -1 + +def blocks_window_position(pos=None): + if pos is not None: _config_parser.set('main', 'blocks_window_position', pos) + else: + try: return _config_parser.getint('main', 'blocks_window_position') or 1 #greater than 0 + except: return -1 diff --git a/grc/gui/StateCache.py b/grc/gui/StateCache.py new file mode 100644 index 000000000..04b18b18a --- /dev/null +++ b/grc/gui/StateCache.py @@ -0,0 +1,92 @@ +""" +Copyright 2007 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +from Actions import FLOW_GRAPH_UNDO, FLOW_GRAPH_REDO, get_action_from_name +from Constants import STATE_CACHE_SIZE + +class StateCache(object): + """ + The state cache is an interface to a list to record data/states and to revert to previous states. + States are recorded into the list in a circular fassion by using an index for the current state, + and counters for the range where states are stored. + """ + + def __init__(self, initial_state): + """ + StateCache constructor. + @param initial_state the intial state (nested data) + """ + self.states = [None] * STATE_CACHE_SIZE #fill states + self.current_state_index = 0 + self.num_prev_states = 0 + self.num_next_states = 0 + self.states[0] = initial_state + self.update_actions() + + def save_new_state(self, state): + """ + Save a new state. + Place the new state at the next index and add one to the number of previous states. + @param state the new state + """ + self.current_state_index = (self.current_state_index + 1)%STATE_CACHE_SIZE + self.states[self.current_state_index] = state + self.num_prev_states = self.num_prev_states + 1 + if self.num_prev_states == STATE_CACHE_SIZE: self.num_prev_states = STATE_CACHE_SIZE - 1 + self.num_next_states = 0 + self.update_actions() + + def get_current_state(self): + """ + Get the state at the current index. + @return the current state (nested data) + """ + self.update_actions() + return self.states[self.current_state_index] + + def get_prev_state(self): + """ + Get the previous state and decrement the current index. + @return the previous state or None + """ + if self.num_prev_states > 0: + self.current_state_index = (self.current_state_index + STATE_CACHE_SIZE -1)%STATE_CACHE_SIZE + self.num_next_states = self.num_next_states + 1 + self.num_prev_states = self.num_prev_states - 1 + return self.get_current_state() + return None + + def get_next_state(self): + """ + Get the nest state and increment the current index. + @return the next state or None + """ + if self.num_next_states > 0: + self.current_state_index = (self.current_state_index + 1)%STATE_CACHE_SIZE + self.num_next_states = self.num_next_states - 1 + self.num_prev_states = self.num_prev_states + 1 + return self.get_current_state() + return None + + def update_actions(self): + """ + Update the undo and redo actions based on the number of next and prev states. + """ + get_action_from_name(FLOW_GRAPH_REDO).set_sensitive(self.num_next_states != 0) + get_action_from_name(FLOW_GRAPH_UNDO).set_sensitive(self.num_prev_states != 0) diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py new file mode 100644 index 000000000..ee6dc6cdc --- /dev/null +++ b/grc/gui/Utils.py @@ -0,0 +1,82 @@ +""" +Copyright 2008, 2009 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +from Constants import POSSIBLE_ROTATIONS +from Cheetah.Template import Template + +def get_rotated_coordinate(coor, rotation): + """ + Rotate the coordinate by the given rotation. + @param coor the coordinate x, y tuple + @param rotation the angle in degrees + @return the rotated coordinates + """ + #handles negative angles + rotation = (rotation + 360)%360 + assert rotation in POSSIBLE_ROTATIONS + #determine the number of degrees to rotate + cos_r, sin_r = { + 0: (1, 0), + 90: (0, 1), + 180: (-1, 0), + 270: (0, -1), + }[rotation] + x, y = coor + return (x*cos_r + y*sin_r, -x*sin_r + y*cos_r) + +def get_angle_from_coordinates((x1,y1), (x2,y2)): + """ + Given two points, calculate the vector direction from point1 to point2, directions are multiples of 90 degrees. + @param (x1,y1) the coordinate of point 1 + @param (x2,y2) the coordinate of point 2 + @return the direction in degrees + """ + if y1 == y2:#0 or 180 + if x2 > x1: return 0 + else: return 180 + else:#90 or 270 + if y2 > y1: return 270 + else: return 90 + +def xml_encode(string): + """ + Encode a string into an xml safe string by replacing special characters. + Needed for gtk pango markup in labels. + @param string the input string + @return output string with safe characters + """ + string = str(string) + for char, safe in ( + ('&', '&'), + ('<', '<'), + ('>', '>'), + ('"', '"'), + ("'", '''), + ): string = string.replace(char, safe) + return string + +def parse_template(tmpl_str, **kwargs): + """ + Parse the template string with the given args. + Pass in the xml encode method for pango escape chars. + @param tmpl_str the template as a string + @return a string of the parsed template + """ + kwargs['encode'] = xml_encode + return str(Template(tmpl_str, kwargs)) diff --git a/grc/gui/__init__.py b/grc/gui/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/grc/gui/__init__.py @@ -0,0 +1 @@ + -- cgit From 636a8cae9b5fd9cdc1e45f7a630068b905713057 Mon Sep 17 00:00:00 2001 From: jblum Date: Thu, 25 Jun 2009 17:36:36 +0000 Subject: minor fix so deleted elements are not referenced in the selected ports git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11282 221aa14e-8319-0410-a670-987f0aec2ac5 --- grc/gui/FlowGraph.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'grc/gui') diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index 26544faab..63f289027 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -281,6 +281,10 @@ class FlowGraph(Element): for selected in selected_elements: if selected in elements: continue selected_elements.remove(selected) + if self._old_selected_port not in elements: + self._old_selected_port = None + if self._new_selected_port not in elements: + self._new_selected_port = None #update highlighting for element in elements: element.set_highlighted(element in selected_elements) -- cgit From deb7e98d2c413869e6615fb011f24c2a9944c6d3 Mon Sep 17 00:00:00 2001 From: jblum Date: Thu, 25 Jun 2009 17:59:26 +0000 Subject: better fix for selected ports, added variable config example to examples git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11283 221aa14e-8319-0410-a670-987f0aec2ac5 --- grc/gui/FlowGraph.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index 63f289027..f8028f199 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -281,10 +281,10 @@ class FlowGraph(Element): for selected in selected_elements: if selected in elements: continue selected_elements.remove(selected) - if self._old_selected_port not in elements: - self._old_selected_port = None - if self._new_selected_port not in elements: - self._new_selected_port = None + try: assert self._old_selected_port.get_parent() in elements + except: self._old_selected_port = None + try: assert self._new_selected_port.get_parent() in elements + except: self._new_selected_port = None #update highlighting for element in elements: element.set_highlighted(element in selected_elements) -- cgit From 03a27276e566e1e641d115fce129d3351ea52d81 Mon Sep 17 00:00:00 2001 From: jblum Date: Mon, 29 Jun 2009 23:36:20 +0000 Subject: fixed screenshot capability, calls get pixbuf in drawing area git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11307 221aa14e-8319-0410-a670-987f0aec2ac5 --- grc/gui/ActionHandler.py | 5 +---- grc/gui/DrawingArea.py | 5 +++++ 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 2c411a175..ff137f669 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -297,10 +297,7 @@ class ActionHandler: elif state == Actions.FLOW_GRAPH_SCREEN_CAPTURE: file_path = SaveImageFileDialog(self.get_page().get_file_path()).run() if file_path is not None: - pixmap = self.get_flow_graph().get_pixmap() - width, height = pixmap.get_size() - pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, width, height) - pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, width, height) + pixbuf = self.get_flow_graph().get_drawing_area().get_pixbuf() pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:]) ################################################## # Gen/Exec/Stop diff --git a/grc/gui/DrawingArea.py b/grc/gui/DrawingArea.py index 6f90049c5..b70468ed0 100644 --- a/grc/gui/DrawingArea.py +++ b/grc/gui/DrawingArea.py @@ -62,6 +62,11 @@ class DrawingArea(gtk.DrawingArea): self.connect('enter-notify-event', _handle_focus_event, True) def new_pixmap(self, width, height): return gtk.gdk.Pixmap(self.window, width, height, -1) + def get_pixbuf(self): + width, height = self._pixmap.get_size() + pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, width, height) + pixbuf.get_from_drawable(self._pixmap, self._pixmap.get_colormap(), 0, 0, 0, 0, width, height) + return pixbuf ########################################################################## ## Handlers -- cgit From 9edb56cf1800d929d41561a3121495d855ca0e4d Mon Sep 17 00:00:00 2001 From: jblum Date: Thu, 2 Jul 2009 02:06:20 +0000 Subject: removed redundant constants.py entry in makefile git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11323 221aa14e-8319-0410-a670-987f0aec2ac5 --- grc/gui/Makefile.am | 1 - 1 file changed, 1 deletion(-) (limited to 'grc/gui') diff --git a/grc/gui/Makefile.am b/grc/gui/Makefile.am index c31bc5f61..cb45d5359 100644 --- a/grc/gui/Makefile.am +++ b/grc/gui/Makefile.am @@ -37,7 +37,6 @@ ourpython_PYTHON = \ Actions.py \ Bars.py \ BlockTreeWindow.py \ - Constants.py \ Dialogs.py \ DrawingArea.py \ FileDialogs.py \ -- cgit From 25c5d91fb7c4b54f1e7d77fd9af213a3675a8339 Mon Sep 17 00:00:00 2001 From: jblum Date: Mon, 6 Jul 2009 02:28:52 +0000 Subject: Merged r11309:11357 from grc branch. Adds notebook cabability to grc and its wxgui windows/controls. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11358 221aa14e-8319-0410-a670-987f0aec2ac5 --- grc/gui/Param.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Param.py b/grc/gui/Param.py index 3029569b4..a11fd9065 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -25,8 +25,7 @@ import gtk PARAM_MARKUP_TMPL="""\ #set $foreground = $param.is_valid() and 'black' or 'red' -#set $value = not $param.is_valid() and 'error' or repr($param) -$encode($param.get_name()): $encode($value)""" +$encode($param.get_name()): $encode(repr($param))""" PARAM_LABEL_MARKUP_TMPL="""\ #set $foreground = $param.is_valid() and 'black' or 'red' -- cgit From 253018c6cdb114f5662a2d7ba8ed748c6e68e3a7 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 14 Aug 2009 18:10:11 +0000 Subject: Added git ignore files auto created from svn:ignore properties. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11592 221aa14e-8319-0410-a670-987f0aec2ac5 --- grc/gui/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 grc/gui/.gitignore (limited to 'grc/gui') diff --git a/grc/gui/.gitignore b/grc/gui/.gitignore new file mode 100644 index 000000000..b336cc7ce --- /dev/null +++ b/grc/gui/.gitignore @@ -0,0 +1,2 @@ +/Makefile +/Makefile.in -- cgit From de213686dfa9608bcf59e2c14a4e326049e9779e Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Fri, 14 Aug 2009 23:23:53 -0700 Subject: params, sources, and sinks now stored internally as lists. The keys for said objects are now only stored in one place (in the object). --- grc/gui/Block.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Block.py b/grc/gui/Block.py index 0496f0a28..4add3aa19 100644 --- a/grc/gui/Block.py +++ b/grc/gui/Block.py @@ -43,7 +43,7 @@ class Block(Element): Add graphics related params to the block. """ #add the position param - self._params['_coordinate'] = self.get_parent().get_parent().Param( + self.get_params().append(self.get_parent().get_parent().Param( self, odict({ 'name': 'GUI Coordinate', @@ -52,8 +52,8 @@ class Block(Element): 'value': '(0, 0)', 'hide': 'all', }) - ) - self._params['_rotation'] = self.get_parent().get_parent().Param( + )) + self.get_params().append(self.get_parent().get_parent().Param( self, odict({ 'name': 'GUI Rotation', @@ -62,7 +62,7 @@ class Block(Element): 'value': '0', 'hide': 'all', }) - ) + )) Element.__init__(self) def get_coordinate(self): -- cgit From 63c928575c10741ac6a6c3c3c8be9c238e7b8432 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Mon, 17 Aug 2009 00:54:11 -0700 Subject: Removed Source and Sink classes as Port subclasses. A port can be a source or a sink based on the dir parameter. --- grc/gui/Platform.py | 5 ++--- grc/gui/Port.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Platform.py b/grc/gui/Platform.py index a32b0209f..1530a69d6 100644 --- a/grc/gui/Platform.py +++ b/grc/gui/Platform.py @@ -1,5 +1,5 @@ """ -Copyright 2008 Free Software Foundation, Inc. +Copyright 2008, 2009 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -38,8 +38,7 @@ def Platform(platform): ('FlowGraph', FlowGraph), ('Connection', Connection), ('Block', Block), - ('Source', Port), - ('Sink', Port), + ('Port', Port), ('Param', Param), ): old_value = getattr(platform, attr) diff --git a/grc/gui/Port.py b/grc/gui/Port.py index d1f36f8b9..6fc2c4b15 100644 --- a/grc/gui/Port.py +++ b/grc/gui/Port.py @@ -1,5 +1,5 @@ """ -Copyright 2007 Free Software Foundation, Inc. +Copyright 2007, 2008, 2009 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or -- cgit From b8df6584312f1f03d0bf86375945b8d743b6a6d7 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Mon, 24 Aug 2009 20:40:36 -0700 Subject: renamed the colors dialog to types --- grc/gui/ActionHandler.py | 6 +++--- grc/gui/Actions.py | 4 ++-- grc/gui/Bars.py | 2 +- grc/gui/Dialogs.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index ff137f669..9af580e8e 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -133,7 +133,7 @@ class ActionHandler: Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS, Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY, Actions.FLOW_GRAPH_SCREEN_CAPTURE, Actions.HELP_WINDOW_DISPLAY, - Actions.COLORS_WINDOW_DISPLAY, + Actions.TYPES_WINDOW_DISPLAY, ): Actions.get_action_from_name(action).set_sensitive(True) if not self.init_file_paths: self.init_file_paths = Preferences.files_open() @@ -237,8 +237,8 @@ class ActionHandler: Dialogs.AboutDialog(self.get_flow_graph().get_parent()) elif state == Actions.HELP_WINDOW_DISPLAY: Dialogs.HelpDialog() - elif state == Actions.COLORS_WINDOW_DISPLAY: - Dialogs.ColorsDialog(self.get_flow_graph().get_parent()) + elif state == Actions.TYPES_WINDOW_DISPLAY: + Dialogs.TypesDialog(self.get_flow_graph().get_parent()) ################################################## # Param Modifications ################################################## diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index 3695e09ef..c3ef2711a 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -57,7 +57,7 @@ FLOW_GRAPH_KILL = 'flow graph kill' FLOW_GRAPH_SCREEN_CAPTURE = 'flow graph screen capture' ABOUT_WINDOW_DISPLAY = 'about window display' HELP_WINDOW_DISPLAY = 'help window display' -COLORS_WINDOW_DISPLAY = 'colors window display' +TYPES_WINDOW_DISPLAY = 'types window display' ###################################################################################################### # Action Key Map @@ -132,7 +132,7 @@ _actions_list = ( gtk.Action(BLOCK_PASTE, '_Paste', 'Paste', gtk.STOCK_PASTE), gtk.Action(ABOUT_WINDOW_DISPLAY, '_About', 'About this program', gtk.STOCK_ABOUT), gtk.Action(HELP_WINDOW_DISPLAY, '_Help', 'Usage Tips', gtk.STOCK_HELP), - gtk.Action(COLORS_WINDOW_DISPLAY, '_Colors', 'Color Mapping', gtk.STOCK_DIALOG_INFO), + gtk.Action(TYPES_WINDOW_DISPLAY, '_Types', 'Types Color Mapping', gtk.STOCK_DIALOG_INFO), gtk.Action(FLOW_GRAPH_GEN, '_Generate', 'Generate the flow graph', gtk.STOCK_CONVERT), gtk.Action(FLOW_GRAPH_EXEC, '_Execute', 'Execute the flow graph', gtk.STOCK_EXECUTE), gtk.Action(FLOW_GRAPH_KILL, '_Kill', 'Kill the flow graph', gtk.STOCK_STOP), diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py index e0c547eba..697d48a3c 100644 --- a/grc/gui/Bars.py +++ b/grc/gui/Bars.py @@ -88,7 +88,7 @@ MENU_BAR_LIST = ( ]), (gtk.Action('Help', '_Help', None, None), [ Actions.HELP_WINDOW_DISPLAY, - Actions.COLORS_WINDOW_DISPLAY, + Actions.TYPES_WINDOW_DISPLAY, None, Actions.ABOUT_WINDOW_DISPLAY, ]), diff --git a/grc/gui/Dialogs.py b/grc/gui/Dialogs.py index 8d764e28e..3cf617b92 100644 --- a/grc/gui/Dialogs.py +++ b/grc/gui/Dialogs.py @@ -98,8 +98,8 @@ COLORS_DIALOG_MARKUP_TMPL = """\ #end if """ -def ColorsDialog(platform): MessageDialogHelper( +def TypesDialog(platform): MessageDialogHelper( type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_CLOSE, - title='Colors', + title='Types', markup=Utils.parse_template(COLORS_DIALOG_MARKUP_TMPL, colors=platform.get_colors())) -- cgit From 854bed10dfb61e9f9feab5259a75e809941089ab Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 26 Aug 2009 11:23:23 -0700 Subject: Added virtual sink and logic to clone port. Tweaks to the base validation routines. Validate twice in the update until rewrite functions are implemented. --- grc/gui/ActionHandler.py | 2 -- grc/gui/FlowGraph.py | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 9af580e8e..0e64aa89d 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -221,13 +221,11 @@ class ActionHandler: elif state == Actions.PORT_CONTROLLER_INC: if self.get_flow_graph().port_controller_modify_selected(1): self.get_flow_graph().update() - self.get_flow_graph().update() #2 times self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) self.get_page().set_saved(False) elif state == Actions.PORT_CONTROLLER_DEC: if self.get_flow_graph().port_controller_modify_selected(-1): self.get_flow_graph().update() - self.get_flow_graph().update() #2 times self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) self.get_page().set_saved(False) ################################################## diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index f8028f199..007bb622c 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -292,8 +292,12 @@ class FlowGraph(Element): def update(self): """ Call update on all elements. + Validate twice: + 1) elements call special rewrite rules that may break validation + 2) elements should come up with the same results, validation can pass """ self.validate() + self.validate() for element in self.get_elements(): element.update() ########################################################################## -- cgit From dc9e9db16047ec589a7b0488fac04c5bb682903c Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 26 Aug 2009 13:29:28 -0700 Subject: added rewrite methods to element to separate from validation logic --- grc/gui/FlowGraph.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index 007bb622c..5e645be72 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -291,12 +291,10 @@ class FlowGraph(Element): def update(self): """ + Do a global rewrite and validate. Call update on all elements. - Validate twice: - 1) elements call special rewrite rules that may break validation - 2) elements should come up with the same results, validation can pass """ - self.validate() + self.rewrite() self.validate() for element in self.get_elements(): element.update() -- cgit From 152fcbc219cd2e4f6df7b38843844bc85fdf2bc2 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sun, 30 Aug 2009 10:34:10 -0700 Subject: Switched the python classes to inherit from the base and gui classes. Use only **kwargs so all contructor parameters must be passed with keys. Moved gui input forms classes from base to gui param module. --- grc/gui/ActionHandler.py | 2 -- grc/gui/Block.py | 8 ++--- grc/gui/Param.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++++ grc/gui/Platform.py | 29 ++---------------- 4 files changed, 83 insertions(+), 33 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 0e64aa89d..8f317d6a8 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -30,7 +30,6 @@ from threading import Thread import Messages from .. base import ParseXML import random -from Platform import Platform from MainWindow import MainWindow from ParamsDialog import ParamsDialog import Dialogs @@ -53,7 +52,6 @@ class ActionHandler: @param platform platform module """ self.clipboard = None - platform = Platform(platform) for action in Actions.get_all_actions(): action.connect('activate', self._handle_actions) #setup the main window self.main_window = MainWindow(self.handle_states, platform) diff --git a/grc/gui/Block.py b/grc/gui/Block.py index 4add3aa19..0f3e511d8 100644 --- a/grc/gui/Block.py +++ b/grc/gui/Block.py @@ -44,8 +44,8 @@ class Block(Element): """ #add the position param self.get_params().append(self.get_parent().get_parent().Param( - self, - odict({ + block=self, + n=odict({ 'name': 'GUI Coordinate', 'key': '_coordinate', 'type': 'raw', @@ -54,8 +54,8 @@ class Block(Element): }) )) self.get_params().append(self.get_parent().get_parent().Param( - self, - odict({ + block=self, + n=odict({ 'name': 'GUI Rotation', 'key': '_rotation', 'type': 'raw', diff --git a/grc/gui/Param.py b/grc/gui/Param.py index a11fd9065..4955d3336 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -23,6 +23,71 @@ import pygtk pygtk.require('2.0') import gtk +class InputParam(gtk.HBox): + """The base class for an input parameter inside the input parameters dialog.""" + + def __init__(self, param, _handle_changed): + gtk.HBox.__init__(self) + self.param = param + self._handle_changed = _handle_changed + self.label = gtk.Label('') #no label, markup is added by set_markup + self.label.set_size_request(150, -1) + self.pack_start(self.label, False) + self.set_markup = lambda m: self.label.set_markup(m) + self.tp = None + def set_color(self, color): pass + +class EntryParam(InputParam): + """Provide an entry box for strings and numbers.""" + + def __init__(self, *args, **kwargs): + InputParam.__init__(self, *args, **kwargs) + self.entry = input = gtk.Entry() + input.set_text(self.param.get_value()) + input.connect('changed', self._handle_changed) + self.pack_start(input, True) + self.get_text = input.get_text + #tool tip + self.tp = gtk.Tooltips() + self.tp.set_tip(self.entry, '') + self.tp.enable() + def set_color(self, color): self.entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) + +class EnumParam(InputParam): + """Provide an entry box for Enum types with a drop down menu.""" + + def __init__(self, *args, **kwargs): + InputParam.__init__(self, *args, **kwargs) + self._input = gtk.combo_box_new_text() + for option in self.param.get_options(): self._input.append_text(option.get_name()) + self._input.set_active(self.param.get_option_keys().index(self.param.get_value())) + self._input.connect('changed', self._handle_changed) + self.pack_start(self._input, False) + def get_text(self): return self.param.get_option_keys()[self._input.get_active()] + +class EnumEntryParam(InputParam): + """Provide an entry box and drop down menu for Raw Enum types.""" + + def __init__(self, *args, **kwargs): + InputParam.__init__(self, *args, **kwargs) + self._input = gtk.combo_box_entry_new_text() + for option in self.param.get_options(): self._input.append_text(option.get_name()) + try: self._input.set_active(self.param.get_option_keys().index(self.param.get_value())) + except: + self._input.set_active(-1) + self._input.get_child().set_text(self.param.get_value()) + self._input.connect('changed', self._handle_changed) + self._input.get_child().connect('changed', self._handle_changed) + self.pack_start(self._input, False) + def get_text(self): + if self._input.get_active() == -1: return self._input.get_child().get_text() + return self.param.get_option_keys()[self._input.get_active()] + def set_color(self, color): + if self._input.get_active() == -1: #custom entry, use color + self._input.get_child().modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) + else: #from enum, make white background + self._input.get_child().modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse('#ffffff')) + PARAM_MARKUP_TMPL="""\ #set $foreground = $param.is_valid() and 'black' or 'red' $encode($param.get_name()): $encode(repr($param))""" @@ -49,6 +114,18 @@ Error: class Param(Element): """The graphical parameter.""" + def get_input_class(self): + """ + Get the graphical gtk class to represent this parameter. + An enum requires and combo parameter. + A non-enum with options gets a combined entry/combo parameter. + All others get a standard entry parameter. + @return gtk input class + """ + if self.is_enum(): return EnumParam + if self.get_options(): return EnumEntryParam + return EntryParam + def update(self): """ Called when an external change occurs. diff --git a/grc/gui/Platform.py b/grc/gui/Platform.py index 1530a69d6..8f0aa533d 100644 --- a/grc/gui/Platform.py +++ b/grc/gui/Platform.py @@ -17,31 +17,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -from FlowGraph import FlowGraph -from Connection import Connection -from Block import Block -from Port import Port -from Param import Param +from Element import Element -def conjoin_classes(name, c1, c2): - exec(""" -class %s(c1, c2): - def __init__(self, *args, **kwargs): - c1.__init__(self, *args, **kwargs) - c2.__init__(self, *args, **kwargs) -"""%name, locals()) - return locals()[name] - -def Platform(platform): - #combine with gui class - for attr, value in ( - ('FlowGraph', FlowGraph), - ('Connection', Connection), - ('Block', Block), - ('Port', Port), - ('Param', Param), - ): - old_value = getattr(platform, attr) - c = conjoin_classes(attr, old_value, value) - setattr(platform, attr, c) - return platform +class Platform(Element): pass -- cgit From 5f54b018b3a84ba4b68009a1c326ba73eaea8cfd Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sat, 5 Sep 2009 01:54:41 -0700 Subject: standardized the Element inheritance __init__ usage in gui --- grc/gui/Block.py | 2 +- grc/gui/Connection.py | 2 ++ grc/gui/Element.py | 2 +- grc/gui/FlowGraph.py | 2 +- grc/gui/Param.py | 2 ++ grc/gui/Platform.py | 3 ++- grc/gui/Port.py | 2 +- 7 files changed, 10 insertions(+), 5 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Block.py b/grc/gui/Block.py index 0f3e511d8..68c4da9c3 100644 --- a/grc/gui/Block.py +++ b/grc/gui/Block.py @@ -37,7 +37,7 @@ BLOCK_MARKUP_TMPL="""\ class Block(Element): """The graphical signal block.""" - def __init__(self, *args, **kwargs): + def __init__(self): """ Block contructor. Add graphics related params to the block. diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py index 013bcb00f..a85650ee2 100644 --- a/grc/gui/Connection.py +++ b/grc/gui/Connection.py @@ -32,6 +32,8 @@ class Connection(Element): The arrow coloring exposes the enabled and valid states. """ + def __init__(self): Element.__init__(self) + def get_coordinate(self): """ Get the 0,0 coordinate. diff --git a/grc/gui/Element.py b/grc/gui/Element.py index 315191723..ecf1de1ca 100644 --- a/grc/gui/Element.py +++ b/grc/gui/Element.py @@ -32,7 +32,7 @@ class Element(object): and methods to detect selection of those areas. """ - def __init__(self, *args, **kwargs): + def __init__(self): """ Make a new list of rectangular areas and lines, and set the coordinate and the rotation. """ diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index 5e645be72..8a908ff50 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -39,7 +39,7 @@ class FlowGraph(Element): and the connections between inputs and outputs. """ - def __init__(self, *args, **kwargs): + def __init__(self): """ FlowGraph contructor. Create a list for signal blocks and connections. Connect mouse handlers. diff --git a/grc/gui/Param.py b/grc/gui/Param.py index 4955d3336..5cc8d9c7f 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -114,6 +114,8 @@ Error: class Param(Element): """The graphical parameter.""" + def __init__(self): Element.__init__(self) + def get_input_class(self): """ Get the graphical gtk class to represent this parameter. diff --git a/grc/gui/Platform.py b/grc/gui/Platform.py index 8f0aa533d..8bbfaca23 100644 --- a/grc/gui/Platform.py +++ b/grc/gui/Platform.py @@ -19,4 +19,5 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from Element import Element -class Platform(Element): pass +class Platform(Element): + def __init__(self): Element.__init__(self) diff --git a/grc/gui/Port.py b/grc/gui/Port.py index 6fc2c4b15..9c8d87a16 100644 --- a/grc/gui/Port.py +++ b/grc/gui/Port.py @@ -34,7 +34,7 @@ PORT_MARKUP_TMPL="""\ class Port(Element): """The graphical port.""" - def __init__(self, *args, **kwargs): + def __init__(self): """ Port contructor. Create list of connector coordinates. -- cgit From 5bb2a70a94be9c0f83712ee259b7125e3a582b08 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sat, 5 Sep 2009 02:01:41 -0700 Subject: replaced dict[rot] storage of areas and lines with a single list for the current rotation --- grc/gui/Element.py | 28 ++++++++++++---------------- grc/gui/Port.py | 2 +- 2 files changed, 13 insertions(+), 17 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Element.py b/grc/gui/Element.py index ecf1de1ca..bda187059 100644 --- a/grc/gui/Element.py +++ b/grc/gui/Element.py @@ -70,14 +70,14 @@ class Element(object): @param bg_color the color for the inside of the rectangle """ X,Y = self.get_coordinate() - for (rX,rY),(W,H) in self.areas_dict[self.get_rotation()]: + for (rX,rY),(W,H) in self._areas_list: aX = X + rX aY = Y + rY gc.set_foreground(bg_color) window.draw_rectangle(gc, True, aX, aY, W, H) gc.set_foreground(border_color) window.draw_rectangle(gc, False, aX, aY, W, H) - for (x1, y1),(x2, y2) in self.lines_dict[self.get_rotation()]: + for (x1, y1),(x2, y2) in self._lines_list: gc.set_foreground(border_color) window.draw_line(gc, X+x1, Y+y1, X+x2, Y+y2) @@ -90,8 +90,8 @@ class Element(object): def clear(self): """Empty the lines and areas.""" - self.areas_dict = dict((rotation, list()) for rotation in POSSIBLE_ROTATIONS) - self.lines_dict = dict((rotation, list()) for rotation in POSSIBLE_ROTATIONS) + self._areas_list = list() + self._lines_list = list() def set_coordinate(self, coor): """ @@ -136,7 +136,7 @@ class Element(object): X, Y = self.get_coordinate() self.set_coordinate((X+deltaX, Y+deltaY)) - def add_area(self, rel_coor, area, rotation=None): + def add_area(self, rel_coor, area): """ Add an area to the area list. An area is actually a coordinate relative to the main coordinate @@ -144,25 +144,21 @@ class Element(object): A positive width is to the right of the coordinate. A positive height is above the coordinate. The area is associated with a rotation. - If rotation is not specified, the element's current rotation is used. @param rel_coor (x,y) offset from this element's coordinate @param area (width,height) tuple - @param rotation rotation in degrees """ - self.areas_dict[rotation or self.get_rotation()].append((rel_coor, area)) + self._areas_list.append((rel_coor, area)) - def add_line(self, rel_coor1, rel_coor2, rotation=None): + def add_line(self, rel_coor1, rel_coor2): """ Add a line to the line list. A line is defined by 2 relative coordinates. Lines must be horizontal or vertical. The line is associated with a rotation. - If rotation is not specified, the element's current rotation is used. @param rel_coor1 relative (x1,y1) tuple @param rel_coor2 relative (x2,y2) tuple - @param rotation rotation in degrees """ - self.lines_dict[rotation or self.get_rotation()].append((rel_coor1, rel_coor2)) + self._lines_list.append((rel_coor1, rel_coor2)) def what_is_selected(self, coor, coor_m=None): """ @@ -183,24 +179,24 @@ class Element(object): if coor_m: x_m, y_m = [a-b for a,b in zip(coor_m, self.get_coordinate())] #handle rectangular areas - for (x1,y1), (w,h) in self.areas_dict[self.get_rotation()]: + for (x1,y1), (w,h) in self._areas_list: if in_between(x1, x, x_m) and in_between(y1, y, y_m) or \ in_between(x1+w, x, x_m) and in_between(y1, y, y_m) or \ in_between(x1, x, x_m) and in_between(y1+h, y, y_m) or \ in_between(x1+w, x, x_m) and in_between(y1+h, y, y_m): return self #handle horizontal or vertical lines - for (x1, y1), (x2, y2) in self.lines_dict[self.get_rotation()]: + for (x1, y1), (x2, y2) in self._lines_list: if in_between(x1, x, x_m) and in_between(y1, y, y_m) or \ in_between(x2, x, x_m) and in_between(y2, y, y_m): return self return None else: #handle rectangular areas - for (x1,y1), (w,h) in self.areas_dict[self.get_rotation()]: + for (x1,y1), (w,h) in self._areas_list: if in_between(x, x1, x1+w) and in_between(y, y1, y1+h): return self #handle horizontal or vertical lines - for (x1, y1), (x2, y2) in self.lines_dict[self.get_rotation()]: + for (x1, y1), (x2, y2) in self._lines_list: if x1 == x2: x1, x2 = x1-LINE_SELECT_SENSITIVITY, x2+LINE_SELECT_SENSITIVITY if y1 == y2: y1, y2 = y1-LINE_SELECT_SENSITIVITY, y2+LINE_SELECT_SENSITIVITY if in_between(x, x1, x2) and in_between(y, y1, y2): return self diff --git a/grc/gui/Port.py b/grc/gui/Port.py index 9c8d87a16..f8bfefee6 100644 --- a/grc/gui/Port.py +++ b/grc/gui/Port.py @@ -114,7 +114,7 @@ class Port(Element): border_color=self.is_highlighted() and Colors.HIGHLIGHT_COLOR or Colors.BORDER_COLOR, ) X,Y = self.get_coordinate() - (x,y),(w,h) = self.areas_dict[self.get_rotation()][0] #use the first area's sizes to place the labels + (x,y),(w,h) = self._areas_list[0] #use the first area's sizes to place the labels if self.is_horizontal(): window.draw_image(gc, self.horizontal_label, 0, 0, x+X+(self.W-self.w)/2, y+Y+(self.H-self.h)/2, -1, -1) elif self.is_vertical(): -- cgit From fa465d160b0c53fae3ad7876cf429263157dd60a Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sat, 5 Sep 2009 02:21:37 -0700 Subject: Created recursive create labels and shapes method for gui element. Replaces update methods in the gui classes and simplifies calls. The master update method in flow graph calls create labels and shapes. --- grc/gui/Block.py | 25 ++++++++++++------------- grc/gui/Connection.py | 7 ++++--- grc/gui/Element.py | 19 +++++++++++++++---- grc/gui/FlowGraph.py | 7 ++++--- grc/gui/Port.py | 7 ++++--- 5 files changed, 39 insertions(+), 26 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Block.py b/grc/gui/Block.py index 68c4da9c3..fd8cfc226 100644 --- a/grc/gui/Block.py +++ b/grc/gui/Block.py @@ -113,23 +113,16 @@ class Block(Element): """ self.get_param('_rotation').set_value(str(rot)) - def update(self): + def create_shapes(self): """Update the block, parameters, and ports when a change occurs.""" - self._bg_color = self.get_enabled() and Colors.BLOCK_ENABLED_COLOR or Colors.BLOCK_DISABLED_COLOR - self.clear() - self._create_labels() - self.W = self.label_width + 2*BLOCK_LABEL_PADDING - self.H = max(*( - [self.label_height+2*BLOCK_LABEL_PADDING] + [2*PORT_BORDER_SEPARATION + \ - sum([port.H + PORT_SEPARATION for port in ports]) - PORT_SEPARATION - for ports in (self.get_sources(), self.get_sinks())] - )) + Element.create_shapes(self) if self.is_horizontal(): self.add_area((0, 0), (self.W, self.H)) elif self.is_vertical(): self.add_area((0, 0), (self.H, self.W)) - map(lambda p: p.update(), self.get_ports()) - def _create_labels(self): + def create_labels(self): """Create the labels for the signal block.""" + Element.create_labels(self) + self._bg_color = self.get_enabled() and Colors.BLOCK_ENABLED_COLOR or Colors.BLOCK_DISABLED_COLOR layouts = list() #create the main layout layout = gtk.DrawingArea().create_pango_layout('') @@ -164,7 +157,13 @@ class Block(Element): self.vertical_label = vimage = gtk.gdk.Image(gtk.gdk.IMAGE_NORMAL, pixmap.get_visual(), height, width) for i in range(width): for j in range(height): vimage.put_pixel(j, width-i-1, image.get_pixel(i, j)) - map(lambda p: p._create_labels(), self.get_ports()) + #calculate width and height needed + self.W = self.label_width + 2*BLOCK_LABEL_PADDING + self.H = max(*( + [self.label_height+2*BLOCK_LABEL_PADDING] + [2*PORT_BORDER_SEPARATION + \ + sum([port.H + PORT_SEPARATION for port in ports]) - PORT_SEPARATION + for ports in (self.get_sources(), self.get_sinks())] + )) def draw(self, gc, window): """ diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py index a85650ee2..45f8a689a 100644 --- a/grc/gui/Connection.py +++ b/grc/gui/Connection.py @@ -50,8 +50,9 @@ class Connection(Element): """ return 0 - def update(self): + def create_shapes(self): """Precalculate relative coordinates.""" + Element.create_shapes(self) self._sink_rot = None self._source_rot = None self._sink_coor = None @@ -74,7 +75,7 @@ class Connection(Element): def _update_after_move(self): """Calculate coordinates.""" - self.clear() + self.clear() #FIXME do i want this here? #source connector source = self.get_source() X, Y = source.get_connector_coordinate() @@ -125,7 +126,7 @@ class Connection(Element): sink = self.get_sink() source = self.get_source() #check for changes - if self._sink_rot != sink.get_rotation() or self._source_rot != source.get_rotation(): self.update() + if self._sink_rot != sink.get_rotation() or self._source_rot != source.get_rotation(): self.create_shapes() elif self._sink_coor != sink.get_coordinate() or self._source_coor != source.get_coordinate(): self._update_after_move() #cache values self._sink_rot = sink.get_rotation() diff --git a/grc/gui/Element.py b/grc/gui/Element.py index bda187059..2e20b9a22 100644 --- a/grc/gui/Element.py +++ b/grc/gui/Element.py @@ -61,6 +61,21 @@ class Element(object): rotation = rotation or self.get_rotation() return rotation in (90, 270) + def create_labels(self): + """ + Create labels (if applicable) and call on all children. + Call this base method before creating labels in the element. + """ + for child in self.get_children(): child.create_labels() + + def create_shapes(self): + """ + Create shapes (if applicable) and call on all children. + Call this base method before creating shapes in the element. + """ + self.clear() + for child in self.get_children(): child.create_shapes() + def draw(self, gc, window, border_color, bg_color): """ Draw in the given window. @@ -216,7 +231,3 @@ class Element(object): if rotation not in POSSIBLE_ROTATIONS: raise Exception('"%s" is not one of the possible rotations: (%s)'%(rotation, POSSIBLE_ROTATIONS)) self.rotation = rotation - - def update(self): - """Do nothing for the update. Dummy method.""" - pass diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index 8a908ff50..35ccf5e27 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -291,12 +291,13 @@ class FlowGraph(Element): def update(self): """ - Do a global rewrite and validate. - Call update on all elements. + Call the top level rewrite and validate. + Call the top level create labels and shapes. """ self.rewrite() self.validate() - for element in self.get_elements(): element.update() + self.create_labels() + self.create_shapes() ########################################################################## ## Get Selected diff --git a/grc/gui/Port.py b/grc/gui/Port.py index f8bfefee6..6763f6cbd 100644 --- a/grc/gui/Port.py +++ b/grc/gui/Port.py @@ -42,9 +42,9 @@ class Port(Element): Element.__init__(self) self.connector_coordinates = dict() - def update(self): + def create_shapes(self): """Create new areas and labels for the port.""" - self.clear() + Element.create_shapes(self) #get current rotation rotation = self.get_rotation() #get all sibling ports @@ -82,8 +82,9 @@ class Port(Element): #the connector length self._connector_length = CONNECTOR_EXTENSION_MINIMAL + CONNECTOR_EXTENSION_INCREMENT*index - def _create_labels(self): + def create_labels(self): """Create the labels for the socket.""" + Element.create_labels(self) self._bg_color = Colors.get_color(self.get_color()) #create the layout layout = gtk.DrawingArea().create_pango_layout('') -- cgit From caf93a02068e8379ffa7f553deabb62cfc42b9a7 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sat, 5 Sep 2009 02:38:46 -0700 Subject: remove unused imports, copyright date update, tweak --- grc/gui/Block.py | 2 +- grc/gui/Connection.py | 2 +- grc/gui/Element.py | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Block.py b/grc/gui/Block.py index fd8cfc226..8c65bf06f 100644 --- a/grc/gui/Block.py +++ b/grc/gui/Block.py @@ -135,7 +135,7 @@ class Block(Element): layouts.append(layout) w,h = layout.get_pixel_size() self.label_width = max(w, self.label_width) - self.label_height = self.label_height + h + LABEL_SEPARATION + self.label_height += h + LABEL_SEPARATION width = self.label_width height = self.label_height #setup the pixmap diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py index 45f8a689a..fabf34ee7 100644 --- a/grc/gui/Connection.py +++ b/grc/gui/Connection.py @@ -1,5 +1,5 @@ """ -Copyright 2007, 2008 Free Software Foundation, Inc. +Copyright 2007, 2008, 2009 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or diff --git a/grc/gui/Element.py b/grc/gui/Element.py index 2e20b9a22..f0518ee12 100644 --- a/grc/gui/Element.py +++ b/grc/gui/Element.py @@ -1,5 +1,5 @@ """ -Copyright 2007 Free Software Foundation, Inc. +Copyright 2007, 2008, 2009 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -17,11 +17,9 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -import Colors import pygtk pygtk.require('2.0') import gtk -import pango from Constants import LINE_SELECT_SENSITIVITY from Constants import POSSIBLE_ROTATIONS -- cgit From 79bace9eb9e441405535e082f3f0ee1a26740fe0 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sat, 5 Sep 2009 21:11:51 -0700 Subject: renamed params dialog to props dialog --- grc/gui/ActionHandler.py | 4 +- grc/gui/Makefile.am | 2 +- grc/gui/ParamsDialog.py | 145 ----------------------------------------------- grc/gui/PropsDialog.py | 145 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 148 deletions(-) delete mode 100644 grc/gui/ParamsDialog.py create mode 100644 grc/gui/PropsDialog.py (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 8f317d6a8..f12893579 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -31,7 +31,7 @@ import Messages from .. base import ParseXML import random from MainWindow import MainWindow -from ParamsDialog import ParamsDialog +from PropsDialog import PropsDialog import Dialogs from FileDialogs import OpenFlowGraphFileDialog, SaveFlowGraphFileDialog, SaveImageFileDialog @@ -240,7 +240,7 @@ class ActionHandler: ################################################## elif state == Actions.BLOCK_PARAM_MODIFY: selected_block = self.get_flow_graph().get_selected_block() - if selected_block and ParamsDialog(selected_block).run(): + if selected_block and PropsDialog(selected_block).run(): self.get_flow_graph().update() self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) self.get_page().set_saved(False) diff --git a/grc/gui/Makefile.am b/grc/gui/Makefile.am index cb45d5359..b14817d04 100644 --- a/grc/gui/Makefile.am +++ b/grc/gui/Makefile.am @@ -43,7 +43,7 @@ ourpython_PYTHON = \ MainWindow.py \ Messages.py \ NotebookPage.py \ - ParamsDialog.py \ + PropsDialog.py \ Preferences.py \ StateCache.py \ __init__.py diff --git a/grc/gui/ParamsDialog.py b/grc/gui/ParamsDialog.py deleted file mode 100644 index ccf19d1a2..000000000 --- a/grc/gui/ParamsDialog.py +++ /dev/null @@ -1,145 +0,0 @@ -""" -Copyright 2007, 2008, 2009 Free Software Foundation, Inc. -This file is part of GNU Radio - -GNU Radio Companion is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -GNU Radio Companion is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA -""" - -import pygtk -pygtk.require('2.0') -import gtk - -from Dialogs import TextDisplay -from Constants import MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT - -def get_title_label(title): - """ - Get a title label for the params window. - The title will be bold, underlined, and left justified. - @param title the text of the title - @return a gtk object - """ - label = gtk.Label() - label.set_markup('\n%s:\n'%title) - hbox = gtk.HBox() - hbox.pack_start(label, False, False, padding=11) - return hbox - -class ParamsDialog(gtk.Dialog): - """A dialog box to set block parameters.""" - - def __init__(self, block): - """ - SignalBlockParamsDialog contructor. - @param block the signal block - """ - gtk.Dialog.__init__(self, - title='Properties: %s'%block.get_name(), - buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE), - ) - self.block = block - self.set_size_request(MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT) - vbox = gtk.VBox() - #Add the title label - vbox.pack_start(get_title_label('Parameters'), False) - #Create the scrolled window to hold all the parameters - scrolled_window = gtk.ScrolledWindow() - scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrolled_window.add_with_viewport(vbox) - self.vbox.pack_start(scrolled_window, True) - #Error Messages for the block - self._error_box = gtk.VBox() - self._error_messages_text_display = TextDisplay() - self._error_box.pack_start(gtk.Label(), False, False, 7) #spacing - self._error_box.pack_start(get_title_label('Error Messages'), False) - self._error_box.pack_start(self._error_messages_text_display, False) - #Docs for the block - self._docs_box = err_box = gtk.VBox() - self._docs_text_display = TextDisplay() - self._docs_box.pack_start(gtk.Label(), False, False, 7) #spacing - self._docs_box.pack_start(get_title_label('Documentation'), False) - self._docs_box.pack_start(self._docs_text_display, False) - #Add all the parameters - for param in self.block.get_params(): - vbox.pack_start(param.get_input_object(self._handle_changed), False) - #Add the error and docs box - vbox.pack_start(self._error_box, False) - vbox.pack_start(self._docs_box, False) - #connect and show - self.connect('key_press_event', self._handle_key_press) - self.show_all() - #initial update - for param in self.block.get_params(): param.update() - self._update() - - def _update(self): - """ - Update the error messages box. - Hide the box if there are no errors. - Update the documentation block. - Hide the box if there are no docs. - """ - self.block.validate() - #update the errors box - if self.block.is_valid(): self._error_box.hide() - else: self._error_box.show() - messages = '\n\n'.join(self.block.get_error_messages()) - self._error_messages_text_display.set_text(messages) - #update the docs box - if self.block.get_doc(): self._docs_box.show() - else: self._docs_box.hide() - self._docs_text_display.set_text(self.block.get_doc()) - - def _handle_key_press(self, widget, event): - """ - Handle key presses from the keyboard. - Call the ok response when enter is pressed. - @return false to forward the keypress - """ - keyname = gtk.gdk.keyval_name(event.keyval) - if keyname == 'Return': self.response(gtk.RESPONSE_OK) - return False #forward the keypress - - def _handle_changed(self, param): - """ - A change occured, update any dependent parameters: - The enum inside the variable type may have changed and, - the variable param will need an external update. - @param param the graphical parameter that initiated the callback - """ - #update dependent params - if param.is_enum(): - for other_param in param.get_parent().get_params(): - if param.get_key() is not other_param.get_key() and ( - param.get_key() in other_param._type or \ - param.get_key() in other_param._hide): other_param.update() - #update - self._update() - return True - - def run(self): - """ - Call run(). - @return true if a change occured. - """ - original_data = list() - for param in self.block.get_params(): - original_data.append(param.get_value()) - gtk.Dialog.run(self) - self.destroy() - new_data = list() - for param in self.block.get_params(): - new_data.append(param.get_value()) - return original_data != new_data diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py new file mode 100644 index 000000000..200cff1f5 --- /dev/null +++ b/grc/gui/PropsDialog.py @@ -0,0 +1,145 @@ +""" +Copyright 2007, 2008, 2009 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import pygtk +pygtk.require('2.0') +import gtk + +from Dialogs import TextDisplay +from Constants import MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT + +def get_title_label(title): + """ + Get a title label for the params window. + The title will be bold, underlined, and left justified. + @param title the text of the title + @return a gtk object + """ + label = gtk.Label() + label.set_markup('\n%s:\n'%title) + hbox = gtk.HBox() + hbox.pack_start(label, False, False, padding=11) + return hbox + +class PropsDialog(gtk.Dialog): + """A dialog box to set block parameters.""" + + def __init__(self, block): + """ + SignalBlockParamsDialog contructor. + @param block the signal block + """ + gtk.Dialog.__init__(self, + title='Properties: %s'%block.get_name(), + buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE), + ) + self.block = block + self.set_size_request(MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT) + vbox = gtk.VBox() + #Add the title label + vbox.pack_start(get_title_label('Parameters'), False) + #Create the scrolled window to hold all the parameters + scrolled_window = gtk.ScrolledWindow() + scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolled_window.add_with_viewport(vbox) + self.vbox.pack_start(scrolled_window, True) + #Error Messages for the block + self._error_box = gtk.VBox() + self._error_messages_text_display = TextDisplay() + self._error_box.pack_start(gtk.Label(), False, False, 7) #spacing + self._error_box.pack_start(get_title_label('Error Messages'), False) + self._error_box.pack_start(self._error_messages_text_display, False) + #Docs for the block + self._docs_box = err_box = gtk.VBox() + self._docs_text_display = TextDisplay() + self._docs_box.pack_start(gtk.Label(), False, False, 7) #spacing + self._docs_box.pack_start(get_title_label('Documentation'), False) + self._docs_box.pack_start(self._docs_text_display, False) + #Add all the parameters + for param in self.block.get_params(): + vbox.pack_start(param.get_input_object(self._handle_changed), False) + #Add the error and docs box + vbox.pack_start(self._error_box, False) + vbox.pack_start(self._docs_box, False) + #connect and show + self.connect('key_press_event', self._handle_key_press) + self.show_all() + #initial update + for param in self.block.get_params(): param.update() + self._update() + + def _update(self): + """ + Update the error messages box. + Hide the box if there are no errors. + Update the documentation block. + Hide the box if there are no docs. + """ + self.block.validate() + #update the errors box + if self.block.is_valid(): self._error_box.hide() + else: self._error_box.show() + messages = '\n\n'.join(self.block.get_error_messages()) + self._error_messages_text_display.set_text(messages) + #update the docs box + if self.block.get_doc(): self._docs_box.show() + else: self._docs_box.hide() + self._docs_text_display.set_text(self.block.get_doc()) + + def _handle_key_press(self, widget, event): + """ + Handle key presses from the keyboard. + Call the ok response when enter is pressed. + @return false to forward the keypress + """ + keyname = gtk.gdk.keyval_name(event.keyval) + if keyname == 'Return': self.response(gtk.RESPONSE_OK) + return False #forward the keypress + + def _handle_changed(self, param): + """ + A change occured, update any dependent parameters: + The enum inside the variable type may have changed and, + the variable param will need an external update. + @param param the graphical parameter that initiated the callback + """ + #update dependent params + if param.is_enum(): + for other_param in param.get_parent().get_params(): + if param.get_key() is not other_param.get_key() and ( + param.get_key() in other_param._type or \ + param.get_key() in other_param._hide): other_param.update() + #update + self._update() + return True + + def run(self): + """ + Call run(). + @return true if a change occured. + """ + original_data = list() + for param in self.block.get_params(): + original_data.append(param.get_value()) + gtk.Dialog.run(self) + self.destroy() + new_data = list() + for param in self.block.get_params(): + new_data.append(param.get_value()) + return original_data != new_data -- cgit From 49b8dd0586241f59df4b74679349718cbe946fde Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sat, 5 Sep 2009 23:41:49 -0700 Subject: Rework the params/properties dialog and param gui class: Better handles dynamic changes and subsequent code cleanup. --- grc/gui/Param.py | 65 +++++++++++++++++++------------------- grc/gui/PropsDialog.py | 84 +++++++++++++++++++++++++++++++------------------- 2 files changed, 85 insertions(+), 64 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Param.py b/grc/gui/Param.py index 5cc8d9c7f..3c5e99e9e 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -26,10 +26,10 @@ import gtk class InputParam(gtk.HBox): """The base class for an input parameter inside the input parameters dialog.""" - def __init__(self, param, _handle_changed): + def __init__(self, param, callback=None): gtk.HBox.__init__(self) self.param = param - self._handle_changed = _handle_changed + self._callback = callback self.label = gtk.Label('') #no label, markup is added by set_markup self.label.set_size_request(150, -1) self.pack_start(self.label, False) @@ -37,6 +37,34 @@ class InputParam(gtk.HBox): self.tp = None def set_color(self, color): pass + def update(self): + """ + Set the markup, color, and tooltip. + """ + #set the markup + has_cb = \ + hasattr(self.param.get_parent(), 'get_callbacks') and \ + filter(lambda c: self.param.get_key() in c, self.param.get_parent()._callbacks) + self.set_markup(Utils.parse_template(PARAM_LABEL_MARKUP_TMPL, param=self.param, has_cb=has_cb)) + #set the color + self.set_color(self.param.get_color()) + #set the tooltip + if self.tp: self.tp.set_tip( + self.entry, + Utils.parse_template(TIP_MARKUP_TMPL, param=self.param).strip(), + ) + + def _handle_changed(self, *args): + """ + Handle a gui change by setting the new param value, + calling the callback (if applicable), and updating. + """ + #set the new value + self.param.set_value(self.get_text()) + #call the callback + if self._callback: self._callback() + #self.update() #dont update here, parent will update + class EntryParam(InputParam): """Provide an entry box for strings and numbers.""" @@ -138,39 +166,10 @@ class Param(Element): def get_input_object(self, callback=None): """ Get the graphical gtk object to represent this parameter. - Create the input object with this data type and the handle changed method. - @param callback a function of one argument(this param) to be called from the change handler + @param callback a function to be called from the input object. @return gtk input object """ - self._callback = callback - self._input = self.get_input_class()(self, self._handle_changed) - if not self._callback: self.update() - return self._input - - def _handle_changed(self, widget=None): - """ - When the input changes, write the inputs to the data type. - Finish by calling the exteral callback. - """ - self.set_value(self._input.get_text()) - self.validate() - #is param is involved in a callback? #FIXME: messy - has_cb = \ - hasattr(self.get_parent(), 'get_callbacks') and \ - filter(lambda c: self.get_key() in c, self.get_parent()._callbacks) - self._input.set_markup(Utils.parse_template(PARAM_LABEL_MARKUP_TMPL, param=self, has_cb=has_cb)) - #hide/show - if self.get_hide() == 'all': self._input.hide_all() - else: self._input.show_all() - #set the color - self._input.set_color(self.get_color()) - #set the tooltip - if self._input.tp: self._input.tp.set_tip( - self._input.entry, - Utils.parse_template(TIP_MARKUP_TMPL, param=self).strip(), - ) - #execute the external callback - if self._callback: self._callback(self) + return self.get_input_class()(self, callback=callback) def get_layout(self): """ diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index 200cff1f5..bd66b1178 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -38,13 +38,17 @@ def get_title_label(title): return hbox class PropsDialog(gtk.Dialog): - """A dialog box to set block parameters.""" + """ + A dialog to set block parameters, view errors, and view documentation. + """ def __init__(self, block): """ - SignalBlockParamsDialog contructor. - @param block the signal block + Properties dialog contructor. + @param block a block instance """ + self._hash = '' + LABEL_SPACING = 7 gtk.Dialog.__init__(self, title='Properties: %s'%block.get_name(), buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE), @@ -52,46 +56,81 @@ class PropsDialog(gtk.Dialog): self.block = block self.set_size_request(MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT) vbox = gtk.VBox() - #Add the title label - vbox.pack_start(get_title_label('Parameters'), False) #Create the scrolled window to hold all the parameters scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.add_with_viewport(vbox) self.vbox.pack_start(scrolled_window, True) + #Params box for block parameters + self._params_box = gtk.VBox() + self._params_box.pack_start(get_title_label('Parameters'), False) + self._input_object_params = list() #Error Messages for the block self._error_box = gtk.VBox() self._error_messages_text_display = TextDisplay() - self._error_box.pack_start(gtk.Label(), False, False, 7) #spacing + self._error_box.pack_start(gtk.Label(), False, False, LABEL_SPACING) self._error_box.pack_start(get_title_label('Error Messages'), False) self._error_box.pack_start(self._error_messages_text_display, False) #Docs for the block self._docs_box = err_box = gtk.VBox() self._docs_text_display = TextDisplay() - self._docs_box.pack_start(gtk.Label(), False, False, 7) #spacing + self._docs_box.pack_start(gtk.Label(), False, False, LABEL_SPACING) self._docs_box.pack_start(get_title_label('Documentation'), False) self._docs_box.pack_start(self._docs_text_display, False) - #Add all the parameters - for param in self.block.get_params(): - vbox.pack_start(param.get_input_object(self._handle_changed), False) - #Add the error and docs box + #Add the boxes + vbox.pack_start(self._params_box, False) vbox.pack_start(self._error_box, False) vbox.pack_start(self._docs_box, False) - #connect and show + #connect key press event self.connect('key_press_event', self._handle_key_press) + #initial update to populate the params self.show_all() - #initial update - for param in self.block.get_params(): param.update() self._update() + def _params_changed(self): + """ + Have the params in this dialog changed? + Ex: Added, removed, type change, hidden, shown? + Make a hash that uniquely represents the params state. + @return true if changed + """ + old_hash = self._hash + str_accum = '' + for param in self.block.get_params(): + str_accum += param.get_key() + str_accum += param.get_type() + str_accum += param.get_hide() + self._hash = hash(str_accum) + return self._hash != old_hash + def _update(self): """ + Repopulate the parameters box (if changed). + Update all the input parameters. Update the error messages box. Hide the box if there are no errors. Update the documentation block. Hide the box if there are no docs. """ + #update for the block + self.block.rewrite() self.block.validate() + #update the params box + if self._params_changed(): + #empty the params box + for io_param in list(self._input_object_params): + self._params_box.remove(io_param) + self._input_object_params.remove(io_param) + io_param.destroy() + #repopulate the params box + for param in self.block.get_params(): + if param.get_hide() == 'all': continue + io_param = param.get_input_object(self._update) + self._input_object_params.append(io_param) + self._params_box.pack_start(io_param, False) + self._params_box.show_all() + #update the gui inputs + for io_param in self._input_object_params: io_param.update() #update the errors box if self.block.is_valid(): self._error_box.hide() else: self._error_box.show() @@ -112,23 +151,6 @@ class PropsDialog(gtk.Dialog): if keyname == 'Return': self.response(gtk.RESPONSE_OK) return False #forward the keypress - def _handle_changed(self, param): - """ - A change occured, update any dependent parameters: - The enum inside the variable type may have changed and, - the variable param will need an external update. - @param param the graphical parameter that initiated the callback - """ - #update dependent params - if param.is_enum(): - for other_param in param.get_parent().get_params(): - if param.get_key() is not other_param.get_key() and ( - param.get_key() in other_param._type or \ - param.get_key() in other_param._hide): other_param.update() - #update - self._update() - return True - def run(self): """ Call run(). -- cgit From a6cb9eceeb62593e852b6dea0f640436381ec947 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sat, 5 Sep 2009 23:49:34 -0700 Subject: more code cleanup for properties dialog --- grc/gui/Param.py | 15 --------------- grc/gui/PropsDialog.py | 2 +- 2 files changed, 1 insertion(+), 16 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Param.py b/grc/gui/Param.py index 3c5e99e9e..9cd31b8a4 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -156,21 +156,6 @@ class Param(Element): if self.get_options(): return EnumEntryParam return EntryParam - def update(self): - """ - Called when an external change occurs. - Update the graphical input by calling the change handler. - """ - if hasattr(self, '_input'): self._handle_changed() - - def get_input_object(self, callback=None): - """ - Get the graphical gtk object to represent this parameter. - @param callback a function to be called from the input object. - @return gtk input object - """ - return self.get_input_class()(self, callback=callback) - def get_layout(self): """ Create a layout based on the current markup. diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index bd66b1178..9be0400fe 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -125,7 +125,7 @@ class PropsDialog(gtk.Dialog): #repopulate the params box for param in self.block.get_params(): if param.get_hide() == 'all': continue - io_param = param.get_input_object(self._update) + io_param = param.get_input_class()(param, callback=self._update) self._input_object_params.append(io_param) self._params_box.pack_start(io_param, False) self._params_box.show_all() -- cgit From e39507bf32666f9b17d2249106aac0d6cbcacc58 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sun, 6 Sep 2009 01:17:35 -0700 Subject: propsdialog tweaks --- grc/gui/Param.py | 13 ++++++++----- grc/gui/PropsDialog.py | 39 ++++++++++++++++++--------------------- 2 files changed, 26 insertions(+), 26 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Param.py b/grc/gui/Param.py index 9cd31b8a4..b84598e61 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -39,7 +39,7 @@ class InputParam(gtk.HBox): def update(self): """ - Set the markup, color, and tooltip. + Set the markup, color, tooltip, show/hide. """ #set the markup has_cb = \ @@ -53,6 +53,9 @@ class InputParam(gtk.HBox): self.entry, Utils.parse_template(TIP_MARKUP_TMPL, param=self.param).strip(), ) + #show/hide + if self.param.get_hide() == 'all': self.hide_all() + else: self.show_all() def _handle_changed(self, *args): """ @@ -144,7 +147,7 @@ class Param(Element): def __init__(self): Element.__init__(self) - def get_input_class(self): + def get_input(self, *args, **kwargs): """ Get the graphical gtk class to represent this parameter. An enum requires and combo parameter. @@ -152,9 +155,9 @@ class Param(Element): All others get a standard entry parameter. @return gtk input class """ - if self.is_enum(): return EnumParam - if self.get_options(): return EnumEntryParam - return EntryParam + if self.is_enum(): return EnumParam(*args, **kwargs) + if self.get_options(): return EnumEntryParam(*args, **kwargs) + return EntryParam(*args, **kwargs) def get_layout(self): """ diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index 9be0400fe..aa86f7214 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -47,13 +47,13 @@ class PropsDialog(gtk.Dialog): Properties dialog contructor. @param block a block instance """ - self._hash = '' + self._hash = 0 LABEL_SPACING = 7 gtk.Dialog.__init__(self, title='Properties: %s'%block.get_name(), buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE), ) - self.block = block + self._block = block self.set_size_request(MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT) vbox = gtk.VBox() #Create the scrolled window to hold all the parameters @@ -90,17 +90,15 @@ class PropsDialog(gtk.Dialog): def _params_changed(self): """ Have the params in this dialog changed? - Ex: Added, removed, type change, hidden, shown? + Ex: Added, removed, type change... Make a hash that uniquely represents the params state. @return true if changed """ old_hash = self._hash - str_accum = '' - for param in self.block.get_params(): - str_accum += param.get_key() - str_accum += param.get_type() - str_accum += param.get_hide() - self._hash = hash(str_accum) + self._hash = 0 + for param in self._block.get_params(): + self._hash ^= hash(param) + self._hash ^= hash(param.get_type()) return self._hash != old_hash def _update(self): @@ -113,33 +111,32 @@ class PropsDialog(gtk.Dialog): Hide the box if there are no docs. """ #update for the block - self.block.rewrite() - self.block.validate() + self._block.rewrite() + self._block.validate() #update the params box if self._params_changed(): #empty the params box for io_param in list(self._input_object_params): + io_param.hide_all() self._params_box.remove(io_param) self._input_object_params.remove(io_param) io_param.destroy() #repopulate the params box - for param in self.block.get_params(): - if param.get_hide() == 'all': continue - io_param = param.get_input_class()(param, callback=self._update) + for param in self._block.get_params(): + io_param = param.get_input(param, callback=self._update) self._input_object_params.append(io_param) self._params_box.pack_start(io_param, False) - self._params_box.show_all() #update the gui inputs for io_param in self._input_object_params: io_param.update() #update the errors box - if self.block.is_valid(): self._error_box.hide() + if self._block.is_valid(): self._error_box.hide() else: self._error_box.show() - messages = '\n\n'.join(self.block.get_error_messages()) + messages = '\n\n'.join(self._block.get_error_messages()) self._error_messages_text_display.set_text(messages) #update the docs box - if self.block.get_doc(): self._docs_box.show() + if self._block.get_doc(): self._docs_box.show() else: self._docs_box.hide() - self._docs_text_display.set_text(self.block.get_doc()) + self._docs_text_display.set_text(self._block.get_doc()) def _handle_key_press(self, widget, event): """ @@ -157,11 +154,11 @@ class PropsDialog(gtk.Dialog): @return true if a change occured. """ original_data = list() - for param in self.block.get_params(): + for param in self._block.get_params(): original_data.append(param.get_value()) gtk.Dialog.run(self) self.destroy() new_data = list() - for param in self.block.get_params(): + for param in self._block.get_params(): new_data.append(param.get_value()) return original_data != new_data -- cgit From 6b1d8817a7fc6dd99a770cb11fac7ca48a3c81b0 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sun, 6 Sep 2009 01:58:25 -0700 Subject: Fixed the usrp and usrp2 probe scripts to work with the new gui param api. Also fixed the scripts to work since they were broken by previous changes. Get input in param class now pases a param instance (self) into the object. --- grc/gui/Param.py | 14 +++++++++----- grc/gui/PropsDialog.py | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Param.py b/grc/gui/Param.py index b84598e61..7fabb6671 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -30,7 +30,7 @@ class InputParam(gtk.HBox): gtk.HBox.__init__(self) self.param = param self._callback = callback - self.label = gtk.Label('') #no label, markup is added by set_markup + self.label = gtk.Label() #no label, markup is added by set_markup self.label.set_size_request(150, -1) self.pack_start(self.label, False) self.set_markup = lambda m: self.label.set_markup(m) @@ -66,7 +66,11 @@ class InputParam(gtk.HBox): self.param.set_value(self.get_text()) #call the callback if self._callback: self._callback() - #self.update() #dont update here, parent will update + else: + #no callback mode (used in supporting gui scripts) + #internally re-validate the param and update the gui + self.param.validate() + self.update() class EntryParam(InputParam): """Provide an entry box for strings and numbers.""" @@ -155,9 +159,9 @@ class Param(Element): All others get a standard entry parameter. @return gtk input class """ - if self.is_enum(): return EnumParam(*args, **kwargs) - if self.get_options(): return EnumEntryParam(*args, **kwargs) - return EntryParam(*args, **kwargs) + if self.is_enum(): return EnumParam(self, *args, **kwargs) + if self.get_options(): return EnumEntryParam(self, *args, **kwargs) + return EntryParam(self, *args, **kwargs) def get_layout(self): """ diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index aa86f7214..34fd7ec17 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -123,7 +123,7 @@ class PropsDialog(gtk.Dialog): io_param.destroy() #repopulate the params box for param in self._block.get_params(): - io_param = param.get_input(param, callback=self._update) + io_param = param.get_input(self._update) self._input_object_params.append(io_param) self._params_box.pack_start(io_param, False) #update the gui inputs -- cgit From b8f69ad7ba49aa85239f6de611ddfd040344f66b Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Tue, 8 Sep 2009 23:04:38 -0700 Subject: use show signal to perform initial gui update --- grc/gui/Param.py | 14 +++++++------- grc/gui/PropsDialog.py | 34 ++++++++++++++++++++++------------ 2 files changed, 29 insertions(+), 19 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Param.py b/grc/gui/Param.py index 7fabb6671..4464a57ab 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -35,9 +35,11 @@ class InputParam(gtk.HBox): self.pack_start(self.label, False) self.set_markup = lambda m: self.label.set_markup(m) self.tp = None + #connect events + self.connect('show', self._update_gui) def set_color(self, color): pass - def update(self): + def _update_gui(self, *args): """ Set the markup, color, tooltip, show/hide. """ @@ -65,12 +67,10 @@ class InputParam(gtk.HBox): #set the new value self.param.set_value(self.get_text()) #call the callback - if self._callback: self._callback() - else: - #no callback mode (used in supporting gui scripts) - #internally re-validate the param and update the gui - self.param.validate() - self.update() + if self._callback: self._callback(*args) + else: self.param.validate() + #gui update + self._update_gui() class EntryParam(InputParam): """Provide an entry box for strings and numbers.""" diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index 34fd7ec17..496500416 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -81,16 +81,16 @@ class PropsDialog(gtk.Dialog): vbox.pack_start(self._params_box, False) vbox.pack_start(self._error_box, False) vbox.pack_start(self._docs_box, False) - #connect key press event + #connect events self.connect('key_press_event', self._handle_key_press) - #initial update to populate the params + self.connect('show', self._update_gui) + #show all (performs initial gui update) self.show_all() - self._update() def _params_changed(self): """ Have the params in this dialog changed? - Ex: Added, removed, type change... + Ex: Added, removed, type change, hide change... Make a hash that uniquely represents the params state. @return true if changed """ @@ -99,9 +99,20 @@ class PropsDialog(gtk.Dialog): for param in self._block.get_params(): self._hash ^= hash(param) self._hash ^= hash(param.get_type()) + self._hash ^= hash(param.get_hide()) return self._hash != old_hash - def _update(self): + def _handle_changed(self, *args): + """ + A change occured within a param: + Rewrite/validate the block and update the gui. + """ + #update for the block + self._block.rewrite() + self._block.validate() + self._update_gui() + + def _update_gui(self, *args): """ Repopulate the parameters box (if changed). Update all the input parameters. @@ -110,24 +121,23 @@ class PropsDialog(gtk.Dialog): Update the documentation block. Hide the box if there are no docs. """ - #update for the block - self._block.rewrite() - self._block.validate() #update the params box if self._params_changed(): + #hide params box before changing + self._params_box.hide_all() #empty the params box for io_param in list(self._input_object_params): - io_param.hide_all() self._params_box.remove(io_param) self._input_object_params.remove(io_param) io_param.destroy() #repopulate the params box for param in self._block.get_params(): - io_param = param.get_input(self._update) + if param.get_hide() == 'all': continue + io_param = param.get_input(self._handle_changed) self._input_object_params.append(io_param) self._params_box.pack_start(io_param, False) - #update the gui inputs - for io_param in self._input_object_params: io_param.update() + #show params box with new params + self._params_box.show_all() #update the errors box if self._block.is_valid(): self._error_box.hide() else: self._error_box.show() -- cgit From bced51e1fe3694e073bebf053b2c69bc5128e00b Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 9 Sep 2009 21:35:58 -0700 Subject: properties dialog with ok/cancel buttons --- grc/gui/ActionHandler.py | 15 +++++++++++---- grc/gui/PropsDialog.py | 18 ++++++------------ 2 files changed, 17 insertions(+), 16 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index f12893579..656f99c37 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -240,10 +240,17 @@ class ActionHandler: ################################################## elif state == Actions.BLOCK_PARAM_MODIFY: selected_block = self.get_flow_graph().get_selected_block() - if selected_block and PropsDialog(selected_block).run(): - self.get_flow_graph().update() - self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) - self.get_page().set_saved(False) + if selected_block: + if PropsDialog(selected_block).run(): + #save the new state + self.get_flow_graph().update() + self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + self.get_page().set_saved(False) + else: + #restore the current state + n = self.get_page().get_state_cache().get_current_state() + self.get_flow_graph().import_data(n) + self.get_flow_graph().update() ################################################## # Undo/Redo ################################################## diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index 496500416..29b3c7b40 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -51,7 +51,7 @@ class PropsDialog(gtk.Dialog): LABEL_SPACING = 7 gtk.Dialog.__init__(self, title='Properties: %s'%block.get_name(), - buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE), + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT), ) self._block = block self.set_size_request(MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT) @@ -155,20 +155,14 @@ class PropsDialog(gtk.Dialog): @return false to forward the keypress """ keyname = gtk.gdk.keyval_name(event.keyval) - if keyname == 'Return': self.response(gtk.RESPONSE_OK) + if keyname == 'Return': self.response(gtk.RESPONSE_ACCEPT) return False #forward the keypress def run(self): """ - Call run(). - @return true if a change occured. + Run the dialog and get its response. + @return true if the response was accept """ - original_data = list() - for param in self._block.get_params(): - original_data.append(param.get_value()) - gtk.Dialog.run(self) + response = gtk.Dialog.run(self) self.destroy() - new_data = list() - for param in self._block.get_params(): - new_data.append(param.get_value()) - return original_data != new_data + return response == gtk.RESPONSE_ACCEPT -- cgit From 417bb0aacf320043994ac1a0a5f49b977d1a9d22 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Thu, 10 Sep 2009 14:47:53 -0700 Subject: ignore irrelevant modifiers and events pending --- grc/gui/ActionHandler.py | 2 -- grc/gui/Actions.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 656f99c37..970b731c8 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -79,8 +79,6 @@ class ActionHandler: When not in focus, gtk and the accelerators handle the the key press. @return false to let gtk handle the key action """ - #dont allow key presses to queue up - if gtk.events_pending(): return True #extract action name from this key press key_name = gtk.gdk.keyval_name(event.keyval) mod_mask = event.state diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index c3ef2711a..1d6a2afba 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -97,6 +97,7 @@ _actions_key_list = ( ) _actions_key_dict = dict(((key_name, mod_mask), action_name) for action_name, key_name, mod_mask in _actions_key_list) +_all_mods_mask = reduce(lambda x, y: x | y, [mod_mask for action_name, key_name, mod_mask in _actions_key_list], 0) def get_action_name_from_key_name(key_name, mod_mask=0): """ Get the action name associated with the key name and mask. @@ -105,6 +106,7 @@ def get_action_name_from_key_name(key_name, mod_mask=0): @param mod_mask the key press mask (shift, ctrl) 0 for none @return the action name or blank string """ + mod_mask &= _all_mods_mask #ignore irrelevant modifiers key_name_mod_mask = (key_name, mod_mask) if key_name_mod_mask in _actions_key_dict: return _actions_key_dict[key_name_mod_mask] return '' -- cgit From ab55c4a6e5f9a21ea743b78ea5a8206cf3d4ffe6 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Thu, 10 Sep 2009 16:17:25 -0700 Subject: use the keymap's translate_keyboard_state, use the key value rather than name --- grc/gui/ActionHandler.py | 7 ++-- grc/gui/Actions.py | 83 ++++++++++++++++++++++++------------------------ 2 files changed, 46 insertions(+), 44 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 970b731c8..0d39ea5fd 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -79,10 +79,11 @@ class ActionHandler: When not in focus, gtk and the accelerators handle the the key press. @return false to let gtk handle the key action """ + keyval, egroup, level, consumed = \ + gtk.gdk.keymap_get_default().translate_keyboard_state( + event.hardware_keycode, event.state, event.group) #extract action name from this key press - key_name = gtk.gdk.keyval_name(event.keyval) - mod_mask = event.state - action_name = Actions.get_action_name_from_key_name(key_name, mod_mask) + action_name = Actions.get_action_name_from_key_press(keyval, event.state & ~consumed) #handle the action if flow graph is in focus if action_name and self.get_focus_flag(): self.handle_states(action_name) diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index 1d6a2afba..d3d63f293 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -62,54 +62,55 @@ TYPES_WINDOW_DISPLAY = 'types window display' ###################################################################################################### # Action Key Map ###################################################################################################### +NO_MODS_MASK = 0 _actions_key_list = ( - #action name, key name, mod mask - (FLOW_GRAPH_NEW, 'n', gtk.gdk.CONTROL_MASK), - (FLOW_GRAPH_OPEN, 'o', gtk.gdk.CONTROL_MASK), - (FLOW_GRAPH_SAVE, 's', gtk.gdk.CONTROL_MASK), - (FLOW_GRAPH_SAVE_AS, 's', gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK), - (FLOW_GRAPH_CLOSE, 'w', gtk.gdk.CONTROL_MASK), - (APPLICATION_QUIT, 'q', gtk.gdk.CONTROL_MASK), - (FLOW_GRAPH_UNDO, 'z', gtk.gdk.CONTROL_MASK), - (FLOW_GRAPH_REDO, 'y', gtk.gdk.CONTROL_MASK), - (ELEMENT_DELETE, 'Delete', 0), - (BLOCK_ROTATE_CCW, 'Left', 0), - (BLOCK_ROTATE_CW, 'Right', 0), - (BLOCK_DEC_TYPE, 'Up', 0), - (BLOCK_INC_TYPE, 'Down', 0), - (BLOCK_PARAM_MODIFY, 'Return', 0), - (BLOCK_ENABLE, 'e', 0), - (BLOCK_DISABLE, 'd', 0), - (BLOCK_CUT, 'x', gtk.gdk.CONTROL_MASK), - (BLOCK_COPY, 'c', gtk.gdk.CONTROL_MASK), - (BLOCK_PASTE, 'v', gtk.gdk.CONTROL_MASK), - (FLOW_GRAPH_GEN, 'F5', 0), - (FLOW_GRAPH_EXEC, 'F6', 0), - (FLOW_GRAPH_KILL, 'F7', 0), - (FLOW_GRAPH_SCREEN_CAPTURE, 'Print', 0), - (HELP_WINDOW_DISPLAY, 'F1', 0), + #action name, key value, mod mask + (FLOW_GRAPH_NEW, gtk.keysyms.n, gtk.gdk.CONTROL_MASK), + (FLOW_GRAPH_OPEN, gtk.keysyms.o, gtk.gdk.CONTROL_MASK), + (FLOW_GRAPH_SAVE, gtk.keysyms.s, gtk.gdk.CONTROL_MASK), + (FLOW_GRAPH_SAVE_AS, gtk.keysyms.s, gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK), + (FLOW_GRAPH_CLOSE, gtk.keysyms.w, gtk.gdk.CONTROL_MASK), + (APPLICATION_QUIT, gtk.keysyms.q, gtk.gdk.CONTROL_MASK), + (FLOW_GRAPH_UNDO, gtk.keysyms.z, gtk.gdk.CONTROL_MASK), + (FLOW_GRAPH_REDO, gtk.keysyms.y, gtk.gdk.CONTROL_MASK), + (ELEMENT_DELETE, gtk.keysyms.Delete, NO_MODS_MASK), + (BLOCK_ROTATE_CCW, gtk.keysyms.Left, NO_MODS_MASK), + (BLOCK_ROTATE_CW, gtk.keysyms.Right, NO_MODS_MASK), + (BLOCK_DEC_TYPE, gtk.keysyms.Up, NO_MODS_MASK), + (BLOCK_INC_TYPE, gtk.keysyms.Down, NO_MODS_MASK), + (BLOCK_PARAM_MODIFY, gtk.keysyms.Return, NO_MODS_MASK), + (BLOCK_ENABLE, gtk.keysyms.e, NO_MODS_MASK), + (BLOCK_DISABLE, gtk.keysyms.d, NO_MODS_MASK), + (BLOCK_CUT, gtk.keysyms.x, gtk.gdk.CONTROL_MASK), + (BLOCK_COPY, gtk.keysyms.c, gtk.gdk.CONTROL_MASK), + (BLOCK_PASTE, gtk.keysyms.v, gtk.gdk.CONTROL_MASK), + (FLOW_GRAPH_GEN, gtk.keysyms.F5, NO_MODS_MASK), + (FLOW_GRAPH_EXEC, gtk.keysyms.F6, NO_MODS_MASK), + (FLOW_GRAPH_KILL, gtk.keysyms.F7, NO_MODS_MASK), + (FLOW_GRAPH_SCREEN_CAPTURE, gtk.keysyms.Print, NO_MODS_MASK), + (HELP_WINDOW_DISPLAY, gtk.keysyms.F1, NO_MODS_MASK), #the following have no associated gtk.Action - (PORT_CONTROLLER_INC, 'equal', 0), - (PORT_CONTROLLER_INC, 'plus', 0), - (PORT_CONTROLLER_INC, 'KP_Add', 0), - (PORT_CONTROLLER_DEC, 'minus', 0), - (PORT_CONTROLLER_DEC, 'KP_Subtract', 0), + (PORT_CONTROLLER_INC, gtk.keysyms.equal, NO_MODS_MASK), + (PORT_CONTROLLER_INC, gtk.keysyms.plus, NO_MODS_MASK), + (PORT_CONTROLLER_INC, gtk.keysyms.KP_Add, NO_MODS_MASK), + (PORT_CONTROLLER_DEC, gtk.keysyms.minus, NO_MODS_MASK), + (PORT_CONTROLLER_DEC, gtk.keysyms.KP_Subtract, NO_MODS_MASK), ) -_actions_key_dict = dict(((key_name, mod_mask), action_name) for action_name, key_name, mod_mask in _actions_key_list) -_all_mods_mask = reduce(lambda x, y: x | y, [mod_mask for action_name, key_name, mod_mask in _actions_key_list], 0) -def get_action_name_from_key_name(key_name, mod_mask=0): +_actions_key_dict = dict(((key_val, mod_mask), action_name) for action_name, key_val, mod_mask in _actions_key_list) +_all_mods_mask = reduce(lambda x, y: x | y, [mod_mask for action_name, key_val, mod_mask in _actions_key_list], NO_MODS_MASK) +def get_action_name_from_key_press(key_val, mod_mask=NO_MODS_MASK): """ - Get the action name associated with the key name and mask. - Both keyname and mask have to match. - @param key_name the name of the key + Get the action name associated with the key value and mask. + Both the key value and the mask have to match. + @param key_val the value of the key @param mod_mask the key press mask (shift, ctrl) 0 for none @return the action name or blank string """ mod_mask &= _all_mods_mask #ignore irrelevant modifiers - key_name_mod_mask = (key_name, mod_mask) - if key_name_mod_mask in _actions_key_dict: return _actions_key_dict[key_name_mod_mask] - return '' + key_val_mod_mask = (key_val, mod_mask) + try: return _actions_key_dict[key_val_mod_mask] + except KeyError: return '' ###################################################################################################### # Actions @@ -162,10 +163,10 @@ def get_accel_group(): return _accel_group #set the accelerator group, and accelerator path #register the key name and mod mask with the accelerator path -for action_name, key_name, mod_mask in _actions_key_list: +for action_name, key_val, mod_mask in _actions_key_list: try: accel_path = '
/'+action_name get_action_from_name(action_name).set_accel_group(get_accel_group()) get_action_from_name(action_name).set_accel_path(accel_path) - gtk.accel_map_add_entry(accel_path, gtk.gdk.keyval_from_name(key_name), mod_mask) + gtk.accel_map_add_entry(accel_path, key_val, mod_mask) except KeyError: pass #no action was created for this action name -- cgit From 14ae3c5141e3d39b89a4aac19681ec8ac7cd05ee Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Thu, 10 Sep 2009 17:38:53 -0700 Subject: rename variable, use keysyms in props dialog --- grc/gui/Actions.py | 4 ++-- grc/gui/PropsDialog.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index d3d63f293..cacee22ce 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -98,7 +98,7 @@ _actions_key_list = ( ) _actions_key_dict = dict(((key_val, mod_mask), action_name) for action_name, key_val, mod_mask in _actions_key_list) -_all_mods_mask = reduce(lambda x, y: x | y, [mod_mask for action_name, key_val, mod_mask in _actions_key_list], NO_MODS_MASK) +_used_mods_mask = reduce(lambda x, y: x | y, [mod_mask for action_name, key_val, mod_mask in _actions_key_list], NO_MODS_MASK) def get_action_name_from_key_press(key_val, mod_mask=NO_MODS_MASK): """ Get the action name associated with the key value and mask. @@ -107,7 +107,7 @@ def get_action_name_from_key_press(key_val, mod_mask=NO_MODS_MASK): @param mod_mask the key press mask (shift, ctrl) 0 for none @return the action name or blank string """ - mod_mask &= _all_mods_mask #ignore irrelevant modifiers + mod_mask &= _used_mods_mask #ignore irrelevant modifiers key_val_mod_mask = (key_val, mod_mask) try: return _actions_key_dict[key_val_mod_mask] except KeyError: return '' diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index 29b3c7b40..b295ba412 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -154,8 +154,9 @@ class PropsDialog(gtk.Dialog): Call the ok response when enter is pressed. @return false to forward the keypress """ - keyname = gtk.gdk.keyval_name(event.keyval) - if keyname == 'Return': self.response(gtk.RESPONSE_ACCEPT) + if event.keyval == gtk.keysyms.Return: + self.response(gtk.RESPONSE_ACCEPT) + return True #handled here return False #forward the keypress def run(self): -- cgit From 9dd47b6732c9d6a94fc0e15717d7332fc6c8270f Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sun, 13 Sep 2009 00:04:58 -0700 Subject: Move key press extraction logic into actions module. Replaced xml encode with gtk escape text. Added templates for category and docs tool tips. Other various code tweaks in gui modules. --- grc/gui/ActionHandler.py | 20 ++++++++------------ grc/gui/Actions.py | 35 +++++++++++++++++++---------------- grc/gui/BlockTreeWindow.py | 13 +++++++++++-- grc/gui/Utils.py | 20 ++------------------ 4 files changed, 40 insertions(+), 48 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 0d39ea5fd..901619e69 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -79,16 +79,11 @@ class ActionHandler: When not in focus, gtk and the accelerators handle the the key press. @return false to let gtk handle the key action """ - keyval, egroup, level, consumed = \ - gtk.gdk.keymap_get_default().translate_keyboard_state( - event.hardware_keycode, event.state, event.group) - #extract action name from this key press - action_name = Actions.get_action_name_from_key_press(keyval, event.state & ~consumed) - #handle the action if flow graph is in focus - if action_name and self.get_focus_flag(): - self.handle_states(action_name) - return True #handled by this method - return False #let gtk handle the key press + try: assert self.get_focus_flag() + except AssertionError: return False + try: self.handle_states(Actions.get_action_name_from_key_press(event)) + except KeyError: return False + return True #handled by this method def _quit(self, window, event): """ @@ -100,13 +95,14 @@ class ActionHandler: self.handle_states(Actions.APPLICATION_QUIT) return True - def _handle_actions(self, event): + def _handle_actions(self, action): """ Handle all of the activate signals from the gtk actions. The action signals derive from clicking on a toolbar or menu bar button. Forward the action to the state handler. """ - self.handle_states(event.get_name()) + self.handle_states(action.get_name()) + return True def handle_states(self, state=''): """ diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index cacee22ce..531888ac1 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -97,20 +97,23 @@ _actions_key_list = ( (PORT_CONTROLLER_DEC, gtk.keysyms.KP_Subtract, NO_MODS_MASK), ) -_actions_key_dict = dict(((key_val, mod_mask), action_name) for action_name, key_val, mod_mask in _actions_key_list) -_used_mods_mask = reduce(lambda x, y: x | y, [mod_mask for action_name, key_val, mod_mask in _actions_key_list], NO_MODS_MASK) -def get_action_name_from_key_press(key_val, mod_mask=NO_MODS_MASK): +_actions_key_dict = dict(((keyval, mod_mask), action_name) for action_name, keyval, mod_mask in _actions_key_list) +_used_mods_mask = reduce(lambda x, y: x | y, [mod_mask for action_name, keyval, mod_mask in _actions_key_list], NO_MODS_MASK) +_keymap = gtk.gdk.keymap_get_default() +def get_action_name_from_key_press(event): """ - Get the action name associated with the key value and mask. - Both the key value and the mask have to match. - @param key_val the value of the key - @param mod_mask the key press mask (shift, ctrl) 0 for none + Get the action name associated with the key press event. + Both the key value and the mask must have a match. + @param event a gtk key press event @return the action name or blank string """ - mod_mask &= _used_mods_mask #ignore irrelevant modifiers - key_val_mod_mask = (key_val, mod_mask) - try: return _actions_key_dict[key_val_mod_mask] - except KeyError: return '' + #extract the key value and the consumed modifiers + keyval, egroup, level, consumed = _keymap.translate_keyboard_state( + event.hardware_keycode, event.state, event.group) + #get the modifier mask and ignore irrelevant modifiers + mod_mask = event.state & ~consumed & _used_mods_mask + try: return _actions_key_dict[(keyval, mod_mask)] + except KeyError: raise KeyError, 'Keypress: "%s, %s" does not have an associated action'%(gtk.gdk.keyval_name(keyval), mod_mask) ###################################################################################################### # Actions @@ -143,7 +146,7 @@ _actions_list = ( ) def get_all_actions(): return _actions_list -_actions_dict = dict((action.get_name(), action) for action in _actions_list) +_actions_dict = dict((action.get_name(), action) for action in get_all_actions()) def get_action_from_name(action_name): """ Retrieve the action from the action list. @@ -152,8 +155,8 @@ def get_action_from_name(action_name): @throw KeyError bad action name @return a gtk action object """ - if action_name in _actions_dict: return _actions_dict[action_name] - raise KeyError('Action Name: "%s" does not exist'%action_name) + try: return _actions_dict[action_name] + except KeyError: raise KeyError, 'Action Name: "%s" does not exist'%action_name ###################################################################################################### # Accelerators @@ -163,10 +166,10 @@ def get_accel_group(): return _accel_group #set the accelerator group, and accelerator path #register the key name and mod mask with the accelerator path -for action_name, key_val, mod_mask in _actions_key_list: +for action_name, keyval, mod_mask in _actions_key_list: try: accel_path = '
/'+action_name get_action_from_name(action_name).set_accel_group(get_accel_group()) get_action_from_name(action_name).set_accel_path(accel_path) - gtk.accel_map_add_entry(accel_path, key_val, mod_mask) + gtk.accel_map_add_entry(accel_path, keyval, mod_mask) except KeyError: pass #no action was created for this action name diff --git a/grc/gui/BlockTreeWindow.py b/grc/gui/BlockTreeWindow.py index 379c4a6a2..5c5d21b64 100644 --- a/grc/gui/BlockTreeWindow.py +++ b/grc/gui/BlockTreeWindow.py @@ -28,6 +28,15 @@ NAME_INDEX = 0 KEY_INDEX = 1 DOC_INDEX = 2 +DOC_MARKUP_TMPL="""\ +#if $doc +$encode($doc)#slurp +#else +undocumented#slurp +#end if""" + +CAT_MARKUP_TMPL="""Category: $cat""" + class BlockTreeWindow(gtk.VBox): """The block selection panel.""" @@ -97,14 +106,14 @@ class BlockTreeWindow(gtk.VBox): iter = self.treestore.insert_before(self._categories[sub_category[:-1]], None) self.treestore.set_value(iter, NAME_INDEX, '[ %s ]'%cat_name) self.treestore.set_value(iter, KEY_INDEX, '') - self.treestore.set_value(iter, DOC_INDEX, Utils.xml_encode('Category: %s'%cat_name)) + self.treestore.set_value(iter, DOC_INDEX, Utils.parse_template(CAT_MARKUP_TMPL, cat=cat_name)) self._categories[sub_category] = iter #add block if block is None: return iter = self.treestore.insert_before(self._categories[category], None) self.treestore.set_value(iter, NAME_INDEX, block.get_name()) self.treestore.set_value(iter, KEY_INDEX, block.get_key()) - self.treestore.set_value(iter, DOC_INDEX, Utils.xml_encode(block.get_doc() or 'undocumented')) + self.treestore.set_value(iter, DOC_INDEX, Utils.parse_template(DOC_MARKUP_TMPL, doc=block.get_doc())) ############################################################ ## Helper Methods diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py index ee6dc6cdc..83036a4b8 100644 --- a/grc/gui/Utils.py +++ b/grc/gui/Utils.py @@ -19,6 +19,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from Constants import POSSIBLE_ROTATIONS from Cheetah.Template import Template +import gobject def get_rotated_coordinate(coor, rotation): """ @@ -54,23 +55,6 @@ def get_angle_from_coordinates((x1,y1), (x2,y2)): if y2 > y1: return 270 else: return 90 -def xml_encode(string): - """ - Encode a string into an xml safe string by replacing special characters. - Needed for gtk pango markup in labels. - @param string the input string - @return output string with safe characters - """ - string = str(string) - for char, safe in ( - ('&', '&'), - ('<', '<'), - ('>', '>'), - ('"', '"'), - ("'", '''), - ): string = string.replace(char, safe) - return string - def parse_template(tmpl_str, **kwargs): """ Parse the template string with the given args. @@ -78,5 +62,5 @@ def parse_template(tmpl_str, **kwargs): @param tmpl_str the template as a string @return a string of the parsed template """ - kwargs['encode'] = xml_encode + kwargs['encode'] = gobject.markup_escape_text return str(Template(tmpl_str, kwargs)) -- cgit From 91a83e6f1fda6483bfd4b449a1ef7903a00af0ab Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sun, 13 Sep 2009 02:14:27 -0700 Subject: Reworked actions api and actions objects: Created standardized Action object for all gui actions. Actions module constants are actual Action objects (not strings). Keypresses, labels, tooltips, stock icons, etc all associate in the Action constructor. Usage of the action's signaling call () eliminated the need for a reference to handle_states. --- grc/gui/ActionHandler.py | 151 +++++++++----------- grc/gui/Actions.py | 359 +++++++++++++++++++++++++++++------------------ grc/gui/Bars.py | 12 +- grc/gui/FlowGraph.py | 12 +- grc/gui/MainWindow.py | 20 +-- grc/gui/NotebookPage.py | 5 +- grc/gui/StateCache.py | 6 +- 7 files changed, 319 insertions(+), 246 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 901619e69..59e535bd4 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -52,10 +52,10 @@ class ActionHandler: @param platform platform module """ self.clipboard = None - for action in Actions.get_all_actions(): action.connect('activate', self._handle_actions) + for action in Actions.get_all_actions(): action.connect('activate', self._handle_action) #setup the main window - self.main_window = MainWindow(self.handle_states, platform) - self.main_window.connect('delete_event', self._quit) + self.main_window = MainWindow(platform) + self.main_window.connect('delete-event', self._quit) self.main_window.connect('key-press-event', self._handle_key_press) self.get_page = self.main_window.get_page self.get_flow_graph = self.main_window.get_flow_graph @@ -65,7 +65,7 @@ class ActionHandler: Messages.send_init(platform) #initialize self.init_file_paths = file_paths - self.handle_states(Actions.APPLICATION_INITIALIZE) + Actions.APPLICATION_INITIALIZE() #enter the mainloop gtk.main() @@ -81,7 +81,7 @@ class ActionHandler: """ try: assert self.get_focus_flag() except AssertionError: return False - try: self.handle_states(Actions.get_action_name_from_key_press(event)) + try: Actions.get_action_from_key_press(event)() except KeyError: return False return True #handled by this method @@ -92,42 +92,24 @@ class ActionHandler: This method in turns calls the state handler to quit. @return true """ - self.handle_states(Actions.APPLICATION_QUIT) + Actions.APPLICATION_QUIT() return True - def _handle_actions(self, action): - """ - Handle all of the activate signals from the gtk actions. - The action signals derive from clicking on a toolbar or menu bar button. - Forward the action to the state handler. - """ - self.handle_states(action.get_name()) - return True - - def handle_states(self, state=''): - """ - Handle the state changes in the GUI. - Handle all of the state changes that arise from the action handler or other gui and - inputs in the application. The state passed to the handle_states method is a string descriping - the change. A series of if/elif statements handle the state by greying out action buttons, causing - changes in the flow graph, saving/opening files... The handle_states method is passed to the - contructors of many of the classes used in this application enabling them to report any state change. - @param state a string describing the state change - """ - #print state + def _handle_action(self, action): + #print action ################################################## # Initalize/Quit ################################################## - if state == Actions.APPLICATION_INITIALIZE: + if action == Actions.APPLICATION_INITIALIZE: for action in Actions.get_all_actions(): action.set_sensitive(False) #set all actions disabled - # enable a select few actions + #enable a select few actions for action in ( Actions.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW, Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS, Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY, Actions.FLOW_GRAPH_SCREEN_CAPTURE, Actions.HELP_WINDOW_DISPLAY, Actions.TYPES_WINDOW_DISPLAY, - ): Actions.get_action_from_name(action).set_sensitive(True) + ): action.set_sensitive(True) if not self.init_file_paths: self.init_file_paths = Preferences.files_open() if not self.init_file_paths: self.init_file_paths = [''] @@ -136,26 +118,26 @@ class ActionHandler: if Preferences.file_open() in self.init_file_paths: self.main_window.new_page(Preferences.file_open(), show=True) if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists - elif state == Actions.APPLICATION_QUIT: + elif action == Actions.APPLICATION_QUIT: if self.main_window.close_pages(): gtk.main_quit() exit(0) ################################################## # Selections ################################################## - elif state == Actions.ELEMENT_SELECT: + elif action == Actions.ELEMENT_SELECT: pass #do nothing, update routines below - elif state == Actions.NOTHING_SELECT: + elif action == Actions.NOTHING_SELECT: self.get_flow_graph().unselect() ################################################## # Enable/Disable ################################################## - elif state == Actions.BLOCK_ENABLE: + elif action == Actions.BLOCK_ENABLE: if self.get_flow_graph().enable_selected(True): self.get_flow_graph().update() self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) self.get_page().set_saved(False) - elif state == Actions.BLOCK_DISABLE: + elif action == Actions.BLOCK_DISABLE: if self.get_flow_graph().enable_selected(False): self.get_flow_graph().update() self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) @@ -163,12 +145,12 @@ class ActionHandler: ################################################## # Cut/Copy/Paste ################################################## - elif state == Actions.BLOCK_CUT: - self.handle_states(Actions.BLOCK_COPY) - self.handle_states(Actions.ELEMENT_DELETE) - elif state == Actions.BLOCK_COPY: + elif action == Actions.BLOCK_CUT: + Actions.BLOCK_COPY() + Actions.ELEMENT_DELETE() + elif action == Actions.BLOCK_COPY: self.clipboard = self.get_flow_graph().copy_to_clipboard() - elif state == Actions.BLOCK_PASTE: + elif action == Actions.BLOCK_PASTE: if self.clipboard: self.get_flow_graph().paste_from_clipboard(self.clipboard) self.get_flow_graph().update() @@ -177,46 +159,46 @@ class ActionHandler: ################################################## # Move/Rotate/Delete/Create ################################################## - elif state == Actions.BLOCK_MOVE: + elif action == Actions.BLOCK_MOVE: self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) self.get_page().set_saved(False) - elif state == Actions.BLOCK_ROTATE_CCW: + elif action == Actions.BLOCK_ROTATE_CCW: if self.get_flow_graph().rotate_selected(90): self.get_flow_graph().update() self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) self.get_page().set_saved(False) - elif state == Actions.BLOCK_ROTATE_CW: + elif action == Actions.BLOCK_ROTATE_CW: if self.get_flow_graph().rotate_selected(-90): self.get_flow_graph().update() self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) self.get_page().set_saved(False) - elif state == Actions.ELEMENT_DELETE: + elif action == Actions.ELEMENT_DELETE: if self.get_flow_graph().remove_selected(): self.get_flow_graph().update() self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) - self.handle_states(Actions.NOTHING_SELECT) + Actions.NOTHING_SELECT() self.get_page().set_saved(False) - elif state == Actions.ELEMENT_CREATE: + elif action == Actions.ELEMENT_CREATE: self.get_flow_graph().update() self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) - self.handle_states(Actions.NOTHING_SELECT) + Actions.NOTHING_SELECT() self.get_page().set_saved(False) - elif state == Actions.BLOCK_INC_TYPE: + elif action == Actions.BLOCK_INC_TYPE: if self.get_flow_graph().type_controller_modify_selected(1): self.get_flow_graph().update() self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) self.get_page().set_saved(False) - elif state == Actions.BLOCK_DEC_TYPE: + elif action == Actions.BLOCK_DEC_TYPE: if self.get_flow_graph().type_controller_modify_selected(-1): self.get_flow_graph().update() self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) self.get_page().set_saved(False) - elif state == Actions.PORT_CONTROLLER_INC: + elif action == Actions.PORT_CONTROLLER_INC: if self.get_flow_graph().port_controller_modify_selected(1): self.get_flow_graph().update() self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) self.get_page().set_saved(False) - elif state == Actions.PORT_CONTROLLER_DEC: + elif action == Actions.PORT_CONTROLLER_DEC: if self.get_flow_graph().port_controller_modify_selected(-1): self.get_flow_graph().update() self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) @@ -224,16 +206,16 @@ class ActionHandler: ################################################## # Window stuff ################################################## - elif state == Actions.ABOUT_WINDOW_DISPLAY: + elif action == Actions.ABOUT_WINDOW_DISPLAY: Dialogs.AboutDialog(self.get_flow_graph().get_parent()) - elif state == Actions.HELP_WINDOW_DISPLAY: + elif action == Actions.HELP_WINDOW_DISPLAY: Dialogs.HelpDialog() - elif state == Actions.TYPES_WINDOW_DISPLAY: + elif action == Actions.TYPES_WINDOW_DISPLAY: Dialogs.TypesDialog(self.get_flow_graph().get_parent()) ################################################## # Param Modifications ################################################## - elif state == Actions.BLOCK_PARAM_MODIFY: + elif action == Actions.BLOCK_PARAM_MODIFY: selected_block = self.get_flow_graph().get_selected_block() if selected_block: if PropsDialog(selected_block).run(): @@ -249,14 +231,14 @@ class ActionHandler: ################################################## # Undo/Redo ################################################## - elif state == Actions.FLOW_GRAPH_UNDO: + elif action == Actions.FLOW_GRAPH_UNDO: n = self.get_page().get_state_cache().get_prev_state() if n: self.get_flow_graph().unselect() self.get_flow_graph().import_data(n) self.get_flow_graph().update() self.get_page().set_saved(False) - elif state == Actions.FLOW_GRAPH_REDO: + elif action == Actions.FLOW_GRAPH_REDO: n = self.get_page().get_state_cache().get_next_state() if n: self.get_flow_graph().unselect() @@ -266,19 +248,19 @@ class ActionHandler: ################################################## # New/Open/Save/Close ################################################## - elif state == Actions.FLOW_GRAPH_NEW: + elif action == Actions.FLOW_GRAPH_NEW: self.main_window.new_page() - elif state == Actions.FLOW_GRAPH_OPEN: + elif action == Actions.FLOW_GRAPH_OPEN: file_paths = OpenFlowGraphFileDialog(self.get_page().get_file_path()).run() if file_paths: #open a new page for each file, show only the first for i,file_path in enumerate(file_paths): self.main_window.new_page(file_path, show=(i==0)) - elif state == Actions.FLOW_GRAPH_CLOSE: + elif action == Actions.FLOW_GRAPH_CLOSE: self.main_window.close_page() - elif state == Actions.FLOW_GRAPH_SAVE: + elif action == Actions.FLOW_GRAPH_SAVE: #read-only or undefined file path, do save-as if self.get_page().get_read_only() or not self.get_page().get_file_path(): - self.handle_states(Actions.FLOW_GRAPH_SAVE_AS) + Actions.FLOW_GRAPH_SAVE_AS() #otherwise try to save else: try: @@ -287,12 +269,12 @@ class ActionHandler: except IOError: Messages.send_fail_save(self.get_page().get_file_path()) self.get_page().set_saved(False) - elif state == Actions.FLOW_GRAPH_SAVE_AS: + elif action == Actions.FLOW_GRAPH_SAVE_AS: file_path = SaveFlowGraphFileDialog(self.get_page().get_file_path()).run() if file_path is not None: self.get_page().set_file_path(file_path) - self.handle_states(Actions.FLOW_GRAPH_SAVE) - elif state == Actions.FLOW_GRAPH_SCREEN_CAPTURE: + Actions.FLOW_GRAPH_SAVE() + elif action == Actions.FLOW_GRAPH_SCREEN_CAPTURE: file_path = SaveImageFileDialog(self.get_page().get_file_path()).run() if file_path is not None: pixbuf = self.get_flow_graph().get_drawing_area().get_pixbuf() @@ -300,10 +282,10 @@ class ActionHandler: ################################################## # Gen/Exec/Stop ################################################## - elif state == Actions.FLOW_GRAPH_GEN: + elif action == Actions.FLOW_GRAPH_GEN: if not self.get_page().get_pid(): if not self.get_page().get_saved() or not self.get_page().get_file_path(): - self.handle_states(Actions.FLOW_GRAPH_SAVE) #only save if file path missing or not saved + Actions.FLOW_GRAPH_SAVE() #only save if file path missing or not saved if self.get_page().get_saved() and self.get_page().get_file_path(): generator = self.get_page().get_generator() try: @@ -311,37 +293,37 @@ class ActionHandler: generator.write() except Exception,e: Messages.send_fail_gen(e) else: self.generator = None - elif state == Actions.FLOW_GRAPH_EXEC: + elif action == Actions.FLOW_GRAPH_EXEC: if not self.get_page().get_pid(): - self.handle_states(Actions.FLOW_GRAPH_GEN) + Actions.FLOW_GRAPH_GEN() if self.get_page().get_saved() and self.get_page().get_file_path(): ExecFlowGraphThread(self) - elif state == Actions.FLOW_GRAPH_KILL: + elif action == Actions.FLOW_GRAPH_KILL: if self.get_page().get_pid(): try: os.kill(self.get_page().get_pid(), signal.SIGKILL) except: print "could not kill pid: %s"%self.get_page().get_pid() - elif state == '': #pass and run the global actions + elif action == Actions.PAGE_CHANGE: #pass and run the global actions pass - else: print '!!! State "%s" not handled !!!'%state + else: print '!!! Action "%s" not handled !!!'%action ################################################## # Global Actions for all States ################################################## #update general buttons - Actions.get_action_from_name(Actions.ELEMENT_DELETE).set_sensitive(bool(self.get_flow_graph().get_selected_elements())) - Actions.get_action_from_name(Actions.BLOCK_PARAM_MODIFY).set_sensitive(bool(self.get_flow_graph().get_selected_block())) - Actions.get_action_from_name(Actions.BLOCK_ROTATE_CCW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) - Actions.get_action_from_name(Actions.BLOCK_ROTATE_CW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) + Actions.ELEMENT_DELETE.set_sensitive(bool(self.get_flow_graph().get_selected_elements())) + Actions.BLOCK_PARAM_MODIFY.set_sensitive(bool(self.get_flow_graph().get_selected_block())) + Actions.BLOCK_ROTATE_CCW.set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) + Actions.BLOCK_ROTATE_CW.set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) #update cut/copy/paste - Actions.get_action_from_name(Actions.BLOCK_CUT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) - Actions.get_action_from_name(Actions.BLOCK_COPY).set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) - Actions.get_action_from_name(Actions.BLOCK_PASTE).set_sensitive(bool(self.clipboard)) + Actions.BLOCK_CUT.set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) + Actions.BLOCK_COPY.set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) + Actions.BLOCK_PASTE.set_sensitive(bool(self.clipboard)) #update enable/disable - Actions.get_action_from_name(Actions.BLOCK_ENABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) - Actions.get_action_from_name(Actions.BLOCK_DISABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) + Actions.BLOCK_ENABLE.set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) + Actions.BLOCK_DISABLE.set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) #set the exec and stop buttons self.update_exec_stop() #saved status - Actions.get_action_from_name(Actions.FLOW_GRAPH_SAVE).set_sensitive(not self.get_page().get_saved()) + Actions.FLOW_GRAPH_SAVE.set_sensitive(not self.get_page().get_saved()) self.main_window.update() try: #set the size of the flow graph area (if changed) new_size = self.get_flow_graph().get_option('window_size') @@ -351,6 +333,7 @@ class ActionHandler: #draw the flow graph self.get_flow_graph().update_selected() self.get_flow_graph().queue_draw() + return True #action was handled def update_exec_stop(self): """ @@ -358,9 +341,9 @@ class ActionHandler: Lock and unlock the mutex for race conditions with exec flow graph threads. """ sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid() - Actions.get_action_from_name(Actions.FLOW_GRAPH_GEN).set_sensitive(sensitive) - Actions.get_action_from_name(Actions.FLOW_GRAPH_EXEC).set_sensitive(sensitive) - Actions.get_action_from_name(Actions.FLOW_GRAPH_KILL).set_sensitive(self.get_page().get_pid() != None) + Actions.FLOW_GRAPH_GEN.set_sensitive(sensitive) + Actions.FLOW_GRAPH_EXEC.set_sensitive(sensitive) + Actions.FLOW_GRAPH_KILL.set_sensitive(self.get_page().get_pid() != None) class ExecFlowGraphThread(Thread): """Execute the flow graph as a new process and wait on it to finish.""" diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index 531888ac1..90017987f 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -21,155 +21,248 @@ import pygtk pygtk.require('2.0') import gtk -###################################################################################################### -# Action Names -###################################################################################################### -APPLICATION_INITIALIZE = 'app init' -APPLICATION_QUIT = 'app quit' -PARAM_MODIFY = 'param modify' -BLOCK_MOVE = 'block move' -BLOCK_ROTATE_CCW = 'block rotate ccw' -BLOCK_ROTATE_CW = 'block rotate cw' -BLOCK_PARAM_MODIFY = 'block param modify' -BLOCK_INC_TYPE = 'block increment type' -BLOCK_DEC_TYPE = 'block decrement type' -BLOCK_ENABLE = 'block enable' -BLOCK_DISABLE = 'block disable' -BLOCK_CUT = 'block cut' -BLOCK_COPY = 'block copy' -BLOCK_PASTE = 'block paste' -PORT_CONTROLLER_INC = 'port controller increment' -PORT_CONTROLLER_DEC = 'port controller decrement' -ELEMENT_CREATE = 'element create' -ELEMENT_DELETE = 'element delete' -ELEMENT_SELECT = 'element select' -NOTHING_SELECT = 'nothing select' -FLOW_GRAPH_OPEN = 'flow graph open' -FLOW_GRAPH_UNDO = 'flow graph undo' -FLOW_GRAPH_REDO = 'flow graph redo' -FLOW_GRAPH_SAVE = 'flow graph save' -FLOW_GRAPH_SAVE_AS = 'flow graph save as' -FLOW_GRAPH_CLOSE = 'flow graph close' -FLOW_GRAPH_NEW = 'flow graph new' -FLOW_GRAPH_GEN = 'flow graph gen' -FLOW_GRAPH_EXEC = 'flow graph exec' -FLOW_GRAPH_KILL = 'flow graph kill' -FLOW_GRAPH_SCREEN_CAPTURE = 'flow graph screen capture' -ABOUT_WINDOW_DISPLAY = 'about window display' -HELP_WINDOW_DISPLAY = 'help window display' -TYPES_WINDOW_DISPLAY = 'types window display' - -###################################################################################################### -# Action Key Map -###################################################################################################### NO_MODS_MASK = 0 -_actions_key_list = ( - #action name, key value, mod mask - (FLOW_GRAPH_NEW, gtk.keysyms.n, gtk.gdk.CONTROL_MASK), - (FLOW_GRAPH_OPEN, gtk.keysyms.o, gtk.gdk.CONTROL_MASK), - (FLOW_GRAPH_SAVE, gtk.keysyms.s, gtk.gdk.CONTROL_MASK), - (FLOW_GRAPH_SAVE_AS, gtk.keysyms.s, gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK), - (FLOW_GRAPH_CLOSE, gtk.keysyms.w, gtk.gdk.CONTROL_MASK), - (APPLICATION_QUIT, gtk.keysyms.q, gtk.gdk.CONTROL_MASK), - (FLOW_GRAPH_UNDO, gtk.keysyms.z, gtk.gdk.CONTROL_MASK), - (FLOW_GRAPH_REDO, gtk.keysyms.y, gtk.gdk.CONTROL_MASK), - (ELEMENT_DELETE, gtk.keysyms.Delete, NO_MODS_MASK), - (BLOCK_ROTATE_CCW, gtk.keysyms.Left, NO_MODS_MASK), - (BLOCK_ROTATE_CW, gtk.keysyms.Right, NO_MODS_MASK), - (BLOCK_DEC_TYPE, gtk.keysyms.Up, NO_MODS_MASK), - (BLOCK_INC_TYPE, gtk.keysyms.Down, NO_MODS_MASK), - (BLOCK_PARAM_MODIFY, gtk.keysyms.Return, NO_MODS_MASK), - (BLOCK_ENABLE, gtk.keysyms.e, NO_MODS_MASK), - (BLOCK_DISABLE, gtk.keysyms.d, NO_MODS_MASK), - (BLOCK_CUT, gtk.keysyms.x, gtk.gdk.CONTROL_MASK), - (BLOCK_COPY, gtk.keysyms.c, gtk.gdk.CONTROL_MASK), - (BLOCK_PASTE, gtk.keysyms.v, gtk.gdk.CONTROL_MASK), - (FLOW_GRAPH_GEN, gtk.keysyms.F5, NO_MODS_MASK), - (FLOW_GRAPH_EXEC, gtk.keysyms.F6, NO_MODS_MASK), - (FLOW_GRAPH_KILL, gtk.keysyms.F7, NO_MODS_MASK), - (FLOW_GRAPH_SCREEN_CAPTURE, gtk.keysyms.Print, NO_MODS_MASK), - (HELP_WINDOW_DISPLAY, gtk.keysyms.F1, NO_MODS_MASK), - #the following have no associated gtk.Action - (PORT_CONTROLLER_INC, gtk.keysyms.equal, NO_MODS_MASK), - (PORT_CONTROLLER_INC, gtk.keysyms.plus, NO_MODS_MASK), - (PORT_CONTROLLER_INC, gtk.keysyms.KP_Add, NO_MODS_MASK), - (PORT_CONTROLLER_DEC, gtk.keysyms.minus, NO_MODS_MASK), - (PORT_CONTROLLER_DEC, gtk.keysyms.KP_Subtract, NO_MODS_MASK), -) -_actions_key_dict = dict(((keyval, mod_mask), action_name) for action_name, keyval, mod_mask in _actions_key_list) -_used_mods_mask = reduce(lambda x, y: x | y, [mod_mask for action_name, keyval, mod_mask in _actions_key_list], NO_MODS_MASK) +######################################################################## +# Actions API +######################################################################## +_actions_keypress_dict = dict() _keymap = gtk.gdk.keymap_get_default() -def get_action_name_from_key_press(event): +_used_mods_mask = NO_MODS_MASK +def get_action_from_key_press(event): """ - Get the action name associated with the key press event. + Get the action associated with the key press event. Both the key value and the mask must have a match. @param event a gtk key press event - @return the action name or blank string + @throws a key error when no action matches + @return the action object """ + _used_mods_mask = reduce(lambda x, y: x | y, [mod_mask for keyval, mod_mask in _actions_keypress_dict], NO_MODS_MASK) #extract the key value and the consumed modifiers keyval, egroup, level, consumed = _keymap.translate_keyboard_state( event.hardware_keycode, event.state, event.group) #get the modifier mask and ignore irrelevant modifiers mod_mask = event.state & ~consumed & _used_mods_mask - try: return _actions_key_dict[(keyval, mod_mask)] + try: return _actions_keypress_dict[(keyval, mod_mask)] except KeyError: raise KeyError, 'Keypress: "%s, %s" does not have an associated action'%(gtk.gdk.keyval_name(keyval), mod_mask) -###################################################################################################### -# Actions -###################################################################################################### -_actions_list = ( - gtk.Action(FLOW_GRAPH_NEW, '_New', 'Create a new flow graph', gtk.STOCK_NEW), - gtk.Action(FLOW_GRAPH_OPEN, '_Open', 'Open an existing flow graph', gtk.STOCK_OPEN), - gtk.Action(FLOW_GRAPH_SAVE, '_Save', 'Save the current flow graph', gtk.STOCK_SAVE), - gtk.Action(FLOW_GRAPH_SAVE_AS, 'Save _As', 'Save the current flow graph as...', gtk.STOCK_SAVE_AS), - gtk.Action(FLOW_GRAPH_CLOSE, '_Close', 'Close the current flow graph', gtk.STOCK_CLOSE), - gtk.Action(APPLICATION_QUIT, '_Quit', 'Quit program', gtk.STOCK_QUIT), - gtk.Action(FLOW_GRAPH_UNDO, '_Undo', 'Undo a change to the flow graph', gtk.STOCK_UNDO), - gtk.Action(FLOW_GRAPH_REDO, '_Redo', 'Redo a change to the flow graph', gtk.STOCK_REDO), - gtk.Action(ELEMENT_DELETE, '_Delete', 'Delete the selected blocks', gtk.STOCK_DELETE), - gtk.Action(BLOCK_ROTATE_CCW, 'Rotate Counterclockwise', 'Rotate the selected blocks 90 degrees to the left', gtk.STOCK_GO_BACK), - gtk.Action(BLOCK_ROTATE_CW, 'Rotate Clockwise', 'Rotate the selected blocks 90 degrees to the right', gtk.STOCK_GO_FORWARD), - gtk.Action(BLOCK_PARAM_MODIFY, '_Properties', 'Modify params for the selected block', gtk.STOCK_PROPERTIES), - gtk.Action(BLOCK_ENABLE, 'E_nable', 'Enable the selected blocks', gtk.STOCK_CONNECT), - gtk.Action(BLOCK_DISABLE, 'D_isable', 'Disable the selected blocks', gtk.STOCK_DISCONNECT), - gtk.Action(BLOCK_CUT, 'Cu_t', 'Cut', gtk.STOCK_CUT), - gtk.Action(BLOCK_COPY, '_Copy', 'Copy', gtk.STOCK_COPY), - gtk.Action(BLOCK_PASTE, '_Paste', 'Paste', gtk.STOCK_PASTE), - gtk.Action(ABOUT_WINDOW_DISPLAY, '_About', 'About this program', gtk.STOCK_ABOUT), - gtk.Action(HELP_WINDOW_DISPLAY, '_Help', 'Usage Tips', gtk.STOCK_HELP), - gtk.Action(TYPES_WINDOW_DISPLAY, '_Types', 'Types Color Mapping', gtk.STOCK_DIALOG_INFO), - gtk.Action(FLOW_GRAPH_GEN, '_Generate', 'Generate the flow graph', gtk.STOCK_CONVERT), - gtk.Action(FLOW_GRAPH_EXEC, '_Execute', 'Execute the flow graph', gtk.STOCK_EXECUTE), - gtk.Action(FLOW_GRAPH_KILL, '_Kill', 'Kill the flow graph', gtk.STOCK_STOP), - gtk.Action(FLOW_GRAPH_SCREEN_CAPTURE, 'S_creen Capture', 'Create a screen capture of the flow graph', gtk.STOCK_PRINT), -) -def get_all_actions(): return _actions_list +_all_actions_list = list() +def get_all_actions(): return _all_actions_list + +_accel_group = gtk.AccelGroup() +def get_accel_group(): return _accel_group -_actions_dict = dict((action.get_name(), action) for action in get_all_actions()) -def get_action_from_name(action_name): +class Action(gtk.Action): """ - Retrieve the action from the action list. - Search the list and find an action with said name. - @param action_name the action name(string) - @throw KeyError bad action name - @return a gtk action object + A custom Action class based on gtk.Action. + Pass additional arguments such as keypresses. + Register actions and keypresses with this module. """ - try: return _actions_dict[action_name] - except KeyError: raise KeyError, 'Action Name: "%s" does not exist'%action_name -###################################################################################################### -# Accelerators -###################################################################################################### -_accel_group = gtk.AccelGroup() -def get_accel_group(): return _accel_group + def __init__(self, keypresses=(), name=None, label=None, tooltip=None, stock_id=None): + """ + Create a new Action instance. + @param key_presses a tuple of (keyval1, mod_mask1, keyval2, mod_mask2, ...) + @param the regular gtk.Action parameters (defaults to None) + """ + if name is None: name = label + gtk.Action.__init__(self, + name=name, label=label, + tooltip=tooltip, stock_id=stock_id, + ) + #register this action + _all_actions_list.append(self) + for i in range(len(keypresses)/2): + keyval, mod_mask = keypresses[i*2:(i+1)*2] + #register this keypress + assert not _actions_keypress_dict.has_key((keyval, mod_mask)) + _actions_keypress_dict[(keyval, mod_mask)] = self + #set the accelerator group, and accelerator path + #register the key name and mod mask with the accelerator path + if label is None: continue #dont register accel + accel_path = '
/'+self.get_name() + self.set_accel_group(get_accel_group()) + self.set_accel_path(accel_path) + gtk.accel_map_add_entry(accel_path, keyval, mod_mask) + + def __str__(self): + """ + The string representation should be the name of the action id. + Try to find the action id for this action by searching this module. + """ + try: + import Actions + return filter(lambda attr: getattr(Actions, attr) == self, dir(Actions))[0] + except: return self.get_name() + + def __repr__(self): return str(self) -#set the accelerator group, and accelerator path -#register the key name and mod mask with the accelerator path -for action_name, keyval, mod_mask in _actions_key_list: - try: - accel_path = '
/'+action_name - get_action_from_name(action_name).set_accel_group(get_accel_group()) - get_action_from_name(action_name).set_accel_path(accel_path) - gtk.accel_map_add_entry(accel_path, keyval, mod_mask) - except KeyError: pass #no action was created for this action name + def __call__(self): + """ + Emit the activate signal when called with (). + """ + self.emit('activate') + +######################################################################## +# Actions +######################################################################## +PAGE_CHANGE = Action() +FLOW_GRAPH_NEW = Action( + label='_New', + tooltip='Create a new flow graph', + stock_id=gtk.STOCK_NEW, + keypresses=(gtk.keysyms.n, gtk.gdk.CONTROL_MASK), +) +FLOW_GRAPH_OPEN = Action( + label='_Open', + tooltip='Open an existing flow graph', + stock_id=gtk.STOCK_OPEN, + keypresses=(gtk.keysyms.o, gtk.gdk.CONTROL_MASK), +) +FLOW_GRAPH_SAVE = Action( + label='_Save', + tooltip='Save the current flow graph', + stock_id=gtk.STOCK_SAVE, + keypresses=(gtk.keysyms.s, gtk.gdk.CONTROL_MASK), +) +FLOW_GRAPH_SAVE_AS = Action( + label='Save _As', + tooltip='Save the current flow graph as...', + stock_id=gtk.STOCK_SAVE_AS, + keypresses=(gtk.keysyms.s, gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK), +) +FLOW_GRAPH_CLOSE = Action( + label='_Close', + tooltip='Close the current flow graph', + stock_id=gtk.STOCK_CLOSE, + keypresses=(gtk.keysyms.w, gtk.gdk.CONTROL_MASK), +) +APPLICATION_INITIALIZE = Action() +APPLICATION_QUIT = Action( + label='_Quit', + tooltip='Quit program', + stock_id=gtk.STOCK_QUIT, + keypresses=(gtk.keysyms.q, gtk.gdk.CONTROL_MASK), +) +FLOW_GRAPH_UNDO = Action( + label='_Undo', + tooltip='Undo a change to the flow graph', + stock_id=gtk.STOCK_UNDO, + keypresses=(gtk.keysyms.z, gtk.gdk.CONTROL_MASK), +) +FLOW_GRAPH_REDO = Action( + label='_Redo', + tooltip='Redo a change to the flow graph', + stock_id=gtk.STOCK_REDO, + keypresses=(gtk.keysyms.y, gtk.gdk.CONTROL_MASK), +) +NOTHING_SELECT = Action() +ELEMENT_SELECT = Action() +ELEMENT_CREATE = Action() +ELEMENT_DELETE = Action( + label='_Delete', + tooltip='Delete the selected blocks', + stock_id=gtk.STOCK_DELETE, + keypresses=(gtk.keysyms.Delete, NO_MODS_MASK), +) +BLOCK_MOVE = Action() +BLOCK_ROTATE_CCW = Action( + label='Rotate Counterclockwise', + tooltip='Rotate the selected blocks 90 degrees to the left', + stock_id=gtk.STOCK_GO_BACK, + keypresses=(gtk.keysyms.Left, NO_MODS_MASK), +) +BLOCK_ROTATE_CW = Action( + label='Rotate Clockwise', + tooltip='Rotate the selected blocks 90 degrees to the right', + stock_id=gtk.STOCK_GO_FORWARD, + keypresses=(gtk.keysyms.Right, NO_MODS_MASK), +) +BLOCK_PARAM_MODIFY = Action( + label='_Properties', + tooltip='Modify params for the selected block', + stock_id=gtk.STOCK_PROPERTIES, + keypresses=(gtk.keysyms.Return, NO_MODS_MASK), +) +BLOCK_ENABLE = Action( + label='E_nable', + tooltip='Enable the selected blocks', + stock_id=gtk.STOCK_CONNECT, + keypresses=(gtk.keysyms.e, NO_MODS_MASK), +) +BLOCK_DISABLE = Action( + label='D_isable', + tooltip='Disable the selected blocks', + stock_id=gtk.STOCK_DISCONNECT, + keypresses=(gtk.keysyms.d, NO_MODS_MASK), +) +BLOCK_CUT = Action( + label='Cu_t', + tooltip='Cut', + stock_id=gtk.STOCK_CUT, + keypresses=(gtk.keysyms.x, gtk.gdk.CONTROL_MASK), +) +BLOCK_COPY = Action( + label='_Copy', + tooltip='Copy', + stock_id=gtk.STOCK_COPY, + keypresses=(gtk.keysyms.c, gtk.gdk.CONTROL_MASK), +) +BLOCK_PASTE = Action( + label='_Paste', + tooltip='Paste', + stock_id=gtk.STOCK_PASTE, + keypresses=(gtk.keysyms.v, gtk.gdk.CONTROL_MASK), +) +ABOUT_WINDOW_DISPLAY = Action( + label='_About', + tooltip='About this program', + stock_id=gtk.STOCK_ABOUT, +) +HELP_WINDOW_DISPLAY = Action( + label='_Help', + tooltip='Usage Tips', + stock_id=gtk.STOCK_HELP, + keypresses=(gtk.keysyms.F1, NO_MODS_MASK), +) +TYPES_WINDOW_DISPLAY = Action( + label='_Types', + tooltip='Types Color Mapping', + stock_id=gtk.STOCK_DIALOG_INFO, +) +FLOW_GRAPH_GEN = Action( + label='_Generate', + tooltip='Generate the flow graph', + stock_id=gtk.STOCK_CONVERT, + keypresses=(gtk.keysyms.F5, NO_MODS_MASK), +) +FLOW_GRAPH_EXEC = Action( + label='_Execute', + tooltip='Execute the flow graph', + stock_id=gtk.STOCK_EXECUTE, + keypresses=(gtk.keysyms.F6, NO_MODS_MASK), +) +FLOW_GRAPH_KILL = Action( + label='_Kill', + tooltip='Kill the flow graph', + stock_id=gtk.STOCK_STOP, + keypresses=(gtk.keysyms.F7, NO_MODS_MASK), +) +FLOW_GRAPH_SCREEN_CAPTURE = Action( + label='S_creen Capture', + tooltip='Create a screen capture of the flow graph', + stock_id=gtk.STOCK_PRINT, + keypresses=(gtk.keysyms.Print, NO_MODS_MASK), +) +PORT_CONTROLLER_DEC = Action( + keypresses=(gtk.keysyms.minus, NO_MODS_MASK, gtk.keysyms.KP_Subtract, NO_MODS_MASK), +) +PORT_CONTROLLER_INC = Action( + keypresses=(gtk.keysyms.plus, NO_MODS_MASK, gtk.keysyms.KP_Add, NO_MODS_MASK), +) +BLOCK_INC_TYPE = Action( + keypresses=(gtk.keysyms.Down, NO_MODS_MASK), +) +BLOCK_DEC_TYPE = Action( + keypresses=(gtk.keysyms.Up, NO_MODS_MASK), +) diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py index 697d48a3c..fff5ebc08 100644 --- a/grc/gui/Bars.py +++ b/grc/gui/Bars.py @@ -104,9 +104,8 @@ class Toolbar(gtk.Toolbar): """ gtk.Toolbar.__init__(self) self.set_style(gtk.TOOLBAR_ICONS) - for action_name in TOOLBAR_LIST: - if action_name: #add a tool item - action = Actions.get_action_from_name(action_name) + for action in TOOLBAR_LIST: + if action: #add a tool item self.add(action.create_tool_item()) #this reset of the tooltip property is required (after creating the tool item) for the tooltip to show action.set_property('tooltip', action.get_property('tooltip')) @@ -123,16 +122,15 @@ class MenuBar(gtk.MenuBar): Add the submenu to the menu bar. """ gtk.MenuBar.__init__(self) - for main_action,action_names in MENU_BAR_LIST: + for main_action, actions in MENU_BAR_LIST: #create the main menu item main_menu_item = main_action.create_menu_item() self.append(main_menu_item) #create the menu main_menu = gtk.Menu() main_menu_item.set_submenu(main_menu) - for action_name in action_names: - if action_name: #append a menu item - action = Actions.get_action_from_name(action_name) + for action in actions: + if action: #append a menu item main_menu.append(action.create_menu_item()) else: main_menu.append(gtk.SeparatorMenuItem()) main_menu.show_all() #this show all is required for the separators to show diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index 35ccf5e27..c90071f23 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -86,7 +86,7 @@ class FlowGraph(Element): block.set_coordinate(coor) block.set_rotation(0) block.get_param('id').set_value(id) - self.handle_states(ELEMENT_CREATE) + ELEMENT_CREATE() ########################################################################### # Copy Paste @@ -409,7 +409,7 @@ class FlowGraph(Element): self._old_selected_port is not self._new_selected_port: try: self.connect(self._old_selected_port, self._new_selected_port) - self.handle_states(ELEMENT_CREATE) + ELEMENT_CREATE() except: Messages.send_fail_connection() self._old_selected_port = None self._new_selected_port = None @@ -424,7 +424,7 @@ class FlowGraph(Element): self._selected_elements = list( set.union(old_elements, new_elements) - set.intersection(old_elements, new_elements) ) - self.handle_states(ELEMENT_SELECT) + ELEMENT_SELECT() ########################################################################## ## Event Handlers @@ -446,7 +446,7 @@ class FlowGraph(Element): #double click detected, bring up params dialog if possible if double_click and self.get_selected_block(): self.mouse_pressed = False - self.handle_states(BLOCK_PARAM_MODIFY) + BLOCK_PARAM_MODIFY() def handle_mouse_button_release(self, left_click, coordinate): """ @@ -457,7 +457,7 @@ class FlowGraph(Element): self.time = 0 self.mouse_pressed = False if self.element_moved: - self.handle_states(BLOCK_MOVE) + BLOCK_MOVE() self.element_moved = False self.update_selected_elements() @@ -487,7 +487,7 @@ class FlowGraph(Element): adj.emit('changed') #remove the connection if selected in drag event if len(self.get_selected_elements()) == 1 and self.get_selected_element().is_connection(): - self.handle_states(ELEMENT_DELETE) + ELEMENT_DELETE() #move the selected elements and record the new coordinate X, Y = self.get_coordinate() if not self.get_ctrl_mask(): self.move_selected((int(x - X), int(y - Y))) diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py index 6d36f4cf7..39cd84da9 100644 --- a/grc/gui/MainWindow.py +++ b/grc/gui/MainWindow.py @@ -1,5 +1,5 @@ """ -Copyright 2008 Free Software Foundation, Inc. +Copyright 2008, 2009 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -21,7 +21,8 @@ from Constants import \ NEW_FLOGRAPH_TITLE, DEFAULT_REPORTS_WINDOW_WIDTH from Actions import \ APPLICATION_QUIT, FLOW_GRAPH_KILL, \ - FLOW_GRAPH_SAVE, get_accel_group + FLOW_GRAPH_SAVE, PAGE_CHANGE, \ + get_accel_group import pygtk pygtk.require('2.0') import gtk @@ -67,14 +68,13 @@ PAGE_TITLE_MARKUP_TMPL = """\ class MainWindow(gtk.Window): """The topmost window with menus, the tool bar, and other major windows.""" - def __init__(self, handle_states, platform): + def __init__(self, platform): """ - MainWindow contructor. - @param handle_states the callback function + MainWindow contructor + Setup the menu, toolbar, flowgraph editor notebook, block selection window... """ self._platform = platform #setup window - self.handle_states = handle_states gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) vbox = gtk.VBox() self.hpaned = gtk.HPaned() @@ -123,7 +123,7 @@ class MainWindow(gtk.Window): This method in turns calls the state handler to quit. @return true """ - self.handle_states(APPLICATION_QUIT) + APPLICATION_QUIT() return True def _handle_page_change(self, notebook, page, page_num): @@ -137,7 +137,7 @@ class MainWindow(gtk.Window): """ self.current_page = self.notebook.get_nth_page(page_num) Messages.send_page_switch(self.current_page.get_file_path()) - self.handle_states() + PAGE_CHANGE() ############################################################ # Report Window @@ -223,12 +223,12 @@ class MainWindow(gtk.Window): self._set_page(self.page_to_be_closed) #unsaved? ask the user if not self.page_to_be_closed.get_saved() and self._save_changes(): - self.handle_states(FLOW_GRAPH_SAVE) #try to save + FLOW_GRAPH_SAVE() #try to save if not self.page_to_be_closed.get_saved(): #still unsaved? self.page_to_be_closed = None #set the page to be closed back to None return #stop the flow graph if executing - if self.page_to_be_closed.get_pid(): self.handle_states(FLOW_GRAPH_KILL) + if self.page_to_be_closed.get_pid(): FLOW_GRAPH_KILL() #remove the page self.notebook.remove_page(self.notebook.page_num(self.page_to_be_closed)) if ensure and self.notebook.get_n_pages() == 0: self.new_page() #no pages, make a new one diff --git a/grc/gui/NotebookPage.py b/grc/gui/NotebookPage.py index cb6b7ed30..645af3f7f 100644 --- a/grc/gui/NotebookPage.py +++ b/grc/gui/NotebookPage.py @@ -80,9 +80,8 @@ class NotebookPage(gtk.HBox): self.drawing_area = DrawingArea(self.get_flow_graph()) self.scrolled_window.add_with_viewport(self.get_drawing_area()) self.pack_start(self.scrolled_window) - #inject drawing area and handle states into flow graph + #inject drawing area into flow graph self.get_flow_graph().drawing_area = self.get_drawing_area() - self.get_flow_graph().handle_states = main_window.handle_states self.show_all() def get_drawing_area(self): return self.drawing_area @@ -104,7 +103,7 @@ class NotebookPage(gtk.HBox): @param the button """ self.main_window.page_to_be_closed = self - self.main_window.handle_states(FLOW_GRAPH_CLOSE) + FLOW_GRAPH_CLOSE() def set_markup(self, markup): """ diff --git a/grc/gui/StateCache.py b/grc/gui/StateCache.py index 04b18b18a..60ab3a6b4 100644 --- a/grc/gui/StateCache.py +++ b/grc/gui/StateCache.py @@ -17,7 +17,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -from Actions import FLOW_GRAPH_UNDO, FLOW_GRAPH_REDO, get_action_from_name +from Actions import FLOW_GRAPH_UNDO, FLOW_GRAPH_REDO from Constants import STATE_CACHE_SIZE class StateCache(object): @@ -88,5 +88,5 @@ class StateCache(object): """ Update the undo and redo actions based on the number of next and prev states. """ - get_action_from_name(FLOW_GRAPH_REDO).set_sensitive(self.num_next_states != 0) - get_action_from_name(FLOW_GRAPH_UNDO).set_sensitive(self.num_prev_states != 0) + FLOW_GRAPH_REDO.set_sensitive(self.num_next_states != 0) + FLOW_GRAPH_UNDO.set_sensitive(self.num_prev_states != 0) -- cgit From 8442dfc877a89de00e5fd0fd1b4b1890a91af630 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sun, 13 Sep 2009 02:29:26 -0700 Subject: Simply Actions module imports, using module prefix. --- grc/gui/FlowGraph.py | 17 +++++++---------- grc/gui/MainWindow.py | 15 ++++++--------- grc/gui/NotebookPage.py | 4 ++-- grc/gui/StateCache.py | 6 +++--- 4 files changed, 18 insertions(+), 24 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index c90071f23..8feb171f1 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -18,10 +18,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ from Constants import SCROLL_PROXIMITY_SENSITIVITY, SCROLL_DISTANCE -from Actions import \ - ELEMENT_CREATE, ELEMENT_SELECT, \ - BLOCK_PARAM_MODIFY, BLOCK_MOVE, \ - ELEMENT_DELETE +import Actions import Colors import Utils from Element import Element @@ -86,7 +83,7 @@ class FlowGraph(Element): block.set_coordinate(coor) block.set_rotation(0) block.get_param('id').set_value(id) - ELEMENT_CREATE() + Actions.ELEMENT_CREATE() ########################################################################### # Copy Paste @@ -409,7 +406,7 @@ class FlowGraph(Element): self._old_selected_port is not self._new_selected_port: try: self.connect(self._old_selected_port, self._new_selected_port) - ELEMENT_CREATE() + Actions.ELEMENT_CREATE() except: Messages.send_fail_connection() self._old_selected_port = None self._new_selected_port = None @@ -424,7 +421,7 @@ class FlowGraph(Element): self._selected_elements = list( set.union(old_elements, new_elements) - set.intersection(old_elements, new_elements) ) - ELEMENT_SELECT() + Actions.ELEMENT_SELECT() ########################################################################## ## Event Handlers @@ -446,7 +443,7 @@ class FlowGraph(Element): #double click detected, bring up params dialog if possible if double_click and self.get_selected_block(): self.mouse_pressed = False - BLOCK_PARAM_MODIFY() + Actions.BLOCK_PARAM_MODIFY() def handle_mouse_button_release(self, left_click, coordinate): """ @@ -457,7 +454,7 @@ class FlowGraph(Element): self.time = 0 self.mouse_pressed = False if self.element_moved: - BLOCK_MOVE() + Actions.BLOCK_MOVE() self.element_moved = False self.update_selected_elements() @@ -487,7 +484,7 @@ class FlowGraph(Element): adj.emit('changed') #remove the connection if selected in drag event if len(self.get_selected_elements()) == 1 and self.get_selected_element().is_connection(): - ELEMENT_DELETE() + Actions.ELEMENT_DELETE() #move the selected elements and record the new coordinate X, Y = self.get_coordinate() if not self.get_ctrl_mask(): self.move_selected((int(x - X), int(y - Y))) diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py index 39cd84da9..9fcbe2a6c 100644 --- a/grc/gui/MainWindow.py +++ b/grc/gui/MainWindow.py @@ -19,10 +19,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from Constants import \ NEW_FLOGRAPH_TITLE, DEFAULT_REPORTS_WINDOW_WIDTH -from Actions import \ - APPLICATION_QUIT, FLOW_GRAPH_KILL, \ - FLOW_GRAPH_SAVE, PAGE_CHANGE, \ - get_accel_group +import Actions import pygtk pygtk.require('2.0') import gtk @@ -80,7 +77,7 @@ class MainWindow(gtk.Window): self.hpaned = gtk.HPaned() self.add(vbox) #create the menu bar and toolbar - self.add_accel_group(get_accel_group()) + self.add_accel_group(Actions.get_accel_group()) vbox.pack_start(Bars.MenuBar(), False) vbox.pack_start(Bars.Toolbar(), False) vbox.pack_start(self.hpaned) @@ -123,7 +120,7 @@ class MainWindow(gtk.Window): This method in turns calls the state handler to quit. @return true """ - APPLICATION_QUIT() + Actions.APPLICATION_QUIT() return True def _handle_page_change(self, notebook, page, page_num): @@ -137,7 +134,7 @@ class MainWindow(gtk.Window): """ self.current_page = self.notebook.get_nth_page(page_num) Messages.send_page_switch(self.current_page.get_file_path()) - PAGE_CHANGE() + Actions.PAGE_CHANGE() ############################################################ # Report Window @@ -223,12 +220,12 @@ class MainWindow(gtk.Window): self._set_page(self.page_to_be_closed) #unsaved? ask the user if not self.page_to_be_closed.get_saved() and self._save_changes(): - FLOW_GRAPH_SAVE() #try to save + Actions.FLOW_GRAPH_SAVE() #try to save if not self.page_to_be_closed.get_saved(): #still unsaved? self.page_to_be_closed = None #set the page to be closed back to None return #stop the flow graph if executing - if self.page_to_be_closed.get_pid(): FLOW_GRAPH_KILL() + if self.page_to_be_closed.get_pid(): Actions.FLOW_GRAPH_KILL() #remove the page self.notebook.remove_page(self.notebook.page_num(self.page_to_be_closed)) if ensure and self.notebook.get_n_pages() == 0: self.new_page() #no pages, make a new one diff --git a/grc/gui/NotebookPage.py b/grc/gui/NotebookPage.py index 645af3f7f..fddfeaf5f 100644 --- a/grc/gui/NotebookPage.py +++ b/grc/gui/NotebookPage.py @@ -17,10 +17,10 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -from Actions import FLOW_GRAPH_CLOSE import pygtk pygtk.require('2.0') import gtk +import Actions from StateCache import StateCache from Constants import MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT from DrawingArea import DrawingArea @@ -103,7 +103,7 @@ class NotebookPage(gtk.HBox): @param the button """ self.main_window.page_to_be_closed = self - FLOW_GRAPH_CLOSE() + Actions.FLOW_GRAPH_CLOSE() def set_markup(self, markup): """ diff --git a/grc/gui/StateCache.py b/grc/gui/StateCache.py index 60ab3a6b4..3f6b79224 100644 --- a/grc/gui/StateCache.py +++ b/grc/gui/StateCache.py @@ -17,7 +17,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -from Actions import FLOW_GRAPH_UNDO, FLOW_GRAPH_REDO +import Actions from Constants import STATE_CACHE_SIZE class StateCache(object): @@ -88,5 +88,5 @@ class StateCache(object): """ Update the undo and redo actions based on the number of next and prev states. """ - FLOW_GRAPH_REDO.set_sensitive(self.num_next_states != 0) - FLOW_GRAPH_UNDO.set_sensitive(self.num_prev_states != 0) + Actions.FLOW_GRAPH_REDO.set_sensitive(self.num_next_states != 0) + Actions.FLOW_GRAPH_UNDO.set_sensitive(self.num_prev_states != 0) -- cgit From 6bd669d6e2fbe4b0ba5f74b697e532cf909c9e1d Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sun, 13 Sep 2009 02:39:40 -0700 Subject: fix for uniformity convention with gtk signal name strings --- grc/gui/BlockTreeWindow.py | 2 +- grc/gui/PropsDialog.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/BlockTreeWindow.py b/grc/gui/BlockTreeWindow.py index 5c5d21b64..07b8ea7e0 100644 --- a/grc/gui/BlockTreeWindow.py +++ b/grc/gui/BlockTreeWindow.py @@ -57,7 +57,7 @@ class BlockTreeWindow(gtk.VBox): self.treeview = gtk.TreeView(self.treestore) self.treeview.set_enable_search(False) #disable pop up search box self.treeview.add_events(gtk.gdk.BUTTON_PRESS_MASK) - self.treeview.connect('button_press_event', self._handle_mouse_button_press) + self.treeview.connect('button-press-event', self._handle_mouse_button_press) selection = self.treeview.get_selection() selection.set_mode('single') selection.connect('changed', self._handle_selection_change) diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index b295ba412..e3cd3a06f 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -82,7 +82,7 @@ class PropsDialog(gtk.Dialog): vbox.pack_start(self._error_box, False) vbox.pack_start(self._docs_box, False) #connect events - self.connect('key_press_event', self._handle_key_press) + self.connect('key-press-event', self._handle_key_press) self.connect('show', self._update_gui) #show all (performs initial gui update) self.show_all() -- cgit From ae3c009666f2bba0e10e054b0747d8f82a29515f Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sun, 13 Sep 2009 03:25:56 -0700 Subject: tweaked key handling callbacks --- grc/gui/ActionHandler.py | 4 +--- grc/gui/Actions.py | 13 +++++++------ 2 files changed, 8 insertions(+), 9 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 59e535bd4..361be1cf8 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -81,9 +81,7 @@ class ActionHandler: """ try: assert self.get_focus_flag() except AssertionError: return False - try: Actions.get_action_from_key_press(event)() - except KeyError: return False - return True #handled by this method + return Actions.handle_key_press(event) def _quit(self, window, event): """ diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index 90017987f..b22279c1d 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -29,13 +29,12 @@ NO_MODS_MASK = 0 _actions_keypress_dict = dict() _keymap = gtk.gdk.keymap_get_default() _used_mods_mask = NO_MODS_MASK -def get_action_from_key_press(event): +def handle_key_press(event): """ - Get the action associated with the key press event. + Call the action associated with the key press event. Both the key value and the mask must have a match. @param event a gtk key press event - @throws a key error when no action matches - @return the action object + @return true if handled """ _used_mods_mask = reduce(lambda x, y: x | y, [mod_mask for keyval, mod_mask in _actions_keypress_dict], NO_MODS_MASK) #extract the key value and the consumed modifiers @@ -43,8 +42,10 @@ def get_action_from_key_press(event): event.hardware_keycode, event.state, event.group) #get the modifier mask and ignore irrelevant modifiers mod_mask = event.state & ~consumed & _used_mods_mask - try: return _actions_keypress_dict[(keyval, mod_mask)] - except KeyError: raise KeyError, 'Keypress: "%s, %s" does not have an associated action'%(gtk.gdk.keyval_name(keyval), mod_mask) + #look up the keypress and call the action + try: _actions_keypress_dict[(keyval, mod_mask)]() + except KeyError: return False #not handled + return True #handled here _all_actions_list = list() def get_all_actions(): return _all_actions_list -- cgit From 3fb876217316bb8ac9a59fffc75bb3c523ae1704 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Mon, 14 Sep 2009 22:37:55 -0700 Subject: fixed issue where entry boxes lost focus (mishandling of hide changing) --- grc/gui/PropsDialog.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'grc/gui') diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index e3cd3a06f..a7822b228 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -91,6 +91,8 @@ class PropsDialog(gtk.Dialog): """ Have the params in this dialog changed? Ex: Added, removed, type change, hide change... + To the props dialog, the hide setting of 'none' and 'part' are identical. + Therfore, the props dialog only cares if the hide setting is/not 'all'. Make a hash that uniquely represents the params state. @return true if changed """ @@ -99,7 +101,7 @@ class PropsDialog(gtk.Dialog): for param in self._block.get_params(): self._hash ^= hash(param) self._hash ^= hash(param.get_type()) - self._hash ^= hash(param.get_hide()) + self._hash ^= hash(param.get_hide() == 'all') return self._hash != old_hash def _handle_changed(self, *args): -- cgit From 14895064d7345c2223ff2b8ff3b9cbcdf69dd8c9 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Fri, 18 Sep 2009 02:10:13 -0700 Subject: added errors dialog to show all error messages in flow graph --- grc/gui/ActionHandler.py | 3 +++ grc/gui/Actions.py | 5 +++++ grc/gui/Bars.py | 3 +++ grc/gui/Dialogs.py | 14 ++++++++++++++ 4 files changed, 25 insertions(+) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 361be1cf8..ee3e19a6c 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -210,6 +210,8 @@ class ActionHandler: Dialogs.HelpDialog() elif action == Actions.TYPES_WINDOW_DISPLAY: Dialogs.TypesDialog(self.get_flow_graph().get_parent()) + elif action == Actions.ERRORS_WINDOW_DISPLAY: + Dialogs.ErrorsDialog(self.get_flow_graph()) ################################################## # Param Modifications ################################################## @@ -307,6 +309,7 @@ class ActionHandler: # Global Actions for all States ################################################## #update general buttons + Actions.ERRORS_WINDOW_DISPLAY.set_sensitive(not self.get_flow_graph().is_valid()) Actions.ELEMENT_DELETE.set_sensitive(bool(self.get_flow_graph().get_selected_elements())) Actions.BLOCK_PARAM_MODIFY.set_sensitive(bool(self.get_flow_graph().get_selected_block())) Actions.BLOCK_ROTATE_CCW.set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index b22279c1d..1cc12a819 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -215,6 +215,11 @@ BLOCK_PASTE = Action( stock_id=gtk.STOCK_PASTE, keypresses=(gtk.keysyms.v, gtk.gdk.CONTROL_MASK), ) +ERRORS_WINDOW_DISPLAY = Action( + label='_Errors', + tooltip='Flowgraph error messages', + stock_id=gtk.STOCK_DIALOG_ERROR, +) ABOUT_WINDOW_DISPLAY = Action( label='_About', tooltip='About this program', diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py index fff5ebc08..17835eb00 100644 --- a/grc/gui/Bars.py +++ b/grc/gui/Bars.py @@ -81,6 +81,9 @@ MENU_BAR_LIST = ( None, Actions.BLOCK_PARAM_MODIFY, ]), + (gtk.Action('View', '_View', None, None), [ + Actions.ERRORS_WINDOW_DISPLAY, + ]), (gtk.Action('Build', '_Build', None, None), [ Actions.FLOW_GRAPH_GEN, Actions.FLOW_GRAPH_EXEC, diff --git a/grc/gui/Dialogs.py b/grc/gui/Dialogs.py index 3cf617b92..a8e7afb05 100644 --- a/grc/gui/Dialogs.py +++ b/grc/gui/Dialogs.py @@ -57,6 +57,20 @@ def MessageDialogHelper(type, buttons, title=None, markup=None): message_dialog.destroy() return response + +ERRORS_MARKUP_TMPL="""\ +#for $i, $err_msg in enumerate($errors) +Error $i: +$encode($err_msg.replace('\t', ' ')) + +#end for""" +def ErrorsDialog(flowgraph): MessageDialogHelper( + type=gtk.MESSAGE_ERROR, + buttons=gtk.BUTTONS_CLOSE, + title='Flowgraph Errors', + markup=Utils.parse_template(ERRORS_MARKUP_TMPL, errors=flowgraph.get_error_messages()), +) + class AboutDialog(gtk.AboutDialog): """A cute little about dialog.""" -- cgit From d28d40d33dac481296e1f836ec57f1018041d418 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Fri, 18 Sep 2009 02:22:43 -0700 Subject: put the flow graph errors button into the toolbar --- grc/gui/Actions.py | 6 +++--- grc/gui/Bars.py | 1 + grc/gui/Dialogs.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index 1cc12a819..f374efde1 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -217,7 +217,7 @@ BLOCK_PASTE = Action( ) ERRORS_WINDOW_DISPLAY = Action( label='_Errors', - tooltip='Flowgraph error messages', + tooltip='View flow graph errors', stock_id=gtk.STOCK_DIALOG_ERROR, ) ABOUT_WINDOW_DISPLAY = Action( @@ -227,13 +227,13 @@ ABOUT_WINDOW_DISPLAY = Action( ) HELP_WINDOW_DISPLAY = Action( label='_Help', - tooltip='Usage Tips', + tooltip='Usage tips', stock_id=gtk.STOCK_HELP, keypresses=(gtk.keysyms.F1, NO_MODS_MASK), ) TYPES_WINDOW_DISPLAY = Action( label='_Types', - tooltip='Types Color Mapping', + tooltip='Types color mapping', stock_id=gtk.STOCK_DIALOG_INFO, ) FLOW_GRAPH_GEN = Action( diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py index 17835eb00..8fd167869 100644 --- a/grc/gui/Bars.py +++ b/grc/gui/Bars.py @@ -39,6 +39,7 @@ TOOLBAR_LIST = ( Actions.FLOW_GRAPH_UNDO, Actions.FLOW_GRAPH_REDO, None, + Actions.ERRORS_WINDOW_DISPLAY, Actions.FLOW_GRAPH_GEN, Actions.FLOW_GRAPH_EXEC, Actions.FLOW_GRAPH_KILL, diff --git a/grc/gui/Dialogs.py b/grc/gui/Dialogs.py index a8e7afb05..af40f47c0 100644 --- a/grc/gui/Dialogs.py +++ b/grc/gui/Dialogs.py @@ -67,7 +67,7 @@ $encode($err_msg.replace('\t', ' ')) def ErrorsDialog(flowgraph): MessageDialogHelper( type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_CLOSE, - title='Flowgraph Errors', + title='Flow Graph Errors', markup=Utils.parse_template(ERRORS_MARKUP_TMPL, errors=flowgraph.get_error_messages()), ) -- cgit From fd37328c778ea8014e9ea9d932e61e5d229dd012 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Fri, 25 Sep 2009 00:24:48 -0700 Subject: Added a run options to the "no gui" generate options. The user can select between run to completion and prompt for exit. Also fixed the props dialog is changed function to have better hashes. Now we hash a tuple of all "relevant" items which is "order aware". Since xoring the individual hashes proved faulty when 2 params alternated hiding. --- grc/gui/PropsDialog.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index a7822b228..cc84fd088 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -93,15 +93,14 @@ class PropsDialog(gtk.Dialog): Ex: Added, removed, type change, hide change... To the props dialog, the hide setting of 'none' and 'part' are identical. Therfore, the props dialog only cares if the hide setting is/not 'all'. - Make a hash that uniquely represents the params state. + Make a hash that uniquely represents the params' state. @return true if changed """ old_hash = self._hash - self._hash = 0 - for param in self._block.get_params(): - self._hash ^= hash(param) - self._hash ^= hash(param.get_type()) - self._hash ^= hash(param.get_hide() == 'all') + #create a tuple of things from each param that affects the params box + self._hash = hash(tuple([( + hash(param), param.get_type(), param.get_hide() == 'all', + ) for param in self._block.get_params()])) return self._hash != old_hash def _handle_changed(self, *args): -- cgit From 5d1ce94095bd2120a2fd2df087aae45085f53989 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Fri, 25 Sep 2009 12:27:29 -0700 Subject: Revert "Merge branch 'grc' of http://gnuradio.org/git/jblum" This reverts commit 06281feea16143ca97a77348f72e1c6dd0616c57. --- grc/gui/PropsDialog.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index cc84fd088..a7822b228 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -93,14 +93,15 @@ class PropsDialog(gtk.Dialog): Ex: Added, removed, type change, hide change... To the props dialog, the hide setting of 'none' and 'part' are identical. Therfore, the props dialog only cares if the hide setting is/not 'all'. - Make a hash that uniquely represents the params' state. + Make a hash that uniquely represents the params state. @return true if changed """ old_hash = self._hash - #create a tuple of things from each param that affects the params box - self._hash = hash(tuple([( - hash(param), param.get_type(), param.get_hide() == 'all', - ) for param in self._block.get_params()])) + self._hash = 0 + for param in self._block.get_params(): + self._hash ^= hash(param) + self._hash ^= hash(param.get_type()) + self._hash ^= hash(param.get_hide() == 'all') return self._hash != old_hash def _handle_changed(self, *args): -- cgit From 02a14678e889b09d41eaf903962733c47791c9cb Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Fri, 25 Sep 2009 00:24:48 -0700 Subject: Added a run options to the "no gui" generate options. The user can select between run to completion and prompt for exit. Also fixed the props dialog is changed function to have better hashes. Now we hash a tuple of all "relevant" items which is "order aware". Since xoring the individual hashes proved faulty when 2 params alternated hiding. (cherry picked from commit fd37328c778ea8014e9ea9d932e61e5d229dd012) --- grc/gui/PropsDialog.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index a7822b228..cc84fd088 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -93,15 +93,14 @@ class PropsDialog(gtk.Dialog): Ex: Added, removed, type change, hide change... To the props dialog, the hide setting of 'none' and 'part' are identical. Therfore, the props dialog only cares if the hide setting is/not 'all'. - Make a hash that uniquely represents the params state. + Make a hash that uniquely represents the params' state. @return true if changed """ old_hash = self._hash - self._hash = 0 - for param in self._block.get_params(): - self._hash ^= hash(param) - self._hash ^= hash(param.get_type()) - self._hash ^= hash(param.get_hide() == 'all') + #create a tuple of things from each param that affects the params box + self._hash = hash(tuple([( + hash(param), param.get_type(), param.get_hide() == 'all', + ) for param in self._block.get_params()])) return self._hash != old_hash def _handle_changed(self, *args): -- cgit From fcc492b0cdeca4163dd8df47cda00f9ec3a1a38c Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 7 Oct 2009 20:17:02 -0700 Subject: a working block tree search --- grc/gui/BlockTreeWindow.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'grc/gui') diff --git a/grc/gui/BlockTreeWindow.py b/grc/gui/BlockTreeWindow.py index 07b8ea7e0..6b7af4584 100644 --- a/grc/gui/BlockTreeWindow.py +++ b/grc/gui/BlockTreeWindow.py @@ -64,6 +64,9 @@ class BlockTreeWindow(gtk.VBox): renderer = gtk.CellRendererText() column = gtk.TreeViewColumn('Blocks', renderer, text=NAME_INDEX) self.treeview.append_column(column) + #setup the search + self.treeview.set_enable_search(True) + self.treeview.set_search_equal_func(self._handle_search) #try to enable the tooltips (available in pygtk 2.12 and above) try: self.treeview.set_tooltip_column(DOC_INDEX) except: pass @@ -145,6 +148,24 @@ class BlockTreeWindow(gtk.VBox): ############################################################ ## Event Handlers ############################################################ + def _handle_search(self, model, column, key, iter): + #determine what block keys match the search key + platform = self.get_flow_graph().get_parent() + block_keys = platform.get_block_keys() + matching_keys = filter(lambda k: key in k, block_keys) + if not matching_keys: return + #clear the old search category + try: self.treestore.remove(self._categories.pop((self._search_category, ))) + except AttributeError: pass + #create a search category + self._search_category = 'Search: %s'%key + for matching_key in matching_keys: + self.add_block(self._search_category, platform.get_block(matching_key)) + #expand the category + iter = self._categories[(self._search_category, )] + path = self.treestore.get_path(iter) + self.treeview.expand_row(path, open_all=False) + def _handle_drag_get_data(self, widget, drag_context, selection_data, info, time): """ Handle a drag and drop by setting the key to the selection object. -- cgit From 3a3cfaf293a46d83ffb905de04b43f3b1ff07d88 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 7 Oct 2009 20:31:04 -0700 Subject: tweaks to the search --- grc/gui/BlockTreeWindow.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/BlockTreeWindow.py b/grc/gui/BlockTreeWindow.py index 6b7af4584..c12120eaf 100644 --- a/grc/gui/BlockTreeWindow.py +++ b/grc/gui/BlockTreeWindow.py @@ -149,21 +149,19 @@ class BlockTreeWindow(gtk.VBox): ## Event Handlers ############################################################ def _handle_search(self, model, column, key, iter): - #determine what block keys match the search key - platform = self.get_flow_graph().get_parent() - block_keys = platform.get_block_keys() - matching_keys = filter(lambda k: key in k, block_keys) - if not matching_keys: return - #clear the old search category + #determine which blocks match the search key + blocks = self.get_flow_graph().get_parent().get_blocks() + matching_blocks = filter(lambda b: key in b.get_key() or key in b.get_name().lower(), blocks) + #remove the old search category try: self.treestore.remove(self._categories.pop((self._search_category, ))) - except AttributeError: pass + except (KeyError, AttributeError): pass #nothing to remove #create a search category + if not matching_blocks: return self._search_category = 'Search: %s'%key - for matching_key in matching_keys: - self.add_block(self._search_category, platform.get_block(matching_key)) - #expand the category - iter = self._categories[(self._search_category, )] - path = self.treestore.get_path(iter) + for block in matching_blocks: self.add_block(self._search_category, block) + #expand the search category + path = self.treestore.get_path(self._categories[(self._search_category, )]) + self.treeview.collapse_all() self.treeview.expand_row(path, open_all=False) def _handle_drag_get_data(self, widget, drag_context, selection_data, info, time): -- cgit From 175c074ba74143d5af530e5cc4bd50335f64b1d5 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 28 Oct 2009 14:33:52 -0700 Subject: Created a grc_blockdir in makefile.common. Switched the grc src prefix in grc makefiles. Removed grc/Makefile.inc as it was no longer neededed. --- grc/gui/Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Makefile.am b/grc/gui/Makefile.am index b14817d04..a73b6855d 100644 --- a/grc/gui/Makefile.am +++ b/grc/gui/Makefile.am @@ -19,9 +19,9 @@ # Boston, MA 02110-1301, USA. # -include $(top_srcdir)/grc/Makefile.inc +include $(top_srcdir)/Makefile.common -ourpythondir = $(grc_src_prefix)/gui +ourpythondir = $(pkgpythondir)/grc/gui ourpython_PYTHON = \ Block.py \ Colors.py \ -- cgit From 8efb3fce53717a1f219e45c78fa6472c8db24d2d Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Thu, 19 Nov 2009 21:58:54 -0800 Subject: gtk.Tooltips() is deprecated, use set_tooltip_text. Added a set_tooltop_text method to each InputParam. --- grc/gui/Param.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Param.py b/grc/gui/Param.py index 4464a57ab..cb8bfdc52 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -38,6 +38,7 @@ class InputParam(gtk.HBox): #connect events self.connect('show', self._update_gui) def set_color(self, color): pass + def set_tooltip_text(self, text): pass def _update_gui(self, *args): """ @@ -51,8 +52,7 @@ class InputParam(gtk.HBox): #set the color self.set_color(self.param.get_color()) #set the tooltip - if self.tp: self.tp.set_tip( - self.entry, + self.set_tooltip_text( Utils.parse_template(TIP_MARKUP_TMPL, param=self.param).strip(), ) #show/hide @@ -77,16 +77,13 @@ class EntryParam(InputParam): def __init__(self, *args, **kwargs): InputParam.__init__(self, *args, **kwargs) - self.entry = input = gtk.Entry() - input.set_text(self.param.get_value()) - input.connect('changed', self._handle_changed) - self.pack_start(input, True) - self.get_text = input.get_text - #tool tip - self.tp = gtk.Tooltips() - self.tp.set_tip(self.entry, '') - self.tp.enable() - def set_color(self, color): self.entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) + self._input = gtk.Entry() + self._input.set_text(self.param.get_value()) + self._input.connect('changed', self._handle_changed) + self.pack_start(self._input, True) + def get_text(self): return self._input.get_text() + def set_color(self, color): self._input.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) + def set_tooltip_text(self, text): self._input.set_tooltip_text(text) class EnumParam(InputParam): """Provide an entry box for Enum types with a drop down menu.""" @@ -99,6 +96,7 @@ class EnumParam(InputParam): self._input.connect('changed', self._handle_changed) self.pack_start(self._input, False) def get_text(self): return self.param.get_option_keys()[self._input.get_active()] + def set_tooltip_text(self, text): self._input.set_tooltip_text(text) class EnumEntryParam(InputParam): """Provide an entry box and drop down menu for Raw Enum types.""" @@ -117,6 +115,10 @@ class EnumEntryParam(InputParam): def get_text(self): if self._input.get_active() == -1: return self._input.get_child().get_text() return self.param.get_option_keys()[self._input.get_active()] + def set_tooltip_text(self, text): + if self._input.get_active() == -1: #custom entry + self._input.get_child().set_tooltip_text(text) + else: self._input.set_tooltip_text(text) def set_color(self, color): if self._input.get_active() == -1: #custom entry, use color self._input.get_child().modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) -- cgit From 87decb3b420e88bfa0d57b328b2b7404de4a61ba Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sat, 5 Dec 2009 11:06:08 -0500 Subject: combined param layouts --- grc/gui/Block.py | 8 ++++++-- grc/gui/Param.py | 10 ++++------ 2 files changed, 10 insertions(+), 8 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Block.py b/grc/gui/Block.py index 8c65bf06f..bed50de9f 100644 --- a/grc/gui/Block.py +++ b/grc/gui/Block.py @@ -29,6 +29,7 @@ from Constants import \ import pygtk pygtk.require('2.0') import gtk +import pango BLOCK_MARKUP_TMPL="""\ #set $foreground = $block.is_valid() and 'black' or 'red' @@ -130,8 +131,11 @@ class Block(Element): layout.set_markup(Utils.parse_template(BLOCK_MARKUP_TMPL, block=self)) self.label_width, self.label_height = layout.get_pixel_size() #display the params - for param in filter(lambda p: p.get_hide() not in ('all', 'part'), self.get_params()): - layout = param.get_layout() + markups = [param.get_markup() for param in self.get_params() if param.get_hide() not in ('all', 'part')] + if markups: + layout = gtk.DrawingArea().create_pango_layout('') + layout.set_spacing(LABEL_SEPARATION*pango.SCALE) + layout.set_markup('\n'.join(markups)) layouts.append(layout) w,h = layout.get_pixel_size() self.label_width = max(w, self.label_width) diff --git a/grc/gui/Param.py b/grc/gui/Param.py index cb8bfdc52..b3018dab2 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -165,11 +165,9 @@ class Param(Element): if self.get_options(): return EnumEntryParam(self, *args, **kwargs) return EntryParam(self, *args, **kwargs) - def get_layout(self): + def get_markup(self): """ - Create a layout based on the current markup. - @return the pango layout + Get the markup for this param. + @return a pango markup string """ - layout = gtk.DrawingArea().create_pango_layout('') - layout.set_markup(Utils.parse_template(PARAM_MARKUP_TMPL, param=self)) - return layout + return Utils.parse_template(PARAM_MARKUP_TMPL, param=self) -- cgit From 8122329533a6186f45f8cf3211c7ef9fda37bd19 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sat, 5 Dec 2009 17:44:12 -0500 Subject: Created a pixmap rotation routine in Utils for handling those rotated labels. The rotation is now performed by the gtk pixbuf class and not manually in python. In addition, the block and port classes now draw from pixmaps and not from images. Pixmaps are server-side objects, where images are client-side (meaning: possible speed-up). --- grc/gui/Block.py | 13 ++++++------- grc/gui/Port.py | 13 ++++++------- grc/gui/Utils.py | 23 +++++++++++++++++++++++ 3 files changed, 35 insertions(+), 14 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Block.py b/grc/gui/Block.py index bed50de9f..27143e070 100644 --- a/grc/gui/Block.py +++ b/grc/gui/Block.py @@ -155,12 +155,11 @@ class Block(Element): else: w_off = 0 pixmap.draw_layout(gc, w_off, h_off, layout) h_off = h + h_off + LABEL_SEPARATION - #create vertical and horizontal images - self.horizontal_label = image = pixmap.get_image(0, 0, width, height) + #create vertical and horizontal pixmaps + self.horizontal_label = pixmap if self.is_vertical(): - self.vertical_label = vimage = gtk.gdk.Image(gtk.gdk.IMAGE_NORMAL, pixmap.get_visual(), height, width) - for i in range(width): - for j in range(height): vimage.put_pixel(j, width-i-1, image.get_pixel(i, j)) + self.vertical_label = self.get_parent().new_pixmap(height, width) + Utils.rotate_pixmap(gc, self.horizontal_label, self.vertical_label) #calculate width and height needed self.W = self.label_width + 2*BLOCK_LABEL_PADDING self.H = max(*( @@ -183,9 +182,9 @@ class Block(Element): ) #draw label image if self.is_horizontal(): - window.draw_image(gc, self.horizontal_label, 0, 0, x+BLOCK_LABEL_PADDING, y+(self.H-self.label_height)/2, -1, -1) + window.draw_drawable(gc, self.horizontal_label, 0, 0, x+BLOCK_LABEL_PADDING, y+(self.H-self.label_height)/2, -1, -1) elif self.is_vertical(): - window.draw_image(gc, self.vertical_label, 0, 0, x+(self.H-self.label_height)/2, y+BLOCK_LABEL_PADDING, -1, -1) + window.draw_drawable(gc, self.vertical_label, 0, 0, x+(self.H-self.label_height)/2, y+BLOCK_LABEL_PADDING, -1, -1) #draw ports for port in self.get_ports(): port.draw(gc, window) diff --git a/grc/gui/Port.py b/grc/gui/Port.py index 6763f6cbd..2896d04c1 100644 --- a/grc/gui/Port.py +++ b/grc/gui/Port.py @@ -97,12 +97,11 @@ class Port(Element): gc.set_foreground(self._bg_color) pixmap.draw_rectangle(gc, True, 0, 0, self.w, self.h) pixmap.draw_layout(gc, 0, 0, layout) - #create the images - self.horizontal_label = image = pixmap.get_image(0, 0, self.w, self.h) + #create vertical and horizontal pixmaps + self.horizontal_label = pixmap if self.is_vertical(): - self.vertical_label = vimage = gtk.gdk.Image(gtk.gdk.IMAGE_NORMAL, pixmap.get_visual(), self.h, self.w) - for i in range(self.w): - for j in range(self.h): vimage.put_pixel(j, self.w-i-1, image.get_pixel(i, j)) + self.vertical_label = self.get_parent().get_parent().new_pixmap(self.h, self.w) + Utils.rotate_pixmap(gc, self.horizontal_label, self.vertical_label) def draw(self, gc, window): """ @@ -117,9 +116,9 @@ class Port(Element): X,Y = self.get_coordinate() (x,y),(w,h) = self._areas_list[0] #use the first area's sizes to place the labels if self.is_horizontal(): - window.draw_image(gc, self.horizontal_label, 0, 0, x+X+(self.W-self.w)/2, y+Y+(self.H-self.h)/2, -1, -1) + window.draw_drawable(gc, self.horizontal_label, 0, 0, x+X+(self.W-self.w)/2, y+Y+(self.H-self.h)/2, -1, -1) elif self.is_vertical(): - window.draw_image(gc, self.vertical_label, 0, 0, x+X+(self.H-self.h)/2, y+Y+(self.W-self.w)/2, -1, -1) + window.draw_drawable(gc, self.vertical_label, 0, 0, x+X+(self.H-self.h)/2, y+Y+(self.W-self.w)/2, -1, -1) def get_connector_coordinate(self): """ diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py index 83036a4b8..b5489d56e 100644 --- a/grc/gui/Utils.py +++ b/grc/gui/Utils.py @@ -19,8 +19,31 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from Constants import POSSIBLE_ROTATIONS from Cheetah.Template import Template +import pygtk +pygtk.require('2.0') +import gtk import gobject +def rotate_pixmap(gc, src_pixmap, dst_pixmap, angle=gtk.gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE): + """ + Load the destination pixmap with a rotated version of the source pixmap. + The source pixmap will be loaded into a pixbuf, rotated, and drawn to the destination pixmap. + The pixbuf is a client-side drawable, where a pixmap is a server-side drawable. + @param gc the graphics context + @param src_pixmap the source pixmap + @param dst_pixmap the destination pixmap + @param angle the angle to rotate by + """ + width, height = src_pixmap.get_size() + pixbuf = gtk.gdk.Pixbuf( + colorspace=gtk.gdk.COLORSPACE_RGB, + has_alpha=False, bits_per_sample=8, + width=width, height=height, + ) + pixbuf.get_from_drawable(src_pixmap, src_pixmap.get_colormap(), 0, 0, 0, 0, -1, -1) + pixbuf = pixbuf.rotate_simple(angle) + dst_pixmap.draw_pixbuf(gc, pixbuf, 0, 0, 0, 0) + def get_rotated_coordinate(coor, rotation): """ Rotate the coordinate by the given rotation. -- cgit From 61830989ce554e6dfac41bba2ced7006c424e0bc Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sun, 6 Dec 2009 23:18:27 -0500 Subject: removed unused import statements, thanks pyflakes --- grc/gui/ActionHandler.py | 1 - grc/gui/Dialogs.py | 1 - grc/gui/Element.py | 3 --- grc/gui/FlowGraph.py | 1 - 4 files changed, 6 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index ee3e19a6c..108e23a23 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -29,7 +29,6 @@ import Preferences from threading import Thread import Messages from .. base import ParseXML -import random from MainWindow import MainWindow from PropsDialog import PropsDialog import Dialogs diff --git a/grc/gui/Dialogs.py b/grc/gui/Dialogs.py index af40f47c0..473c796af 100644 --- a/grc/gui/Dialogs.py +++ b/grc/gui/Dialogs.py @@ -20,7 +20,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA import pygtk pygtk.require('2.0') import gtk -import Preferences import Utils class TextDisplay(gtk.TextView): diff --git a/grc/gui/Element.py b/grc/gui/Element.py index f0518ee12..e020c5caa 100644 --- a/grc/gui/Element.py +++ b/grc/gui/Element.py @@ -17,9 +17,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -import pygtk -pygtk.require('2.0') -import gtk from Constants import LINE_SELECT_SENSITIVITY from Constants import POSSIBLE_ROTATIONS diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index 8feb171f1..5adecccc1 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -22,7 +22,6 @@ import Actions import Colors import Utils from Element import Element -from .. base import FlowGraph as _FlowGraph import pygtk pygtk.require('2.0') import gtk -- cgit From d844c4f06dc10c8499eb2b8f1cb5b55f55a5b48d Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Tue, 9 Mar 2010 12:40:43 -0800 Subject: added a right click context menu for the flow graph elements --- grc/gui/DrawingArea.py | 12 +++++++----- grc/gui/FlowGraph.py | 41 ++++++++++++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 12 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/DrawingArea.py b/grc/gui/DrawingArea.py index b70468ed0..790df7c3f 100644 --- a/grc/gui/DrawingArea.py +++ b/grc/gui/DrawingArea.py @@ -1,5 +1,5 @@ """ -Copyright 2007, 2008, 2009 Free Software Foundation, Inc. +Copyright 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -82,19 +82,21 @@ class DrawingArea(gtk.DrawingArea): Forward button click information to the flow graph. """ self.ctrl_mask = event.state & gtk.gdk.CONTROL_MASK - self._flow_graph.handle_mouse_button_press( - left_click=(event.button == 1), + if event.button == 1: self._flow_graph.handle_mouse_selector_press( double_click=(event.type == gtk.gdk._2BUTTON_PRESS), coordinate=(event.x, event.y), ) + if event.button == 3: self._flow_graph.handle_mouse_context_press( + coordinate=(event.x, event.y), + event=event, + ) def _handle_mouse_button_release(self, widget, event): """ Forward button release information to the flow graph. """ self.ctrl_mask = event.state & gtk.gdk.CONTROL_MASK - self._flow_graph.handle_mouse_button_release( - left_click=(event.button == 1), + if event.button == 1: self._flow_graph.handle_mouse_selector_release( coordinate=(event.x, event.y), ) diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index 5adecccc1..897145a1d 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -1,5 +1,5 @@ """ -Copyright 2007, 2008, 2009 Free Software Foundation, Inc. +Copyright 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -51,6 +51,19 @@ class FlowGraph(Element): #selected ports self._old_selected_port = None self._new_selected_port = None + #context menu + self._context_menu = gtk.Menu() + for action in [ + Actions.BLOCK_CUT, + Actions.BLOCK_COPY, + Actions.BLOCK_PASTE, + Actions.ELEMENT_DELETE, + Actions.BLOCK_ROTATE_CCW, + Actions.BLOCK_ROTATE_CW, + Actions.BLOCK_ENABLE, + Actions.BLOCK_DISABLE, + Actions.BLOCK_PARAM_MODIFY, + ]: self._context_menu.append(action.create_menu_item()) ########################################################################### # Access Drawing Area @@ -425,14 +438,27 @@ class FlowGraph(Element): ########################################################################## ## Event Handlers ########################################################################## - def handle_mouse_button_press(self, left_click, double_click, coordinate): + def handle_mouse_context_press(self, coordinate, event): """ - A mouse button is pressed, only respond to left clicks. + The context mouse button was pressed: + If no elements were selected, perform re-selection at this coordinate. + Then, show the context menu at the mouse click location. + """ + selections = self.what_is_selected(coordinate) + if not set(selections).intersection(self.get_selected_elements()): + self.set_coordinate(coordinate) + self.mouse_pressed = True + self.update_selected_elements() + self.mouse_pressed = False + self._context_menu.popup(None, None, None, event.button, event.time) + + def handle_mouse_selector_press(self, double_click, coordinate): + """ + The selector mouse button was pressed: Find the selected element. Attempt a new connection if possible. Open the block params window on a double click. Update the selection state of the flow graph. """ - if not left_click: return self.press_coor = coordinate self.set_coordinate(coordinate) self.time = 0 @@ -444,11 +470,12 @@ class FlowGraph(Element): self.mouse_pressed = False Actions.BLOCK_PARAM_MODIFY() - def handle_mouse_button_release(self, left_click, coordinate): + def handle_mouse_selector_release(self, coordinate): """ - A mouse button is released, record the state. + The selector mouse button was released: + Update the state, handle motion (dragging). + And update the selected flowgraph elements. """ - if not left_click: return self.set_coordinate(coordinate) self.time = 0 self.mouse_pressed = False -- cgit From 96a20bb09dc6b07b3d2651645e579dff6c3f3a45 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Tue, 23 Mar 2010 23:39:05 -0700 Subject: work on the string representations for parameters (large vectors could be too much to render, ie use truncation) --- grc/gui/Param.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Param.py b/grc/gui/Param.py index b3018dab2..7c00c1b67 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -1,5 +1,5 @@ """ -Copyright 2007, 2008, 2009 Free Software Foundation, Inc. +Copyright 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -135,10 +135,21 @@ PARAM_LABEL_MARKUP_TMPL="""\ $encode($param.get_name())""" TIP_MARKUP_TMPL="""\ +######################################## +#def truncate(string) + #set $max_len = 100 + #set $string = str($string) + #if len($string) > $max_len +$('%s...%s'%($string[:$max_len/2], $string[-$max_len/2:]))#slurp + #else +$string#slurp + #end if +#end def +######################################## Key: $param.get_key() Type: $param.get_type() #if $param.is_valid() -Value: $param.get_evaluated() +Value: $truncate($param.get_evaluated()) #elif len($param.get_error_messages()) == 1 Error: $(param.get_error_messages()[0]) #else -- cgit From 21158d425ff8d8b1ba8bbc6a89609b33fb4252b4 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sun, 13 Mar 2011 11:36:03 -0700 Subject: grc: swap store the subprocess object rather than the pid when executing For some reason os.kill(p.pid, SIGKILL) does not work on windows. However, the subprocess p.kill() works just fine for both systems. --- grc/gui/ActionHandler.py | 20 ++++++++++---------- grc/gui/MainWindow.py | 6 +++--- grc/gui/NotebookPage.py | 20 ++++++++++---------- 3 files changed, 23 insertions(+), 23 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 108e23a23..350b297bb 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -1,5 +1,5 @@ """ -Copyright 2007, 2008, 2009 Free Software Foundation, Inc. +Copyright 2007, 2008, 2009, 2011 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -282,7 +282,7 @@ class ActionHandler: # Gen/Exec/Stop ################################################## elif action == Actions.FLOW_GRAPH_GEN: - if not self.get_page().get_pid(): + if not self.get_page().get_proc(): if not self.get_page().get_saved() or not self.get_page().get_file_path(): Actions.FLOW_GRAPH_SAVE() #only save if file path missing or not saved if self.get_page().get_saved() and self.get_page().get_file_path(): @@ -293,14 +293,14 @@ class ActionHandler: except Exception,e: Messages.send_fail_gen(e) else: self.generator = None elif action == Actions.FLOW_GRAPH_EXEC: - if not self.get_page().get_pid(): + if not self.get_page().get_proc(): Actions.FLOW_GRAPH_GEN() if self.get_page().get_saved() and self.get_page().get_file_path(): ExecFlowGraphThread(self) elif action == Actions.FLOW_GRAPH_KILL: - if self.get_page().get_pid(): - try: os.kill(self.get_page().get_pid(), signal.SIGKILL) - except: print "could not kill pid: %s"%self.get_page().get_pid() + if self.get_page().get_proc(): + try: self.get_page().get_proc().kill() + except: print "could not kill process: %d"%self.get_page().get_proc().pid elif action == Actions.PAGE_CHANGE: #pass and run the global actions pass else: print '!!! Action "%s" not handled !!!'%action @@ -340,10 +340,10 @@ class ActionHandler: Update the exec and stop buttons. Lock and unlock the mutex for race conditions with exec flow graph threads. """ - sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid() + sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_proc() Actions.FLOW_GRAPH_GEN.set_sensitive(sensitive) Actions.FLOW_GRAPH_EXEC.set_sensitive(sensitive) - Actions.FLOW_GRAPH_KILL.set_sensitive(self.get_page().get_pid() != None) + Actions.FLOW_GRAPH_KILL.set_sensitive(self.get_page().get_proc() != None) class ExecFlowGraphThread(Thread): """Execute the flow graph as a new process and wait on it to finish.""" @@ -362,7 +362,7 @@ class ExecFlowGraphThread(Thread): #get the popen try: self.p = self.page.get_generator().get_popen() - self.page.set_pid(self.p.pid) + self.page.set_proc(self.p) #update self.update_exec_stop() self.start() @@ -385,5 +385,5 @@ class ExecFlowGraphThread(Thread): def done(self): """Perform end of execution tasks.""" Messages.send_end_exec() - self.page.set_pid(None) + self.page.set_proc(None) self.update_exec_stop() diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py index 9fcbe2a6c..2f761df1f 100644 --- a/grc/gui/MainWindow.py +++ b/grc/gui/MainWindow.py @@ -1,5 +1,5 @@ """ -Copyright 2008, 2009 Free Software Foundation, Inc. +Copyright 2008, 2009, 2011 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -216,7 +216,7 @@ class MainWindow(gtk.Window): """ if not self.page_to_be_closed: self.page_to_be_closed = self.get_page() #show the page if it has an executing flow graph or is unsaved - if self.page_to_be_closed.get_pid() or not self.page_to_be_closed.get_saved(): + if self.page_to_be_closed.get_proc() or not self.page_to_be_closed.get_saved(): self._set_page(self.page_to_be_closed) #unsaved? ask the user if not self.page_to_be_closed.get_saved() and self._save_changes(): @@ -225,7 +225,7 @@ class MainWindow(gtk.Window): self.page_to_be_closed = None #set the page to be closed back to None return #stop the flow graph if executing - if self.page_to_be_closed.get_pid(): Actions.FLOW_GRAPH_KILL() + if self.page_to_be_closed.get_proc(): Actions.FLOW_GRAPH_KILL() #remove the page self.notebook.remove_page(self.notebook.page_num(self.page_to_be_closed)) if ensure and self.notebook.get_n_pages() == 0: self.new_page() #no pages, make a new one diff --git a/grc/gui/NotebookPage.py b/grc/gui/NotebookPage.py index fddfeaf5f..86b6f1513 100644 --- a/grc/gui/NotebookPage.py +++ b/grc/gui/NotebookPage.py @@ -1,5 +1,5 @@ """ -Copyright 2008, 2009 Free Software Foundation, Inc. +Copyright 2008, 2009, 2011 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -40,7 +40,7 @@ class NotebookPage(gtk.HBox): @param file_path path to a flow graph file """ self._flow_graph = flow_graph - self.set_pid(None) + self.set_proc(None) #import the file self.main_window = main_window self.set_file_path(file_path) @@ -119,19 +119,19 @@ class NotebookPage(gtk.HBox): """ return self.tab - def get_pid(self): + def get_proc(self): """ - Get the pid for the flow graph. - @return the pid number + Get the subprocess for the flow graph. + @return the subprocess object """ - return self.pid + return self.process - def set_pid(self, pid): + def set_proc(self, process): """ - Set the pid number. - @param pid the new pid number + Set the subprocess object. + @param process the new subprocess """ - self.pid = pid + self.process = process def get_flow_graph(self): """ -- cgit From af1d0a61d01c7c17dedcb5388ed8a077213d4b4f Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Thu, 14 Apr 2011 10:49:23 -0700 Subject: grc: replaced asserts in gui subdirectory --- grc/gui/ActionHandler.py | 5 ++--- grc/gui/Actions.py | 5 +++-- grc/gui/FlowGraph.py | 10 +++++----- grc/gui/Utils.py | 5 +++-- 4 files changed, 13 insertions(+), 12 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 350b297bb..15785f2ee 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -1,5 +1,5 @@ """ -Copyright 2007, 2008, 2009, 2011 Free Software Foundation, Inc. +Copyright 2007-2011 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -78,8 +78,7 @@ class ActionHandler: When not in focus, gtk and the accelerators handle the the key press. @return false to let gtk handle the key action """ - try: assert self.get_focus_flag() - except AssertionError: return False + if not self.get_focus_flag(): return False return Actions.handle_key_press(event) def _quit(self, window, event): diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index f374efde1..4d196477e 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -1,5 +1,5 @@ """ -Copyright 2007, 2008, 2009 Free Software Foundation, Inc. +Copyright 2007-2011 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -76,7 +76,8 @@ class Action(gtk.Action): for i in range(len(keypresses)/2): keyval, mod_mask = keypresses[i*2:(i+1)*2] #register this keypress - assert not _actions_keypress_dict.has_key((keyval, mod_mask)) + if _actions_keypress_dict.has_key((keyval, mod_mask)): + raise KeyError('keyval/mod_mask pair already registered "%s"'%str((keyval, mod_mask))) _actions_keypress_dict[(keyval, mod_mask)] = self #set the accelerator group, and accelerator path #register the key name and mod mask with the accelerator path diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index 897145a1d..9f3326ada 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -1,5 +1,5 @@ """ -Copyright 2007, 2008, 2009, 2010 Free Software Foundation, Inc. +Copyright 2007-2011 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -290,10 +290,10 @@ class FlowGraph(Element): for selected in selected_elements: if selected in elements: continue selected_elements.remove(selected) - try: assert self._old_selected_port.get_parent() in elements - except: self._old_selected_port = None - try: assert self._new_selected_port.get_parent() in elements - except: self._new_selected_port = None + if self._old_selected_port and self._old_selected_port.get_parent() not in elements: + self._old_selected_port = None + if self._new_selected_port and self._new_selected_port.get_parent() not in elements: + self._new_selected_port = None #update highlighting for element in elements: element.set_highlighted(element in selected_elements) diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py index b5489d56e..bb19ed3d9 100644 --- a/grc/gui/Utils.py +++ b/grc/gui/Utils.py @@ -1,5 +1,5 @@ """ -Copyright 2008, 2009 Free Software Foundation, Inc. +Copyright 2008-2011 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -53,7 +53,8 @@ def get_rotated_coordinate(coor, rotation): """ #handles negative angles rotation = (rotation + 360)%360 - assert rotation in POSSIBLE_ROTATIONS + if rotation not in POSSIBLE_ROTATIONS: + raise ValueError('unusable rotation angle "%s"'%str(rotation)) #determine the number of degrees to rotate cos_r, sin_r = { 0: (1, 0), -- cgit From accb9f2fe8fd8f6a1e114adac5b15304b0e0012d Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 20 Jul 2011 19:04:32 -0700 Subject: gr: squashed cmakelists.txt into one commit --- grc/gui/CMakeLists.txt | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 grc/gui/CMakeLists.txt (limited to 'grc/gui') diff --git a/grc/gui/CMakeLists.txt b/grc/gui/CMakeLists.txt new file mode 100644 index 000000000..c2eb16e9f --- /dev/null +++ b/grc/gui/CMakeLists.txt @@ -0,0 +1,48 @@ +# Copyright 2011 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. + +######################################################################## +GR_PYTHON_INSTALL(FILES + Block.py + Colors.py + Constants.py + Connection.py + Element.py + FlowGraph.py + Param.py + Platform.py + Port.py + Utils.py + ActionHandler.py + Actions.py + Bars.py + BlockTreeWindow.py + Dialogs.py + DrawingArea.py + FileDialogs.py + MainWindow.py + Messages.py + NotebookPage.py + PropsDialog.py + Preferences.py + StateCache.py + __init__.py + DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/gui + COMPONENT "grc" +) -- cgit From c2bf4895ca65c9c3f7efaf24c64bc8dae5716830 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Mon, 21 Nov 2011 22:23:51 -0800 Subject: grc: force param text to black to guarantee readability --- grc/gui/Colors.py | 3 +++ grc/gui/Param.py | 13 +++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Colors.py b/grc/gui/Colors.py index f0b989b37..c79dadee3 100644 --- a/grc/gui/Colors.py +++ b/grc/gui/Colors.py @@ -26,6 +26,9 @@ def get_color(color_code): return _COLORMAP.alloc_color(color_code, True, True) HIGHLIGHT_COLOR = get_color('#00FFFF') BORDER_COLOR = get_color('black') +#param entry boxes +PARAM_ENTRY_TEXT_COLOR = get_color('black') +ENTRYENUM_CUSTOM_COLOR = get_color('#EEEEEE') #flow graph color constants FLOWGRAPH_BACKGROUND_COLOR = get_color('#FFF9FF') #block color constants diff --git a/grc/gui/Param.py b/grc/gui/Param.py index 7c00c1b67..cb6c663e3 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -1,5 +1,5 @@ """ -Copyright 2007, 2008, 2009, 2010 Free Software Foundation, Inc. +Copyright 2007-2011 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -22,6 +22,7 @@ from Element import Element import pygtk pygtk.require('2.0') import gtk +import Colors class InputParam(gtk.HBox): """The base class for an input parameter inside the input parameters dialog.""" @@ -82,7 +83,9 @@ class EntryParam(InputParam): self._input.connect('changed', self._handle_changed) self.pack_start(self._input, True) def get_text(self): return self._input.get_text() - def set_color(self, color): self._input.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) + def set_color(self, color): + self._input.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) + self._input.modify_text(gtk.STATE_NORMAL, Colors.PARAM_ENTRY_TEXT_COLOR) def set_tooltip_text(self, text): self._input.set_tooltip_text(text) class EnumParam(InputParam): @@ -122,8 +125,10 @@ class EnumEntryParam(InputParam): def set_color(self, color): if self._input.get_active() == -1: #custom entry, use color self._input.get_child().modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) - else: #from enum, make white background - self._input.get_child().modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse('#ffffff')) + self._input.get_child().modify_text(gtk.STATE_NORMAL, Colors.PARAM_ENTRY_TEXT_COLOR) + else: #from enum, make pale background + self._input.get_child().modify_base(gtk.STATE_NORMAL, Colors.ENTRYENUM_CUSTOM_COLOR) + self._input.get_child().modify_text(gtk.STATE_NORMAL, Colors.PARAM_ENTRY_TEXT_COLOR) PARAM_MARKUP_TMPL="""\ #set $foreground = $param.is_valid() and 'black' or 'red' -- cgit From 9a3f3caf81837ff088491b61ebe8a38277cb1d78 Mon Sep 17 00:00:00 2001 From: Timo Juhani Lindfors Date: Wed, 7 Dec 2011 15:09:38 -0800 Subject: grc: fix typos --- grc/gui/Messages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/Messages.py b/grc/gui/Messages.py index 80057e0ba..e98e897aa 100644 --- a/grc/gui/Messages.py +++ b/grc/gui/Messages.py @@ -62,7 +62,7 @@ def send_end_load(): def send_fail_load(error): send('Error: %s\n'%error) - send('>>> Failue\n') + send('>>> Failure\n') traceback.print_exc() ################# functions for generating flow graphs ######################################## @@ -71,7 +71,7 @@ def send_start_gen(file_path): def send_fail_gen(error): send('Generate Error: %s\n'%error) - send('>>> Failue\n') + send('>>> Failure\n') traceback.print_exc() ################# functions for executing flow graphs ######################################## -- cgit From 00420d32081d8252bb37142b2be19a8a7c4dc4c4 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Thu, 8 Dec 2011 13:48:48 -0800 Subject: Removed autotools, gr-waveform, some cleanup Nick Foster owes Nick Corgan a six-pack of beer! --- grc/gui/.gitignore | 2 -- grc/gui/Makefile.am | 49 ------------------------------------------------- 2 files changed, 51 deletions(-) delete mode 100644 grc/gui/.gitignore delete mode 100644 grc/gui/Makefile.am (limited to 'grc/gui') diff --git a/grc/gui/.gitignore b/grc/gui/.gitignore deleted file mode 100644 index b336cc7ce..000000000 --- a/grc/gui/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/Makefile -/Makefile.in diff --git a/grc/gui/Makefile.am b/grc/gui/Makefile.am deleted file mode 100644 index a73b6855d..000000000 --- a/grc/gui/Makefile.am +++ /dev/null @@ -1,49 +0,0 @@ -# -# Copyright 2008, 2009 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -include $(top_srcdir)/Makefile.common - -ourpythondir = $(pkgpythondir)/grc/gui -ourpython_PYTHON = \ - Block.py \ - Colors.py \ - Constants.py \ - Connection.py \ - Element.py \ - FlowGraph.py \ - Param.py \ - Platform.py \ - Port.py \ - Utils.py \ - ActionHandler.py \ - Actions.py \ - Bars.py \ - BlockTreeWindow.py \ - Dialogs.py \ - DrawingArea.py \ - FileDialogs.py \ - MainWindow.py \ - Messages.py \ - NotebookPage.py \ - PropsDialog.py \ - Preferences.py \ - StateCache.py \ - __init__.py -- cgit From f919f9dcbb54a08e6e26d6c229ce92fb784fa1b2 Mon Sep 17 00:00:00 2001 From: Tom Rondeau Date: Fri, 13 Apr 2012 18:36:53 -0400 Subject: Removed whitespace and added dtools/bin/remove-whitespace as a tool to do this in the future. The sed script was provided by Moritz Fischer. --- grc/gui/ActionHandler.py | 2 +- grc/gui/BlockTreeWindow.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 15785f2ee..476c82b4f 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -18,7 +18,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ import os -import signal +import signal from Constants import IMAGE_FILE_EXTENSION import Actions import pygtk diff --git a/grc/gui/BlockTreeWindow.py b/grc/gui/BlockTreeWindow.py index c12120eaf..0175c8bec 100644 --- a/grc/gui/BlockTreeWindow.py +++ b/grc/gui/BlockTreeWindow.py @@ -67,7 +67,7 @@ class BlockTreeWindow(gtk.VBox): #setup the search self.treeview.set_enable_search(True) self.treeview.set_search_equal_func(self._handle_search) - #try to enable the tooltips (available in pygtk 2.12 and above) + #try to enable the tooltips (available in pygtk 2.12 and above) try: self.treeview.set_tooltip_column(DOC_INDEX) except: pass #setup drag and drop -- cgit From a07fe1904412af78b3d70a6225e6efe10c9efbe5 Mon Sep 17 00:00:00 2001 From: Tim O'Shea Date: Tue, 25 Sep 2012 11:40:10 -0400 Subject: adding GRC Reload Block XML action --- grc/gui/ActionHandler.py | 6 ++++++ grc/gui/Actions.py | 5 +++++ grc/gui/Bars.py | 2 ++ grc/gui/BlockTreeWindow.py | 5 +++++ grc/gui/MainWindow.py | 3 ++- 5 files changed, 20 insertions(+), 1 deletion(-) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 476c82b4f..5600edc06 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -53,6 +53,7 @@ class ActionHandler: self.clipboard = None for action in Actions.get_all_actions(): action.connect('activate', self._handle_action) #setup the main window + self.platform = platform; self.main_window = MainWindow(platform) self.main_window.connect('delete-event', self._quit) self.main_window.connect('key-press-event', self._handle_key_press) @@ -302,6 +303,10 @@ class ActionHandler: except: print "could not kill process: %d"%self.get_page().get_proc().pid elif action == Actions.PAGE_CHANGE: #pass and run the global actions pass + elif action == Actions.RELOAD_BLOCKS: + self.platform.loadblocks() + self.main_window.btwin.clear(); + self.platform.load_block_tree(self.main_window.btwin); else: print '!!! Action "%s" not handled !!!'%action ################################################## # Global Actions for all States @@ -319,6 +324,7 @@ class ActionHandler: #update enable/disable Actions.BLOCK_ENABLE.set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) Actions.BLOCK_DISABLE.set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) + Actions.RELOAD_BLOCKS.set_sensitive(True) #set the exec and stop buttons self.update_exec_stop() #saved status diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index 4d196477e..4185de048 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -273,3 +273,8 @@ BLOCK_INC_TYPE = Action( BLOCK_DEC_TYPE = Action( keypresses=(gtk.keysyms.Up, NO_MODS_MASK), ) +RELOAD_BLOCKS = Action( + label='Reload _Blocks', + tooltip='Reload Blocks', + stock_id=gtk.STOCK_REFRESH +) diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py index 8fd167869..0cdbdbb12 100644 --- a/grc/gui/Bars.py +++ b/grc/gui/Bars.py @@ -49,6 +49,8 @@ TOOLBAR_LIST = ( None, Actions.BLOCK_ENABLE, Actions.BLOCK_DISABLE, + None, + Actions.RELOAD_BLOCKS, ) ##The list of actions and categories for the menu bar. diff --git a/grc/gui/BlockTreeWindow.py b/grc/gui/BlockTreeWindow.py index 0175c8bec..62afb6205 100644 --- a/grc/gui/BlockTreeWindow.py +++ b/grc/gui/BlockTreeWindow.py @@ -90,6 +90,11 @@ class BlockTreeWindow(gtk.VBox): #initialize self._update_add_button() + def clear(self): + self.treestore.clear(); + self._categories = {tuple(): None} + + ############################################################ ## Block Tree Methods ############################################################ diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py index 2f761df1f..429bd9e86 100644 --- a/grc/gui/MainWindow.py +++ b/grc/gui/MainWindow.py @@ -93,7 +93,8 @@ class MainWindow(gtk.Window): #flow_graph_box.pack_start(self.scrolled_window) self.flow_graph_vpaned.pack1(self.notebook) self.hpaned.pack1(self.flow_graph_vpaned) - self.hpaned.pack2(BlockTreeWindow(platform, self.get_flow_graph), False) #dont allow resize + self.btwin = BlockTreeWindow(platform, self.get_flow_graph); + self.hpaned.pack2(self.btwin, False) #dont allow resize #create the reports window self.text_display = TextDisplay() #house the reports in a scrolled window -- cgit From 515d1b6f91f5dd28997525b1e88006bbfc0f170a Mon Sep 17 00:00:00 2001 From: Tim O'Shea Date: Wed, 26 Sep 2012 14:18:30 -0400 Subject: added the ability to open custom GRC hier block definitions from graphs using an instance of them --- grc/gui/ActionHandler.py | 6 ++++++ grc/gui/Actions.py | 5 +++++ grc/gui/Bars.py | 1 + grc/gui/FlowGraph.py | 1 + grc/gui/MainWindow.py | 2 ++ 5 files changed, 15 insertions(+) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 5600edc06..d1491db0b 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -307,6 +307,11 @@ class ActionHandler: self.platform.loadblocks() self.main_window.btwin.clear(); self.platform.load_block_tree(self.main_window.btwin); + elif action == Actions.OPEN_HIER: + bn = []; + for b in self.get_flow_graph().get_selected_blocks(): + if b._grc_source: + self.main_window.new_page(b._grc_source, show=True); else: print '!!! Action "%s" not handled !!!'%action ################################################## # Global Actions for all States @@ -324,6 +329,7 @@ class ActionHandler: #update enable/disable Actions.BLOCK_ENABLE.set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) Actions.BLOCK_DISABLE.set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) + Actions.OPEN_HIER.set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) Actions.RELOAD_BLOCKS.set_sensitive(True) #set the exec and stop buttons self.update_exec_stop() diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index 4185de048..03aa43057 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -278,3 +278,8 @@ RELOAD_BLOCKS = Action( tooltip='Reload Blocks', stock_id=gtk.STOCK_REFRESH ) +OPEN_HIER = Action( + label='Open H_ier', + tooltip='Open the source of the selected hierarchical block', + stock_id=gtk.STOCK_JUMP_TO, +) diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py index 0cdbdbb12..d95d23f1f 100644 --- a/grc/gui/Bars.py +++ b/grc/gui/Bars.py @@ -51,6 +51,7 @@ TOOLBAR_LIST = ( Actions.BLOCK_DISABLE, None, Actions.RELOAD_BLOCKS, + Actions.OPEN_HIER, ) ##The list of actions and categories for the menu bar. diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index 9f3326ada..0f69d4878 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -63,6 +63,7 @@ class FlowGraph(Element): Actions.BLOCK_ENABLE, Actions.BLOCK_DISABLE, Actions.BLOCK_PARAM_MODIFY, + Actions.OPEN_HIER, ]: self._context_menu.append(action.create_menu_item()) ########################################################################### diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py index 429bd9e86..37a100c94 100644 --- a/grc/gui/MainWindow.py +++ b/grc/gui/MainWindow.py @@ -170,6 +170,8 @@ class MainWindow(gtk.Window): try: #try to load from file if file_path: Messages.send_start_load(file_path) flow_graph = self._platform.get_new_flow_graph() + flow_graph.grc_file_path = file_path; + #print flow_graph page = NotebookPage( self, flow_graph=flow_graph, -- cgit From de014bd0cb98226d90bdec6bcf4c7a20cc4ba6b9 Mon Sep 17 00:00:00 2001 From: Tom Rondeau Date: Wed, 3 Oct 2012 11:01:04 -0400 Subject: grc: populate file variable correctly when we save. --- grc/gui/ActionHandler.py | 1 + 1 file changed, 1 insertion(+) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index d1491db0b..430a05fc2 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -264,6 +264,7 @@ class ActionHandler: else: try: ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path()) + self.get_flow_graph().grc_file_path = self.get_page().get_file_path() self.get_page().set_saved(True) except IOError: Messages.send_fail_save(self.get_page().get_file_path()) -- cgit From d7b57e43f186097f147534ad49c6337edc4fdc88 Mon Sep 17 00:00:00 2001 From: Tom Rondeau Date: Wed, 3 Oct 2012 11:10:11 -0400 Subject: grc: ability to automatically create hier_blocks from a flowgraph. Highlight a set of blocks and hit 'c' or right-click and 'Create Hier' to automatically build a new hier_block that opens in GRC with the probes in place. Any parameter/variables become parameters in the new graph. Save, generate, and hit the 'Reload Blocks' to get access to new block. --- grc/gui/ActionHandler.py | 129 +++++++++++++++++++++++++++++++++++++++++++++++ grc/gui/Actions.py | 6 +++ grc/gui/FlowGraph.py | 3 ++ 3 files changed, 138 insertions(+) (limited to 'grc/gui') diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 430a05fc2..9fb5e4ebf 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -153,6 +153,134 @@ class ActionHandler: self.get_flow_graph().update() self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) self.get_page().set_saved(False) + ################################################## + # Create heir block + ################################################## + elif action == Actions.BLOCK_CREATE_HIER: + + # keeping track of coordinates for pasting later + coords = self.get_flow_graph().get_selected_blocks()[0].get_coordinate() + x,y = coords + x_min = x + y_min = y + + pads = []; + params = []; + + # Save the state of the leaf blocks + for block in self.get_flow_graph().get_selected_blocks(): + + # Check for string variables within the blocks + for param in block.get_params(): + for variable in self.get_flow_graph().get_variables(): + # If a block parameter exists that is a variable, create a parameter for it + if param.get_value() == variable.get_id(): + params.append(param.get_value()) + for flow_param in self.get_flow_graph().get_parameters(): + # If a block parameter exists that is a parameter, create a parameter for it + if param.get_value() == flow_param.get_id(): + params.append(param.get_value()) + + + # keep track of x,y mins for pasting later + (x,y) = block.get_coordinate() + if x < x_min: + x_min = x + if y < y_min: + y_min = y + + for connection in block.get_connections(): + + # Get id of connected blocks + source_id = connection.get_source().get_parent().get_id() + sink_id = connection.get_sink().get_parent().get_id() + + # If connected block is not in the list of selected blocks create a pad for it + if self.get_flow_graph().get_block(source_id) not in self.get_flow_graph().get_selected_blocks(): + pads.append({'key': connection.get_sink().get_key(), 'coord': connection.get_source().get_coordinate(), 'block_id' : block.get_id(), 'direction': 'source'}) + + if self.get_flow_graph().get_block(sink_id) not in self.get_flow_graph().get_selected_blocks(): + pads.append({'key': connection.get_source().get_key(), 'coord': connection.get_sink().get_coordinate(), 'block_id' : block.get_id(), 'direction': 'sink'}) + + + # Copy the selected blocks and paste them into a new page + # then move the flowgraph to a reasonable position + Actions.BLOCK_COPY() + self.main_window.new_page() + Actions.BLOCK_PASTE() + coords = (x_min,y_min) + self.get_flow_graph().move_selected(coords) + + + # Set flow graph to heir block type + top_block = self.get_flow_graph().get_block("top_block") + top_block.get_param('generate_options').set_value('hb') + + # this needs to be a unique name + top_block.get_param('id').set_value('new_heir') + + # Remove the default samp_rate variable block that is created + remove_me = self.get_flow_graph().get_block("samp_rate") + self.get_flow_graph().remove_element(remove_me) + + + # Add the param blocks along the top of the window + x_pos = 150 + for param in params: + param_id = self.get_flow_graph().add_new_block('parameter',(x_pos,10)) + param_block = self.get_flow_graph().get_block(param_id) + param_block.get_param('id').set_value(param) + x_pos = x_pos + 100 + + for pad in pads: + # Add the pad sources and sinks within the new heir block + if pad['direction'] == 'sink': + + # Add new PAD_SINK block to the canvas + pad_id = self.get_flow_graph().add_new_block('pad_sink', pad['coord']) + + # setup the references to the sink and source + pad_block = self.get_flow_graph().get_block(pad_id) + pad_sink = pad_block.get_sinks()[0] + + source_block = self.get_flow_graph().get_block(pad['block_id']) + source = source_block.get_source(pad['key']) + + # Ensure the port types match + while pad_sink.get_type() != source.get_type(): + + # Special case for some blocks that have non-standard type names, e.g. uhd + if pad_sink.get_type() == 'complex' and source.get_type() == 'fc32': + break; + pad_block.type_controller_modify(1) + + # Connect the pad to the proper sinks + new_connection = self.get_flow_graph().connect(source,pad_sink) + + elif pad['direction'] == 'source': + pad_id = self.get_flow_graph().add_new_block('pad_source', pad['coord']) + + # setup the references to the sink and source + pad_block = self.get_flow_graph().get_block(pad_id) + pad_source = pad_block.get_sources()[0] + + sink_block = self.get_flow_graph().get_block(pad['block_id']) + sink = sink_block.get_sink(pad['key']) + + # Ensure the port types match + while sink.get_type() != pad_source.get_type(): + # Special case for some blocks that have non-standard type names, e.g. uhd + if pad_source.get_type() == 'complex' and sink.get_type() == 'fc32': + break; + pad_block.type_controller_modify(1) + + # Connect the pad to the proper sinks + new_connection = self.get_flow_graph().connect(pad_source,sink) + + # update the new heir block flow graph + self.get_flow_graph().update() + + ################################################## # Move/Rotate/Delete/Create ################################################## @@ -330,6 +458,7 @@ class ActionHandler: #update enable/disable Actions.BLOCK_ENABLE.set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) Actions.BLOCK_DISABLE.set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) + Actions.BLOCK_CREATE_HIER.set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) Actions.OPEN_HIER.set_sensitive(bool(self.get_flow_graph().get_selected_blocks())) Actions.RELOAD_BLOCKS.set_sensitive(True) #set the exec and stop buttons diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index 03aa43057..8087f4955 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -198,6 +198,12 @@ BLOCK_DISABLE = Action( stock_id=gtk.STOCK_DISCONNECT, keypresses=(gtk.keysyms.d, NO_MODS_MASK), ) +BLOCK_CREATE_HIER = Action( + label='C_reate Hier', + tooltip='Create hier block from selected blocks', + stock_id=gtk.STOCK_CONNECT, + keypresses=(gtk.keysyms.c, NO_MODS_MASK), +) BLOCK_CUT = Action( label='Cu_t', tooltip='Cut', diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index 0f69d4878..67e5af97b 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -63,6 +63,7 @@ class FlowGraph(Element): Actions.BLOCK_ENABLE, Actions.BLOCK_DISABLE, Actions.BLOCK_PARAM_MODIFY, + Actions.BLOCK_CREATE_HIER, Actions.OPEN_HIER, ]: self._context_menu.append(action.create_menu_item()) @@ -98,6 +99,8 @@ class FlowGraph(Element): block.get_param('id').set_value(id) Actions.ELEMENT_CREATE() + return id + ########################################################################### # Copy Paste ########################################################################### -- cgit From cf2a4f174bb49a6c9e839217369b1e22741a10da Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Tue, 15 Jan 2013 14:30:39 +0100 Subject: grc: Fixed Bug #485 by gracefully exiting when user sets GR_DONT_LOAD_PREFS=1 GRC_BLOCKS_PATH= --- grc/gui/MainWindow.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'grc/gui') diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py index 37a100c94..1dc02dabb 100644 --- a/grc/gui/MainWindow.py +++ b/grc/gui/MainWindow.py @@ -180,6 +180,9 @@ class MainWindow(gtk.Window): if file_path: Messages.send_end_load() except Exception, e: #return on failure Messages.send_fail_load(e) + if isinstance(e, KeyError) and str(e) == "'options'": + # This error is unrecoverable, so crash gracefully + exit(-1) return #add this page to the notebook self.notebook.append_page(page, page.get_tab()) -- cgit From d3c7e93f2143e31ed5ff80aa21c8d983df92d19f Mon Sep 17 00:00:00 2001 From: Balint Seeber Date: Fri, 18 Jan 2013 16:26:09 -0800 Subject: Removed check for pending events during mouse drag of GRC blocks on FlowGraph canvas. This is always true on Windows with latest PyGTK and so blocks would never move with mouse movement. Disabling the check appears to have no adverse effects. --- grc/gui/FlowGraph.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'grc/gui') diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index 67e5af97b..6af4bcb62 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -494,8 +494,9 @@ class FlowGraph(Element): Move a selected element to the new coordinate. Auto-scroll the scroll bars at the boundaries. """ - #to perform a movement, the mouse must be pressed, no pending events - if gtk.events_pending() or not self.mouse_pressed: return + #to perform a movement, the mouse must be pressed + # (no longer checking pending events via gtk.events_pending() - always true in Windows) + if not self.mouse_pressed: return #perform autoscrolling width, height = self.get_size() x, y = coordinate -- cgit From d1f48160a87388369c54c7c6eeef404c1a31fe3e Mon Sep 17 00:00:00 2001 From: Tim O'Shea Date: Thu, 23 May 2013 09:20:21 -0400 Subject: grc: remove "c" key shortcut for create heir action --- grc/gui/Actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'grc/gui') diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index 8087f4955..fab026c5e 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -202,7 +202,7 @@ BLOCK_CREATE_HIER = Action( label='C_reate Hier', tooltip='Create hier block from selected blocks', stock_id=gtk.STOCK_CONNECT, - keypresses=(gtk.keysyms.c, NO_MODS_MASK), +# keypresses=(gtk.keysyms.c, NO_MODS_MASK), ) BLOCK_CUT = Action( label='Cu_t', -- cgit