diff options
-rw-r--r-- | plugins/python/scripting/Makefile.am | 6 | ||||
-rw-r--r-- | plugins/python/scripting/core.py | 43 | ||||
-rw-r--r-- | plugins/python/scripting/manager.py | 53 | ||||
-rw-r--r-- | plugins/python/scripting/panel.py | 140 | ||||
-rw-r--r-- | plugins/python/scripting/panel.ui | 70 | ||||
-rw-r--r-- | plugins/python/scripting/python-script-icon-16x16.png | bin | 0 -> 601 bytes |
6 files changed, 307 insertions, 5 deletions
diff --git a/plugins/python/scripting/Makefile.am b/plugins/python/scripting/Makefile.am index 5d38d6e..3c44f1f 100644 --- a/plugins/python/scripting/Makefile.am +++ b/plugins/python/scripting/Makefile.am @@ -3,6 +3,10 @@ scriptingdir = $(pluginsdatadir)/python/scripting scripting_DATA = \ __init__.py \ - core.py + core.py \ + manager.py \ + panel.py \ + panel.ui \ + python-script-icon-16x16.png EXTRA_DIST = $(scripting_DATA) diff --git a/plugins/python/scripting/core.py b/plugins/python/scripting/core.py index 7aff551..71afaf2 100644 --- a/plugins/python/scripting/core.py +++ b/plugins/python/scripting/core.py @@ -4,8 +4,12 @@ from gi.repository import Gtk from pychrysalide import PluginModule from pychrysalide import core from pychrysalide.gui import core as gcore +from pychrysalide.gui import MenuBar from pychrysalide.gtkext import EasyGtk +from .manager import get_recent_python_script_manager, remember_python_script +from .panel import ScriptPanel + class ScriptingEngine(PluginModule): """Extend the GUI to run external Python scripts.""" @@ -15,7 +19,7 @@ class ScriptingEngine(PluginModule): _version = '0.1' _url = 'https://www.chrysalide.re/' - _actions = ( ) + _actions = ( PluginModule.PluginAction.PLUGINS_LOADED, PluginModule.PluginAction.PANEL_CREATION ) def __init__(self): @@ -23,9 +27,13 @@ class ScriptingEngine(PluginModule): super(ScriptingEngine, self).__init__() + # Scripts panel + + gcore.register_panel(ScriptPanel) + # Insert the new menu item into 'File' submenu - bar = gcore.find_editor_item_by_key('menubar') + bar = gcore.find_editor_item_by_type(MenuBar) builder = gcore.get_editor_builder() @@ -54,6 +62,25 @@ class ScriptingEngine(PluginModule): file_menu.insert(item, index) + def _notify_plugins_loaded(self, action): + """Ack the full loading of all plugins.""" + + if action == PluginModule.PluginAction.PLUGINS_LOADED: + + filename = self.build_config_filename('recents.xbel', True) + + get_recent_python_script_manager(filename) + + + def _on_panel_creation(self, action, item): + """Get notified of a new panel creation.""" + + if type(item) == ScriptPanel: + + item.connect('run-requested', self._on_run_requested) + item.connect('ask-for-new-script', lambda x: self._on_file_run_script_activate(None)) + + def _on_file_run_script_activate(self, widget): """Look for a new script to run.""" @@ -87,7 +114,9 @@ class ScriptingEngine(PluginModule): def _run_script_file(self, filename): """Run a given script file.""" - core.log_message(core.LogMessageType.INFO, 'Execute the script file \'%s\'' % filename) + self.log_message(core.LogMessageType.INFO, 'Execute the script file \'%s\'' % filename) + + remember_python_script(filename) try: with open(filename, 'r') as fd: @@ -98,4 +127,10 @@ class ScriptingEngine(PluginModule): eval(code) except Exception as e: - core.log_message(core.LogMessageType.EXT_ERROR, 'Error while running the script: %s' % str(e)) + self.log_message(core.LogMessageType.EXT_ERROR, 'Error while running the script: %s' % str(e)) + + + def _on_run_requested(self, panel, filename): + """Run a script file from the recents panel.""" + + self._run_script_file(filename) diff --git a/plugins/python/scripting/manager.py b/plugins/python/scripting/manager.py new file mode 100644 index 0000000..6b27b48 --- /dev/null +++ b/plugins/python/scripting/manager.py @@ -0,0 +1,53 @@ + +import os + +from gi.repository import GLib, Gtk + + +_manager = None + + +def get_recent_python_script_manager(xbel = None): + """Provide the manager for the recently run Python scripts.""" + + global _manager + + # As a first panel creation is forced by the Chrysalide core to register + # its final GType, xbel is not defined at the first call of this function. + # Thus relying on the definition of xbel is a better filter than relying + # on the existence of _manager. + # + # In that special initial case, result is None + + if not(xbel is None): + + assert(_manager is None) + + _manager = Gtk.RecentManager(filename=xbel) + + return _manager + + +def remember_python_script(filename): + """Register a Python script into the recents list.""" + + uri = GLib.filename_to_uri(filename) + + recent_data = Gtk.RecentData() + recent_data.app_name = 'Chrysalide Python plugin' + recent_data.app_exec = 'chrysalide' + recent_data.display_name = os.path.basename(filename) + recent_data.description = 'Python script run inside Chrysalide' + recent_data.mime_type = 'text/x-python' + + manager = get_recent_python_script_manager() + manager.add_full(uri, recent_data) + + +def forget_python_script(filename): + """Unregister a Python script from the recents list.""" + + uri = GLib.filename_to_uri(filename) + + manager = get_recent_python_script_manager() + manager.remove_item(uri) diff --git a/plugins/python/scripting/panel.py b/plugins/python/scripting/panel.py new file mode 100644 index 0000000..75b50e3 --- /dev/null +++ b/plugins/python/scripting/panel.py @@ -0,0 +1,140 @@ + +import os +from gi.repository import Gdk, GdkPixbuf, GLib, GObject +from pychrysalide.gtkext import BuiltNamedWidget +from pychrysalide.gui import core +from pychrysalide.gui import PanelItem + +from .manager import get_recent_python_script_manager, forget_python_script + + +class ScriptPanel(PanelItem): + + _key = 'pyscripting' + + _path = 'MEN' + _key_bindings = '<Shift>F5' + + + def __init__(self): + """Initialize the GUI panel.""" + + directory = os.path.dirname(os.path.realpath(__file__)) + filename = os.path.join(directory, 'panel.ui') + + widget = BuiltNamedWidget('Python scripts', 'Recently run Python scripts', filename) + + super(ScriptPanel, self).__init__(widget) + + if not('run-requested' in GObject.signal_list_names(ScriptPanel)): + + GObject.signal_new('run-requested', ScriptPanel, GObject.SignalFlags.RUN_FIRST, + GObject.TYPE_NONE, (GObject.TYPE_STRING, )) + + GObject.signal_new('ask-for-new-script', ScriptPanel, GObject.SignalFlags.RUN_FIRST, + GObject.TYPE_NONE, ()) + + self._last_selected = None + + builder = self.named_widget.builder + + icon_renderer = builder.get_object('icon_renderer') + icon_renderer.props.xpad = 8 + + builder.connect_signals(self) + + manager = get_recent_python_script_manager() + + if manager: + + manager.connect("changed", self._on_recent_list_changed) + + self._on_recent_list_changed(manager) + + + def _on_row_activated(self, treeview, path, column): + """React on a row activation.""" + + store = self.named_widget.builder.get_object('store') + + siter = store.get_iter(path) + + self.emit('run-requested', store[siter][3]) + + + def _on_selection_changed(self, selection): + """React on tree selection change.""" + + model, treeiter = selection.get_selected() + + if treeiter: + self._last_selected = model[treeiter][3] + else: + self._last_selected = None + + + def on_key_press_event(self, widget, event): + """React on a key press inside the tree view.""" + + if event.keyval == Gdk.KEY_Delete: + + selection = self.named_widget.builder.get_object('selection') + + model, treeiter = selection.get_selected() + + if treeiter: + + forget_python_script(model[treeiter][3]) + + elif event.keyval == Gdk.KEY_Insert: + + self.emit('ask-for-new-script') + + + def _add_entry(self, filename): + """Add an entry for a new recent Python script.""" + + directory = os.path.dirname(os.path.realpath(__file__)) + icon_filename = os.path.join(directory, 'python-script-icon-16x16.png') + + icon = GdkPixbuf.Pixbuf.new_from_file(icon_filename) + name = os.path.basename(filename) + path = os.path.dirname(filename) + + store = self.named_widget.builder.get_object('store') + + store.append([ icon, name, path, filename ]) + + + def _on_recent_list_changed(self, manager): + """React on resources manager content change.""" + + saved = self._last_selected + + # Register the new item + + builder = self.named_widget.builder + + store = builder.get_object('store') + store.clear() + + for item in manager.get_items(): + + filename = GLib.filename_from_uri(item.get_uri())[0] + + self._add_entry(filename) + + # Restore previous selection + + selection = builder.get_object('selection') + + siter = store.iter_children() + + while siter: + + if store[siter][3] == saved: + + selection.select_iter(siter) + break + + siter = store.iter_next(siter) diff --git a/plugins/python/scripting/panel.ui b/plugins/python/scripting/panel.ui new file mode 100644 index 0000000..f87088a --- /dev/null +++ b/plugins/python/scripting/panel.ui @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.37.0 --> +<interface> + <requires lib="gtk+" version="3.20"/> + <object class="GtkListStore" id="store"> + <columns> + <!-- column-name icon --> + <column type="GdkPixbuf"/> + <!-- column-name name --> + <column type="gchararray"/> + <!-- column-name path --> + <column type="gchararray"/> + <!-- column-name filename --> + <column type="gchararray"/> + </columns> + </object> + <object class="GtkScrolledWindow" id="box"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="shadow-type">in</property> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkTreeView" id="treeview"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="model">store</property> + <signal name="key-press-event" handler="on_key_press_event" swapped="no"/> + <signal name="row-activated" handler="_on_row_activated" swapped="no"/> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="selection"> + <signal name="changed" handler="_on_selection_changed" swapped="no"/> + </object> + </child> + <child> + <object class="GtkTreeViewColumn"> + <property name="title" translatable="yes">Filename</property> + <child> + <object class="GtkCellRendererPixbuf" id="icon_renderer"/> + <attributes> + <attribute name="pixbuf">0</attribute> + </attributes> + </child> + <child> + <object class="GtkCellRendererText" id="name_renderer"/> + <attributes> + <attribute name="text">1</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn"> + <property name="title" translatable="yes">Path</property> + <child> + <object class="GtkCellRendererText" id="path_renderer"/> + <attributes> + <attribute name="text">2</attribute> + </attributes> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </object> +</interface> diff --git a/plugins/python/scripting/python-script-icon-16x16.png b/plugins/python/scripting/python-script-icon-16x16.png Binary files differnew file mode 100644 index 0000000..c96599e --- /dev/null +++ b/plugins/python/scripting/python-script-icon-16x16.png |