From b3e60df70d30451437b045b1d4c0d456da80d844 Mon Sep 17 00:00:00 2001 From: Stefano Karapetsas Date: Thu, 12 Jan 2012 18:43:33 +0100 Subject: Alacarte => Mozo --- Mozo/MainWindow.py | 624 ++++++++++++++++++++++++++++++++++++++++++ Mozo/Makefile.am | 24 ++ Mozo/Makefile.in | 482 +++++++++++++++++++++++++++++++++ Mozo/MenuEditor.py | 775 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Mozo/__init__.py | 0 Mozo/config.py.in | 9 + Mozo/util.py | 244 +++++++++++++++++ 7 files changed, 2158 insertions(+) create mode 100644 Mozo/MainWindow.py create mode 100644 Mozo/Makefile.am create mode 100644 Mozo/Makefile.in create mode 100644 Mozo/MenuEditor.py create mode 100644 Mozo/__init__.py create mode 100644 Mozo/config.py.in create mode 100644 Mozo/util.py (limited to 'Mozo') diff --git a/Mozo/MainWindow.py b/Mozo/MainWindow.py new file mode 100644 index 0000000..08525b2 --- /dev/null +++ b/Mozo/MainWindow.py @@ -0,0 +1,624 @@ +# -*- coding: utf-8 -*- +# Mozo 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 Mozo import config + gettext.bindtextdomain(config.GETTEXT_PACKAGE,config.localedir) + gettext.textdomain(config.GETTEXT_PACKAGE) +except: + pass +_ = gettext.gettext +from Mozo.MenuEditor import MenuEditor +from Mozo import util + +class MainWindow: + timer = None + #hack to make editing menu properties work + allow_update = True + #drag-and-drop stuff + dnd_items = [('MOZO_ITEM_ROW', gtk.TARGET_SAME_APP, 0), ('text/plain', 0, 1)] + dnd_menus = [('MOZO_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('mozo') + self.tree = gtk.Builder() + self.tree.set_translation_domain(config.GETTEXT_PACKAGE) + self.tree.add_from_file(os.path.join(self.file_path, 'mozo.ui')) + self.tree.connect_signals(self) + self.setupMenuTree() + self.setupItemTree() + self.tree.get_object('edit_delete').set_sensitive(False) + self.tree.get_object('edit_revert_to_original').set_sensitive(False) + self.tree.get_object('edit_properties').set_sensitive(False) + self.tree.get_object('move_up_button').set_sensitive(False) + self.tree.get_object('move_down_button').set_sensitive(False) + self.tree.get_object('new_separator_button').set_sensitive(False) + accelgroup = gtk.AccelGroup() + keyval, modifier = gtk.accelerator_parse('Z') + accelgroup.connect_group(keyval, modifier, gtk.ACCEL_VISIBLE, self.on_mainwindow_undo) + keyval, modifier = gtk.accelerator_parse('Z') + accelgroup.connect_group(keyval, modifier, gtk.ACCEL_VISIBLE, self.on_mainwindow_redo) + 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 = '' + cgi.escape(menu.get_name()) + '' + icon = util.getIcon(menu) + iters[depth] = self.menu_store.append(iters[depth-1], (icon, name, menu)) + self.loadMenu(iters, menu, depth) + depth -= 1 + + def loadItems(self, menu, menu_path): + self.item_store.clear() + for item, show in self.editor.getItems(menu): + menu_icon = None + if item.get_type() == matemenu.TYPE_SEPARATOR: + name = '---' + icon = None + elif item.get_type() == matemenu.TYPE_ENTRY: + if show: + name = cgi.escape(item.get_display_name()) + else: + name = '' + cgi.escape(item.get_display_name()) + '' + icon = util.getIcon(item) + else: + if show: + name = cgi.escape(item.get_name()) + else: + name = '' + cgi.escape(item.get_name()) + '' + icon = util.getIcon(item) + self.item_store.append((show, icon, name, item)) + + #this is a little timeout callback to insert new items after + #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('mozo-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('mozo-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('mozo-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 ('MOZO_ITEM_ROW', 'MOZO_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 == 'MOZO_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/mozo-dnd.desktop', 'w').write(input_stream.read()) + parser = util.DesktopParser('/tmp/mozo-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/Mozo/Makefile.am b/Mozo/Makefile.am new file mode 100644 index 0000000..31217e1 --- /dev/null +++ b/Mozo/Makefile.am @@ -0,0 +1,24 @@ +## Process this file with automake to produce Makefile.in + +appdir = $(pythondir)/Mozo +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/Mozo/Makefile.in b/Mozo/Makefile.in new file mode 100644 index 0000000..2fa0171 --- /dev/null +++ b/Mozo/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 = Mozo +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@ +MOZO_CFLAGS = @MOZO_CFLAGS@ +MOZO_LIBS = @MOZO_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)/Mozo +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 Mozo/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu Mozo/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/Mozo/MenuEditor.py b/Mozo/MenuEditor.py new file mode 100644 index 0000000..d74cb2b --- /dev/null +++ b/Mozo/MenuEditor.py @@ -0,0 +1,775 @@ +# -*- coding: utf-8 -*- +# Mozo 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 Mozo 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]*\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 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 elements + for node in self.__getXmlNodesByName(['Move'], element): + nodes.insert(0, node) + #if the matches our old parent we've found a stage to undo + for node in nodes: + xml_old = node.getElementsByTagName('Old')[0] + xml_new = node.getElementsByTagName('New')[0] + if xml_new.childNodes[0].nodeValue == old: + matches.append(node) + #we should end up with this path when completed + final_old = xml_old.childNodes[0].nodeValue + #undoing s + for node in matches: + element.removeChild(node) + if len(matches) > 0: + for node in nodes: + xml_old = node.getElementsByTagName('Old')[0] + xml_new = node.getElementsByTagName('New')[0] + path = os.path.split(xml_new.childNodes[0].nodeValue) + if path[0] == original_old: + element.removeChild(node) + for node in dom.getElementsByTagName('Menu'): + name_node = node.getElementsByTagName('Name')[0] + name = name_node.childNodes[0].nodeValue + if name == os.path.split(new)[1]: + #copy app and dir directory info from old + root_path = dom.getElementsByTagName('Menu')[0].getElementsByTagName('Name')[0].childNodes[0].nodeValue + xml_menu = self.__getXmlMenu(root_path + '/' + new, dom, dom) + for app_dir in node.getElementsByTagName('AppDir'): + xml_menu.appendChild(app_dir) + for dir_dir in node.getElementsByTagName('DirectoryDir'): + xml_menu.appendChild(dir_dir) + parent = node.parentNode + parent.removeChild(node) + node = dom.createElement('Move') + node.appendChild(self.__addXmlTextElement(node, 'Old', xml_old.childNodes[0].nodeValue, dom)) + node.appendChild(self.__addXmlTextElement(node, 'New', os.path.join(new, path[1]), dom)) + element.appendChild(node) + if final_old == new: + return True + node = dom.createElement('Move') + node.appendChild(self.__addXmlTextElement(node, 'Old', final_old, dom)) + node.appendChild(self.__addXmlTextElement(node, 'New', new, dom)) + return element.appendChild(node) + +class Layout: + def __init__(self, node=None): + self.order = [] + + def parseMenuname(self, value): + self.order.append(['Menuname', value]) + + def parseSeparator(self): + self.order.append(['Separator']) + + def parseFilename(self, value): + self.order.append(['Filename', value]) + + def parseMerge(self, merge_type='all'): + self.order.append(['Merge', merge_type]) diff --git a/Mozo/__init__.py b/Mozo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Mozo/config.py.in b/Mozo/config.py.in new file mode 100644 index 0000000..1b90871 --- /dev/null +++ b/Mozo/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/Mozo/util.py b/Mozo/util.py new file mode 100644 index 0000000..2fb123c --- /dev/null +++ b/Mozo/util.py @@ -0,0 +1,244 @@ +# -*- coding: utf-8 -*- +# Mozo 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 = "\n" + menu_xml += "\n " + name + "\n " + menu_xml += "" + system_file + "\n\n" + return menu_xml + +def getIcon(item, for_properties=False): + pixbuf, path = None, None + if item == None: + if for_properties: + return None, None + return None + if isinstance(item, str): + iconName = item + else: + iconName = item.get_icon() + if iconName and not '/' in iconName and iconName[-3:] in ('png', 'svg', 'xpm'): + iconName = iconName[:-4] + icon_theme = gtk.icon_theme_get_default() + try: + pixbuf = icon_theme.load_icon(iconName, 24, 0) + path = icon_theme.lookup_icon(iconName, 24, 0).get_filename() + except: + if iconName and '/' in iconName: + try: + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(iconName, 24, 24) + path = iconName + except: + pass + if pixbuf == None: + if for_properties: + return None, None + if item.get_type() == 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 -- cgit v1.2.1