diff options
author | Sebastian Koslowski <koslowski@kit.edu> | 2015-07-23 16:36:22 +0200 |
---|---|---|
committer | Sebastian Koslowski <koslowski@kit.edu> | 2015-11-11 21:39:35 +0100 |
commit | 2fee267dfee11bd687b4d780cb6b82924d59af3c (patch) | |
tree | 3768cd0fde949c1b99e919d0cdff958764064e79 /grc | |
parent | c9a5fabe17efe8af1c9435f746ed55c5f5790917 (diff) |
grc: open and update params from external editor
Diffstat (limited to 'grc')
-rw-r--r-- | grc/gui/ActionHandler.py | 4 | ||||
-rw-r--r-- | grc/gui/Actions.py | 1 | ||||
-rw-r--r-- | grc/gui/CMakeLists.txt | 1 | ||||
-rw-r--r-- | grc/gui/Constants.py | 14 | ||||
-rw-r--r-- | grc/gui/Dialogs.py | 23 | ||||
-rw-r--r-- | grc/gui/FlowGraph.py | 37 | ||||
-rw-r--r-- | grc/gui/Param.py | 67 | ||||
-rw-r--r-- | grc/gui/external_editor.py | 93 |
8 files changed, 228 insertions, 12 deletions
diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index ee01595a33..6273178955 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -441,6 +441,10 @@ class ActionHandler: # null action, that updates the main window Actions.ELEMENT_SELECT() dialog.destroy() + elif action == Actions.EXTERNAL_UPDATE: + self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + self.get_flow_graph().update() + self.get_page().set_saved(False) ################################################## # View Parser Errors ################################################## diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index 20929344c0..c3ae6c971f 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -170,6 +170,7 @@ class ToggleAction(gtk.ToggleAction, _ActionBase): # Actions ######################################################################## PAGE_CHANGE = Action() +EXTERNAL_UPDATE = Action() FLOW_GRAPH_NEW = Action( label='_New', tooltip='Create a new flow graph', diff --git a/grc/gui/CMakeLists.txt b/grc/gui/CMakeLists.txt index 08aaf3e4df..99140df7c4 100644 --- a/grc/gui/CMakeLists.txt +++ b/grc/gui/CMakeLists.txt @@ -19,6 +19,7 @@ ######################################################################## GR_PYTHON_INSTALL(FILES + external_editor.py Block.py Colors.py Constants.py diff --git a/grc/gui/Constants.py b/grc/gui/Constants.py index 980396f85d..741c6fda95 100644 --- a/grc/gui/Constants.py +++ b/grc/gui/Constants.py @@ -17,16 +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 +import os +import sys +import pygtk pygtk.require('2.0') import gtk -import os -import sys + from gnuradio import gr -_gr_prefs = gr.prefs() +prefs = gr.prefs() GR_PREFIX = gr.prefix() +EDITOR = prefs.get_string('grc', 'editor', '') # default path for the open/save dialogs DEFAULT_FILE_PATH = os.getcwd() @@ -49,7 +51,7 @@ DEFAULT_BLOCKS_WINDOW_WIDTH = 100 DEFAULT_REPORTS_WINDOW_WIDTH = 100 try: # ugly, but matches current code style - raw = _gr_prefs.get_string('grc', 'canvas_default_size', '1280, 1024') + raw = prefs.get_string('grc', 'canvas_default_size', '1280, 1024') DEFAULT_CANVAS_SIZE = tuple(int(x.strip('() ')) for x in raw.split(',')) if len(DEFAULT_CANVAS_SIZE) != 2 or not all(300 < x < 4096 for x in DEFAULT_CANVAS_SIZE): raise Exception() @@ -59,7 +61,7 @@ except: # flow-graph canvas fonts try: # ugly, but matches current code style - FONT_SIZE = _gr_prefs.get_long('grc', 'canvas_font_size', 8) + FONT_SIZE = prefs.get_long('grc', 'canvas_font_size', 8) if FONT_SIZE <= 0: raise Exception() except: diff --git a/grc/gui/Dialogs.py b/grc/gui/Dialogs.py index 6c01219dee..631dc0fd98 100644 --- a/grc/gui/Dialogs.py +++ b/grc/gui/Dialogs.py @@ -20,8 +20,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA import pygtk pygtk.require('2.0') import gtk -import Utils -import Actions + +from . import Utils, Actions, Constants class SimpleTextDisplay(gtk.TextView): @@ -234,3 +234,22 @@ def MissingXTermDialog(xterm): "\n" "(This message is shown only once)").format(xterm) ) + + +def ChooseEditorDialog(): + file_dialog = gtk.FileChooserDialog( + 'Open a Data File...', None, + gtk.FILE_CHOOSER_ACTION_OPEN, + ('gtk-cancel', gtk.RESPONSE_CANCEL, 'gtk-open', gtk.RESPONSE_OK) + ) + file_dialog.set_select_multiple(False) + file_dialog.set_local_only(True) + file_dialog.set_current_folder('/usr/bin') + response = file_dialog.run() + + if response == gtk.RESPONSE_OK: + file_path = file_dialog.get_filename() + Constants.prefs.set_string('grc', 'editor', file_path) + Constants.prefs.save() + Constants.EDITOR = file_path + file_dialog.destroy() diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index fc6a711572..b27f0153db 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -18,12 +18,16 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ import random +import functools from itertools import chain from operator import methodcaller +import gobject + from . import Actions, Colors, Utils, Messages, Bars -from .Constants import SCROLL_PROXIMITY_SENSITIVITY, SCROLL_DISTANCE -from .Element import Element +from . Element import Element +from . Constants import SCROLL_PROXIMITY_SENSITIVITY, SCROLL_DISTANCE +from . external_editor import ExternalEditor class FlowGraph(Element): @@ -55,6 +59,35 @@ class FlowGraph(Element): self._context_menu = Bars.ContextMenu() self.get_context_menu = lambda: self._context_menu + self._external_updaters = {} + + def install_external_editor(self, param): + target = (param.get_parent().get_id(), param.get_key()) + + if target in self._external_updaters: + editor = self._external_updaters[target] + else: + updater = functools.partial( + self.handle_external_editor_change, target=target) + editor = self._external_updaters[target] = ExternalEditor( + name=target[0], value=param.get_value(), + callback=functools.partial(gobject.idle_add, updater) + ) + editor.start() + editor.open_editor() + + def handle_external_editor_change(self, new_value, target): + try: + block_id, param_key = target + self.get_block(block_id).get_param(param_key).set_value(new_value) + + except (IndexError, ValueError): # block no longer exists + self._external_updaters[target].stop() + del self._external_updaters[target] + return + Actions.EXTERNAL_UPDATE() + + ########################################################################### # Access Drawing Area ########################################################################### diff --git a/grc/gui/Param.py b/grc/gui/Param.py index 6bd45fae86..3588b81d8c 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -23,7 +23,7 @@ import pygtk pygtk.require('2.0') import gtk -from . import Colors, Utils, Constants +from . import Colors, Utils, Constants, Dialogs from . Element import Element @@ -173,6 +173,66 @@ class MultiLineEntryParam(InputParam): 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') +# +# class PythonEditorParam(InputParam): +# expand = True +# +# def __init__(self, *args, **kwargs): +# InputParam.__init__(self, *args, **kwargs) +# +# 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) + input = gtk.Button('Open in Editor') + input.connect('clicked', self.open_editor) + self.pack_start(input, True) + + def open_editor(self, widget=None): + if not os.path.exists(Constants.EDITOR): + Dialogs.ChooseEditorDialog() + 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 _apply_change(self, *args): + pass + + class EnumParam(InputParam): """Provide an entry box for Enum types with a drop down menu.""" @@ -341,9 +401,12 @@ class Param(Element): elif self.get_options(): input_widget = EnumEntryParam(self, *args, **kwargs) - elif self.get_type() in ('_multiline', '_multiline_python_external'): + elif self.get_type() == '_multiline': input_widget = MultiLineEntryParam(self, *args, **kwargs) + elif self.get_type() == '_multiline_python_external': + input_widget = PythonEditorParam(self, *args, **kwargs) + else: input_widget = EntryParam(self, *args, **kwargs) diff --git a/grc/gui/external_editor.py b/grc/gui/external_editor.py new file mode 100644 index 0000000000..3322556ce7 --- /dev/null +++ b/grc/gui/external_editor.py @@ -0,0 +1,93 @@ +""" +Copyright 2015 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 sys +import time +import threading +import tempfile +import subprocess + +import Constants + + +class ExternalEditor(threading.Thread): + + def __init__(self, name, value, callback): + threading.Thread.__init__(self) + self.daemon = True + self._stop_event = threading.Event() + + self.callback = callback + self.tempfile = self._create_tempfile(name, value) + + def _create_tempfile(self, name, value): + fp = tempfile.NamedTemporaryFile(mode='w', suffix='.py', + prefix=name + '_') + fp.write(value) + fp.flush() + return fp + + @property + def filename(self): + return self.tempfile.name + + def open_editor(self): + proc = subprocess.Popen( + args=(Constants.EDITOR, self.filename) + ) + proc.poll() + return proc + + def stop(self): + self._stop_event.set() + + def run(self): + filename = self.filename + # print "file monitor: started for", filename + last_change = os.path.getmtime(filename) + try: + while not self._stop_event.is_set(): + mtime = os.path.getmtime(filename) + if mtime > last_change: + # print "file monitor: reload trigger for", filename + last_change = mtime + with open(filename) as fp: + data = fp.read() + self.callback(data) + time.sleep(1) + + except Exception as e: + print >> sys.stderr, "file monitor crashed:", str(e) + else: + # print "file monitor: done with", filename + pass + + +if __name__ == '__main__': + def p(data): + print data + + Constants.EDITOR = '/usr/bin/gedit' + editor = ExternalEditor("test", "content", p) + editor.open_editor() + editor.start() + time.sleep(15) + editor.stop() + editor.join() |