Viewing file: MenuEditor.py (17.97 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
""" 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)
|