From 1bc87f9f04a8c05f7c8c86c86f6c0bcf6328728e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= <manuel.por.aca@gmail.com>
Date: Thu, 18 May 2017 17:16:31 -0300
Subject: [PATCH] externalapp: remove OpenWithDialog and use GTK+

Important note: this bumps the GTK+ dependency from 3.12 to 3.22 .

Since GTK+ 3.22 there is a `Gtk.show_uri_on_window()` that does the job
implemented in OpenWithDialog.

This not only removes a lot of custom code.  Is also a must to run
MyPaint in sandboxed contexts,like Flatpak or Snappy.  Because under the
hood, `Gtk.show_uri_on_window()` will automatically use the `open_uri`
portal from xdg-desktop-portal in those situations.
---
 BUILDING.md        |   4 +-
 gui/externalapp.py | 210 +++--------------------------------------------------
 2 files changed, 12 insertions(+), 202 deletions(-)

diff --git a/BUILDING.md b/BUILDING.md
index 6bdef41..df1a9c0 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -47,11 +47,13 @@ installed before you can build it.
 
 - setuptools
 - pygobject
-- gtk3 (>= 3.12)
+- gtk3 (>= 3.22)
 - python (>= 2.7.4)
 - swig
 - numpy
 - pycairo (>= 1.4)
+- xdg-desktop-portal
+- xdg-desktop-portal-gtk
 
 ### Debian and derivatives
 
diff --git a/gui/externalapp.py b/gui/externalapp.py
index 9c0a15f..a245796 100644
--- a/gui/externalapp.py
+++ b/gui/externalapp.py
@@ -33,11 +33,9 @@ import lib.xml
 
 ## UI string consts
 
-_LAUNCH_SUCCESS_MSG = _(u"Launched {app_name} to edit layer “{layer_name}”")
-_LAUNCH_FAILED_MSG = _(u"Error: failed to launch {app_name} to edit "
+_LAUNCH_SUCCESS_MSG = _(u"Launched application to edit layer “{layer_name}”")
+_LAUNCH_FAILED_MSG = _(u"Error: failed to launch application to edit "
                        u"layer “{layer_name}”")
-_LAUNCH_CANCELLED_MSG = _(u"Editing cancelled. You can still edit "
-                          u"“{layer_name}” from the Layers menu.")
 _LAYER_UPDATED_MSG = _(u"Updated layer “{layer_name}” with external edits")
 
 # XXX: Stolen from gui.filehandling during string freeze for v1.2.0.
@@ -53,150 +51,6 @@ _LAYER_UPDATE_FAILED_MSG = C_(
 
 ## Class definitions
 
-class OpenWithDialog (Gtk.Dialog):
-    """Choose an app from those recommended for a type"""
-
-    ICON_SIZE = Gtk.IconSize.DIALOG
-    SPECIFIC_FILE_MSG = _(
-        u"MyPaint needs to edit a file of type \u201c{type_name}\u201d "
-        u"({content_type}). What application should it use?"
-        )
-    GENERIC_MSG = _(
-        u"What application should MyPaint use for editing files of "
-        u"type \u201c{type_name}\u201d ({content_type})?"
-        )
-
-    def __init__(self, content_type, specific_file=False):
-        Gtk.Dialog.__init__(self)
-        self.set_title(_("Open With..."))
-        self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
-        self.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
-        self.set_default_response(Gtk.ResponseType.CANCEL)
-        self.set_response_sensitive(Gtk.ResponseType.OK, False)
-        self.connect("show", self._show_cb)
-
-        content_box = self.get_content_area()
-        content_box.set_border_width(12)
-        content_box.set_spacing(12)
-
-        msg_template = self.GENERIC_MSG
-        if specific_file:
-            msg_template = self.SPECIFIC_FILE_MSG
-        msg_text = msg_template.format(
-            content_type=content_type,
-            type_name=Gio.content_type_get_description(content_type),
-            )
-        msg_label = Gtk.Label(label=msg_text)
-        msg_label.set_single_line_mode(False)
-        msg_label.set_line_wrap(True)
-        msg_label.set_alignment(0.0, 0.5)
-        content_box.pack_start(msg_label, False, False, 0)
-
-        default_app = Gio.AppInfo.get_default_for_type(content_type, False)
-        default_iter = None
-        app_list_store = Gtk.ListStore(object)
-        apps = Gio.AppInfo.get_all_for_type(content_type)
-        for app in apps:
-            if not app.should_show():
-                continue
-            row_iter = app_list_store.append([app])
-            if default_iter is not None:
-                continue
-            if default_app and Gio.AppInfo.equal(app, default_app):
-                default_iter = row_iter
-
-        # TreeView to show available apps for this content type
-        view = Gtk.TreeView()
-        view.set_model(app_list_store)
-        view.set_headers_clickable(False)
-        view.set_headers_visible(False)
-        view.connect("row-activated", self._row_activated_cb)
-
-        view_scrolls = Gtk.ScrolledWindow()
-        view_scrolls.set_shadow_type(Gtk.ShadowType.IN)
-        view_scrolls.add(view)
-        view_scrolls.set_size_request(375, 225)
-        content_box.pack_start(view_scrolls, True, True, 0)
-
-        # Column 0: application icon
-        cell = Gtk.CellRendererPixbuf()
-        col = Gtk.TreeViewColumn(_("Icon"), cell)
-        col.set_cell_data_func(cell, self._app_icon_datafunc)
-        icon_size_ok, icon_w, icon_h = Gtk.icon_size_lookup(self.ICON_SIZE)
-        if icon_size_ok:
-            col.set_min_width(icon_w)
-        col.set_expand(False)
-        col.set_resizable(False)
-        view.append_column(col)
-
-        # Column 1: application name
-        cell = Gtk.CellRendererText()
-        cell.set_property("ellipsize", Pango.EllipsizeMode.END)
-        cell.set_property("editable", False)
-        col = Gtk.TreeViewColumn(_("Name"), cell)
-        col.set_cell_data_func(cell, self._app_name_datafunc)
-        col.set_expand(True)
-        col.set_min_width(150)
-        view.append_column(col)
-
-        # Selection: mode and initial value
-        selection = view.get_selection()
-        selection.set_mode(Gtk.SelectionMode.SINGLE)
-        if default_iter:
-            selection.select_iter(default_iter)
-            self.set_default_response(Gtk.ResponseType.OK)
-            self.set_response_sensitive(Gtk.ResponseType.OK, True)
-        selection.connect("changed", self._selection_changed_cb)
-
-        # Results go here
-        self.selected_appinfo = default_app   #: The app the user chose
-
-    def _show_cb(self, dialog):
-        content_box = self.get_content_area()
-        content_box.show_all()
-
-    def _app_name_datafunc(self, col, cell, model, it, data):
-        app = model.get_value(it, 0)
-        name = app.get_display_name()
-        desc = app.get_description()
-        if desc is not None:
-            markup_template = "<b>{name}</b>\n{description}"
-        else:
-            markup_template = "<b>{name}</b>\n<i>({description})</i>"
-            desc = _("no description")
-        markup = markup_template.format(
-            name=lib.xml.escape(name),
-            description=lib.xml.escape(desc),
-            )
-        cell.set_property("markup", markup)
-
-    def _app_icon_datafunc(self, col, cell, model, it, data):
-        app = model.get_value(it, 0)
-        icon = app.get_icon()
-        cell.set_property("gicon", icon)
-        cell.set_property("stock-size", self.ICON_SIZE)
-
-    def _row_activated_cb(self, view, treepath, column):
-        model = view.get_model()
-        treeiter = model.get_iter(treepath)
-        if treeiter:
-            appinfo = model.get_value(treeiter, 0)
-            self.selected_appinfo = appinfo
-            self.response(Gtk.ResponseType.OK)
-
-    def _selection_changed_cb(self, selection):
-        model, selected_iter = selection.get_selected()
-        if selected_iter:
-            appinfo = model.get_value(selected_iter, 0)
-            self.selected_appinfo = appinfo
-            self.set_response_sensitive(Gtk.ResponseType.OK, True)
-            self.set_default_response(Gtk.ResponseType.OK)
-        else:
-            self.selected_appinfo = None
-            self.set_response_sensitive(Gtk.ResponseType.OK, False)
-            self.set_default_response(Gtk.ResponseType.CANCEL)
-
-
 class LayerEditManager (object):
     """Launch external apps to edit layers, monitoring file changes"""
 
@@ -245,60 +99,27 @@ class LayerEditManager (object):
         os.startfile(file_path, "edit")
         self._doc.app.show_transient_message(
             _LAUNCH_SUCCESS_MSG.format(
-                app_name = "(unknown Win32 app)",  # FIXME: needs i18n
                 layer_name = layer.name,
             ))
 
     def _begin_file_edit_using_gio(self, file_path, layer):
-        logger.info("Using OpenWithDialog and GIO to open %r", file_path)
-        logger.debug("Querying file path for info")
-        file = Gio.File.new_for_path(file_path)
-        flags = Gio.FileQueryInfoFlags.NONE
-        attr = Gio.FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE
-        file_info = file.query_info(attr, flags, None)
-        file_type = file_info.get_attribute_string(attr)
-
-        logger.debug("Creating and launching external layer edit dialog")
-        dialog = OpenWithDialog(file_type, specific_file=True)
-        dialog.set_modal(True)
-        dialog.set_transient_for(self._doc.app.drawWindow)
-        dialog.set_position(Gtk.WindowPosition.CENTER)
-        response = dialog.run()
-        dialog.destroy()
-        if response != Gtk.ResponseType.OK:
-            self._doc.app.show_transient_message(
-                _LAUNCH_CANCELLED_MSG.format(
-                    layer_name=layer.name,
-                ))
-            return
-        appinfo = dialog.selected_appinfo
-        assert appinfo is not None
-
-        disp = self._doc.tdw.get_display()
-        launch_ctx = disp.get_app_launch_context()
-
-        logger.debug(
-            "Launching %r with %r (user-chosen app for %r)",
-            appinfo.get_name(),
-            file_path,
-            file_type,
-        )
-        launched_app = appinfo.launch([file], launch_ctx)
-        if not launched_app:
+        import application
+        file_url = "file://" + file_path
+        app = application.get_app()
+        logger.debug("Using show_uri_on_window to open %r", file_url)
+        success = Gtk.show_uri_on_window(app.drawWindow, file_url, Gdk.CURRENT_TIME)
+        if not success:
             self._doc.app.show_transient_message(
                 _LAUNCH_FAILED_MSG.format(
-                    app_name=appinfo.get_name(),
                     layer_name=layer.name,
                 ))
             logger.error(
-                "Failed to launch %r with %r",
-                appinfo.get_name(),
+                "Failed to launch application to edit %r",
                 file_path,
                 )
             return
         self._doc.app.show_transient_message(
             _LAUNCH_SUCCESS_MSG.format(
-                app_name=appinfo.get_name(),
                 layer_name=layer.name,
             ))
 
@@ -378,16 +199,3 @@ class LayerEditManager (object):
                 logger.info("File %r is no longer monitored", file.get_path())
                 self._active_edits[i:i+1] = []
 
-
-if __name__ == '__main__':
-    logging.basicConfig(level=logging.DEBUG)
-    dialog = OpenWithDialog("image/svg+xml")
-    #dialog = OpenWithDialog("text/plain")
-    #dialog = OpenWithDialog("image/jpeg")
-    #dialog = OpenWithDialog("application/xml")
-    response = dialog.run()
-    if response == Gtk.ResponseType.OK:
-        app_name = dialog.selected_appinfo.get_name()
-        logger.debug("AppInfo chosen: %r", app_name)
-    else:
-        logger.debug("Dialog was cancelled")
-- 
2.9.3

