--- a/files/usr/bin/cinnamon-menu-editor +++ b/files/usr/bin/cinnamon-menu-editor @@ -12,7 +12,7 @@ def main(): except: datadir = '.' version = '0.9' - app = MainWindow.MainWindow(datadir, version, sys.argv) + app = MainWindow.MainWindow(datadir, version) app.run() if __name__ == '__main__': --- a/files/usr/lib/cinnamon-menu-editor/Alacarte/config.py +++ b/files/usr/lib/cinnamon-menu-editor/Alacarte/config.py @@ -5,5 +5,5 @@ pkgdatadir="/usr/share/alacarte" libdir="/usr/lib" libexecdir="/usr/lib/alacarte" PACKAGE="alacarte" -VERSION="0.13.2" +VERSION="3.5.4" GETTEXT_PACKAGE="alacarte" --- a/files/usr/lib/cinnamon-menu-editor/Alacarte/MainWindow.py +++ b/files/usr/lib/cinnamon-menu-editor/Alacarte/MainWindow.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# vim: set noexpandtab: # Alacarte Menu Editor - Simple fd.o Compliant Menu Editor # Copyright (C) 2006 Travis Watkins # @@ -16,614 +17,437 @@ # 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 gtk, gmenu, gobject, gio -import cgi, os -import gettext, locale +from gi.repository import Gtk, GObject, Gio, GdkPixbuf, Gdk, GMenu, GLib +import cgi +import os +import gettext import subprocess -import urllib -try: - from Alacarte import config - gettext.bindtextdomain(config.GETTEXT_PACKAGE,config.localedir) - gettext.textdomain(config.GETTEXT_PACKAGE) - locale.bind_textdomain_codeset(config.GETTEXT_PACKAGE,'UTF-8') -except: - pass + +from Alacarte import config +gettext.bindtextdomain(config.GETTEXT_PACKAGE, config.localedir) +gettext.textdomain(config.GETTEXT_PACKAGE) + _ = gettext.gettext from Alacarte.MenuEditor import MenuEditor from Alacarte import util -class MainWindow: - timer = None - #hack to make editing menu properties work - allow_update = True - #drag-and-drop stuff - dnd_items = [('ALACARTE_ITEM_ROW', gtk.TARGET_SAME_APP, 0), ('text/plain', 0, 1)] - dnd_menus = [('ALACARTE_MENU_ROW', gtk.TARGET_SAME_APP, 0)] - dnd_both = [dnd_items[0],] + dnd_menus - drag_data = None - edit_pool = [] - - def __init__(self, datadir, version, argv): - self.file_path = datadir - self.version = version - self.editor = MenuEditor() - gtk.window_set_default_icon_name('alacarte') - self.tree = gtk.Builder() - self.tree.set_translation_domain(config.GETTEXT_PACKAGE) - self.tree.add_from_file('/usr/lib/cinnamon-menu-editor/cinnamon-menu-editor.ui') - self.tree.connect_signals(self) - self.setupMenuTree() - self.setupItemTree() - self.tree.get_object('edit_delete').set_sensitive(False) - self.tree.get_object('edit_revert_to_original').set_sensitive(False) - self.tree.get_object('edit_properties').set_sensitive(False) - self.tree.get_object('move_up_button').set_sensitive(False) - self.tree.get_object('move_down_button').set_sensitive(False) - self.tree.get_object('new_separator_button').set_sensitive(False) - accelgroup = gtk.AccelGroup() - keyval, modifier = gtk.accelerator_parse('Z') - accelgroup.connect_group(keyval, modifier, gtk.ACCEL_VISIBLE, self.on_mainwindow_undo) - keyval, modifier = gtk.accelerator_parse('Z') - accelgroup.connect_group(keyval, modifier, gtk.ACCEL_VISIBLE, self.on_mainwindow_redo) - self.tree.get_object('mainwindow').add_accel_group(accelgroup) - - def run(self): - self.loadMenus() - self.editor.applications.tree.add_monitor(self.menuChanged, None) - self.tree.get_object('mainwindow').show_all() - gtk.main() - - def menuChanged(self, *a): - if self.timer: - gobject.source_remove(self.timer) - self.timer = None - self.timer = gobject.timeout_add(3, self.loadUpdates) - - def loadUpdates(self): - if not self.allow_update: - return False - menu_tree = self.tree.get_object('menu_tree') - item_tree = self.tree.get_object('item_tree') - items, iter = item_tree.get_selection().get_selected() - update_items = False - item_id, separator_path = None, None - if iter: - update_items = True - if items[iter][3].get_type() == gmenu.TYPE_DIRECTORY: - item_id = os.path.split(items[iter][3].get_desktop_file_path())[1] - update_items = True - elif items[iter][3].get_type() == gmenu.TYPE_ENTRY: - item_id = items[iter][3].get_desktop_file_id() - update_items = True - elif items[iter][3].get_type() == gmenu.TYPE_SEPARATOR: - item_id = items.get_path(iter) - update_items = True - menus, iter = menu_tree.get_selection().get_selected() - update_menus = False - menu_id = None - if iter: - if menus[iter][2].get_desktop_file_path(): - menu_id = os.path.split(menus[iter][2].get_desktop_file_path())[1] - else: - menu_id = menus[iter][2].get_menu_id() - update_menus = True - self.loadMenus() - #find current menu in new tree - if update_menus: - menu_tree.get_model().foreach(self.findMenu, menu_id) - menus, iter = menu_tree.get_selection().get_selected() - if iter: - self.on_menu_tree_cursor_changed(menu_tree) - #find current item in new list - if update_items: - i = 0 - for item in item_tree.get_model(): - found = False - if item[3].get_type() == gmenu.TYPE_ENTRY and item[3].get_desktop_file_id() == item_id: - found = True - if item[3].get_type() == gmenu.TYPE_DIRECTORY and item[3].get_desktop_file_path(): - if os.path.split(item[3].get_desktop_file_path())[1] == item_id: - found = True - if item[3].get_type() == gmenu.TYPE_SEPARATOR: - if not isinstance(item_id, tuple): - continue - #separators have no id, have to find them manually - #probably won't work with two separators together - if (item_id[0] - 1,) == (i,): - found = True - elif (item_id[0] + 1,) == (i,): - found = True - elif (item_id[0],) == (i,): - found = True - if found: - item_tree.get_selection().select_path((i,)) - self.on_item_tree_cursor_changed(item_tree) - break - i += 1 - return False - - def findMenu(self, menus, path, iter, menu_id): - if not menus[path][2].get_desktop_file_path(): - if menu_id == menus[path][2].get_menu_id(): - menu_tree = self.tree.get_object('menu_tree') - menu_tree.expand_to_path(path) - menu_tree.get_selection().select_path(path) - return True - return False - if os.path.split(menus[path][2].get_desktop_file_path())[1] == menu_id: - menu_tree = self.tree.get_object('menu_tree') - menu_tree.expand_to_path(path) - menu_tree.get_selection().select_path(path) - return True - - def setupMenuTree(self): - self.menu_store = gtk.TreeStore(gtk.gdk.Pixbuf, str, object) - menus = self.tree.get_object('menu_tree') - column = gtk.TreeViewColumn(_("Name")) - column.set_spacing(4) - cell = gtk.CellRendererPixbuf() - column.pack_start(cell, False) - column.set_attributes(cell, pixbuf=0) - cell = gtk.CellRendererText() - cell.set_fixed_size(-1, 25) - column.pack_start(cell, True) - column.set_attributes(cell, markup=1) - column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) - menus.append_column(column) - menus.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, self.dnd_menus, gtk.gdk.ACTION_COPY) - menus.enable_model_drag_dest(self.dnd_both, gtk.gdk.ACTION_PRIVATE) - - def setupItemTree(self): - items = self.tree.get_object('item_tree') - column = gtk.TreeViewColumn(_("Show")) - cell = gtk.CellRendererToggle() - cell.connect('toggled', self.on_item_tree_show_toggled) - column.pack_start(cell, True) - column.set_attributes(cell, active=0) - #hide toggle for separators - column.set_cell_data_func(cell, self._cell_data_toggle_func) - items.append_column(column) - column = gtk.TreeViewColumn(_("Item")) - column.set_spacing(4) - cell = gtk.CellRendererPixbuf() - column.pack_start(cell, False) - column.set_attributes(cell, pixbuf=1) - cell = gtk.CellRendererText() - cell.set_fixed_size(-1, 25) - column.pack_start(cell, True) - column.set_attributes(cell, markup=2) - items.append_column(column) - self.item_store = gtk.ListStore(bool, gtk.gdk.Pixbuf, str, object) - items.set_model(self.item_store) - items.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, self.dnd_items, gtk.gdk.ACTION_COPY) - items.enable_model_drag_dest(self.dnd_items, gtk.gdk.ACTION_PRIVATE) - - def _cell_data_toggle_func(self, tree_column, renderer, model, treeiter): - if model[treeiter][3].get_type() == gmenu.TYPE_SEPARATOR: - renderer.set_property('visible', False) - else: - renderer.set_property('visible', True) - - def loadMenus(self): - self.menu_store.clear() - for menu in self.editor.getMenus(): - iters = [None]*20 - self.loadMenu(iters, menu) - menu_tree = self.tree.get_object('menu_tree') - menu_tree.set_model(self.menu_store) - for menu in self.menu_store: - #this might not work for some reason - try: - menu_tree.expand_to_path(menu.path) - except: - pass - menu_tree.get_selection().select_path((0,)) - self.on_menu_tree_cursor_changed(menu_tree) - - def loadMenu(self, iters, parent, depth=0): - if depth == 0: - icon = util.getIcon(parent) - iters[depth] = self.menu_store.append(None, (icon, cgi.escape(parent.get_name()), parent)) - depth += 1 - for menu, show in self.editor.getMenus(parent): - if show: - name = cgi.escape(menu.get_name()) - else: - name = '' + cgi.escape(menu.get_name()) + '' - icon = util.getIcon(menu) - iters[depth] = self.menu_store.append(iters[depth-1], (icon, name, menu)) - self.loadMenu(iters, menu, depth) - depth -= 1 - - def loadItems(self, menu, menu_path): - self.item_store.clear() - for item, show in self.editor.getItems(menu): - menu_icon = None - if item.get_type() == gmenu.TYPE_SEPARATOR: - name = '---' - icon = None - elif item.get_type() == gmenu.TYPE_ENTRY: - if show: - name = cgi.escape(item.get_display_name()) - else: - name = '' + cgi.escape(item.get_display_name()) + '' - icon = util.getIcon(item) - else: - if show: - name = cgi.escape(item.get_name()) - else: - name = '' + cgi.escape(item.get_name()) + '' - icon = util.getIcon(item) - self.item_store.append((show, icon, name, item)) - - #this is a little timeout callback to insert new items after - #gnome-desktop-item-edit has finished running - def waitForNewItemProcess(self, process, parent, file_path): - if process.poll() != None: - if os.path.isfile(file_path): - self.editor.insertExternalItem(os.path.split(file_path)[1], parent) - return False - return True - - def waitForNewMenuProcess(self, process, parent_id, file_path): - if process.poll() != None: - #hack for broken gnome-desktop-item-edit - broken_path = os.path.join(os.path.split(file_path)[0], '.directory') - if os.path.isfile(broken_path): - os.rename(broken_path, file_path) - if os.path.isfile(file_path): - self.editor.insertExternalMenu(os.path.split(file_path)[1], parent_id) - return False - return True - - #this callback keeps you from editing the same item twice - def waitForEditProcess(self, process, file_path): - if process.poll() != 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() - if not iter: - parent = menus[(0,)][2] - menu_tree.expand_to_path((0,)) - menu_tree.get_selection().select_path((0,)) - else: - parent = menus[iter][2] - 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.menu_id, file_path) - - def on_new_item_button_clicked(self, button): - menu_tree = self.tree.get_object('menu_tree') - menus, iter = menu_tree.get_selection().get_selected() - if not iter: - parent = menus[(0,)][2] - menu_tree.expand_to_path((0,)) - menu_tree.get_selection().select_path((0,)) - else: - parent = menus[iter][2] - 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, file_path) - - def on_new_separator_button_clicked(self, button): - item_tree = self.tree.get_object('item_tree') - items, iter = item_tree.get_selection().get_selected() - if not iter: - return - else: - after = items[iter][3] - menu_tree = self.tree.get_object('menu_tree') - menus, iter = menu_tree.get_selection().get_selected() - parent = menus[iter][2] - self.editor.createSeparator(parent, after=after) - - def on_edit_delete_activate(self, menu): - item_tree = self.tree.get_object('item_tree') - items, iter = item_tree.get_selection().get_selected() - if not iter: - return - item = items[iter][3] - if item.get_type() == gmenu.TYPE_ENTRY: - self.editor.deleteItem(item) - elif item.get_type() == gmenu.TYPE_DIRECTORY: - self.editor.deleteMenu(item) - elif item.get_type() == gmenu.TYPE_SEPARATOR: - self.editor.deleteSeparator(item) - - def on_edit_revert_to_original_activate(self, menu): - item_tree = self.tree.get_object('item_tree') - items, iter = item_tree.get_selection().get_selected() - if not iter: - return - item = items[iter][3] - if item.get_type() == gmenu.TYPE_ENTRY: - self.editor.revertItem(item) - elif item.get_type() == gmenu.TYPE_DIRECTORY: - self.editor.revertMenu(item) - - def on_edit_properties_activate(self, menu): - item_tree = self.tree.get_object('item_tree') - items, iter = item_tree.get_selection().get_selected() - if not iter: - return - item = items[iter][3] - if item.get_type() not in (gmenu.TYPE_ENTRY, gmenu.TYPE_DIRECTORY): - return - - if item.get_type() == gmenu.TYPE_ENTRY: - file_path = os.path.join(util.getUserItemPath(), item.get_desktop_file_id()) - file_type = 'Item' - elif item.get_type() == gmenu.TYPE_DIRECTORY: - if item.get_desktop_file_path() == None: - file_path = util.getUniqueFileId('alacarte-made', '.directory') - parser = util.DesktopParser(file_path, 'Directory') - parser.set('Name', item.get_name()) - parser.set('Comment', item.get_comment()) - parser.set('Icon', item.get_icon()) - parser.write(open(file_path)) - else: - file_path = os.path.join(util.getUserDirectoryPath(), os.path.split(item.get_desktop_file_path())[1]) - file_type = 'Menu' - - if not os.path.isfile(file_path): - data = open(item.get_desktop_file_path()).read() - open(file_path, 'w').write(data) - self.editor._MenuEditor__addUndo([(file_type, os.path.split(file_path)[1]),]) - else: - self.editor._MenuEditor__addUndo([item,]) - 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) - - def on_menu_tree_cursor_changed(self, treeview): - menus, iter = treeview.get_selection().get_selected() - menu_path = menus.get_path(iter) - item_tree = self.tree.get_object('item_tree') - item_tree.get_selection().unselect_all() - self.loadItems(self.menu_store[menu_path][2], menu_path) - self.tree.get_object('edit_delete').set_sensitive(False) - self.tree.get_object('edit_revert_to_original').set_sensitive(False) - self.tree.get_object('edit_properties').set_sensitive(False) - self.tree.get_object('move_up_button').set_sensitive(False) - self.tree.get_object('move_down_button').set_sensitive(False) - self.tree.get_object('new_separator_button').set_sensitive(False) - self.tree.get_object('properties_button').set_sensitive(False) - self.tree.get_object('delete_button').set_sensitive(False) - - def on_menu_tree_drag_data_get(self, treeview, context, selection, target_id, etime): - menus, iter = treeview.get_selection().get_selected() - self.drag_data = menus[iter][2] - - def on_menu_tree_drag_data_received(self, treeview, context, x, y, selection, info, etime): - menus = treeview.get_model() - drop_info = treeview.get_dest_row_at_pos(x, y) - if drop_info: - path, position = drop_info - types = (gtk.TREE_VIEW_DROP_INTO_OR_BEFORE, gtk.TREE_VIEW_DROP_INTO_OR_AFTER) - if position not in types: - context.finish(False, False, etime) - return False - if selection.target in ('ALACARTE_ITEM_ROW', 'ALACARTE_MENU_ROW'): - if self.drag_data == None: - return False - item = self.drag_data - new_parent = menus[path][2] - treeview.get_selection().select_path(path) - if item.get_type() == gmenu.TYPE_ENTRY: - self.editor.copyItem(item, new_parent) - elif item.get_type() == gmenu.TYPE_DIRECTORY: - if self.editor.moveMenu(item, new_parent) == False: - self.loadUpdates() - else: - context.finish(False, False, etime) - context.finish(True, True, etime) - self.drag_data = None - - def on_item_tree_show_toggled(self, cell, path): - item = self.item_store[path][3] - if item.get_type() == gmenu.TYPE_SEPARATOR: - return - if self.item_store[path][0]: - self.editor.setVisible(item, False) - else: - self.editor.setVisible(item, True) - self.item_store[path][0] = not self.item_store[path][0] - - def on_item_tree_cursor_changed(self, treeview): - items, iter = treeview.get_selection().get_selected() - if iter is None: - return - item = items[iter][3] - self.tree.get_object('edit_delete').set_sensitive(True) - self.tree.get_object('new_separator_button').set_sensitive(True) - self.tree.get_object('delete_button').set_sensitive(True) - if self.editor.canRevert(item): - self.tree.get_object('edit_revert_to_original').set_sensitive(True) - else: - self.tree.get_object('edit_revert_to_original').set_sensitive(False) - if not item.get_type() == gmenu.TYPE_SEPARATOR: - self.tree.get_object('edit_properties').set_sensitive(True) - self.tree.get_object('properties_button').set_sensitive(True) - else: - self.tree.get_object('edit_properties').set_sensitive(False) - self.tree.get_object('properties_button').set_sensitive(False) - - # If first item... - if items.get_path(iter)[0] == 0: - self.tree.get_object('move_up_button').set_sensitive(False) - else: - self.tree.get_object('move_up_button').set_sensitive(True) - - # If last item... - if items.get_path(iter)[0] == (len(items)-1): - self.tree.get_object('move_down_button').set_sensitive(False) - else: - self.tree.get_object('move_down_button').set_sensitive(True) - - def on_item_tree_row_activated(self, treeview, path, column): - self.on_edit_properties_activate(None) - - def on_item_tree_popup_menu(self, item_tree, event=None): - model, iter = item_tree.get_selection().get_selected() - if event: - #don't show if it's not the right mouse button - if event.button != 3: - return - button = event.button - event_time = event.time - info = item_tree.get_path_at_pos(int(event.x), int(event.y)) - if info != None: - path, col, cellx, celly = info - item_tree.grab_focus() - item_tree.set_cursor(path, col, 0) - else: - path = model.get_path(iter) - button = 0 - event_time = 0 - item_tree.grab_focus() - item_tree.set_cursor(path, item_tree.get_columns()[0], 0) - popup = self.tree.get_object('edit_menu') - popup.popup(None, None, None, button, event_time) - #without this shift-f10 won't work - return True - - def on_item_tree_drag_data_get(self, treeview, context, selection, target_id, etime): - items, iter = treeview.get_selection().get_selected() - self.drag_data = items[iter][3] - - def on_item_tree_drag_data_received(self, treeview, context, x, y, selection, info, etime): - items = treeview.get_model() - types = (gtk.TREE_VIEW_DROP_BEFORE, gtk.TREE_VIEW_DROP_INTO_OR_BEFORE) - if selection.target == 'ALACARTE_ITEM_ROW': - drop_info = treeview.get_dest_row_at_pos(x, y) - before = None - after = None - if self.drag_data == None: - return False - item = self.drag_data - if drop_info: - path, position = drop_info - if position in types: - before = items[path][3] - else: - after = items[path][3] - else: - path = (len(items) - 1,) - after = items[path][3] - if item.get_type() == gmenu.TYPE_ENTRY: - self.editor.moveItem(item, item.get_parent(), before, after) - elif item.get_type() == gmenu.TYPE_DIRECTORY: - if self.editor.moveMenu(item, item.get_parent(), before, after) == False: - self.loadUpdates() - elif item.get_type() == gmenu.TYPE_SEPARATOR: - self.editor.moveSeparator(item, item.get_parent(), before, after) - context.finish(True, True, etime) - elif selection.target == 'text/plain': - if selection.data == None: - return False - menus, iter = self.tree.get_object('menu_tree').get_selection().get_selected() - parent = menus[iter][2] - drop_info = treeview.get_dest_row_at_pos(x, y) - before = None - after = None - if drop_info: - path, position = drop_info - if position in types: - before = items[path][3] - else: - after = items[path][3] - else: - path = (len(items) - 1,) - after = items[path][3] - file_path = urllib.unquote(selection.data).strip() - if not file_path.startswith('file:'): - return - myfile = gio.File(uri=file_path) - file_info = myfile.query_info(gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE) - content_type = file_info.get_content_type() - if content_type == 'application/x-desktop': - input_stream = myfile.read() - open('/tmp/alacarte-dnd.desktop', 'w').write(input_stream.read()) - parser = util.DesktopParser('/tmp/alacarte-dnd.desktop') - self.editor.createItem(parent, parser.get('Icon'), parser.get('Name', self.editor.locale), parser.get('Comment', self.editor.locale), parser.get('Exec'), parser.get('Terminal'), before, after) - elif content_type in ('application/x-shellscript', 'application/x-executable'): - self.editor.createItem(parent, None, os.path.split(file_path)[1].strip(), None, file_path.replace('file://', '').strip(), False, before, after) - self.drag_data = None - - def on_item_tree_key_press_event(self, item_tree, event): - if event.keyval == gtk.keysyms.Delete: - self.on_edit_delete_activate(item_tree) - - def on_move_up_button_clicked(self, button): - item_tree = self.tree.get_object('item_tree') - items, iter = item_tree.get_selection().get_selected() - if not iter: - return - path = items.get_path(iter) - #at top, can't move up - if path[0] == 0: - return - item = items[path][3] - before = items[(path[0] - 1,)][3] - if item.get_type() == gmenu.TYPE_ENTRY: - self.editor.moveItem(item, item.get_parent(), before=before) - elif item.get_type() == gmenu.TYPE_DIRECTORY: - self.editor.moveMenu(item, item.get_parent(), before=before) - elif item.get_type() == gmenu.TYPE_SEPARATOR: - self.editor.moveSeparator(item, item.get_parent(), before=before) - - def on_move_down_button_clicked(self, button): - item_tree = self.tree.get_object('item_tree') - items, iter = item_tree.get_selection().get_selected() - if not iter: - return - path = items.get_path(iter) - #at bottom, can't move down - if path[0] == (len(items) - 1): - return - item = items[path][3] - after = items[path][3] - if item.get_type() == gmenu.TYPE_ENTRY: - self.editor.moveItem(item, item.get_parent(), after=after) - elif item.get_type() == gmenu.TYPE_DIRECTORY: - self.editor.moveMenu(item, item.get_parent(), after=after) - elif item.get_type() == gmenu.TYPE_SEPARATOR: - self.editor.moveSeparator(item, item.get_parent(), after=after) - - def on_mainwindow_undo(self, accelgroup, window, keyval, modifier): - self.editor.undo() - - def on_mainwindow_redo(self, accelgroup, window, keyval, modifier): - self.editor.redo() - - def on_revert_button_clicked(self, button): - dialog = self.tree.get_object('revertdialog') - dialog.set_transient_for(self.tree.get_object('mainwindow')) - dialog.show_all() - if dialog.run() == gtk.RESPONSE_YES: - self.editor.revert() - dialog.hide() - - def on_close_button_clicked(self, button): - try: - self.tree.get_object('mainwindow').hide() - except: - pass - gobject.timeout_add(10, self.quit) - - def on_properties_button_clicked(self, button): - self.on_edit_properties_activate(None) - def on_delete_button_clicked(self, button): - self.on_edit_delete_activate(None) - - def on_style_set(self, *args): - self.loadUpdates() - - def quit(self): - self.editor.quit() - gtk.main_quit() +class MainWindow(object): + timer = None + #hack to make editing menu properties work + edit_pool = [] + + def __init__(self, datadir, version): + self.file_path = datadir + self.version = version + self.editor = MenuEditor() + self.editor.tree.connect("changed", self.menuChanged) + Gtk.Window.set_default_icon_name('alacarte') + self.tree = Gtk.Builder() + self.tree.set_translation_domain(config.GETTEXT_PACKAGE) + self.tree.add_from_file('/usr/lib/cinnamon-menu-editor/cinnamon-menu-editor.ui') + self.tree.connect_signals(self) + self.setupMenuTree() + self.setupItemTree() + self.tree.get_object('edit_delete').set_sensitive(False) + self.tree.get_object('edit_properties').set_sensitive(False) + self.tree.get_object('move_up_button').set_sensitive(False) + self.tree.get_object('move_down_button').set_sensitive(False) + self.tree.get_object('new_separator_button').set_sensitive(False) + accelgroup = Gtk.AccelGroup() + keyval, modifier = Gtk.accelerator_parse('F1') + accelgroup.connect(keyval, modifier, Gtk.AccelFlags.VISIBLE, self.on_help_button_clicked) + self.tree.get_object('mainwindow').add_accel_group(accelgroup) + + def run(self): + self.loadMenus() + self.tree.get_object('mainwindow').show_all() + Gtk.main() + + def menuChanged(self, *a): + self.loadUpdates() + + def loadUpdates(self): + menu_tree = self.tree.get_object('menu_tree') + item_tree = self.tree.get_object('item_tree') + items, iter = item_tree.get_selection().get_selected() + update_items = False + update_type = None + item_id = None + if iter: + update_items = True + if isinstance(items[iter][3], GMenu.TreeEntry): + item_id = items[iter][3].get_desktop_file_id() + update_type = GMenu.TreeItemType.ENTRY + elif isinstance(items[iter][3], GMenu.TreeDirectory): + item_id = os.path.split(items[iter][3].get_desktop_file_path())[1] + update_type = GMenu.TreeItemType.DIRECTORY + elif isinstance(items[iter][3], GMenu.TreeSeparator): + item_id = items.get_path(iter) + update_type = GMenu.TreeItemType.SEPARATOR + menus, iter = menu_tree.get_selection().get_selected() + update_menus = False + menu_id = None + if iter: + if menus[iter][2].get_desktop_file_path(): + menu_id = os.path.split(menus[iter][2].get_desktop_file_path())[1] + else: + menu_id = menus[iter][2].get_menu_id() + update_menus = True + self.loadMenus() + #find current menu in new tree + if update_menus: + menu_tree.get_model().foreach(self.findMenu, menu_id) + menus, iter = menu_tree.get_selection().get_selected() + if iter: + self.on_menu_tree_cursor_changed(menu_tree) + #find current item in new list + if update_items: + i = 0 + for item in item_tree.get_model(): + found = False + if update_type != GMenu.TreeItemType.SEPARATOR: + if isinstance (item[3], GMenu.TreeEntry) and item[3].get_desktop_file_id() == item_id: + found = True + if isinstance (item[3], GMenu.TreeDirectory) and item[3].get_desktop_file_path() and update_type == GMenu.TreeItemType.DIRECTORY: + if os.path.split(item[3].get_desktop_file_path())[1] == item_id: + found = True + if isinstance(item[3], GMenu.TreeSeparator): + if not isinstance(item_id, tuple): + #we may not skip the increment via "continue" + i += 1 + continue + #separators have no id, have to find them manually + #probably won't work with two separators together + if (item_id[0] - 1,) == (i,): + found = True + elif (item_id[0] + 1,) == (i,): + found = True + elif (item_id[0],) == (i,): + found = True + if found: + item_tree.get_selection().select_path((i,)) + self.on_item_tree_cursor_changed(item_tree) + break + i += 1 + return False + + def findMenu(self, menus, path, iter, menu_id): + if not menus[path][2].get_desktop_file_path(): + if menu_id == menus[path][2].get_menu_id(): + menu_tree = self.tree.get_object('menu_tree') + menu_tree.expand_to_path(path) + menu_tree.get_selection().select_path(path) + return True + return False + if os.path.split(menus[path][2].get_desktop_file_path())[1] == menu_id: + menu_tree = self.tree.get_object('menu_tree') + menu_tree.expand_to_path(path) + menu_tree.get_selection().select_path(path) + return True + + def setupMenuTree(self): + self.menu_store = Gtk.TreeStore(GdkPixbuf.Pixbuf, str, object) + menus = self.tree.get_object('menu_tree') + column = Gtk.TreeViewColumn(_('Name')) + column.set_spacing(4) + cell = Gtk.CellRendererPixbuf() + column.pack_start(cell, False) + column.add_attribute(cell, 'pixbuf', 0) + cell = Gtk.CellRendererText() + column.pack_start(cell, True) + column.add_attribute(cell, 'markup', 1) + menus.append_column(column) + menus.get_selection().set_mode(Gtk.SelectionMode.BROWSE) + + def setupItemTree(self): + items = self.tree.get_object('item_tree') + column = Gtk.TreeViewColumn(_('Show')) + cell = Gtk.CellRendererToggle() + cell.connect('toggled', self.on_item_tree_show_toggled) + column.pack_start(cell, True) + column.add_attribute(cell, 'active', 0) + #hide toggle for separators + column.set_cell_data_func(cell, self._cell_data_toggle_func) + items.append_column(column) + column = Gtk.TreeViewColumn(_('Item')) + column.set_spacing(4) + cell = Gtk.CellRendererPixbuf() + column.pack_start(cell, False) + column.add_attribute(cell, 'pixbuf', 1) + cell = Gtk.CellRendererText() + column.pack_start(cell, True) + column.add_attribute(cell, 'markup', 2) + items.append_column(column) + self.item_store = Gtk.ListStore(bool, GdkPixbuf.Pixbuf, str, object) + items.set_model(self.item_store) + + def _cell_data_toggle_func(self, tree_column, renderer, model, treeiter, data=None): + if isinstance(model[treeiter][3], GMenu.TreeSeparator): + renderer.set_property('visible', False) + else: + renderer.set_property('visible', True) + + def loadMenus(self): + self.menu_store.clear() + self.loadMenu({ None: None }) + + menu_tree = self.tree.get_object('menu_tree') + menu_tree.set_model(self.menu_store) + for menu in self.menu_store: + menu_tree.expand_to_path(menu.path) + menu_tree.get_selection().select_path((0,)) + self.on_menu_tree_cursor_changed(menu_tree) + + def loadMenu(self, iters, parent=None): + for menu, show in self.editor.getMenus(parent): + name = cgi.escape(menu.get_name()) + if not show: + name = "%s" % (name,) + + icon = util.getIcon(menu) + iters[menu] = self.menu_store.append(iters[parent], (icon, name, menu)) + self.loadMenu(iters, menu) + + def loadItems(self, menu): + self.item_store.clear() + for item, show in self.editor.getItems(menu): + icon = util.getIcon(item) + if isinstance(item, GMenu.TreeDirectory): + name = item.get_name() + elif isinstance(item, GMenu.TreeEntry): + name = item.get_app_info().get_display_name() + elif isinstance(item, GMenu.TreeSeparator): + name = '---' + else: + assert False, 'should not be reached' + + name = cgi.escape(name) + if not show: + name = "%s" % (name,) + + self.item_store.append((show, icon, name, item)) + + #this is a little timeout callback to insert new items after + #gnome-desktop-item-edit has finished running + def waitForNewItemProcess(self, process, parent_id, file_path): + if process.poll() is not None: + if os.path.isfile(file_path): + self.editor.insertExternalItem(os.path.split(file_path)[1], parent_id) + return False + return True + + def waitForNewMenuProcess(self, process, parent_id, file_path): + if process.poll() is not None: + if os.path.isfile(file_path): + self.editor.insertExternalMenu(os.path.split(file_path)[1], parent_id) + 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() + if not iter: + parent = menus[(0,)][2] + menu_tree.expand_to_path((0,)) + menu_tree.get_selection().select_path((0,)) + else: + parent = menus[iter][2] + 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) + + def on_new_item_button_clicked(self, button): + menu_tree = self.tree.get_object('menu_tree') + menus, iter = menu_tree.get_selection().get_selected() + if not iter: + parent = menus[(0,)][2] + menu_tree.expand_to_path((0,)) + menu_tree.get_selection().select_path((0,)) + else: + parent = menus[iter][2] + 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) + + def on_new_separator_button_clicked(self, button): + item_tree = self.tree.get_object('item_tree') + items, iter = item_tree.get_selection().get_selected() + if not iter: + return + else: + after = items[iter][3] + menu_tree = self.tree.get_object('menu_tree') + menus, iter = menu_tree.get_selection().get_selected() + parent = menus[iter][2] + self.editor.createSeparator(parent, after=after) + + def on_edit_delete_activate(self, menu): + item_tree = self.tree.get_object('item_tree') + items, iter = item_tree.get_selection().get_selected() + if not iter: + return + item = items[iter][3] + if isinstance(item, GMenu.TreeEntry): + self.editor.deleteItem(item) + elif isinstance(item, GMenu.TreeDirectory): + self.editor.deleteMenu(item) + elif isinstance(item, GMenu.TreeSeparator): + self.editor.deleteSeparator(item) + + def on_edit_properties_activate(self, menu): + item_tree = self.tree.get_object('item_tree') + items, iter = item_tree.get_selection().get_selected() + if not iter: + return + item = items[iter][3] + if not isinstance(item, GMenu.TreeEntry) and not isinstance(item, GMenu.TreeDirectory): + return + + if isinstance(item, GMenu.TreeEntry): + file_path = os.path.join(util.getUserItemPath(), item.get_desktop_file_id()) + file_type = 'Item' + elif isinstance(item, GMenu.TreeDirectory): + file_path = os.path.join(util.getUserDirectoryPath(), os.path.split(item.get_desktop_file_path())[1]) + file_type = 'Menu' + + if not os.path.isfile(file_path): + data = open(item.get_desktop_file_path()).read() + open(file_path, 'w').write(data) + + 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) + + def on_menu_tree_cursor_changed(self, treeview): + selection = treeview.get_selection() + if selection is None: + return + menus, iter = selection.get_selected() + if iter is None: + return + menu_path = menus.get_path(iter) + item_tree = self.tree.get_object('item_tree') + item_tree.get_selection().unselect_all() + self.loadItems(self.menu_store[menu_path][2]) + self.tree.get_object('edit_delete').set_sensitive(False) + self.tree.get_object('edit_properties').set_sensitive(False) + self.tree.get_object('move_up_button').set_sensitive(False) + self.tree.get_object('move_down_button').set_sensitive(False) + self.tree.get_object('new_separator_button').set_sensitive(False) + self.tree.get_object('properties_button').set_sensitive(False) + self.tree.get_object('delete_button').set_sensitive(False) + + def on_item_tree_show_toggled(self, cell, path): + item = self.item_store[path][3] + if isinstance(item, GMenu.TreeSeparator): + return + if self.item_store[path][0]: + self.editor.setVisible(item, False) + else: + self.editor.setVisible(item, True) + self.item_store[path][0] = not self.item_store[path][0] + + def on_item_tree_cursor_changed(self, treeview): + selection = treeview.get_selection() + if selection is None: + return + items, iter = selection.get_selected() + if iter is None: + return + + item = items[iter][3] + self.tree.get_object('edit_delete').set_sensitive(True) + self.tree.get_object('new_separator_button').set_sensitive(True) + self.tree.get_object('delete_button').set_sensitive(True) + + can_edit = not isinstance(item, GMenu.TreeSeparator) + self.tree.get_object('edit_properties').set_sensitive(can_edit) + self.tree.get_object('properties_button').set_sensitive(can_edit) + + index = items.get_path(iter).get_indices()[0] + can_go_up = index > 0 + can_go_down = index < len(items) - 1 + self.tree.get_object('move_up_button').set_sensitive(can_go_up) + self.tree.get_object('move_down_button').set_sensitive(can_go_down) + + def on_item_tree_row_activated(self, treeview, path, column): + self.on_edit_properties_activate(None) + + def on_item_tree_popup_menu(self, item_tree, event=None): + model, iter = item_tree.get_selection().get_selected() + if event: + #don't show if it's not the right mouse button + if event.button != 3: + return + button = event.button + event_time = event.time + info = item_tree.get_path_at_pos(int(event.x), int(event.y)) + if info is not None: + path, col, cellx, celly = info + item_tree.grab_focus() + item_tree.set_cursor(path, col, 0) + else: + path = model.get_path(iter) + button = 0 + event_time = 0 + item_tree.grab_focus() + item_tree.set_cursor(path, item_tree.get_columns()[0], 0) + popup = self.tree.get_object('edit_menu') + popup.popup(None, None, None, None, button, event_time) + #without this shift-f10 won't work + return True + + def on_item_tree_key_press_event(self, item_tree, event): + if event.keyval == Gdk.KEY_Delete: + self.on_edit_delete_activate(item_tree) + + def on_move_up_button_clicked(self, button): + item_tree = self.tree.get_object('item_tree') + items, iter = item_tree.get_selection().get_selected() + if not iter: + return + path = items.get_path(iter) + #at top, can't move up + if path.get_indices()[0] == 0: + return + item = items[path][3] + before = items[(path.get_indices()[0] - 1,)][3] + self.editor.moveItem(item.get_parent(), item, before=before) + + def on_move_down_button_clicked(self, button): + item_tree = self.tree.get_object('item_tree') + items, iter = item_tree.get_selection().get_selected() + if not iter: + return + path = items.get_path(iter) + #at bottom, can't move down + if path.get_indices()[0] == (len(items) - 1): + return + item = items[path][3] + after = items[path][3] + self.editor.moveItem(item.get_parent(), item, after=after) + + def on_help_button_clicked(self, *args): + Gtk.show_uri(Gdk.Screen.get_default(), "ghelp:user-guide#menu-editor", Gtk.get_current_event_time()) + + def on_restore_button_clicked(self, button): + self.editor.restoreToSystem() + + def on_close_button_clicked(self, button): + self.quit() + + def on_properties_button_clicked(self, button): + self.on_edit_properties_activate(None) + def on_delete_button_clicked(self, button): + self.on_edit_delete_activate(None) + + def quit(self): + Gtk.main_quit() --- a/files/usr/lib/cinnamon-menu-editor/Alacarte/MenuEditor.py +++ b/files/usr/lib/cinnamon-menu-editor/Alacarte/MenuEditor.py @@ -16,731 +16,555 @@ # 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 os, re, xml.dom.minidom, locale -import gmenu +import os +import xml.dom.minidom +import xml.parsers.expat +from gi.repository import GMenu, GLib from Alacarte import util -class Menu: - tree = None - visible_tree = None - path = None - dom = None - -class MenuEditor: - #lists for undo/redo functionality - __undo = [] - __redo = [] - - def __init__(self): - self.locale = locale.getdefaultlocale()[0] - self.__loadMenus() - - def __loadMenus(self): - self.applications = Menu() - self.applications.tree = gmenu.lookup_tree('cinnamon-applications.menu', gmenu.FLAGS_SHOW_EMPTY|gmenu.FLAGS_INCLUDE_EXCLUDED|gmenu.FLAGS_INCLUDE_NODISPLAY|gmenu.FLAGS_SHOW_ALL_SEPARATORS) - self.applications.visible_tree = gmenu.lookup_tree('cinnamon-applications.menu') - self.applications.tree.sort_key = gmenu.SORT_DISPLAY_NAME - self.applications.visible_tree.sort_key = gmenu.SORT_DISPLAY_NAME - self.applications.path = os.path.join(util.getUserMenuPath(), self.applications.tree.get_menu_file()) - if not os.path.isfile(self.applications.path): - self.applications.dom = xml.dom.minidom.parseString(util.getUserMenuXml(self.applications.tree)) - else: - self.applications.dom = xml.dom.minidom.parse(self.applications.path) - self.__remove_whilespace_nodes(self.applications.dom) - - self.save(True) - - def save(self, from_loading=False): - for menu in ('applications',): - fd = open(getattr(self, menu).path, 'w') - fd.write(re.sub("\n[\s]*([^\n<]*)\n[\s]*\n', ''))) - fd.close() - if not from_loading: - self.__loadMenus() - - def quit(self): - for file_name in os.listdir(util.getUserItemPath()): - if file_name[-6:-2] in ('redo', 'undo'): - file_path = os.path.join(util.getUserItemPath(), file_name) - os.unlink(file_path) - for file_name in os.listdir(util.getUserDirectoryPath()): - if file_name[-6:-2] in ('redo', 'undo'): - file_path = os.path.join(util.getUserDirectoryPath(), file_name) - os.unlink(file_path) - for file_name in os.listdir(util.getUserMenuPath()): - if file_name[-6:-2] in ('redo', 'undo'): - file_path = os.path.join(util.getUserMenuPath(), file_name) - os.unlink(file_path) - - def revert(self): - for name in ('applications',): - menu = getattr(self, name) - self.revertTree(menu.tree.root) - path = os.path.join(util.getUserMenuPath(), menu.tree.get_menu_file()) - try: - os.unlink(path) - except OSError: - pass - #reload DOM for each menu - if not os.path.isfile(menu.path): - menu.dom = xml.dom.minidom.parseString(util.getUserMenuXml(menu.tree)) - else: - menu.dom = xml.dom.minidom.parse(menu.path) - self.__remove_whilespace_nodes(menu.dom) - #reset undo/redo, no way to recover from this - self.__undo, self.__redo = [], [] - self.save() - - def revertTree(self, menu): - for child in menu.get_contents(): - if child.get_type() == gmenu.TYPE_DIRECTORY: - self.revertTree(child) - elif child.get_type() == gmenu.TYPE_ENTRY: - self.revertItem(child) - self.revertMenu(menu) - - def undo(self): - if len(self.__undo) == 0: - return - files = self.__undo.pop() - redo = [] - for file_path in files: - new_path = file_path.rsplit('.', 1)[0] - redo_path = util.getUniqueRedoFile(new_path) - data = open(new_path).read() - open(redo_path, 'w').write(data) - data = open(file_path).read() - open(new_path, 'w').write(data) - os.unlink(file_path) - redo.append(redo_path) - #reload DOM to make changes stick - for name in ('applications',): - menu = getattr(self, name) - if not os.path.isfile(menu.path): - menu.dom = xml.dom.minidom.parseString(util.getUserMenuXml(menu.tree)) - else: - menu.dom = xml.dom.minidom.parse(menu.path) - self.__remove_whilespace_nodes(menu.dom) - self.__redo.append(redo) - - def redo(self): - if len(self.__redo) == 0: - return - files = self.__redo.pop() - undo = [] - for file_path in files: - new_path = file_path.rsplit('.', 1)[0] - undo_path = util.getUniqueUndoFile(new_path) - data = open(new_path).read() - open(undo_path, 'w').write(data) - data = open(file_path).read() - open(new_path, 'w').write(data) - os.unlink(file_path) - undo.append(undo_path) - #reload DOM to make changes stick - for name in ('applications',): - menu = getattr(self, name) - if not os.path.isfile(menu.path): - menu.dom = xml.dom.minidom.parseString(util.getUserMenuXml(menu.tree)) - else: - menu.dom = xml.dom.minidom.parse(menu.path) - self.__remove_whilespace_nodes(menu.dom) - self.__undo.append(undo) - - def getMenus(self, parent=None): - if parent == None: - yield self.applications.tree.root - else: - for menu in parent.get_contents(): - if menu.get_type() == gmenu.TYPE_DIRECTORY: - yield (menu, self.__isVisible(menu)) - - def getItems(self, menu): - for item in menu.get_contents(): - if item.get_type() == gmenu.TYPE_SEPARATOR: - yield (item, True) - else: - if item.get_type() == gmenu.TYPE_ENTRY and item.get_desktop_file_id()[-19:] == '-usercustom.desktop': - continue - yield (item, self.__isVisible(item)) - - def canRevert(self, item): - if item.get_type() == gmenu.TYPE_ENTRY: - if util.getItemPath(item.get_desktop_file_id()): - path = util.getUserItemPath() - if os.path.isfile(os.path.join(path, item.get_desktop_file_id())): - return True - elif item.get_type() == gmenu.TYPE_DIRECTORY: - if item.get_desktop_file_path(): - file_id = os.path.split(item.get_desktop_file_path())[1] - else: - file_id = item.get_menu_id() + '.directory' - if util.getDirectoryPath(file_id): - path = util.getUserDirectoryPath() - if os.path.isfile(os.path.join(path, file_id)): - return True - return False - - def setVisible(self, item, visible): - dom = self.__getMenu(item).dom - if item.get_type() == gmenu.TYPE_ENTRY: - self.__addUndo([self.__getMenu(item), item]) - menu_xml = self.__getXmlMenu(self.__getPath(item.get_parent()), dom, dom) - if visible: - self.__addXmlFilename(menu_xml, dom, item.get_desktop_file_id(), 'Include') - self.__writeItem(item, no_display=False) - else: - self.__addXmlFilename(menu_xml, dom, item.get_desktop_file_id(), 'Exclude') - self.__addXmlTextElement(menu_xml, 'AppDir', util.getUserItemPath(), dom) - elif item.get_type() == gmenu.TYPE_DIRECTORY: - self.__addUndo([self.__getMenu(item), item]) - #don't mess with it if it's empty - if len(item.get_contents()) == 0: - return - menu_xml = self.__getXmlMenu(self.__getPath(item), dom, dom) - for node in self.__getXmlNodesByName(['Deleted', 'NotDeleted'], menu_xml): - node.parentNode.removeChild(node) - if visible: - self.__writeMenu(item, no_display=False) - else: - self.__writeMenu(item, no_display=True) - self.__addXmlTextElement(menu_xml, 'DirectoryDir', util.getUserDirectoryPath(), dom) - self.save() - - def createItem(self, parent, icon, name, comment, command, use_term, before=None, after=None): - file_id = self.__writeItem(None, icon, name, comment, command, use_term) - self.insertExternalItem(file_id, parent, before, after) - - def insertExternalItem(self, file_id, parent, before=None, after=None): - dom = self.__getMenu(parent).dom - self.__addItem(parent, file_id, dom) - self.__positionItem(parent, ('Item', file_id), before, after) - self.__addUndo([self.__getMenu(parent), ('Item', file_id)]) - self.save() - - def createMenu(self, parent, icon, name, comment, before=None, after=None): - file_id = self.__writeMenu(None, icon, name, comment) - self.insertExternalMenu(file_id, parent.menu_id, before, after) - - def insertExternalMenu(self, file_id, parent_id, before=None, after=None): - menu_id = file_id.rsplit('.', 1)[0] - parent = self.__findMenu(parent_id) - dom = self.__getMenu(parent).dom - self.__addXmlDefaultLayout(self.__getXmlMenu(self.__getPath(parent), dom, dom) , dom) - menu_xml = self.__getXmlMenu(self.__getPath(parent) + '/' + menu_id, dom, dom) - self.__addXmlTextElement(menu_xml, 'Directory', file_id, dom) - self.__positionItem(parent, ('Menu', menu_id), before, after) - self.__addUndo([self.__getMenu(parent), ('Menu', file_id)]) - self.save() - - def createSeparator(self, parent, before=None, after=None): - self.__positionItem(parent, ('Separator',), before, after) - self.__addUndo([self.__getMenu(parent), ('Separator',)]) - self.save() - - def editItem(self, item, icon, name, comment, command, use_term, parent=None, final=True): - #if nothing changed don't make a user copy - if icon == item.get_icon() and name == item.get_display_name() and comment == item.get_comment() and command == item.get_exec() and use_term == item.get_launch_in_terminal(): - return - #hack, item.get_parent() seems to fail a lot - if not parent: - parent = item.get_parent() - if final: - self.__addUndo([self.__getMenu(parent), item]) - self.__writeItem(item, icon, name, comment, command, use_term) - if final: - dom = self.__getMenu(parent).dom - menu_xml = self.__getXmlMenu(self.__getPath(parent), dom, dom) - self.__addXmlTextElement(menu_xml, 'AppDir', util.getUserItemPath(), dom) - self.save() - - def editMenu(self, menu, icon, name, comment, final=True): - #if nothing changed don't make a user copy - if icon == menu.get_icon() and name == menu.get_name() and comment == menu.get_comment(): - return - #we don't use this, we just need to make sure the exists - #otherwise changes won't show up - dom = self.__getMenu(menu).dom - menu_xml = self.__getXmlMenu(self.__getPath(menu), dom, dom) - file_id = self.__writeMenu(menu, icon, name, comment) - if final: - self.__addXmlTextElement(menu_xml, 'DirectoryDir', util.getUserDirectoryPath(), dom) - self.__addUndo([self.__getMenu(menu), menu]) - self.save() - - def copyItem(self, item, new_parent, before=None, after=None): - dom = self.__getMenu(new_parent).dom - file_path = item.get_desktop_file_path() - keyfile = util.DesktopParser(file_path) - #erase Categories in new file - keyfile.set('Categories', ('',)) - keyfile.set('Hidden', False) - file_id = util.getUniqueFileId(item.get_name(), '.desktop') - out_path = os.path.join(util.getUserItemPath(), file_id) - keyfile.write(open(out_path, 'w')) - self.__addItem(new_parent, file_id, dom) - self.__positionItem(new_parent, ('Item', file_id), before, after) - self.__addUndo([self.__getMenu(new_parent), ('Item', file_id)]) - self.save() - return file_id - - def moveItem(self, item, new_parent, before=None, after=None): - undo = [] - if item.get_parent() != new_parent: - #hide old item - self.deleteItem(item) - undo.append(item) - file_id = self.copyItem(item, new_parent) - item = ('Item', file_id) - undo.append(item) - self.__positionItem(new_parent, item, before, after) - undo.append(self.__getMenu(new_parent)) - self.__addUndo(undo) - self.save() - - def moveMenu(self, menu, new_parent, before=None, after=None): - parent = new_parent - #don't move a menu into it's child - while parent.get_parent(): - parent = parent.get_parent() - if parent == menu: - return False - - #don't move a menu into itself - if new_parent == menu: - return False - - #can't move between top-level menus - if self.__getMenu(menu) != self.__getMenu(new_parent): - return False - if menu.get_parent() != new_parent: - dom = self.__getMenu(menu).dom - root_path = self.__getPath(menu).split('/', 1)[0] - xml_root = self.__getXmlMenu(root_path, dom, dom) - old_path = self.__getPath(menu).split('/', 1)[1] - #root menu's path has no / - if '/' in self.__getPath(new_parent): - new_path = self.__getPath(new_parent).split('/', 1)[1] + '/' + menu.get_menu_id() - else: - new_path = menu.get_menu_id() - self.__addXmlMove(xml_root, old_path, new_path, dom) - self.__positionItem(new_parent, menu, before, after) - self.__addUndo([self.__getMenu(new_parent),]) - self.save() - - def moveSeparator(self, separator, new_parent, before=None, after=None): - self.__positionItem(new_parent, separator, before, after) - self.__addUndo([self.__getMenu(new_parent),]) - self.save() - - def deleteItem(self, item): - self.__addUndo([item,]) - self.__writeItem(item, hidden=True) - self.save() - - def deleteMenu(self, menu): - dom = self.__getMenu(menu).dom - menu_xml = self.__getXmlMenu(self.__getPath(menu), dom, dom) - self.__addDeleted(menu_xml, dom) - self.__addUndo([self.__getMenu(menu),]) - self.save() - - def deleteSeparator(self, item): - parent = item.get_parent() - contents = parent.get_contents() - contents.remove(item) - layout = self.__createLayout(contents) - dom = self.__getMenu(parent).dom - menu_xml = self.__getXmlMenu(self.__getPath(parent), dom, dom) - self.__addXmlLayout(menu_xml, layout, dom) - self.__addUndo([self.__getMenu(item.get_parent()),]) - self.save() - - def revertItem(self, item): - if not self.canRevert(item): - return - self.__addUndo([item,]) - try: - os.remove(item.get_desktop_file_path()) - except OSError: - pass - self.save() - - def revertMenu(self, menu): - if not self.canRevert(menu): - return - #wtf happened here? oh well, just bail - if not menu.get_desktop_file_path(): - return - self.__addUndo([menu,]) - file_id = os.path.split(menu.get_desktop_file_path())[1] - path = os.path.join(util.getUserDirectoryPath(), file_id) - try: - os.remove(path) - except OSError: - pass - self.save() - - #private stuff - def __addUndo(self, items): - self.__undo.append([]) - for item in items: - if isinstance(item, Menu): - file_path = item.path - elif isinstance(item, tuple): - if item[0] == 'Item': - file_path = os.path.join(util.getUserItemPath(), item[1]) - if not os.path.isfile(file_path): - file_path = util.getItemPath(item[1]) - elif item[0] == 'Menu': - file_path = os.path.join(util.getUserDirectoryPath(), item[1]) - if not os.path.isfile(file_path): - file_path = util.getDirectoryPath(item[1]) - else: - continue - elif item.get_type() == gmenu.TYPE_DIRECTORY: - if item.get_desktop_file_path() == None: - continue - file_path = os.path.join(util.getUserDirectoryPath(), os.path.split(item.get_desktop_file_path())[1]) - if not os.path.isfile(file_path): - file_path = item.get_desktop_file_path() - elif item.get_type() == gmenu.TYPE_ENTRY: - file_path = os.path.join(util.getUserItemPath(), item.get_desktop_file_id()) - if not os.path.isfile(file_path): - file_path = item.get_desktop_file_path() - else: - continue - data = open(file_path).read() - undo_path = util.getUniqueUndoFile(file_path) - open(undo_path, 'w').write(data) - self.__undo[-1].append(undo_path) - - def __getMenu(self, item): - return self.applications - - def __findMenu(self, menu_id, parent=None): - if parent == None: - return self.__findMenu(menu_id, self.applications.tree.root) - if menu_id == self.applications.tree.root.menu_id: - return self.applications.tree.root - for item in parent.get_contents(): - if item.get_type() == gmenu.TYPE_DIRECTORY: - if item.menu_id == menu_id: - return item - menu = self.__findMenu(menu_id, item) - if menu != None: - return menu - - def __isVisible(self, item): - if item.get_type() == gmenu.TYPE_ENTRY: - return not (item.get_is_excluded() or item.get_is_nodisplay()) - menu = self.__getMenu(item) - if menu == self.applications: - root = self.applications.visible_tree.root - if item.get_type() == gmenu.TYPE_DIRECTORY: - if self.__findMenu(item.menu_id, root) == None: - return False - return True - - def __getPath(self, menu, path=None): - if not path: - path = menu.tree.root.get_menu_id() - if menu.get_parent(): - path = self.__getPath(menu.get_parent(), path) - path += '/' - path += menu.menu_id - return path - - def __getXmlMenu(self, path, element, dom): - if '/' in path: - (name, path) = path.split('/', 1) - else: - name = path - path = '' - - found = None - for node in self.__getXmlNodesByName('Menu', element): - for child in self.__getXmlNodesByName('Name', node): - if child.childNodes[0].nodeValue == name: - if path: - found = self.__getXmlMenu(path, node, dom) - else: - found = node - break - if found: - break - if not found: - node = self.__addXmlMenuElement(element, name, dom) - if path: - found = self.__getXmlMenu(path, node, dom) - else: - found = node - - return found - - def __addXmlMenuElement(self, element, name, dom): - node = dom.createElement('Menu') - self.__addXmlTextElement(node, 'Name', name, dom) - return element.appendChild(node) - - def __addXmlTextElement(self, element, name, text, dom): - for temp in element.childNodes: - if temp.nodeName == name: - if temp.childNodes[0].nodeValue == text: - return - node = dom.createElement(name) - text = dom.createTextNode(text) - node.appendChild(text) - return element.appendChild(node) - - def __addXmlFilename(self, element, dom, filename, type = 'Include'): - # remove old filenames - for node in self.__getXmlNodesByName(['Include', 'Exclude'], element): - if node.childNodes[0].nodeName == 'Filename' and node.childNodes[0].childNodes[0].nodeValue == filename: - element.removeChild(node) - - # add new filename - node = dom.createElement(type) - node.appendChild(self.__addXmlTextElement(node, 'Filename', filename, dom)) - return element.appendChild(node) - - def __addDeleted(self, element, dom): - node = dom.createElement('Deleted') - return element.appendChild(node) - - def __writeItem(self, item=None, icon=None, name=None, comment=None, command=None, use_term=None, no_display=None, startup_notify=None, hidden=None): - if item: - file_path = item.get_desktop_file_path() - file_id = item.get_desktop_file_id() - keyfile = util.DesktopParser(file_path) - elif item == None and name == None: - raise Exception('New menu items need a name') - else: - file_id = util.getUniqueFileId(name, '.desktop') - keyfile = util.DesktopParser() - if icon: - keyfile.set('Icon', icon) - keyfile.set('Icon', icon, self.locale) - if name: - keyfile.set('Name', name) - keyfile.set('Name', name, self.locale) - if comment: - keyfile.set('Comment', comment) - keyfile.set('Comment', comment, self.locale) - if command: - keyfile.set('Exec', command) - if use_term != None: - keyfile.set('Terminal', use_term) - if no_display != None: - keyfile.set('NoDisplay', no_display) - if startup_notify != None: - keyfile.set('StartupNotify', startup_notify) - if hidden != None: - keyfile.set('Hidden', hidden) - out_path = os.path.join(util.getUserItemPath(), file_id) - keyfile.write(open(out_path, 'w')) - return file_id - - def __writeMenu(self, menu=None, icon=None, name=None, comment=None, no_display=None): - if menu: - file_id = os.path.split(menu.get_desktop_file_path())[1] - file_path = menu.get_desktop_file_path() - keyfile = util.DesktopParser(file_path) - elif menu == None and name == None: - raise Exception('New menus need a name') - else: - file_id = util.getUniqueFileId(name, '.directory') - keyfile = util.DesktopParser(file_type='Directory') - if icon: - keyfile.set('Icon', icon) - if name: - keyfile.set('Name', name) - keyfile.set('Name', name, self.locale) - if comment: - keyfile.set('Comment', comment) - keyfile.set('Comment', comment, self.locale) - if no_display != None: - keyfile.set('NoDisplay', no_display) - out_path = os.path.join(util.getUserDirectoryPath(), file_id) - keyfile.write(open(out_path, 'w')) - return file_id - - def __getXmlNodesByName(self, name, element): - for child in element.childNodes: - if child.nodeType == xml.dom.Node.ELEMENT_NODE: - if isinstance(name, str) and child.nodeName == name: - yield child - elif isinstance(name, list) or isinstance(name, tuple): - if child.nodeName in name: - yield child - - def __remove_whilespace_nodes(self, node): - remove_list = [] - for child in node.childNodes: - if child.nodeType == xml.dom.minidom.Node.TEXT_NODE: - child.data = child.data.strip() - if not child.data.strip(): - remove_list.append(child) - elif child.hasChildNodes(): - self.__remove_whilespace_nodes(child) - for node in remove_list: - node.parentNode.removeChild(node) - - def __addXmlMove(self, element, old, new, dom): - if not self.__undoMoves(element, old, new, dom): - node = dom.createElement('Move') - node.appendChild(self.__addXmlTextElement(node, 'Old', old, dom)) - node.appendChild(self.__addXmlTextElement(node, 'New', new, dom)) - #are parsed in reverse order, need to put at the beginning - return element.insertBefore(node, element.firstChild) - - def __addXmlLayout(self, element, layout, dom): - # remove old layout - for node in self.__getXmlNodesByName('Layout', element): - element.removeChild(node) - - # add new layout - node = dom.createElement('Layout') - for order in layout.order: - if order[0] == 'Separator': - child = dom.createElement('Separator') - node.appendChild(child) - elif order[0] == 'Filename': - child = self.__addXmlTextElement(node, 'Filename', order[1], dom) - elif order[0] == 'Menuname': - child = self.__addXmlTextElement(node, 'Menuname', order[1], dom) - elif order[0] == 'Merge': - child = dom.createElement('Merge') - child.setAttribute('type', order[1]) - node.appendChild(child) - return element.appendChild(node) - - def __addXmlDefaultLayout(self, element, dom): - # remove old default layout - for node in self.__getXmlNodesByName('DefaultLayout', element): - element.removeChild(node) - - # add new layout - node = dom.createElement('DefaultLayout') - node.setAttribute('inline', 'false') - return element.appendChild(node) - - def __createLayout(self, items): - layout = Layout() - layout.order = [] - - layout.order.append(['Merge', 'menus']) - for item in items: - if isinstance(item, tuple): - if item[0] == 'Separator': - layout.parseSeparator() - elif item[0] == 'Menu': - layout.parseMenuname(item[1]) - elif item[0] == 'Item': - layout.parseFilename(item[1]) - elif item.get_type() == gmenu.TYPE_DIRECTORY: - layout.parseMenuname(item.get_menu_id()) - elif item.get_type() == gmenu.TYPE_ENTRY: - layout.parseFilename(item.get_desktop_file_id()) - elif item.get_type() == gmenu.TYPE_SEPARATOR: - layout.parseSeparator() - layout.order.append(['Merge', 'files']) - return layout - - def __addItem(self, parent, file_id, dom): - xml_parent = self.__getXmlMenu(self.__getPath(parent), dom, dom) - self.__addXmlFilename(xml_parent, dom, file_id, 'Include') - - def __deleteItem(self, parent, file_id, dom, before=None, after=None): - xml_parent = self.__getXmlMenu(self.__getPath(parent), dom, dom) - self.__addXmlFilename(xml_parent, dom, file_id, 'Exclude') - - def __positionItem(self, parent, item, before=None, after=None): - if not before and not after: - return - current = parent.contents.index(item) - if after: - index = parent.contents.index(after) - if current > index: - index += 1 - elif before: - index = parent.contents.index(before) - if current < index: - index -= 1 - contents = parent.contents - #if this is a move to a new parent you can't remove the item - try: - contents.remove(item) - except: - pass - contents.insert(index, item) - layout = self.__createLayout(contents) - dom = self.__getMenu(parent).dom - menu_xml = self.__getXmlMenu(self.__getPath(parent), dom, dom) - self.__addXmlLayout(menu_xml, layout, dom) - - def __undoMoves(self, element, old, new, dom): - nodes = [] - matches = [] - original_old = old - final_old = old - #get all elements - for node in self.__getXmlNodesByName(['Move'], element): - nodes.insert(0, node) - #if the matches our old parent we've found a stage to undo - for node in nodes: - xml_old = node.getElementsByTagName('Old')[0] - xml_new = node.getElementsByTagName('New')[0] - if xml_new.childNodes[0].nodeValue == old: - matches.append(node) - #we should end up with this path when completed - final_old = xml_old.childNodes[0].nodeValue - #undoing s - for node in matches: - element.removeChild(node) - if len(matches) > 0: - for node in nodes: - xml_old = node.getElementsByTagName('Old')[0] - xml_new = node.getElementsByTagName('New')[0] - path = os.path.split(xml_new.childNodes[0].nodeValue) - if path[0] == original_old: - element.removeChild(node) - for node in dom.getElementsByTagName('Menu'): - name_node = node.getElementsByTagName('Name')[0] - name = name_node.childNodes[0].nodeValue - if name == os.path.split(new)[1]: - #copy app and dir directory info from old - root_path = dom.getElementsByTagName('Menu')[0].getElementsByTagName('Name')[0].childNodes[0].nodeValue - xml_menu = self.__getXmlMenu(root_path + '/' + new, dom, dom) - for app_dir in node.getElementsByTagName('AppDir'): - xml_menu.appendChild(app_dir) - for dir_dir in node.getElementsByTagName('DirectoryDir'): - xml_menu.appendChild(dir_dir) - parent = node.parentNode - parent.removeChild(node) - node = dom.createElement('Move') - node.appendChild(self.__addXmlTextElement(node, 'Old', xml_old.childNodes[0].nodeValue, dom)) - node.appendChild(self.__addXmlTextElement(node, 'New', os.path.join(new, path[1]), dom)) - element.appendChild(node) - if final_old == new: - return True - node = dom.createElement('Move') - node.appendChild(self.__addXmlTextElement(node, 'Old', final_old, dom)) - node.appendChild(self.__addXmlTextElement(node, 'New', new, dom)) - return element.appendChild(node) - -class Layout: - def __init__(self, node=None): - self.order = [] - - def parseMenuname(self, value): - self.order.append(['Menuname', value]) - - def parseSeparator(self): - self.order.append(['Separator']) - - def parseFilename(self, value): - self.order.append(['Filename', value]) - - def parseMerge(self, merge_type='all'): - self.order.append(['Merge', merge_type]) +class MenuEditor(object): + def __init__(self, name='cinnamon-applications.menu'): + self.name = name + + self.tree = GMenu.Tree.new(name, GMenu.TreeFlags.SHOW_EMPTY|GMenu.TreeFlags.INCLUDE_EXCLUDED|GMenu.TreeFlags.INCLUDE_NODISPLAY|GMenu.TreeFlags.SHOW_ALL_SEPARATORS|GMenu.TreeFlags.SORT_DISPLAY_NAME) + self.tree.connect('changed', self.menuChanged) + self.load() + + self.path = os.path.join(util.getUserMenuPath(), self.tree.props.menu_basename) + self.loadDOM() + + def loadDOM(self): + try: + self.dom = xml.dom.minidom.parse(self.path) + except (IOError, xml.parsers.expat.ExpatError), e: + self.dom = xml.dom.minidom.parseString(util.getUserMenuXml(self.tree)) + util.removeWhitespaceNodes(self.dom) + + def load(self): + if not self.tree.load_sync(): + raise ValueError("can not load menu tree %r" % (self.name,)) + + def menuChanged(self, *a): + self.load() + + def save(self): + fd = open(self.path, 'w') + fd.write(self.dom.toprettyxml()) + fd.close() + + def restoreToSystem(self): + self.restoreTree(self.tree.get_root_directory()) + path = os.path.join(util.getUserMenuPath(), os.path.basename(self.tree.get_canonical_menu_path())) + try: + os.unlink(path) + except OSError: + pass + + self.loadDOM() + self.save() + + def restoreTree(self, menu): + item_iter = menu.iter() + item_type = item_iter.next() + while item_type != GMenu.TreeItemType.INVALID: + if item_type == GMenu.TreeItemType.DIRECTORY: + item = item_iter.get_directory() + self.restoreTree(item) + elif item_type == GMenu.TreeItemType.ENTRY: + item = item_iter.get_entry() + self.restoreItem(item) + item_type = item_iter.next() + self.restoreMenu(menu) + + def restoreItem(self, item): + if not self.canRevert(item): + return + try: + os.remove(item.get_desktop_file_path()) + except OSError: + pass + self.save() + + def restoreMenu(self, menu): + if not self.canRevert(menu): + return + #wtf happened here? oh well, just bail + if not menu.get_desktop_file_path(): + return + file_id = os.path.split(menu.get_desktop_file_path())[1] + path = os.path.join(util.getUserDirectoryPath(), file_id) + try: + os.remove(path) + except OSError: + pass + self.save() + + def getMenus(self, parent): + if parent is None: + yield (self.tree.get_root_directory(), True) + return + + item_iter = parent.iter() + item_type = item_iter.next() + while item_type != GMenu.TreeItemType.INVALID: + if item_type == GMenu.TreeItemType.DIRECTORY: + item = item_iter.get_directory() + yield (item, self.isVisible(item)) + item_type = item_iter.next() + + def getContents(self, item): + contents = [] + item_iter = item.iter() + item_type = item_iter.next() + + while item_type != GMenu.TreeItemType.INVALID: + item = None + if item_type == GMenu.TreeItemType.DIRECTORY: + item = item_iter.get_directory() + elif item_type == GMenu.TreeItemType.ENTRY: + item = item_iter.get_entry() + elif item_type == GMenu.TreeItemType.HEADER: + item = item_iter.get_header() + elif item_type == GMenu.TreeItemType.ALIAS: + item = item_iter.get_alias() + elif item_type == GMenu.TreeItemType.SEPARATOR: + item = item_iter.get_separator() + if item: + contents.append(item) + item_type = item_iter.next() + return contents + + def getItems(self, menu): + item_iter = menu.iter() + item_type = item_iter.next() + while item_type != GMenu.TreeItemType.INVALID: + item = None + if item_type == GMenu.TreeItemType.ENTRY: + item = item_iter.get_entry() + elif item_type == GMenu.TreeItemType.DIRECTORY: + item = item_iter.get_directory() + elif item_type == GMenu.TreeItemType.HEADER: + item = item_iter.get_header() + elif item_type == GMenu.TreeItemType.ALIAS: + item = item_iter.get_alias() + elif item_type == GMenu.TreeItemType.SEPARATOR: + item = item_iter.get_separator() + yield (item, self.isVisible(item)) + item_type = item_iter.next() + + def canRevert(self, item): + if isinstance(item, GMenu.TreeEntry): + if util.getItemPath(item.get_desktop_file_id()) is not None: + path = util.getUserItemPath() + if os.path.isfile(os.path.join(path, item.get_desktop_file_id())): + return True + elif isinstance(item, GMenu.TreeDirectory): + if item.get_desktop_file_path(): + file_id = os.path.split(item.get_desktop_file_path())[1] + else: + file_id = item.get_menu_id() + '.directory' + if util.getDirectoryPath(file_id) is not None: + path = util.getUserDirectoryPath() + if os.path.isfile(os.path.join(path, file_id)): + return True + return False + + def setVisible(self, item, visible): + dom = self.dom + if isinstance(item, GMenu.TreeEntry): + menu_xml = self.getXmlMenu(self.getPath(item.get_parent()), dom.documentElement, dom) + if visible: + self.addXmlFilename(menu_xml, dom, item.get_desktop_file_id(), 'Include') + self.writeItem(item, NoDisplay=False) + else: + self.addXmlFilename(menu_xml, dom, item.get_desktop_file_id(), 'Exclude') + self.addXmlTextElement(menu_xml, 'AppDir', util.getUserItemPath(), dom) + elif isinstance(item, GMenu.TreeDirectory): + item_iter = item.iter() + first_child_type = item_iter.next() + #don't mess with it if it's empty + if first_child_type == GMenu.TreeItemType.INVALID: + return + menu_xml = self.getXmlMenu(self.getPath(item), dom.documentElement, dom) + for node in self.getXmlNodesByName(['Deleted', 'NotDeleted'], menu_xml): + node.parentNode.removeChild(node) + self.writeMenu(item, NoDisplay=not visible) + self.addXmlTextElement(menu_xml, 'DirectoryDir', util.getUserDirectoryPath(), dom) + self.save() + + def createItem(self, parent, before, after, **kwargs): + file_id = self.writeItem(None, **kwargs) + self.insertExternalItem(file_id, parent.get_menu_id(), before, after) + + def insertExternalItem(self, file_id, parent_id, before=None, after=None): + parent = self.findMenu(parent_id) + dom = self.dom + self.addItem(parent, file_id, dom) + self.positionItem(parent, ('Item', file_id), before, after) + self.save() + + def insertExternalMenu(self, file_id, parent_id, before=None, after=None): + menu_id = file_id.rsplit('.', 1)[0] + parent = self.findMenu(parent_id) + dom = self.dom + self.addXmlDefaultLayout(self.getXmlMenu(self.getPath(parent), dom.documentElement, dom) , dom) + menu_xml = self.getXmlMenu(self.getPath(parent) + [menu_id], dom.documentElement, dom) + self.addXmlTextElement(menu_xml, 'Directory', file_id, dom) + self.positionItem(parent, ('Menu', menu_id), before, after) + self.save() + + def createSeparator(self, parent, before=None, after=None): + self.positionItem(parent, ('Separator',), before, after) + self.save() + + def editItem(self, item, icon, name, comment, command, use_term, parent=None, final=True): + #if nothing changed don't make a user copy + app_info = item.get_app_info() + if icon == app_info.get_icon() and name == app_info.get_display_name() and comment == item.get_comment() and command == item.get_exec() and use_term == item.get_launch_in_terminal(): + return + #hack, item.get_parent() seems to fail a lot + if not parent: + parent = item.get_parent() + self.writeItem(item, Icon=icon, Name=name, Comment=comment, Exec=command, Terminal=use_term) + if final: + dom = self.dom + menu_xml = self.getXmlMenu(self.getPath(parent), dom.documentElement, dom) + self.addXmlTextElement(menu_xml, 'AppDir', util.getUserItemPath(), dom) + self.save() + + def editMenu(self, menu, icon, name, comment, final=True): + #if nothing changed don't make a user copy + if icon == menu.get_icon() and name == menu.get_name() and comment == menu.get_comment(): + return + #we don't use this, we just need to make sure the exists + #otherwise changes won't show up + dom = self.dom + menu_xml = self.getXmlMenu(self.getPath(menu), dom.documentElement, dom) + self.writeMenu(menu, Icon=icon, Name=name, Comment=comment) + if final: + self.addXmlTextElement(menu_xml, 'DirectoryDir', util.getUserDirectoryPath(), dom) + self.save() + + def copyItem(self, item, new_parent, before=None, after=None): + dom = self.dom + file_path = item.get_desktop_file_path() + keyfile = GLib.KeyFile() + keyfile.load_from_file(file_path, util.KEY_FILE_FLAGS) + + util.fillKeyFile(keyfile, dict(Categories=[], Hidden=False)) + + app_info = item.get_app_info() + file_id = util.getUniqueFileId(app_info.get_name().replace(os.sep, '-'), '.desktop') + out_path = os.path.join(util.getUserItemPath(), file_id) + + contents, length = keyfile.to_data() + + f = open(out_path, 'w') + f.write(contents) + f.close() + + self.addItem(new_parent, file_id, dom) + self.positionItem(new_parent, ('Item', file_id), before, after) + self.save() + return file_id + + def deleteItem(self, item): + self.writeItem(item, Hidden=True) + self.save() + + def deleteMenu(self, menu): + dom = self.dom + menu_xml = self.getXmlMenu(self.getPath(menu), dom.documentElement, dom) + self.addDeleted(menu_xml, dom) + self.save() + + def deleteSeparator(self, item): + parent = item.get_parent() + contents = self.getContents(parent) + contents.remove(item) + layout = self.createLayout(contents) + dom = self.dom + menu_xml = self.getXmlMenu(self.getPath(parent), dom.documentElement, dom) + self.addXmlLayout(menu_xml, layout, dom) + self.save() + + def findMenu(self, menu_id, parent=None): + if parent is None: + parent = self.tree.get_root_directory() + + if menu_id == parent.get_menu_id(): + return parent + + item_iter = parent.iter() + item_type = item_iter.next() + while item_type != GMenu.TreeItemType.INVALID: + if item_type == GMenu.TreeItemType.DIRECTORY: + item = item_iter.get_directory() + if item.get_menu_id() == menu_id: + return item + menu = self.findMenu(menu_id, item) + if menu is not None: + return menu + item_type = item_iter.next() + + def isVisible(self, item): + if isinstance(item, GMenu.TreeEntry): + app_info = item.get_app_info() + return not (item.get_is_excluded() or app_info.get_nodisplay()) + elif isinstance(item, GMenu.TreeDirectory): + return not item.get_is_nodisplay() + return True + + def getPath(self, menu): + names = [] + current = menu + while current is not None: + names.append(current.get_menu_id()) + current = current.get_parent() + + # XXX - don't append root menu name, alacarte doesn't + # expect it. look into this more. + names.pop(-1) + return names[::-1] + + def getXmlMenuPart(self, element, name): + for node in self.getXmlNodesByName('Menu', element): + for child in self.getXmlNodesByName('Name', node): + if child.childNodes[0].nodeValue == name: + return node + return None + + def getXmlMenu(self, path, element, dom): + for name in path: + found = self.getXmlMenuPart(element, name) + if found is not None: + element = found + else: + element = self.addXmlMenuElement(element, name, dom) + return element + + def addXmlMenuElement(self, element, name, dom): + node = dom.createElement('Menu') + self.addXmlTextElement(node, 'Name', name, dom) + return element.appendChild(node) + + def addXmlTextElement(self, element, name, text, dom): + for temp in element.childNodes: + if temp.nodeName == name: + if temp.childNodes[0].nodeValue == text: + return + node = dom.createElement(name) + text = dom.createTextNode(text) + node.appendChild(text) + return element.appendChild(node) + + def addXmlFilename(self, element, dom, filename, type = 'Include'): + # remove old filenames + for node in self.getXmlNodesByName(['Include', 'Exclude'], element): + if node.childNodes[0].nodeName == 'Filename' and node.childNodes[0].childNodes[0].nodeValue == filename: + element.removeChild(node) + + # add new filename + node = dom.createElement(type) + node.appendChild(self.addXmlTextElement(node, 'Filename', filename, dom)) + return element.appendChild(node) + + def addDeleted(self, element, dom): + node = dom.createElement('Deleted') + return element.appendChild(node) + + def makeKeyFile(self, file_path, kwargs): + if 'KeyFile' in kwargs: + return kwargs['KeyFile'] + + keyfile = GLib.KeyFile() + + if file_path is not None: + keyfile.load_from_file(file_path, util.KEY_FILE_FLAGS) + + util.fillKeyFile(keyfile, kwargs) + return keyfile + + def writeItem(self, item, **kwargs): + if item is not None: + file_path = item.get_desktop_file_path() + else: + file_path = None + + keyfile = self.makeKeyFile(file_path, kwargs) + + if item is not None: + file_id = item.get_desktop_file_id() + else: + file_id = util.getUniqueFileId(keyfile.get_string(GLib.KEY_FILE_DESKTOP_GROUP, 'Name'), '.desktop') + + contents, length = keyfile.to_data() + + f = open(os.path.join(util.getUserItemPath(), file_id), 'w') + f.write(contents) + f.close() + return file_id + + def writeMenu(self, menu, **kwargs): + if menu is not None: + file_id = os.path.split(menu.get_desktop_file_path())[1] + file_path = menu.get_desktop_file_path() + keyfile = GLib.KeyFile() + keyfile.load_from_file(file_path, util.KEY_FILE_FLAGS) + elif menu is None and 'Name' not in kwargs: + raise Exception('New menus need a name') + else: + file_id = util.getUniqueFileId(kwargs['Name'], '.directory') + keyfile = GLib.KeyFile() + + util.fillKeyFile(keyfile, kwargs) + + contents, length = keyfile.to_data() + + f = open(os.path.join(util.getUserDirectoryPath(), file_id), 'w') + f.write(contents) + f.close() + return file_id + + def getXmlNodesByName(self, name, element): + for child in element.childNodes: + if child.nodeType == xml.dom.Node.ELEMENT_NODE: + if isinstance(name, str) and child.nodeName == name: + yield child + elif isinstance(name, list) or isinstance(name, tuple): + if child.nodeName in name: + yield child + + def addXmlMove(self, element, old, new, dom): + if not self.undoMoves(element, old, new, dom): + node = dom.createElement('Move') + node.appendChild(self.addXmlTextElement(node, 'Old', old, dom)) + node.appendChild(self.addXmlTextElement(node, 'New', new, dom)) + #are parsed in reverse order, need to put at the beginning + return element.insertBefore(node, element.firstChild) + + def addXmlLayout(self, element, layout, dom): + # remove old layout + for node in self.getXmlNodesByName('Layout', element): + element.removeChild(node) + + # add new layout + node = dom.createElement('Layout') + for order in layout: + if order[0] == 'Separator': + child = dom.createElement('Separator') + node.appendChild(child) + elif order[0] == 'Filename': + child = self.addXmlTextElement(node, 'Filename', order[1], dom) + elif order[0] == 'Menuname': + child = self.addXmlTextElement(node, 'Menuname', order[1], dom) + elif order[0] == 'Merge': + child = dom.createElement('Merge') + child.setAttribute('type', order[1]) + node.appendChild(child) + return element.appendChild(node) + + def addXmlDefaultLayout(self, element, dom): + # remove old default layout + for node in self.getXmlNodesByName('DefaultLayout', element): + element.removeChild(node) + + # add new layout + node = dom.createElement('DefaultLayout') + node.setAttribute('inline', 'false') + return element.appendChild(node) + + def createLayout(self, items): + layout = [] + layout.append(('Merge', 'menus')) + for item in items: + if isinstance(item, GMenu.TreeDirectory): + layout.append(('Menuname', item.get_menu_id())) + elif isinstance(item, GMenu.TreeEntry): + layout.append(('Filename', item.get_desktop_file_id())) + elif isinstance(item, GMenu.TreeSeparator): + layout.append(('Separator',)) + else: + layout.append(item) + layout.append(('Merge', 'files')) + return layout + + def addItem(self, parent, file_id, dom): + xml_parent = self.getXmlMenu(self.getPath(parent), dom.documentElement, dom) + self.addXmlFilename(xml_parent, dom, file_id, 'Include') + + def moveItem(self, parent, item, before=None, after=None): + self.positionItem(parent, item, before=before, after=after) + self.save() + + def positionItem(self, parent, item, before=None, after=None): + contents = self.getContents(parent) + if after: + index = contents.index(after) + 1 + elif before: + index = contents.index(before) + else: + # append the item to the list + index = len(contents) + #if this is a move to a new parent you can't remove the item + if item in contents: + # decrease the destination index, if we shorten the list + if (before and (contents.index(item) < index)) \ + or (after and (contents.index(item) < index - 1)): + index -= 1 + contents.remove(item) + contents.insert(index, item) + layout = self.createLayout(contents) + dom = self.dom + menu_xml = self.getXmlMenu(self.getPath(parent), dom.documentElement, dom) + self.addXmlLayout(menu_xml, layout, dom) + + def undoMoves(self, element, old, new, dom): + nodes = [] + matches = [] + original_old = old + final_old = old + #get all elements + for node in self.getXmlNodesByName(['Move'], element): + nodes.insert(0, node) + #if the matches our old parent we've found a stage to undo + for node in nodes: + xml_old = node.getElementsByTagName('Old')[0] + xml_new = node.getElementsByTagName('New')[0] + if xml_new.childNodes[0].nodeValue == old: + matches.append(node) + #we should end up with this path when completed + final_old = xml_old.childNodes[0].nodeValue + #undoing s + for node in matches: + element.removeChild(node) + if len(matches) > 0: + for node in nodes: + xml_old = node.getElementsByTagName('Old')[0] + xml_new = node.getElementsByTagName('New')[0] + path = os.path.split(xml_new.childNodes[0].nodeValue) + if path[0] == original_old: + element.removeChild(node) + for node in dom.getElementsByTagName('Menu'): + name_node = node.getElementsByTagName('Name')[0] + name = name_node.childNodes[0].nodeValue + if name == os.path.split(new)[1]: + #copy app and dir directory info from old + root_path = dom.getElementsByTagName('Menu')[0].getElementsByTagName('Name')[0].childNodes[0].nodeValue + xml_menu = self.getXmlMenu(root_path + '/' + new, dom.documentElement, dom) + for app_dir in node.getElementsByTagName('AppDir'): + xml_menu.appendChild(app_dir) + for dir_dir in node.getElementsByTagName('DirectoryDir'): + xml_menu.appendChild(dir_dir) + parent = node.parentNode + parent.removeChild(node) + node = dom.createElement('Move') + node.appendChild(self.addXmlTextElement(node, 'Old', xml_old.childNodes[0].nodeValue, dom)) + node.appendChild(self.addXmlTextElement(node, 'New', os.path.join(new, path[1]), dom)) + element.appendChild(node) + if final_old == new: + return True + node = dom.createElement('Move') + node.appendChild(self.addXmlTextElement(node, 'Old', final_old, dom)) + node.appendChild(self.addXmlTextElement(node, 'New', new, dom)) + return element.appendChild(node) --- a/files/usr/lib/cinnamon-menu-editor/Alacarte/util.py +++ b/files/usr/lib/cinnamon-menu-editor/Alacarte/util.py @@ -17,228 +17,158 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os -import gtk, gmenu -from ConfigParser import ConfigParser - -class DesktopParser(ConfigParser): - def __init__(self, filename=None, file_type='Application'): - ConfigParser.__init__(self) - self.filename = filename - self.file_type = file_type - if filename: - if len(self.read(filename)) == 0: - #file doesn't exist - self.add_section('Desktop Entry') - else: - self.add_section('Desktop Entry') - self._list_separator = ';' - - def optionxform(self, option): - #makes keys not be lowercase - return option - - def get(self, option, locale=None): - locale_option = option + '[%s]' % locale - try: - value = ConfigParser.get(self, 'Desktop Entry', locale_option) - except: - try: - value = ConfigParser.get(self, 'Desktop Entry', option) - except: - return None - if self._list_separator in value: - value = value.split(self._list_separator) - if value == 'true': - value = True - if value == 'false': - value = False - return value - - def set(self, option, value, locale=None): - if locale: - option = option + '[%s]' % locale - if value == True: - value = 'true' - if value == False: - value = 'false' - if isinstance(value, tuple) or isinstance(value, list): - value = self._list_separator.join(value) + ';' - ConfigParser.set(self, 'Desktop Entry', option, value) - - def write(self, file_object): - file_object.write('[Desktop Entry]\n') - items = [] - if not self.filename: - file_object.write('Encoding=UTF-8\n') - file_object.write('Type=' + str(self.file_type) + '\n') - for item in self.items('Desktop Entry'): - items.append(item) - items.sort() - for item in items: - file_object.write(item[0] + '=' + item[1] + '\n') +import xml.dom.minidom +from collections import Sequence +from gi.repository import Gtk, GdkPixbuf, GMenu, GLib + +# XXX: look into pygobject error marshalling +from gi._glib import GError + +DESKTOP_GROUP = GLib.KEY_FILE_DESKTOP_GROUP +KEY_FILE_FLAGS = GLib.KeyFileFlags.KEEP_COMMENTS | GLib.KeyFileFlags.KEEP_TRANSLATIONS + +def fillKeyFile(keyfile, items): + for key, item in items.iteritems(): + if item is None: + continue + + 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) def getUniqueFileId(name, extension): - append = 0 - while 1: - if append == 0: - filename = name + extension - else: - filename = name + '-' + str(append) + extension - if extension == '.desktop': - path = getUserItemPath() - if not os.path.isfile(os.path.join(path, filename)) and not getItemPath(filename): - break - elif extension == '.directory': - path = getUserDirectoryPath() - if not os.path.isfile(os.path.join(path, filename)) and not getDirectoryPath(filename): - break - append += 1 - return filename + append = 0 + while 1: + if append == 0: + filename = name + extension + else: + filename = name + '-' + str(append) + extension + if extension == '.desktop': + path = getUserItemPath() + if not os.path.isfile(os.path.join(path, filename)) and not getItemPath(filename): + break + elif extension == '.directory': + path = getUserDirectoryPath() + if not os.path.isfile(os.path.join(path, filename)) and not getDirectoryPath(filename): + break + append += 1 + return filename def getUniqueRedoFile(filepath): - append = 0 - while 1: - new_filepath = filepath + '.redo-' + str(append) - if not os.path.isfile(new_filepath): - break - else: - append += 1 - return new_filepath + append = 0 + while 1: + new_filepath = filepath + '.redo-' + str(append) + if not os.path.isfile(new_filepath): + break + else: + append += 1 + return new_filepath def getUniqueUndoFile(filepath): - filename, extension = os.path.split(filepath)[1].rsplit('.', 1) - append = 0 - while 1: - if extension == 'desktop': - path = getUserItemPath() - elif extension == 'directory': - path = getUserDirectoryPath() - elif extension == 'menu': - path = getUserMenuPath() - new_filepath = os.path.join(path, filename + '.' + extension + '.undo-' + str(append)) - if not os.path.isfile(new_filepath): - break - else: - append += 1 - return new_filepath - -def getUserMenuPath(): - menu_dir = None - if os.environ.has_key('XDG_CONFIG_HOME'): - menu_dir = os.path.join(os.environ['XDG_CONFIG_HOME'], 'menus') - else: - menu_dir = os.path.join(os.environ['HOME'], '.config', 'menus') - #move .config out of the way if it's not a dir, it shouldn't be there - if os.path.isfile(os.path.split(menu_dir)[0]): - os.rename(os.path.split(menu_dir)[0], os.path.split(menu_dir)[0] + '.old') - if not os.path.isdir(menu_dir): - os.makedirs(menu_dir) - return menu_dir + filename, extension = os.path.split(filepath)[1].rsplit('.', 1) + append = 0 + while 1: + if extension == 'desktop': + path = getUserItemPath() + elif extension == 'directory': + path = getUserDirectoryPath() + elif extension == 'menu': + path = getUserMenuPath() + new_filepath = os.path.join(path, filename + '.' + extension + '.undo-' + str(append)) + if not os.path.isfile(new_filepath): + break + else: + append += 1 + return new_filepath def getItemPath(file_id): - if os.environ.has_key('XDG_DATA_DIRS'): - for system_path in os.environ['XDG_DATA_DIRS'].split(':'): - file_path = os.path.join(system_path, 'applications', file_id) - if os.path.isfile(file_path): - return file_path - file_path = os.path.join('/', 'usr', 'share', 'applications', file_id) - if os.path.isfile(file_path): - return file_path - return False + for path in GLib.get_system_data_dirs(): + file_path = os.path.join(path, 'applications', file_id) + if os.path.isfile(file_path): + return file_path + return None def getUserItemPath(): - item_dir = None - if os.environ.has_key('XDG_DATA_HOME'): - item_dir = os.path.join(os.environ['XDG_DATA_HOME'], 'applications') - else: - item_dir = os.path.join(os.environ['HOME'], '.local', 'share', 'applications') - if not os.path.isdir(item_dir): - os.makedirs(item_dir) - return item_dir + item_dir = os.path.join(GLib.get_user_data_dir(), 'applications') + if not os.path.isdir(item_dir): + os.makedirs(item_dir) + return item_dir def getDirectoryPath(file_id): - home = getUserDirectoryPath() - file_path = os.path.join(home, file_id) - if os.path.isfile(file_path): - return file_path - if os.environ.has_key('XDG_DATA_DIRS'): - for system_path in os.environ['XDG_DATA_DIRS'].split(':'): - file_path = os.path.join(system_path, 'desktop-directories', file_id) - if os.path.isfile(file_path): - return file_path - file_path = os.path.join('/', 'usr', 'share', 'desktop-directories', file_id) - if os.path.isfile(file_path): - return file_path - return False + for path in GLib.get_system_data_dirs(): + file_path = os.path.join(path, 'desktop-directories', file_id) + if os.path.isfile(file_path): + return file_path + return None def getUserDirectoryPath(): - menu_dir = None - if os.environ.has_key('XDG_DATA_HOME'): - menu_dir = os.path.join(os.environ['XDG_DATA_HOME'], 'desktop-directories') - else: - menu_dir = os.path.join(os.environ['HOME'], '.local', 'share', 'desktop-directories') - if not os.path.isdir(menu_dir): - os.makedirs(menu_dir) - return menu_dir - -def getSystemMenuPath(file_name): - if os.environ.has_key('XDG_CONFIG_DIRS'): - for system_path in os.environ['XDG_CONFIG_DIRS'].split(':'): - file_path = os.path.join(system_path, 'menus', file_name) - if os.path.isfile(file_path): - return file_path - file_path = os.path.join('/', 'etc', 'xdg', 'menus', file_name) - if os.path.isfile(file_path): - return file_path - return False + menu_dir = os.path.join(GLib.get_user_data_dir(), 'desktop-directories') + if not os.path.isdir(menu_dir): + os.makedirs(menu_dir) + return menu_dir + +def getUserMenuPath(): + menu_dir = os.path.join(GLib.get_user_config_dir(), 'menus') + if not os.path.isdir(menu_dir): + os.makedirs(menu_dir) + return menu_dir + +def getSystemMenuPath(file_id): + for path in GLib.get_system_config_dirs(): + file_path = os.path.join(path, 'menus', file_id) + if os.path.isfile(file_path): + return file_path + return None def getUserMenuXml(tree): - system_file = getSystemMenuPath(tree.get_menu_file()) - name = tree.root.get_menu_id() - menu_xml = "\n" - menu_xml += "\n " + name + "\n " - menu_xml += "" + system_file + "\n\n" - return menu_xml - -def getIcon(item, for_properties=False): - pixbuf, path = None, None - if item == None: - if for_properties: - return None, None - return None - if isinstance(item, str): - iconName = item - else: - iconName = item.get_icon() - if iconName and not '/' in iconName and iconName[-3:] in ('png', 'svg', 'xpm'): - iconName = iconName[:-4] - icon_theme = gtk.icon_theme_get_default() - try: - pixbuf = icon_theme.load_icon(iconName, 24, 0) - path = icon_theme.lookup_icon(iconName, 24, 0).get_filename() - except: - if iconName and '/' in iconName: - try: - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(iconName, 24, 24) - path = iconName - except: - pass - if pixbuf == None: - if for_properties: - return None, None - if item.get_type() == gmenu.TYPE_DIRECTORY: - iconName = 'gnome-fs-directory' - elif item.get_type() == gmenu.TYPE_ENTRY: - iconName = 'application-default-icon' - try: - pixbuf = icon_theme.load_icon(iconName, 24, 0) - path = icon_theme.lookup_icon(iconName, 24, 0).get_filename() - except: - return None - if pixbuf == None: - return None - if pixbuf.get_width() != 24 or pixbuf.get_height() != 24: - pixbuf = pixbuf.scale_simple(24, 24, gtk.gdk.INTERP_HYPER) - if for_properties: - return pixbuf, path - return pixbuf + system_file = getSystemMenuPath(os.path.basename(tree.get_canonical_menu_path())) + name = tree.get_root_directory().get_menu_id() + menu_xml = "\n" + menu_xml += "\n " + name + "\n " + menu_xml += "" + system_file + "\n\n" + return menu_xml + +def getIcon(item): + pixbuf = None + if item is None: + return None + + if isinstance(item, GMenu.TreeDirectory): + gicon = item.get_icon() + elif isinstance(item, GMenu.TreeEntry): + app_info = item.get_app_info() + gicon = app_info.get_icon() + else: + return None + + if gicon is None: + return None + + icon_theme = Gtk.IconTheme.get_default() + info = icon_theme.lookup_by_gicon(gicon, 24, 0) + if info is None: + return None + try: + pixbuf = info.load_icon() + except GError: + return None + if pixbuf is None: + return None + if pixbuf.get_width() != 24 or pixbuf.get_height() != 24: + pixbuf = pixbuf.scale_simple(24, 24, GdkPixbuf.InterpType.HYPER) + return pixbuf + +def removeWhitespaceNodes(node): + remove_list = [] + for child in node.childNodes: + if child.nodeType == xml.dom.minidom.Node.TEXT_NODE: + child.data = child.data.strip() + if not child.data.strip(): + remove_list.append(child) + elif child.hasChildNodes(): + removeWhitespaceNodes(child) + for node in remove_list: + node.parentNode.removeChild(node) --- a/files/usr/lib/cinnamon-menu-editor/cinnamon-menu-editor.ui +++ b/files/usr/lib/cinnamon-menu-editor/cinnamon-menu-editor.ui @@ -8,22 +8,14 @@ gtk-properties edit_properties - - - - - - gtk-revert-to-saved - edit_revert_to_original - _Revert to Original - + gtk-delete edit_delete - + @@ -31,8 +23,6 @@ - - @@ -49,8 +39,8 @@ 5 - 675 - 530 + 675 + 530 True Main Menu GTK_WINDOW_TOPLEVEL @@ -65,10 +55,8 @@ GDK_GRAVITY_NORTH_WEST True False - False - - - + + @@ -78,18 +66,30 @@ True - GTK_BUTTONBOX_END + GTK_BUTTONBOX_END + + + True + True + True + gtk-help + True + GTK_RELIEF_NORMAL + True + + + - + True Restore the default menu layout True True - gtk-revert-to-saved + Restore System Configuration True GTK_RELIEF_NORMAL True - + @@ -102,7 +102,7 @@ True GTK_RELIEF_NORMAL True - + @@ -140,31 +140,6 @@ False 6 - - True - _Menus: - True - False - GTK_JUSTIFY_LEFT - False - False - 0 - 0.5 - 0 - 0 - menu_tree - PANGO_ELLIPSIZE_NONE - -1 - True - 0 - - - 0 - False - False - - - True True @@ -185,8 +160,6 @@ False False - - @@ -208,31 +181,6 @@ False 6 - - True - It_ems: - True - False - GTK_JUSTIFY_LEFT - False - False - 0 - 0.5 - 0 - 0 - item_tree - PANGO_ELLIPSIZE_NONE - -1 - True - 0 - - - 0 - False - False - - - True False @@ -259,11 +207,9 @@ - - - - - + + + @@ -285,142 +231,28 @@ 6 + _New Menu + True True True True GTK_RELIEF_NORMAL True - - - - True - 0.5 - 0.5 - 0 - 0 - 0 - 0 - 0 - 0 - - - True - False - 2 - - - True - gtk-new - 4 - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - True - _New Menu - True - False - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 0 - False - False - - - - - - + new_menu_image + + Ne_w Item + True True True True GTK_RELIEF_NORMAL True - - - - True - 0.5 - 0.5 - 0 - 0 - 0 - 0 - 0 - 0 - - - True - False - 2 - - - True - gtk-add - 4 - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - True - Ne_w Item - True - False - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 0 - False - False - - - - - - + new_item_image + @@ -432,7 +264,7 @@ True GTK_RELIEF_NORMAL True - + @@ -460,174 +292,58 @@ 6 + Move Up True True True GTK_RELIEF_NORMAL True - - - - True - 0.5 - 0.5 - 0 - 0 - 0 - 0 - 0 - 0 - - - True - False - 2 - - - True - gtk-go-up - 4 - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - True - Move Up - True - False - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 0 - False - False - - - - - - + move_up_image + + Move Down True True True GTK_RELIEF_NORMAL True - - - - True - 0.5 - 0.5 - 0 - 0 - 0 - 0 - 0 - 0 - - - True - False - 2 - - - True - gtk-go-down - 4 - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - True - Move Down - True - False - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 0 - False - False - - - - - - + move_down_image + - - gtk-properties + + gtk-delete True True True True - + False False 2 - + - - gtk-delete + + gtk-properties True True True True - + False False 3 - + @@ -675,124 +391,26 @@ - - revert_button + + help_button + restore_button close_button - - 5 - Revert Changes? - GTK_WINDOW_TOPLEVEL - GTK_WIN_POS_NONE - False - False - False - True - False - False - GDK_WINDOW_TYPE_HINT_DIALOG - GDK_GRAVITY_NORTH_WEST - True - False - False - - - True - False - 2 - - - True - GTK_BUTTONBOX_END - - - True - True - True - gtk-cancel - True - GTK_RELIEF_NORMAL - True - - - - - True - True - True - gtk-revert-to-saved - True - GTK_RELIEF_NORMAL - True - - - - - 0 - False - True - GTK_PACK_END - - - - - 5 - True - False - 8 - - - True - 6 - gtk-dialog-question - 0 - 0.5 - 0 - 0 - - - 0 - False - True - - - - - True - Revert all menus to original settings? - False - False - GTK_JUSTIFY_LEFT - False - False - 0 - 0 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 0 - True - True - - - - - 0 - True - True - - - - - - cancel_revert_button - button2 - + + True + gtk-new + + + True + gtk-add + + + True + gtk-go-down + + + True + gtk-go-up