[pitivi] plugins: Add Pitivi Developer Console plugin



commit f6cdd1ad3228f26f116782d287c920eb1e245dd7
Author: Fabian Orccon <cfoch fabian gmail com>
Date:   Fri Jul 13 11:13:32 2018 -0500

    plugins: Add Pitivi Developer Console plugin
    
    Closes #2055

 plugins/console/console.plugin   |  9 ++++
 plugins/console/console.py       | 89 ++++++++++++++++++++++++++++++++++++++++
 plugins/console/consolebuffer.py | 83 +++++++++++++++++++++++++++++++++++++
 plugins/console/utils.py         | 52 +++++++++++++++++++++++
 plugins/console/widgets.py       | 79 +++++++++++++++++++++++++++++++++++
 5 files changed, 312 insertions(+)
---
diff --git a/plugins/console/console.plugin b/plugins/console/console.plugin
new file mode 100644
index 00000000..317b7a12
--- /dev/null
+++ b/plugins/console/console.plugin
@@ -0,0 +1,9 @@
+[Plugin]
+Module=console
+Name=Pitivi Developer Console
+Loader=Python3
+Description=Add a developer console to Pitivi.
+Authors=Fabian Orccon <cfoch fabian gmail com>
+Copyright=Copyright © 2017 Fabian Orccon
+Website=http://pitivi.org
+Help=http://developer.pitivi.org
diff --git a/plugins/console/console.py b/plugins/console/console.py
new file mode 100644
index 00000000..cceb028a
--- /dev/null
+++ b/plugins/console/console.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+# Pitivi Developer Console
+# Copyright (c) 2017-2018, Fabian Orccon <cfoch fabian gmail com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This program 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+"""Python console for inspecting and interacting with Pitivi and the project."""
+import sys
+from gettext import gettext as _
+
+from gi.repository import GObject
+from gi.repository import Gtk
+from gi.repository import Peas
+from widgets import ConsoleWidget
+
+
+class Console(GObject.GObject, Peas.Activatable):
+    """Plugin which adds a Python console for development purposes."""
+
+    __gtype_name__ = "ConsolePlugin"
+    object = GObject.Property(type=GObject.Object)
+
+    def __init__(self):
+        GObject.GObject.__init__(self)
+        self.window = None
+        self.terminal = None
+        self.menu_item = None
+        self.app = None
+
+        # Set prompt.
+        sys.ps1 = ">>> "
+        sys.ps2 = "... "
+
+    def do_activate(self):
+        api = self.object
+        self.app = api.app
+        self._setup_dialog()
+        self.add_menu_item()
+        self.menu_item.show()
+
+    def do_deactivate(self):
+        self.window.destroy()
+        self.remove_menu_item()
+        self.window = None
+        self.terminal = None
+        self.menu_item = None
+        self.app = None
+
+    def add_menu_item(self):
+        """Inserts a menu item into the Pitivi menu."""
+        menu = self.app.gui.editor.builder.get_object("menu")
+        self.menu_item = Gtk.MenuItem.new_with_label(_("Developer Console"))
+        self.menu_item.connect("activate", self.__menu_item_activate_cb)
+        menu.add(self.menu_item)
+
+    def remove_menu_item(self):
+        """Removes a menu item from the Pitivi menu."""
+        menu = self.app.gui.editor.builder.get_object("menu")
+        menu.remove(self.menu_item)
+        self.menu_item = None
+
+    def _setup_dialog(self):
+        namespace = {"app": self.app}
+        self.window = Gtk.Window()
+        self.terminal = ConsoleWidget(namespace)
+
+        self.window.set_default_size(600, 400)
+        self.window.set_title(_("Pitivi Console"))
+        self.window.connect("delete-event", self.__delete_event_cb)
+        self.window.add(self.terminal)
+
+    def __menu_item_activate_cb(self, unused_data):
+        self.window.show_all()
+        self.window.set_keep_above(True)
+
+    def __delete_event_cb(self, unused_widget, unused_data):
+        return self.window.hide_on_delete()
diff --git a/plugins/console/consolebuffer.py b/plugins/console/consolebuffer.py
new file mode 100644
index 00000000..d65f9c36
--- /dev/null
+++ b/plugins/console/consolebuffer.py
@@ -0,0 +1,83 @@
+# pylint: disable=missing-docstring
+# -*- coding: utf-8 -*-
+# Pitivi Developer Console
+# Copyright (c) 2017-2018, Fabian Orccon <cfoch fabian gmail com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This program 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+import code
+import sys
+
+from gi.repository import Gtk
+from utils import FakeOut
+from utils import swap_std
+
+
+class ConsoleBuffer(Gtk.TextBuffer):
+
+    def __init__(self, namespace):
+        Gtk.TextBuffer.__init__(self)
+
+        self.insert_at_cursor(sys.ps1)
+        self.prompt_mark = self.create_mark("after-prompt", self.get_end_iter(), left_gravity=True)
+
+        self._stdout = FakeOut(self)
+        self._stderr = FakeOut(self)
+        self._console = code.InteractiveConsole(namespace)
+
+    def process_command_line(self):
+        """Process the current input command line executing it if complete."""
+        cmd = self.get_command_line()
+
+        with swap_std(self._stdout, self._stderr):
+            self.write("\n")
+            is_command_incomplete = self._console.push(cmd)
+
+        if is_command_incomplete:
+            prompt = sys.ps2
+        else:
+            prompt = sys.ps1
+        self.write(prompt)
+
+        self.move_mark(self.prompt_mark, self.get_end_iter())
+        self.place_cursor(self.get_end_iter())
+
+    def is_cursor(self, before=False, at=False, after=False):
+        """Compares the position of the cursor compared to the prompt."""
+        prompt_iter = self.get_iter_at_mark(self.prompt_mark)
+        cursor_iter = self.get_iter_at_mark(self.get_insert())
+        res = cursor_iter.compare(prompt_iter)
+        return (before and res == -1) or (at and res == 0) or (after and res == 1)
+
+    def write(self, text):
+        """Writes a text to the buffer."""
+        self.insert(self.get_end_iter(), text)
+
+    def get_command_line(self):
+        """Gets the last command line after the prompt.
+
+        A command line can be a single line or multiple lines for example when
+        a function or a class is defined.
+        """
+        after_prompt_iter = self.get_iter_at_mark(self.prompt_mark)
+        end_iter = self.get_end_iter()
+        return self.get_text(after_prompt_iter, end_iter, include_hidden_chars=False)
+
+    def set_command_line(self, cmd):
+        """Inserts a command line after the prompt."""
+        after_prompt_iter = self.get_iter_at_mark(self.prompt_mark)
+        end_iter = self.get_end_iter()
+        self.delete(after_prompt_iter, end_iter)
+        self.write(cmd)
diff --git a/plugins/console/utils.py b/plugins/console/utils.py
new file mode 100644
index 00000000..33f2850c
--- /dev/null
+++ b/plugins/console/utils.py
@@ -0,0 +1,52 @@
+# pylint: disable=missing-docstring
+# -*- coding: utf-8 -*-
+# Pitivi Developer Console
+# Copyright (c) 2017-2018, Fabian Orccon <cfoch fabian gmail com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This program 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+import sys
+from contextlib import contextmanager
+from io import TextIOBase
+
+
+@contextmanager
+def swap_std(stdout=None, stderr=None):
+    """Swaps temporarily stdout and stderr with the respective arguments."""
+    try:
+        if stdout:
+            sys.stdout, stdout = stdout, sys.stdout
+        if stderr:
+            sys.stderr, stderr = stderr, sys.stderr
+        yield
+    finally:
+        if stdout:
+            sys.stdout = stdout
+        if stderr:
+            sys.stderr = stderr
+
+
+class FakeOut(TextIOBase):
+    """Replacement for sys.stdout/err which redirects writes."""
+
+    def __init__(self, buf):
+        TextIOBase.__init__(self)
+        self.buf = buf
+
+    def write(self, string):
+        self.buf.write(string)
+
+    def writelines(self, lines):
+        self.buf.write(lines)
diff --git a/plugins/console/widgets.py b/plugins/console/widgets.py
new file mode 100644
index 00000000..c35c4976
--- /dev/null
+++ b/plugins/console/widgets.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+# Pitivi Developer Console
+# Copyright (c) 2017-2018, Fabian Orccon <cfoch fabian gmail com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This program 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+"""The developer console widget:"""
+from gi.repository import Gdk
+from gi.repository import GLib
+from gi.repository import Gtk
+
+from consolebuffer import ConsoleBuffer
+
+
+class ConsoleWidget(Gtk.ScrolledWindow):
+    """An emulated Python console.
+
+    The console can be used to access an app, window, or anything through the
+    provided namespace. It works redirecting stdout and stderr to a
+    GtkTextBuffer. This class is (and should be) independent of the application
+    it is integrated with.
+    """
+
+    def __init__(self, namespace):
+        Gtk.ScrolledWindow.__init__(self)
+        self._view = Gtk.TextView()
+        buf = ConsoleBuffer(namespace)
+        self._view.set_buffer(buf)
+        self._view.set_editable(True)
+        self.add(self._view)
+
+        self._view.connect("key-press-event", self.__key_press_event_cb)
+        buf.connect("mark-set", self.__mark_set_cb)
+        buf.connect("insert-text", self.__insert_text_cb)
+
+    def scroll_to_end(self):
+        """Scrolls the view to the end."""
+        end_iter = self._view.get_buffer().get_end_iter()
+        self._view.scroll_to_iter(end_iter, within_margin=0.0, use_align=False,
+                                  xalign=0, yalign=0)
+        return False
+
+    @classmethod
+    def __key_press_event_cb(cls, view, event):
+        buf = view.get_buffer()
+        if event.keyval == Gdk.KEY_Return:
+            buf.process_command_line()
+            return True
+        if event.keyval in (Gdk.KEY_KP_Down, Gdk.KEY_Down):
+            return True
+        if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_Up):
+            return True
+        if event.keyval in (Gdk.KEY_KP_Left, Gdk.KEY_Left, Gdk.KEY_BackSpace):
+            return buf.is_cursor(at=True)
+        if event.keyval in (Gdk.KEY_KP_Home, Gdk.KEY_Home):
+            buf.place_cursor(buf.get_iter_at_mark(buf.prompt_mark))
+            return True
+        return False
+
+    def __mark_set_cb(self, buf, unused_iter, mark):
+        if not mark.props.name == "insert":
+            return
+
+        self._view.set_editable(buf.is_cursor(at=True, after=True))
+
+    def __insert_text_cb(self, buf, unused_iter, unused_text, unused_len):
+        GLib.idle_add(self.scroll_to_end)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]