diff options
Diffstat (limited to 'grc/gui')
-rw-r--r-- | grc/gui/ActionHandler.py | 215 | ||||
-rw-r--r-- | grc/gui/Actions.py | 215 | ||||
-rw-r--r-- | grc/gui/Bars.py | 70 | ||||
-rw-r--r-- | grc/gui/Block.py | 291 | ||||
-rw-r--r-- | grc/gui/BlockTreeWindow.py | 203 | ||||
-rw-r--r-- | grc/gui/Colors.py | 119 | ||||
-rw-r--r-- | grc/gui/Config.py | 7 | ||||
-rw-r--r-- | grc/gui/Connection.py | 78 | ||||
-rw-r--r-- | grc/gui/Constants.py | 21 | ||||
-rw-r--r-- | grc/gui/Dialogs.py | 118 | ||||
-rw-r--r-- | grc/gui/DrawingArea.py | 195 | ||||
-rw-r--r-- | grc/gui/Element.py | 45 | ||||
-rw-r--r-- | grc/gui/Executor.py | 17 | ||||
-rw-r--r-- | grc/gui/FileDialogs.py | 67 | ||||
-rw-r--r-- | grc/gui/FlowGraph.py | 232 | ||||
-rw-r--r-- | grc/gui/MainWindow.py | 200 | ||||
-rw-r--r-- | grc/gui/NotebookPage.py | 183 | ||||
-rw-r--r-- | grc/gui/Param.py | 459 | ||||
-rw-r--r-- | grc/gui/ParamWidgets.py | 300 | ||||
-rw-r--r-- | grc/gui/ParserErrorsDialog.py | 37 | ||||
-rw-r--r-- | grc/gui/Platform.py | 4 | ||||
-rw-r--r-- | grc/gui/Port.py | 105 | ||||
-rw-r--r-- | grc/gui/Preferences.py | 21 | ||||
-rw-r--r-- | grc/gui/PropsDialog.py | 202 | ||||
-rw-r--r-- | grc/gui/StateCache.py | 5 | ||||
-rw-r--r-- | grc/gui/Utils.py | 65 | ||||
-rw-r--r-- | grc/gui/VariableEditor.py | 130 | ||||
-rw-r--r-- | grc/gui/external_editor.py | 6 |
28 files changed, 1705 insertions, 1905 deletions
diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 11e81c4613..25c779b4d2 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -18,11 +18,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -import gobject -import gtk +from __future__ import absolute_import, print_function + import os import subprocess +from gi.repository import Gtk +from gi.repository import GObject + from . import Dialogs, Preferences, Actions, Executor, Constants from .FileDialogs import (OpenFlowGraphFileDialog, SaveFlowGraphFileDialog, SaveConsoleFileDialog, SaveScreenShotDialog, @@ -33,8 +36,6 @@ from .PropsDialog import PropsDialog from ..core import ParseXML, Messages -gobject.threads_init() - class ActionHandler: """ @@ -60,7 +61,6 @@ class ActionHandler: self.main_window = MainWindow(platform, self._handle_action) 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_focus_flag = self.main_window.get_focus_flag #setup the messages Messages.register_messenger(self.main_window.add_console_line) @@ -85,9 +85,8 @@ class ActionHandler: # prevent key event stealing while the search box is active # .has_focus() only in newer versions 2.17+? # .is_focus() seems to work, but exactly the same - if self.main_window.btwin.search_entry.flags() & gtk.HAS_FOCUS: + if self.main_window.btwin.search_entry.has_focus(): return False - if not self.get_focus_flag(): return False return Actions.handle_key_press(event) def _quit(self, window, event): @@ -105,11 +104,11 @@ class ActionHandler: def _handle_action(self, action, *args): #print action main = self.main_window - page = main.get_page() - flow_graph = page.get_flow_graph() if page else None + page = main.current_page + flow_graph = page.flow_graph if page else None def flow_graph_update(fg=flow_graph): - main.vars.update_gui() + main.vars.update_gui(fg.blocks) fg.update() ################################################## @@ -117,13 +116,13 @@ class ActionHandler: ################################################## if action == Actions.APPLICATION_INITIALIZE: if not self.init_file_paths: - self.init_file_paths = filter(os.path.exists, Preferences.get_open_files()) + self.init_file_paths = list(filter(os.path.exists, Preferences.get_open_files())) if not self.init_file_paths: self.init_file_paths = [''] for file_path in self.init_file_paths: if file_path: main.new_page(file_path) #load pages from file paths if Preferences.file_open() in self.init_file_paths: main.new_page(Preferences.file_open(), show=True) - if not self.get_page(): + if not self.main_window.current_page: main.new_page() # ensure that at least a blank page exists main.btwin.search_entry.hide() @@ -159,7 +158,7 @@ class ActionHandler: self.init = True elif action == Actions.APPLICATION_QUIT: if main.close_pages(): - gtk.main_quit() + Gtk.main_quit() exit(0) ################################################## # Selections @@ -176,18 +175,18 @@ class ActionHandler: elif action == Actions.BLOCK_ENABLE: if flow_graph.enable_selected(True): flow_graph_update() - page.get_state_cache().save_new_state(flow_graph.export_data()) - page.set_saved(False) + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False elif action == Actions.BLOCK_DISABLE: if flow_graph.enable_selected(False): flow_graph_update() - page.get_state_cache().save_new_state(flow_graph.export_data()) - page.set_saved(False) + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False elif action == Actions.BLOCK_BYPASS: if flow_graph.bypass_selected(): flow_graph_update() - page.get_state_cache().save_new_state(flow_graph.export_data()) - page.set_saved(False) + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False ################################################## # Cut/Copy/Paste ################################################## @@ -200,8 +199,8 @@ class ActionHandler: if self.clipboard: flow_graph.paste_from_clipboard(self.clipboard) flow_graph_update() - page.get_state_cache().save_new_state(flow_graph.export_data()) - page.set_saved(False) + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False ################################################## # Create heir block ################################################## @@ -241,15 +240,15 @@ class ActionHandler: for connection in block.connections: # Get id of connected blocks - source_id = connection.get_source().get_parent().get_id() - sink_id = connection.get_sink().get_parent().get_id() + source_id = connection.source_block.get_id() + sink_id = connection.sink_block.get_id() # If connected block is not in the list of selected blocks create a pad for it if flow_graph.get_block(source_id) not in 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'}) + pads.append({'key': connection.sink_port.get_key(), 'coord': connection.source_port.get_coordinate(), 'block_id' : block.get_id(), 'direction': 'source'}) if flow_graph.get_block(sink_id) not in 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'}) + pads.append({'key': connection.source_port.get_key(), 'coord': connection.sink_port.get_coordinate(), 'block_id' : block.get_id(), 'direction': 'sink'}) # Copy the selected blocks and paste them into a new page @@ -334,63 +333,62 @@ class ActionHandler: # Move/Rotate/Delete/Create ################################################## elif action == Actions.BLOCK_MOVE: - page.get_state_cache().save_new_state(flow_graph.export_data()) - page.set_saved(False) + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False elif action in Actions.BLOCK_ALIGNMENTS: if flow_graph.align_selected(action): - page.get_state_cache().save_new_state(flow_graph.export_data()) - page.set_saved(False) + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False elif action == Actions.BLOCK_ROTATE_CCW: if flow_graph.rotate_selected(90): flow_graph_update() - page.get_state_cache().save_new_state(flow_graph.export_data()) - page.set_saved(False) + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False elif action == Actions.BLOCK_ROTATE_CW: if flow_graph.rotate_selected(-90): flow_graph_update() - page.get_state_cache().save_new_state(flow_graph.export_data()) - page.set_saved(False) + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False elif action == Actions.ELEMENT_DELETE: if flow_graph.remove_selected(): flow_graph_update() - page.get_state_cache().save_new_state(flow_graph.export_data()) + page.state_cache.save_new_state(flow_graph.export_data()) Actions.NOTHING_SELECT() - page.set_saved(False) + page.saved = False elif action == Actions.ELEMENT_CREATE: flow_graph_update() - page.get_state_cache().save_new_state(flow_graph.export_data()) + page.state_cache.save_new_state(flow_graph.export_data()) Actions.NOTHING_SELECT() - page.set_saved(False) + page.saved = False elif action == Actions.BLOCK_INC_TYPE: if flow_graph.type_controller_modify_selected(1): flow_graph_update() - page.get_state_cache().save_new_state(flow_graph.export_data()) - page.set_saved(False) + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False elif action == Actions.BLOCK_DEC_TYPE: if flow_graph.type_controller_modify_selected(-1): flow_graph_update() - page.get_state_cache().save_new_state(flow_graph.export_data()) - page.set_saved(False) + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False elif action == Actions.PORT_CONTROLLER_INC: if flow_graph.port_controller_modify_selected(1): flow_graph_update() - page.get_state_cache().save_new_state(flow_graph.export_data()) - page.set_saved(False) + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False elif action == Actions.PORT_CONTROLLER_DEC: if flow_graph.port_controller_modify_selected(-1): flow_graph_update() - page.get_state_cache().save_new_state(flow_graph.export_data()) - page.set_saved(False) + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False ################################################## # Window stuff ################################################## elif action == Actions.ABOUT_WINDOW_DISPLAY: - platform = flow_graph.get_parent() - Dialogs.AboutDialog(platform.config) + Dialogs.AboutDialog(self.platform.config) elif action == Actions.HELP_WINDOW_DISPLAY: Dialogs.HelpDialog() elif action == Actions.TYPES_WINDOW_DISPLAY: - Dialogs.TypesDialog(flow_graph.get_parent()) + Dialogs.TypesDialog(self.platform) elif action == Actions.ERRORS_WINDOW_DISPLAY: Dialogs.ErrorsDialog(flow_graph) elif action == Actions.TOGGLE_CONSOLE_WINDOW: @@ -408,7 +406,7 @@ class ActionHandler: elif action == Actions.CLEAR_CONSOLE: main.text_display.clear() elif action == Actions.SAVE_CONSOLE: - file_path = SaveConsoleFileDialog(page.get_file_path()).run() + file_path = SaveConsoleFileDialog(page.file_path).run() if file_path is not None: main.text_display.save(file_path) elif action == Actions.TOGGLE_HIDE_DISABLED_BLOCKS: @@ -416,7 +414,7 @@ class ActionHandler: elif action == Actions.TOGGLE_AUTO_HIDE_PORT_LABELS: action.save_to_preferences() for page in main.get_pages(): - page.get_flow_graph().create_shapes() + page.flow_graph.create_shapes() elif action in (Actions.TOGGLE_SNAP_TO_GRID, Actions.TOGGLE_SHOW_BLOCK_COMMENTS, Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB): @@ -424,7 +422,7 @@ class ActionHandler: elif action == Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY: action.save_to_preferences() for page in main.get_pages(): - flow_graph_update(page.get_flow_graph()) + flow_graph_update(page.flow_graph) elif action == Actions.TOGGLE_HIDE_VARIABLES: # Call the variable editor TOGGLE in case it needs to be showing Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR() @@ -448,9 +446,9 @@ class ActionHandler: action.save_to_preferences() elif action == Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR: if self.init: - md = gtk.MessageDialog(main, - gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, - gtk.BUTTONS_CLOSE, "Moving the variable editor requires a restart of GRC.") + md = Gtk.MessageDialog(main, + Gtk.DIALOG_DESTROY_WITH_PARENT, Gtk.MESSAGE_INFO, + Gtk.BUTTONS_CLOSE, "Moving the variable editor requires a restart of GRC.") md.run() md.destroy() action.save_to_preferences() @@ -463,33 +461,33 @@ class ActionHandler: else: selected_block = flow_graph.get_selected_block() if selected_block: - self.dialog = PropsDialog(selected_block) - response = gtk.RESPONSE_APPLY - while response == gtk.RESPONSE_APPLY: # rerun the dialog if Apply was hit + self.dialog = PropsDialog(self.main_window, selected_block) + response = Gtk.ResponseType.APPLY + while response == Gtk.ResponseType.APPLY: # rerun the dialog if Apply was hit response = self.dialog.run() - if response in (gtk.RESPONSE_APPLY, gtk.RESPONSE_ACCEPT): + if response in (Gtk.ResponseType.APPLY, Gtk.ResponseType.ACCEPT): flow_graph_update() - page.get_state_cache().save_new_state(flow_graph.export_data()) - page.set_saved(False) + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False else: # restore the current state - n = page.get_state_cache().get_current_state() + n = page.state_cache.get_current_state() flow_graph.import_data(n) flow_graph_update() - if response == gtk.RESPONSE_APPLY: + if response == Gtk.ResponseType.APPLY: # null action, that updates the main window Actions.ELEMENT_SELECT() self.dialog.destroy() self.dialog = None elif action == Actions.EXTERNAL_UPDATE: - page.get_state_cache().save_new_state(flow_graph.export_data()) + page.state_cache.save_new_state(flow_graph.export_data()) flow_graph_update() if self.dialog is not None: self.dialog.update_gui(force=True) - page.set_saved(False) + page.saved = False elif action == Actions.VARIABLE_EDITOR_UPDATE: - page.get_state_cache().save_new_state(flow_graph.export_data()) + page.state_cache.save_new_state(flow_graph.export_data()) flow_graph_update() - page.set_saved(False) + page.saved = False ################################################## # View Parser Errors ################################################## @@ -499,19 +497,19 @@ class ActionHandler: # Undo/Redo ################################################## elif action == Actions.FLOW_GRAPH_UNDO: - n = page.get_state_cache().get_prev_state() + n = page.state_cache.get_prev_state() if n: flow_graph.unselect() flow_graph.import_data(n) flow_graph_update() - page.set_saved(False) + page.saved = False elif action == Actions.FLOW_GRAPH_REDO: - n = page.get_state_cache().get_next_state() + n = page.state_cache.get_next_state() if n: flow_graph.unselect() flow_graph.import_data(n) flow_graph_update() - page.set_saved(False) + page.saved = False ################################################## # New/Open/Save/Close ################################################## @@ -521,15 +519,18 @@ class ActionHandler: flow_graph._options_block.get_param('generate_options').set_value(args[0]) flow_graph_update() elif action == Actions.FLOW_GRAPH_OPEN: - file_paths = args if args else OpenFlowGraphFileDialog(page.get_file_path()).run() - if file_paths: #open a new page for each file, show only the first + # TODO: Disable opening the flowgraph + pass + ''' + file_paths = args if args else OpenFlowGraphFileDialog(page.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): main.new_page(file_path, show=(i==0)) Preferences.add_recent_file(file_path) main.tool_bar.refresh_submenus() main.menu_bar.refresh_submenus() main.vars.update_gui() - + ''' elif action == Actions.FLOW_GRAPH_OPEN_QSS_THEME: file_paths = OpenQSSFileDialog(self.platform.config.install_prefix + '/share/gnuradio/themes/').run() @@ -544,27 +545,27 @@ class ActionHandler: main.close_page() elif action == Actions.FLOW_GRAPH_SAVE: #read-only or undefined file path, do save-as - if page.get_read_only() or not page.get_file_path(): + if page.get_read_only() or not page.file_path: Actions.FLOW_GRAPH_SAVE_AS() #otherwise try to save else: try: - ParseXML.to_file(flow_graph.export_data(), page.get_file_path()) - flow_graph.grc_file_path = page.get_file_path() - page.set_saved(True) + ParseXML.to_file(flow_graph.export_data(), page.file_path) + flow_graph.grc_file_path = page.file_path + page.saved = True except IOError: - Messages.send_fail_save(page.get_file_path()) - page.set_saved(False) + Messages.send_fail_save(page.file_path) + page.saved = False elif action == Actions.FLOW_GRAPH_SAVE_AS: - file_path = SaveFlowGraphFileDialog(page.get_file_path()).run() + file_path = SaveFlowGraphFileDialog(page.file_path).run() if file_path is not None: - page.set_file_path(file_path) + page.file_path = os.path.abspath(file_path) Actions.FLOW_GRAPH_SAVE() Preferences.add_recent_file(file_path) main.tool_bar.refresh_submenus() main.menu_bar.refresh_submenus() elif action == Actions.FLOW_GRAPH_SCREEN_CAPTURE: - file_path, background_transparent = SaveScreenShotDialog(page.get_file_path()).run() + file_path, background_transparent = SaveScreenShotDialog(page.file_path).run() if file_path is not None: pixbuf = flow_graph.get_drawing_area().get_screenshot(background_transparent) pixbuf.save(file_path, Constants.IMAGE_FILE_EXTENSION[1:]) @@ -572,38 +573,38 @@ class ActionHandler: # Gen/Exec/Stop ################################################## elif action == Actions.FLOW_GRAPH_GEN: - if not page.get_proc(): - if not page.get_saved() or not page.get_file_path(): - Actions.FLOW_GRAPH_SAVE() #only save if file path missing or not saved - if page.get_saved() and page.get_file_path(): + if not page.process: + if not page.saved or not page.file_path: + Actions.FLOW_GRAPH_SAVE() # only save if file path missing or not saved + if page.saved and page.file_path: generator = page.get_generator() try: - Messages.send_start_gen(generator.get_file_path()) + Messages.send_start_gen(generator.file_path) generator.write() except Exception as e: Messages.send_fail_gen(e) else: self.generator = None elif action == Actions.FLOW_GRAPH_EXEC: - if not page.get_proc(): + if not page.process: Actions.FLOW_GRAPH_GEN() xterm = self.platform.config.xterm_executable if Preferences.xterm_missing() != xterm: if not os.path.exists(xterm): Dialogs.MissingXTermDialog(xterm) Preferences.xterm_missing(xterm) - if page.get_saved() and page.get_file_path(): + if page.saved and page.file_path: Executor.ExecFlowGraphThread( flow_graph_page=page, xterm_executable=xterm, callback=self.update_exec_stop ) elif action == Actions.FLOW_GRAPH_KILL: - if page.get_proc(): + if page.process: try: - page.get_proc().kill() + page.process.kill() except: - print "could not kill process: %d" % page.get_proc().pid + print("could not kill process: %d" % page.process.pid) elif action == Actions.PAGE_CHANGE: # pass and run the global actions pass elif action == Actions.RELOAD_BLOCKS: @@ -644,12 +645,12 @@ class ActionHandler: shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) else: - print '!!! Action "%s" not handled !!!' % action + print('!!! Action "%s" not handled !!!' % action) ################################################## # Global Actions for all States ################################################## - page = main.get_page() # page and flowgraph might have changed - flow_graph = page.get_flow_graph() if page else None + page = main.current_page # page and flow graph might have changed + flow_graph = page.flow_graph if page else None selected_blocks = flow_graph.get_selected_blocks() selected_block = selected_blocks[0] if selected_blocks else None @@ -674,7 +675,7 @@ class ActionHandler: can_disable = any(block.get_state() != Constants.BLOCK_DISABLED for block in selected_blocks) can_bypass_all = all(block.can_bypass() for block in selected_blocks) \ - and any (not block.get_bypassed() for block in selected_blocks) + and any(not block.get_bypassed() for block in selected_blocks) Actions.BLOCK_ENABLE.set_sensitive(can_enable) Actions.BLOCK_DISABLE.set_sensitive(can_disable) Actions.BLOCK_BYPASS.set_sensitive(can_bypass_all) @@ -685,28 +686,24 @@ class ActionHandler: Actions.BUSSIFY_SINKS.set_sensitive(bool(selected_blocks)) Actions.RELOAD_BLOCKS.set_sensitive(True) Actions.FIND_BLOCKS.set_sensitive(True) - #set the exec and stop buttons + self.update_exec_stop() - #saved status - Actions.FLOW_GRAPH_SAVE.set_sensitive(not page.get_saved()) + + Actions.FLOW_GRAPH_SAVE.set_sensitive(not page.saved) main.update() - try: #set the size of the flow graph area (if changed) - new_size = (flow_graph.get_option('window_size') or - self.platform.config.default_canvas_size) - if flow_graph.get_size() != tuple(new_size): - flow_graph.set_size(*new_size) - except: pass - #draw the flow graph + flow_graph.update_selected() flow_graph.queue_draw() - return True #action was handled + + return True # action was handled 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.main_window.get_flow_graph().is_valid() and not self.get_page().get_proc() + page = self.main_window.current_page + sensitive = page.flow_graph.is_valid() and not page.process Actions.FLOW_GRAPH_GEN.set_sensitive(sensitive) Actions.FLOW_GRAPH_EXEC.set_sensitive(sensitive) - Actions.FLOW_GRAPH_KILL.set_sensitive(self.get_page().get_proc() is not None) + Actions.FLOW_GRAPH_KILL.set_sensitive(page.process is not None) diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index 9b2af36b93..3a51e80918 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -17,19 +17,26 @@ 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 __future__ import absolute_import -import Preferences +import six + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import GObject + +from . import Preferences NO_MODS_MASK = 0 + ######################################################################## # Actions API ######################################################################## _actions_keypress_dict = dict() -_keymap = gtk.gdk.keymap_get_default() +_keymap = Gdk.Keymap.get_default() _used_mods_mask = NO_MODS_MASK @@ -44,12 +51,11 @@ def handle_key_press(event): Returns: 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 - keyval, egroup, level, consumed = _keymap.translate_keyboard_state( - event.hardware_keycode, event.state, event.group) + _unused, keyval, egroup, level, consumed = _keymap.translate_keyboard_state( + event.hardware_keycode, event.get_state(), event.group) # get the modifier mask and ignore irrelevant modifiers - mod_mask = event.state & ~consumed & _used_mods_mask + mod_mask = event.get_state() & ~consumed & _used_mods_mask # look up the keypress and call the action try: _actions_keypress_dict[(keyval, mod_mask)]() @@ -64,7 +70,7 @@ _all_actions_list = list() def get_all_actions(): return _all_actions_list -_accel_group = gtk.AccelGroup() +_accel_group = Gtk.AccelGroup() def get_accel_group(): @@ -77,13 +83,16 @@ class _ActionBase(object): Register actions and keypresses with this module. """ def __init__(self, label, keypresses): + global _used_mods_mask + _all_actions_list.append(self) for i in range(len(keypresses)/2): keyval, mod_mask = keypresses[i*2:(i+1)*2] # register this keypress - if _actions_keypress_dict.has_key((keyval, mod_mask)): + if (keyval, mod_mask) in _actions_keypress_dict: raise KeyError('keyval/mod_mask pair already registered "%s"' % str((keyval, mod_mask))) _actions_keypress_dict[(keyval, mod_mask)] = self + _used_mods_mask |= mod_mask # set the accelerator group, and accelerator path # register the key name and mod mask with the accelerator path if label is None: @@ -91,7 +100,7 @@ class _ActionBase(object): accel_path = '<main>/' + 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) + Gtk.AccelMap.add_entry(accel_path, keyval, mod_mask) self.args = None def __str__(self): @@ -99,7 +108,7 @@ class _ActionBase(object): The string representation should be the name of the action id. Try to find the action id for this action by searching this module. """ - for name, value in globals().iteritems(): + for name, value in six.iteritems(globals()): if value == self: return name return self.get_name() @@ -114,9 +123,9 @@ class _ActionBase(object): self.emit('activate') -class Action(gtk.Action, _ActionBase): +class Action(Gtk.Action, _ActionBase): """ - A custom Action class based on gtk.Action. + A custom Action class based on Gtk.Action. Pass additional arguments such as keypresses. """ @@ -127,18 +136,18 @@ class Action(gtk.Action, _ActionBase): Args: key_presses: a tuple of (keyval1, mod_mask1, keyval2, mod_mask2, ...) - the: regular gtk.Action parameters (defaults to None) + the: regular Gtk.Action parameters (defaults to None) """ if name is None: name = label - gtk.Action.__init__(self, name=name, label=label, tooltip=tooltip, + GObject.GObject.__init__(self, name=name, label=label, tooltip=tooltip, stock_id=stock_id) _ActionBase.__init__(self, label, keypresses) -class ToggleAction(gtk.ToggleAction, _ActionBase): +class ToggleAction(Gtk.ToggleAction, _ActionBase): """ - A custom Action class based on gtk.ToggleAction. + A custom Action class based on Gtk.ToggleAction. Pass additional arguments such as keypresses. """ @@ -149,11 +158,11 @@ class ToggleAction(gtk.ToggleAction, _ActionBase): Args: key_presses: a tuple of (keyval1, mod_mask1, keyval2, mod_mask2, ...) - the: regular gtk.Action parameters (defaults to None) + the: regular Gtk.Action parameters (defaults to None) """ if name is None: name = label - gtk.ToggleAction.__init__(self, name=name, label=label, + GObject.GObject.__init__(self, name=name, label=label, tooltip=tooltip, stock_id=stock_id) _ActionBase.__init__(self, label, keypresses) self.preference_name = preference_name @@ -177,114 +186,114 @@ VARIABLE_EDITOR_UPDATE = 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), + stock_id=Gtk.STOCK_NEW, + keypresses=(Gdk.KEY_n, Gdk.ModifierType.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), + stock_id=Gtk.STOCK_OPEN, + keypresses=(Gdk.KEY_o, Gdk.ModifierType.CONTROL_MASK), ) FLOW_GRAPH_OPEN_RECENT = Action( label='Open _Recent', tooltip='Open a recently used flow graph', - stock_id=gtk.STOCK_OPEN, + stock_id=Gtk.STOCK_OPEN, ) 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), + stock_id=Gtk.STOCK_SAVE, + keypresses=(Gdk.KEY_s, Gdk.ModifierType.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), + stock_id=Gtk.STOCK_SAVE_AS, + keypresses=(Gdk.KEY_s, Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.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), + stock_id=Gtk.STOCK_CLOSE, + keypresses=(Gdk.KEY_w, Gdk.ModifierType.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), + stock_id=Gtk.STOCK_QUIT, + keypresses=(Gdk.KEY_q, Gdk.ModifierType.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), + stock_id=Gtk.STOCK_UNDO, + keypresses=(Gdk.KEY_z, Gdk.ModifierType.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), + stock_id=Gtk.STOCK_REDO, + keypresses=(Gdk.KEY_y, Gdk.ModifierType.CONTROL_MASK), ) NOTHING_SELECT = Action() SELECT_ALL = Action( label='Select _All', tooltip='Select all blocks and connections in the flow graph', - stock_id=gtk.STOCK_SELECT_ALL, - keypresses=(gtk.keysyms.a, gtk.gdk.CONTROL_MASK), + stock_id=Gtk.STOCK_SELECT_ALL, + keypresses=(Gdk.KEY_a, Gdk.ModifierType.CONTROL_MASK), ) 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), + stock_id=Gtk.STOCK_DELETE, + keypresses=(Gdk.KEY_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), + stock_id=Gtk.STOCK_GO_BACK, + keypresses=(Gdk.KEY_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), + stock_id=Gtk.STOCK_GO_FORWARD, + keypresses=(Gdk.KEY_Right, NO_MODS_MASK), ) BLOCK_VALIGN_TOP = Action( label='Vertical Align Top', tooltip='Align tops of selected blocks', - keypresses=(gtk.keysyms.t, gtk.gdk.SHIFT_MASK), + keypresses=(Gdk.KEY_t, Gdk.ModifierType.SHIFT_MASK), ) BLOCK_VALIGN_MIDDLE = Action( label='Vertical Align Middle', tooltip='Align centers of selected blocks vertically', - keypresses=(gtk.keysyms.m, gtk.gdk.SHIFT_MASK), + keypresses=(Gdk.KEY_m, Gdk.ModifierType.SHIFT_MASK), ) BLOCK_VALIGN_BOTTOM = Action( label='Vertical Align Bottom', tooltip='Align bottoms of selected blocks', - keypresses=(gtk.keysyms.b, gtk.gdk.SHIFT_MASK), + keypresses=(Gdk.KEY_b, Gdk.ModifierType.SHIFT_MASK), ) BLOCK_HALIGN_LEFT = Action( label='Horizontal Align Left', tooltip='Align left edges of blocks selected blocks', - keypresses=(gtk.keysyms.l, gtk.gdk.SHIFT_MASK), + keypresses=(Gdk.KEY_l, Gdk.ModifierType.SHIFT_MASK), ) BLOCK_HALIGN_CENTER = Action( label='Horizontal Align Center', tooltip='Align centers of selected blocks horizontally', - keypresses=(gtk.keysyms.c, gtk.gdk.SHIFT_MASK), + keypresses=(Gdk.KEY_c, Gdk.ModifierType.SHIFT_MASK), ) BLOCK_HALIGN_RIGHT = Action( label='Horizontal Align Right', tooltip='Align right edges of selected blocks', - keypresses=(gtk.keysyms.r, gtk.gdk.SHIFT_MASK), + keypresses=(Gdk.KEY_r, Gdk.ModifierType.SHIFT_MASK), ) BLOCK_ALIGNMENTS = [ BLOCK_VALIGN_TOP, @@ -298,26 +307,26 @@ BLOCK_ALIGNMENTS = [ 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), + stock_id=Gtk.STOCK_PROPERTIES, + keypresses=(Gdk.KEY_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), + stock_id=Gtk.STOCK_CONNECT, + keypresses=(Gdk.KEY_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), + stock_id=Gtk.STOCK_DISCONNECT, + keypresses=(Gdk.KEY_d, NO_MODS_MASK), ) BLOCK_BYPASS = Action( label='_Bypass', tooltip='Bypass the selected block', - stock_id=gtk.STOCK_MEDIA_FORWARD, - keypresses=(gtk.keysyms.b, NO_MODS_MASK), + stock_id=Gtk.STOCK_MEDIA_FORWARD, + keypresses=(Gdk.KEY_b, NO_MODS_MASK), ) TOGGLE_SNAP_TO_GRID = ToggleAction( label='_Snap to grid', @@ -327,8 +336,8 @@ TOGGLE_SNAP_TO_GRID = ToggleAction( TOGGLE_HIDE_DISABLED_BLOCKS = ToggleAction( label='Hide _Disabled Blocks', tooltip='Toggle visibility of disabled blocks and connections', - stock_id=gtk.STOCK_MISSING_IMAGE, - keypresses=(gtk.keysyms.d, gtk.gdk.CONTROL_MASK), + stock_id=Gtk.STOCK_MISSING_IMAGE, + keypresses=(Gdk.KEY_d, Gdk.ModifierType.CONTROL_MASK), ) TOGGLE_HIDE_VARIABLES = ToggleAction( label='Hide Variables', @@ -339,9 +348,9 @@ TOGGLE_HIDE_VARIABLES = ToggleAction( TOGGLE_FLOW_GRAPH_VAR_EDITOR = ToggleAction( label='Show _Variable Editor', tooltip='Show the variable editor. Modify variables and imports in this flow graph', - stock_id=gtk.STOCK_EDIT, + stock_id=Gtk.STOCK_EDIT, default=True, - keypresses=(gtk.keysyms.e, gtk.gdk.CONTROL_MASK), + keypresses=(Gdk.KEY_e, Gdk.ModifierType.CONTROL_MASK), preference_name='variable_editor_visable', ) TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR = ToggleAction( @@ -376,42 +385,42 @@ TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY = ToggleAction( 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), + stock_id=Gtk.STOCK_CONNECT, +# keypresses=(Gdk.KEY_c, NO_MODS_MASK), ) BLOCK_CUT = Action( label='Cu_t', tooltip='Cut', - stock_id=gtk.STOCK_CUT, - keypresses=(gtk.keysyms.x, gtk.gdk.CONTROL_MASK), + stock_id=Gtk.STOCK_CUT, + keypresses=(Gdk.KEY_x, Gdk.ModifierType.CONTROL_MASK), ) BLOCK_COPY = Action( label='_Copy', tooltip='Copy', - stock_id=gtk.STOCK_COPY, - keypresses=(gtk.keysyms.c, gtk.gdk.CONTROL_MASK), + stock_id=Gtk.STOCK_COPY, + keypresses=(Gdk.KEY_c, Gdk.ModifierType.CONTROL_MASK), ) BLOCK_PASTE = Action( label='_Paste', tooltip='Paste', - stock_id=gtk.STOCK_PASTE, - keypresses=(gtk.keysyms.v, gtk.gdk.CONTROL_MASK), + stock_id=Gtk.STOCK_PASTE, + keypresses=(Gdk.KEY_v, Gdk.ModifierType.CONTROL_MASK), ) ERRORS_WINDOW_DISPLAY = Action( label='Flowgraph _Errors', tooltip='View flow graph errors', - stock_id=gtk.STOCK_DIALOG_ERROR, + stock_id=Gtk.STOCK_DIALOG_ERROR, ) TOGGLE_CONSOLE_WINDOW = ToggleAction( label='Show _Console Panel', tooltip='Toggle visibility of the console', - keypresses=(gtk.keysyms.r, gtk.gdk.CONTROL_MASK), + keypresses=(Gdk.KEY_r, Gdk.ModifierType.CONTROL_MASK), preference_name='console_window_visible' ) TOGGLE_BLOCKS_WINDOW = ToggleAction( label='Show _Block Tree Panel', tooltip='Toggle visibility of the block tree widget', - keypresses=(gtk.keysyms.b, gtk.gdk.CONTROL_MASK), + keypresses=(Gdk.KEY_b, Gdk.ModifierType.CONTROL_MASK), preference_name='blocks_window_visible' ) TOGGLE_SCROLL_LOCK = ToggleAction( @@ -422,106 +431,106 @@ TOGGLE_SCROLL_LOCK = ToggleAction( ABOUT_WINDOW_DISPLAY = Action( label='_About', tooltip='About this program', - stock_id=gtk.STOCK_ABOUT, + 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), + stock_id=Gtk.STOCK_HELP, + keypresses=(Gdk.KEY_F1, NO_MODS_MASK), ) TYPES_WINDOW_DISPLAY = Action( label='_Types', tooltip='Types color mapping', - stock_id=gtk.STOCK_DIALOG_INFO, + 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), + stock_id=Gtk.STOCK_CONVERT, + keypresses=(Gdk.KEY_F5, NO_MODS_MASK), ) FLOW_GRAPH_EXEC = Action( label='_Execute', tooltip='Execute the flow graph', - stock_id=gtk.STOCK_MEDIA_PLAY, - keypresses=(gtk.keysyms.F6, NO_MODS_MASK), + stock_id=Gtk.STOCK_MEDIA_PLAY, + keypresses=(Gdk.KEY_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), + stock_id=Gtk.STOCK_STOP, + keypresses=(Gdk.KEY_F7, NO_MODS_MASK), ) FLOW_GRAPH_SCREEN_CAPTURE = Action( label='Screen Ca_pture', tooltip='Create a screen capture of the flow graph', - stock_id=gtk.STOCK_PRINT, - keypresses=(gtk.keysyms.Print, NO_MODS_MASK), + stock_id=Gtk.STOCK_PRINT, + keypresses=(Gdk.KEY_Print, NO_MODS_MASK), ) PORT_CONTROLLER_DEC = Action( - keypresses=(gtk.keysyms.minus, NO_MODS_MASK, gtk.keysyms.KP_Subtract, NO_MODS_MASK), + keypresses=(Gdk.KEY_minus, NO_MODS_MASK, Gdk.KEY_KP_Subtract, NO_MODS_MASK), ) PORT_CONTROLLER_INC = Action( - keypresses=(gtk.keysyms.plus, NO_MODS_MASK, gtk.keysyms.KP_Add, NO_MODS_MASK), + keypresses=(Gdk.KEY_plus, NO_MODS_MASK, Gdk.KEY_KP_Add, NO_MODS_MASK), ) BLOCK_INC_TYPE = Action( - keypresses=(gtk.keysyms.Down, NO_MODS_MASK), + keypresses=(Gdk.KEY_Down, NO_MODS_MASK), ) BLOCK_DEC_TYPE = Action( - keypresses=(gtk.keysyms.Up, NO_MODS_MASK), + keypresses=(Gdk.KEY_Up, NO_MODS_MASK), ) RELOAD_BLOCKS = Action( label='Reload _Blocks', tooltip='Reload Blocks', - stock_id=gtk.STOCK_REFRESH + stock_id=Gtk.STOCK_REFRESH ) FIND_BLOCKS = Action( label='_Find Blocks', tooltip='Search for a block by name (and key)', - stock_id=gtk.STOCK_FIND, - keypresses=(gtk.keysyms.f, gtk.gdk.CONTROL_MASK, - gtk.keysyms.slash, NO_MODS_MASK), + stock_id=Gtk.STOCK_FIND, + keypresses=(Gdk.KEY_f, Gdk.ModifierType.CONTROL_MASK, + Gdk.KEY_slash, NO_MODS_MASK), ) CLEAR_CONSOLE = Action( label='_Clear Console', tooltip='Clear Console', - stock_id=gtk.STOCK_CLEAR, + stock_id=Gtk.STOCK_CLEAR, ) SAVE_CONSOLE = Action( label='_Save Console', tooltip='Save Console', - stock_id=gtk.STOCK_SAVE, + stock_id=Gtk.STOCK_SAVE, ) OPEN_HIER = Action( label='Open H_ier', tooltip='Open the source of the selected hierarchical block', - stock_id=gtk.STOCK_JUMP_TO, + stock_id=Gtk.STOCK_JUMP_TO, ) BUSSIFY_SOURCES = Action( label='Toggle So_urce Bus', tooltip='Gang source ports into a single bus port', - stock_id=gtk.STOCK_JUMP_TO, + stock_id=Gtk.STOCK_JUMP_TO, ) BUSSIFY_SINKS = Action( label='Toggle S_ink Bus', tooltip='Gang sink ports into a single bus port', - stock_id=gtk.STOCK_JUMP_TO, + stock_id=Gtk.STOCK_JUMP_TO, ) XML_PARSER_ERRORS_DISPLAY = Action( label='_Parser Errors', tooltip='View errors that occurred while parsing XML files', - stock_id=gtk.STOCK_DIALOG_ERROR, + stock_id=Gtk.STOCK_DIALOG_ERROR, ) FLOW_GRAPH_OPEN_QSS_THEME = Action( label='Set Default QT GUI _Theme', tooltip='Set a default QT Style Sheet file to use for QT GUI', - stock_id=gtk.STOCK_OPEN, + stock_id=Gtk.STOCK_OPEN, ) TOOLS_RUN_FDESIGN = Action( label='Filter Design Tool', tooltip='Execute gr_filter_design', - stock_id=gtk.STOCK_EXECUTE, + stock_id=Gtk.STOCK_EXECUTE, ) TOOLS_MORE_TO_COME = Action( label='More to come', diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py index a4819b973c..0c18836c4e 100644 --- a/grc/gui/Bars.py +++ b/grc/gui/Bars.py @@ -17,9 +17,11 @@ 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 __future__ import absolute_import +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import GObject from . import Actions @@ -60,9 +62,10 @@ TOOLBAR_LIST = ( Actions.OPEN_HIER, ) + # The list of actions and categories for the menu bar. MENU_BAR_LIST = ( - (gtk.Action('File', '_File', None, None), [ + (Gtk.Action(name='File', label='_File'), [ 'flow_graph_new', Actions.FLOW_GRAPH_OPEN, 'flow_graph_recent', @@ -75,7 +78,7 @@ MENU_BAR_LIST = ( Actions.FLOW_GRAPH_CLOSE, Actions.APPLICATION_QUIT, ]), - (gtk.Action('Edit', '_Edit', None, None), [ + (Gtk.Action(name='Edit', label='_Edit'), [ Actions.FLOW_GRAPH_UNDO, Actions.FLOW_GRAPH_REDO, None, @@ -87,7 +90,7 @@ MENU_BAR_LIST = ( None, Actions.BLOCK_ROTATE_CCW, Actions.BLOCK_ROTATE_CW, - (gtk.Action('Align', '_Align', None, None), Actions.BLOCK_ALIGNMENTS), + (Gtk.Action('Align', '_Align', None, None), Actions.BLOCK_ALIGNMENTS), None, Actions.BLOCK_ENABLE, Actions.BLOCK_DISABLE, @@ -95,7 +98,7 @@ MENU_BAR_LIST = ( None, Actions.BLOCK_PARAM_MODIFY, ]), - (gtk.Action('View', '_View', None, None), [ + (Gtk.Action(name='View', label='_View'), [ Actions.TOGGLE_BLOCKS_WINDOW, None, Actions.TOGGLE_CONSOLE_WINDOW, @@ -117,12 +120,12 @@ MENU_BAR_LIST = ( Actions.ERRORS_WINDOW_DISPLAY, Actions.FIND_BLOCKS, ]), - (gtk.Action('Run', '_Run', None, None), [ + (Gtk.Action(name='Run', label='_Run'), [ Actions.FLOW_GRAPH_GEN, Actions.FLOW_GRAPH_EXEC, Actions.FLOW_GRAPH_KILL, ]), - (gtk.Action('Tools', '_Tools', None, None), [ + (Gtk.Action(name='Tools', label='_Tools'), [ Actions.TOOLS_RUN_FDESIGN, Actions.FLOW_GRAPH_OPEN_QSS_THEME, None, @@ -130,7 +133,7 @@ MENU_BAR_LIST = ( None, Actions.TOOLS_MORE_TO_COME, ]), - (gtk.Action('Help', '_Help', None, None), [ + (Gtk.Action(name='Help', label='_Help'), [ Actions.HELP_WINDOW_DISPLAY, Actions.TYPES_WINDOW_DISPLAY, Actions.XML_PARSER_ERRORS_DISPLAY, @@ -139,6 +142,7 @@ MENU_BAR_LIST = ( ]), ) + # The list of actions for the context menu. CONTEXT_MENU_LIST = [ Actions.BLOCK_CUT, @@ -152,7 +156,7 @@ CONTEXT_MENU_LIST = [ Actions.BLOCK_DISABLE, Actions.BLOCK_BYPASS, None, - (gtk.Action('More', '_More', None, None), [ + (Gtk.Action(name='More', label='_More'), [ Actions.BLOCK_CREATE_HIER, Actions.OPEN_HIER, None, @@ -189,13 +193,13 @@ class SubMenuCreator(object): def _fill_flow_graph_new_submenu(self, action): """Sub menu to create flow-graph with pre-set generate mode""" - menu = gtk.Menu() + menu = Gtk.Menu() for key, name, default in self.generate_modes: if default: item = Actions.FLOW_GRAPH_NEW.create_menu_item() item.set_label(name) else: - item = gtk.MenuItem(name, use_underline=False) + item = Gtk.MenuItem(name=name, use_underline=False) item.connect('activate', self.callback_adaptor, (action, key)) menu.append(item) menu.show_all() @@ -203,12 +207,12 @@ class SubMenuCreator(object): def _fill_flow_graph_recent_submenu(self, action): """menu showing recent flow-graphs""" - import Preferences - menu = gtk.Menu() + from . import Preferences + menu = Gtk.Menu() recent_files = Preferences.get_recent_files() if len(recent_files) > 0: for i, file_name in enumerate(recent_files): - item = gtk.MenuItem("%d. %s" % (i+1, file_name), use_underline=False) + item = Gtk.MenuItem(name="%d. %s" % (i+1, file_name), use_underline=False) item.connect('activate', self.callback_adaptor, (action, file_name)) menu.append(item) @@ -217,7 +221,7 @@ class SubMenuCreator(object): return None -class Toolbar(gtk.Toolbar, SubMenuCreator): +class Toolbar(Gtk.Toolbar, SubMenuCreator): """The gtk toolbar with actions added from the toolbar list.""" def __init__(self, generate_modes, action_handler_callback): @@ -226,23 +230,25 @@ class Toolbar(gtk.Toolbar, SubMenuCreator): 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) + GObject.GObject.__init__(self) + self.set_style(Gtk.ToolbarStyle.ICONS) SubMenuCreator.__init__(self, generate_modes, action_handler_callback) for action in TOOLBAR_LIST: if isinstance(action, tuple) and isinstance(action[1], str): # create a button with a sub-menu - action[0].set_tool_item_type(gtk.MenuToolButton) + # TODO: Fix later + #action[0].set_tool_item_type(Gtk.MenuToolButton) item = action[0].create_tool_item() - self.create_submenu(action, item) - self.refresh_submenus() + #self.create_submenu(action, item) + #self.refresh_submenus() elif action is None: - item = gtk.SeparatorToolItem() + item = Gtk.SeparatorToolItem() else: - action.set_tool_item_type(gtk.ToolButton) + #TODO: Fix later + #action.set_tool_item_type(Gtk.ToolButton) item = action.create_tool_item() # this reset of the tooltip property is required # (after creating the tool item) for the tooltip to show @@ -255,14 +261,14 @@ class MenuHelperMixin(object): def _fill_menu(self, actions, menu=None): """Create a menu from list of actions""" - menu = menu or gtk.Menu() + menu = menu or Gtk.Menu() for item in actions: if isinstance(item, tuple): menu_item = self._make_sub_menu(*item) elif isinstance(item, str): menu_item = getattr(self, 'create_' + item)() elif item is None: - menu_item = gtk.SeparatorMenuItem() + menu_item = Gtk.SeparatorMenuItem() else: menu_item = item.create_menu_item() menu.append(menu_item) @@ -276,7 +282,7 @@ class MenuHelperMixin(object): return main -class MenuBar(gtk.MenuBar, MenuHelperMixin, SubMenuCreator): +class MenuBar(Gtk.MenuBar, MenuHelperMixin, SubMenuCreator): """The gtk menu bar with actions added from the menu bar list.""" def __init__(self, generate_modes, action_handler_callback): @@ -286,13 +292,13 @@ class MenuBar(gtk.MenuBar, MenuHelperMixin, SubMenuCreator): 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) + GObject.GObject.__init__(self) SubMenuCreator.__init__(self, generate_modes, action_handler_callback) for main_action, actions in MENU_BAR_LIST: self.append(self._make_sub_menu(main_action, actions)) def create_flow_graph_new(self): - main = gtk.ImageMenuItem(gtk.STOCK_NEW) + main = Gtk.ImageMenuItem(Gtk.STOCK_NEW) main.set_label(Actions.FLOW_GRAPH_NEW.get_label()) func = self._fill_flow_graph_new_submenu self.submenus.append((Actions.FLOW_GRAPH_NEW, func, main)) @@ -300,7 +306,7 @@ class MenuBar(gtk.MenuBar, MenuHelperMixin, SubMenuCreator): return main def create_flow_graph_recent(self): - main = gtk.ImageMenuItem(gtk.STOCK_OPEN) + main = Gtk.ImageMenuItem(Gtk.STOCK_OPEN) main.set_label(Actions.FLOW_GRAPH_OPEN_RECENT.get_label()) func = self._fill_flow_graph_recent_submenu self.submenus.append((Actions.FLOW_GRAPH_OPEN, func, main)) @@ -310,9 +316,9 @@ class MenuBar(gtk.MenuBar, MenuHelperMixin, SubMenuCreator): return main -class ContextMenu(gtk.Menu, MenuHelperMixin): +class ContextMenu(Gtk.Menu, MenuHelperMixin): """The gtk menu with actions added from the context menu list.""" def __init__(self): - gtk.Menu.__init__(self) + GObject.GObject.__init__(self) self._fill_menu(CONTEXT_MENU_LIST, self) diff --git a/grc/gui/Block.py b/grc/gui/Block.py index 55c8805fae..49bba4f3db 100644 --- a/grc/gui/Block.py +++ b/grc/gui/Block.py @@ -17,37 +17,24 @@ 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 pango - -from . import Actions, Colors, Utils, Constants - +from __future__ import absolute_import +import math +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('PangoCairo', '1.0') +from gi.repository import Gtk, Pango, PangoCairo + +from . import Actions, Colors, Utils + +from .Constants import ( + BLOCK_LABEL_PADDING, PORT_SPACING, PORT_SEPARATION, LABEL_SEPARATION, + PORT_BORDER_SEPARATION, POSSIBLE_ROTATIONS, BLOCK_FONT, PARAM_FONT +) from . Element import Element from ..core.Param import num_to_str -from ..core.utils import odict from ..core.utils.complexity import calculate_flowgraph_complexity from ..core.Block import Block as _Block -BLOCK_MARKUP_TMPL="""\ -#set $foreground = $block.is_valid() and 'black' or 'red' -<span foreground="$foreground" font_desc="$font"><b>$encode($block.get_name())</b></span>""" - -# Includes the additional complexity markup if enabled -COMMENT_COMPLEXITY_MARKUP_TMPL="""\ -#set $foreground = $block.get_enabled() and '#444' or '#888' -#if $complexity -<span foreground="#444" size="medium" font_desc="$font"><b>$encode($complexity)</b></span>#slurp -#end if -#if $complexity and $comment -<span></span> -#end if -#if $comment -<span foreground="$foreground" font_desc="$font">$encode($comment)</span>#slurp -#end if -""" - class Block(Element, _Block): """The graphical signal block.""" @@ -58,33 +45,17 @@ class Block(Element, _Block): Add graphics related params to the block. """ _Block.__init__(self, flow_graph, n) + self.W = self.H = 0 + self._add_param(key='_coordinate', name='GUI Coordinate', value='(0, 0)', + hide='all') + self._add_param(key='_rotation', name='GUI Rotation', value='0', + hide='all') - self.W = 0 - self.H = 0 - #add the position param - self.get_params().append(self.get_parent().get_parent().Param( - block=self, - n=odict({ - 'name': 'GUI Coordinate', - 'key': '_coordinate', - 'type': 'raw', - 'value': '(0, 0)', - 'hide': 'all', - }) - )) - self.get_params().append(self.get_parent().get_parent().Param( - block=self, - n=odict({ - 'name': 'GUI Rotation', - 'key': '_rotation', - 'type': 'raw', - 'value': '0', - 'hide': 'all', - }) - )) - Element.__init__(self) + Element.__init__(self) # needs the params self._comment_pixmap = None + self._bg_color = Colors.BLOCK_ENABLED_COLOR self.has_busses = [False, False] # source, sink + self.layouts = [] def get_coordinate(self): """ @@ -93,23 +64,13 @@ class Block(Element, _Block): Returns: the coordinate tuple (x, y) or (0, 0) if failure """ - proximity = Constants.BORDER_PROXIMITY_SENSITIVITY - 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 - proximity: - x = fgW - proximity - if y <= 0: - y = 0 - elif y >= fgH - proximity: - y = fgH - proximity - return (x, y) + try: + coor = self.get_param('_coordinate').get_value() # should evaluate to tuple + coor = tuple(int(x) for x in coor[1:-1].split(',')) except: - self.set_coordinate((0, 0)) - return (0, 0) + coor = 0, 0 + self.set_coordinate(coor) + return coor def set_coordinate(self, coor): """ @@ -124,41 +85,7 @@ class Block(Element, _Block): Utils.align_to_grid(coor[0] + offset_x) - offset_x, Utils.align_to_grid(coor[1] + offset_y) - offset_y ) - self.get_param('_coordinate').set_value(str(coor)) - - def bound_move_delta(self, delta_coor): - """ - Limit potential moves from exceeding the bounds of the canvas - - Args: - delta_coor: requested delta coordinate (dX, dY) to move - - Returns: - The delta coordinate possible to move while keeping the block on the canvas - or the input (dX, dY) on failure - """ - dX, dY = delta_coor - - try: - fgW, fgH = self.get_parent().get_size() - x, y = map(int, eval(self.get_param("_coordinate").get_value())) - if self.is_horizontal(): - sW, sH = self.W, self.H - else: - sW, sH = self.H, self.W - - if x + dX < 0: - dX = -x - elif dX + x + sW >= fgW: - dX = fgW - x - sW - if y + dY < 0: - dY = -y - elif dY + y + sH >= fgH: - dY = fgH - y - sH - except: - pass - - return ( dX, dY ) + self.get_param('_coordinate').set_value(repr(coor)) def get_rotation(self): """ @@ -168,11 +95,11 @@ class Block(Element, _Block): the rotation in degrees or 0 if failure """ try: #should evaluate to dict - rotation = eval(self.get_param('_rotation').get_value()) - return int(rotation) + rotation = int(self.get_param('_rotation').get_value()) except: - self.set_rotation(Constants.POSSIBLE_ROTATIONS[0]) - return Constants.POSSIBLE_ROTATIONS[0] + rotation = POSSIBLE_ROTATIONS[0] + self.set_rotation(rotation) + return rotation def set_rotation(self, rot): """ @@ -181,7 +108,7 @@ class Block(Element, _Block): Args: rot: the rotation in degrees """ - self.get_param('_rotation').set_value(str(rot)) + self.get_param('_rotation').set_value(repr(int(rot))) def create_shapes(self): """Update the block, parameters, and ports when a change occurs.""" @@ -192,71 +119,53 @@ class Block(Element, _Block): def create_labels(self): """Create the labels for the signal block.""" Element.create_labels(self) - self._bg_color = self.is_dummy_block and Colors.MISSING_BLOCK_BACKGROUND_COLOR or \ - self.get_bypassed() and Colors.BLOCK_BYPASSED_COLOR or \ - self.get_enabled() and Colors.BLOCK_ENABLED_COLOR or Colors.BLOCK_DISABLED_COLOR - - layouts = list() + self._bg_color = Colors.MISSING_BLOCK_BACKGROUND_COLOR if self.is_dummy_block else \ + Colors.BLOCK_BYPASSED_COLOR if self.get_bypassed() else \ + Colors.BLOCK_ENABLED_COLOR if self.get_enabled() else \ + Colors.BLOCK_DISABLED_COLOR + del self.layouts[:] #create the main layout - layout = gtk.DrawingArea().create_pango_layout('') - layouts.append(layout) - layout.set_markup(Utils.parse_template(BLOCK_MARKUP_TMPL, block=self, font=Constants.BLOCK_FONT)) + layout = Gtk.DrawingArea().create_pango_layout('') + layout.set_markup('<span foreground="{foreground}" font_desc="{font}"><b>{name}</b></span>'.format( + foreground='black' if self.is_valid() else 'red', font=BLOCK_FONT, name=Utils.encode(self.get_name()) + )) self.label_width, self.label_height = layout.get_pixel_size() + self.layouts.append(layout) #display the params if self.is_dummy_block: - markups = [ - '<span foreground="black" font_desc="{font}"><b>key: </b>{key}</span>' - ''.format(font=Constants.PARAM_FONT, key=self._key) - ] + markups = ['<span foreground="black" font_desc="{font}"><b>key: </b>{key}</span>'.format( + font=PARAM_FONT, key=self._key + )] else: - markups = [param.get_markup() for param in self.get_params() if param.get_hide() not in ('all', 'part')] + markups = [param.format_block_surface_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(Constants.LABEL_SEPARATION * pango.SCALE) + layout = Gtk.DrawingArea().create_pango_layout('') + layout.set_spacing(LABEL_SEPARATION*Pango.SCALE) layout.set_markup('\n'.join(markups)) - layouts.append(layout) + self.layouts.append(layout) w, h = layout.get_pixel_size() self.label_width = max(w, self.label_width) - self.label_height += h + Constants.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 + Constants.LABEL_SEPARATION - #create vertical and horizontal pixmaps - self.horizontal_label = pixmap - if self.is_vertical(): - self.vertical_label = self.get_parent().new_pixmap(height, width) - Utils.rotate_pixmap(gc, self.horizontal_label, self.vertical_label) + self.label_height += h + LABEL_SEPARATION + #calculate width and height needed - W = self.label_width + 2 * Constants.BLOCK_LABEL_PADDING + W = self.label_width + 2 * BLOCK_LABEL_PADDING def get_min_height_for_ports(): - visible_ports = filter(lambda p: not p.get_hide(), ports) - min_height = 2*Constants.PORT_BORDER_SEPARATION + len(visible_ports) * Constants.PORT_SEPARATION + visible_ports = [p for p in ports if not p.get_hide()] + min_height = 2*PORT_BORDER_SEPARATION + len(visible_ports) * PORT_SEPARATION if visible_ports: min_height -= ports[0].H return min_height H = max( [ # labels - self.label_height + 2 * Constants.BLOCK_LABEL_PADDING + self.label_height + 2 * BLOCK_LABEL_PADDING ] + [ # ports get_min_height_for_ports() for ports in (self.get_sources_gui(), self.get_sinks_gui()) ] + [ # bus ports only - 2 * Constants.PORT_BORDER_SEPARATION + - sum([port.H + Constants.PORT_SPACING for port in ports if port.get_type() == 'bus']) - Constants.PORT_SPACING + 2 * PORT_BORDER_SEPARATION + + sum([port.H + PORT_SPACING for port in ports if port.get_type() == 'bus']) - PORT_SPACING for ports in (self.get_sources_gui(), self.get_sinks_gui()) ] ) @@ -268,26 +177,31 @@ class Block(Element, _Block): self.create_comment_label() def create_comment_label(self): - comment = self.get_comment() # Returns None if there are no comments - complexity = None + markups = [] - # Show the flowgraph complexity on the top block if enabled + # Show the flow graph complexity on the top block if enabled if Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY.get_active() and self.get_key() == "options": - complexity = calculate_flowgraph_complexity(self.get_parent()) - complexity = "Complexity: {}bal".format(num_to_str(complexity)) + complexity = calculate_flowgraph_complexity(self.parent) + markups.append( + '<span foreground="#444" size="medium" font_desc="{font}">' + '<b>Complexity: {num}bal</b></span>'.format(num=num_to_str(complexity), font=BLOCK_FONT) + ) + comment = self.get_comment() # Returns None if there are no comments + if comment: + if markups: + markups.append('<span></span>') - layout = gtk.DrawingArea().create_pango_layout('') - layout.set_markup(Utils.parse_template(COMMENT_COMPLEXITY_MARKUP_TMPL, - block=self, - comment=comment, - complexity=complexity, - font=Constants.BLOCK_FONT)) + markups.append('<span foreground="{foreground}" font_desc="{font}">{comment}</span>'.format( + foreground='#444' if self.get_enabled() else '#888', font=BLOCK_FONT, comment=Utils.encode(comment) + )) + layout = Gtk.DrawingArea().create_pango_layout('') + layout.set_markup(''.join(markups)) # Setup the pixel map. Make sure that layout not empty width, height = layout.get_pixel_size() if width and height: - padding = Constants.BLOCK_LABEL_PADDING - pixmap = self.get_parent().new_pixmap(width + 2 * padding, + padding = BLOCK_LABEL_PADDING + pixmap = self.parent.new_pixmap(width + 2 * padding, height + 2 * padding) gc = pixmap.new_gc() gc.set_foreground(Colors.COMMENT_BACKGROUND_COLOR) @@ -298,29 +212,50 @@ class Block(Element, _Block): else: self._comment_pixmap = None - def draw(self, gc, window): + def draw(self, widget, cr): """ Draw the signal block with label and inputs/outputs. - - Args: - gc: the graphics context - window: the gtk window to draw on """ # draw ports for port in self.get_ports_gui(): - port.draw(gc, window) + port.draw(widget, cr) # draw main block - x, y = self.get_coordinate() - Element.draw( - self, gc, window, bg_color=self._bg_color, - border_color=self.is_highlighted() and Colors.HIGHLIGHT_COLOR or - self.is_dummy_block and Colors.MISSING_BLOCK_BORDER_COLOR or Colors.BORDER_COLOR, + border_color = ( + Colors.HIGHLIGHT_COLOR if self.is_highlighted() else + Colors.MISSING_BLOCK_BORDER_COLOR if self.is_dummy_block else + Colors.BORDER_COLOR ) - #draw label image + Element.draw(self, widget, cr, border_color, self._bg_color) + x, y = self.get_coordinate() + # create the image surface + width = self.label_width + height = self.label_height + cr.set_source_rgb(*self._bg_color) + cr.save() if self.is_horizontal(): - window.draw_drawable(gc, self.horizontal_label, 0, 0, x+Constants.BLOCK_LABEL_PADDING, y+(self.H-self.label_height)/2, -1, -1) + cr.translate(x + BLOCK_LABEL_PADDING, y + (self.H - self.label_height) / 2) elif self.is_vertical(): - window.draw_drawable(gc, self.vertical_label, 0, 0, x+(self.H-self.label_height)/2, y+Constants.BLOCK_LABEL_PADDING, -1, -1) + cr.translate(x + (self.H - self.label_height) / 2, y + BLOCK_LABEL_PADDING) + cr.rotate(-90 * math.pi / 180.) + cr.translate(-width, 0) + + # cr.rectangle(0, 0, width, height) + # cr.fill() + + # draw the layouts + h_off = 0 + for i, layout in enumerate(self.layouts): + w, h = layout.get_pixel_size() + if i == 0: + w_off = (width - w) / 2 + else: + w_off = 0 + cr.translate(w_off, h_off) + PangoCairo.update_layout(cr, layout) + PangoCairo.show_layout(cr, layout) + cr.translate(-w_off, -h_off) + h_off = h + h_off + LABEL_SEPARATION + cr.restore() def what_is_selected(self, coor, coor_m=None): """ @@ -344,8 +279,8 @@ class Block(Element, _Block): x, y = self.get_coordinate() if self.is_horizontal(): - y += self.H + Constants.BLOCK_LABEL_PADDING + y += self.H + BLOCK_LABEL_PADDING else: - x += self.H + Constants.BLOCK_LABEL_PADDING + x += self.H + BLOCK_LABEL_PADDING window.draw_drawable(gc, self._comment_pixmap, 0, 0, x, y, -1, -1) diff --git a/grc/gui/BlockTreeWindow.py b/grc/gui/BlockTreeWindow.py index f49eb6c4fe..89aac53a0e 100644 --- a/grc/gui/BlockTreeWindow.py +++ b/grc/gui/BlockTreeWindow.py @@ -17,71 +17,61 @@ 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 gobject +from __future__ import absolute_import +import six + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import GObject from . import Actions, Utils from . import Constants -NAME_INDEX = 0 -KEY_INDEX = 1 -DOC_INDEX = 2 - -DOC_MARKUP_TMPL = """\ -#set $docs = [] -#if $doc.get('') - #set $docs += $doc.pop('').splitlines() + [''] -#end if -#for b, d in $doc.iteritems() - #set $docs += ['--- {0} ---'.format(b)] + d.splitlines() + [''] -#end for -#set $len_out = 0 -#for $n, $line in $enumerate($docs[:-1]) -#if $n - -#end if -$encode($line)#slurp -#set $len_out += $len($line) -#if $n > 10 or $len_out > 500 - -...#slurp -#break -#end if -#end for -#if $len_out == 0 -undocumented#slurp -#end if""" - -CAT_MARKUP_TMPL = """ -#set $name = $cat[-1] -#if len($cat) > 1 -Category: $cat[-1] -## -#elif $name == 'Core' -Module: Core - -This subtree is meant for blocks included with GNU Radio (in-tree). -## -#elif $name == $default_module -This subtree holds all blocks (from OOT modules) that specify no module name. \ -The module name is the root category enclosed in square brackets. - -Please consider contacting OOT module maintainer for any block in here \ -and kindly ask to update their GRC Block Descriptions or Block Tree to include a module name. -#else -Module: $name -## -#end if -""".strip() - - -class BlockTreeWindow(gtk.VBox): +NAME_INDEX, KEY_INDEX, DOC_INDEX = range(3) + + +def _format_doc(doc): + docs = [] + if doc.get(''): + docs += doc.pop('').splitlines() + [''] + for block_name, docstring in six.iteritems(doc): + docs.append('--- {0} ---'.format(block_name)) + docs += docstring.splitlines() + docs.append('') + out = '' + for n, line in enumerate(docs[:-1]): + if n: + out += '\n' + out += Utils.encode(line) + if n > 10 or len(out) > 500: + out += '\n...' + break + return out or 'undocumented' + + +def _format_cat_tooltip(category): + tooltip = '{}: {}'.format('Category' if len(category) > 1 else 'Module', category[-1]) + + if category == ('Core',): + tooltip += '\n\nThis subtree is meant for blocks included with GNU Radio (in-tree).' + + elif category == (Constants.DEFAULT_BLOCK_MODULE_NAME,): + tooltip += '\n\n' + Constants.DEFAULT_BLOCK_MODULE_TOOLTIP + + return tooltip + + +class BlockTreeWindow(Gtk.VBox): """The block selection panel.""" - def __init__(self, platform, get_flow_graph): + __gsignals__ = { + 'create_new_block': (GObject.SIGNAL_RUN_FIRST, None, (str,)) + } + + def __init__(self, platform): """ BlockTreeWindow constructor. Create a tree view of the possible blocks in the platform. @@ -90,58 +80,52 @@ class BlockTreeWindow(gtk.VBox): Args: platform: the particular platform will all block prototypes - get_flow_graph: get the selected flow graph """ - gtk.VBox.__init__(self) + Gtk.VBox.__init__(self) self.platform = platform - self.get_flow_graph = get_flow_graph # search entry - self.search_entry = gtk.Entry() + self.search_entry = Gtk.Entry() try: - self.search_entry.set_icon_from_stock(gtk.ENTRY_ICON_PRIMARY, gtk.STOCK_FIND) - self.search_entry.set_icon_activatable(gtk.ENTRY_ICON_PRIMARY, False) - self.search_entry.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, gtk.STOCK_CLOSE) + self.search_entry.set_icon_from_stock(Gtk.EntryIconPosition.PRIMARY, Gtk.STOCK_FIND) + self.search_entry.set_icon_activatable(Gtk.EntryIconPosition.PRIMARY, False) + self.search_entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, Gtk.STOCK_CLOSE) self.search_entry.connect('icon-release', self._handle_icon_event) except AttributeError: pass # no icon for old pygtk self.search_entry.connect('changed', self._update_search_tree) self.search_entry.connect('key-press-event', self._handle_search_key_press) - self.pack_start(self.search_entry, False) + self.pack_start(self.search_entry, False, False, 0) # make the tree model for holding blocks and a temporary one for search results - self.treestore = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING) - self.treestore_search = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING) + self.treestore = Gtk.TreeStore(GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING) + self.treestore_search = Gtk.TreeStore(GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING) - self.treeview = gtk.TreeView(self.treestore) + self.treeview = Gtk.TreeView(self.treestore) self.treeview.set_enable_search(False) # disable pop up search box self.treeview.set_search_column(-1) # really disable search self.treeview.set_headers_visible(False) - self.treeview.add_events(gtk.gdk.BUTTON_PRESS_MASK) + self.treeview.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self.treeview.connect('button-press-event', self._handle_mouse_button_press) self.treeview.connect('key-press-event', self._handle_search_key_press) - self.treeview.get_selection().set_mode('single') - renderer = gtk.CellRendererText() - column = gtk.TreeViewColumn('Blocks', renderer, text=NAME_INDEX) + self.treeview.get_selection().set_mode(Gtk.SelectionMode.SINGLE) + 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 + self.treeview.set_tooltip_column(DOC_INDEX) # setup sort order column.set_sort_column_id(0) - self.treestore.set_sort_column_id(0, gtk.SORT_ASCENDING) + self.treestore.set_sort_column_id(0, Gtk.SortType.ASCENDING) # setup drag and drop - self.treeview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, Constants.DND_TARGETS, gtk.gdk.ACTION_COPY) + self.treeview.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, Constants.DND_TARGETS, Gdk.DragAction.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 = Gtk.ScrolledWindow() + scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolled_window.add_with_viewport(self.treeview) scrolled_window.set_size_request(Constants.DEFAULT_BLOCKS_WINDOW_WIDTH, -1) - self.pack_start(scrolled_window) + self.pack_start(scrolled_window, True, True, 0) # map categories to iters, automatic mapping for root self._categories = {tuple(): None} self._categories_search = {tuple(): None} @@ -154,7 +138,7 @@ class BlockTreeWindow(gtk.VBox): def repopulate(self): self.clear() - for block in self.platform.blocks.itervalues(): + for block in six.itervalues(self.platform.blocks): if block.category: self.add_block(block) self.expand_module_in_tree() @@ -188,15 +172,13 @@ class BlockTreeWindow(gtk.VBox): iter_ = treestore.insert_before(categories[parent_category[:-1]], None) treestore.set_value(iter_, NAME_INDEX, parent_cat_name) treestore.set_value(iter_, KEY_INDEX, '') - treestore.set_value(iter_, DOC_INDEX, Utils.parse_template( - CAT_MARKUP_TMPL, cat=parent_category, default_module=Constants.DEFAULT_BLOCK_MODULE_NAME)) + treestore.set_value(iter_, DOC_INDEX, _format_cat_tooltip(parent_cat_name)) categories[parent_category] = iter_ - # add block iter_ = treestore.insert_before(categories[category], None) treestore.set_value(iter_, NAME_INDEX, block.get_name()) treestore.set_value(iter_, KEY_INDEX, block.get_key()) - treestore.set_value(iter_, DOC_INDEX, Utils.parse_template(DOC_MARKUP_TMPL, doc=block.get_doc())) + treestore.set_value(iter_, DOC_INDEX, _format_doc(block.get_doc())) def update_docs(self): """Update the documentation column of every block""" @@ -206,8 +188,7 @@ class BlockTreeWindow(gtk.VBox): return # category node, no doc string key = model.get_value(iter_, KEY_INDEX) block = self.platform.blocks[key] - doc = Utils.parse_template(DOC_MARKUP_TMPL, doc=block.get_doc()) - model.set_value(iter_, DOC_INDEX, doc) + model.set_value(iter_, DOC_INDEX, _format_doc(block.get_doc())) self.treestore.foreach(update_doc) self.treestore_search.foreach(update_doc) @@ -226,16 +207,6 @@ class BlockTreeWindow(gtk.VBox): treestore, iter = selection.get_selected() return iter and treestore.get_value(iter, KEY_INDEX) or '' - 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) - return True - return False - def _expand_category(self): treestore, iter = self.treeview.get_selection().get_selected() if iter and treestore.iter_has_child(iter): @@ -246,9 +217,9 @@ class BlockTreeWindow(gtk.VBox): ## Event Handlers ############################################################ def _handle_icon_event(self, widget, icon, event): - if icon == gtk.ENTRY_ICON_PRIMARY: + if icon == Gtk.EntryIconPosition.PRIMARY: pass - elif icon == gtk.ENTRY_ICON_SECONDARY: + elif icon == Gtk.EntryIconPosition.SECONDARY: widget.set_text('') self.search_entry.hide() @@ -258,8 +229,8 @@ class BlockTreeWindow(gtk.VBox): self.treeview.set_model(self.treestore) self.expand_module_in_tree() else: - matching_blocks = filter(lambda b: key in b.get_key().lower() or key in b.get_name().lower(), - self.platform.blocks.values()) + matching_blocks = [b for b in list(self.platform.blocks.values()) + if key in b.get_key().lower() or key in b.get_name().lower()] self.treestore_search.clear() self._categories_search = {tuple(): None} @@ -270,7 +241,7 @@ class BlockTreeWindow(gtk.VBox): def _handle_search_key_press(self, widget, event): """Handle Return and Escape key events in search entry and treeview""" - if event.keyval == gtk.keysyms.Return: + if event.keyval == Gdk.KEY_Return: # add block on enter if widget == self.search_entry: @@ -280,24 +251,28 @@ class BlockTreeWindow(gtk.VBox): selected = self.treestore_search.iter_children(selected) if selected is not None: key = self.treestore_search.get_value(selected, KEY_INDEX) - if key: self.get_flow_graph().add_new_block(key) + if key: self.emit('create_new_block', key) elif widget == self.treeview: - self._add_selected_block() or self._expand_category() + key = self._get_selected_block_key() + if key: + self.emit('create_new_block', key) + else: + self._expand_category() else: return False # propagate event - elif event.keyval == gtk.keysyms.Escape: + elif event.keyval == Gdk.KEY_Escape: # reset the search self.search_entry.set_text('') self.search_entry.hide() - elif (event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.f) \ - or event.keyval == gtk.keysyms.slash: + elif (event.get_state() & Gdk.ModifierType.CONTROL_MASK and event.keyval == Gdk.KEY_f) \ + or event.keyval == Gdk.KEY_slash: # propagation doesn't work although treeview search is disabled =( # manually trigger action... Actions.FIND_BLOCKS.activate() - elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.b: + elif event.get_state() & Gdk.ModifierType.CONTROL_MASK and event.keyval == Gdk.KEY_b: # ugly... Actions.TOGGLE_BLOCKS_WINDOW.activate() @@ -320,5 +295,7 @@ class BlockTreeWindow(gtk.VBox): 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() + if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS: + key = self._get_selected_block_key() + if key: + self.emit('create_new_block', key) diff --git a/grc/gui/Colors.py b/grc/gui/Colors.py index d322afa410..6ee31e5e18 100644 --- a/grc/gui/Colors.py +++ b/grc/gui/Colors.py @@ -16,35 +16,90 @@ 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 """ -try: - 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('#444444') - # missing blocks stuff - MISSING_BLOCK_BACKGROUND_COLOR = get_color('#FFF2F2') - MISSING_BLOCK_BORDER_COLOR = get_color('red') - #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('#FFFFFF') - COMMENT_BACKGROUND_COLOR = get_color('#F3F3F3') - FLOWGRAPH_EDGE_COLOR = COMMENT_BACKGROUND_COLOR - #block color constants - BLOCK_ENABLED_COLOR = get_color('#F1ECFF') - BLOCK_DISABLED_COLOR = get_color('#CCCCCC') - BLOCK_BYPASSED_COLOR = get_color('#F4FF81') - #connection color constants - CONNECTION_ENABLED_COLOR = get_color('black') - CONNECTION_DISABLED_COLOR = get_color('#BBBBBB') - CONNECTION_ERROR_COLOR = get_color('red') -except: - print 'Unable to import Colors' - -DEFAULT_DOMAIN_COLOR_CODE = '#777777' + +from __future__ import absolute_import + + +def get_color(color_code): + chars_per_color = 2 if len(color_code) > 4 else 1 + offsets = range(1, 3 * chars_per_color + 1, chars_per_color) + return tuple(int(color_code[o:o + 2], 16) / 255.0 for o in offsets) + +HIGHLIGHT_COLOR = get_color('#00FFFF') +BORDER_COLOR = get_color('#444444') + +# Missing blocks stuff +MISSING_BLOCK_BACKGROUND_COLOR = get_color('#FFF2F2') +MISSING_BLOCK_BORDER_COLOR = get_color('#FF0000') + +# Flow graph color constants +FLOWGRAPH_BACKGROUND_COLOR = get_color('#FFFFFF') +COMMENT_BACKGROUND_COLOR = get_color('#F3F3F3') +FLOWGRAPH_EDGE_COLOR = COMMENT_BACKGROUND_COLOR + +# Block color constants +BLOCK_ENABLED_COLOR = get_color('#F1ECFF') +BLOCK_DISABLED_COLOR = get_color('#CCCCCC') +BLOCK_BYPASSED_COLOR = get_color('#F4FF81') + +# Connection color constants +CONNECTION_ENABLED_COLOR = get_color('#000000') +CONNECTION_DISABLED_COLOR = get_color('#BBBBBB') +CONNECTION_ERROR_COLOR = get_color('#FF0000') + +DEFAULT_DOMAIN_COLOR = get_color('#777777') + +################################################################################# +# param box colors +################################################################################# + +from gi.repository import Gdk + + +def _color_parse(color_code): + color = Gdk.RGBA() + color.parse(color_code) + return color + +COMPLEX_COLOR_SPEC = _color_parse('#3399FF') +FLOAT_COLOR_SPEC = _color_parse('#FF8C69') +INT_COLOR_SPEC = _color_parse('#00FF99') +SHORT_COLOR_SPEC = _color_parse('#FFFF66') +BYTE_COLOR_SPEC = _color_parse('#FF66FF') + +ID_COLOR_SPEC = _color_parse('#DDDDDD') +WILDCARD_COLOR_SPEC = _color_parse('#FFFFFF') + +COMPLEX_VECTOR_COLOR_SPEC = _color_parse('#3399AA') +FLOAT_VECTOR_COLOR_SPEC = _color_parse('#CC8C69') +INT_VECTOR_COLOR_SPEC = _color_parse('#00CC99') +SHORT_VECTOR_COLOR_SPEC = _color_parse('#CCCC33') +BYTE_VECTOR_COLOR_SPEC = _color_parse('#CC66CC') + +PARAM_ENTRY_COLORS = { + + # Number types + 'complex': COMPLEX_COLOR_SPEC, + 'real': FLOAT_COLOR_SPEC, + 'float': FLOAT_COLOR_SPEC, + 'int': INT_COLOR_SPEC, + + # Vector types + 'complex_vector': COMPLEX_VECTOR_COLOR_SPEC, + 'real_vector': FLOAT_VECTOR_COLOR_SPEC, + 'float_vector': FLOAT_VECTOR_COLOR_SPEC, + 'int_vector': INT_VECTOR_COLOR_SPEC, + + # Special + 'bool': INT_COLOR_SPEC, + 'hex': INT_COLOR_SPEC, + 'string': BYTE_VECTOR_COLOR_SPEC, + 'id': ID_COLOR_SPEC, + 'stream_id': ID_COLOR_SPEC, + 'grid_pos': INT_VECTOR_COLOR_SPEC, + 'notebook': INT_VECTOR_COLOR_SPEC, + 'raw': WILDCARD_COLOR_SPEC, +} + +PARAM_ENTRY_DEFAULT_COLOR = _color_parse('#FFFFFF') +PARAM_ENTRY_ENUM_CUSTOM_COLOR = _color_parse('#EEEEEE') diff --git a/grc/gui/Config.py b/grc/gui/Config.py index 9b0c5d4afe..b6556ad724 100644 --- a/grc/gui/Config.py +++ b/grc/gui/Config.py @@ -17,8 +17,11 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ +from __future__ import absolute_import, print_function + import sys import os + from ..core.Config import Config as _Config from . import Constants @@ -57,7 +60,7 @@ class Config(_Config): raise Exception() return value except: - print >> sys.stderr, "Error: invalid 'canvas_default_size' setting." + print("Error: invalid 'canvas_default_size' setting.", file=sys.stderr) return Constants.DEFAULT_CANVAS_SIZE_DEFAULT @property @@ -69,6 +72,6 @@ class Config(_Config): raise Exception() except: font_size = Constants.DEFAULT_FONT_SIZE - print >> sys.stderr, "Error: invalid 'canvas_font_size' setting." + print("Error: invalid 'canvas_font_size' setting.", file=sys.stderr) return font_size diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py index 50361c19d0..d893060aa6 100644 --- a/grc/gui/Connection.py +++ b/grc/gui/Connection.py @@ -17,14 +17,15 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -import gtk +from __future__ import absolute_import -import Colors -import Utils -from Constants import CONNECTOR_ARROW_BASE, CONNECTOR_ARROW_HEIGHT -from Element import Element +from six.moves import map + +from . import Colors +from . import Utils +from .Constants import CONNECTOR_ARROW_BASE, CONNECTOR_ARROW_HEIGHT +from .Element import Element -from ..core.Constants import GR_MESSAGE_DOMAIN from ..core.Connection import Connection as _Connection @@ -73,27 +74,31 @@ class Connection(Element, _Connection): self._source_coor = None #get the source coordinate try: - connector_length = self.get_source().get_connector_length() + connector_length = self.source_port.get_connector_length() except: return - self.x1, self.y1 = Utils.get_rotated_coordinate((connector_length, 0), self.get_source().get_rotation()) + self.x1, self.y1 = Utils.get_rotated_coordinate((connector_length, 0), self.source_port.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()) + connector_length = self.sink_port.get_connector_length() + CONNECTOR_ARROW_HEIGHT + self.x2, self.y2 = Utils.get_rotated_coordinate((-connector_length, 0), self.sink_port.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()), + Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, -CONNECTOR_ARROW_BASE/2), self.sink_port.get_rotation()), + Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, CONNECTOR_ARROW_BASE/2), self.sink_port.get_rotation()), ] - source_domain = self.get_source().get_domain() - sink_domain = self.get_sink().get_domain() - self.line_attributes[0] = 2 if source_domain != sink_domain else 0 - self.line_attributes[1] = gtk.gdk.LINE_DOUBLE_DASH \ - if not source_domain == sink_domain == GR_MESSAGE_DOMAIN \ - else gtk.gdk.LINE_ON_OFF_DASH - get_domain_color = lambda d: Colors.get_color(( - self.get_parent().get_parent().domains.get(d, {}) - ).get('color') or Colors.DEFAULT_DOMAIN_COLOR_CODE) + source_domain = self.source_port.get_domain() + sink_domain = self.sink_port.get_domain() + # self.line_attributes[0] = 2 if source_domain != sink_domain else 0 + # self.line_attributes[1] = Gdk.LINE_DOUBLE_DASH \ + # if not source_domain == sink_domain == GR_MESSAGE_DOMAIN \ + # else Gdk.LINE_ON_OFF_DASH + + def get_domain_color(domain_name): + domain = self.parent_platform.domains.get(domain_name, {}) + color_spec = domain.get('color') + return Colors.get_color(color_spec) if color_spec else \ + Colors.DEFAULT_DOMAIN_COLOR + self._color = get_domain_color(source_domain) self._bg_color = get_domain_color(sink_domain) self._arrow_color = self._bg_color if self.is_valid() else Colors.CONNECTION_ERROR_COLOR @@ -103,12 +108,12 @@ class Connection(Element, _Connection): """Calculate coordinates.""" self.clear() #FIXME do i want this here? #source connector - source = self.get_source() + source = self.source_port 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() + sink = self.sink_port X, Y = sink.get_connector_coordinate() x2, y2 = self.x2 + X, self.y2 + Y self.add_line((x2, y2), (X, Y)) @@ -126,7 +131,7 @@ class Connection(Element, _Connection): #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]) + p1, p2 = list(map(int, points[0][0])), list(map(int, points[0][1])) self.add_line((x1, y1), p1) self.add_line(p1, p2) self.add_line((x2, y2), p2) @@ -143,16 +148,12 @@ class Connection(Element, _Connection): self.add_line((x1, y1), points[0]) self.add_line((x2, y2), points[0]) - def draw(self, gc, window): + def draw(self, widget, cr): """ Draw the connection. - - Args: - gc: the graphics context - window: the gtk window to draw on """ - sink = self.get_sink() - source = self.get_source() + sink = self.sink_port + source = self.source_port #check for changes 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(): @@ -171,11 +172,12 @@ class Connection(Element, _Connection): Colors.CONNECTION_DISABLED_COLOR if not self.get_enabled() else color ) - Element.draw(self, gc, window, mod_color(self._color), mod_color(self._bg_color)) + Element.draw(self, widget, cr, mod_color(self._color), mod_color(self._bg_color)) # draw arrow on sink port - try: - gc.set_foreground(mod_color(self._arrow_color)) - gc.set_line_attributes(0, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER) - window.draw_polygon(gc, True, self._arrow) - except: - pass + cr.set_source_rgb(*self._arrow_color) + # TODO: gc.set_line_attributes(0, Gdk.LINE_SOLID, Gdk.CAP_BUTT, Gdk.JOIN_MITER) + cr.move_to(*self._arrow[0]) + cr.line_to(*self._arrow[1]) + cr.line_to(*self._arrow[2]) + cr.close_path() + cr.fill() diff --git a/grc/gui/Constants.py b/grc/gui/Constants.py index 022564cd77..035a7f8ca9 100644 --- a/grc/gui/Constants.py +++ b/grc/gui/Constants.py @@ -17,7 +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 """ -import gtk +from __future__ import absolute_import +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk from ..core.Constants import * @@ -36,7 +39,7 @@ NEW_FLOGRAPH_TITLE = 'untitled' MIN_WINDOW_WIDTH = 600 MIN_WINDOW_HEIGHT = 400 # dialog constraints -MIN_DIALOG_WIDTH = 500 +MIN_DIALOG_WIDTH = 600 MIN_DIALOG_HEIGHT = 500 # default sizes DEFAULT_BLOCKS_WINDOW_WIDTH = 100 @@ -53,7 +56,7 @@ PARAM_FONT = "Sans 7.5" STATE_CACHE_SIZE = 42 # Shared targets for drag and drop of blocks -DND_TARGETS = [('STRING', gtk.TARGET_SAME_APP, 0)] +DND_TARGETS = [('STRING', Gtk.TargetFlags.SAME_APP, 0)] # label constraint dimensions LABEL_SEPARATION = 3 @@ -84,11 +87,8 @@ 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 +SCROLL_PROXIMITY_SENSITIVITY = 50 # When the window has to be scrolled, move it this distance in the required direction. SCROLL_DISTANCE = 15 @@ -96,6 +96,13 @@ SCROLL_DISTANCE = 15 # How close the mouse click can be to a line and register a connection select. LINE_SELECT_SENSITIVITY = 5 +DEFAULT_BLOCK_MODULE_TOOLTIP = """\ +This subtree holds all blocks (from OOT modules) that specify no module name. \ +The module name is the root category enclosed in square brackets. + +Please consider contacting OOT module maintainer for any block in here \ +and kindly ask to update their GRC Block Descriptions or Block Tree to include a module name.""" + def update_font_size(font_size): global PORT_SEPARATION, BLOCK_FONT, PORT_FONT, PARAM_FONT, FONT_SIZE diff --git a/grc/gui/Dialogs.py b/grc/gui/Dialogs.py index 1d114356c8..8f0f60d764 100644 --- a/grc/gui/Dialogs.py +++ b/grc/gui/Dialogs.py @@ -17,7 +17,12 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -import gtk +from __future__ import absolute_import +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import GObject import sys from distutils.spawn import find_executable @@ -26,7 +31,7 @@ from . import Utils, Actions from ..core import Messages -class SimpleTextDisplay(gtk.TextView): +class SimpleTextDisplay(Gtk.TextView): """A non editable gtk text view.""" def __init__(self, text=''): @@ -36,13 +41,13 @@ class SimpleTextDisplay(gtk.TextView): Args: text: the text to display (string) """ - text_buffer = gtk.TextBuffer() + text_buffer = Gtk.TextBuffer() text_buffer.set_text(text) self.set_text = text_buffer.set_text - gtk.TextView.__init__(self, text_buffer) + GObject.GObject.__init__(self) self.set_editable(False) self.set_cursor_visible(False) - self.set_wrap_mode(gtk.WRAP_WORD_CHAR) + self.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) class TextDisplay(SimpleTextDisplay): @@ -85,7 +90,8 @@ class TextDisplay(SimpleTextDisplay): if self.scroll_lock: buffer = self.get_buffer() buffer.move_mark(buffer.get_insert(), buffer.get_end_iter()) - self.scroll_to_mark(buffer.get_insert(), 0.0) + # TODO: Fix later + #self.scroll_to_mark(buffer.get_insert(), 0.0) def clear(self): buffer = self.get_buffer() @@ -110,18 +116,18 @@ class TextDisplay(SimpleTextDisplay): def populate_popup(self, view, menu): """Create a popup menu for the scroll lock and clear functions""" - menu.append(gtk.SeparatorMenuItem()) + menu.append(Gtk.SeparatorMenuItem()) - lock = gtk.CheckMenuItem("Scroll Lock") + lock = Gtk.CheckMenuItem("Scroll Lock") menu.append(lock) lock.set_active(self.scroll_lock) lock.connect('activate', self.scroll_back_cb, view) - save = gtk.ImageMenuItem(gtk.STOCK_SAVE) + save = Gtk.ImageMenuItem(Gtk.STOCK_SAVE) menu.append(save) save.connect('activate', self.save_cb, view) - clear = gtk.ImageMenuItem(gtk.STOCK_CLEAR) + clear = Gtk.ImageMenuItem(Gtk.STOCK_CLEAR) menu.append(clear) clear.connect('activate', self.clear_cb, view) menu.show_all() @@ -133,9 +139,9 @@ def MessageDialogHelper(type, buttons, title=None, markup=None, default_response Create a modal message dialog and run it. Args: - type: the type of message: gtk.MESSAGE_INFO, gtk.MESSAGE_WARNING, gtk.MESSAGE_QUESTION or gtk.MESSAGE_ERROR + type: the type of message: Gtk.MessageType.INFO, Gtk.MessageType.WARNING, Gtk.MessageType.QUESTION or Gtk.MessageType.ERROR 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 + Gtk.ButtonsType.NONE, Gtk.ButtonsType.OK, Gtk.ButtonsType.CLOSE, Gtk.ButtonsType.CANCEL, Gtk.ButtonsType.YES_NO, Gtk.ButtonsType.OK_CANCEL Args: title: the title of the window (string) @@ -146,7 +152,7 @@ def MessageDialogHelper(type, buttons, title=None, markup=None, default_response Returns: the gtk response from run() """ - message_dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, type, buttons) + message_dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, type, buttons) if title: message_dialog.set_title(title) if markup: message_dialog.set_markup(markup) if extra_buttons: message_dialog.add_buttons(*extra_buttons) @@ -156,28 +162,24 @@ def MessageDialogHelper(type, buttons, title=None, markup=None, default_response return response -ERRORS_MARKUP_TMPL="""\ -#for $i, $err_msg in enumerate($errors) -<b>Error $i:</b> -$encode($err_msg.replace('\t', ' ')) - -#end for""" - - -def ErrorsDialog(flowgraph): MessageDialogHelper( - type=gtk.MESSAGE_ERROR, - buttons=gtk.BUTTONS_CLOSE, - title='Flow Graph Errors', - markup=Utils.parse_template(ERRORS_MARKUP_TMPL, errors=flowgraph.get_error_messages()), -) +def ErrorsDialog(flowgraph): + MessageDialogHelper( + type=Gtk.MessageType.ERROR, + buttons=Gtk.ButtonsType.CLOSE, + title='Flow Graph Errors', + markup='\n\n'.join( + '<b>Error {num}:</b>\n{msg}'.format(num=i, msg=Utils.encode(msg.replace('\t', ' '))) + for i, msg in enumerate(flowgraph.get_error_messages()) + ), + ) -class AboutDialog(gtk.AboutDialog): +class AboutDialog(Gtk.AboutDialog): """A cute little about dialog.""" def __init__(self, config): """AboutDialog constructor.""" - gtk.AboutDialog.__init__(self) + GObject.GObject.__init__(self) self.set_name(config.name) self.set_version(config.version) self.set_license(config.license) @@ -188,8 +190,8 @@ class AboutDialog(gtk.AboutDialog): def HelpDialog(): MessageDialogHelper( - type=gtk.MESSAGE_INFO, - buttons=gtk.BUTTONS_CLOSE, + type=Gtk.MessageType.INFO, + buttons=Gtk.ButtonsType.CLOSE, title='Help', markup="""\ <b>Usage Tips</b> @@ -203,32 +205,28 @@ def HelpDialog(): MessageDialogHelper( * See the menu for other keyboard shortcuts.""") -COLORS_DIALOG_MARKUP_TMPL = """\ -<b>Color Mapping</b> - -#if $colors - #set $max_len = max([len(color[0]) for color in $colors]) + 10 - #for $title, $color_spec in $colors -<span background="$color_spec"><tt>$($encode($title).center($max_len))</tt></span> - #end for -#end if -""" - def TypesDialog(platform): + colors = platform.get_colors() + max_len = 10 + max(len(name) for name, code in colors) + + message = '\n'.join( + '<span background="{color}"><tt>{name}</tt></span>' + ''.format(color=color, name=Utils.encode(name).center(max_len)) + for name, color in colors + ) MessageDialogHelper( - type=gtk.MESSAGE_INFO, - buttons=gtk.BUTTONS_CLOSE, - title='Types', - markup=Utils.parse_template(COLORS_DIALOG_MARKUP_TMPL, - colors=platform.get_colors()) + type=Gtk.MessageType.INFO, + buttons=Gtk.ButtonsType.CLOSE, + title='Types - Color Mapping', + markup=message ) def MissingXTermDialog(xterm): MessageDialogHelper( - type=gtk.MESSAGE_WARNING, - buttons=gtk.BUTTONS_OK, + type=Gtk.MessageType.WARNING, + buttons=Gtk.ButtonsType.OK, title='Warning: missing xterm executable', markup=("The xterm executable {0!r} is missing.\n\n" "You can change this setting in your gnuradio.conf, in " @@ -242,28 +240,28 @@ def ChooseEditorDialog(config): # Give the option to either choose an editor or use the default # Always return true/false so the caller knows it was successful buttons = ( - 'Choose Editor', gtk.RESPONSE_YES, - 'Use Default', gtk.RESPONSE_NO, - gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL + 'Choose Editor', Gtk.ResponseType.YES, + 'Use Default', Gtk.ResponseType.NO, + Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL ) response = MessageDialogHelper( - gtk.MESSAGE_QUESTION, gtk.BUTTONS_NONE, 'Choose Editor', - 'Would you like to choose the editor to use?', gtk.RESPONSE_YES, buttons + Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, 'Choose Editor', + 'Would you like to choose the editor to use?', Gtk.ResponseType.YES, buttons ) # Handle the inital default/choose/cancel response # User wants to choose the editor to use - if response == gtk.RESPONSE_YES: - file_dialog = gtk.FileChooserDialog( + if response == Gtk.ResponseType.YES: + file_dialog = Gtk.FileChooserDialog( 'Select an Editor...', None, - gtk.FILE_CHOOSER_ACTION_OPEN, - ('gtk-cancel', gtk.RESPONSE_CANCEL, 'gtk-open', gtk.RESPONSE_OK) + Gtk.FileChooserAction.OPEN, + ('gtk-cancel', Gtk.ResponseType.CANCEL, 'gtk-open', Gtk.ResponseType.OK) ) file_dialog.set_select_multiple(False) file_dialog.set_local_only(True) file_dialog.set_current_folder('/usr/bin') try: - if file_dialog.run() == gtk.RESPONSE_OK: + if file_dialog.run() == Gtk.ResponseType.OK: config.editor = file_path = file_dialog.get_filename() file_dialog.destroy() return file_path @@ -271,7 +269,7 @@ def ChooseEditorDialog(config): file_dialog.destroy() # Go with the default editor - elif response == gtk.RESPONSE_NO: + elif response == Gtk.ResponseType.NO: # Determine the platform try: process = None diff --git a/grc/gui/DrawingArea.py b/grc/gui/DrawingArea.py index 6a1df27a8c..33c669c99f 100644 --- a/grc/gui/DrawingArea.py +++ b/grc/gui/DrawingArea.py @@ -17,15 +17,14 @@ 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 __future__ import absolute_import +from gi.repository import Gtk, Gdk, GObject -from Constants import MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT, DND_TARGETS -import Colors +from . import Constants, Colors -class DrawingArea(gtk.DrawingArea): + +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. @@ -39,52 +38,47 @@ class DrawingArea(gtk.DrawingArea): Args: main_window: the main_window containing all flow graphs """ + Gtk.DrawingArea.__init__(self) + + self._flow_graph = flow_graph + + self.zoom_factor = 1.0 self.ctrl_mask = False self.mod1_mask = False - self._flow_graph = flow_graph - gtk.DrawingArea.__init__(self) - self.set_size_request(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT) + self.button_state = [False] * 10 + + # 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('draw', self.draw) 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.connect('scroll-event', self._handle_mouse_scroll) 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 | \ - gtk.gdk.FOCUS_CHANGE_MASK + Gdk.EventMask.BUTTON_PRESS_MASK | + Gdk.EventMask.POINTER_MOTION_MASK | + Gdk.EventMask.BUTTON_RELEASE_MASK | + Gdk.EventMask.SCROLL_MASK | + Gdk.EventMask.LEAVE_NOTIFY_MASK | + Gdk.EventMask.ENTER_NOTIFY_MASK + #Gdk.EventMask.FOCUS_CHANGE_MASK ) - #setup drag and drop - self.drag_dest_set(gtk.DEST_DEFAULT_ALL, DND_TARGETS, gtk.gdk.ACTION_COPY) + + # setup drag and drop + self.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) self.connect('drag-data-received', self._handle_drag_data_received) - #setup the focus flag + self.drag_dest_set_target_list(None) + self.drag_dest_add_text_targets() + + # setup the focus flag self._focus_flag = False self.get_focus_flag = lambda: self._focus_flag def _handle_notify_event(widget, event, focus_flag): self._focus_flag = focus_flag self.connect('leave-notify-event', _handle_notify_event, False) self.connect('enter-notify-event', _handle_notify_event, True) - self.set_flags(gtk.CAN_FOCUS) # self.set_can_focus(True) - self.connect('focus-out-event', self._handle_focus_lost_event) - - def new_pixmap(self, width, height): - return gtk.gdk.Pixmap(self.window, width, height, -1) - - def get_screenshot(self, transparent_bg=False): - pixmap = self._pixmap - W, H = pixmap.get_size() - pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, W, H) - pixbuf.fill(0xFF + Colors.FLOWGRAPH_BACKGROUND_COLOR.pixel << 8) - pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, W-1, H-1) - if transparent_bg: - bgc = Colors.FLOWGRAPH_BACKGROUND_COLOR - pixbuf = pixbuf.add_alpha(True, bgc.red, bgc.green, bgc.blue) - return pixbuf - + # todo: fix +# self.set_flags(Gtk.CAN_FOCUS) # self.set_can_focus(True) +# self.connect('focus-out-event', self._handle_focus_lost_event) ########################################################################## ## Handlers @@ -93,83 +87,120 @@ class DrawingArea(gtk.DrawingArea): """ Handle a drag and drop by adding a block at the given coordinate. """ - self._flow_graph.add_new_block(selection_data.data, (x, y)) + self._flow_graph.add_new_block(selection_data.get_text(), (x, y)) def _handle_mouse_scroll(self, widget, event): - if event.state & gtk.gdk.SHIFT_MASK: - if event.direction == gtk.gdk.SCROLL_UP: - event.direction = gtk.gdk.SCROLL_LEFT - else: - event.direction = gtk.gdk.SCROLL_RIGHT + if event.get_state() & Gdk.ModifierType.SHIFT_MASK: + if event.direction == Gdk.ScrollDirection.UP: + event.direction = Gdk.ScrollDirection.LEFT + else: + event.direction = Gdk.ScrollDirection.RIGHT + + elif event.get_state() & Gdk.ModifierType.CONTROL_MASK: + change = 1.2 if event.direction == Gdk.ScrollDirection.UP else 1/1.2 + zoom_factor = min(max(self.zoom_factor * change, 0.1), 5.0) + + if zoom_factor != self.zoom_factor: + self.zoom_factor = zoom_factor + self.queue_draw() + return True + + return False def _handle_mouse_button_press(self, widget, event): """ Forward button click information to the flow graph. """ self.grab_focus() - self.ctrl_mask = event.state & gtk.gdk.CONTROL_MASK - self.mod1_mask = event.state & gtk.gdk.MOD1_MASK - 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, - ) + self.ctrl_mask = event.get_state() & Gdk.ModifierType.CONTROL_MASK + self.mod1_mask = event.get_state() & Gdk.ModifierType.MOD1_MASK + self.button_state[event.button] = True + + if event.button == 1: + self._flow_graph.handle_mouse_selector_press( + double_click=(event.type == Gdk.EventType._2BUTTON_PRESS), + coordinate=self._translate_event_coords(event), + ) + elif event.button == 3: + self._flow_graph.handle_mouse_context_press( + coordinate=self._translate_event_coords(event), + 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.mod1_mask = event.state & gtk.gdk.MOD1_MASK - if event.button == 1: self._flow_graph.handle_mouse_selector_release( - coordinate=(event.x, event.y), - ) + self.ctrl_mask = event.get_state() & Gdk.ModifierType.CONTROL_MASK + self.mod1_mask = event.get_state() & Gdk.ModifierType.MOD1_MASK + self.button_state[event.button] = False + if event.button == 1: + self._flow_graph.handle_mouse_selector_release( + coordinate=self._translate_event_coords(event), + ) 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.mod1_mask = event.state & gtk.gdk.MOD1_MASK + self.ctrl_mask = event.get_state() & Gdk.ModifierType.CONTROL_MASK + self.mod1_mask = event.get_state() & Gdk.ModifierType.MOD1_MASK + + if self.button_state[1]: + self._auto_scroll(event) + self._flow_graph.handle_mouse_motion( - coordinate=(event.x, event.y), + coordinate=self._translate_event_coords(event), + button1_pressed=self.button_state[1] ) + def _auto_scroll(self, event): + x, y = event.x, event.y + scrollbox = self.get_parent().get_parent() + + w, h = self._flow_graph.get_max_coords(initial=(x, y)) + self.set_size_request(w + 100, h + 100) + + def scroll(pos, adj): + """scroll if we moved near the border""" + adj_val = adj.get_value() + adj_len = adj.get_page_size() + if pos - adj_val > adj_len - Constants.SCROLL_PROXIMITY_SENSITIVITY: + adj.set_value(adj_val + Constants.SCROLL_DISTANCE) + adj.emit('changed') + elif pos - adj_val < Constants.SCROLL_PROXIMITY_SENSITIVITY: + adj.set_value(adj_val - Constants.SCROLL_DISTANCE) + adj.emit('changed') + + scroll(x, scrollbox.get_hadjustment()) + scroll(y, scrollbox.get_vadjustment()) + def _handle_window_realize(self, widget): """ Called when the window is realized. Update the flowgraph, which calls new pixmap. """ self._flow_graph.update() + w, h = self._flow_graph.get_max_coords() + self.set_size_request(w + 100, h + 100) - 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 draw(self, widget, cr): + width = widget.get_allocated_width() + height = widget.get_allocated_height() + cr.set_source_rgb(*Colors.FLOWGRAPH_BACKGROUND_COLOR) + cr.rectangle(0, 0, width, height) - 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) - # draw a light grey line on the bottom and right end of the canvas. - # this is useful when the theme uses the same panel bg color as the canvas - W, H = self._pixmap.get_size() - gc.set_foreground(Colors.FLOWGRAPH_EDGE_COLOR) - self.window.draw_line(gc, 0, H-1, W, H-1) - self.window.draw_line(gc, W-1, 0, W-1, H) + cr.scale(self.zoom_factor, self.zoom_factor) + cr.fill() + + self._flow_graph.draw(widget, cr) + + def _translate_event_coords(self, event): + return event.x / self.zoom_factor, event.y / self.zoom_factor def _handle_focus_lost_event(self, widget, event): # don't clear selection while context menu is active - if not self._flow_graph.get_context_menu().flags() & gtk.VISIBLE: + if not self._flow_graph.get_context_menu().flags() & Gtk.VISIBLE: self._flow_graph.unselect() self._flow_graph.update_selected() self._flow_graph.queue_draw() diff --git a/grc/gui/Element.py b/grc/gui/Element.py index 9385424772..4e88df375f 100644 --- a/grc/gui/Element.py +++ b/grc/gui/Element.py @@ -17,10 +17,15 @@ 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 LINE_SELECT_SENSITIVITY -from Constants import POSSIBLE_ROTATIONS +from __future__ import absolute_import +from .Constants import LINE_SELECT_SENSITIVITY +from .Constants import POSSIBLE_ROTATIONS -import gtk +import gi +from six.moves import zip +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import Gdk class Element(object): @@ -38,9 +43,11 @@ class Element(object): self.set_coordinate((0, 0)) self.clear() self.set_highlighted(False) - self.line_attributes = [ - 0, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER - ] + self.line_attributes = [] + """ # No idea where this is in pygobject + 0, Gdk.LINE_SOLID, Gdk.CAP_BUTT, Gdk.JOIN_MITER + ]""" + def is_horizontal(self, rotation=None): """ @@ -85,29 +92,33 @@ class Element(object): self.clear() for child in self.get_children(): child.create_shapes() - def draw(self, gc, window, border_color, bg_color): + def draw(self, widget, cr, border_color, bg_color): """ Draw in the given window. Args: - gc: the graphics context - window: the gtk window to draw on + widget: + cr: border_color: the color for lines and rectangle borders bg_color: the color for the inside of the rectangle """ X, Y = self.get_coordinate() - gc.set_line_attributes(*self.line_attributes) + # TODO: gc.set_line_attributes(*self.line_attributes) 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) + cr.set_source_rgb(*bg_color) + cr.rectangle(aX, aY, W, H) + cr.fill() + cr.set_source_rgb(*border_color) + cr.rectangle(aX, aY, W, H) + cr.stroke() + for (x1, y1), (x2, y2) in self._lines_list: - gc.set_foreground(border_color) - gc.set_background(bg_color) - window.draw_line(gc, X+x1, Y+y1, X+x2, Y+y2) + cr.set_source_rgb(*border_color) + cr.move_to(X + x1, Y + y1) + cr.line_to(X + x2, Y + y2) + cr.stroke() def rotate(self, rotation): """ diff --git a/grc/gui/Executor.py b/grc/gui/Executor.py index bf9eecb9a8..a8c67986e5 100644 --- a/grc/gui/Executor.py +++ b/grc/gui/Executor.py @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA -import gobject +from __future__ import absolute_import import os import threading import shlex @@ -24,6 +24,8 @@ import sys import re from distutils.spawn import find_executable +from gi.repository import GLib + from ..core import Messages @@ -44,8 +46,7 @@ class ExecFlowGraphThread(threading.Thread): self.update_callback = callback try: - self.process = self._popen() - self.page.set_proc(self.process) + self.process = self.page.process = self._popen() self.update_callback() self.start() except Exception as e: @@ -56,7 +57,7 @@ class ExecFlowGraphThread(threading.Thread): """ Execute this python flow graph. """ - run_command = self.page.get_flow_graph().get_option('run_command') + run_command = self.page.flow_graph.get_option('run_command') generator = self.page.get_generator() try: @@ -86,20 +87,20 @@ class ExecFlowGraphThread(threading.Thread): def run(self): """ Wait on the executing process by reading from its stdout. - Use gobject.idle_add when calling functions that modify gtk objects. + 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) + GLib.idle_add(Messages.send_verbose_exec, r) r = os.read(self.process.stdout.fileno(), 1024) self.process.poll() - gobject.idle_add(self.done) + GLib.idle_add(self.done) def done(self): """Perform end of execution tasks.""" Messages.send_end_exec(self.process.returncode) - self.page.set_proc(None) + self.page.process = None self.update_callback() diff --git a/grc/gui/FileDialogs.py b/grc/gui/FileDialogs.py index e9430b1f88..3ee715dac6 100644 --- a/grc/gui/FileDialogs.py +++ b/grc/gui/FileDialogs.py @@ -17,16 +17,19 @@ 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 \ +from __future__ import absolute_import +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import GObject + +from .Dialogs import MessageDialogHelper +from .Constants import \ DEFAULT_FILE_PATH, IMAGE_FILE_EXTENSION, TEXT_FILE_EXTENSION, \ NEW_FLOGRAPH_TITLE -import Preferences +from . import Preferences from os import path -import Utils +from . import Utils ################################################## # Constants @@ -37,52 +40,45 @@ SAVE_CONSOLE = 'save console' SAVE_IMAGE = 'save image' OPEN_QSS_THEME = 'open qss theme' -FILE_OVERWRITE_MARKUP_TMPL="""\ -File <b>$encode($filename)</b> Exists!\nWould you like to overwrite the existing file?""" - -FILE_DNE_MARKUP_TMPL="""\ -File <b>$encode($filename)</b> Does not Exist!""" - - # File Filters def get_flow_graph_files_filter(): - filter = gtk.FileFilter() + filter = Gtk.FileFilter() filter.set_name('Flow Graph Files') filter.add_pattern('*'+Preferences.file_extension()) return filter def get_text_files_filter(): - filter = gtk.FileFilter() + filter = Gtk.FileFilter() filter.set_name('Text Files') filter.add_pattern('*'+TEXT_FILE_EXTENSION) return filter def get_image_files_filter(): - filter = gtk.FileFilter() + filter = Gtk.FileFilter() filter.set_name('Image Files') filter.add_pattern('*'+IMAGE_FILE_EXTENSION) return filter def get_all_files_filter(): - filter = gtk.FileFilter() + filter = Gtk.FileFilter() filter.set_name('All Files') filter.add_pattern('*') return filter def get_qss_themes_filter(): - filter = gtk.FileFilter() + filter = Gtk.FileFilter() filter.set_name('QSS Themes') filter.add_pattern('*.qss') return filter # File Dialogs -class FileDialogHelper(gtk.FileChooserDialog): +class FileDialogHelper(Gtk.FileChooserDialog): """ A wrapper class for the gtk file chooser dialog. Implement a file chooser dialog with only necessary parameters. @@ -95,11 +91,11 @@ class FileDialogHelper(gtk.FileChooserDialog): Use standard settings: no multiple selection, local files only, and the * filter. Args: - action: gtk.FILE_CHOOSER_ACTION_OPEN or gtk.FILE_CHOOSER_ACTION_SAVE + action: Gtk.FileChooserAction.OPEN or Gtk.FileChooserAction.SAVE 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)) + ok_stock = {Gtk.FileChooserAction.OPEN : 'gtk-open', Gtk.FileChooserAction.SAVE : 'gtk-save'}[action] + GObject.GObject.__init__(self, title, None, action, ('gtk-cancel', Gtk.ResponseType.CANCEL, ok_stock, Gtk.ResponseType.OK)) self.set_select_multiple(False) self.set_local_only(True) self.add_filter(get_all_files_filter()) @@ -117,25 +113,25 @@ class FileDialog(FileDialogHelper): """ 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...') + FileDialogHelper.__init__(self, Gtk.FileChooserAction.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...') + FileDialogHelper.__init__(self, Gtk.FileChooserAction.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)) elif self.type == SAVE_CONSOLE: - FileDialogHelper.__init__(self, gtk.FILE_CHOOSER_ACTION_SAVE, 'Save Console to a File...') + FileDialogHelper.__init__(self, Gtk.FileChooserAction.SAVE, 'Save Console to a File...') self.add_and_set_filter(get_text_files_filter()) file_path = path.splitext(path.basename(current_file_path))[0] self.set_current_name(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...') + FileDialogHelper.__init__(self, Gtk.FileChooserAction.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 elif self.type == OPEN_QSS_THEME: - FileDialogHelper.__init__(self, gtk.FILE_CHOOSER_ACTION_OPEN, 'Open a QSS theme...') + FileDialogHelper.__init__(self, Gtk.FileChooserAction.OPEN, 'Open a QSS theme...') self.add_and_set_filter(get_qss_themes_filter()) self.set_select_multiple(False) self.set_current_folder(path.dirname(current_file_path)) #current directory @@ -160,7 +156,7 @@ class FileDialog(FileDialogHelper): Returns: the complete file path """ - if gtk.FileChooserDialog.run(self) != gtk.RESPONSE_OK: return None #response was cancel + if Gtk.FileChooserDialog.run(self) != Gtk.ResponseType.OK: return None #response was cancel ############################################# # Handle Save Dialogs ############################################# @@ -176,9 +172,10 @@ class FileDialog(FileDialogHelper): 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() + Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, 'Confirm Overwrite!', + 'File <b>{filename}</b> Exists!\nWould you like to overwrite the existing file?' + ''.format(filename=Utils.encode(filename)), + ) == Gtk.ResponseType.NO: return self.get_rectified_filename() return filename ############################################# # Handle Open Dialogs @@ -188,8 +185,8 @@ class FileDialog(FileDialogHelper): 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), + Gtk.MessageType.WARNING, Gtk.ButtonsType.CLOSE, 'Cannot Open!', + 'File <b>{filename}</b> Does not Exist!'.format(filename=Utils.encode(filename)), ) return self.get_rectified_filename() return filenames @@ -230,7 +227,7 @@ class SaveScreenShotDialog(SaveImageFileDialog): def __init__(self, current_file_path=''): SaveImageFileDialog.__init__(self, current_file_path) - self._button = button = gtk.CheckButton('_Background transparent') + self._button = button = Gtk.CheckButton('_Background transparent') self._button.set_active(Preferences.screen_shot_background_transparent()) self.set_extra_widget(button) diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index f98aec41d5..d7745a529d 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -17,16 +17,20 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ +from __future__ import absolute_import + import functools import random from distutils.spawn import find_executable from itertools import chain, count from operator import methodcaller -import gobject +import six +from six.moves import filter + +from gi.repository import GObject -from . import Actions, Colors, Constants, Utils, Bars, Dialogs -from .Constants import SCROLL_PROXIMITY_SENSITIVITY, SCROLL_DISTANCE +from . import Actions, Colors, Utils, Bars, Dialogs from .Element import Element from .external_editor import ExternalEditor @@ -76,19 +80,20 @@ class FlowGraph(Element, _Flowgraph): Returns: a unique id """ + block_ids = set(b.get_id() for b in self.blocks) for index in count(): block_id = '{}_{}'.format(base_id, index) - if block_id not in (b.get_id() for b in self.blocks): + if block_id not in block_ids: break return block_id def install_external_editor(self, param): - target = (param.get_parent().get_id(), param.get_key()) + target = (param.parent_block.get_id(), param.get_key()) if target in self._external_updaters: editor = self._external_updaters[target] else: - config = self.get_parent().config + config = self.parent_platform.config editor = (find_executable(config.editor) or Dialogs.ChooseEditorDialog(config)) if not editor: @@ -98,7 +103,7 @@ class FlowGraph(Element, _Flowgraph): editor = self._external_updaters[target] = ExternalEditor( editor=editor, name=target[0], value=param.get_value(), - callback=functools.partial(gobject.idle_add, updater) + callback=functools.partial(GObject.idle_add, updater) ) editor.start() try: @@ -107,7 +112,7 @@ class FlowGraph(Element, _Flowgraph): # Problem launching the editor. Need to select a new editor. Messages.send('>>> Error opening an external editor. Please select a different editor.\n') # Reset the editor to force the user to select a new one. - self.get_parent().config.editor = '' + self.parent_platform.config.editor = '' def handle_external_editor_change(self, new_value, target): try: @@ -126,9 +131,7 @@ class FlowGraph(Element, _Flowgraph): ########################################################################### 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_scroll_pane(self): return self.drawing_area.get_parent().get_parent() def get_ctrl_mask(self): return self.drawing_area.ctrl_mask def get_mod1_mask(self): return self.drawing_area.mod1_mask def new_pixmap(self, *args): return self.get_drawing_area().new_pixmap(*args) @@ -143,14 +146,11 @@ class FlowGraph(Element, _Flowgraph): """ id = self._get_unique_id(key) #calculate the position coordinate - W, H = self.get_size() 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) * min(h_adj.page_size, W) + - h_adj.get_value()), - int(random.uniform(.25, .75) * min(v_adj.page_size, H) + - v_adj.get_value()), + int(random.uniform(.25, .75)*h_adj.get_page_size() + h_adj.get_value()), + int(random.uniform(.25, .75)*v_adj.get_page_size() + v_adj.get_value()), ) #get the new block block = self.new_block(key) @@ -172,7 +172,8 @@ class FlowGraph(Element, _Flowgraph): """ #get selected blocks blocks = self.get_selected_blocks() - if not blocks: return None + if not blocks: + return None #calc x and y min x_min, y_min = blocks[0].get_coordinate() for block in blocks: @@ -180,10 +181,10 @@ class FlowGraph(Element, _Flowgraph): 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, + connections = list(filter( + lambda c: c.source_block in blocks and c.sink_block in blocks, self.connections, - ) + )) clipboard = ( (x_min, y_min), [block.export_data() for block in blocks], @@ -204,26 +205,26 @@ class FlowGraph(Element, _Flowgraph): #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 + x_off = h_adj.get_value() - x_min + h_adj.get_page_size() / 4 + y_off = v_adj.get_value() - y_min + v_adj.get_page_size() / 4 if len(self.get_elements()) <= 1: x_off, y_off = 0, 0 #create blocks for block_n in blocks_n: - block_key = block_n.find('key') - if block_key == 'options': continue + block_key = block_n.get('key') + if block_key == 'options': + continue block = self.new_block(block_key) if not block: continue # unknown block was pasted (e.g. dummy block) selected.add(block) #set params - params = dict((n.find('key'), n.find('value')) - for n in block_n.findall('param')) + params = {n['key']: n['value'] for n in block_n.get('param', [])} if block_key == 'epy_block': block.get_param('_io_cache').set_value(params.pop('_io_cache')) block.get_param('_source_code').set_value(params.pop('_source_code')) block.rewrite() # this creates the other params - for param_key, param_value in params.iteritems(): + for param_key, param_value in six.iteritems(params): #setup id parameter if param_key == 'id': old_id2block[param_value] = block @@ -238,8 +239,8 @@ class FlowGraph(Element, _Flowgraph): 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')) + source = old_id2block[connection_n.get('source_block_id')].get_source(connection_n.get('source_key')) + sink = old_id2block[connection_n.get('sink_block_id')].get_sink(connection_n.get('sink_key')) self.connect(source, sink) #set all pasted elements selected for block in selected: selected = selected.union(set(block.get_connections())) @@ -284,7 +285,8 @@ class FlowGraph(Element, _Flowgraph): """ changed = False for selected_block in self.get_selected_blocks(): - if selected_block.set_enabled(enable): changed = True + if selected_block.set_enabled(enable): + changed = True return changed def bypass_selected(self): @@ -298,7 +300,8 @@ class FlowGraph(Element, _Flowgraph): """ changed = False for selected_block in self.get_selected_blocks(): - if selected_block.set_bypassed(): changed = True + if selected_block.set_bypassed(): + changed = True return changed def move_selected(self, delta_coordinate): @@ -309,9 +312,6 @@ class FlowGraph(Element, _Flowgraph): delta_coordinate: the change in coordinates """ for selected_block in self.get_selected_blocks(): - delta_coordinate = selected_block.bound_move_delta(delta_coordinate) - - for selected_block in self.get_selected_blocks(): selected_block.move(delta_coordinate) self.element_moved = True @@ -400,52 +400,44 @@ class FlowGraph(Element, _Flowgraph): changed = True return changed - def draw(self, gc, window): + def draw(self, widget, cr): """ 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() - hide_disabled_blocks = Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active() - hide_variables = Actions.TOGGLE_HIDE_VARIABLES.get_active() - - #draw the background - gc.set_foreground(Colors.FLOWGRAPH_BACKGROUND_COLOR) - window.draw_rectangle(gc, True, 0, 0, W, H) - # draw comments first if Actions.TOGGLE_SHOW_BLOCK_COMMENTS.get_active(): for block in self.blocks: - if hide_variables and (block.is_variable or block.is_import): - continue # skip hidden disabled blocks and connections if block.get_enabled(): - block.draw_comment(gc, window) - #draw multi select rectangle + # block.draw_comment(widget, cr) + pass + # 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 + cr.set_source_rgb(*Colors.HIGHLIGHT_COLOR) + cr.rectangle(x, y, w, h) + cr.fill() + cr.set_source_rgb(*Colors.BORDER_COLOR) + cr.rectangle(x, y, w, h) + cr.stroke() + + # draw blocks on top of connections + hide_disabled_blocks = Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active() + hide_variables = Actions.TOGGLE_HIDE_VARIABLES.get_active() blocks = sorted(self.blocks, key=methodcaller('get_enabled')) + for element in chain(self.connections, blocks): if hide_disabled_blocks and not element.get_enabled(): continue # skip hidden disabled blocks and connections if hide_variables and (element.is_variable or element.is_import): continue # skip hidden disabled blocks and connections - element.draw(gc, window) - #draw selected blocks on top of selected connections + element.draw(widget, cr) + + # 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) + selected_element.draw(widget, cr) def update_selected(self): """ @@ -458,9 +450,9 @@ class FlowGraph(Element, _Flowgraph): for selected in selected_elements: if selected in elements: continue selected_elements.remove(selected) - if self._old_selected_port and self._old_selected_port.get_parent() not in elements: + if self._old_selected_port and self._old_selected_port.parent not in elements: self._old_selected_port = None - if self._new_selected_port and self._new_selected_port.get_parent() not in elements: + if self._new_selected_port and self._new_selected_port.parent not in elements: self._new_selected_port = None #update highlighting for element in elements: @@ -538,7 +530,7 @@ class FlowGraph(Element, _Flowgraph): #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_element = selected_element.parent_block selected.add(selected_element) #place at the end of the list self.get_elements().remove(element) @@ -670,7 +662,7 @@ class FlowGraph(Element, _Flowgraph): self.mouse_pressed = True self.update_selected_elements() self.mouse_pressed = False - self._context_menu.popup(None, None, None, event.button, event.time) + self._context_menu.popup(None, None, None, None, event.button, event.time) def handle_mouse_selector_press(self, double_click, coordinate): """ @@ -681,11 +673,12 @@ class FlowGraph(Element, _Flowgraph): """ self.press_coor = coordinate self.set_coordinate(coordinate) - self.time = 0 self.mouse_pressed = True - if double_click: self.unselect() + + 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 Actions.BLOCK_PARAM_MODIFY() @@ -697,69 +690,70 @@ class FlowGraph(Element, _Flowgraph): And update the selected flowgraph elements. """ self.set_coordinate(coordinate) - self.time = 0 self.mouse_pressed = False if self.element_moved: Actions.BLOCK_MOVE() self.element_moved = False self.update_selected_elements() - def handle_mouse_motion(self, coordinate): + def handle_mouse_motion(self, coordinate, button1_pressed): """ The mouse has moved, respond to mouse dragging or notify elements 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 longer checking pending events via gtk.events_pending() - always true in Windows) - if not self.mouse_pressed: - # only continue if mouse-over stuff is enabled (just the auto-hide port label stuff for now) - if not Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active(): return - redraw = False - for element in reversed(self.get_elements()): - over_element = element.what_is_selected(coordinate) - if not over_element: continue - if over_element != self.element_under_mouse: # over sth new - if self.element_under_mouse: - redraw |= self.element_under_mouse.mouse_out() or False - self.element_under_mouse = over_element - redraw |= over_element.mouse_over() or False - break - else: + # to perform a movement, the mouse must be pressed + # (no longer checking pending events via Gtk.events_pending() - always true in Windows) + if not button1_pressed: + self._handle_mouse_motion_move(coordinate) + else: + self._handle_mouse_motion_drag(coordinate) + + def _handle_mouse_motion_move(self, coordinate): + # only continue if mouse-over stuff is enabled (just the auto-hide port label stuff for now) + if not Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active(): + return + redraw = False + for element in reversed(self.get_elements()): + over_element = element.what_is_selected(coordinate) + if not over_element: + continue + if over_element != self.element_under_mouse: # over sth new if self.element_under_mouse: redraw |= self.element_under_mouse.mouse_out() or False - self.element_under_mouse = None - if redraw: - #self.create_labels() - self.create_shapes() - self.queue_draw() + self.element_under_mouse = over_element + redraw |= over_element.mouse_over() or False + break else: - #perform auto-scrolling - 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: - Actions.ELEMENT_DELETE() - #move the selected elements and record the new coordinate - if not self.get_ctrl_mask(): - X, Y = self.get_coordinate() - dX, dY = int(x - X), int(y - Y) - active = Actions.TOGGLE_SNAP_TO_GRID.get_active() or self.get_mod1_mask() - if not active or abs(dX) >= Utils.CANVAS_GRID_SIZE or abs(dY) >= Utils.CANVAS_GRID_SIZE: - self.move_selected((dX, dY)) - self.set_coordinate((x, y)) - #queue draw for animation + if self.element_under_mouse: + redraw |= self.element_under_mouse.mouse_out() or False + self.element_under_mouse = None + if redraw: + # self.create_labels() + self.create_shapes() self.queue_draw() + + def _handle_mouse_motion_drag(self, coordinate): + # remove the connection if selected in drag event + if len(self.get_selected_elements()) == 1 and self.get_selected_element().is_connection: + Actions.ELEMENT_DELETE() + + # move the selected elements and record the new coordinate + x, y = coordinate + if not self.get_ctrl_mask(): + X, Y = self.get_coordinate() + dX, dY = int(x - X), int(y - Y) + active = Actions.TOGGLE_SNAP_TO_GRID.get_active() or self.get_mod1_mask() + if not active or abs(dX) >= Utils.CANVAS_GRID_SIZE or abs(dY) >= Utils.CANVAS_GRID_SIZE: + self.move_selected((dX, dY)) + self.set_coordinate((x, y)) + # queue draw for animation + self.queue_draw() + + def get_max_coords(self, initial=(0, 0)): + w, h = initial + for block in self.blocks: + x, y = block.get_coordinate() + w = max(w, x + block.W) + h = max(h, y + block.H) + return w, h diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py index 1437391236..3236768969 100644 --- a/grc/gui/MainWindow.py +++ b/grc/gui/MainWindow.py @@ -17,9 +17,16 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ +from __future__ import absolute_import + import os -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import GObject + from . import Bars, Actions, Preferences, Utils from .BlockTreeWindow import BlockTreeWindow @@ -31,38 +38,11 @@ from .NotebookPage import NotebookPage from ..core import Messages -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' -<span foreground="$foreground">$encode($title or $new_flowgraph_title)</span>#slurp -#if $read_only - (ro)#slurp -#end if -""" - - ############################################################ # Main window ############################################################ -class MainWindow(gtk.Window): +class MainWindow(Gtk.Window): """The topmost window with menus, the tool bar, and other major windows.""" # Constants the action handler can use to indicate which panel visibility to change. @@ -87,23 +67,23 @@ class MainWindow(gtk.Window): Preferences.load(platform) # Setup window - gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) - vbox = gtk.VBox() + GObject.GObject.__init__(self) + vbox = Gtk.VBox() self.add(vbox) # Create the menu bar and toolbar self.add_accel_group(Actions.get_accel_group()) self.menu_bar = Bars.MenuBar(generate_modes, action_handler_callback) - vbox.pack_start(self.menu_bar, False) + vbox.pack_start(self.menu_bar, False, False, 0) self.tool_bar = Bars.Toolbar(generate_modes, action_handler_callback) - vbox.pack_start(self.tool_bar, False) + vbox.pack_start(self.tool_bar, False, False, 0) # Main parent container for the different panels - self.container = gtk.HPaned() - vbox.pack_start(self.container) + self.main = Gtk.HPaned() #(orientation=Gtk.Orientation.HORIZONTAL) + vbox.pack_start(self.main, True, True, 0) # Create the notebook - self.notebook = gtk.Notebook() + self.notebook = Gtk.Notebook() self.page_to_be_closed = None self.current_page = None self.notebook.set_show_border(False) @@ -112,19 +92,22 @@ class MainWindow(gtk.Window): # Create the console window self.text_display = TextDisplay() - self.console_window = gtk.ScrolledWindow() - self.console_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.console_window = Gtk.ScrolledWindow() + self.console_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.console_window.add(self.text_display) self.console_window.set_size_request(-1, DEFAULT_CONSOLE_WINDOW_WIDTH) # Create the block tree and variable panels - self.btwin = BlockTreeWindow(platform, self.get_flow_graph) - self.vars = VariableEditor(platform, self.get_flow_graph) + self.btwin = BlockTreeWindow(platform) + self.btwin.connect('create_new_block', self._add_block_to_current_flow_graph) + self.vars = VariableEditor() + self.vars.connect('create_new_block', self._add_block_to_current_flow_graph) + self.vars.connect('remove_block', self._remove_block_from_current_flow_graph) # Figure out which place to put the variable editor - self.left = gtk.VPaned() - self.right = gtk.VPaned() - self.left_subpanel = gtk.HPaned() + self.left = Gtk.VPaned() #orientation=Gtk.Orientation.VERTICAL) + self.right = Gtk.VPaned() #orientation=Gtk.Orientation.VERTICAL) + self.left_subpanel = Gtk.HPaned() #orientation=Gtk.Orientation.HORIZONTAL) self.variable_panel_sidebar = Preferences.variable_editor_sidebar() if self.variable_panel_sidebar: @@ -142,12 +125,12 @@ class MainWindow(gtk.Window): # Create the right panel self.right.pack1(self.btwin) - self.container.pack1(self.left) - self.container.pack2(self.right, False) + self.main.pack1(self.left) + self.main.pack2(self.right, False) - # load preferences and show the main window + # Load preferences and show the main window self.resize(*Preferences.main_window_size()) - self.container.set_position(Preferences.blocks_window_position()) + self.main.set_position(Preferences.blocks_window_position()) self.left.set_position(Preferences.console_window_position()) if self.variable_panel_sidebar: self.right.set_position(Preferences.variable_editor_position(sidebar=True)) @@ -163,6 +146,13 @@ class MainWindow(gtk.Window): # Event Handlers ############################################################ + def _add_block_to_current_flow_graph(self, widget, key): + self.current_flow_graph.add_new_block(key) + + def _remove_block_from_current_flow_graph(self, widget, key): + block = self.current_flow_graph.get_block(key) + self.current_flow_graph.remove_element(block) + def _quit(self, window, event): """ Handle the delete event from the main window. @@ -263,17 +253,15 @@ class MainWindow(gtk.Window): file_path=file_path, ) if file_path: Messages.send_end_load() - except Exception, e: #return on failure + except Exception as 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()) - 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) + self.notebook.append_page(page, page.tab) + self.notebook.set_tab_reorderable(page, True) #only show if blank or manual if not file_path or show: self._set_page(page) @@ -284,10 +272,10 @@ class MainWindow(gtk.Window): Returns: true if all closed """ - open_files = filter(lambda file: file, self._get_files()) #filter blank files - open_file = self.get_page().get_file_path() + open_files = [file for file in self._get_files() if file] #filter blank files + open_file = self.current_page.file_path #close each page - for page in sorted(self.get_pages(), key=lambda p: p.get_saved()): + for page in sorted(self.get_pages(), key=lambda p: p.saved): self.page_to_be_closed = page closed = self.close_page(False) if not closed: @@ -298,7 +286,7 @@ class MainWindow(gtk.Window): Preferences.file_open(open_file) Preferences.main_window_size(self.get_size()) Preferences.console_window_position(self.left.get_position()) - Preferences.blocks_window_position(self.container.get_position()) + Preferences.blocks_window_position(self.main.get_position()) if self.variable_panel_sidebar: Preferences.variable_editor_position(self.right.get_position(), sidebar=True) else: @@ -315,23 +303,24 @@ class MainWindow(gtk.Window): Args: ensure: boolean """ - if not self.page_to_be_closed: self.page_to_be_closed = self.get_page() + if not self.page_to_be_closed: self.page_to_be_closed = self.current_page #show the page if it has an executing flow graph or is unsaved - if self.page_to_be_closed.get_proc() or not self.page_to_be_closed.get_saved(): + if self.page_to_be_closed.process or not self.page_to_be_closed.saved: self._set_page(self.page_to_be_closed) #unsaved? ask the user - if not self.page_to_be_closed.get_saved(): + if not self.page_to_be_closed.saved: response = self._save_changes() # return value is either OK, CLOSE, or CANCEL - if response == gtk.RESPONSE_OK: + if response == Gtk.ResponseType.OK: Actions.FLOW_GRAPH_SAVE() #try to save - if not self.page_to_be_closed.get_saved(): #still unsaved? + if not self.page_to_be_closed.saved: #still unsaved? self.page_to_be_closed = None #set the page to be closed back to None return False - elif response == gtk.RESPONSE_CANCEL: + elif response == Gtk.ResponseType.CANCEL: self.page_to_be_closed = None return False #stop the flow graph if executing - if self.page_to_be_closed.get_proc(): Actions.FLOW_GRAPH_KILL() + if self.page_to_be_closed.process: + 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 @@ -347,69 +336,49 @@ class MainWindow(gtk.Window): Set the title of the main window. Set the titles on the page tabs. Show/hide the console window. - - Args: - 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.config.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 + page = self.current_page + + basename = os.path.basename(page.file_path) + dirname = os.path.dirname(page.file_path) + Gtk.Window.set_title(self, ''.join(( + '*' if not page.saved else '', basename if basename else NEW_FLOGRAPH_TITLE, + '(read only)' if page.get_read_only() else '', ' - ', + dirname if dirname else self._platform.config.name, + ))) + # set tab titles + for page in self.get_pages(): + file_name = os.path.splitext(os.path.basename(page.file_path))[0] + page.set_markup('<span foreground="{foreground}">{title}{ro}</span>'.format( + foreground='black' if page.saved else 'red', ro=' (ro)' if page.get_read_only() else '', + title=Utils.encode(file_name or NEW_FLOGRAPH_TITLE), + )) + # show/hide notebook tabs self.notebook.set_show_tabs(len(self.get_pages()) > 1) - # Need to update the variable window when changing - self.vars.update_gui() + # Need to update the variable window when changing + self.vars.update_gui(self.current_flow_graph.blocks) def update_pages(self): """ Forces a reload of all the pages in this notebook. """ for page in self.get_pages(): - success = page.get_flow_graph().reload() + success = page.flow_graph.reload() if success: # Only set saved if errors occurred during import - page.set_saved(False) + page.saved = False - def get_page(self): - """ - Get the selected page. - - Returns: - the selected page - """ - return self.current_page - - def get_flow_graph(self): - """ - Get the selected flow graph. - - Returns: - the selected flow graph - """ - return self.get_page().get_flow_graph() + @property + def current_flow_graph(self): + return self.current_page.flow_graph def get_focus_flag(self): """ Get the focus flag from the current page. - Returns: the focus flag """ - return self.get_page().get_drawing_area().get_focus_flag() + return self.current_page.get_drawing_area().get_focus_flag() ############################################################ # Helpers @@ -433,13 +402,13 @@ class MainWindow(gtk.Window): the response_id (see buttons variable below) """ buttons = ( - 'Close without saving', gtk.RESPONSE_CLOSE, - gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_SAVE, gtk.RESPONSE_OK + 'Close without saving', Gtk.ResponseType.CLOSE, + Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, + Gtk.STOCK_SAVE, Gtk.ResponseType.OK ) return MessageDialogHelper( - gtk.MESSAGE_QUESTION, gtk.BUTTONS_NONE, 'Unsaved Changes!', - 'Would you like to save changes before closing?', gtk.RESPONSE_OK, buttons + Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, 'Unsaved Changes!', + 'Would you like to save changes before closing?', Gtk.ResponseType.OK, buttons ) def _get_files(self): @@ -449,7 +418,7 @@ class MainWindow(gtk.Window): Returns: list of file paths """ - return map(lambda page: page.get_file_path(), self.get_pages()) + return [page.file_path for page in self.get_pages()] def get_pages(self): """ @@ -458,4 +427,5 @@ class MainWindow(gtk.Window): Returns: list of pages """ - return [self.notebook.get_nth_page(page_num) for page_num in range(self.notebook.get_n_pages())] + return [self.notebook.get_nth_page(page_num) + for page_num in range(self.notebook.get_n_pages())] diff --git a/grc/gui/NotebookPage.py b/grc/gui/NotebookPage.py index c9e8d0f186..4745035aff 100644 --- a/grc/gui/NotebookPage.py +++ b/grc/gui/NotebookPage.py @@ -17,17 +17,18 @@ 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 Actions -from StateCache import StateCache -from Constants import MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT -from DrawingArea import DrawingArea +from __future__ import absolute_import import os +from gi.repository import Gtk, Gdk, GObject -class NotebookPage(gtk.HBox): +from . import Actions +from .StateCache import StateCache +from .Constants import MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT +from .DrawingArea import DrawingArea + + +class NotebookPage(Gtk.HBox): """A page in the notebook.""" def __init__(self, main_window, flow_graph, file_path=''): @@ -38,59 +39,53 @@ class NotebookPage(gtk.HBox): main_window: main window file_path: path to a flow graph file """ - self._flow_graph = flow_graph - self.process = None - #import the file + Gtk.HBox.__init__(self) + self.main_window = main_window + self.flow_graph = flow_graph self.file_path = file_path - initial_state = flow_graph.get_parent().parse_flow_graph(file_path) - self.state_cache = StateCache(initial_state) + + self.process = None self.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) + + # import the file + initial_state = flow_graph.parent_platform.parse_flow_graph(file_path) + flow_graph.import_data(initial_state) + self.state_cache = StateCache(initial_state) + + # tab box to hold label and close button + self.label = Gtk.Label() + image = Gtk.Image.new_from_icon_name('window-close', Gtk.IconSize.MENU) + image_box = Gtk.HBox(homogeneous=False, spacing=0) image_box.pack_start(image, True, False, 0) - #setup the button - button = gtk.Button() + button = Gtk.Button() button.connect("clicked", self._handle_button) - button.set_relief(gtk.RELIEF_NONE) + button.set_relief(Gtk.ReliefStyle.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() + + tab = self.tab = Gtk.HBox(homogeneous=False, spacing=0) + tab.pack_start(self.label, False, False, 0) + tab.pack_start(button, False, False, 0) + tab.show_all() + + # setup scroll window and drawing area + self.drawing_area = DrawingArea(flow_graph) + flow_graph.drawing_area = self.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.scrolled_window.set_policy(Gtk.PolicyType.ALWAYS, Gtk.PolicyType.ALWAYS) self.scrolled_window.connect('key-press-event', self._handle_scroll_window_key_press) - 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 into flow graph - self.get_flow_graph().drawing_area = self.get_drawing_area() - self.show_all() + self.scrolled_window.add_with_viewport(self.drawing_area) + self.pack_start(self.scrolled_window, True, True, 0) - def get_drawing_area(self): return self.drawing_area + self.show_all() def _handle_scroll_window_key_press(self, widget, event): """forward Ctrl-PgUp/Down to NotebookPage (switch fg instead of horiz. scroll""" is_ctrl_pg = ( - event.state & gtk.gdk.CONTROL_MASK and - event.keyval in (gtk.keysyms.Page_Up, gtk.keysyms.Page_Down) + event.state & Gdk.ModifierType.CONTROL_MASK and + event.keyval in (Gdk.KEY_Page_Up, Gdk.KEY_Page_Down) ) if is_ctrl_pg: return self.get_parent().event(event) @@ -102,8 +97,8 @@ class NotebookPage(gtk.HBox): Returns: generator """ - platform = self.get_flow_graph().get_parent() - return platform.Generator(self.get_flow_graph(), self.get_file_path()) + platform = self.flow_graph.parent_platform + return platform.Generator(self.flow_graph, self.file_path) def _handle_button(self, button): """ @@ -125,42 +120,6 @@ class NotebookPage(gtk.HBox): """ self.label.set_markup(markup) - def get_tab(self): - """ - Get the gtk widget for this page's tab. - - Returns: - gtk widget - """ - return self.tab - - def get_proc(self): - """ - Get the subprocess for the flow graph. - - Returns: - the subprocess object - """ - return self.process - - def set_proc(self, process): - """ - Set the subprocess object. - - Args: - process: the new subprocess - """ - self.process = process - - def get_flow_graph(self): - """ - Get the flow graph. - - Returns: - the flow graph - """ - return self._flow_graph - def get_read_only(self): """ Get the read-only state of the file. @@ -169,51 +128,7 @@ class NotebookPage(gtk.HBox): Returns: 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. - - Returns: - the file path or '' - """ - return self.file_path - - def set_file_path(self, file_path=''): - """ - Set the file path, '' for no file path. - - Args: - file_path: file path string - """ - self.file_path = os.path.abspath(file_path) if file_path else '' - - def get_saved(self): - """ - Get the saved status for the flow graph. - - Returns: - true if saved - """ - return self.saved - - def set_saved(self, saved=True): - """ - Set the saved status. - - Args: - saved: boolean status - """ - self.saved = saved - - def get_state_cache(self): - """ - Get the state cache for the flow graph. - - Returns: - the state cache - """ - return self.state_cache + if not self.file_path: + return False + return (os.path.exists(self.file_path) and + not os.access(self.file_path, os.W_OK)) diff --git a/grc/gui/Param.py b/grc/gui/Param.py index 4b5a3c294a..a630f5faa3 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -1,396 +1,27 @@ -""" -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 -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 pygtk -pygtk.require('2.0') -import gtk - -from . import Colors, Utils, Constants -from .Element import Element - -from ..core.Param import Param as _Param - - -class InputParam(gtk.HBox): - """The base class for an input parameter inside the input parameters dialog.""" - expand = False - - def __init__(self, param, changed_callback=None, editing_callback=None): - gtk.HBox.__init__(self) - self.param = param - self._changed_callback = changed_callback - self._editing_callback = editing_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) - self.set_markup = lambda m: self.label.set_markup(m) - self.tp = None - self._have_pending_changes = False - #connect events - self.connect('show', self._update_gui) - - def set_color(self, color): - pass - - def set_tooltip_text(self, text): - pass - - def get_text(self): - raise NotImplementedError() - - def _update_gui(self, *args): - """ - Set the markup, color, tooltip, show/hide. - """ - #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, - modified=self._have_pending_changes)) - #set the color - self.set_color(self.param.get_color()) - #set the tooltip - self.set_tooltip_text( - 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 _mark_changed(self, *args): - """ - Mark this param as modified on change, but validate only on focus-lost - """ - self._have_pending_changes = True - self._update_gui() - if self._editing_callback: - self._editing_callback(self, None) - - def _apply_change(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._changed_callback: - self._changed_callback(self, None) - else: - self.param.validate() - #gui update - self._have_pending_changes = False - self._update_gui() - - def _handle_key_press(self, widget, event): - if event.keyval == gtk.keysyms.Return and event.state & gtk.gdk.CONTROL_MASK: - self._apply_change(widget, event) - return True - return False - - def apply_pending_changes(self): - if self._have_pending_changes: - self._apply_change() - - -class EntryParam(InputParam): - """Provide an entry box for strings and numbers.""" - - def __init__(self, *args, **kwargs): - InputParam.__init__(self, *args, **kwargs) - self._input = gtk.Entry() - self._input.set_text(self.param.get_value()) - self._input.connect('changed', self._mark_changed) - self._input.connect('focus-out-event', self._apply_change) - self._input.connect('key-press-event', self._handle_key_press) - self.pack_start(self._input, True) - - def get_text(self): - return self._input.get_text() - - def set_color(self, color): - need_status_color = self.label not in self.get_children() - text_color = ( - Colors.PARAM_ENTRY_TEXT_COLOR if not need_status_color else - gtk.gdk.color_parse('blue') if self._have_pending_changes else - gtk.gdk.color_parse('red') if not self.param.is_valid() else - Colors.PARAM_ENTRY_TEXT_COLOR) - base_color = ( - Colors.BLOCK_DISABLED_COLOR - if need_status_color and not self.param.get_parent().get_enabled() - else gtk.gdk.color_parse(color) - ) - self._input.modify_base(gtk.STATE_NORMAL, base_color) - self._input.modify_text(gtk.STATE_NORMAL, text_color) - - def set_tooltip_text(self, text): - try: - self._input.set_tooltip_text(text) - except AttributeError: - pass # no tooltips for old GTK - - -class MultiLineEntryParam(InputParam): - """Provide an multi-line box for strings.""" - expand = True - - def __init__(self, *args, **kwargs): - InputParam.__init__(self, *args, **kwargs) - self._buffer = gtk.TextBuffer() - self._buffer.set_text(self.param.get_value()) - self._buffer.connect('changed', self._mark_changed) - - self._view = gtk.TextView(self._buffer) - self._view.connect('focus-out-event', self._apply_change) - self._view.connect('key-press-event', self._handle_key_press) - - self._sw = gtk.ScrolledWindow() - self._sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - self._sw.add_with_viewport(self._view) - - self.pack_start(self._sw, True) - - def get_text(self): - buf = self._buffer - return buf.get_text(buf.get_start_iter(), - buf.get_end_iter()).strip() - - def set_color(self, color): - self._view.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) - self._view.modify_text(gtk.STATE_NORMAL, Colors.PARAM_ENTRY_TEXT_COLOR) - - def set_tooltip_text(self, text): - try: - self._view.set_tooltip_text(text) - except AttributeError: - pass # no tooltips for old GTK - - -# try: -# import gtksourceview -# lang_manager = gtksourceview.SourceLanguagesManager() -# py_lang = lang_manager.get_language_from_mime_type('text/x-python') +# Copyright 2007-2016 Free Software Foundation, Inc. +# This file is part of GNU Radio # -# class PythonEditorParam(InputParam): -# expand = True +# 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. # -# def __init__(self, *args, **kwargs): -# InputParam.__init__(self, *args, **kwargs) +# 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. # -# buf = self._buffer = gtksourceview.SourceBuffer() -# buf.set_language(py_lang) -# buf.set_highlight(True) -# buf.set_text(self.param.get_value()) -# buf.connect('changed', self._mark_changed) -# -# view = self._view = gtksourceview.SourceView(self._buffer) -# view.connect('focus-out-event', self._apply_change) -# view.connect('key-press-event', self._handle_key_press) -# view.set_tabs_width(4) -# view.set_insert_spaces_instead_of_tabs(True) -# view.set_auto_indent(True) -# view.set_border_width(2) -# -# scroll = gtk.ScrolledWindow() -# scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) -# scroll.add_with_viewport(view) -# self.pack_start(scroll, True) -# -# def get_text(self): -# buf = self._buffer -# return buf.get_text(buf.get_start_iter(), -# buf.get_end_iter()).strip() -# -# except ImportError: -# print "Package 'gtksourceview' not found. No Syntax highlighting." -# PythonEditorParam = MultiLineEntryParam - -class PythonEditorParam(InputParam): - - def __init__(self, *args, **kwargs): - InputParam.__init__(self, *args, **kwargs) - button = self._button = gtk.Button('Open in Editor') - button.connect('clicked', self.open_editor) - self.pack_start(button, True) - - def open_editor(self, widget=None): - flowgraph = self.param.get_parent().get_parent() - flowgraph.install_external_editor(self.param) - - def get_text(self): - pass # we never update the value from here - - def set_color(self, color): - # self._button.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) - self._button.modify_text(gtk.STATE_NORMAL, Colors.PARAM_ENTRY_TEXT_COLOR) - - def _apply_change(self, *args): - pass - - -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._editing_callback) - self._input.connect('changed', self._apply_change) - 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): - try: - self._input.set_tooltip_text(text) - except AttributeError: - pass # no tooltips for old GTK +# 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 __future__ import absolute_import +from . import Utils, Constants -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._apply_change) - self._input.get_child().connect('changed', self._mark_changed) - self._input.get_child().connect('focus-out-event', self._apply_change) - self._input.get_child().connect('key-press-event', self._handle_key_press) - 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_tooltip_text(self, text): - try: - if self._input.get_active() == -1: #custom entry - self._input.get_child().set_tooltip_text(text) - else: - self._input.set_tooltip_text(text) - except AttributeError: - pass # no tooltips for old GTK - - 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)) - 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) - - -class FileParam(EntryParam): - """Provide an entry box for filename and a button to browse for a file.""" - - def __init__(self, *args, **kwargs): - EntryParam.__init__(self, *args, **kwargs) - input = gtk.Button('...') - input.connect('clicked', self._handle_clicked) - self.pack_start(input, False) - - def _handle_clicked(self, widget=None): - """ - If the button was clicked, open a file dialog in open/save format. - Replace the text in the entry with the new filename from the file dialog. - """ - #get the paths - file_path = self.param.is_valid() and self.param.get_evaluated() or '' - (dirname, basename) = os.path.isfile(file_path) and os.path.split(file_path) or (file_path, '') - # check for qss theme default directory - if self.param.get_key() == 'qt_qss_theme': - dirname = os.path.dirname(dirname) # trim filename - if not os.path.exists(dirname): - platform = self.param.get_parent().get_parent().get_parent() - dirname = os.path.join(platform.config.install_prefix, - '/share/gnuradio/themes') - if not os.path.exists(dirname): - dirname = os.getcwd() # fix bad paths - - #build the dialog - if self.param.get_type() == 'file_open': - file_dialog = gtk.FileChooserDialog('Open a Data File...', None, - gtk.FILE_CHOOSER_ACTION_OPEN, ('gtk-cancel',gtk.RESPONSE_CANCEL,'gtk-open',gtk.RESPONSE_OK)) - elif self.param.get_type() == 'file_save': - file_dialog = gtk.FileChooserDialog('Save a Data File...', None, - gtk.FILE_CHOOSER_ACTION_SAVE, ('gtk-cancel',gtk.RESPONSE_CANCEL, 'gtk-save',gtk.RESPONSE_OK)) - file_dialog.set_do_overwrite_confirmation(True) - file_dialog.set_current_name(basename) #show the current filename - else: - raise ValueError("Can't open file chooser dialog for type " + repr(self.param.get_type())) - file_dialog.set_current_folder(dirname) #current directory - file_dialog.set_select_multiple(False) - file_dialog.set_local_only(True) - if gtk.RESPONSE_OK == file_dialog.run(): #run the dialog - file_path = file_dialog.get_filename() #get the file path - self._input.set_text(file_path) - self._editing_callback() - self._apply_change() - file_dialog.destroy() #destroy the dialog - - -PARAM_MARKUP_TMPL="""\ -#set $foreground = $param.is_valid() and 'black' or 'red' -<span foreground="$foreground" font_desc="$font"><b>$encode($param.get_name()): </b>$encode(repr($param).replace('\\n',' '))</span>""" - -PARAM_LABEL_MARKUP_TMPL="""\ -#set $foreground = $modified and 'blue' or $param.is_valid() and 'black' or 'red' -#set $underline = $has_cb and 'low' or 'none' -<span underline="$underline" foreground="$foreground" font_desc="Sans 9">$encode($param.get_name())</span>""" +from . import ParamWidgets +from .Element import Element -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: $truncate($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""" +from ..core.Param import Param as _Param class Param(Element, _Param): @@ -411,31 +42,63 @@ class Param(Element, _Param): gtk input class """ if self.get_type() in ('file_open', 'file_save'): - input_widget = FileParam(self, *args, **kwargs) + input_widget_cls = ParamWidgets.FileParam elif self.is_enum(): - input_widget = EnumParam(self, *args, **kwargs) + input_widget_cls = ParamWidgets.EnumParam elif self.get_options(): - input_widget = EnumEntryParam(self, *args, **kwargs) + input_widget_cls = ParamWidgets.EnumEntryParam elif self.get_type() == '_multiline': - input_widget = MultiLineEntryParam(self, *args, **kwargs) + input_widget_cls = ParamWidgets.MultiLineEntryParam elif self.get_type() == '_multiline_python_external': - input_widget = PythonEditorParam(self, *args, **kwargs) + input_widget_cls = ParamWidgets.PythonEditorParam else: - input_widget = EntryParam(self, *args, **kwargs) - - return input_widget + input_widget_cls = ParamWidgets.EntryParam + + return input_widget_cls(self, *args, **kwargs) + + def format_label_markup(self, have_pending_changes=False): + block = self.parent + # fixme: using non-public attribute here + has_callback = \ + hasattr(block, 'get_callbacks') and \ + any(self.get_key() in callback for callback in block._callbacks) + + return '<span underline="{line}" foreground="{color}" font_desc="Sans 9">{label}</span>'.format( + line='low' if has_callback else 'none', + color='blue' if have_pending_changes else + 'black' if self.is_valid() else + 'red', + label=Utils.encode(self.get_name()) + ) - def get_markup(self): + def format_tooltip_text(self): + errors = self.get_error_messages() + tooltip_lines = ['Key: ' + self.get_key(), 'Type: ' + self.get_type()] + if self.is_valid(): + value = str(self.get_evaluated()) + if len(value) > 100: + value = '{}...{}'.format(value[:50], value[-50:]) + tooltip_lines.append('Value: ' + value) + elif len(errors) == 1: + tooltip_lines.append('Error: ' + errors[0]) + elif len(errors) > 1: + tooltip_lines.append('Error:') + tooltip_lines.extend(' * ' + msg for msg in errors) + return '\n'.join(tooltip_lines) + + def format_block_surface_markup(self): """ Get the markup for this param. Returns: a pango markup string """ - return Utils.parse_template(PARAM_MARKUP_TMPL, - param=self, font=Constants.PARAM_FONT) + return '<span foreground="{color}" font_desc="{font}"><b>{label}:</b> {value}</span>'.format( + color='black' if self.is_valid() else 'red', font=Constants.PARAM_FONT, + label=Utils.encode(self.get_name()), value=Utils.encode(repr(self).replace('\n', ' ')) + ) diff --git a/grc/gui/ParamWidgets.py b/grc/gui/ParamWidgets.py new file mode 100644 index 0000000000..fbbfa93d1d --- /dev/null +++ b/grc/gui/ParamWidgets.py @@ -0,0 +1,300 @@ +# Copyright 2007-2016 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 __future__ import absolute_import +import os + +from gi.repository import Gtk, Gdk + +from . import Colors + + +class InputParam(Gtk.HBox): + """The base class for an input parameter inside the input parameters dialog.""" + expand = False + + def __init__(self, param, changed_callback=None, editing_callback=None): + Gtk.HBox.__init__(self) + + self.param = param + self._changed_callback = changed_callback + self._editing_callback = editing_callback + + self.label = Gtk.Label() + self.label.set_size_request(150, -1) + self.label.show() + self.pack_start(self.label, False, False, 0) + + self.tp = None + self._have_pending_changes = False + + self.connect('show', self._update_gui) + + def set_color(self, color): + pass + + def set_tooltip_text(self, text): + pass + + def get_text(self): + raise NotImplementedError() + + def _update_gui(self, *args): + """ + Set the markup, color, tooltip, show/hide. + """ + self.label.set_markup(self.param.format_label_markup(self._have_pending_changes)) + + # fixme: find a non-deprecated way to change colors + # self.set_color(Colors.PARAM_ENTRY_COLORS.get( + # self.param.get_type(), Colors.PARAM_ENTRY_DEFAULT_COLOR) + # ) + + self.set_tooltip_text(self.param.format_tooltip_text()) + + if self.param.get_hide() == 'all': + self.hide() + else: + self.show_all() + + def _mark_changed(self, *args): + """ + Mark this param as modified on change, but validate only on focus-lost + """ + self._have_pending_changes = True + self._update_gui() + if self._editing_callback: + self._editing_callback(self, None) + + def _apply_change(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._changed_callback: + self._changed_callback(self, None) + else: + self.param.validate() + #gui update + self._have_pending_changes = False + self._update_gui() + + def _handle_key_press(self, widget, event): + if event.keyval == Gdk.KEY_Return and event.get_state() & Gdk.ModifierType.CONTROL_MASK: + self._apply_change(widget, event) + return True + return False + + def apply_pending_changes(self): + if self._have_pending_changes: + self._apply_change() + + +class EntryParam(InputParam): + """Provide an entry box for strings and numbers.""" + + def __init__(self, *args, **kwargs): + InputParam.__init__(self, *args, **kwargs) + self._input = Gtk.Entry() + self._input.set_text(self.param.get_value()) + self._input.connect('changed', self._mark_changed) + self._input.connect('focus-out-event', self._apply_change) + self._input.connect('key-press-event', self._handle_key_press) + self.pack_start(self._input, True, True, 0) + + def get_text(self): + return self._input.get_text() + + def set_color(self, color): + self._input.override_background_color(Gtk.StateType.NORMAL, color) + + def set_tooltip_text(self, text): + self._input.set_tooltip_text(text) + + +class MultiLineEntryParam(InputParam): + """Provide an multi-line box for strings.""" + expand = True + + def __init__(self, *args, **kwargs): + InputParam.__init__(self, *args, **kwargs) + self._buffer = Gtk.TextBuffer() + self._buffer.set_text(self.param.get_value()) + self._buffer.connect('changed', self._mark_changed) + + self._view = Gtk.TextView() + self._view.set_buffer(self._buffer) + self._view.connect('focus-out-event', self._apply_change) + self._view.connect('key-press-event', self._handle_key_press) + # fixme: add border to TextView + + self._sw = Gtk.ScrolledWindow() + self._sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + self._sw.add_with_viewport(self._view) + + self.pack_start(self._sw, True, True, True) + + def get_text(self): + buf = self._buffer + text = buf.get_text(buf.get_start_iter(), buf.get_end_iter(), + include_hidden_chars=False) + return text.strip() + + def set_color(self, color): + self._view.override_background_color(Gtk.StateType.NORMAL, color) + + def set_tooltip_text(self, text): + self._view.set_tooltip_text(text) + + +class PythonEditorParam(InputParam): + + def __init__(self, *args, **kwargs): + InputParam.__init__(self, *args, **kwargs) + button = self._button = Gtk.Button('Open in Editor') + button.connect('clicked', self.open_editor) + self.pack_start(button, True) + + def open_editor(self, widget=None): + self.param.parent_flowgraph.install_external_editor(self.param) + + def get_text(self): + pass # we never update the value from here + + def _apply_change(self, *args): + pass + + +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.ComboBoxText() + for option in self.param.get_options(): + self._input.append_text(option.get_name()) + + value = self.param.get_value() + active_index = self.param.get_option_keys().index(value) + self._input.set_active(active_index) + + self._input.connect('changed', self._editing_callback) + self._input.connect('changed', self._apply_change) + self.pack_start(self._input, False, False, 0) + + 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.""" + + def __init__(self, *args, **kwargs): + InputParam.__init__(self, *args, **kwargs) + self._input = Gtk.ComboBoxText.new_with_entry() + for option in self.param.get_options(): + self._input.append_text(option.get_name()) + + value = self.param.get_value() + try: + active_index = self.param.get_option_keys().index(value) + self._input.set_active(active_index) + except ValueError: + self._input.set_active(-1) + self._input.get_child().set_text(value) + + self._input.connect('changed', self._apply_change) + self._input.get_child().connect('changed', self._mark_changed) + self._input.get_child().connect('focus-out-event', self._apply_change) + self._input.get_child().connect('key-press-event', self._handle_key_press) + self.pack_start(self._input, False, False, 0) + + @property + def has_custom_value(self): + return self._input.get_active() == -1 + + def get_text(self): + if self.has_custom_value: + return self._input.get_child().get_text() + else: + return self.param.get_option_keys()[self._input.get_active()] + + def set_tooltip_text(self, text): + if self.has_custom_value: # custom entry + self._input.get_child().set_tooltip_text(text) + else: + self._input.set_tooltip_text(text) + + def set_color(self, color): + self._input.get_child().modify_base( + Gtk.StateType.NORMAL, + color if not self.has_custom_value else Colors.PARAM_ENTRY_ENUM_CUSTOM_COLOR + ) + + +class FileParam(EntryParam): + """Provide an entry box for filename and a button to browse for a file.""" + + def __init__(self, *args, **kwargs): + EntryParam.__init__(self, *args, **kwargs) + self._open_button = Gtk.Button(label='...') + self._open_button.connect('clicked', self._handle_clicked) + self.pack_start(self._open_button, False, False, 0) + + def _handle_clicked(self, widget=None): + """ + If the button was clicked, open a file dialog in open/save format. + Replace the text in the entry with the new filename from the file dialog. + """ + #get the paths + file_path = self.param.is_valid() and self.param.get_evaluated() or '' + (dirname, basename) = os.path.isfile(file_path) and os.path.split(file_path) or (file_path, '') + # check for qss theme default directory + if self.param.get_key() == 'qt_qss_theme': + dirname = os.path.dirname(dirname) # trim filename + if not os.path.exists(dirname): + config = self.param.parent_platform.config + dirname = os.path.join(config.install_prefix, '/share/gnuradio/themes') + if not os.path.exists(dirname): + dirname = os.getcwd() # fix bad paths + + #build the dialog + if self.param.get_type() == 'file_open': + file_dialog = Gtk.FileChooserDialog('Open a Data File...', None, + Gtk.FileChooserAction.OPEN, ('gtk-cancel',Gtk.ResponseType.CANCEL,'gtk-open',Gtk.ResponseType.OK)) + elif self.param.get_type() == 'file_save': + file_dialog = Gtk.FileChooserDialog('Save a Data File...', None, + Gtk.FileChooserAction.SAVE, ('gtk-cancel',Gtk.ResponseType.CANCEL, 'gtk-save',Gtk.ResponseType.OK)) + file_dialog.set_do_overwrite_confirmation(True) + file_dialog.set_current_name(basename) #show the current filename + else: + raise ValueError("Can't open file chooser dialog for type " + repr(self.param.get_type())) + file_dialog.set_current_folder(dirname) #current directory + file_dialog.set_select_multiple(False) + file_dialog.set_local_only(True) + if Gtk.ResponseType.OK == file_dialog.run(): #run the dialog + file_path = file_dialog.get_filename() #get the file path + self._input.set_text(file_path) + self._editing_callback() + self._apply_change() + file_dialog.destroy() # destroy the dialog diff --git a/grc/gui/ParserErrorsDialog.py b/grc/gui/ParserErrorsDialog.py index 68ee459414..28cc8ece0c 100644 --- a/grc/gui/ParserErrorsDialog.py +++ b/grc/gui/ParserErrorsDialog.py @@ -17,14 +17,19 @@ 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 __future__ import absolute_import -from Constants import MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT +import six +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import GObject -class ParserErrorsDialog(gtk.Dialog): +from .Constants import MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT + + +class ParserErrorsDialog(Gtk.Dialog): """ A dialog for viewing parser errors """ @@ -36,31 +41,31 @@ class ParserErrorsDialog(gtk.Dialog): Args: block: a block instance """ - gtk.Dialog.__init__(self, title='Parser Errors', buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT)) + GObject.GObject.__init__(self, title='Parser Errors', buttons=(Gtk.STOCK_CLOSE, Gtk.ResponseType.ACCEPT)) self._error_logs = None - self.tree_store = gtk.TreeStore(str) + self.tree_store = Gtk.TreeStore(str) self.update_tree_store(error_logs) - column = gtk.TreeViewColumn('XML Parser Errors by Filename') - renderer = gtk.CellRendererText() + column = Gtk.TreeViewColumn('XML Parser Errors by Filename') + renderer = Gtk.CellRendererText() column.pack_start(renderer, True) column.add_attribute(renderer, 'text', 0) column.set_sort_column_id(0) - self.tree_view = tree_view = gtk.TreeView(self.tree_store) + self.tree_view = tree_view = Gtk.TreeView(self.tree_store) tree_view.set_enable_search(False) # disable pop up search box tree_view.set_search_column(-1) # really disable search tree_view.set_reorderable(False) tree_view.set_headers_visible(False) - tree_view.get_selection().set_mode(gtk.SELECTION_NONE) + tree_view.get_selection().set_mode(Gtk.SelectionMode.NONE) tree_view.append_column(column) for row in self.tree_store: tree_view.expand_row(row.path, False) - scrolled_window = gtk.ScrolledWindow() - scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolled_window = Gtk.ScrolledWindow() + scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolled_window.add_with_viewport(tree_view) self.vbox.pack_start(scrolled_window, True) @@ -71,7 +76,7 @@ class ParserErrorsDialog(gtk.Dialog): """set up data model""" self.tree_store.clear() self._error_logs = error_logs - for filename, errors in error_logs.iteritems(): + for filename, errors in six.iteritems(error_logs): parent = self.tree_store.append(None, [str(filename)]) try: with open(filename, 'r') as fp: @@ -95,6 +100,6 @@ class ParserErrorsDialog(gtk.Dialog): Returns: true if the response was accept """ - response = gtk.Dialog.run(self) + response = Gtk.Dialog.run(self) self.destroy() - return response == gtk.RESPONSE_ACCEPT + return response == Gtk.ResponseType.ACCEPT diff --git a/grc/gui/Platform.py b/grc/gui/Platform.py index 500df1cce4..997e96ab59 100644 --- a/grc/gui/Platform.py +++ b/grc/gui/Platform.py @@ -17,6 +17,8 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ +from __future__ import absolute_import, print_function + import os import sys @@ -58,7 +60,7 @@ class Platform(Element, _Platform): import shutil shutil.move(old_gui_prefs_file, gui_prefs_file) except Exception as e: - print >> sys.stderr, e + print(e, file=sys.stderr) ############################################## # Constructors diff --git a/grc/gui/Port.py b/grc/gui/Port.py index 6314b7ede8..8c4500f960 100644 --- a/grc/gui/Port.py +++ b/grc/gui/Port.py @@ -17,23 +17,23 @@ 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 __future__ import absolute_import +import math +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, PangoCairo from . import Actions, Colors, Utils from .Constants import ( PORT_SEPARATION, PORT_SPACING, CONNECTOR_EXTENSION_MINIMAL, - CONNECTOR_EXTENSION_INCREMENT, PORT_LABEL_PADDING, PORT_MIN_WIDTH, PORT_LABEL_HIDDEN_WIDTH, PORT_FONT + CONNECTOR_EXTENSION_INCREMENT, PORT_LABEL_PADDING, PORT_MIN_WIDTH, + PORT_LABEL_HIDDEN_WIDTH, PORT_FONT ) from .Element import Element from ..core.Constants import DEFAULT_DOMAIN, GR_MESSAGE_DOMAIN from ..core.Port import Port as _Port -PORT_MARKUP_TMPL="""\ -<span foreground="black" font_desc="$font">$encode($port.get_name())</span>""" - class Port(_Port, Element): """The graphical port.""" @@ -45,15 +45,19 @@ class Port(_Port, Element): """ _Port.__init__(self, block, n, dir) Element.__init__(self) - self.W = self.H = self.w = self.h = 0 + self.W = self.w = self.h = 0 + self.H = 20 # todo: fix self._connector_coordinate = (0, 0) self._connector_length = 0 self._hovering = True self._force_label_unhidden = False + self.layout = Gtk.DrawingArea().create_pango_layout('') + self._bg_color = Colors.get_color(self.get_color()) def create_shapes(self): """Create new areas and labels for the port.""" Element.create_shapes(self) + self._bg_color = Colors.get_color(self.get_color()) if self.get_hide(): return # this port is hidden, no need to create shapes if self.get_domain() == GR_MESSAGE_DOMAIN: @@ -63,9 +67,9 @@ class Port(_Port, Element): #get current rotation rotation = self.get_rotation() #get all sibling ports - ports = self.get_parent().get_sources_gui() \ - if self.is_source else self.get_parent().get_sinks_gui() - ports = filter(lambda p: not p.get_hide(), ports) + ports = self.parent.get_sources_gui() \ + if self.is_source else self.parent.get_sinks_gui() + ports = [p for p in ports if not p.get_hide()] #get the max width self.W = max([port.W for port in ports] + [PORT_MIN_WIDTH]) W = self.W if not self._label_hidden() else PORT_LABEL_HIDDEN_WIDTH @@ -76,16 +80,15 @@ class Port(_Port, Element): if hasattr(self, '_connector_length'): del self._connector_length return - length = len(filter(lambda p: not p.get_hide(), ports)) #reverse the order of ports for these rotations if rotation in (180, 270): - index = length-index-1 + index = len(ports)-index-1 port_separation = PORT_SEPARATION \ - if not self.get_parent().has_busses[self.is_source] \ + if not self.parent.has_busses[self.is_source] \ else max([port.H for port in ports]) + PORT_SPACING - offset = (self.get_parent().H - (length-1)*port_separation - self.H)/2 + offset = (self.parent.H - (len(ports)-1)*port_separation - self.H)/2 #create areas and connector coordinates if (self.is_sink and rotation == 0) or (self.is_source and rotation == 180): x = -W @@ -93,7 +96,7 @@ class Port(_Port, Element): self.add_area((x, y), (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 + x = self.parent.W y = port_separation*index+offset self.add_area((x, y), (W, self.H)) self._connector_coordinate = (x+1+W, y+self.H/2) @@ -103,7 +106,7 @@ class Port(_Port, Element): self.add_area((x, y), (self.H, 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 + y = self.parent.W x = port_separation*index+offset self.add_area((x, y), (self.H, W)) self._connector_coordinate = (x+self.H/2, y+1+W) @@ -112,50 +115,36 @@ class Port(_Port, Element): 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('') - layout.set_markup(Utils.parse_template(PORT_MARKUP_TMPL, port=self, font=PORT_FONT)) - self.w, self.h = layout.get_pixel_size() - self.W = 2 * PORT_LABEL_PADDING + self.w - self.H = 2 * PORT_LABEL_PADDING + self.h * ( - 3 if self.get_type() == 'bus' else 1) - self.H += self.H % 2 - # 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 vertical and horizontal pixmaps - self.horizontal_label = pixmap - if self.is_vertical(): - 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): + self.layout.set_markup("""<span foreground="black" font_desc="{font}">{name}</span>""".format( + name=Utils.encode(self.get_name()), font=PORT_FONT + )) + + def draw(self, widget, cr): """ Draw the socket with a label. - - Args: - gc: the graphics context - 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 - self.get_parent().is_dummy_block and Colors.MISSING_BLOCK_BORDER_COLOR or - Colors.BORDER_COLOR, + border_color = ( + Colors.HIGHLIGHT_COLOR if self.is_highlighted() else + Colors.MISSING_BLOCK_BORDER_COLOR if self.parent.is_dummy_block else + Colors.BORDER_COLOR ) + Element.draw(self, widget, cr, border_color, self._bg_color) + if not self._areas_list or self._label_hidden(): return # this port is either hidden (no areas) or folded (no label) X, Y = self.get_coordinate() - (x, y), (w, h) = self._areas_list[0] # use the first area's sizes to place the labels + (x, y), _ = self._areas_list[0] + cr.set_source_rgb(*self._bg_color) + cr.save() if self.is_horizontal(): - 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) + cr.translate(x + X + (self.W - self.w) / 2, y + Y + (self.H - self.h) / 2) elif self.is_vertical(): - 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) + cr.translate(x + X + (self.H - self.h) / 2, y + Y + (self.W - self.w) / 2) + cr.rotate(-90 * math.pi / 180.) + cr.translate(-self.w, 0) + PangoCairo.update_layout(cr, self.layout) + PangoCairo.show_layout(cr, self.layout) + cr.restore() def get_connector_coordinate(self): """ @@ -197,7 +186,7 @@ class Port(_Port, Element): Returns: the parent's rotation """ - return self.get_parent().get_rotation() + return self.parent.get_rotation() def move(self, delta_coor): """ @@ -206,7 +195,7 @@ class Port(_Port, Element): Args: delta_corr: the (delta_x, delta_y) tuple """ - self.get_parent().move(delta_coor) + self.parent.move(delta_coor) def rotate(self, direction): """ @@ -215,7 +204,7 @@ class Port(_Port, Element): Args: direction: degrees to rotate """ - self.get_parent().rotate(direction) + self.parent.rotate(direction) def get_coordinate(self): """ @@ -224,7 +213,7 @@ class Port(_Port, Element): Returns: the parents coordinate """ - return self.get_parent().get_coordinate() + return self.parent.get_coordinate() def set_highlighted(self, highlight): """ @@ -233,7 +222,7 @@ class Port(_Port, Element): Args: highlight: true to enable highlighting """ - self.get_parent().set_highlighted(highlight) + self.parent.set_highlighted(highlight) def is_highlighted(self): """ @@ -242,7 +231,7 @@ class Port(_Port, Element): Returns: the parent's highlighting status """ - return self.get_parent().is_highlighted() + return self.parent.is_highlighted() def _label_hidden(self): """ diff --git a/grc/gui/Preferences.py b/grc/gui/Preferences.py index 5fbdfe927a..8756a7ab23 100644 --- a/grc/gui/Preferences.py +++ b/grc/gui/Preferences.py @@ -17,9 +17,12 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ +from __future__ import absolute_import, print_function + import os import sys -import ConfigParser + +from six.moves import configparser HEADER = """\ @@ -31,7 +34,7 @@ HEADER = """\ """ _platform = None -_config_parser = ConfigParser.SafeConfigParser() +_config_parser = configparser.SafeConfigParser() def file_extension(): @@ -45,12 +48,12 @@ def load(platform): for section in ['main', 'files_open', 'files_recent']: try: _config_parser.add_section(section) - except Exception, e: - print e + except Exception as e: + print(e) try: _config_parser.read(_platform.get_prefs_file()) except Exception as err: - print >> sys.stderr, err + print(err, file=sys.stderr) def save(): @@ -59,7 +62,7 @@ def save(): fp.write(HEADER) _config_parser.write(fp) except Exception as err: - print >> sys.stderr, err + print(err, file=sys.stderr) def entry(key, value=None, default=None): @@ -74,7 +77,7 @@ def entry(key, value=None, default=None): }.get(_type, _config_parser.get) try: result = getter('main', key) - except ConfigParser.Error: + except configparser.Error: result = _type() if default is None else default return result @@ -106,7 +109,7 @@ def get_file_list(key): try: files = [value for name, value in _config_parser.items(key) if name.startswith('%s_' % key)] - except ConfigParser.Error: + except configparser.Error: files = [] return files @@ -121,7 +124,7 @@ def set_open_files(files): def get_recent_files(): """ Gets recent files, removes any that do not exist and re-saves it """ - files = filter(os.path.exists, get_file_list('files_recent')) + files = list(filter(os.path.exists, get_file_list('files_recent'))) set_recent_files(files) return files diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index 7c66a77a54..f87ca6e7c1 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -17,114 +17,95 @@ 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 __future__ import absolute_import +from gi.repository import Gtk, Gdk, GObject, Pango -import Actions -from Dialogs import SimpleTextDisplay -from Constants import MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT, FONT_SIZE -import Utils -import pango +from . import Actions, Utils, Constants +from .Dialogs import SimpleTextDisplay +import six -TAB_LABEL_MARKUP_TMPL="""\ -#set $foreground = $valid and 'black' or 'red' -<span foreground="$foreground">$encode($tab)</span>""" - -def get_title_label(title): - """ - Get a title label for the params window. - The title will be bold, underlined, and left justified. - - Args: - title: the text of the title - - Returns: - a gtk object - """ - label = gtk.Label() - label.set_markup('\n<b><span underline="low">%s</span>:</b>\n'%title) - hbox = gtk.HBox() - hbox.pack_start(label, False, False, padding=11) - return hbox - - -class PropsDialog(gtk.Dialog): +class PropsDialog(Gtk.Dialog): """ A dialog to set block parameters, view errors, and view documentation. """ - def __init__(self, block): + def __init__(self, parent, block): """ Properties dialog constructor. - Args: + Args:% block: a block instance """ - self._hash = 0 - gtk.Dialog.__init__( + Gtk.Dialog.__init__( self, - title='Properties: %s' % block.get_name(), - buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT, - gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, - gtk.STOCK_APPLY, gtk.RESPONSE_APPLY) + title='Properties: ' + block.get_name(), + transient_for=parent, + modal=True, + ) + self.add_buttons( + Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT, + Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT, + Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY, ) - self.set_response_sensitive(gtk.RESPONSE_APPLY, False) - self.set_size_request(MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT) + self.set_response_sensitive(Gtk.ResponseType.APPLY, False) + self.set_size_request(Constants.MIN_DIALOG_WIDTH, Constants.MIN_DIALOG_HEIGHT) + self._block = block + self._hash = 0 - vpaned = gtk.VPaned() - self.vbox.pack_start(vpaned) + vpaned = Gtk.VPaned() + self.vbox.pack_start(vpaned, True, True, 0) # Notebook to hold param boxes - notebook = gtk.Notebook() + notebook = Gtk.Notebook() notebook.set_show_border(False) notebook.set_scrollable(True) # scroll arrows for page tabs - notebook.set_tab_pos(gtk.POS_TOP) + notebook.set_tab_pos(Gtk.PositionType.TOP) vpaned.pack1(notebook, True) # Params boxes for block parameters self._params_boxes = list() for tab in block.get_param_tab_labels(): - label = gtk.Label() - vbox = gtk.VBox() - scroll_box = gtk.ScrolledWindow() - scroll_box.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + label = Gtk.Label() + vbox = Gtk.VBox() + scroll_box = Gtk.ScrolledWindow() + scroll_box.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scroll_box.add_with_viewport(vbox) notebook.append_page(scroll_box, label) self._params_boxes.append((tab, label, vbox)) # Docs for the block self._docs_text_display = doc_view = SimpleTextDisplay() - doc_view.get_buffer().create_tag('b', weight=pango.WEIGHT_BOLD) - self._docs_box = gtk.ScrolledWindow() - self._docs_box.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + doc_view.get_buffer().create_tag('b', weight=Pango.Weight.BOLD) + self._docs_box = Gtk.ScrolledWindow() + self._docs_box.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self._docs_box.add_with_viewport(self._docs_text_display) - notebook.append_page(self._docs_box, gtk.Label("Documentation")) + notebook.append_page(self._docs_box, Gtk.Label(label="Documentation")) # Generated code for the block if Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB.get_active(): self._code_text_display = code_view = SimpleTextDisplay() - code_view.set_wrap_mode(gtk.WRAP_NONE) - code_view.get_buffer().create_tag('b', weight=pango.WEIGHT_BOLD) - code_view.modify_font(pango.FontDescription( - 'monospace %d' % FONT_SIZE)) - code_box = gtk.ScrolledWindow() - code_box.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + code_view.set_wrap_mode(Gtk.WrapMode.NONE) + code_view.get_buffer().create_tag('b', weight=Pango.Weight.BOLD) + code_view.set_monospace(True) + # todo: set font size in non-deprecated way + # code_view.override_font(Pango.FontDescription('monospace %d' % Constants.FONT_SIZE)) + code_box = Gtk.ScrolledWindow() + code_box.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) code_box.add_with_viewport(self._code_text_display) - notebook.append_page(code_box, gtk.Label("Generated Code")) + notebook.append_page(code_box, Gtk.Label(label="Generated Code")) else: self._code_text_display = None # Error Messages for the block self._error_messages_text_display = SimpleTextDisplay() - self._error_box = gtk.ScrolledWindow() - self._error_box.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self._error_box = Gtk.ScrolledWindow() + self._error_box.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self._error_box.add_with_viewport(self._error_messages_text_display) vpaned.pack2(self._error_box) - vpaned.set_position(int(0.65 * MIN_DIALOG_HEIGHT)) + vpaned.set_position(int(0.65 * Constants.MIN_DIALOG_HEIGHT)) # Connect events self.connect('key-press-event', self._handle_key_press) @@ -144,25 +125,23 @@ class PropsDialog(gtk.Dialog): 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_name(), param.get_type(), - param.get_hide() == 'all', - ) for param in self._block.get_params()])) - return self._hash != old_hash + new_hash = self._hash = hash(tuple( + (hash(param), param.get_name(), param.get_type(), param.get_hide() == 'all',) + for param in self._block.get_params() + )) + return new_hash != old_hash def _handle_changed(self, *args): """ A change occurred 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 _activate_apply(self, *args): - self.set_response_sensitive(gtk.RESPONSE_APPLY, True) + self.set_response_sensitive(Gtk.ResponseType.APPLY, True) def update_gui(self, widget=None, force=False): """ @@ -173,43 +152,46 @@ class PropsDialog(gtk.Dialog): Update the documentation block. Hide the box if there are no docs. """ - # update the params box if force or self._params_changed(): # hide params box before changing for tab, label, vbox in self._params_boxes: - vbox.hide_all() + vbox.hide() # empty the params box for child in vbox.get_children(): vbox.remove(child) - child.destroy() + # child.destroy() # disabled because it throw errors... # repopulate the params box box_all_valid = True - for param in filter(lambda p: p.get_tab_label() == tab, self._block.get_params()): + for param in [p for p in self._block.get_params() if p.get_tab_label() == tab]: + # fixme: why do we even rebuild instead of really hiding params? if param.get_hide() == 'all': continue box_all_valid = box_all_valid and param.is_valid() + input_widget = param.get_input(self._handle_changed, self._activate_apply) - vbox.pack_start(input_widget, input_widget.expand) - label.set_markup(Utils.parse_template(TAB_LABEL_MARKUP_TMPL, valid=box_all_valid, tab=tab)) - # show params box with new params - vbox.show_all() - # update the errors box + input_widget.show_all() + vbox.pack_start(input_widget, input_widget.expand, True, 1) + + label.set_markup('<span foreground="{color}">{name}</span>'.format( + color='black' if box_all_valid else 'red', name=Utils.encode(tab) + )) + vbox.show() # show params box with new params + 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 + self._update_docs_page() - # update the generated code self._update_generated_code_page() def _update_docs_page(self): """Show documentation from XML and try to display best matching docstring""" - buffer = self._docs_text_display.get_buffer() - buffer.delete(buffer.get_start_iter(), buffer.get_end_iter()) - pos = buffer.get_end_iter() + buf = self._docs_text_display.get_buffer() + buf.delete(buf.get_start_iter(), buf.get_end_iter()) + pos = buf.get_end_iter() docstrings = self._block.get_doc() if not docstrings: @@ -219,11 +201,11 @@ class PropsDialog(gtk.Dialog): from_xml = docstrings.pop('', '') for line in from_xml.splitlines(): if line.lstrip() == line and line.endswith(':'): - buffer.insert_with_tags_by_name(pos, line + '\n', 'b') + buf.insert_with_tags_by_name(pos, line + '\n', 'b') else: - buffer.insert(pos, line + '\n') + buf.insert(pos, line + '\n') if from_xml: - buffer.insert(pos, '\n') + buf.insert(pos, '\n') # if given the current parameters an exact match can be made block_constructor = self._block.get_make().rsplit('.', 2)[-1] @@ -232,17 +214,17 @@ class PropsDialog(gtk.Dialog): docstrings = {block_class: docstrings[block_class]} # show docstring(s) extracted from python sources - for cls_name, docstring in docstrings.iteritems(): - buffer.insert_with_tags_by_name(pos, cls_name + '\n', 'b') - buffer.insert(pos, docstring + '\n\n') + for cls_name, docstring in six.iteritems(docstrings): + buf.insert_with_tags_by_name(pos, cls_name + '\n', 'b') + buf.insert(pos, docstring + '\n\n') pos.backward_chars(2) - buffer.delete(pos, buffer.get_end_iter()) + buf.delete(pos, buf.get_end_iter()) def _update_generated_code_page(self): if not self._code_text_display: return # user disabled code preview - buffer = self._code_text_display.get_buffer() + buf = self._code_text_display.get_buffer() block = self._block key = block.get_key() @@ -256,40 +238,34 @@ class PropsDialog(gtk.Dialog): def insert(header, text): if not text: return - buffer.insert_with_tags_by_name(buffer.get_end_iter(), header, 'b') - buffer.insert(buffer.get_end_iter(), text) + buf.insert_with_tags_by_name(buf.get_end_iter(), header, 'b') + buf.insert(buf.get_end_iter(), text) - buffer.delete(buffer.get_start_iter(), buffer.get_end_iter()) + buf.delete(buf.get_start_iter(), buf.get_end_iter()) insert('# Imports\n', '\n'.join(block.get_imports())) - if key.startswith('variable'): + if block.is_variable: insert('\n\n# Variables\n', block.get_var_make()) insert('\n\n# Blocks\n', block.get_make()) if src: insert('\n\n# External Code ({}.py)\n'.format(block.get_id()), src) def _handle_key_press(self, widget, event): - """ - Handle key presses from the keyboard. - Call the ok response when enter is pressed. - - Returns: - false to forward the keypress - """ - if (event.keyval == gtk.keysyms.Return and - event.state & gtk.gdk.CONTROL_MASK == 0 and - not isinstance(widget.get_focus(), gtk.TextView) - ): - self.response(gtk.RESPONSE_ACCEPT) + close_dialog = ( + event.keyval == Gdk.KEY_Return and + event.get_state() & Gdk.ModifierType.CONTROL_MASK == 0 and + not isinstance(widget.get_focus(), Gtk.TextView) + ) + if close_dialog: + self.response(Gtk.ResponseType.ACCEPT) return True # handled here + return False # forward the keypress def _handle_response(self, widget, response): - if response in (gtk.RESPONSE_APPLY, gtk.RESPONSE_ACCEPT): + if response in (Gtk.ResponseType.APPLY, Gtk.ResponseType.ACCEPT): for tab, label, vbox in self._params_boxes: for child in vbox.get_children(): child.apply_pending_changes() - self.set_response_sensitive(gtk.RESPONSE_APPLY, False) + self.set_response_sensitive(Gtk.ResponseType.APPLY, False) return True return False - - diff --git a/grc/gui/StateCache.py b/grc/gui/StateCache.py index 3cdb5f30ce..b109a1281b 100644 --- a/grc/gui/StateCache.py +++ b/grc/gui/StateCache.py @@ -17,8 +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 Actions -from Constants import STATE_CACHE_SIZE +from __future__ import absolute_import +from . import Actions +from .Constants import STATE_CACHE_SIZE class StateCache(object): """ diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py index 51b9b19e9f..e5d4ccaa35 100644 --- a/grc/gui/Utils.py +++ b/grc/gui/Utils.py @@ -17,37 +17,13 @@ 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 gobject +from __future__ import absolute_import -from Cheetah.Template import Template +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import GLib -from Constants import POSSIBLE_ROTATIONS, CANVAS_GRID_SIZE - - -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. - - Args: - gc: the graphics context - src_pixmap: the source pixmap - dst_pixmap: the destination pixmap - 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) +from .Constants import POSSIBLE_ROTATIONS, CANVAS_GRID_SIZE def get_rotated_coordinate(coor, rotation): @@ -73,7 +49,7 @@ def get_rotated_coordinate(coor, rotation): return x * cos_r + y * sin_r, -x * sin_r + y * cos_r -def get_angle_from_coordinates((x1, y1), (x2, y2)): +def get_angle_from_coordinates(p1, p2): """ Given two points, calculate the vector direction from point1 to point2, directions are multiples of 90 degrees. @@ -84,6 +60,8 @@ def get_angle_from_coordinates((x1, y1), (x2, y2)): Returns: the direction in degrees """ + (x1, y1) = p1 + (x2, y2) = p2 if y1 == y2: # 0 or 180 return 0 if x2 > x1 else 180 else: # 90 or 270 @@ -96,38 +74,15 @@ def encode(value): Older versions of glib seg fault if the last byte starts a multi-byte character. """ - valid_utf8 = value.decode('utf-8', errors='replace').encode('utf-8') - return gobject.markup_escape_text(valid_utf8) - - -class TemplateParser(object): - def __init__(self): - self.cache = {} - - def __call__(self, tmpl_str, **kwargs): - """ - Parse the template string with the given args. - Pass in the xml encode method for pango escape chars. - - Args: - tmpl_str: the template as a string - - Returns: - a string of the parsed template - """ - kwargs['encode'] = encode - template = self.cache.setdefault(tmpl_str, Template.compile(tmpl_str)) - return str(template(namespaces=kwargs)) - -parse_template = TemplateParser() + return GLib.markup_escape_text(valid_utf8) def align_to_grid(coor, mode=round): def align(value): return int(mode(value / (1.0 * CANVAS_GRID_SIZE)) * CANVAS_GRID_SIZE) try: - return map(align, coor) + return [align(c) for c in coor] except TypeError: x = coor return align(coor) diff --git a/grc/gui/VariableEditor.py b/grc/gui/VariableEditor.py index 7721f3bda6..399e4ec475 100644 --- a/grc/gui/VariableEditor.py +++ b/grc/gui/VariableEditor.py @@ -1,5 +1,5 @@ """ -Copyright 2015 Free Software Foundation, Inc. +Copyright 2015, 2016 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -17,12 +17,13 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -from operator import attrgetter +from __future__ import absolute_import -import pygtk -pygtk.require('2.0') -import gtk -import gobject +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import GObject from . import Actions from . import Preferences @@ -32,34 +33,35 @@ BLOCK_INDEX = 0 ID_INDEX = 1 -class VariableEditorContextMenu(gtk.Menu): +class VariableEditorContextMenu(Gtk.Menu): """ A simple context menu for our variable editor """ + def __init__(self, var_edit): - gtk.Menu.__init__(self) + Gtk.Menu.__init__(self) - self.imports = gtk.MenuItem("Add _Import") + self.imports = Gtk.MenuItem("Add _Import") self.imports.connect('activate', var_edit.handle_action, var_edit.ADD_IMPORT) self.add(self.imports) - self.variables = gtk.MenuItem("Add _Variable") + self.variables = Gtk.MenuItem("Add _Variable") self.variables.connect('activate', var_edit.handle_action, var_edit.ADD_VARIABLE) self.add(self.variables) - self.add(gtk.SeparatorMenuItem()) + self.add(Gtk.SeparatorMenuItem()) - self.enable = gtk.MenuItem("_Enable") + self.enable = Gtk.MenuItem("_Enable") self.enable.connect('activate', var_edit.handle_action, var_edit.ENABLE_BLOCK) - self.disable = gtk.MenuItem("_Disable") + self.disable = Gtk.MenuItem("_Disable") self.disable.connect('activate', var_edit.handle_action, var_edit.DISABLE_BLOCK) self.add(self.enable) self.add(self.disable) - self.add(gtk.SeparatorMenuItem()) + self.add(Gtk.SeparatorMenuItem()) - self.delete = gtk.MenuItem("_Delete") + self.delete = Gtk.MenuItem("_Delete") self.delete.connect('activate', var_edit.handle_action, var_edit.DELETE_BLOCK) self.add(self.delete) - self.add(gtk.SeparatorMenuItem()) + self.add(Gtk.SeparatorMenuItem()) - self.properties = gtk.MenuItem("_Properties...") + self.properties = Gtk.MenuItem("_Properties...") self.properties.connect('activate', var_edit.handle_action, var_edit.OPEN_PROPERTIES) self.add(self.properties) self.show_all() @@ -71,7 +73,7 @@ class VariableEditorContextMenu(gtk.Menu): self.disable.set_sensitive(selected and enabled) -class VariableEditor(gtk.VBox): +class VariableEditor(Gtk.VBox): # Actions that are handled by the editor ADD_IMPORT = 0 @@ -82,23 +84,28 @@ class VariableEditor(gtk.VBox): ENABLE_BLOCK = 5 DISABLE_BLOCK = 6 - def __init__(self, platform, get_flow_graph): - gtk.VBox.__init__(self) - self.platform = platform - self.get_flow_graph = get_flow_graph + __gsignals__ = { + 'create_new_block': (GObject.SIGNAL_RUN_FIRST, None, (str,)), + 'remove_block': (GObject.SIGNAL_RUN_FIRST, None, (str,)) + } + + def __init__(self): + Gtk.VBox.__init__(self) self._block = None self._mouse_button_pressed = False + self._imports = [] + self._variables = [] # Only use the model to store the block reference and name. # Generate everything else dynamically - self.treestore = gtk.TreeStore(gobject.TYPE_PYOBJECT, # Block reference - gobject.TYPE_STRING) # Category and block name - self.treeview = gtk.TreeView(self.treestore) + self.treestore = Gtk.TreeStore(GObject.TYPE_PYOBJECT, # Block reference + GObject.TYPE_STRING) # Category and block name + self.treeview = Gtk.TreeView(self.treestore) self.treeview.set_enable_search(False) self.treeview.set_search_column(-1) #self.treeview.set_enable_search(True) #self.treeview.set_search_column(ID_INDEX) - self.treeview.get_selection().set_mode('single') + self.treeview.get_selection().set_mode(Gtk.SelectionMode.SINGLE) self.treeview.set_headers_visible(True) self.treeview.connect('button-press-event', self._handle_mouse_button_press) self.treeview.connect('button-release-event', self._handle_mouse_button_release) @@ -106,67 +113,63 @@ class VariableEditor(gtk.VBox): self.treeview.connect('key-press-event', self._handle_key_button_press) # Block Name or Category - self.id_cell = gtk.CellRendererText() + self.id_cell = Gtk.CellRendererText() self.id_cell.connect('edited', self._handle_name_edited_cb) - id_column = gtk.TreeViewColumn("Id", self.id_cell, text=ID_INDEX) + id_column = Gtk.TreeViewColumn("Id", self.id_cell, text=ID_INDEX) id_column.set_name("id") id_column.set_resizable(True) id_column.set_max_width(300) id_column.set_min_width(80) id_column.set_fixed_width(100) - id_column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) + id_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) id_column.set_cell_data_func(self.id_cell, self.set_properties) self.id_column = id_column self.treeview.append_column(id_column) - self.treestore.set_sort_column_id(ID_INDEX, gtk.SORT_ASCENDING) + self.treestore.set_sort_column_id(ID_INDEX, Gtk.SortType.ASCENDING) # For forcing resize self._col_width = 0 # Block Value - self.value_cell = gtk.CellRendererText() + self.value_cell = Gtk.CellRendererText() self.value_cell.connect('edited', self._handle_value_edited_cb) - value_column = gtk.TreeViewColumn("Value", self.value_cell) + value_column = Gtk.TreeViewColumn("Value", self.value_cell) value_column.set_name("value") value_column.set_resizable(False) value_column.set_expand(True) value_column.set_min_width(100) - value_column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) + value_column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) value_column.set_cell_data_func(self.value_cell, self.set_value) self.value_column = value_column self.treeview.append_column(value_column) # Block Actions (Add, Remove) - self.action_cell = gtk.CellRendererPixbuf() + self.action_cell = Gtk.CellRendererPixbuf() value_column.pack_start(self.action_cell, False) value_column.set_cell_data_func(self.action_cell, self.set_icon) # 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 = Gtk.ScrolledWindow() + scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolled_window.add_with_viewport(self.treeview) scrolled_window.set_size_request(DEFAULT_BLOCKS_WINDOW_WIDTH, -1) - self.pack_start(scrolled_window) + self.pack_start(scrolled_window, True, True, 0) # Context menus self._context_menu = VariableEditorContextMenu(self) self._confirm_delete = Preferences.variable_editor_confirm_delete() # Sets cell contents - def set_icon(self, col, cell, model, iter): + def set_icon(self, col, cell, model, iter, data): block = model.get_value(iter, BLOCK_INDEX) - if block: - pb = self.treeview.render_icon(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU, None) - else: - pb = self.treeview.render_icon(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU, None) - cell.set_property('pixbuf', pb) + cell.set_property('icon-name', 'window-close' if block else 'list-add') - def set_value(self, col, cell, model, iter): + def set_value(self, col, cell, model, iter, data): sp = cell.set_property block = model.get_value(iter, BLOCK_INDEX) # Set the default properties for this column first. # Some set in set_properties() may be overridden (editable for advanced variable blocks) - self.set_properties(col, cell, model, iter) + self.set_properties(col, cell, model, iter, data) # Set defaults value = None @@ -198,7 +201,7 @@ class VariableEditor(gtk.VBox): # Always set the text value. sp('text', value) - def set_properties(self, col, cell, model, iter): + def set_properties(self, col, cell, model, iter, data): sp = cell.set_property block = model.get_value(iter, BLOCK_INDEX) # Set defaults @@ -217,19 +220,12 @@ class VariableEditor(gtk.VBox): if block.get_error_messages(): sp('foreground', 'red') - def update_gui(self): - if not self.get_flow_graph(): - return - self._update_blocks() + def update_gui(self, blocks): + self._imports = [block for block in blocks if block.is_import] + self._variables = [block for block in blocks if block.is_variable] self._rebuild() self.treeview.expand_all() - def _update_blocks(self): - self._imports = filter(attrgetter('is_import'), - self.get_flow_graph().blocks) - self._variables = filter(attrgetter('is_variable'), - self.get_flow_graph().blocks) - def _rebuild(self, *args): self.treestore.clear() imports = self.treestore.append(None, [None, 'Imports']) @@ -258,23 +254,23 @@ class VariableEditor(gtk.VBox): key presses or mouse clicks. Also triggers an update of the flow graph and editor. """ if key == self.ADD_IMPORT: - self.get_flow_graph().add_new_block('import') + self.emit('create_new_block', 'import') elif key == self.ADD_VARIABLE: - self.get_flow_graph().add_new_block('variable') + self.emit('create_new_block', 'variable') elif key == self.OPEN_PROPERTIES: Actions.BLOCK_PARAM_MODIFY(self._block) elif key == self.DELETE_BLOCK: - self.get_flow_graph().remove_element(self._block) + self.emit('remove_block', self._block.get_id()) elif key == self.DELETE_CONFIRM: if self._confirm_delete: # Create a context menu to confirm the delete operation - confirmation_menu = gtk.Menu() + confirmation_menu = Gtk.Menu() block_id = self._block.get_param('id').get_value().replace("_", "__") - confirm = gtk.MenuItem("Delete {}".format(block_id)) + confirm = Gtk.MenuItem(label="Delete {}".format(block_id)) confirm.connect('activate', self.handle_action, self.DELETE_BLOCK) confirmation_menu.add(confirm) confirmation_menu.show_all() - confirmation_menu.popup(None, None, None, event.button, event.time) + confirmation_menu.popup(None, None, None, None, event.button, event.time) else: self.handle_action(None, self.DELETE_BLOCK, None) elif key == self.ENABLE_BLOCK: @@ -302,12 +298,12 @@ class VariableEditor(gtk.VBox): if event.button == 1 and col.get_name() == "value": # Make sure this has a block (not the import/variable rows) - if self._block and event.type == gtk.gdk._2BUTTON_PRESS: + if self._block and event.type == Gdk.EventType._2BUTTON_PRESS: # Open the advanced dialog if it is a gui variable if self._block.get_key() not in ("variable", "import"): self.handle_action(None, self.OPEN_PROPERTIES, event=event) return True - if event.type == gtk.gdk.BUTTON_PRESS: + if event.type == Gdk.EventType.BUTTON_PRESS: # User is adding/removing blocks # Make sure this is the action cell (Add/Remove Icons) if path[2] > col.cell_get_position(self.action_cell)[0]: @@ -320,15 +316,15 @@ class VariableEditor(gtk.VBox): else: self.handle_action(None, self.DELETE_CONFIRM, event=event) return True - elif event.button == 3 and event.type == gtk.gdk.BUTTON_PRESS: + elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS: if self._block: self._context_menu.update_sensitive(True, enabled=self._block.get_enabled()) else: self._context_menu.update_sensitive(False) - self._context_menu.popup(None, None, None, event.button, event.time) + self._context_menu.popup(None, None, None, None, event.button, event.time) # Null handler. Stops the treeview from handling double click events. - if event.type == gtk.gdk._2BUTTON_PRESS: + if event.type == Gdk.EventType._2BUTTON_PRESS: return True return False diff --git a/grc/gui/external_editor.py b/grc/gui/external_editor.py index 76f21412b0..11d6fd7ebb 100644 --- a/grc/gui/external_editor.py +++ b/grc/gui/external_editor.py @@ -17,6 +17,8 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ +from __future__ import absolute_import, print_function + import os import sys import time @@ -71,7 +73,7 @@ class ExternalEditor(threading.Thread): time.sleep(1) except Exception as e: - print >> sys.stderr, "file monitor crashed:", str(e) + print("file monitor crashed:", str(e), file=sys.stderr) else: # print "file monitor: done with", filename pass @@ -79,7 +81,7 @@ class ExternalEditor(threading.Thread): if __name__ == '__main__': def p(data): - print data + print(data) e = ExternalEditor('/usr/bin/gedit', "test", "content", p) e.open_editor() |