[Xfce4-commits] <midori:master> Supersede AddDesktopShortcut by Web App Manager

Christian Dywan noreply at xfce.org
Fri Apr 12 00:02:01 CEST 2013


Updating branch refs/heads/master
         to 67add99e7432e8199362056ecd065a3463be7c93 (commit)
       from a18d036cba8e2601c5d0e7bc1a3b88fb2939b030 (commit)

commit 67add99e7432e8199362056ecd065a3463be7c93
Author: Christian Dywan <christian at twotoasts.de>
Date:   Thu Apr 11 23:57:51 2013 +0200

    Supersede AddDesktopShortcut by Web App Manager
    
    Launchers can be created from websites, apps appear in a panel.

 extensions/apps.vala    |  279 +++++++++++++++++++++++++++++++++++++++++++++++
 katze/katze.vapi        |    3 +-
 midori/midori-app.c     |    2 +-
 midori/midori-browser.c |   60 +----------
 midori/midori-stock.h   |    2 +-
 midori/midori-view.c    |    3 -
 midori/midori.vapi      |   11 ++-
 po/POTFILES.in          |    1 +
 tests/app.vala          |    2 -
 9 files changed, 297 insertions(+), 66 deletions(-)

diff --git a/extensions/apps.vala b/extensions/apps.vala
new file mode 100644
index 0000000..6015d20
--- /dev/null
+++ b/extensions/apps.vala
@@ -0,0 +1,279 @@
+/*
+   Copyright (C) 2013 Christian Dywan <christian at twotoasts.de>
+
+   This library 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.
+
+   See the file COPYING for the full license text.
+*/
+
+namespace Apps {
+    const string EXEC_PREFIX = PACKAGE_NAME + " -a ";
+
+    private class Launcher : GLib.Object, GLib.Initable {
+        internal GLib.File file;
+        internal string name;
+        internal string icon_name;
+        internal string exec;
+        internal string uri;
+
+        internal static async void create (GLib.File folder, string uri, string title) {
+            /* Strip LRE leading character and / */
+            string filename = title.delimit ("‪/", ' ') + ".desktop";
+            string exec = EXEC_PREFIX + uri;
+            string name = title;
+            // TODO: Midori.Paths.get_icon save to png
+            string icon_name = Midori.Stock.WEB_BROWSER;
+            string contents = """
+                [Desktop Entry]
+                Version=1.0
+                Type=Application
+                Name=%s
+                Exec=%s
+                TryExec=%s
+                Icon=%s
+                Categories=Network;
+                """.printf (name, exec, PACKAGE_NAME, icon_name);
+            var file = folder.get_child (filename);
+            try {
+                yield file.replace_contents_async (contents.data, null, false, GLib.FileCreateFlags.NONE, null, null);
+            }
+            catch (Error error) {
+                // TODO GUI infobar
+                warning ("Failed to create new launcher: %s", error.message);
+            }
+        }
+
+        internal Launcher (GLib.File file) {
+            this.file = file;
+        }
+
+        bool init (GLib.Cancellable? cancellable) throws GLib.Error {
+            if (!file.get_basename ().has_suffix (".desktop"))
+                return false;
+
+            var keyfile = new GLib.KeyFile ();
+            keyfile.load_from_file (file.get_path (), GLib.KeyFileFlags.NONE);
+            exec = keyfile.get_string ("Desktop Entry", "Exec");
+            if (!exec.has_prefix (EXEC_PREFIX))
+                return false;
+
+            name = keyfile.get_string ("Desktop Entry", "Name");
+            icon_name = keyfile.get_string ("Desktop Entry", "Icon");
+            uri = exec.replace (EXEC_PREFIX, "");
+            return true;
+        }
+    }
+
+    private class Sidebar : Gtk.VBox, Midori.Viewable {
+        Gtk.Toolbar? toolbar = null;
+        Gtk.ListStore store = new Gtk.ListStore (1, typeof (Launcher));
+        Gtk.TreeView treeview;
+        Katze.Array array;
+
+        public unowned string get_stock_id () {
+            return Midori.Stock.WEB_BROWSER;
+        }
+
+        public unowned string get_label () {
+            return _("Applications");
+        }
+
+        public Gtk.Widget get_toolbar () {
+            if (toolbar == null) {
+                toolbar = new Gtk.Toolbar ();
+                toolbar.set_icon_size (Gtk.IconSize.BUTTON);
+            }
+            return toolbar;
+        }
+
+        public Sidebar (Katze.Array array) {
+            Gtk.TreeViewColumn column;
+
+            treeview = new Gtk.TreeView.with_model (store);
+            treeview.headers_visible = false;
+
+            store.set_sort_column_id (0, Gtk.SortType.ASCENDING);
+            store.set_sort_func (0, tree_sort_func);
+
+            column = new Gtk.TreeViewColumn ();
+            Gtk.CellRendererPixbuf renderer_icon = new Gtk.CellRendererPixbuf ();
+            column.pack_start (renderer_icon, false);
+            column.set_cell_data_func (renderer_icon, on_render_icon);
+            treeview.append_column (column);
+
+            column = new Gtk.TreeViewColumn ();
+            column.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
+            Gtk.CellRendererText renderer_text = new Gtk.CellRendererText ();
+            column.pack_start (renderer_text, true);
+            column.set_expand (true);
+            column.set_cell_data_func (renderer_text, on_render_text);
+            treeview.append_column (column);
+
+            treeview.show ();
+            pack_start (treeview, true, true, 0);
+            assert (treeview.get_n_columns () > 0);;
+
+            this.array = array;
+            array.add_item.connect (launcher_added);
+            array.remove_item.connect (launcher_removed);
+            foreach (GLib.Object item in array.get_items ())
+                launcher_added (item);
+        }
+
+        private int tree_sort_func (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b) {
+            Launcher launcher1, launcher2;
+            model.get (a, 0, out launcher1);
+            model.get (b, 0, out launcher2);
+            return strcmp (launcher1.name, launcher2.name);
+        }
+        void launcher_added (GLib.Object item) {
+            var launcher = item as Launcher;
+            Gtk.TreeIter iter;
+            store.append (out iter);
+            store.set (iter, 0, launcher);
+        }
+
+        void launcher_removed (GLib.Object item) {
+            // TODO remove iter
+        }
+
+        private void on_render_icon (Gtk.CellLayout column, Gtk.CellRenderer renderer,
+            Gtk.TreeModel model, Gtk.TreeIter iter) {
+
+            Launcher launcher;
+            model.get (iter, 0, out launcher);
+            if (launcher.icon_name != null)
+                renderer.set ("icon-name", launcher.icon_name);
+            else
+                renderer.set ("stock-id", Gtk.STOCK_FILE);
+            renderer.set ("stock-size", Gtk.IconSize.BUTTON,
+                          "xpad", 4);
+        }
+
+        private void on_render_text (Gtk.CellLayout column, Gtk.CellRenderer renderer,
+            Gtk.TreeModel model, Gtk.TreeIter iter) {
+
+            Launcher launcher;
+            model.get (iter, 0, out launcher);
+            renderer.set ("markup",
+                Markup.printf_escaped ("<b>%s</b>\n%s",
+                    launcher.name, launcher.uri),
+                          "ellipsize", Pango.EllipsizeMode.END);
+        }
+    }
+
+    private class Manager : Midori.Extension {
+        internal Katze.Array array;
+        internal GLib.File app_folder;
+        internal GLib.FileMonitor? monitor;
+        internal GLib.List<Gtk.Widget> widgets;
+
+        void app_changed (GLib.File file, GLib.File? other, GLib.FileMonitorEvent event) {
+            try {
+                switch (event) {
+                case GLib.FileMonitorEvent.DELETED:
+                    // TODO array.remove_item ();
+                    break;
+                case GLib.FileMonitorEvent.CREATED:
+                    var launcher = new Launcher (file);
+                    if (launcher.init ())
+                        array.add_item (launcher);
+                    break;
+                case GLib.FileMonitorEvent.CHANGED:
+                    // TODO
+                    break;
+                }
+            }
+            catch (Error error) {
+                warning ("Application changed: %s", error.message);
+            }
+        }
+
+        async void populate_apps () {
+            var data_dir = File.new_for_path (Midori.Paths.get_user_data_dir ());
+            app_folder = data_dir.get_child ("applications");
+            try {
+                monitor = app_folder.monitor_directory (0, null);
+                monitor.changed.connect (app_changed);
+
+                var enumerator = yield app_folder.enumerate_children_async (FileAttribute.STANDARD_NAME, 0);
+                while (true) {
+                    var files = yield enumerator.next_files_async (10);
+                    if (files == null)
+                        break;
+                    foreach (var info in files) {
+                        var desktop_file = app_folder.get_child (info.get_name ());
+                        try {
+                            var launcher = new Launcher (desktop_file);
+                            if (launcher.init ())
+                                array.add_item (launcher);
+                        }
+                        catch (Error error) {
+                            warning ("Failed to parse launcher: %s", error.message);
+                        }
+                    }
+                }
+            }
+            catch (Error io_error) {
+                monitor = null;
+                warning ("Failed to list .desktop files: %s", io_error.message);
+            }
+        }
+
+        void tool_menu_populated (Midori.Browser browser, Gtk.Menu menu) {
+            var menuitem = new Gtk.MenuItem.with_mnemonic (_("Create _Launcher"));
+            menuitem.show ();
+            menu.append (menuitem);
+            menuitem.activate.connect (() => {
+                var view = browser.tab as Midori.View;
+                Launcher.create.begin (app_folder,
+                    view.get_display_uri (), view.get_display_title ());
+            });
+        }
+
+        void browser_added (Midori.Browser browser) {
+            var viewable = new Sidebar (array);
+            viewable.show ();
+            browser.panel.append_page (viewable);
+            browser.populate_tool_menu.connect (tool_menu_populated);
+            // TODO website context menu
+            widgets.append (viewable);
+        }
+
+        void activated (Midori.App app) {
+            array = new Katze.Array (typeof (Launcher));
+            populate_apps.begin ();
+            widgets = new GLib.List<Gtk.Widget> ();
+            foreach (var browser in app.get_browsers ())
+                browser_added (browser);
+            app.add_browser.connect (browser_added);
+        }
+
+        void deactivated () {
+            var app = get_app ();
+            if (monitor != null)
+                monitor.changed.disconnect (app_changed);
+            app.add_browser.disconnect (browser_added);
+            foreach (var widget in widgets)
+                widget.destroy ();
+        }
+
+        internal Manager () {
+            GLib.Object (name: _("Web App Manager"),
+                         description: _("Manage websites installed as applications"),
+                         version: "0.1" + Midori.VERSION_SUFFIX,
+                         authors: "Christian Dywan <christian at twotoasts.de>");
+
+            this.activate.connect (activated);
+            this.deactivate.connect (deactivated);
+        }
+    }
+}
+
+public Midori.Extension extension_init () {
+    return new Apps.Manager ();
+}
+
diff --git a/katze/katze.vapi b/katze/katze.vapi
index c5cf318..776b06f 100644
--- a/katze/katze.vapi
+++ b/katze/katze.vapi
@@ -9,7 +9,8 @@ namespace Katze {
     [CCode (cheader_filename = "katze/katze.h")]
     public class Array : Katze.Item {
         public Array (GLib.Type type);
-        public void add_item (GLib.Object item);
+        public signal void add_item (GLib.Object item);
+        public signal void remove_item (GLib.Object item);
         public uint get_length ();
         public GLib.List<unowned Item> get_items ();
     }
diff --git a/midori/midori-app.c b/midori/midori-app.c
index 435f11b..cf59605 100644
--- a/midori/midori-app.c
+++ b/midori/midori-app.c
@@ -1353,7 +1353,7 @@ midori_app_setup (gint               *argc,
     static GtkStockItem items[] =
     {
         { STOCK_IMAGE },
-        { STOCK_WEB_BROWSER },
+        { MIDORI_STOCK_WEB_BROWSER },
         { STOCK_NEWS_FEED },
         { STOCK_STYLE },
 
diff --git a/midori/midori-browser.c b/midori/midori-browser.c
index 2e9dbba..c0d5563 100644
--- a/midori/midori-browser.c
+++ b/midori/midori-browser.c
@@ -2546,48 +2546,6 @@ _action_add_speed_dial_activate (GtkAction*     action,
 }
 
 static void
-_action_add_desktop_shortcut_activate (GtkAction*     action,
-                                       MidoriBrowser* browser)
-{
-    #if defined (GDK_WINDOWING_X11)
-    GtkWidget* tab = midori_browser_get_current_tab (browser);
-    KatzeItem* item = midori_view_get_proxy_item (MIDORI_VIEW (tab));
-    const gchar* app_name = katze_item_get_name (item);
-    gchar* app_exec = g_strconcat ("midori -a ", katze_item_get_uri (item), NULL);
-    GKeyFile* keyfile = g_key_file_new ();
-    /* Strip LRE leading character and / */
-    gchar* filename = g_strdelimit (g_strconcat (app_name, ".desktop", NULL), "‪/", ' ');
-    gchar* app_dir = g_build_filename (g_get_user_data_dir (), "applications", filename, NULL);
-    #if WEBKIT_CHECK_VERSION (1, 3, 13)
-    /* FIXME: midori_paths_get_icon */
-    gchar* app_icon = g_strdup (STOCK_WEB_BROWSER);
-    #else
-    const gchar* icon_uri = midori_view_get_icon_uri (MIDORI_VIEW (tab));
-    gchar* app_icon = katze_net_get_cached_path (NULL, icon_uri, "icons");
-    if (!g_file_test (app_icon, G_FILE_TEST_EXISTS))
-        katze_assign (app_icon, g_strdup (STOCK_WEB_BROWSER));
-    #endif
-    g_key_file_set_string (keyfile, "Desktop Entry", "Version", "1.0");
-    g_key_file_set_string (keyfile, "Desktop Entry", "Type", "Application");
-    g_key_file_set_string (keyfile, "Desktop Entry", "Name", app_name);
-    g_key_file_set_string (keyfile, "Desktop Entry", "Exec", app_exec);
-    g_key_file_set_string (keyfile, "Desktop Entry", "TryExec", PACKAGE_NAME);
-    g_key_file_set_string (keyfile, "Desktop Entry", "Icon", app_icon);
-    g_key_file_set_string (keyfile, "Desktop Entry", "Categories", "Network;");
-    sokoke_key_file_save_to_file (keyfile, app_dir, NULL);
-    g_free (app_dir);
-    g_free (filename);
-    g_free (app_exec);
-    g_free (app_icon);
-    g_key_file_free (keyfile);
-    #elif defined(GDK_WINDOWING_QUARTZ)
-    /* TODO: Implement */
-    #elif defined (GDK_WINDOWING_WIN32)
-    /* TODO: Implement */
-    #endif
-}
-
-static void
 midori_browser_subscribe_to_news_feed (MidoriBrowser* browser,
                                        const gchar*   uri)
 {
@@ -2646,8 +2604,7 @@ _action_compact_add_activate (GtkAction*     action,
 {
     GtkWidget* dialog;
     GtkBox* box;
-    const gchar* actions[] = { "BookmarkAdd", "AddSpeedDial",
-                               "AddDesktopShortcut", "AddNewsFeed" };
+    const gchar* actions[] = { "BookmarkAdd", "AddSpeedDial", "AddNewsFeed" };
     guint i;
 
     dialog = g_object_new (GTK_TYPE_DIALOG,
@@ -3249,9 +3206,6 @@ _action_compact_menu_populate_popup (GtkAction*     action,
       { "BookmarksImport" },
       { "BookmarksExport" },
       { "ClearPrivateData" },
-      #if defined (GDK_WINDOWING_X11)
-      { "AddDesktopShortcut" },
-      #endif
       { "-" },
       { NULL },
       #ifndef HAVE_GRANITE
@@ -5339,10 +5293,6 @@ static const GtkActionEntry entries[] =
     { "AddSpeedDial", NULL,
         N_("Add to Speed _dial"), "<Ctrl>h",
         NULL, G_CALLBACK (_action_add_speed_dial_activate) },
-    { "AddDesktopShortcut", NULL,
-        /* N_("Add Shortcut to the _desktop"), "", */
-        N_("Create _Launcher"), "",
-        NULL, G_CALLBACK (_action_add_desktop_shortcut_activate) },
     { "AddNewsFeed", NULL,
         N_("Subscribe to News _feed"), NULL,
         NULL, G_CALLBACK (_action_add_news_feed_activate) },
@@ -5769,7 +5719,6 @@ static const gchar* ui_markup =
                 "<separator/>"
                 "<menuitem action='SaveAs'/>"
                 "<menuitem action='AddSpeedDial'/>"
-                "<menuitem action='AddDesktopShortcut'/>"
                 "<separator/>"
                 "<menuitem action='TabClose'/>"
                 "<menuitem action='WindowClose'/>"
@@ -5896,7 +5845,7 @@ midori_browser_realize_cb (GtkStyle*      style,
         if (gtk_icon_theme_has_icon (icon_theme, "midori"))
             gtk_window_set_icon_name (GTK_WINDOW (browser), "midori");
         else
-            gtk_window_set_icon_name (GTK_WINDOW (browser), STOCK_WEB_BROWSER);
+            gtk_window_set_icon_name (GTK_WINDOW (browser), MIDORI_STOCK_WEB_BROWSER);
     }
 }
 
@@ -6067,7 +6016,7 @@ midori_browser_init (MidoriBrowser* browser)
     g_signal_connect (browser, "destroy",
                       G_CALLBACK (midori_browser_destroy_cb), NULL);
     gtk_window_set_role (GTK_WINDOW (browser), "browser");
-    gtk_window_set_icon_name (GTK_WINDOW (browser), STOCK_WEB_BROWSER);
+    gtk_window_set_icon_name (GTK_WINDOW (browser), MIDORI_STOCK_WEB_BROWSER);
     #if GTK_CHECK_VERSION (3, 4, 0)
     gtk_window_set_hide_titlebar_when_maximized (GTK_WINDOW (browser), TRUE);
     #endif
@@ -6292,9 +6241,6 @@ midori_browser_init (MidoriBrowser* browser)
 
     _action_set_sensitive (browser, "EncodingCustom", FALSE);
     _action_set_visible (browser, "LastSession", FALSE);
-    #if !defined (GDK_WINDOWING_X11)
-    _action_set_visible (browser, "AddDesktopShortcut", FALSE);
-    #endif
 
     _action_set_visible (browser, "Bookmarks", browser->bookmarks != NULL);
     _action_set_visible (browser, "BookmarkAdd", browser->bookmarks != NULL);
diff --git a/midori/midori-stock.h b/midori/midori-stock.h
index fc8ce5c..e674c37 100644
--- a/midori/midori-stock.h
+++ b/midori/midori-stock.h
@@ -17,7 +17,7 @@
 #define STOCK_BOOKMARKS          "user-bookmarks"
 #define STOCK_EXTENSION          "extension"
 #define STOCK_HISTORY            "document-open-recent"
-#define STOCK_WEB_BROWSER        "web-browser"
+#define MIDORI_STOCK_WEB_BROWSER "web-browser"
 #define STOCK_NEWS_FEED          "internet-news-reader"
 #define STOCK_STYLE              "preferences-desktop-theme"
 #define STOCK_TRANSFER           "package"
diff --git a/midori/midori-view.c b/midori/midori-view.c
index 1d300c3..1a44ed3 100644
--- a/midori/midori-view.c
+++ b/midori/midori-view.c
@@ -2981,9 +2981,6 @@ midori_view_populate_popup (MidoriView* view,
                 gtk_action_group_get_action (actions, "AddSpeedDial"));
             gtk_menu_shell_append (menu_shell, menuitem);
         }
-        menuitem = sokoke_action_create_popup_menu_item (
-                gtk_action_group_get_action (actions, "AddDesktopShortcut"));
-        gtk_menu_shell_append (menu_shell, menuitem);
 
         menuitem = sokoke_action_create_popup_menu_item (
                 gtk_action_group_get_action (actions, "SaveAs"));
diff --git a/midori/midori.vapi b/midori/midori.vapi
index 83871f5..71bfcdc 100644
--- a/midori/midori.vapi
+++ b/midori/midori.vapi
@@ -1,10 +1,13 @@
 /* Copyright (C) 2010 Christian Dywan <christian at twotoasts.de>
    This file is licensed under the terms of the expat license, see the file EXPAT. */
 
+public const string PACKAGE_NAME;
+
 [CCode (cprefix = "Midori", lower_case_cprefix = "midori_")]
 namespace Midori {
     public const string VERSION_SUFFIX;
     namespace Stock {
+        public const string WEB_BROWSER;
         public const string PLUGINS;
     }
 
@@ -71,7 +74,7 @@ namespace Midori {
         [NoAccessorMethod]
         public Gtk.Notebook notebook { owned get; }
         [NoAccessorMethod]
-        public Gtk.Widget panel { owned get; }
+        public Midori.Panel panel { owned get; }
         [NoAccessorMethod]
         public string uri { owned get; set; }
         public Gtk.Widget? tab { get; set; }
@@ -109,6 +112,12 @@ namespace Midori {
     }
 
     [CCode (cheader_filename = "midori/midori.h")]
+    public class Panel : Gtk.HBox {
+        public Panel ();
+        public int append_page (Midori.Viewable viewable);
+    }
+
+    [CCode (cheader_filename = "midori/midori.h")]
     public class Extension : GLib.Object {
         [CCode (has_construct_function = false)]
         public Extension ();
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 2d79251..c0b1680 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -85,3 +85,4 @@ extensions/cookie-permissions/cookie-permission-manager-preferences-window.h
 extensions/cookie-permissions/cookie-permission-manager.c
 extensions/cookie-permissions/cookie-permission-manager.h
 extensions/cookie-permissions/main.c
+extensions/apps.vala
diff --git a/tests/app.vala b/tests/app.vala
index eaa8603..b44726e 100644
--- a/tests/app.vala
+++ b/tests/app.vala
@@ -9,8 +9,6 @@
  See the file COPYING for the full license text.
 */
 
-extern const string PACKAGE_NAME;
-
 bool check_sensible_window_size (Gtk.Window window, Midori.WebSettings settings) {
     Gdk.Rectangle monitor;
     window.screen.get_monitor_geometry (0, out monitor);


More information about the Xfce4-commits mailing list