home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / Alacarte / MenuEditor.py < prev    next >
Encoding:
Python Source  |  2009-03-17  |  26.7 KB  |  772 lines

  1. # -*- coding: utf-8 -*-
  2. #   Alacarte Menu Editor - Simple fd.o Compliant Menu Editor
  3. #   Copyright (C) 2006  Travis Watkins, Heinrich Wendel
  4. #
  5. #   This library is free software; you can redistribute it and/or
  6. #   modify it under the terms of the GNU Library General Public
  7. #   License as published by the Free Software Foundation; either
  8. #   version 2 of the License, or (at your option) any later version.
  9. #
  10. #   This library is distributed in the hope that it will be useful,
  11. #   but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13. #   Library General Public License for more details.
  14. #
  15. #   You should have received a copy of the GNU Library General Public
  16. #   License along with this library; if not, write to the Free Software
  17. #   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  18.  
  19. import os, re, xml.dom.minidom, locale
  20. import gmenu
  21. from Alacarte import util
  22.  
  23. class Menu:
  24.     tree = None
  25.     visible_tree = None
  26.     path = None
  27.     dom = None
  28.  
  29. class MenuEditor:
  30.     #lists for undo/redo functionality
  31.     __undo = []
  32.     __redo = []
  33.  
  34.     def __init__(self):
  35.         self.locale = locale.getdefaultlocale()[0]
  36.         self.__loadMenus()
  37.  
  38.     def __loadMenus(self):
  39.         self.applications = Menu()
  40.         self.applications.tree = gmenu.lookup_tree('applications.menu', gmenu.FLAGS_SHOW_EMPTY|gmenu.FLAGS_INCLUDE_EXCLUDED|gmenu.FLAGS_INCLUDE_NODISPLAY|gmenu.FLAGS_SHOW_ALL_SEPARATORS)
  41.         self.applications.visible_tree = gmenu.lookup_tree('applications.menu')
  42.         self.applications.path = os.path.join(util.getUserMenuPath(), self.applications.tree.get_menu_file())
  43.         if not os.path.isfile(self.applications.path):
  44.             self.applications.dom = xml.dom.minidom.parseString(util.getUserMenuXml(self.applications.tree))
  45.         else:
  46.             self.applications.dom = xml.dom.minidom.parse(self.applications.path)
  47.         self.__remove_whilespace_nodes(self.applications.dom)
  48.  
  49.         self.settings = Menu()      
  50.         self.settings.tree = gmenu.lookup_tree('settings.menu', gmenu.FLAGS_SHOW_EMPTY|gmenu.FLAGS_INCLUDE_EXCLUDED|gmenu.FLAGS_INCLUDE_NODISPLAY|gmenu.FLAGS_SHOW_ALL_SEPARATORS)
  51.         self.settings.visible_tree = gmenu.lookup_tree('settings.menu')      
  52.         self.settings.path = os.path.join(util.getUserMenuPath(), self.settings.tree.get_menu_file())      
  53.         if not os.path.isfile(self.settings.path):      
  54.             self.settings.dom = xml.dom.minidom.parseString(util.getUserMenuXml(self.settings.tree))      
  55.         else:      
  56.             self.settings.dom = xml.dom.minidom.parse(self.settings.path)      
  57.         self.__remove_whilespace_nodes(self.settings.dom)
  58.  
  59.         self.save(True)
  60.  
  61.     def save(self, from_loading=False):
  62.         for menu in ('applications', 'settings'):
  63.             fd = open(getattr(self, menu).path, 'w')
  64.             fd.write(re.sub("\n[\s]*([^\n<]*)\n[\s]*</", "\\1</", getattr(self, menu).dom.toprettyxml().replace('<?xml version="1.0" ?>\n', '')))
  65.             fd.close()
  66.         if not from_loading:
  67.             self.__loadMenus()
  68.  
  69.     def quit(self):
  70.         for file_name in os.listdir(util.getUserItemPath()):
  71.             if file_name[-6:-2] in ('redo', 'undo'):
  72.                 file_path = os.path.join(util.getUserItemPath(), file_name)
  73.                 os.unlink(file_path)
  74.         for file_name in os.listdir(util.getUserDirectoryPath()):
  75.             if file_name[-6:-2] in ('redo', 'undo'):
  76.                 file_path = os.path.join(util.getUserDirectoryPath(), file_name)
  77.                 os.unlink(file_path)
  78.         for file_name in os.listdir(util.getUserMenuPath()):
  79.             if file_name[-6:-2] in ('redo', 'undo'):
  80.                 file_path = os.path.join(util.getUserMenuPath(), file_name)
  81.                 os.unlink(file_path)
  82.  
  83.     def revert(self):
  84.         for name in ('applications', 'settings'):
  85.             menu = getattr(self, name)
  86.             self.revertTree(menu.tree.root)
  87.             path = os.path.join(util.getUserMenuPath(), menu.tree.get_menu_file())
  88.             try:
  89.                 os.unlink(path)
  90.             except OSError:
  91.                 pass
  92.             #reload DOM for each menu
  93.             if not os.path.isfile(menu.path):
  94.                 menu.dom = xml.dom.minidom.parseString(util.getUserMenuXml(menu.tree))
  95.             else:
  96.                 menu.dom = xml.dom.minidom.parse(menu.path)
  97.             self.__remove_whilespace_nodes(menu.dom)
  98.         #reset undo/redo, no way to recover from this
  99.         self.__undo, self.__redo = [], []
  100.         self.save()
  101.  
  102.     def revertTree(self, menu):
  103.         for child in menu.get_contents():
  104.             if child.get_type() == gmenu.TYPE_DIRECTORY:
  105.                 self.revertTree(child)
  106.             elif child.get_type() == gmenu.TYPE_ENTRY:
  107.                 self.revertItem(child)
  108.         self.revertMenu(menu)
  109.  
  110.     def undo(self):
  111.         if len(self.__undo) == 0:
  112.             return
  113.         files = self.__undo.pop()
  114.         redo = []
  115.         for file_path in files:
  116.             new_path = file_path.rsplit('.', 1)[0]
  117.             redo_path = util.getUniqueRedoFile(new_path)
  118.             data = open(new_path).read()
  119.             open(redo_path, 'w').write(data)
  120.             data = open(file_path).read()
  121.             open(new_path, 'w').write(data)
  122.             os.unlink(file_path)
  123.             redo.append(redo_path)
  124.         #reload DOM to make changes stick
  125.         for name in ('applications', 'settings'):
  126.             menu = getattr(self, name)
  127.             if not os.path.isfile(menu.path):
  128.                 menu.dom = xml.dom.minidom.parseString(util.getUserMenuXml(menu.tree))
  129.             else:
  130.                 menu.dom = xml.dom.minidom.parse(menu.path)
  131.             self.__remove_whilespace_nodes(menu.dom)
  132.         self.__redo.append(redo)
  133.  
  134.     def redo(self):
  135.         if len(self.__redo) == 0:
  136.             return
  137.         files = self.__redo.pop()
  138.         undo = []
  139.         for file_path in files:
  140.             new_path = file_path.rsplit('.', 1)[0]
  141.             undo_path = util.getUniqueUndoFile(new_path)
  142.             data = open(new_path).read()
  143.             open(undo_path, 'w').write(data)
  144.             data = open(file_path).read()
  145.             open(new_path, 'w').write(data)
  146.             os.unlink(file_path)
  147.             undo.append(undo_path)
  148.         #reload DOM to make changes stick
  149.         for name in ('applications', 'settings'):
  150.             menu = getattr(self, name)
  151.             if not os.path.isfile(menu.path):
  152.                 menu.dom = xml.dom.minidom.parseString(util.getUserMenuXml(menu.tree))
  153.             else:
  154.                 menu.dom = xml.dom.minidom.parse(menu.path)
  155.             self.__remove_whilespace_nodes(menu.dom)
  156.         self.__undo.append(undo)
  157.  
  158.     def getMenus(self, parent=None):
  159.         if parent == None:
  160.             yield self.applications.tree.root
  161.             yield self.settings.tree.root
  162.         else:
  163.             for menu in parent.get_contents():
  164.                 if menu.get_type() == gmenu.TYPE_DIRECTORY:
  165.                     yield (menu, self.__isVisible(menu))
  166.  
  167.     def getItems(self, menu):
  168.         for item in menu.get_contents():
  169.             if item.get_type() == gmenu.TYPE_SEPARATOR:
  170.                 yield (item, True)
  171.             else:
  172.                 if item.get_type() == gmenu.TYPE_ENTRY and item.get_desktop_file_id()[-19:] == '-usercustom.desktop':
  173.                     continue
  174.                 yield (item, self.__isVisible(item))
  175.  
  176.     def canRevert(self, item):
  177.         if item.get_type() == gmenu.TYPE_ENTRY:
  178.             if util.getItemPath(item.get_desktop_file_id()):
  179.                 path = util.getUserItemPath()
  180.                 if os.path.isfile(os.path.join(path, item.get_desktop_file_id())):
  181.                     return True
  182.         elif item.get_type() == gmenu.TYPE_DIRECTORY:
  183.             if item.get_desktop_file_path():
  184.                 file_id = os.path.split(item.get_desktop_file_path())[1]
  185.             else:
  186.                 file_id = item.get_menu_id() + '.directory'
  187.             if util.getDirectoryPath(file_id):
  188.                 path = util.getUserDirectoryPath()
  189.                 if os.path.isfile(os.path.join(path, file_id)):
  190.                     return True
  191.         return False
  192.  
  193.     def setVisible(self, item, visible):
  194.         dom = self.__getMenu(item).dom
  195.         if item.get_type() == gmenu.TYPE_ENTRY:
  196.             self.__addUndo([self.__getMenu(item), item])
  197.             menu_xml = self.__getXmlMenu(self.__getPath(item.get_parent()), dom, dom)
  198.             if visible:
  199.                 self.__addXmlFilename(menu_xml, dom, item.get_desktop_file_id(), 'Include')
  200.                 self.__writeItem(item, no_display=False)
  201.             else:
  202.                 self.__addXmlFilename(menu_xml, dom, item.get_desktop_file_id(), 'Exclude')
  203.             self.__addXmlTextElement(menu_xml, 'AppDir', util.getUserItemPath(), dom)
  204.         elif item.get_type() == gmenu.TYPE_DIRECTORY:
  205.             self.__addUndo([self.__getMenu(item), item])
  206.             #don't mess with it if it's empty
  207.             if len(item.get_contents()) == 0:
  208.                 return
  209.             menu_xml = self.__getXmlMenu(self.__getPath(item), dom, dom)
  210.             for node in self.__getXmlNodesByName(['Deleted', 'NotDeleted'], menu_xml):
  211.                 node.parentNode.removeChild(node)
  212.             if visible:
  213.                 self.__writeMenu(item, no_display=False)
  214.             else:
  215.                 self.__writeMenu(item, no_display=True)
  216.             self.__addXmlTextElement(menu_xml, 'DirectoryDir', util.getUserDirectoryPath(), dom)
  217.         self.save()
  218.  
  219.     def createItem(self, parent, icon, name, comment, command, use_term, before=None, after=None):
  220.         file_id = self.__writeItem(None, icon, name, comment, command, use_term)
  221.         self.insertExternalItem(file_id, parent.menu_id, before, after)
  222.  
  223.     def insertExternalItem(self, file_id, parent_id, before=None, after=None):
  224.         parent = self.__findMenu(parent_id)
  225.         dom = self.__getMenu(parent).dom
  226.         self.__addItem(parent, file_id, dom)
  227.         self.__positionItem(parent, ('Item', file_id), before, after)
  228.         self.__addUndo([self.__getMenu(parent), ('Item', file_id)])
  229.         self.save()
  230.  
  231.     def createMenu(self, parent, icon, name, comment, before=None, after=None):
  232.         file_id = self.__writeMenu(None, icon, name, comment)
  233.         self.insertExternalMenu(file_id, parent.menu_id, before, after)
  234.  
  235.     def insertExternalMenu(self, file_id, parent_id, before=None, after=None):
  236.         menu_id = file_id.rsplit('.', 1)[0]
  237.         parent = self.__findMenu(parent_id)
  238.         dom = self.__getMenu(parent).dom
  239.         self.__addXmlDefaultLayout(self.__getXmlMenu(self.__getPath(parent), dom, dom) , dom)
  240.         menu_xml = self.__getXmlMenu(self.__getPath(parent) + '/' + menu_id, dom, dom)
  241.         self.__addXmlTextElement(menu_xml, 'Directory', file_id, dom)
  242.         self.__positionItem(parent, ('Menu', menu_id), before, after)
  243.         self.__addUndo([self.__getMenu(parent), ('Menu', file_id)])
  244.         self.save()
  245.  
  246.     def createSeparator(self, parent, before=None, after=None):
  247.         self.__positionItem(parent, ('Separator',), before, after)
  248.         self.__addUndo([self.__getMenu(parent), ('Separator',)])
  249.         self.save()
  250.  
  251.     def editItem(self, item, icon, name, comment, command, use_term, parent=None, final=True):
  252.         #if nothing changed don't make a user copy
  253.         if icon == item.get_icon() and name == item.get_name() and comment == item.get_comment() and command == item.get_exec() and use_term == item.get_launch_in_terminal():
  254.             return
  255.         #hack, item.get_parent() seems to fail a lot
  256.         if not parent:
  257.             parent = item.get_parent()
  258.         if final:
  259.             self.__addUndo([self.__getMenu(parent), item])
  260.         self.__writeItem(item, icon, name, comment, command, use_term)
  261.         if final:
  262.             dom = self.__getMenu(parent).dom
  263.             menu_xml = self.__getXmlMenu(self.__getPath(parent), dom, dom)
  264.             self.__addXmlTextElement(menu_xml, 'AppDir', util.getUserItemPath(), dom)
  265.         self.save()
  266.  
  267.     def editMenu(self, menu, icon, name, comment, final=True):
  268.         #if nothing changed don't make a user copy
  269.         if icon == menu.get_icon() and name == menu.get_name() and comment == menu.get_comment():
  270.             return
  271.         #we don't use this, we just need to make sure the <Menu> exists
  272.         #otherwise changes won't show up
  273.         dom = self.__getMenu(menu).dom
  274.         menu_xml = self.__getXmlMenu(self.__getPath(menu), dom, dom)
  275.         file_id = self.__writeMenu(menu, icon, name, comment)
  276.         if final:
  277.             self.__addXmlTextElement(menu_xml, 'DirectoryDir', util.getUserDirectoryPath(), dom)
  278.             self.__addUndo([self.__getMenu(menu), menu])
  279.         self.save()
  280.  
  281.     def copyItem(self, item, new_parent, before=None, after=None):
  282.         dom = self.__getMenu(new_parent).dom
  283.         file_path = item.get_desktop_file_path()
  284.         keyfile = util.DesktopParser(file_path)
  285.         #erase Categories in new file
  286.         keyfile.set('Categories', ('',))
  287.         keyfile.set('Hidden', False)
  288.         file_id = util.getUniqueFileId(item.get_name(), '.desktop')
  289.         out_path = os.path.join(util.getUserItemPath(), file_id)
  290.         keyfile.write(open(out_path, 'w'))
  291.         self.__addItem(new_parent, file_id, dom)
  292.         self.__positionItem(new_parent, ('Item', file_id), before, after)
  293.         self.__addUndo([self.__getMenu(new_parent), ('Item', file_id)])
  294.         self.save()
  295.         return file_id
  296.  
  297.     def moveItem(self, item, new_parent, before=None, after=None):
  298.         undo = []
  299.         if item.get_parent() != new_parent:
  300.             #hide old item
  301.             self.deleteItem(item)
  302.             undo.append(item)
  303.             file_id = self.copyItem(item, new_parent)
  304.             item = ('Item', file_id)
  305.             undo.append(item)
  306.         self.__positionItem(new_parent, item, before, after)
  307.         undo.append(self.__getMenu(new_parent))
  308.         self.__addUndo(undo)
  309.         self.save()
  310.  
  311.     def moveMenu(self, menu, new_parent, before=None, after=None):
  312.         parent = new_parent
  313.         #don't move a menu into it's child
  314.         while parent.get_parent():
  315.             parent = parent.get_parent()
  316.             if parent == menu:
  317.                 return False
  318.  
  319.         #don't move a menu into itself
  320.         if new_parent == menu:
  321.             return False
  322.  
  323.         #can't move between top-level menus
  324.         if self.__getMenu(menu) != self.__getMenu(new_parent):
  325.             return False
  326.         if menu.get_parent() != new_parent:
  327.             dom = self.__getMenu(menu).dom
  328.             root_path = self.__getPath(menu).split('/', 1)[0]
  329.             xml_root = self.__getXmlMenu(root_path, dom, dom)
  330.             old_path = self.__getPath(menu).split('/', 1)[1]
  331.             #root menu's path has no /
  332.             if '/' in self.__getPath(new_parent):
  333.                 new_path = self.__getPath(new_parent).split('/', 1)[1] + '/' + menu.get_menu_id()
  334.             else:
  335.                 new_path = menu.get_menu_id()
  336.             self.__addXmlMove(xml_root, old_path, new_path, dom)
  337.         self.__positionItem(new_parent, menu, before, after)
  338.         self.__addUndo([self.__getMenu(new_parent),])
  339.         self.save()
  340.  
  341.     def moveSeparator(self, separator, new_parent, before=None, after=None):
  342.         self.__positionItem(new_parent, separator, before, after)
  343.         self.__addUndo([self.__getMenu(new_parent),])
  344.         self.save()
  345.  
  346.     def deleteItem(self, item):
  347.         self.__addUndo([item,])
  348.         self.__writeItem(item, hidden=True)
  349.         self.save()
  350.  
  351.     def deleteMenu(self, menu):
  352.         dom = self.__getMenu(menu).dom
  353.         menu_xml = self.__getXmlMenu(self.__getPath(menu), dom, dom)
  354.         self.__addDeleted(menu_xml, dom)
  355.         self.__addUndo([self.__getMenu(menu),])
  356.         self.save()
  357.  
  358.     def deleteSeparator(self, item):
  359.         parent = item.get_parent()
  360.         contents = parent.get_contents()
  361.         contents.remove(item)
  362.         layout = self.__createLayout(contents)
  363.         dom = self.__getMenu(parent).dom
  364.         menu_xml = self.__getXmlMenu(self.__getPath(parent), dom, dom)
  365.         self.__addXmlLayout(menu_xml, layout, dom)
  366.         self.__addUndo([self.__getMenu(item.get_parent()),])
  367.         self.save()
  368.  
  369.     def revertItem(self, item):
  370.         if not self.canRevert(item):
  371.             return
  372.         self.__addUndo([item,])
  373.         try:
  374.             os.remove(item.get_desktop_file_path())
  375.         except OSError:
  376.             pass
  377.         self.save()
  378.  
  379.     def revertMenu(self, menu):
  380.         if not self.canRevert(menu):
  381.             return
  382.         #wtf happened here? oh well, just bail
  383.         if not menu.get_desktop_file_path():
  384.             return
  385.         self.__addUndo([menu,])
  386.         file_id = os.path.split(menu.get_desktop_file_path())[1]
  387.         path = os.path.join(util.getUserDirectoryPath(), file_id)
  388.         try:
  389.             os.remove(path)
  390.         except OSError:
  391.             pass
  392.         self.save()
  393.  
  394.     #private stuff
  395.     def __addUndo(self, items):
  396.         self.__undo.append([])
  397.         for item in items:
  398.             if isinstance(item, Menu):
  399.                 file_path = item.path
  400.             elif isinstance(item, tuple):
  401.                 if item[0] == 'Item':
  402.                     file_path = os.path.join(util.getUserItemPath(), item[1])
  403.                     if not os.path.isfile(file_path):
  404.                         file_path = util.getItemPath(item[1])
  405.                 elif item[0] == 'Menu':
  406.                     file_path = os.path.join(util.getUserDirectoryPath(), item[1])
  407.                     if not os.path.isfile(file_path):
  408.                         file_path = util.getDirectoryPath(item[1])
  409.                 else:
  410.                     continue
  411.             elif item.get_type() == gmenu.TYPE_DIRECTORY:
  412.                 if item.get_desktop_file_path() == None:
  413.                     continue
  414.                 file_path = os.path.join(util.getUserDirectoryPath(), os.path.split(item.get_desktop_file_path())[1])
  415.                 if not os.path.isfile(file_path):
  416.                     file_path = item.get_desktop_file_path()
  417.             elif item.get_type() == gmenu.TYPE_ENTRY:
  418.                 file_path = os.path.join(util.getUserItemPath(), item.get_desktop_file_id())
  419.                 if not os.path.isfile(file_path):
  420.                     file_path = item.get_desktop_file_path()
  421.             else:
  422.                 continue
  423.             data = open(file_path).read()
  424.             undo_path = util.getUniqueUndoFile(file_path)
  425.             open(undo_path, 'w').write(data)
  426.             self.__undo[-1].append(undo_path)
  427.  
  428.     def __getMenu(self, item):
  429.         root = item.get_parent()
  430.         if not root:
  431.             #already at the top
  432.             root = item
  433.         else:
  434.             while True:
  435.                 if root.get_parent():
  436.                     root = root.get_parent()
  437.                 else:
  438.                     break
  439.         if root.menu_id == self.applications.tree.root.menu_id:
  440.             return self.applications
  441.         return self.settings
  442.  
  443.     def __findMenu(self, menu_id, parent=None):
  444.         if parent == None:
  445.             menu = self.__findMenu(menu_id, self.applications.tree.root)
  446.             if menu != None:
  447.                 return menu
  448.             else:
  449.                 return self.__findMenu(menu_id, self.settings.tree.root)
  450.         if menu_id == self.applications.tree.root.menu_id:
  451.             return self.applications.tree.root
  452.         if menu_id == self.settings.tree.root.menu_id:
  453.             return self.settings.tree.root
  454.         for item in parent.get_contents():
  455.             if item.get_type() == gmenu.TYPE_DIRECTORY:
  456.                 if item.menu_id == menu_id:
  457.                     return item
  458.                 menu = self.__findMenu(menu_id, item)
  459.                 if menu != None:
  460.                     return menu
  461.  
  462.     def __isVisible(self, item):
  463.         if item.get_type() == gmenu.TYPE_ENTRY:
  464.             return not (item.get_is_excluded() or item.get_is_nodisplay())
  465.         menu = self.__getMenu(item)
  466.         if menu == self.applications:
  467.             root = self.applications.visible_tree.root
  468.         elif menu == self.settings:
  469.             root = self.settings.visible_tree.root
  470.         if item.get_type() == gmenu.TYPE_DIRECTORY:
  471.             if self.__findMenu(item.menu_id, root) == None:
  472.                 return False
  473.         return True
  474.  
  475.     def __getPath(self, menu, path=None):
  476.         if not path:
  477.                         path = menu.tree.root.get_menu_id()
  478.         if menu.get_parent():
  479.             path = self.__getPath(menu.get_parent(), path)
  480.             path += '/'
  481.             path += menu.menu_id
  482.         return path
  483.  
  484.     def __getXmlMenu(self, path, element, dom):
  485.         if '/' in path:
  486.             (name, path) = path.split('/', 1)
  487.         else:
  488.             name = path
  489.             path = ''
  490.  
  491.         found = None
  492.         for node in self.__getXmlNodesByName('Menu', element):
  493.             for child in self.__getXmlNodesByName('Name', node):
  494.                 if child.childNodes[0].nodeValue == name:
  495.                     if path:
  496.                         found = self.__getXmlMenu(path, node, dom)
  497.                     else:
  498.                         found = node
  499.                     break
  500.             if found:
  501.                 break
  502.         if not found:
  503.             node = self.__addXmlMenuElement(element, name, dom)
  504.             if path:
  505.                 found = self.__getXmlMenu(path, node, dom)
  506.             else:
  507.                 found = node
  508.  
  509.         return found
  510.  
  511.     def __addXmlMenuElement(self, element, name, dom):
  512.         node = dom.createElement('Menu')
  513.         self.__addXmlTextElement(node, 'Name', name, dom)
  514.         return element.appendChild(node)
  515.  
  516.     def __addXmlTextElement(self, element, name, text, dom):
  517.         for temp in element.childNodes:
  518.             if temp.nodeName == name:
  519.                 if temp.childNodes[0].nodeValue == text:
  520.                     return
  521.         node = dom.createElement(name)
  522.         text = dom.createTextNode(text)
  523.         node.appendChild(text)
  524.         return element.appendChild(node)
  525.  
  526.     def __addXmlFilename(self, element, dom, filename, type = 'Include'):
  527.         # remove old filenames
  528.         for node in self.__getXmlNodesByName(['Include', 'Exclude'], element):
  529.             if node.childNodes[0].nodeName == 'Filename' and node.childNodes[0].childNodes[0].nodeValue == filename:
  530.                 element.removeChild(node)
  531.  
  532.         # add new filename
  533.         node = dom.createElement(type)
  534.         node.appendChild(self.__addXmlTextElement(node, 'Filename', filename, dom))
  535.         return element.appendChild(node)
  536.  
  537.     def __addDeleted(self, element, dom):
  538.         node = dom.createElement('Deleted')
  539.         return element.appendChild(node)
  540.  
  541.     def __writeItem(self, item=None, icon=None, name=None, comment=None, command=None, use_term=None, no_display=None, startup_notify=None, hidden=None):
  542.         if item:
  543.             file_path = item.get_desktop_file_path()
  544.             file_id = item.get_desktop_file_id()
  545.             keyfile = util.DesktopParser(file_path)
  546.         elif item == None and name == None:
  547.             raise Exception('New menu items need a name')
  548.         else:
  549.             file_id = util.getUniqueFileId(name, '.desktop')
  550.             keyfile = util.DesktopParser()
  551.         if icon:
  552.             keyfile.set('Icon', icon)
  553.             keyfile.set('Icon', icon, self.locale)
  554.         if name:
  555.             keyfile.set('Name', name)
  556.             keyfile.set('Name', name, self.locale)
  557.         if comment:
  558.             keyfile.set('Comment', comment)
  559.             keyfile.set('Comment', comment, self.locale)
  560.         if command:
  561.             keyfile.set('Exec', command)
  562.         if use_term != None:
  563.             keyfile.set('Terminal', use_term)
  564.         if no_display != None:
  565.             keyfile.set('NoDisplay', no_display)
  566.         if startup_notify != None:
  567.             keyfile.set('StartupNotify', startup_notify)
  568.         if hidden != None:
  569.             keyfile.set('Hidden', hidden)
  570.         out_path = os.path.join(util.getUserItemPath(), file_id)
  571.         keyfile.write(open(out_path, 'w'))
  572.         return file_id
  573.  
  574.     def __writeMenu(self, menu=None, icon=None, name=None, comment=None, no_display=None):
  575.         if menu:
  576.             file_id = os.path.split(menu.get_desktop_file_path())[1]
  577.             file_path = menu.get_desktop_file_path()
  578.             keyfile = util.DesktopParser(file_path)
  579.         elif menu == None and name == None:
  580.             raise Exception('New menus need a name')
  581.         else:
  582.             file_id = util.getUniqueFileId(name, '.directory')
  583.             keyfile = util.DesktopParser(file_type='Directory')
  584.         if icon:
  585.             keyfile.set('Icon', icon)
  586.         if name:
  587.             keyfile.set('Name', name)
  588.             keyfile.set('Name', name, self.locale)
  589.         if comment:
  590.             keyfile.set('Comment', comment)
  591.             keyfile.set('Comment', comment, self.locale)
  592.         if no_display != None:
  593.             keyfile.set('NoDisplay', no_display)
  594.         out_path = os.path.join(util.getUserDirectoryPath(), file_id)
  595.         keyfile.write(open(out_path, 'w'))
  596.         return file_id
  597.  
  598.     def __getXmlNodesByName(self, name, element):
  599.         for    child in element.childNodes:
  600.             if child.nodeType == xml.dom.Node.ELEMENT_NODE:
  601.                 if isinstance(name, str) and child.nodeName == name:
  602.                     yield child
  603.                 elif isinstance(name, list) or isinstance(name, tuple):
  604.                     if child.nodeName in name:
  605.                         yield child
  606.  
  607.     def __remove_whilespace_nodes(self, node):
  608.         remove_list = []
  609.         for child in node.childNodes:
  610.             if child.nodeType == xml.dom.minidom.Node.TEXT_NODE:
  611.                 child.data = child.data.strip()
  612.                 if not child.data.strip():
  613.                     remove_list.append(child)
  614.             elif child.hasChildNodes():
  615.                 self.__remove_whilespace_nodes(child)
  616.         for node in remove_list:
  617.             node.parentNode.removeChild(node)
  618.  
  619.     def __addXmlMove(self, element, old, new, dom):
  620.         if not self.__undoMoves(element, old, new, dom):
  621.             node = dom.createElement('Move')
  622.             node.appendChild(self.__addXmlTextElement(node, 'Old', old, dom))
  623.             node.appendChild(self.__addXmlTextElement(node, 'New', new, dom))
  624.             #are parsed in reverse order, need to put at the beginning
  625.             return element.insertBefore(node, element.firstChild)
  626.  
  627.     def __addXmlLayout(self, element, layout, dom):
  628.         # remove old layout
  629.         for node in self.__getXmlNodesByName('Layout', element):
  630.             element.removeChild(node)
  631.  
  632.         # add new layout
  633.         node = dom.createElement('Layout')
  634.         for order in layout.order:
  635.             if order[0] == 'Separator':
  636.                 child = dom.createElement('Separator')
  637.                 node.appendChild(child)
  638.             elif order[0] == 'Filename':
  639.                 child = self.__addXmlTextElement(node, 'Filename', order[1], dom)
  640.             elif order[0] == 'Menuname':
  641.                 child = self.__addXmlTextElement(node, 'Menuname', order[1], dom)
  642.             elif order[0] == 'Merge':
  643.                 child = dom.createElement('Merge')
  644.                 child.setAttribute('type', order[1])
  645.                 node.appendChild(child)
  646.         return element.appendChild(node)
  647.  
  648.     def __addXmlDefaultLayout(self, element, dom):
  649.         # remove old default layout
  650.         for node in self.__getXmlNodesByName('DefaultLayout', element):
  651.             element.removeChild(node)
  652.  
  653.         # add new layout
  654.         node = dom.createElement('DefaultLayout')
  655.         node.setAttribute('inline', 'false')
  656.         return element.appendChild(node)
  657.  
  658.     def __createLayout(self, items):
  659.         layout = Layout()
  660.         layout.order = []
  661.  
  662.         layout.order.append(['Merge', 'menus'])
  663.         for item in items:
  664.             if isinstance(item, tuple):
  665.                 if item[0] == 'Separator':
  666.                     layout.parseSeparator()
  667.                 elif item[0] == 'Menu':
  668.                     layout.parseMenuname(item[1])
  669.                 elif item[0] == 'Item':
  670.                     layout.parseFilename(item[1])
  671.             elif item.get_type() == gmenu.TYPE_DIRECTORY:
  672.                 layout.parseMenuname(item.get_menu_id())
  673.             elif item.get_type() == gmenu.TYPE_ENTRY:
  674.                 layout.parseFilename(item.get_desktop_file_id())
  675.             elif item.get_type() == gmenu.TYPE_SEPARATOR:
  676.                 layout.parseSeparator()
  677.         layout.order.append(['Merge', 'files'])
  678.         return layout
  679.  
  680.     def __addItem(self, parent, file_id, dom):
  681.         xml_parent = self.__getXmlMenu(self.__getPath(parent), dom, dom)
  682.         self.__addXmlFilename(xml_parent, dom, file_id, 'Include')
  683.  
  684.     def __deleteItem(self, parent, file_id, dom, before=None, after=None):
  685.         xml_parent = self.__getXmlMenu(self.__getPath(parent), dom, dom)
  686.         self.__addXmlFilename(xml_parent, dom, file_id, 'Exclude')
  687.  
  688.     def __positionItem(self, parent, item, before=None, after=None):
  689.         if not before and not after:
  690.             return
  691.         if after:
  692.             index = parent.contents.index(after) + 1
  693.         elif before:
  694.             index = parent.contents.index(before)
  695.         contents = parent.contents
  696.         #if this is a move to a new parent you can't remove the item
  697.         try:
  698.             contents.remove(item)
  699.         except:
  700.             pass
  701.         contents.insert(index, item)
  702.         layout = self.__createLayout(contents)
  703.         dom = self.__getMenu(parent).dom
  704.         menu_xml = self.__getXmlMenu(self.__getPath(parent), dom, dom)
  705.         self.__addXmlLayout(menu_xml, layout, dom)
  706.  
  707.     def __undoMoves(self, element, old, new, dom):
  708.         nodes = []
  709.         matches = []
  710.         original_old = old
  711.         final_old = old
  712.         #get all <Move> elements
  713.         for node in self.__getXmlNodesByName(['Move'], element):
  714.             nodes.insert(0, node)
  715.         #if the <New> matches our old parent we've found a stage to undo
  716.         for node in nodes:
  717.             xml_old = node.getElementsByTagName('Old')[0]
  718.             xml_new = node.getElementsByTagName('New')[0]
  719.             if xml_new.childNodes[0].nodeValue == old:
  720.                 matches.append(node)
  721.                 #we should end up with this path when completed
  722.                 final_old = xml_old.childNodes[0].nodeValue
  723.         #undoing <Move>s
  724.         for node in matches:
  725.             element.removeChild(node)
  726.         if len(matches) > 0:
  727.             for node in nodes:
  728.                 xml_old = node.getElementsByTagName('Old')[0]
  729.                 xml_new = node.getElementsByTagName('New')[0]
  730.                 path = os.path.split(xml_new.childNodes[0].nodeValue)
  731.                 if path[0] == original_old:
  732.                     element.removeChild(node)
  733.                     for node in dom.getElementsByTagName('Menu'):
  734.                         name_node = node.getElementsByTagName('Name')[0]
  735.                         name = name_node.childNodes[0].nodeValue
  736.                         if name == os.path.split(new)[1]:
  737.                             #copy app and dir directory info from old <Menu>
  738.                             root_path = dom.getElementsByTagName('Menu')[0].getElementsByTagName('Name')[0].childNodes[0].nodeValue
  739.                             xml_menu = self.__getXmlMenu(root_path + '/' + new, dom, dom)
  740.                             for app_dir in node.getElementsByTagName('AppDir'):
  741.                                 xml_menu.appendChild(app_dir)
  742.                             for dir_dir in node.getElementsByTagName('DirectoryDir'):
  743.                                 xml_menu.appendChild(dir_dir)
  744.                             parent = node.parentNode
  745.                             parent.removeChild(node)
  746.                     node = dom.createElement('Move')
  747.                     node.appendChild(self.__addXmlTextElement(node, 'Old', xml_old.childNodes[0].nodeValue, dom))
  748.                     node.appendChild(self.__addXmlTextElement(node, 'New', os.path.join(new, path[1]), dom))
  749.                     element.appendChild(node)
  750.             if final_old == new:
  751.                 return True
  752.             node = dom.createElement('Move')
  753.             node.appendChild(self.__addXmlTextElement(node, 'Old', final_old, dom))
  754.             node.appendChild(self.__addXmlTextElement(node, 'New', new, dom))
  755.             return element.appendChild(node)
  756.  
  757. class Layout:
  758.     def __init__(self, node=None):
  759.         self.order = []
  760.  
  761.     def parseMenuname(self, value):
  762.         self.order.append(['Menuname', value])
  763.  
  764.     def parseSeparator(self):
  765.         self.order.append(['Separator'])
  766.  
  767.     def parseFilename(self, value):
  768.         self.order.append(['Filename', value])
  769.  
  770.     def parseMerge(self, merge_type='all'):
  771.         self.order.append(['Merge', merge_type])
  772.