+++ /dev/null
-""" CLass to edit XDG Menus """
-
-from xdg.Menu import *
-from xdg.BaseDirectory import *
-from xdg.Exceptions import *
-from xdg.DesktopEntry import *
-from xdg.Config import *
-
-import xml.dom.minidom
-import os
-import re
-
-# XML-Cleanups: Move / Exclude
-# FIXME: proper reverte/delete
-# FIXME: pass AppDirs/DirectoryDirs around in the edit/move functions
-# FIXME: catch Exceptions
-# FIXME: copy functions
-# FIXME: More Layout stuff
-# FIXME: unod/redo function / remove menu...
-# FIXME: Advanced MenuEditing Stuff: LegacyDir/MergeFile
-# Complex Rules/Deleted/OnlyAllocated/AppDirs/DirectoryDirs
-
-class MenuEditor:
- def __init__(self, menu=None, filename=None, root=False):
- self.menu = None
- self.filename = None
- self.doc = None
- self.parse(menu, filename, root)
-
- # fix for creating two menus with the same name on the fly
- self.filenames = []
-
- def parse(self, menu=None, filename=None, root=False):
- if root == True:
- setRootMode(True)
-
- if isinstance(menu, Menu):
- self.menu = menu
- elif menu:
- self.menu = parse(menu)
- else:
- self.menu = parse()
-
- if root == True:
- self.filename = self.menu.Filename
- elif filename:
- self.filename = filename
- else:
- self.filename = os.path.join(xdg_config_dirs[0], "menus", os.path.split(self.menu.Filename)[1])
-
- try:
- self.doc = xml.dom.minidom.parse(self.filename)
- except IOError:
- 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>')
- except xml.parsers.expat.ExpatError:
- raise ParsingError('Not a valid .menu file', self.filename)
-
- self.__remove_whilespace_nodes(self.doc)
-
- def save(self):
- self.__saveEntries(self.menu)
- self.__saveMenu()
-
- def createMenuEntry(self, parent, name, command=None, genericname=None, comment=None, icon=None, terminal=None, after=None, before=None):
- menuentry = MenuEntry(self.__getFileName(name, ".desktop"))
- menuentry = self.editMenuEntry(menuentry, name, genericname, comment, command, icon, terminal)
-
- self.__addEntry(parent, menuentry, after, before)
-
- sort(self.menu)
-
- return menuentry
-
- def createMenu(self, parent, name, genericname=None, comment=None, icon=None, after=None, before=None):
- menu = Menu()
-
- menu.Parent = parent
- menu.Depth = parent.Depth + 1
- menu.Layout = parent.DefaultLayout
- menu.DefaultLayout = parent.DefaultLayout
-
- menu = self.editMenu(menu, name, genericname, comment, icon)
-
- self.__addEntry(parent, menu, after, before)
-
- sort(self.menu)
-
- return menu
-
- def createSeparator(self, parent, after=None, before=None):
- separator = Separator(parent)
-
- self.__addEntry(parent, separator, after, before)
-
- sort(self.menu)
-
- return separator
-
- def moveMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None):
- self.__deleteEntry(oldparent, menuentry, after, before)
- self.__addEntry(newparent, menuentry, after, before)
-
- sort(self.menu)
-
- return menuentry
-
- def moveMenu(self, menu, oldparent, newparent, after=None, before=None):
- self.__deleteEntry(oldparent, menu, after, before)
- self.__addEntry(newparent, menu, after, before)
-
- root_menu = self.__getXmlMenu(self.menu.Name)
- if oldparent.getPath(True) != newparent.getPath(True):
- self.__addXmlMove(root_menu, os.path.join(oldparent.getPath(True), menu.Name), os.path.join(newparent.getPath(True), menu.Name))
-
- sort(self.menu)
-
- return menu
-
- def moveSeparator(self, separator, parent, after=None, before=None):
- self.__deleteEntry(parent, separator, after, before)
- self.__addEntry(parent, separator, after, before)
-
- sort(self.menu)
-
- return separator
-
- def copyMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None):
- self.__addEntry(newparent, menuentry, after, before)
-
- sort(self.menu)
-
- return menuentry
-
- def editMenuEntry(self, menuentry, name=None, genericname=None, comment=None, command=None, icon=None, terminal=None, nodisplay=None, hidden=None):
- deskentry = menuentry.DesktopEntry
-
- if name:
- if not deskentry.hasKey("Name"):
- deskentry.set("Name", name)
- deskentry.set("Name", name, locale = True)
- if comment:
- if not deskentry.hasKey("Comment"):
- deskentry.set("Comment", comment)
- deskentry.set("Comment", comment, locale = True)
- if genericname:
- if not deskentry.hasKey("GnericNe"):
- deskentry.set("GenericName", genericname)
- deskentry.set("GenericName", genericname, locale = True)
- if command:
- deskentry.set("Exec", command)
- if icon:
- deskentry.set("Icon", icon)
-
- if terminal == True:
- deskentry.set("Terminal", "true")
- elif terminal == False:
- deskentry.set("Terminal", "false")
-
- if nodisplay == True:
- deskentry.set("NoDisplay", "true")
- elif nodisplay == False:
- deskentry.set("NoDisplay", "false")
-
- if hidden == True:
- deskentry.set("Hidden", "true")
- elif hidden == False:
- deskentry.set("Hidden", "false")
-
- menuentry.updateAttributes()
-
- if len(menuentry.Parents) > 0:
- sort(self.menu)
-
- return menuentry
-
- def editMenu(self, menu, name=None, genericname=None, comment=None, icon=None, nodisplay=None, hidden=None):
- # Hack for legacy dirs
- if isinstance(menu.Directory, MenuEntry) and menu.Directory.Filename == ".directory":
- xml_menu = self.__getXmlMenu(menu.getPath(True, True))
- self.__addXmlTextElement(xml_menu, 'Directory', menu.Name + ".directory")
- menu.Directory.setAttributes(menu.Name + ".directory")
- # Hack for New Entries
- elif not isinstance(menu.Directory, MenuEntry):
- if not name:
- name = menu.Name
- filename = self.__getFileName(name, ".directory").replace("/", "")
- if not menu.Name:
- menu.Name = filename.replace(".directory", "")
- xml_menu = self.__getXmlMenu(menu.getPath(True, True))
- self.__addXmlTextElement(xml_menu, 'Directory', filename)
- menu.Directory = MenuEntry(filename)
-
- deskentry = menu.Directory.DesktopEntry
-
- if name:
- if not deskentry.hasKey("Name"):
- deskentry.set("Name", name)
- deskentry.set("Name", name, locale = True)
- if genericname:
- if not deskentry.hasKey("GenericName"):
- deskentry.set("GenericName", genericname)
- deskentry.set("GenericName", genericname, locale = True)
- if comment:
- if not deskentry.hasKey("Comment"):
- deskentry.set("Comment", comment)
- deskentry.set("Comment", comment, locale = True)
- if icon:
- deskentry.set("Icon", icon)
-
- if nodisplay == True:
- deskentry.set("NoDisplay", "true")
- elif nodisplay == False:
- deskentry.set("NoDisplay", "false")
-
- if hidden == True:
- deskentry.set("Hidden", "true")
- elif hidden == False:
- deskentry.set("Hidden", "false")
-
- menu.Directory.updateAttributes()
-
- if isinstance(menu.Parent, Menu):
- sort(self.menu)
-
- return menu
-
- def hideMenuEntry(self, menuentry):
- self.editMenuEntry(menuentry, nodisplay = True)
-
- def unhideMenuEntry(self, menuentry):
- self.editMenuEntry(menuentry, nodisplay = False, hidden = False)
-
- def hideMenu(self, menu):
- self.editMenu(menu, nodisplay = True)
-
- def unhideMenu(self, menu):
- self.editMenu(menu, nodisplay = False, hidden = False)
- xml_menu = self.__getXmlMenu(menu.getPath(True,True), False)
- for node in self.__getXmlNodesByName(["Deleted", "NotDeleted"], xml_menu):
- node.parentNode.removeChild(node)
-
- def deleteMenuEntry(self, menuentry):
- if self.getAction(menuentry) == "delete":
- self.__deleteFile(menuentry.DesktopEntry.filename)
- for parent in menuentry.Parents:
- self.__deleteEntry(parent, menuentry)
- sort(self.menu)
- return menuentry
-
- def revertMenuEntry(self, menuentry):
- if self.getAction(menuentry) == "revert":
- self.__deleteFile(menuentry.DesktopEntry.filename)
- menuentry.Original.Parents = []
- for parent in menuentry.Parents:
- index = parent.Entries.index(menuentry)
- parent.Entries[index] = menuentry.Original
- index = parent.MenuEntries.index(menuentry)
- parent.MenuEntries[index] = menuentry.Original
- menuentry.Original.Parents.append(parent)
- sort(self.menu)
- return menuentry
-
- def deleteMenu(self, menu):
- if self.getAction(menu) == "delete":
- self.__deleteFile(menu.Directory.DesktopEntry.filename)
- self.__deleteEntry(menu.Parent, menu)
- xml_menu = self.__getXmlMenu(menu.getPath(True, True))
- xml_menu.parentNode.removeChild(xml_menu)
- sort(self.menu)
- return menu
-
- def revertMenu(self, menu):
- if self.getAction(menu) == "revert":
- self.__deleteFile(menu.Directory.DesktopEntry.filename)
- menu.Directory = menu.Directory.Original
- sort(self.menu)
- return menu
-
- def deleteSeparator(self, separator):
- self.__deleteEntry(separator.Parent, separator, after=True)
-
- sort(self.menu)
-
- return separator
-
- """ Private Stuff """
- def getAction(self, entry):
- if isinstance(entry, Menu):
- if not isinstance(entry.Directory, MenuEntry):
- return "none"
- elif entry.Directory.getType() == "Both":
- return "revert"
- elif entry.Directory.getType() == "User" \
- and (len(entry.Submenus) + len(entry.MenuEntries)) == 0:
- return "delete"
-
- elif isinstance(entry, MenuEntry):
- if entry.getType() == "Both":
- return "revert"
- elif entry.getType() == "User":
- return "delete"
- else:
- return "none"
-
- return "none"
-
- def __saveEntries(self, menu):
- if not menu:
- menu = self.menu
- if isinstance(menu.Directory, MenuEntry):
- menu.Directory.save()
- for entry in menu.getEntries(hidden=True):
- if isinstance(entry, MenuEntry):
- entry.save()
- elif isinstance(entry, Menu):
- self.__saveEntries(entry)
-
- def __saveMenu(self):
- if not os.path.isdir(os.path.dirname(self.filename)):
- os.makedirs(os.path.dirname(self.filename))
- fd = open(self.filename, 'w')
- fd.write(re.sub("\n[\s]*([^\n<]*)\n[\s]*</", "\\1</", self.doc.toprettyxml().replace('<?xml version="1.0" ?>\n', '')))
- fd.close()
-
- def __getFileName(self, name, extension):
- postfix = 0
- while 1:
- if postfix == 0:
- filename = name + extension
- else:
- filename = name + "-" + str(postfix) + extension
- if extension == ".desktop":
- dir = "applications"
- elif extension == ".directory":
- dir = "desktop-directories"
- if not filename in self.filenames and not \
- os.path.isfile(os.path.join(xdg_data_dirs[0], dir, filename)):
- self.filenames.append(filename)
- break
- else:
- postfix += 1
-
- return filename
-
- def __getXmlMenu(self, path, create=True, element=None):
- if not element:
- element = self.doc
-
- if "/" in path:
- (name, path) = path.split("/", 1)
- else:
- name = path
- path = ""
-
- found = None
- for node in self.__getXmlNodesByName("Menu", element):
- for child in self.__getXmlNodesByName("Name", node):
- if child.childNodes[0].nodeValue == name:
- if path:
- found = self.__getXmlMenu(path, create, node)
- else:
- found = node
- break
- if found:
- break
- if not found and create == True:
- node = self.__addXmlMenuElement(element, name)
- if path:
- found = self.__getXmlMenu(path, create, node)
- else:
- found = node
-
- return found
-
- def __addXmlMenuElement(self, element, name):
- node = self.doc.createElement('Menu')
- self.__addXmlTextElement(node, 'Name', name)
- return element.appendChild(node)
-
- def __addXmlTextElement(self, element, name, text):
- node = self.doc.createElement(name)
- text = self.doc.createTextNode(text)
- node.appendChild(text)
- return element.appendChild(node)
-
- def __addXmlFilename(self, element, filename, type = "Include"):
- # remove old filenames
- for node in self.__getXmlNodesByName(["Include", "Exclude"], element):
- if node.childNodes[0].nodeName == "Filename" and node.childNodes[0].childNodes[0].nodeValue == filename:
- element.removeChild(node)
-
- # add new filename
- node = self.doc.createElement(type)
- node.appendChild(self.__addXmlTextElement(node, 'Filename', filename))
- return element.appendChild(node)
-
- def __addXmlMove(self, element, old, new):
- node = self.doc.createElement("Move")
- node.appendChild(self.__addXmlTextElement(node, 'Old', old))
- node.appendChild(self.__addXmlTextElement(node, 'New', new))
- return element.appendChild(node)
-
- def __addXmlLayout(self, element, layout):
- # remove old layout
- for node in self.__getXmlNodesByName("Layout", element):
- element.removeChild(node)
-
- # add new layout
- node = self.doc.createElement("Layout")
- for order in layout.order:
- if order[0] == "Separator":
- child = self.doc.createElement("Separator")
- node.appendChild(child)
- elif order[0] == "Filename":
- child = self.__addXmlTextElement(node, "Filename", order[1])
- elif order[0] == "Menuname":
- child = self.__addXmlTextElement(node, "Menuname", order[1])
- elif order[0] == "Merge":
- child = self.doc.createElement("Merge")
- child.setAttribute("type", order[1])
- node.appendChild(child)
- return element.appendChild(node)
-
- def __getXmlNodesByName(self, name, element):
- for child in element.childNodes:
- if child.nodeType == xml.dom.Node.ELEMENT_NODE and child.nodeName in name:
- yield child
-
- def __addLayout(self, parent):
- layout = Layout()
- layout.order = []
- layout.show_empty = parent.Layout.show_empty
- layout.inline = parent.Layout.inline
- layout.inline_header = parent.Layout.inline_header
- layout.inline_alias = parent.Layout.inline_alias
- layout.inline_limit = parent.Layout.inline_limit
-
- layout.order.append(["Merge", "menus"])
- for entry in parent.Entries:
- if isinstance(entry, Menu):
- layout.parseMenuname(entry.Name)
- elif isinstance(entry, MenuEntry):
- layout.parseFilename(entry.DesktopFileID)
- elif isinstance(entry, Separator):
- layout.parseSeparator()
- layout.order.append(["Merge", "files"])
-
- parent.Layout = layout
-
- return layout
-
- def __addEntry(self, parent, entry, after=None, before=None):
- if after or before:
- if after:
- index = parent.Entries.index(after) + 1
- elif before:
- index = parent.Entries.index(before)
- parent.Entries.insert(index, entry)
- else:
- parent.Entries.append(entry)
-
- xml_parent = self.__getXmlMenu(parent.getPath(True, True))
-
- if isinstance(entry, MenuEntry):
- parent.MenuEntries.append(entry)
- entry.Parents.append(parent)
- self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Include")
- elif isinstance(entry, Menu):
- parent.addSubmenu(entry)
-
- if after or before:
- self.__addLayout(parent)
- self.__addXmlLayout(xml_parent, parent.Layout)
-
- def __deleteEntry(self, parent, entry, after=None, before=None):
- parent.Entries.remove(entry)
-
- xml_parent = self.__getXmlMenu(parent.getPath(True, True))
-
- if isinstance(entry, MenuEntry):
- entry.Parents.remove(parent)
- parent.MenuEntries.remove(entry)
- self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Exclude")
- elif isinstance(entry, Menu):
- parent.Submenus.remove(entry)
-
- if after or before:
- self.__addLayout(parent)
- self.__addXmlLayout(xml_parent, parent.Layout)
-
- def __deleteFile(self, filename):
- try:
- os.remove(filename)
- except OSError:
- pass
- try:
- self.filenames.remove(filename)
- except ValueError:
- pass
-
- def __remove_whilespace_nodes(self, node):
- remove_list = []
- for child in node.childNodes:
- if child.nodeType == xml.dom.minidom.Node.TEXT_NODE:
- child.data = child.data.strip()
- if not child.data.strip():
- remove_list.append(child)
- elif child.hasChildNodes():
- self.__remove_whilespace_nodes(child)
- for node in remove_list:
- node.parentNode.removeChild(node)