diff options
-rw-r--r-- | grc/gui/ActionHandler.py | 30 | ||||
-rw-r--r-- | grc/gui/Dialogs.py | 401 | ||||
-rw-r--r-- | grc/gui/FileDialogs.py | 101 | ||||
-rw-r--r-- | grc/gui/FlowGraph.py | 6 | ||||
-rw-r--r-- | grc/gui/MainWindow.py | 8 | ||||
-rw-r--r-- | grc/gui/ParamWidgets.py | 2 | ||||
-rw-r--r-- | grc/gui/PropsDialog.py | 1 |
7 files changed, 326 insertions, 223 deletions
diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 237dd6c84c..9e57565772 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -26,10 +26,7 @@ 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, - OpenQSSFileDialog) +from . import Dialogs, Preferences, Actions, Executor, Constants, FileDialogs from .MainWindow import MainWindow from .ParserErrorsDialog import ParserErrorsDialog from .PropsDialog import PropsDialog @@ -379,13 +376,13 @@ class ActionHandler: # Window stuff ################################################## elif action == Actions.ABOUT_WINDOW_DISPLAY: - Dialogs.AboutDialog(self.platform.config) + Dialogs.show_about(main, self.platform.config) elif action == Actions.HELP_WINDOW_DISPLAY: - Dialogs.HelpDialog() + Dialogs.show_help(main) elif action == Actions.TYPES_WINDOW_DISPLAY: - Dialogs.TypesDialog(self.platform) + Dialogs.show_types(main) elif action == Actions.ERRORS_WINDOW_DISPLAY: - Dialogs.ErrorsDialog(flow_graph) + Dialogs.ErrorsDialog(main, flow_graph).run_and_destroy() elif action == Actions.TOGGLE_CONSOLE_WINDOW: main.update_panel_visibility(main.CONSOLE, action.get_active()) action.save_to_preferences() @@ -401,7 +398,7 @@ class ActionHandler: elif action == Actions.CLEAR_CONSOLE: main.text_display.clear() elif action == Actions.SAVE_CONSOLE: - file_path = SaveConsoleFileDialog(page.file_path).run() + file_path = FileDialogs.SaveConsole(main, page.file_path).run() if file_path is not None: main.text_display.save(file_path) elif action == Actions.TOGGLE_HIDE_DISABLED_BLOCKS: @@ -437,7 +434,7 @@ class ActionHandler: # Leave it enabled action.set_sensitive(True) action.set_active(True) - #Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR.set_sensitive(action.get_active()) + # Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR.set_sensitive(action.get_active()) action.save_to_preferences() elif action == Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR: if self.init: @@ -511,7 +508,7 @@ 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.file_path).run() + file_paths = args if args else FileDialogs.OpenFlowGraph(main, 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)) @@ -519,8 +516,8 @@ class ActionHandler: main.tool_bar.refresh_submenus() main.menu_bar.refresh_submenus() elif action == Actions.FLOW_GRAPH_OPEN_QSS_THEME: - file_paths = OpenQSSFileDialog(self.platform.config.install_prefix + - '/share/gnuradio/themes/').run() + file_paths = FileDialogs.OpenQSS(main, self.platform.config.install_prefix + + '/share/gnuradio/themes/').run() if file_paths: try: prefs = self.platform.config.prefs @@ -544,7 +541,7 @@ class ActionHandler: Messages.send_fail_save(page.file_path) page.saved = False elif action == Actions.FLOW_GRAPH_SAVE_AS: - file_path = SaveFlowGraphFileDialog(page.file_path).run() + file_path = FileDialogs.SaveFlowGraph(main, page.file_path).run() if file_path is not None: page.file_path = os.path.abspath(file_path) Actions.FLOW_GRAPH_SAVE() @@ -552,7 +549,7 @@ class ActionHandler: main.tool_bar.refresh_submenus() main.menu_bar.refresh_submenus() elif action == Actions.FLOW_GRAPH_SCREEN_CAPTURE: - file_path, background_transparent = SaveScreenShotDialog(page.file_path).run() + file_path, background_transparent = FileDialogs.SaveScreenShot(main, 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:]) @@ -576,9 +573,10 @@ class ActionHandler: if not page.process: Actions.FLOW_GRAPH_GEN() xterm = self.platform.config.xterm_executable + Dialogs.show_missing_xterm(main, xterm) if Preferences.xterm_missing() != xterm: if not os.path.exists(xterm): - Dialogs.MissingXTermDialog(xterm) + Dialogs.show_missing_xterm(main, xterm) Preferences.xterm_missing(xterm) if page.saved and page.file_path: Executor.ExecFlowGraphThread( diff --git a/grc/gui/Dialogs.py b/grc/gui/Dialogs.py index da271fe46e..da4b11a3b6 100644 --- a/grc/gui/Dialogs.py +++ b/grc/gui/Dialogs.py @@ -1,38 +1,36 @@ -""" -Copyright 2008, 2009 Free Software Foundation, Inc. -This file is part of GNU Radio - -GNU Radio Companion is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -GNU Radio Companion is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA -""" +# Copyright 2008, 2009, 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 gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk -from gi.repository import Gdk -from gi.repository import GObject import sys +import textwrap from distutils.spawn import find_executable +from gi.repository import Gtk + from . import Utils, Actions, Constants from ..core import Messages class SimpleTextDisplay(Gtk.TextView): - """A non editable gtk text view.""" + """ + A non user-editable gtk text view. + """ def __init__(self, text=''): """ @@ -41,16 +39,18 @@ class SimpleTextDisplay(Gtk.TextView): Args: text: the text to display (string) """ - text_buffer = Gtk.TextBuffer() - text_buffer.set_text(text) - self.set_text = text_buffer.set_text - GObject.GObject.__init__(self) + Gtk.TextView.__init__(self) + self.set_text = self.get_buffer().set_text + self.set_text(text) self.set_editable(False) self.set_cursor_visible(False) self.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) class TextDisplay(SimpleTextDisplay): + """ + A non user-editable scrollable text view with popup menu. + """ def __init__(self, text=''): """ @@ -64,54 +64,77 @@ class TextDisplay(SimpleTextDisplay): self.connect("populate-popup", self.populate_popup) def insert(self, line): - # make backspaces work + """ + Append text after handling backspaces and auto-scroll. + + Args: + line: the text to append (string) + """ line = self._consume_backspaces(line) - # add the remaining text to buffer self.get_buffer().insert(self.get_buffer().get_end_iter(), line) - # Automatically scroll on insert self.scroll_to_end() def _consume_backspaces(self, line): - """removes text from the buffer if line starts with \b*""" - if not line: return + """ + Removes text from the buffer if line starts with '\b' + + Args: + line: a string which may contain backspaces + + Returns: + The string that remains from 'line' with leading '\b's removed. + """ + if not line: + return + # for each \b delete one char from the buffer back_count = 0 start_iter = self.get_buffer().get_end_iter() while line[back_count] == '\b': # stop at the beginning of a line - if not start_iter.starts_line(): start_iter.backward_char() + if not start_iter.starts_line(): + start_iter.backward_char() back_count += 1 - # remove chars + # remove chars from buffer self.get_buffer().delete(start_iter, self.get_buffer().get_end_iter()) - # return remaining text return line[back_count:] def scroll_to_end(self): + """ Update view's scroll position. """ if self.scroll_lock: - buffer = self.get_buffer() - buffer.move_mark(buffer.get_insert(), buffer.get_end_iter()) + buf = self.get_buffer() + buf.move_mark(buf.get_insert(), buf.get_end_iter()) # TODO: Fix later #self.scroll_to_mark(buffer.get_insert(), 0.0) def clear(self): - buffer = self.get_buffer() - buffer.delete(buffer.get_start_iter(), buffer.get_end_iter()) + """ Clear all text from buffer. """ + buf = self.get_buffer() + buf.delete(buf.get_start_iter(), buf.get_end_iter()) def save(self, file_path): - console_file = open(file_path, 'w') - buffer = self.get_buffer() - console_file.write(buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), True)) - console_file.close() + """ + Save context of buffer to the given file. + + Args: + file_path: location to save buffer contents + """ + with open(file_path, 'w') as logfile: + buf = self.get_buffer() + logfile.write(buf.get_text(buf.get_start_iter(), + buf.get_end_iter(), True)) - # Callback functions to handle the scrolling lock and clear context menus options # Action functions are set by the ActionHandler's init function def clear_cb(self, menu_item, web_view): + """ Callback function to clear the text buffer """ Actions.CLEAR_CONSOLE() def scroll_back_cb(self, menu_item, web_view): + """ Callback function to toggle scroll lock """ Actions.TOGGLE_SCROLL_LOCK() def save_cb(self, menu_item, web_view): + """ Callback function to save the buffer """ Actions.SAVE_CONSOLE() def populate_popup(self, view, menu): @@ -134,79 +157,151 @@ class TextDisplay(SimpleTextDisplay): return False -def MessageDialogHelper(type, buttons, title=None, markup=None, default_response=None, extra_buttons=None): - """ - Create a modal message dialog and run it. +class MessageDialogWrapper(Gtk.MessageDialog): + """ Run a message dialog. """ - Args: - 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.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) - markup: the message text with pango markup - default_response: if set, determines which button is highlighted by default - extra_buttons: a tuple containing pairs of values; each value is the button's text and the button's return value - - Returns: - the gtk response from run() - """ - 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) - if default_response: message_dialog.set_default_response(default_response) - response = message_dialog.run() - message_dialog.destroy() - return response - - -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): - """A cute little about dialog.""" - - def __init__(self, config): - """AboutDialog constructor.""" - GObject.GObject.__init__(self) - self.set_name(config.name) - self.set_version(config.version) - self.set_license(config.license) - self.set_copyright(config.license.splitlines()[0]) - self.set_website(config.website) - self.run() - self.destroy() - - -def HelpDialog(): MessageDialogHelper( - type=Gtk.MessageType.INFO, - buttons=Gtk.ButtonsType.CLOSE, - title='Help', - markup="""\ -<b>Usage Tips</b> - -<u>Add block</u>: drag and drop or double click a block in the block selection window. -<u>Rotate block</u>: Select a block, press left/right on the keyboard. -<u>Change type</u>: Select a block, press up/down on the keyboard. -<u>Edit parameters</u>: double click on a block in the flow graph. -<u>Make connection</u>: click on the source port of one block, then click on the sink port of another block. -<u>Remove connection</u>: select the connection and press delete, or drag the connection. - -* See the menu for other keyboard shortcuts.""") + def __init__(self, parent, message_type, buttons, title=None, markup=None, + default_response=None, extra_buttons=None): + """ + Create a modal message dialog. + Args: + message_type: the type of message may be one of: + Gtk.MessageType.INFO + Gtk.MessageType.WARNING + Gtk.MessageType.QUESTION or Gtk.MessageType.ERROR + buttons: the predefined set of buttons to use: + Gtk.ButtonsType.NONE + Gtk.ButtonsType.OK + Gtk.ButtonsType.CLOSE + Gtk.ButtonsType.CANCEL + Gtk.ButtonsType.YES_NO + Gtk.ButtonsType.OK_CANCEL + title: the title of the window (string) + markup: the message text with pango markup + default_response: if set, determines which button is highlighted by default + extra_buttons: a tuple containing pairs of values: + each value is the button's text and the button's return value -def TypesDialog(platform): + """ + Gtk.MessageDialog.__init__( + self, transient_for=parent, modal=True, destroy_with_parent=True, + message_type=message_type, buttons=buttons + ) + if title: + self.set_title(title) + if markup: + self.set_markup(markup) + if extra_buttons: + self.add_buttons(*extra_buttons) + if default_response: + self.set_default_response(default_response) + + def run_and_destroy(self): + response = self.run() + self.hide() + return response + + +class ErrorsDialog(Gtk.Dialog): + """ Display flowgraph errors. """ + + def __init__(self, parent, flowgraph): + """Create a listview of errors""" + Gtk.Dialog.__init__( + self, + title='Errors and Warnings', + transient_for=parent, + modal=True, + destroy_with_parent=True, + ) + self.add_buttons(Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT) + self.set_size_request(750, Constants.MIN_DIALOG_HEIGHT) + self.set_border_width(10) + + self.store = Gtk.ListStore(str, str, str) + self.update(flowgraph) + + self.treeview = Gtk.TreeView(model=self.store) + for i, column_title in enumerate(["Block", "Aspect", "Message"]): + renderer = Gtk.CellRendererText() + column = Gtk.TreeViewColumn(column_title, renderer, text=i) + column.set_sort_column_id(i) # liststore id matches treeview id + column.set_resizable(True) + self.treeview.append_column(column) + + self.scrollable = Gtk.ScrolledWindow() + self.scrollable.set_vexpand(True) + self.scrollable.add(self.treeview) + + self.vbox.pack_start(self.scrollable, True, True, 0) + self.show_all() + + def update(self, flowgraph): + self.store.clear() + for element, message in flowgraph.iter_error_messages(): + if element.is_block: + src, aspect = element.get_id(), '' + elif element.is_connection: + src = element.source_block.get_id() + aspect = "Connection to '{}'".format(element.sink_block.get_id()) + elif element.is_port: + src = element.parent_block.get_id() + aspect = "{} '{}'".format('Sink' if element.is_sink else 'Source', element.name) + elif element.is_param: + src = element.parent_block.get_id() + aspect = "Param '{}'".format(element.name) + else: + src = aspect = '' + self.store.append([src, aspect, message]) + + def run_and_destroy(self): + response = self.run() + self.hide() + return response + + +def show_about(parent, config): + ad = Gtk.AboutDialog(transient_for=parent) + ad.set_program_name(config.name) + ad.set_name('') + ad.set_version(config.version) + ad.set_license(config.license) + + try: + ad.set_logo(Gtk.IconTheme().load_icon('gnuradio-grc', 64, 0)) + except: + pass + + ad.set_copyright(config.license.splitlines()[0]) + ad.set_website(config.website) + + ad.connect("response", lambda action, param: action.hide()) + ad.show() + + +def show_help(parent): + """ Display basic usage tips. """ + markup = textwrap.dedent("""\ + <b>Usage Tips</b> + \n\ + <u>Add block</u>: drag and drop or double click a block in the block selection window. + <u>Rotate block</u>: Select a block, press left/right on the keyboard. + <u>Change type</u>: Select a block, press up/down on the keyboard. + <u>Edit parameters</u>: double click on a block in the flow graph. + <u>Make connection</u>: click on the source port of one block, then click on the sink port of another block. + <u>Remove connection</u>: select the connection and press delete, or drag the connection. + \n\ + * See the menu for other keyboard shortcuts.\ + """) + + MessageDialogWrapper( + parent, Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, title='Help', markup=markup + ).run_and_destroy() + + +def show_types(parent): + """ Display information about standard data types. """ colors = [(name, color) for name, key, sizeof, color in Constants.CORE_TYPES] max_len = 10 + max(len(name) for name, code in colors) @@ -215,62 +310,65 @@ def TypesDialog(platform): ''.format(color=color, name=Utils.encode(name).center(max_len)) for name, color in colors ) - MessageDialogHelper( - type=Gtk.MessageType.INFO, - buttons=Gtk.ButtonsType.CLOSE, - title='Types - Color Mapping', - markup=message - ) + MessageDialogWrapper( + parent, Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, title='Types - Color Mapping', markup=message + ).run_and_destroy() -def MissingXTermDialog(xterm): - MessageDialogHelper( - 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 " - "section [grc], 'xterm_executable'.\n" - "\n" - "(This message is shown only once)").format(xterm) - ) +def show_missing_xterm(parent, xterm): + markup = textwrap.dedent("""\ + The xterm executable {0!r} is missing. + You can change this setting in your gnurado.conf, in section [grc], 'xterm_executable'. + \n\ + (This message is shown only once)\ + """).format(xterm) + + MessageDialogWrapper( + parent, message_type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.OK, + title='Warning: missing xterm executable', markup=markup + ).run_and_destroy() + + +def choose_editor(parent, config): + """ + Give the option to either choose an editor or use the default. + """ + if config.editor and find_executable(config.editor): + return config.editor -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.ResponseType.YES, 'Use Default', Gtk.ResponseType.NO, Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL ) - response = MessageDialogHelper( - Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, 'Choose Editor', - 'Would you like to choose the editor to use?', Gtk.ResponseType.YES, buttons - ) + response = MessageDialogWrapper( + parent, message_type=Gtk.MessageType.QUESTION, buttons=Gtk.ButtonsType.NONE, + title='Choose Editor', markup='Would you like to choose the editor to use?', + default_response=Gtk.ResponseType.YES, extra_buttons=buttons + ).run_and_destroy() - # Handle the inital default/choose/cancel response + # Handle the initial default/choose/cancel response # User wants to choose the editor to use + editor = '' if response == Gtk.ResponseType.YES: file_dialog = Gtk.FileChooserDialog( 'Select an Editor...', None, Gtk.FileChooserAction.OPEN, - ('gtk-cancel', Gtk.ResponseType.CANCEL, 'gtk-open', Gtk.ResponseType.OK) + ('gtk-cancel', Gtk.ResponseType.CANCEL, 'gtk-open', Gtk.ResponseType.OK), + transient_for=parent ) 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.ResponseType.OK: - config.editor = file_path = file_dialog.get_filename() - file_dialog.destroy() - return file_path + editor = file_dialog.get_filename() finally: - file_dialog.destroy() + file_dialog.hide() # Go with the default editor elif response == Gtk.ResponseType.NO: - # Determine the platform try: process = None if sys.platform.startswith('linux'): @@ -280,13 +378,10 @@ def ChooseEditorDialog(config): if process is None: raise ValueError("Can't find default editor executable") # Save - config.editor = process - return process + editor = config.editor = process except Exception: Messages.send('>>> Unable to load the default editor. Please choose an editor.\n') - # Just reset of the constant and force the user to select an editor the next time - config.editor = '' - return - Messages.send('>>> No editor selected.\n') - return + if editor == '': + Messages.send('>>> No editor selected.\n') + return editor diff --git a/grc/gui/FileDialogs.py b/grc/gui/FileDialogs.py index 23fd7e7900..afd41af58c 100644 --- a/grc/gui/FileDialogs.py +++ b/grc/gui/FileDialogs.py @@ -18,18 +18,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ 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 -from . import Preferences from os import path -from . import Utils + +from gi.repository import Gtk + +from . import Constants, Preferences, Utils +from .Dialogs import MessageDialogWrapper + ################################################## # Constants @@ -52,14 +48,14 @@ def get_flow_graph_files_filter(): def get_text_files_filter(): filter = Gtk.FileFilter() filter.set_name('Text Files') - filter.add_pattern('*'+TEXT_FILE_EXTENSION) + filter.add_pattern('*' + Constants.TEXT_FILE_EXTENSION) return filter def get_image_files_filter(): filter = Gtk.FileFilter() filter.set_name('Image Files') - filter.add_pattern('*'+IMAGE_FILE_EXTENSION) + filter.add_pattern('*' + Constants.IMAGE_FILE_EXTENSION) return filter @@ -84,7 +80,7 @@ class FileDialogHelper(Gtk.FileChooserDialog): Implement a file chooser dialog with only necessary parameters. """ - def __init__(self, action, title): + def __init__(self, parent, action, title): """ FileDialogHelper contructor. Create a save or open dialog with cancel and ok buttons. @@ -94,8 +90,16 @@ class FileDialogHelper(Gtk.FileChooserDialog): action: Gtk.FileChooserAction.OPEN or Gtk.FileChooserAction.SAVE title: the title of the dialog (string) """ - ok_stock = {Gtk.FileChooserAction.OPEN : 'gtk-open', Gtk.FileChooserAction.SAVE : 'gtk-save'}[action] - Gtk.FileChooserDialog.__init__(self, title, None, action, ('gtk-cancel', Gtk.ResponseType.CANCEL, ok_stock, Gtk.ResponseType.OK)) + ok_stock = { + Gtk.FileChooserAction.OPEN: 'gtk-open', + Gtk.FileChooserAction.SAVE: 'gtk-save' + }[action] + + Gtk.FileChooserDialog.__init__( + self, title=title, action=action, + transient_for=parent + ) + self.add_buttons('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()) @@ -104,37 +108,39 @@ class FileDialogHelper(Gtk.FileChooserDialog): class FileDialog(FileDialogHelper): """A dialog box to save or open flow graph files. This is a base class, do not use.""" - def __init__(self, current_file_path=''): + def __init__(self, parent, current_file_path=''): """ FileDialog constructor. Args: current_file_path: the current directory or path to the open flow graph """ - if not current_file_path: current_file_path = path.join(DEFAULT_FILE_PATH, NEW_FLOGRAPH_TITLE + Preferences.file_extension()) + if not current_file_path: + current_file_path = path.join(Constants.DEFAULT_FILE_PATH, + Constants.NEW_FLOGRAPH_TITLE + Preferences.file_extension()) if self.type == OPEN_FLOW_GRAPH: - FileDialogHelper.__init__(self, Gtk.FileChooserAction.OPEN, 'Open a Flow Graph from a File...') + FileDialogHelper.__init__(self, parent, 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.FileChooserAction.SAVE, 'Save a Flow Graph to a File...') + FileDialogHelper.__init__(self, parent, 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.FileChooserAction.SAVE, 'Save Console to a File...') + FileDialogHelper.__init__(self, parent, 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 + self.set_current_name(file_path) # show the current filename elif self.type == SAVE_IMAGE: - FileDialogHelper.__init__(self, Gtk.FileChooserAction.SAVE, 'Save a Flow Graph Screen Shot...') + FileDialogHelper.__init__(self, parent, 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 + current_file_path = current_file_path + Constants.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.FileChooserAction.OPEN, 'Open a QSS theme...') + FileDialogHelper.__init__(self, parent, 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 + self.set_current_folder(path.dirname(current_file_path)) # current directory def add_and_set_filter(self, filter): """ @@ -156,7 +162,7 @@ class FileDialog(FileDialogHelper): Returns: the complete file path """ - if Gtk.FileChooserDialog.run(self) != Gtk.ResponseType.OK: return None #response was cancel + if Gtk.FileChooserDialog.run(self) != Gtk.ResponseType.OK: return None # response was cancel ############################################# # Handle Save Dialogs ############################################# @@ -164,18 +170,21 @@ class FileDialog(FileDialogHelper): filename = self.get_filename() extension = { SAVE_FLOW_GRAPH: Preferences.file_extension(), - SAVE_CONSOLE: TEXT_FILE_EXTENSION, - SAVE_IMAGE: IMAGE_FILE_EXTENSION, + SAVE_CONSOLE: Constants.TEXT_FILE_EXTENSION, + SAVE_IMAGE: Constants.IMAGE_FILE_EXTENSION, }[self.type] - #append the missing file extension if the filter matches - if path.splitext(filename)[1].lower() != extension: filename += extension - self.set_current_name(path.basename(filename)) #show the filename with extension - if path.exists(filename): #ask the user to confirm overwrite - if MessageDialogHelper( + # append the missing file extension if the filter matches + if path.splitext(filename)[1].lower() != extension: + filename += extension + self.set_current_name(path.basename(filename)) # show the filename with extension + if path.exists(filename): # ask the user to confirm overwrite + response = MessageDialogWrapper( 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() + ).run_and_destroy() + if response == Gtk.ResponseType.NO: + return self.get_rectified_filename() return filename ############################################# # Handle Open Dialogs @@ -183,11 +192,11 @@ class FileDialog(FileDialogHelper): elif self.type in (OPEN_FLOW_GRAPH, OPEN_QSS_THEME): filenames = self.get_filenames() for filename in filenames: - if not path.exists(filename): #show a warning and re-run - MessageDialogHelper( + if not path.exists(filename): # show a warning and re-run + MessageDialogWrapper( Gtk.MessageType.WARNING, Gtk.ButtonsType.CLOSE, 'Cannot Open!', 'File <b>{filename}</b> Does not Exist!'.format(filename=Utils.encode(filename)), - ) + ).run_and_destroy() return self.get_rectified_filename() return filenames @@ -203,19 +212,19 @@ class FileDialog(FileDialogHelper): return filename -class OpenFlowGraphFileDialog(FileDialog): +class OpenFlowGraph(FileDialog): type = OPEN_FLOW_GRAPH -class SaveFlowGraphFileDialog(FileDialog): +class SaveFlowGraph(FileDialog): type = SAVE_FLOW_GRAPH -class OpenQSSFileDialog(FileDialog): +class OpenQSS(FileDialog): type = OPEN_QSS_THEME -class SaveConsoleFileDialog(FileDialog): +class SaveConsole(FileDialog): type = SAVE_CONSOLE @@ -223,11 +232,11 @@ class SaveImageFileDialog(FileDialog): type = SAVE_IMAGE -class SaveScreenShotDialog(SaveImageFileDialog): +class SaveScreenShot(SaveImageFileDialog): - def __init__(self, current_file_path=''): - SaveImageFileDialog.__init__(self, current_file_path) - self._button = button = Gtk.CheckButton('_Background transparent') + def __init__(self, parent, current_file_path=''): + SaveImageFileDialog.__init__(self, parent, current_file_path) + self._button = button = Gtk.CheckButton(label='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 83796f35fd..7333519a4f 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -28,7 +28,7 @@ from itertools import count import six from six.moves import filter -from gi.repository import GObject +from gi.repository import GLib from . import Actions, Colors, Utils, Bars, Dialogs from .Element import Element @@ -95,7 +95,7 @@ class FlowGraph(CoreFlowgraph, Element): else: config = self.parent_platform.config editor = (find_executable(config.editor) or - Dialogs.ChooseEditorDialog(config)) + Dialogs.choose_editor(None, config)) # todo: pass in parent if not editor: return updater = functools.partial( @@ -103,7 +103,7 @@ class FlowGraph(CoreFlowgraph, Element): 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(GLib.idle_add, updater) ) editor.start() try: diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py index 97f9033974..efa8573c3b 100644 --- a/grc/gui/MainWindow.py +++ b/grc/gui/MainWindow.py @@ -33,7 +33,7 @@ from .BlockTreeWindow import BlockTreeWindow from .VariableEditor import VariableEditor from .Constants import \ NEW_FLOGRAPH_TITLE, DEFAULT_CONSOLE_WINDOW_WIDTH -from .Dialogs import TextDisplay, MessageDialogHelper +from .Dialogs import TextDisplay, MessageDialogWrapper from .NotebookPage import NotebookPage from ..core import Messages @@ -398,10 +398,10 @@ class MainWindow(Gtk.Window): Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK ) - return MessageDialogHelper( - Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, 'Unsaved Changes!', + return MessageDialogWrapper( + self, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, 'Unsaved Changes!', 'Would you like to save changes before closing?', Gtk.ResponseType.OK, buttons - ) + ).run_and_destroy() def _get_files(self): """ diff --git a/grc/gui/ParamWidgets.py b/grc/gui/ParamWidgets.py index 4ab7da6d9a..e5657c288e 100644 --- a/grc/gui/ParamWidgets.py +++ b/grc/gui/ParamWidgets.py @@ -168,7 +168,7 @@ class PythonEditorParam(InputParam): def __init__(self, *args, **kwargs): InputParam.__init__(self, *args, **kwargs) - button = self._button = Gtk.Button('Open in Editor') + button = self._button = Gtk.Button(label='Open in Editor') button.connect('clicked', self.open_editor) self.pack_start(button, True, True, True) diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index 9543a62094..3a0f6ae6de 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -43,6 +43,7 @@ class PropsDialog(Gtk.Dialog): title='Properties: ' + block.name, transient_for=parent, modal=True, + destroy_with_parent=True, ) self.add_buttons( Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT, |