[alacarte] Add our own custom launcher editor



commit 968c81bf5f9d4b8e3c71f33b142b8f77363378f1
Author: Jasper St. Pierre <jstpierre mecheye net>
Date:   Thu Jan 10 22:02:05 2013 -0500

    Add our own custom launcher editor
    
    Replace simple uses of gnome-desktop-item-edit with our own desktop
    file editor. For now, this only supports launcher entries -- menu
    directories are not supported yet.

 Alacarte/ItemEditor.py  |  177 +++++++++++++++++++++++++++++++++
 Alacarte/MainWindow.py  |   14 +--
 Alacarte/Makefile.am    |    2 +-
 data/Makefile.am        |    4 +-
 data/launcher-editor.ui |  251 +++++++++++++++++++++++++++++++++++++++++++++++
 po/POTFILES.in          |    2 +
 6 files changed, 436 insertions(+), 14 deletions(-)
---
diff --git a/Alacarte/ItemEditor.py b/Alacarte/ItemEditor.py
new file mode 100644
index 0000000..41eab09
--- /dev/null
+++ b/Alacarte/ItemEditor.py
@@ -0,0 +1,177 @@
+# -*- coding: utf-8 -*-
+#   Alacarte Menu Editor - Simple fd.o Compliant Menu Editor
+#   Copyright (C) 2013  Red Hat, Inc.
+#
+#   This library is free software; you can redistribute it and/or
+#   modify it under the terms of the GNU Library General Public
+#   License as published by the Free Software Foundation; either
+#   version 2 of the License, or (at your option) any later version.
+#
+#   This library 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
+#   Library General Public License for more details.
+#
+#   You should have received a copy of the GNU Library General Public
+#   License along with this library; if not, write to the Free Software
+#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import gettext
+import os
+from gi.repository import GLib, Gtk
+from Alacarte import config, util
+
+from gi._glib import GError
+
+_ = gettext.gettext
+
+EXTENSIONS = (".png", ".xpm", ".svg")
+
+def try_icon_name(filename):
+    # Detect if the user picked an icon, and make
+    # it into an icon name.
+    if not filename.endswith(EXTENSIONS):
+        return filename
+
+    filename = filename[:-4]
+
+    theme = Gtk.IconTheme.get_default()
+    resolved_path = None
+    for path in theme.get_search_path():
+        if filename.startswith(path):
+            resolved_path = filename[len(path):].lstrip(os.sep)
+            break
+
+    if resolved_path is None:
+        return filename
+
+    parts = resolved_path.split(os.sep)
+    # icon-theme/size/category/icon
+    if len(parts) != 4:
+        return filename
+
+    return parts[3]
+
+def get_icon_string(image):
+    filename = image.props.file
+    if filename is not None:
+        return try_icon_name(filename)
+
+    return image.props.icon_name
+
+def strip_extensions(icon):
+    if icon.endswith(EXTENSIONS):
+        return icon[:-4]
+    else:
+        return icon
+
+def set_icon_string(image, icon):
+    if GLib.path_is_absolute(icon):
+        image.props.file = icon
+    else:
+        image.props.icon_name = strip_extensions(icon)
+
+DESKTOP_GROUP = GLib.KEY_FILE_DESKTOP_GROUP
+
+class LauncherEditor(object):
+    def __init__(self, item_path):
+        self.builder = Gtk.Builder()
+        self.builder.add_from_file('data/launcher-editor.ui')
+
+        self.dialog = self.builder.get_object('launcher-editor')
+        self.dialog.connect('response', self.on_response)
+
+        self.builder.get_object('icon-button').connect('clicked', self.pick_icon)
+        self.builder.get_object('exec-browse').connect('clicked', self.pick_exec)
+
+        self.item_path = item_path
+        self.load()
+
+    def load(self):
+        self.keyfile = GLib.KeyFile()
+        try:
+            self.keyfile.load_from_file(self.item_path, util.KEY_FILE_FLAGS)
+        except IOError:
+            return
+
+        def set_text(ctl, name):
+            try:
+                val = self.keyfile.get_string(DESKTOP_GROUP, name)
+            except GError:
+                pass
+            else:
+                self.builder.get_object(ctl).set_text(val)
+
+        def set_check(ctl, name):
+            try:
+                val = self.keyfile.get_boolean(DESKTOP_GROUP, name)
+            except GError:
+                pass
+            else:
+                self.builder.get_object(ctl).set_active(val)
+
+        def set_icon(ctl, name):
+            try:
+                val = self.keyfile.get_string(DESKTOP_GROUP, name)
+            except GError:
+                pass
+            else:
+                set_icon_string(self.builder.get_object(ctl), val)
+
+        set_text('name-entry', "Name")
+        set_text('exec-entry', "Exec")
+        set_text('comment-entry', "Comment")
+        set_check('terminal-check', "Terminal")
+        set_icon('icon-image', "Icon")
+
+    def run(self):
+        self.dialog.present()
+
+    def save(self):
+        params = dict(Name=self.builder.get_object('name-entry').get_text(),
+                      Exec=self.builder.get_object('exec-entry').get_text(),
+                      Comment=self.builder.get_object('comment-entry').get_text(),
+                      Terminal=self.builder.get_object('terminal-check').get_active(),
+                      Icon=get_icon_string(self.builder.get_object('icon-image')))
+        util.fillKeyFile(self.keyfile, params)
+
+        contents, length = self.keyfile.to_data()
+        with open(self.item_path, 'w') as f:
+            f.write(contents)
+
+    def on_response(self, dialog, response):
+        if response == Gtk.ResponseType.OK:
+            self.save()
+        self.dialog.destroy()
+
+    def pick_icon(self, button):
+        chooser = Gtk.FileChooserDialog(title=_("Choose an icon"),
+                                        parent=self.dialog,
+                                        buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT,
+                                        Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT))
+        response = chooser.run()
+        if response == Gtk.ResponseType.ACCEPT:
+            self.builder.get_object('icon-image').props.file = chooser.get_filename()
+        chooser.destroy()
+
+    def pick_exec(self, button):
+        chooser = Gtk.FileChooserDialog(title=_("Choose a command"),
+                                        parent=self.dialog,
+                                        buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT,
+                                        Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT))
+        response = chooser.run()
+        if response == Gtk.ResponseType.ACCEPT:
+            self.builder.get_object('exec-entry').set_text(chooser.get_filename())
+        chooser.destroy()
+
+def test():
+    import sys
+
+    Gtk.Window.set_default_icon_name('alacarte')
+    editor = LauncherEditor(sys.argv[1])
+    editor.dialog.connect('destroy', Gtk.main_quit)
+    editor.run()
+    Gtk.main()
+
+if __name__ == "__main__":
+    test()
diff --git a/Alacarte/MainWindow.py b/Alacarte/MainWindow.py
index e9d84e0..592fc9b 100644
--- a/Alacarte/MainWindow.py
+++ b/Alacarte/MainWindow.py
@@ -30,6 +30,7 @@ gettext.textdomain(config.GETTEXT_PACKAGE)
 
 _ = gettext.gettext
 from Alacarte.MenuEditor import MenuEditor
+from Alacarte.ItemEditor import LauncherEditor
 from Alacarte import util
 
 class MainWindow(object):
@@ -252,13 +253,6 @@ class MainWindow(object):
             return False
         return True
 
-    #this callback keeps you from editing the same item twice
-    def waitForEditProcess(self, process, file_path):
-        if process.poll() is not None:
-            self.edit_pool.remove(file_path)
-            return False
-        return True
-
     def on_new_menu_button_clicked(self, button):
         menu_tree = self.tree.get_object('menu_tree')
         menus, iter = menu_tree.get_selection().get_selected()
@@ -329,10 +323,8 @@ class MainWindow(object):
         if not os.path.isfile(file_path):
             shutil.copy(item.get_desktop_file_path(), file_path)
 
-        if file_path not in self.edit_pool:
-            self.edit_pool.append(file_path)
-            process = subprocess.Popen(['gnome-desktop-item-edit', file_path], env=os.environ)
-            GObject.timeout_add(100, self.waitForEditProcess, process, file_path)
+        editor = LauncherEditor(file_path)
+        editor.run()
 
     def on_menu_tree_cursor_changed(self, treeview):
         selection = treeview.get_selection()
diff --git a/Alacarte/Makefile.am b/Alacarte/Makefile.am
index f1677ea..7c8705c 100644
--- a/Alacarte/Makefile.am
+++ b/Alacarte/Makefile.am
@@ -1,7 +1,7 @@
 ## Process this file with automake to produce Makefile.in
 
 appdir = $(pythondir)/Alacarte
-app_PYTHON = __init__.py MainWindow.py MenuEditor.py util.py
+app_PYTHON = __init__.py MainWindow.py MenuEditor.py ItemEditor.py util.py
 nodist_app_PYTHON = config.py
 
 config.py: config.py.in
diff --git a/data/Makefile.am b/data/Makefile.am
index 625b2af..e2f7340 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -3,10 +3,10 @@ SUBDIRS = icons
 @INTLTOOL_DESKTOP_RULE@
 
 desktopdir = $(datadir)/applications
-desktop_in_files = alacarte.desktop.in
+ndesktop_in_files = alacarte.desktop.in
 desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
 
-pkgdata_DATA = alacarte.ui
+pkgdata_DATA = alacarte.ui launcher-editor.ui
 
 CLEANFILES = $(desktop_DATA)
 
diff --git a/data/launcher-editor.ui b/data/launcher-editor.ui
new file mode 100644
index 0000000..45a4996
--- /dev/null
+++ b/data/launcher-editor.ui
@@ -0,0 +1,251 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <object class="GtkDialog" id="launcher-editor">
+    <property name="can_focus">False</property>
+    <property name="border_width">4</property>
+    <property name="title" translatable="yes">Launcher Properties</property>
+    <property name="modal">True</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-box">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">4</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action_area">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="cancel">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="ok">
+                <property name="label">gtk-ok</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="hbox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">10</property>
+            <child>
+              <object class="GtkAlignment" id="alignment1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">1</property>
+                <property name="yalign">0</property>
+                <property name="yscale">0</property>
+                <child>
+                  <object class="GtkButton" id="icon-button">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <child>
+                      <object class="GtkImage" id="icon-image">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="pixel_size">64</property>
+                        <property name="icon_name">gnome-panel-launcher</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkGrid" id="grid1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="row_spacing">6</property>
+                <property name="column_spacing">10</property>
+                <child>
+                  <object class="GtkLabel" id="label2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">1</property>
+                    <property name="label" translatable="yes">Name:</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">0</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label3">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">1</property>
+                    <property name="label" translatable="yes">Command:</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">1</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label4">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">1</property>
+                    <property name="label" translatable="yes">Comment:</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">2</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkEntry" id="name-entry">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="invisible_char">â</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">0</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="command-box">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="spacing">10</property>
+                    <child>
+                      <object class="GtkEntry" id="exec-entry">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="invisible_char">â</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="exec-browse">
+                        <property name="label" translatable="yes">Browse</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">1</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkEntry" id="comment-entry">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="invisible_char">â</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">2</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkCheckButton" id="terminal-check">
+                    <property name="label" translatable="yes">Launch in Terminal?</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="xalign">0</property>
+                    <property name="draw_indicator">True</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">3</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="pack_type">end</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">cancel</action-widget>
+      <action-widget response="-5">ok</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 9cd7e73..033686a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,5 +1,7 @@
 [encoding: UTF-8]
 Alacarte/MainWindow.py
 Alacarte/MenuEditor.py
+Alacarte/ItemEditor.py
 data/alacarte.desktop.in.in
 [type: gettext/glade]data/alacarte.ui
+[type: gettext/glade]data/launcher-editor.ui



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