home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / python-xdg / xdg / MenuEditor.py < prev    next >
Encoding:
Python Source  |  2005-06-08  |  15.1 KB  |  512 lines

  1. """ CLass to edit XDG Menus """
  2.  
  3. from xdg.Menu import *
  4. from xdg.BaseDirectory import *
  5. from xdg.Exceptions import *
  6. from xdg.DesktopEntry import *
  7. from xdg.Config import *
  8.  
  9. import xml.dom.minidom
  10. import os
  11. import re
  12.  
  13. # XML-Cleanups: Move / Exclude
  14. # FIXME: proper reverte/delete
  15. # FIXME: pass AppDirs/DirectoryDirs around in the edit/move functions
  16. # FIXME: catch Exceptions
  17. # FIXME: copy functions
  18. # FIXME: More Layout stuff
  19. # FIXME: unod/redo function / remove menu...
  20. # FIXME: Advanced MenuEditing Stuff: LegacyDir/MergeFile
  21. #        Complex Rules/Deleted/OnlyAllocated/AppDirs/DirectoryDirs
  22.  
  23. class MenuEditor:
  24.     def __init__(self, menu=None, filename=None, root=False):
  25.         self.menu = None
  26.         self.filename = None
  27.         self.doc = None
  28.         self.parse(menu, filename, root)
  29.  
  30.         # fix for creating two menus with the same name on the fly
  31.         self.filenames = []
  32.  
  33.     def parse(self, menu=None, filename=None, root=False):
  34.         if root == True:
  35.             setRootMode(True)
  36.  
  37.         if isinstance(menu, Menu):
  38.             self.menu = menu
  39.         elif menu:
  40.             self.menu = parse(menu)
  41.         else:
  42.             self.menu = parse()
  43.  
  44.         if root == True:
  45.             self.filename = self.menu.Filename
  46.         elif filename:
  47.             self.filename = filename
  48.         else:
  49.             self.filename = os.path.join(xdg_config_dirs[0], "menus", os.path.split(self.menu.Filename)[1])
  50.  
  51.         try:
  52.             self.doc = xml.dom.minidom.parse(self.filename)
  53.         except IOError:
  54.             self.doc = xml.dom.minidom.parseString('<!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN" "http://standards.freedesktop.org/menu-spec/menu-1.0.dtd"><Menu><Name>Applications</Name><MergeFile type="parent">'+self.menu.Filename+'</MergeFile></Menu>')
  55.         except xml.parsers.expat.ExpatError:
  56.             raise ParsingError('Not a valid .menu file', self.filename)
  57.  
  58.         self.__remove_whilespace_nodes(self.doc)
  59.  
  60.     def save(self):
  61.         self.__saveEntries(self.menu)
  62.         self.__saveMenu()
  63.  
  64.     def createMenuEntry(self, parent, name, command=None, genericname=None, comment=None, icon=None, terminal=None, after=None, before=None):
  65.         menuentry = MenuEntry(self.__getFileName(name, ".desktop"))
  66.         menuentry = self.editMenuEntry(menuentry, name, genericname, comment, command, icon, terminal)
  67.  
  68.         self.__addEntry(parent, menuentry, after, before)
  69.  
  70.         sort(self.menu)
  71.  
  72.         return menuentry
  73.  
  74.     def createMenu(self, parent, name, genericname=None, comment=None, icon=None, after=None, before=None):
  75.         menu = Menu()
  76.  
  77.         menu.Parent = parent
  78.         menu.Depth = parent.Depth + 1
  79.         menu.Layout = parent.DefaultLayout
  80.         menu.DefaultLayout = parent.DefaultLayout
  81.  
  82.         menu = self.editMenu(menu, name, genericname, comment, icon)
  83.  
  84.         self.__addEntry(parent, menu, after, before)
  85.  
  86.         sort(self.menu)
  87.  
  88.         return menu
  89.  
  90.     def createSeparator(self, parent, after=None, before=None):
  91.         separator = Separator(parent)
  92.  
  93.         self.__addEntry(parent, separator, after, before)
  94.  
  95.         sort(self.menu)
  96.  
  97.         return separator
  98.  
  99.     def moveMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None):
  100.         self.__deleteEntry(oldparent, menuentry, after, before)
  101.         self.__addEntry(newparent, menuentry, after, before)
  102.  
  103.         sort(self.menu)
  104.  
  105.         return menuentry
  106.  
  107.     def moveMenu(self, menu, oldparent, newparent, after=None, before=None):
  108.         self.__deleteEntry(oldparent, menu, after, before)
  109.         self.__addEntry(newparent, menu, after, before)
  110.  
  111.         root_menu = self.__getXmlMenu(self.menu.Name)
  112.         if oldparent.getPath(True) != newparent.getPath(True):
  113.             self.__addXmlMove(root_menu, os.path.join(oldparent.getPath(True), menu.Name), os.path.join(newparent.getPath(True), menu.Name))
  114.  
  115.         sort(self.menu)
  116.  
  117.         return menu
  118.  
  119.     def moveSeparator(self, separator, parent, after=None, before=None):
  120.         self.__deleteEntry(parent, separator, after, before)
  121.         self.__addEntry(parent, separator, after, before)
  122.  
  123.         sort(self.menu)
  124.  
  125.         return separator
  126.  
  127.     def copyMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None):
  128.         self.__addEntry(newparent, menuentry, after, before)
  129.  
  130.         sort(self.menu)
  131.  
  132.         return menuentry
  133.  
  134.     def editMenuEntry(self, menuentry, name=None, genericname=None, comment=None, command=None, icon=None, terminal=None, nodisplay=None, hidden=None):
  135.         deskentry = menuentry.DesktopEntry
  136.  
  137.         if name:
  138.             if not deskentry.hasKey("Name"):
  139.                 deskentry.set("Name", name)
  140.             deskentry.set("Name", name, locale = True)
  141.         if comment:
  142.             if not deskentry.hasKey("Comment"):
  143.                 deskentry.set("Comment", comment)
  144.             deskentry.set("Comment", comment, locale = True)
  145.         if genericname:
  146.             if not deskentry.hasKey("GnericNe"):
  147.                 deskentry.set("GenericName", genericname)
  148.             deskentry.set("GenericName", genericname, locale = True)
  149.         if command:
  150.             deskentry.set("Exec", command)
  151.         if icon:
  152.             deskentry.set("Icon", icon)
  153.  
  154.         if terminal == True:
  155.             deskentry.set("Terminal", "true")
  156.         elif terminal == False:
  157.             deskentry.set("Terminal", "false")
  158.  
  159.         if nodisplay == True:
  160.             deskentry.set("NoDisplay", "true")
  161.         elif nodisplay == False:
  162.             deskentry.set("NoDisplay", "false")
  163.  
  164.         if hidden == True:
  165.             deskentry.set("Hidden", "true")
  166.         elif hidden == False:
  167.             deskentry.set("Hidden", "false")
  168.  
  169.         menuentry.updateAttributes()
  170.  
  171.         if len(menuentry.Parents) > 0:
  172.             sort(self.menu)
  173.  
  174.         return menuentry
  175.  
  176.     def editMenu(self, menu, name=None, genericname=None, comment=None, icon=None, nodisplay=None, hidden=None):
  177.         # Hack for legacy dirs
  178.         if isinstance(menu.Directory, MenuEntry) and menu.Directory.Filename == ".directory":
  179.             xml_menu = self.__getXmlMenu(menu.getPath(True, True))
  180.             self.__addXmlTextElement(xml_menu, 'Directory', menu.Name + ".directory")
  181.             menu.Directory.setAttributes(menu.Name + ".directory")
  182.         # Hack for New Entries
  183.         elif not isinstance(menu.Directory, MenuEntry):
  184.             if not name:
  185.                 name = menu.Name
  186.             filename = self.__getFileName(name, ".directory").replace("/", "")
  187.             if not menu.Name:
  188.                 menu.Name = filename.replace(".directory", "")
  189.             xml_menu = self.__getXmlMenu(menu.getPath(True, True))
  190.             self.__addXmlTextElement(xml_menu, 'Directory', filename)
  191.             menu.Directory = MenuEntry(filename)
  192.  
  193.         deskentry = menu.Directory.DesktopEntry
  194.  
  195.         if name:
  196.             if not deskentry.hasKey("Name"):
  197.                 deskentry.set("Name", name)
  198.             deskentry.set("Name", name, locale = True)
  199.         if genericname:
  200.             if not deskentry.hasKey("GenericName"):
  201.                 deskentry.set("GenericName", genericname)
  202.             deskentry.set("GenericName", genericname, locale = True)
  203.         if comment:
  204.             if not deskentry.hasKey("Comment"):
  205.                 deskentry.set("Comment", comment)
  206.             deskentry.set("Comment", comment, locale = True)
  207.         if icon:
  208.             deskentry.set("Icon", icon)
  209.  
  210.         if nodisplay == True:
  211.             deskentry.set("NoDisplay", "true")
  212.         elif nodisplay == False:
  213.             deskentry.set("NoDisplay", "false")
  214.  
  215.         if hidden == True:
  216.             deskentry.set("Hidden", "true")
  217.         elif hidden == False:
  218.             deskentry.set("Hidden", "false")
  219.  
  220.         menu.Directory.updateAttributes()
  221.  
  222.         if isinstance(menu.Parent, Menu):
  223.             sort(self.menu)
  224.  
  225.         return menu
  226.  
  227.     def hideMenuEntry(self, menuentry):
  228.         self.editMenuEntry(menuentry, nodisplay = True)
  229.  
  230.     def unhideMenuEntry(self, menuentry):
  231.         self.editMenuEntry(menuentry, nodisplay = False, hidden = False)
  232.  
  233.     def hideMenu(self, menu):
  234.         self.editMenu(menu, nodisplay = True)
  235.  
  236.     def unhideMenu(self, menu):
  237.         self.editMenu(menu, nodisplay = False, hidden = False)
  238.         xml_menu = self.__getXmlMenu(menu.getPath(True,True), False)
  239.         for node in self.__getXmlNodesByName(["Deleted", "NotDeleted"], xml_menu):
  240.             node.parentNode.removeChild(node)
  241.  
  242.     def deleteMenuEntry(self, menuentry):
  243.         if self.getAction(menuentry) == "delete":
  244.             self.__deleteFile(menuentry.DesktopEntry.filename)
  245.             for parent in menuentry.Parents:
  246.                 self.__deleteEntry(parent, menuentry)
  247.             sort(self.menu)
  248.         return menuentry
  249.  
  250.     def revertMenuEntry(self, menuentry):
  251.         if self.getAction(menuentry) == "revert":
  252.             self.__deleteFile(menuentry.DesktopEntry.filename)
  253.             menuentry.Original.Parents = []
  254.             for parent in menuentry.Parents:
  255.                 index = parent.Entries.index(menuentry)
  256.                 parent.Entries[index] = menuentry.Original
  257.                 index = parent.MenuEntries.index(menuentry)
  258.                 parent.MenuEntries[index] = menuentry.Original
  259.                 menuentry.Original.Parents.append(parent)
  260.             sort(self.menu)
  261.         return menuentry
  262.  
  263.     def deleteMenu(self, menu):
  264.         if self.getAction(menu) == "delete":
  265.             self.__deleteFile(menu.Directory.DesktopEntry.filename)
  266.             self.__deleteEntry(menu.Parent, menu)
  267.             xml_menu = self.__getXmlMenu(menu.getPath(True, True))
  268.             xml_menu.parentNode.removeChild(xml_menu)
  269.             sort(self.menu)
  270.         return menu
  271.  
  272.     def revertMenu(self, menu):
  273.         if self.getAction(menu) == "revert":
  274.             self.__deleteFile(menu.Directory.DesktopEntry.filename)
  275.             menu.Directory = menu.Directory.Original
  276.             sort(self.menu)
  277.         return menu
  278.  
  279.     def deleteSeparator(self, separator):
  280.         self.__deleteEntry(separator.Parent, separator, after=True)
  281.  
  282.         sort(self.menu)
  283.  
  284.         return separator
  285.  
  286.     """ Private Stuff """
  287.     def getAction(self, entry):
  288.         if isinstance(entry, Menu):
  289.             if not isinstance(entry.Directory, MenuEntry):
  290.                 return "none"
  291.             elif entry.Directory.getType() == "Both":
  292.                 return "revert"
  293.             elif entry.Directory.getType() == "User" \
  294.             and (len(entry.Submenus) + len(entry.MenuEntries)) == 0:
  295.                 return "delete"
  296.  
  297.         elif isinstance(entry, MenuEntry):
  298.             if entry.getType() == "Both":
  299.                 return "revert"
  300.             elif entry.getType() == "User":
  301.                 return "delete"
  302.             else:
  303.                 return "none"
  304.  
  305.         return "none"
  306.  
  307.     def __saveEntries(self, menu):
  308.         if not menu:
  309.             menu = self.menu
  310.         if isinstance(menu.Directory, MenuEntry):
  311.             menu.Directory.save()
  312.         for entry in menu.getEntries(hidden=True):
  313.             if isinstance(entry, MenuEntry):
  314.                 entry.save()
  315.             elif isinstance(entry, Menu):
  316.                 self.__saveEntries(entry)
  317.  
  318.     def __saveMenu(self):
  319.         if not os.path.isdir(os.path.dirname(self.filename)):
  320.             os.makedirs(os.path.dirname(self.filename))
  321.         fd = open(self.filename, 'w')
  322.         fd.write(re.sub("\n[\s]*([^\n<]*)\n[\s]*</", "\\1</", self.doc.toprettyxml().replace('<?xml version="1.0" ?>\n', '')))
  323.         fd.close()
  324.  
  325.     def __getFileName(self, name, extension):
  326.         postfix = 0
  327.         while 1:
  328.             if postfix == 0:
  329.                 filename = name + extension
  330.             else:
  331.                 filename = name + "-" + str(postfix) + extension
  332.             if extension == ".desktop":
  333.                 dir = "applications"
  334.             elif extension == ".directory":
  335.                 dir = "desktop-directories"
  336.             if not filename in self.filenames and not \
  337.                 os.path.isfile(os.path.join(xdg_data_dirs[0], dir, filename)):
  338.                 self.filenames.append(filename)
  339.                 break
  340.             else:
  341.                 postfix += 1
  342.  
  343.         return filename
  344.  
  345.     def __getXmlMenu(self, path, create=True, element=None):
  346.         if not element:
  347.             element = self.doc
  348.  
  349.         if "/" in path:
  350.             (name, path) = path.split("/", 1)
  351.         else:
  352.             name = path
  353.             path = ""
  354.  
  355.         found = None
  356.         for node in self.__getXmlNodesByName("Menu", element):
  357.             for child in self.__getXmlNodesByName("Name", node):
  358.                 if child.childNodes[0].nodeValue == name:
  359.                     if path:
  360.                         found = self.__getXmlMenu(path, create, node)
  361.                     else:
  362.                         found = node
  363.                     break
  364.             if found:
  365.                 break
  366.         if not found and create == True:
  367.             node = self.__addXmlMenuElement(element, name)
  368.             if path:
  369.                 found = self.__getXmlMenu(path, create, node)
  370.             else:
  371.                 found = node
  372.  
  373.         return found
  374.  
  375.     def __addXmlMenuElement(self, element, name):
  376.         node = self.doc.createElement('Menu')
  377.         self.__addXmlTextElement(node, 'Name', name)
  378.         return element.appendChild(node)
  379.  
  380.     def __addXmlTextElement(self, element, name, text):
  381.         node = self.doc.createElement(name)
  382.         text = self.doc.createTextNode(text)
  383.         node.appendChild(text)
  384.         return element.appendChild(node)
  385.  
  386.     def __addXmlFilename(self, element, filename, type = "Include"):
  387.         # remove old filenames
  388.         for node in self.__getXmlNodesByName(["Include", "Exclude"], element):
  389.             if node.childNodes[0].nodeName == "Filename" and node.childNodes[0].childNodes[0].nodeValue == filename:
  390.                 element.removeChild(node)
  391.  
  392.         # add new filename
  393.         node = self.doc.createElement(type)
  394.         node.appendChild(self.__addXmlTextElement(node, 'Filename', filename))
  395.         return element.appendChild(node)
  396.  
  397.     def __addXmlMove(self, element, old, new):
  398.         node = self.doc.createElement("Move")
  399.         node.appendChild(self.__addXmlTextElement(node, 'Old', old))
  400.         node.appendChild(self.__addXmlTextElement(node, 'New', new))
  401.         return element.appendChild(node)
  402.  
  403.     def __addXmlLayout(self, element, layout):
  404.         # remove old layout
  405.         for node in self.__getXmlNodesByName("Layout", element):
  406.             element.removeChild(node)
  407.  
  408.         # add new layout
  409.         node = self.doc.createElement("Layout")
  410.         for order in layout.order:
  411.             if order[0] == "Separator":
  412.                 child = self.doc.createElement("Separator")
  413.                 node.appendChild(child)
  414.             elif order[0] == "Filename":
  415.                 child = self.__addXmlTextElement(node, "Filename", order[1])
  416.             elif order[0] == "Menuname":
  417.                 child = self.__addXmlTextElement(node, "Menuname", order[1])
  418.             elif order[0] == "Merge":
  419.                 child = self.doc.createElement("Merge")
  420.                 child.setAttribute("type", order[1])
  421.                 node.appendChild(child)
  422.         return element.appendChild(node)
  423.  
  424.     def __getXmlNodesByName(self, name, element):
  425.         for    child in element.childNodes:
  426.             if child.nodeType == xml.dom.Node.ELEMENT_NODE and child.nodeName in name:
  427.                 yield child
  428.  
  429.     def __addLayout(self, parent):
  430.         layout = Layout()
  431.         layout.order = []
  432.         layout.show_empty = parent.Layout.show_empty
  433.         layout.inline = parent.Layout.inline
  434.         layout.inline_header = parent.Layout.inline_header
  435.         layout.inline_alias = parent.Layout.inline_alias
  436.         layout.inline_limit = parent.Layout.inline_limit
  437.  
  438.         layout.order.append(["Merge", "menus"])
  439.         for entry in parent.Entries:
  440.             if isinstance(entry, Menu):
  441.                 layout.parseMenuname(entry.Name)
  442.             elif isinstance(entry, MenuEntry):
  443.                 layout.parseFilename(entry.DesktopFileID)
  444.             elif isinstance(entry, Separator):
  445.                 layout.parseSeparator()
  446.         layout.order.append(["Merge", "files"])
  447.  
  448.         parent.Layout = layout
  449.  
  450.         return layout
  451.  
  452.     def __addEntry(self, parent, entry, after=None, before=None):
  453.         if after or before:
  454.             if after:
  455.                 index = parent.Entries.index(after) + 1
  456.             elif before:
  457.                 index = parent.Entries.index(before)
  458.             parent.Entries.insert(index, entry)
  459.         else:
  460.             parent.Entries.append(entry)
  461.  
  462.         xml_parent = self.__getXmlMenu(parent.getPath(True, True))
  463.  
  464.         if isinstance(entry, MenuEntry):
  465.             parent.MenuEntries.append(entry)
  466.             entry.Parents.append(parent)
  467.             self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Include")
  468.         elif isinstance(entry, Menu):
  469.             parent.addSubmenu(entry)
  470.  
  471.         if after or before:
  472.             self.__addLayout(parent)
  473.             self.__addXmlLayout(xml_parent, parent.Layout)
  474.  
  475.     def __deleteEntry(self, parent, entry, after=None, before=None):
  476.         parent.Entries.remove(entry)
  477.  
  478.         xml_parent = self.__getXmlMenu(parent.getPath(True, True))
  479.  
  480.         if isinstance(entry, MenuEntry):
  481.             entry.Parents.remove(parent)
  482.             parent.MenuEntries.remove(entry)
  483.             self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Exclude")
  484.         elif isinstance(entry, Menu):
  485.             parent.Submenus.remove(entry)
  486.  
  487.         if after or before:
  488.             self.__addLayout(parent)
  489.             self.__addXmlLayout(xml_parent, parent.Layout)
  490.  
  491.     def __deleteFile(self, filename):
  492.         try:
  493.             os.remove(filename)
  494.         except OSError:
  495.             pass
  496.         try:
  497.             self.filenames.remove(filename)
  498.         except ValueError:
  499.             pass
  500.  
  501.     def __remove_whilespace_nodes(self, node):
  502.         remove_list = []
  503.         for child in node.childNodes:
  504.             if child.nodeType == xml.dom.minidom.Node.TEXT_NODE:
  505.                 child.data = child.data.strip()
  506.                 if not child.data.strip():
  507.                     remove_list.append(child)
  508.             elif child.hasChildNodes():
  509.                 self.__remove_whilespace_nodes(child)
  510.         for node in remove_list:
  511.             node.parentNode.removeChild(node)
  512.