Browse files

Added cinnamon-menu-editor (forked from alacarte)

  • Loading branch information...
1 parent e6eb171 commit afd1225ec584ac5c028ef303ed3be8f83c794428 @clefebvre clefebvre committed Mar 7, 2012
View
20 files/usr/bin/cinnamon-menu-editor
@@ -0,0 +1,20 @@
+#! /usr/bin/python -OOt
+
+import sys
+sys.path.insert(0,'/usr/lib/cinnamon-menu-editor')
+from Alacarte import MainWindow
+
+def main():
+ try:
+ from MenuEditor import config
+ datadir = config.pkgdatadir
+ version = config.VERSION
+ except:
+ datadir = '.'
+ version = '0.9'
+ app = MainWindow.MainWindow(datadir, version, sys.argv)
+ app.run()
+
+if __name__ == '__main__':
+ main()
+
View
629 files/usr/lib/cinnamon-menu-editor/Alacarte/MainWindow.py
@@ -0,0 +1,629 @@
+# -*- coding: utf-8 -*-
+# Alacarte Menu Editor - Simple fd.o Compliant Menu Editor
+# Copyright (C) 2006 Travis Watkins
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import gtk, gmenu, gobject, gio
+import cgi, os
+import gettext, locale
+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
+_ = 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('<Ctrl>Z')
+ accelgroup.connect_group(keyval, modifier, gtk.ACCEL_VISIBLE, self.on_mainwindow_undo)
+ keyval, modifier = gtk.accelerator_parse('<Ctrl><Shift>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 = '<small><i>' + cgi.escape(menu.get_name()) + '</i></small>'
+ 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 = '<small><i>' + cgi.escape(item.get_display_name()) + '</i></small>'
+ icon = util.getIcon(item)
+ else:
+ if show:
+ name = cgi.escape(item.get_name())
+ else:
+ name = '<small><i>' + cgi.escape(item.get_name()) + '</i></small>'
+ 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()
View
746 files/usr/lib/cinnamon-menu-editor/Alacarte/MenuEditor.py
@@ -0,0 +1,746 @@
+# -*- coding: utf-8 -*-
+# Alacarte Menu Editor - Simple fd.o Compliant Menu Editor
+# Copyright (C) 2006 Travis Watkins, Heinrich Wendel
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import os, re, xml.dom.minidom, locale
+import gmenu
+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]*</", "\\1</", getattr(self, menu).dom.toprettyxml().replace('<?xml version="1.0" ?>\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 <Menu> 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 <Move> elements
+ for node in self.__getXmlNodesByName(['Move'], element):
+ nodes.insert(0, node)
+ #if the <New> 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 <Move>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 <Menu>
+ 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])
View
0 files/usr/lib/cinnamon-menu-editor/Alacarte/__init__.py
No changes.
View
9 files/usr/lib/cinnamon-menu-editor/Alacarte/config.py
@@ -0,0 +1,9 @@
+prefix="/usr"
+datadir="/usr/share"
+localedir=datadir+"/locale"
+pkgdatadir="/usr/share/alacarte"
+libdir="/usr/lib"
+libexecdir="/usr/lib/alacarte"
+PACKAGE="alacarte"
+VERSION="0.13.2"
+GETTEXT_PACKAGE="alacarte"
View
244 files/usr/lib/cinnamon-menu-editor/Alacarte/util.py
@@ -0,0 +1,244 @@
+# -*- coding: utf-8 -*-
+# Alacarte Menu Editor - Simple fd.o Compliant Menu Editor
+# Copyright (C) 2006 Travis Watkins
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import 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')
+
+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
+
+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
+
+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
+
+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
+
+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
+
+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
+
+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
+
+def getUserMenuXml(tree):
+ system_file = getSystemMenuPath(tree.get_menu_file())
+ name = tree.root.get_menu_id()
+ menu_xml = "<!DOCTYPE Menu PUBLIC '-//freedesktop//DTD Menu 1.0//EN' 'http://standards.freedesktop.org/menu-spec/menu-1.0.dtd'>\n"
+ menu_xml += "<Menu>\n <Name>" + name + "</Name>\n "
+ menu_xml += "<MergeFile type=\"parent\">" + system_file + "</MergeFile>\n</Menu>\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
View
798 files/usr/lib/cinnamon-menu-editor/cinnamon-menu-editor.ui
@@ -0,0 +1,798 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <object class="GtkUIManager" id="uimanager1">
+ <child>
+ <object class="GtkActionGroup" id="actiongroup1">
+ <child>
+ <object class="GtkAction" id="edit_properties">
+ <property name="stock_id">gtk-properties</property>
+ <property name="name">edit_properties</property>
+ <signal handler="on_edit_properties_activate" last_modification_time="Sun, 23 Apr 2006 02:16:34 GMT" name="activate"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkAction" id="edit_revert_to_original">
+ <property name="stock_id">gtk-revert-to-saved</property>
+ <property name="name">edit_revert_to_original</property>
+ <property name="label" translatable="yes">_Revert to Original</property>
+ <signal handler="on_edit_revert_to_original_activate" last_modification_time="Sun, 23 Apr 2006 02:16:34 GMT" name="activate"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkAction" id="edit_delete">
+ <property name="stock_id">gtk-delete</property>
+ <property name="name">edit_delete</property>
+ <signal handler="on_edit_delete_activate" last_modification_time="Sun, 23 Apr 2006 02:16:34 GMT" name="activate"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <ui>
+ <popup name="edit_menu">
+ <menuitem action="edit_properties"/>
+ <menuitem action="edit_revert_to_original"/>
+ <separator/>
+ <menuitem action="edit_delete"/>
+ </popup>
+ </ui>
+ </object>
+ <object class="GtkMenu" constructor="uimanager1" id="edit_menu">
+
+
+
+
+
+
+
+
+</object>
+ <object class="GtkDialog" id="mainwindow">
+ <property name="border_width">5</property>
+ <property name="width_request">675</property>
+ <property name="height_request">530</property>
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Main Menu</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_CENTER</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">False</property>
+ <signal handler="on_close_button_clicked" last_modification_time="Wed, 26 Apr 2006 18:46:45 GMT" name="close"/>
+ <signal handler="on_close_button_clicked" last_modification_time="Fri, 28 Apr 2006 10:49:37 GMT" name="destroy"/>
+ <signal handler="on_style_set" name="style-set"/>
+ <accelerator key="Escape" modifiers="0" signal="close"/>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox5">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area5">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="revert_button">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Restore the default menu layout</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-revert-to-saved</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="on_revert_button_clicked" last_modification_time="Wed, 26 Apr 2006 18:38:17 GMT" name="clicked"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="close_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="on_close_button_clicked" last_modification_time="Wed, 26 Apr 2006 18:38:03 GMT" name="clicked"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment9">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">0</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHPaned" id="hpaned1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="position">200</property>
+ <child>
+ <object class="GtkVBox" id="vbox4">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label20">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Menus:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">menu_tree</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">True</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow3">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+ <child>
+ <object class="GtkTreeView" id="menu_tree">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ <signal handler="on_menu_tree_cursor_changed" name="cursor-changed"/>
+ <signal handler="on_menu_tree_drag_data_received" last_modification_time="Tue, 18 Apr 2006 01:13:34 GMT" name="drag_data_received"/>
+ <signal handler="on_menu_tree_drag_data_get" last_modification_time="Tue, 18 Apr 2006 23:58:24 GMT" name="drag_data_get"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="shrink">True</property>
+ <property name="resize">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label21">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">It_ems:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">item_tree</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">True</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox16">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+ <child>
+ <object class="GtkTreeView" id="item_tree">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">True</property>
+ <property name="rules_hint">True</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ <signal handler="on_item_tree_row_activated" name="row-activated"/>
+ <signal handler="on_item_tree_popup_menu" name="popup-menu"/>
+ <signal handler="on_item_tree_cursor_changed" name="cursor-changed"/>
+ <signal handler="on_item_tree_popup_menu" last_modification_time="Thu, 06 Apr 2006 01:25:48 GMT" name="button_press_event"/>
+ <signal handler="on_item_tree_drag_data_get" last_modification_time="Tue, 18 Apr 2006 01:13:21 GMT" name="drag_data_get"/>
+ <signal handler="on_item_tree_cursor_changed" last_modification_time="Tue, 18 Apr 2006 15:32:26 GMT" name="cursor_changed"/>
+ <signal handler="on_item_tree_drag_data_received" last_modification_time="Tue, 18 Apr 2006 23:58:15 GMT" name="drag_data_received"/>
+ <signal handler="on_item_tree_key_press_event" last_modification_time="Sun, 23 Apr 2006 02:21:53 GMT" name="key_press_event"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkVButtonBox" id="vbuttonbox1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_START</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="new_menu_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="on_new_menu_button_clicked" last_modification_time="Wed, 26 Apr 2006 18:04:38 GMT" name="clicked"/>
+ <child>
+ <object class="GtkAlignment" id="alignment7">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">0</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox14">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkImage" id="image21">
+ <property name="visible">True</property>
+ <property name="stock">gtk-new</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label18">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_New Menu</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="new_item_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="on_new_item_button_clicked" last_modification_time="Wed, 26 Apr 2006 18:04:43 GMT" name="clicked"/>
+ <child>
+ <object class="GtkAlignment" id="alignment8">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">0</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox15">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkImage" id="image22">
+ <property name="visible">True</property>
+ <property name="stock">gtk-add</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label19">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Ne_w Item</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="new_separator_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">New _Separator</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="on_new_separator_button_clicked" last_modification_time="Wed, 26 Apr 2006 18:04:48 GMT" name="clicked"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment12">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">12</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">0</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVButtonBox" id="vbuttonbox2">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_START</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="move_up_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="on_move_up_button_clicked" last_modification_time="Wed, 26 Apr 2006 22:09:11 GMT" name="clicked"/>
+ <child>
+ <object class="GtkAlignment" id="alignment10">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">0</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox17">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkImage" id="image23">
+ <property name="visible">True</property>
+ <property name="stock">gtk-go-up</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label22">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Move Up</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </o