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