diff --git a/files/usr/bin/cinnamon-launcher-creator b/files/usr/bin/cinnamon-launcher-creator new file mode 100755 index 0000000..6f51401 --- /dev/null +++ b/files/usr/bin/cinnamon-launcher-creator @@ -0,0 +1,21 @@ +#! /usr/bin/python -OOt + +import sys +sys.path.insert(0,'/usr/lib/cinnamon-menu-editor') +from cme import ItemEditor, MenuEditor + +def main(): + try: + from MenuEditor import config + datadir = config.pkgdatadir + version = config.VERSION + except: + datadir = '.' + version = '0.9' + if len(sys.argv) > 1: + app = ItemEditor.DesktopLauncherCreator(sys.argv[1]) + else: + print "Missing path argument for launcher location" + +if __name__ == '__main__': + main() diff --git a/files/usr/lib/cinnamon-menu-editor/cme/ItemEditor.py b/files/usr/lib/cinnamon-menu-editor/cme/ItemEditor.py new file mode 100644 index 0000000..80c2acd --- /dev/null +++ b/files/usr/lib/cinnamon-menu-editor/cme/ItemEditor.py @@ -0,0 +1,247 @@ +# -*- 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 +import gi +from gi.repository import GLib, Gtk +from cme import config, util + +_ = 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 + +# XXX - replace with a better UI eventually +class IconPicker(object): + def __init__(self, dialog, button, image): + self.dialog = dialog + self.button = button + self.button.connect('clicked', self.pick_icon) + self.image = image + + 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.image.props.file = chooser.get_filename() + chooser.destroy() + +class ItemEditor(object): + ui_file = None + + def __init__(self, item_path, parent=None): + self.builder = Gtk.Builder() + self.builder.add_from_file(self.ui_file) + + self.dialog = self.builder.get_object('editor') + if parent: + self.dialog.set_transient_for(parent) + self.dialog.connect('response', self.on_response) + + self.build_ui() + + self.item_path = item_path + self.load() + self.resync_validity() + + def build_ui(self): + raise NotImplementedError() + + def get_keyfile_edits(self): + raise NotImplementedError() + + def set_text(self, ctl, name): + try: + val = self.keyfile.get_string(DESKTOP_GROUP, name) + except GLib.GError: + pass + else: + self.builder.get_object(ctl).set_text(val) + + def set_check(self, ctl, name): + try: + val = self.keyfile.get_boolean(DESKTOP_GROUP, name) + except GLib.GError: + pass + else: + self.builder.get_object(ctl).set_active(val) + + def set_icon(self, ctl, name): + try: + val = self.keyfile.get_string(DESKTOP_GROUP, name) + except GLib.GError: + pass + else: + set_icon_string(self.builder.get_object(ctl), val) + + def load(self): + self.keyfile = GLib.KeyFile() + try: + self.keyfile.load_from_file(self.item_path, util.KEY_FILE_FLAGS) + except GLib.GError: + pass + + def save(self): + util.fillKeyFile(self.keyfile, self.get_keyfile_edits()) + contents, length = self.keyfile.to_data() + need_exec = False + if ".desktop" not in self.item_path and ".directory" not in self.item_path: + need_exec = True + self.item_path = os.path.join(self.item_path, (self.builder.get_object('name-entry').get_text() + ".desktop")) + with open(self.item_path, 'w') as f: + f.write(contents) + if need_exec: + os.system("chmod a+x " + self.item_path) + + + def run(self): + self.dialog.present() + + def on_response(self, dialog, response): + if response == Gtk.ResponseType.OK: + self.save() + self.dialog.destroy() + +class LauncherEditor(ItemEditor): + ui_file = '/usr/lib/cinnamon-menu-editor/launcher-editor.ui' + + def build_ui(self): + self.icon_picker = IconPicker(self.dialog, + self.builder.get_object('icon-button'), + self.builder.get_object('icon-image')) + + self.builder.get_object('exec-browse').connect('clicked', self.pick_exec) + + self.builder.get_object('name-entry').connect('changed', self.resync_validity) + self.builder.get_object('exec-entry').connect('changed', self.resync_validity) + + def resync_validity(self, *args): + name_text = self.builder.get_object('name-entry').get_text() + exec_text = self.builder.get_object('exec-entry').get_text() + valid = (name_text is not None and exec_text is not None) + self.builder.get_object('ok').set_sensitive(valid) + + def load(self): + super(LauncherEditor, self).load() + self.set_text('name-entry', "Name") + self.set_text('exec-entry', "Exec") + self.set_text('comment-entry', "Comment") + self.set_check('terminal-check', "Terminal") + self.set_icon('icon-image', "Icon") + + def get_keyfile_edits(self): + return 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')), + Type="Application") + + 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() + +class DirectoryEditor(ItemEditor): + ui_file = '/usr/lib/cinnamon-menu-editor/directory-editor.ui' + + def build_ui(self): + self.icon_picker = IconPicker(self.dialog, + self.builder.get_object('icon-button'), + self.builder.get_object('icon-image')) + + self.builder.get_object('name-entry').connect('changed', self.resync_validity) + + def resync_validity(self, *args): + name_text = self.builder.get_object('name-entry').get_text() + valid = (name_text is not None) + self.builder.get_object('ok').set_sensitive(valid) + + def load(self): + super(DirectoryEditor, self).load() + self.set_text('name-entry', "Name") + self.set_text('comment-entry', "Comment") + self.set_icon('icon-image', "Icon") + + def get_keyfile_edits(self): + return dict(Name=self.builder.get_object('name-entry').get_text(), + Comment=self.builder.get_object('comment-entry').get_text(), + Icon=get_icon_string(self.builder.get_object('icon-image')), + Type="Directory") + +def DesktopLauncherCreator(path): + Gtk.Window.set_default_icon_name('alacarte') + editor = LauncherEditor(path, None) + editor.dialog.connect('destroy', Gtk.main_quit) + editor.dialog.show_all() + Gtk.main() + diff --git a/files/usr/lib/cinnamon-menu-editor/cme/MainWindow.py b/files/usr/lib/cinnamon-menu-editor/cme/MainWindow.py index 33106a9..735d420 100644 --- a/files/usr/lib/cinnamon-menu-editor/cme/MainWindow.py +++ b/files/usr/lib/cinnamon-menu-editor/cme/MainWindow.py @@ -22,6 +22,7 @@ import cgi import os import gettext import subprocess +import shutil from cme import config gettext.bindtextdomain(config.GETTEXT_PACKAGE, config.localedir) @@ -29,6 +30,7 @@ gettext.textdomain(config.GETTEXT_PACKAGE) _ = gettext.gettext from cme.MenuEditor import MenuEditor +from cme.ItemEditor import LauncherEditor, DirectoryEditor from cme import util class MainWindow(object): @@ -58,6 +60,7 @@ class MainWindow(object): self.cut_copy_buffer = None self.file_id = None self.last_tree = None + self.main_window = self.tree.get_object('mainwindow') def run(self): self.loadMenus() @@ -261,8 +264,8 @@ class MainWindow(object): else: parent = menus[iter][3] file_path = os.path.join(util.getUserDirectoryPath(), util.getUniqueFileId('alacarte-made', '.directory')) - process = subprocess.Popen(['gnome-desktop-item-edit', file_path], env=os.environ) - GObject.timeout_add(100, self.waitForNewMenuProcess, process, parent.get_menu_id(), file_path) + editor = DirectoryEditor(file_path, self.main_window) + editor.run() def on_new_item_button_clicked(self, button): menu_tree = self.tree.get_object('menu_tree') @@ -274,8 +277,8 @@ class MainWindow(object): else: parent = menus[iter][3] file_path = os.path.join(util.getUserItemPath(), util.getUniqueFileId('alacarte-made', '.desktop')) - process = subprocess.Popen(['gnome-desktop-item-edit', file_path], env=os.environ) - GObject.timeout_add(100, self.waitForNewItemProcess, process, parent.get_menu_id(), file_path) + editor = LauncherEditor(file_path, self.main_window) + editor.run() def on_edit_delete_activate(self, menu): item_tree = self.tree.get_object('item_tree') @@ -302,18 +305,17 @@ class MainWindow(object): if isinstance(item, GMenu.TreeEntry): file_path = os.path.join(util.getUserItemPath(), item.get_desktop_file_id()) file_type = 'Item' + Editor = LauncherEditor elif isinstance(item, GMenu.TreeDirectory): file_path = os.path.join(util.getUserDirectoryPath(), os.path.split(item.get_desktop_file_path())[1]) file_type = 'Menu' + Editor = DirectoryEditor if not os.path.isfile(file_path): - data = open(item.get_desktop_file_path()).read() - open(file_path, 'w').write(data) + 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 = Editor(file_path, self.main_window) + editor.run() def on_edit_cut_activate(self, menu): item_tree = self.tree.get_object('item_tree') diff --git a/files/usr/lib/cinnamon-menu-editor/cme/util.py b/files/usr/lib/cinnamon-menu-editor/cme/util.py index 237f03b..459c222 100644 --- a/files/usr/lib/cinnamon-menu-editor/cme/util.py +++ b/files/usr/lib/cinnamon-menu-editor/cme/util.py @@ -32,10 +32,10 @@ def fillKeyFile(keyfile, items): if isinstance(item, bool): keyfile.set_boolean(DESKTOP_GROUP, key, item) - elif isinstance(item, Sequence): - keyfile.set_string_list(DESKTOP_GROUP, key, item) elif isinstance(item, basestring): keyfile.set_string(DESKTOP_GROUP, key, item) + elif isinstance(item, Sequence): + keyfile.set_string_list(DESKTOP_GROUP, key, item) def getNameFromKeyFile(keyfile): return keyfile.get_string(DESKTOP_GROUP, "Name") diff --git a/files/usr/lib/cinnamon-menu-editor/directory-editor.ui b/files/usr/lib/cinnamon-menu-editor/directory-editor.ui new file mode 100644 index 0000000..9228c49 --- /dev/null +++ b/files/usr/lib/cinnamon-menu-editor/directory-editor.ui @@ -0,0 +1,178 @@ + + + + + False + 4 + Directory Properties + True + dialog + + + False + vertical + 4 + + + False + end + + + gtk-cancel + True + True + True + True + + + False + True + 0 + + + + + gtk-ok + True + True + True + True + + + False + True + 1 + + + + + False + True + end + 0 + + + + + True + False + 10 + + + True + False + 1 + 0 + 0 + + + True + True + True + + + True + False + 64 + folder + + + + + + + False + True + 0 + + + + + True + False + 6 + 10 + + + True + False + 1 + Name: + + + + + + 0 + 0 + 1 + 1 + + + + + True + False + 1 + Comment: + + + + + + 0 + 1 + 1 + 1 + + + + + True + True + True + + + + 1 + 0 + 1 + 1 + + + + + True + True + + + + 1 + 1 + 1 + 1 + + + + + True + True + end + 1 + + + + + True + True + 1 + + + + + + cancel + ok + + + \ No newline at end of file diff --git a/files/usr/lib/cinnamon-menu-editor/launcher-editor.ui b/files/usr/lib/cinnamon-menu-editor/launcher-editor.ui new file mode 100644 index 0000000..a643c63 --- /dev/null +++ b/files/usr/lib/cinnamon-menu-editor/launcher-editor.ui @@ -0,0 +1,252 @@ + + + + + False + 4 + Launcher Properties + True + dialog + + + False + vertical + 4 + + + False + end + + + gtk-cancel + True + True + True + True + + + False + True + 0 + + + + + gtk-ok + True + True + True + True + + + False + True + 1 + + + + + False + True + end + 0 + + + + + True + False + 10 + + + True + False + 1 + 0 + 0 + + + True + True + True + + + True + False + 64 + gnome-panel-launcher + + + + + + + False + True + 0 + + + + + True + False + 6 + 10 + + + True + False + 1 + Name: + + + + + + 0 + 0 + 1 + 1 + + + + + True + False + 1 + Command: + + + + + + 0 + 1 + 1 + 1 + + + + + True + False + 1 + Comment: + + + + + + 0 + 2 + 1 + 1 + + + + + True + True + True + + + + 1 + 0 + 1 + 1 + + + + + True + False + 10 + + + True + True + + + + True + True + 0 + + + + + Browse + True + True + True + + + False + True + 1 + + + + + 1 + 1 + 1 + 1 + + + + + True + True + + + + 1 + 2 + 1 + 1 + + + + + Launch in Terminal? + True + True + False + 0 + True + + + 1 + 3 + 1 + 1 + + + + + + + + True + True + end + 1 + + + + + True + True + 1 + + + + + + cancel + ok + + + \ No newline at end of file