summaryrefslogtreecommitdiff
path: root/Alacarte
diff options
context:
space:
mode:
authorStefano Karapetsas <[email protected]>2012-01-12 18:28:35 +0100
committerStefano Karapetsas <[email protected]>2012-01-12 18:28:35 +0100
commite87fbefcec0c44579495b1bcb22ac59814029566 (patch)
treee6f14613713d8e431dc26868e50e1d12900beb8a /Alacarte
downloadmozo-e87fbefcec0c44579495b1bcb22ac59814029566.tar.bz2
mozo-e87fbefcec0c44579495b1bcb22ac59814029566.tar.xz
import from Mate-Extra
Diffstat (limited to 'Alacarte')
-rw-r--r--Alacarte/MainWindow.py624
-rw-r--r--Alacarte/Makefile.am24
-rw-r--r--Alacarte/Makefile.in482
-rw-r--r--Alacarte/MenuEditor.py775
-rw-r--r--Alacarte/__init__.py0
-rw-r--r--Alacarte/config.py.in9
-rw-r--r--Alacarte/util.py244
7 files changed, 2158 insertions, 0 deletions
diff --git a/Alacarte/MainWindow.py b/Alacarte/MainWindow.py
new file mode 100644
index 0000000..07467d0
--- /dev/null
+++ b/Alacarte/MainWindow.py
@@ -0,0 +1,624 @@
+# -*- 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, matemenu, gobject, gio
+import cgi, os
+import gettext
+import subprocess
+import urllib
+try:
+ from Alacarte import config
+ gettext.bindtextdomain(config.GETTEXT_PACKAGE,config.localedir)
+ gettext.textdomain(config.GETTEXT_PACKAGE)
+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(os.path.join(self.file_path, 'alacarte.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)
+ keyval, modifier = gtk.accelerator_parse('F1')
+ accelgroup.connect_group(keyval, modifier, gtk.ACCEL_VISIBLE, self.on_help_button_clicked)
+ self.tree.get_object('mainwindow').add_accel_group(accelgroup)
+
+ def run(self):
+ self.loadMenus()
+ self.editor.applications.tree.add_monitor(self.menuChanged, None)
+ self.editor.settings.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() == matemenu.TYPE_DIRECTORY:
+ item_id = os.path.split(items[iter][3].get_desktop_file_path())[1]
+ update_items = True
+ elif items[iter][3].get_type() == matemenu.TYPE_ENTRY:
+ item_id = items[iter][3].get_desktop_file_id()
+ update_items = True
+ elif items[iter][3].get_type() == matemenu.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() == matemenu.TYPE_ENTRY and item[3].get_desktop_file_id() == item_id:
+ found = True
+ if item[3].get_type() == matemenu.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() == matemenu.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() == matemenu.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() == matemenu.TYPE_SEPARATOR:
+ name = '---'
+ icon = None
+ elif item.get_type() == matemenu.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
+ #mate-desktop-item-edit has finished running
+ def waitForNewItemProcess(self, process, parent_id, file_path):
+ if process.poll() != 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() != None:
+ #hack for broken mate-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(['mate-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(['mate-desktop-item-edit', file_path], env=os.environ)
+ gobject.timeout_add(100, self.waitForNewItemProcess, process, parent.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 item.get_type() == matemenu.TYPE_ENTRY:
+ self.editor.deleteItem(item)
+ elif item.get_type() == matemenu.TYPE_DIRECTORY:
+ self.editor.deleteMenu(item)
+ elif item.get_type() == matemenu.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() == matemenu.TYPE_ENTRY:
+ self.editor.revertItem(item)
+ elif item.get_type() == matemenu.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 (matemenu.TYPE_ENTRY, matemenu.TYPE_DIRECTORY):
+ return
+
+ if item.get_type() == matemenu.TYPE_ENTRY:
+ file_path = os.path.join(util.getUserItemPath(), item.get_desktop_file_id())
+ file_type = 'Item'
+ elif item.get_type() == matemenu.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(['mate-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)
+
+ 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() == matemenu.TYPE_ENTRY:
+ self.editor.copyItem(item, new_parent)
+ elif item.get_type() == matemenu.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() == matemenu.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)
+ 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() == matemenu.TYPE_SEPARATOR:
+ self.tree.get_object('edit_properties').set_sensitive(True)
+ else:
+ self.tree.get_object('edit_properties').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() == matemenu.TYPE_ENTRY:
+ self.editor.moveItem(item, item.get_parent(), before, after)
+ elif item.get_type() == matemenu.TYPE_DIRECTORY:
+ if self.editor.moveMenu(item, item.get_parent(), before, after) == False:
+ self.loadUpdates()
+ elif item.get_type() == matemenu.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() == matemenu.TYPE_ENTRY:
+ self.editor.moveItem(item, item.get_parent(), before=before)
+ elif item.get_type() == matemenu.TYPE_DIRECTORY:
+ self.editor.moveMenu(item, item.get_parent(), before=before)
+ elif item.get_type() == matemenu.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() == matemenu.TYPE_ENTRY:
+ self.editor.moveItem(item, item.get_parent(), after=after)
+ elif item.get_type() == matemenu.TYPE_DIRECTORY:
+ self.editor.moveMenu(item, item.get_parent(), after=after)
+ elif item.get_type() == matemenu.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_help_button_clicked(self, *args):
+ gtk.show_uri(gtk.gdk.screen_get_default(), "ghelp:user-guide#menu-editor", gtk.get_current_event_time())
+
+ 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_style_set(self, *args):
+ self.loadUpdates()
+
+ def quit(self):
+ self.editor.quit()
+ gtk.main_quit()
diff --git a/Alacarte/Makefile.am b/Alacarte/Makefile.am
new file mode 100644
index 0000000..6359d93
--- /dev/null
+++ b/Alacarte/Makefile.am
@@ -0,0 +1,24 @@
+## Process this file with automake to produce Makefile.in
+
+appdir = $(pythondir)/Alacarte
+app_PYTHON = __init__.py MainWindow.py MenuEditor.py util.py
+nodist_app_PYTHON = config.py
+
+config.py: config.py.in
+ sed \
+ -e s!\@prefix\@!$(prefix)! \
+ -e s!\@datadir\@!$(datadir)! \
+ -e s!\@pkgdatadir\@!$(pkgdatadir)! \
+ -e s!\@libexecdir\@!$(libexecdir)! \
+ -e s!\@libdir\@!$(libdir)! \
+ -e s!\@PACKAGE\@!$(PACKAGE)! \
+ -e s!\@VERSION\@!$(VERSION)! \
+ -e s!\@GETTEXT_PACKAGE\@!$(GETTEXT_PACKAGE)! \
+ < $< > $@
+config.py: Makefile
+
+CLEANFILES = config.py
+EXTRA_DIST = config.py.in
+
+all-local: config.py
+
diff --git a/Alacarte/Makefile.in b/Alacarte/Makefile.in
new file mode 100644
index 0000000..f4f6510
--- /dev/null
+++ b/Alacarte/Makefile.in
@@ -0,0 +1,482 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+subdir = Alacarte
+DIST_COMMON = $(app_PYTHON) $(srcdir)/Makefile.am \
+ $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+SOURCES =
+DIST_SOURCES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__installdirs = "$(DESTDIR)$(appdir)" "$(DESTDIR)$(appdir)"
+py_compile = $(top_srcdir)/py-compile
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ALACARTE_CFLAGS = @ALACARTE_CFLAGS@
+ALACARTE_LIBS = @ALACARTE_LIBS@
+ALL_LINGUAS = @ALL_LINGUAS@
+AMTAR = @AMTAR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CATALOGS = @CATALOGS@
+CATOBJEXT = @CATOBJEXT@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DATADIRNAME = @DATADIRNAME@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
+GMOFILES = @GMOFILES@
+GMSGFMT = @GMSGFMT@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INSTOBJEXT = @INSTOBJEXT@
+INTLLIBS = @INTLLIBS@
+INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@
+INTLTOOL_MERGE = @INTLTOOL_MERGE@
+INTLTOOL_PERL = @INTLTOOL_PERL@
+INTLTOOL_UPDATE = @INTLTOOL_UPDATE@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MKINSTALLDIRS = @MKINSTALLDIRS@
+MSGFMT = @MSGFMT@
+MSGFMT_OPTS = @MSGFMT_OPTS@
+MSGMERGE = @MSGMERGE@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POFILES = @POFILES@
+POSUB = @POSUB@
+PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@
+PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@
+PYTHON = @PYTHON@
+PYTHON2_4 = @PYTHON2_4@
+PYTHON2_5 = @PYTHON2_5@
+PYTHON2_6 = @PYTHON2_6@
+PYTHON2_7 = @PYTHON2_7@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+XGETTEXT = @XGETTEXT@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+appdir = $(pythondir)/Alacarte
+app_PYTHON = __init__.py MainWindow.py MenuEditor.py util.py
+nodist_app_PYTHON = config.py
+CLEANFILES = config.py
+EXTRA_DIST = config.py.in
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu Alacarte/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu Alacarte/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-appPYTHON: $(app_PYTHON)
+ @$(NORMAL_INSTALL)
+ test -z "$(appdir)" || $(MKDIR_P) "$(DESTDIR)$(appdir)"
+ @list='$(app_PYTHON)'; dlist=; list2=; test -n "$(appdir)" || list=; \
+ for p in $$list; do \
+ if test -f "$$p"; then b=; else b="$(srcdir)/"; fi; \
+ if test -f $$b$$p; then \
+ $(am__strip_dir) \
+ dlist="$$dlist $$f"; \
+ list2="$$list2 $$b$$p"; \
+ else :; fi; \
+ done; \
+ for file in $$list2; do echo $$file; done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(appdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(appdir)" || exit $$?; \
+ done || exit $$?; \
+ if test -n "$$dlist"; then \
+ if test -z "$(DESTDIR)"; then \
+ PYTHON=$(PYTHON) $(py_compile) --basedir "$(appdir)" $$dlist; \
+ else \
+ PYTHON=$(PYTHON) $(py_compile) --destdir "$(DESTDIR)" --basedir "$(appdir)" $$dlist; \
+ fi; \
+ else :; fi
+
+uninstall-appPYTHON:
+ @$(NORMAL_UNINSTALL)
+ @list='$(app_PYTHON)'; test -n "$(appdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ test -n "$$files" || exit 0; \
+ filesc=`echo "$$files" | sed 's|$$|c|'`; \
+ fileso=`echo "$$files" | sed 's|$$|o|'`; \
+ echo " ( cd '$(DESTDIR)$(appdir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(appdir)" && rm -f $$files || exit $$?; \
+ echo " ( cd '$(DESTDIR)$(appdir)' && rm -f" $$filesc ")"; \
+ cd "$(DESTDIR)$(appdir)" && rm -f $$filesc || exit $$?; \
+ echo " ( cd '$(DESTDIR)$(appdir)' && rm -f" $$fileso ")"; \
+ cd "$(DESTDIR)$(appdir)" && rm -f $$fileso
+install-nodist_appPYTHON: $(nodist_app_PYTHON)
+ @$(NORMAL_INSTALL)
+ test -z "$(appdir)" || $(MKDIR_P) "$(DESTDIR)$(appdir)"
+ @list='$(nodist_app_PYTHON)'; dlist=; list2=; test -n "$(appdir)" || list=; \
+ for p in $$list; do \
+ if test -f "$$p"; then b=; else b="$(srcdir)/"; fi; \
+ if test -f $$b$$p; then \
+ $(am__strip_dir) \
+ dlist="$$dlist $$f"; \
+ list2="$$list2 $$b$$p"; \
+ else :; fi; \
+ done; \
+ for file in $$list2; do echo $$file; done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(appdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(appdir)" || exit $$?; \
+ done || exit $$?; \
+ if test -n "$$dlist"; then \
+ if test -z "$(DESTDIR)"; then \
+ PYTHON=$(PYTHON) $(py_compile) --basedir "$(appdir)" $$dlist; \
+ else \
+ PYTHON=$(PYTHON) $(py_compile) --destdir "$(DESTDIR)" --basedir "$(appdir)" $$dlist; \
+ fi; \
+ else :; fi
+
+uninstall-nodist_appPYTHON:
+ @$(NORMAL_UNINSTALL)
+ @list='$(nodist_app_PYTHON)'; test -n "$(appdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ test -n "$$files" || exit 0; \
+ filesc=`echo "$$files" | sed 's|$$|c|'`; \
+ fileso=`echo "$$files" | sed 's|$$|o|'`; \
+ echo " ( cd '$(DESTDIR)$(appdir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(appdir)" && rm -f $$files || exit $$?; \
+ echo " ( cd '$(DESTDIR)$(appdir)' && rm -f" $$filesc ")"; \
+ cd "$(DESTDIR)$(appdir)" && rm -f $$filesc || exit $$?; \
+ echo " ( cd '$(DESTDIR)$(appdir)' && rm -f" $$fileso ")"; \
+ cd "$(DESTDIR)$(appdir)" && rm -f $$fileso
+tags: TAGS
+TAGS:
+
+ctags: CTAGS
+CTAGS:
+
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile all-local
+installdirs:
+ for dir in "$(DESTDIR)$(appdir)" "$(DESTDIR)$(appdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-appPYTHON install-nodist_appPYTHON
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-appPYTHON uninstall-nodist_appPYTHON
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am all-local check check-am clean clean-generic \
+ distclean distclean-generic distdir dvi dvi-am html html-am \
+ info info-am install install-am install-appPYTHON install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-nodist_appPYTHON \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic pdf pdf-am ps ps-am uninstall uninstall-am \
+ uninstall-appPYTHON uninstall-nodist_appPYTHON
+
+
+config.py: config.py.in
+ sed \
+ -e s!\@prefix\@!$(prefix)! \
+ -e s!\@datadir\@!$(datadir)! \
+ -e s!\@pkgdatadir\@!$(pkgdatadir)! \
+ -e s!\@libexecdir\@!$(libexecdir)! \
+ -e s!\@libdir\@!$(libdir)! \
+ -e s!\@PACKAGE\@!$(PACKAGE)! \
+ -e s!\@VERSION\@!$(VERSION)! \
+ -e s!\@GETTEXT_PACKAGE\@!$(GETTEXT_PACKAGE)! \
+ < $< > $@
+config.py: Makefile
+
+all-local: config.py
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/Alacarte/MenuEditor.py b/Alacarte/MenuEditor.py
new file mode 100644
index 0000000..5ec5b21
--- /dev/null
+++ b/Alacarte/MenuEditor.py
@@ -0,0 +1,775 @@
+# -*- 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 matemenu
+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 = matemenu.lookup_tree('mate-applications.menu', matemenu.FLAGS_SHOW_EMPTY|matemenu.FLAGS_INCLUDE_EXCLUDED|matemenu.FLAGS_INCLUDE_NODISPLAY|matemenu.FLAGS_SHOW_ALL_SEPARATORS)
+ self.applications.visible_tree = matemenu.lookup_tree('mate-applications.menu')
+ self.applications.tree.sort_key = matemenu.SORT_DISPLAY_NAME
+ self.applications.visible_tree.sort_key = matemenu.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.settings = Menu()
+ self.settings.tree = matemenu.lookup_tree('mate-settings.menu', matemenu.FLAGS_SHOW_EMPTY|matemenu.FLAGS_INCLUDE_EXCLUDED|matemenu.FLAGS_INCLUDE_NODISPLAY|matemenu.FLAGS_SHOW_ALL_SEPARATORS)
+ self.settings.visible_tree = matemenu.lookup_tree('mate-settings.menu')
+ self.settings.tree.sort_key = matemenu.SORT_DISPLAY_NAME
+ self.settings.visible_tree.sort_key = matemenu.SORT_DISPLAY_NAME
+ self.settings.path = os.path.join(util.getUserMenuPath(), self.settings.tree.get_menu_file())
+ if not os.path.isfile(self.settings.path):
+ self.settings.dom = xml.dom.minidom.parseString(util.getUserMenuXml(self.settings.tree))
+ else:
+ self.settings.dom = xml.dom.minidom.parse(self.settings.path)
+ self.__remove_whilespace_nodes(self.settings.dom)
+
+ self.save(True)
+
+ def save(self, from_loading=False):
+ for menu in ('applications', 'settings'):
+ 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', 'settings'):
+ 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() == matemenu.TYPE_DIRECTORY:
+ self.revertTree(child)
+ elif child.get_type() == matemenu.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', 'settings'):
+ 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', 'settings'):
+ 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
+ yield self.settings.tree.root
+ else:
+ for menu in parent.get_contents():
+ if menu.get_type() == matemenu.TYPE_DIRECTORY:
+ yield (menu, self.__isVisible(menu))
+
+ def getItems(self, menu):
+ for item in menu.get_contents():
+ if item.get_type() == matemenu.TYPE_SEPARATOR:
+ yield (item, True)
+ else:
+ if item.get_type() == matemenu.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() == matemenu.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() == matemenu.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() == matemenu.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() == matemenu.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.menu_id, before, after)
+
+ def insertExternalItem(self, file_id, parent_id, before=None, after=None):
+ parent = self.__findMenu(parent_id)
+ 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.__writeItem(item, hidden=True)
+ self.__addUndo([item,])
+ 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() == matemenu.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() == matemenu.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):
+ root = item.get_parent()
+ if not root:
+ #already at the top
+ root = item
+ else:
+ while True:
+ if root.get_parent():
+ root = root.get_parent()
+ else:
+ break
+ if root.menu_id == self.applications.tree.root.menu_id:
+ return self.applications
+ return self.settings
+
+ def __findMenu(self, menu_id, parent=None):
+ if parent == None:
+ menu = self.__findMenu(menu_id, self.applications.tree.root)
+ if menu != None:
+ return menu
+ else:
+ return self.__findMenu(menu_id, self.settings.tree.root)
+ if menu_id == self.applications.tree.root.menu_id:
+ return self.applications.tree.root
+ if menu_id == self.settings.tree.root.menu_id:
+ return self.settings.tree.root
+ for item in parent.get_contents():
+ if item.get_type() == matemenu.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() == matemenu.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
+ elif menu == self.settings:
+ root = self.settings.visible_tree.root
+ if item.get_type() == matemenu.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() == matemenu.TYPE_DIRECTORY:
+ layout.parseMenuname(item.get_menu_id())
+ elif item.get_type() == matemenu.TYPE_ENTRY:
+ layout.parseFilename(item.get_desktop_file_id())
+ elif item.get_type() == matemenu.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
+ if after:
+ index = parent.contents.index(after) + 1
+ elif before:
+ index = parent.contents.index(before)
+ 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])
diff --git a/Alacarte/__init__.py b/Alacarte/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Alacarte/__init__.py
diff --git a/Alacarte/config.py.in b/Alacarte/config.py.in
new file mode 100644
index 0000000..1b90871
--- /dev/null
+++ b/Alacarte/config.py.in
@@ -0,0 +1,9 @@
+prefix="@prefix@"
+datadir="@datadir@"
+localedir=datadir+"/locale"
+pkgdatadir="@pkgdatadir@"
+libdir="@libdir@"
+libexecdir="@libexecdir@"
+PACKAGE="@PACKAGE@"
+VERSION="@VERSION@"
+GETTEXT_PACKAGE="@GETTEXT_PACKAGE@"
diff --git a/Alacarte/util.py b/Alacarte/util.py
new file mode 100644
index 0000000..affd2f5
--- /dev/null
+++ b/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, matemenu
+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() == matemenu.TYPE_DIRECTORY:
+ iconName = 'mate-fs-directory'
+ elif item.get_type() == matemenu.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