# -*- coding: utf-8 -*-
# Mozo Menu Editor - Simple fd.o Compliant Menu Editor
# Copyright (C) 2006 Travis Watkins, Heinrich Wendel
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import codecs
import os
import re
import xml.dom.minidom
import xml.parsers.expat
import locale
import matemenu
from Mozo import util
class Menu:
tree = None
visible_tree = None
path = None
dom = None
class MenuEditor:
#lists for undo/redo functionality
__undo = []
__redo = []
def __init__(self):
self.locale = locale.getdefaultlocale()[0]
self.__loadMenus()
def __loadMenus(self):
self.applications = Menu()
self.applications.tree = matemenu.lookup_tree('mate-applications.menu', matemenu.FLAGS_SHOW_EMPTY|matemenu.FLAGS_INCLUDE_EXCLUDED|matemenu.FLAGS_INCLUDE_NODISPLAY|matemenu.FLAGS_SHOW_ALL_SEPARATORS)
self.applications.visible_tree = matemenu.lookup_tree('mate-applications.menu')
self.applications.tree.sort_key = matemenu.SORT_DISPLAY_NAME
self.applications.visible_tree.sort_key = matemenu.SORT_DISPLAY_NAME
self.applications.path = os.path.join(util.getUserMenuPath(), self.applications.tree.get_menu_file())
try:
self.applications.dom = xml.dom.minidom.parse(self.applications.path)
except (IOError, xml.parsers.expat.ExpatError):
self.applications.dom = xml.dom.minidom.parseString(util.getUserMenuXml(self.applications.tree))
self.__remove_whilespace_nodes(self.applications.dom)
self.settings = Menu()
self.settings.tree = matemenu.lookup_tree('mate-settings.menu', matemenu.FLAGS_SHOW_EMPTY|matemenu.FLAGS_INCLUDE_EXCLUDED|matemenu.FLAGS_INCLUDE_NODISPLAY|matemenu.FLAGS_SHOW_ALL_SEPARATORS)
self.settings.visible_tree = matemenu.lookup_tree('mate-settings.menu')
self.settings.tree.sort_key = matemenu.SORT_DISPLAY_NAME
self.settings.visible_tree.sort_key = matemenu.SORT_DISPLAY_NAME
self.settings.path = os.path.join(util.getUserMenuPath(), self.settings.tree.get_menu_file())
try:
self.settings.dom = xml.dom.minidom.parse(self.settings.path)
except (IOError, xml.parsers.expat.ExpatError):
self.settings.dom = xml.dom.minidom.parseString(util.getUserMenuXml(self.settings.tree))
self.__remove_whilespace_nodes(self.settings.dom)
self.save(True)
def save(self, from_loading=False):
for menu in ('applications', 'settings'):
xml = getattr(self, menu).dom.toprettyxml().replace('\n', '')
with codecs.open(getattr(self, menu).path, 'w', 'utf-8') as f:
f.write(re.sub("\n[\s]*([^\n<]*)\n[\s]*", "\\1", xml))
if not from_loading:
self.__loadMenus()
def quit(self):
for file_name in os.listdir(util.getUserItemPath()):
if file_name[-6:-2] in ('redo', 'undo'):
file_path = os.path.join(util.getUserItemPath(), file_name)
os.unlink(file_path)
for file_name in os.listdir(util.getUserDirectoryPath()):
if file_name[-6:-2] in ('redo', 'undo'):
file_path = os.path.join(util.getUserDirectoryPath(), file_name)
os.unlink(file_path)
for file_name in os.listdir(util.getUserMenuPath()):
if file_name[-6:-2] in ('redo', 'undo'):
file_path = os.path.join(util.getUserMenuPath(), file_name)
os.unlink(file_path)
def revert(self):
for name in ('applications', 'settings'):
menu = getattr(self, name)
self.revertTree(menu.tree.root)
path = os.path.join(util.getUserMenuPath(), menu.tree.get_menu_file())
try:
os.unlink(path)
except OSError:
pass
#reload DOM for each menu
try:
menu.dom = xml.dom.minidom.parse(menu.path)
except (IOError, xml.parsers.expat.ExpatError):
menu.dom = xml.dom.minidom.parseString(util.getUserMenuXml(menu.tree))
self.__remove_whilespace_nodes(menu.dom)
#reset undo/redo, no way to recover from this
self.__undo, self.__redo = [], []
self.save()
def revertTree(self, menu):
for child in menu.get_contents():
if child.get_type() == matemenu.TYPE_DIRECTORY:
self.revertTree(child)
elif child.get_type() == matemenu.TYPE_ENTRY:
self.revertItem(child)
self.revertMenu(menu)
def undo(self):
if len(self.__undo) == 0:
return
files = self.__undo.pop()
redo = []
for file_path in files:
new_path = file_path.rsplit('.', 1)[0]
redo_path = util.getUniqueRedoFile(new_path)
f_file_path = codecs.open(new_path, 'r', 'utf-8')
f_new_path = codecs.open(new_path, 'rw', 'utf-8')
f_redo_path = codecs.open(redo_path, 'rw', 'utf-8')
data = f_new_path.read()
f_redo_path.write(data)
data = f_file_path.read()
f_new_path.write(data)
f_file_path.close()
f_new_path.close()
f_redo_path.close()
os.unlink(file_path)
redo.append(redo_path)
# reload DOM to make changes stick
for name in ('applications', 'settings'):
menu = getattr(self, name)
try:
menu.dom = xml.dom.minidom.parse(menu.path)
except (IOError, xml.parsers.expat.ExpatError):
menu.dom = xml.dom.minidom.parseString(util.getUserMenuXml(menu.tree))
self.__remove_whilespace_nodes(menu.dom)
self.__redo.append(redo)
def redo(self):
if len(self.__redo) == 0:
return
files = self.__redo.pop()
undo = []
for file_path in files:
new_path = file_path.rsplit('.', 1)[0]
undo_path = util.getUniqueUndoFile(new_path)
f_file_path = codecs.open(new_path, 'r', 'utf-8')
f_new_path = codecs.open(new_path, 'rw', 'utf-8')
f_undo_path = codecs.open(undo_path, 'rw', 'utf-8')
data = f_new_path.read()
f_undo_path.write(data)
data = f_file_path.read()
f_new_path.write(data)
os.unlink(file_path)
undo.append(undo_path)
f_file_path.close()
f_new_path.close()
f_undo_path.close()
#reload DOM to make changes stick
for name in ('applications', 'settings'):
menu = getattr(self, name)
try:
menu.dom = xml.dom.minidom.parse(menu.path)
except (IOError, xml.parsers.expat.ExpatError):
menu.dom = xml.dom.minidom.parseString(util.getUserMenuXml(menu.tree))
self.__remove_whilespace_nodes(menu.dom)
self.__undo.append(undo)
def getMenus(self, parent=None):
if parent == None:
yield self.applications.tree.root
yield self.settings.tree.root
else:
for menu in parent.get_contents():
if menu.get_type() == matemenu.TYPE_DIRECTORY:
yield (menu, self.__isVisible(menu))
def getItems(self, menu):
for item in menu.get_contents():
if item.get_type() == matemenu.TYPE_SEPARATOR:
yield (item, True)
else:
if item.get_type() == matemenu.TYPE_ENTRY and item.get_desktop_file_id()[-19:] == '-usercustom.desktop':
continue
yield (item, self.__isVisible(item))
def canRevert(self, item):
if item.get_type() == matemenu.TYPE_ENTRY:
if util.getItemPath(item.get_desktop_file_id()):
path = util.getUserItemPath()
if os.path.isfile(os.path.join(path, item.get_desktop_file_id())):
return True
elif item.get_type() == matemenu.TYPE_DIRECTORY:
if item.get_desktop_file_path():
file_id = os.path.split(item.get_desktop_file_path())[1]
else:
file_id = item.get_menu_id() + '.directory'
if util.getDirectoryPath(file_id):
path = util.getUserDirectoryPath()
if os.path.isfile(os.path.join(path, file_id)):
return True
return False
def setVisible(self, item, visible):
dom = self.__getMenu(item).dom
if item.get_type() == matemenu.TYPE_ENTRY:
self.__addUndo([self.__getMenu(item), item])
menu_xml = self.__getXmlMenu(self.__getPath(item.get_parent()), dom, dom)
if visible:
self.__addXmlFilename(menu_xml, dom, item.get_desktop_file_id(), 'Include')
self.__writeItem(item, no_display=False)
else:
self.__addXmlFilename(menu_xml, dom, item.get_desktop_file_id(), 'Exclude')
self.__addXmlTextElement(menu_xml, 'AppDir', util.getUserItemPath(), dom)
elif item.get_type() == matemenu.TYPE_DIRECTORY:
self.__addUndo([self.__getMenu(item), item])
#don't mess with it if it's empty
if len(item.get_contents()) == 0:
return
menu_xml = self.__getXmlMenu(self.__getPath(item), dom, dom)
for node in self.__getXmlNodesByName(['Deleted', 'NotDeleted'], menu_xml):
node.parentNode.removeChild(node)
if visible:
self.__writeMenu(item, no_display=False)
else:
self.__writeMenu(item, no_display=True)
self.__addXmlTextElement(menu_xml, 'DirectoryDir', util.getUserDirectoryPath(), dom)
self.save()
def createItem(self, parent, icon, name, comment, command, use_term, before=None, after=None):
file_id = self.__writeItem(None, icon, name, comment, command, use_term)
self.insertExternalItem(file_id, parent.menu_id, before, after)
def insertExternalItem(self, file_id, parent_id, before=None, after=None):
parent = self.__findMenu(parent_id)
dom = self.__getMenu(parent).dom
self.__addItem(parent, file_id, dom)
self.__positionItem(parent, ('Item', file_id), before, after)
self.__addUndo([self.__getMenu(parent), ('Item', file_id)])
self.save()
def createMenu(self, parent, icon, name, comment, before=None, after=None):
file_id = self.__writeMenu(None, icon, name, comment)
self.insertExternalMenu(file_id, parent.menu_id, before, after)
def insertExternalMenu(self, file_id, parent_id, before=None, after=None):
menu_id = file_id.rsplit('.', 1)[0]
parent = self.__findMenu(parent_id)
dom = self.__getMenu(parent).dom
self.__addXmlDefaultLayout(self.__getXmlMenu(self.__getPath(parent), dom, dom) , dom)
menu_xml = self.__getXmlMenu(self.__getPath(parent) + '/' + menu_id, dom, dom)
self.__addXmlTextElement(menu_xml, 'Directory', file_id, dom)
self.__positionItem(parent, ('Menu', menu_id), before, after)
self.__addUndo([self.__getMenu(parent), ('Menu', file_id)])
self.save()
def createSeparator(self, parent, before=None, after=None):
self.__positionItem(parent, ('Separator',), before, after)
self.__addUndo([self.__getMenu(parent), ('Separator',)])
self.save()
def editItem(self, item, icon, name, comment, command, use_term, parent=None, final=True):
#if nothing changed don't make a user copy
if icon == item.get_icon() and name == item.get_display_name() and comment == item.get_comment() and command == item.get_exec() and use_term == item.get_launch_in_terminal():
return
#hack, item.get_parent() seems to fail a lot
if not parent:
parent = item.get_parent()
if final:
self.__addUndo([self.__getMenu(parent), item])
self.__writeItem(item, icon, name, comment, command, use_term)
if final:
dom = self.__getMenu(parent).dom
menu_xml = self.__getXmlMenu(self.__getPath(parent), dom, dom)
self.__addXmlTextElement(menu_xml, 'AppDir', util.getUserItemPath(), dom)
self.save()
def editMenu(self, menu, icon, name, comment, final=True):
#if nothing changed don't make a user copy
if icon == menu.get_icon() and name == menu.get_name() and comment == menu.get_comment():
return
#we don't use this, we just need to make sure the