menusort: initial checkin
[enigma2-plugins.git] / menusort / src / plugin.py
1 # Plugin definition
2 from Plugins.Plugin import PluginDescriptor
3
4 from Screens.Menu import Menu, mdom
5
6 from Tools.BoundFunction import boundFunction
7 from Tools.Directories import fileExists
8
9 from enigma import eListboxPythonMultiContent, gFont, RT_HALIGN_LEFT, \
10                 RT_WRAP
11 from Components.MenuList import MenuList
12 from skin import parseColor, parseFont
13
14 from Components.ActionMap import ActionMap
15 from Components.SystemInfo import SystemInfo
16
17 from xml.etree.cElementTree import parse as cet_parse
18 try:
19         from xml.etree.cElementTree import ParseError
20 except ImportError, ie:
21         ParseError = SyntaxError
22
23 from shutil import copyfile, Error
24
25 XML_CONFIG = "/etc/enigma2/menusort.xml"
26 DEBUG = False
27
28 class baseMethods:
29         pass
30
31 class MenuWeights:
32         def __init__(self):
33                 # map text -> weight
34                 self.weights = {}
35                 self.load()
36
37         def load(self):
38                 if not fileExists(XML_CONFIG):
39                         return
40
41                 try:
42                         config = cet_parse(XML_CONFIG).getroot()
43                 except ParseError, pe:
44                         from time import time
45                         print "[MenuSort] Parse Error occured in configuration, backing it up and starting from scratch!"
46                         try:
47                                 copyfile(XML_CONFIG, "/etc/enigma2/menusort.xml.%d" % (int(time()),))
48                         except Error, she:
49                                 print "[MenuSort] Uh oh, failed to create the backup... I hope you have one anyway :D"
50                         return
51
52                 for node in config.findall('entry'):
53                         text = node.get('text', '').encode("UTF-8")
54                         weight = node.get("weight", None)
55                         try:
56                                 weight = int(weight)
57                         except ValueError, ve:
58                                 print "[MenuSort] Invalid value for weight on entry %s: %s" % (repr(text), repr(weight))
59                                 continue
60                         if not text or weight is None:
61                                 print "[MenuSort] Invalid entry in xml (%s, %s), ignoring" % (repr(text), repr(weight))
62                                 continue
63                         self.weights[text] = weight
64
65         def save(self):
66                 list = ['<?xml version="1.0" ?>\n<menusort>\n\n']
67                 append = list.append
68                 extend = list.extend
69
70                 for text, weight in self.weights.iteritems():
71                         extend((' <entry text="', str(text), '" weight="', str(weight), '" />\n'))
72                 append('\n</menusort>\n')
73
74                 file = open(XML_CONFIG, 'w')
75                 file.writelines(list)
76                 file.close()
77
78         def get(self, tuple):
79                 return int(self.weights.get(tuple[0], tuple[3]))
80
81         def cmp(self, first, second):
82                 return self.get(first) - self.get(second)
83
84         def set(self, tuple):
85                 self.weights[tuple[0]] = tuple[3]
86 menuWeights = MenuWeights()
87
88 def Menu__init__(self, session, parent, *args, **kwargs):
89         baseMethods.Menu__init__(self, session, parent, *args, **kwargs)
90         list = self["menu"].list
91         list.sort(cmp=menuWeights.cmp)
92         self["menu"].list = list
93
94 class SortableMenuList(MenuList):
95         def __init__(self, list):
96                 MenuList.__init__(self, list, False, content=eListboxPythonMultiContent)
97
98                 l = self.l
99                 l.setFont(0, gFont("Regular", 22))
100                 l.setBuildFunc(self.buildListboxEntry)
101                 self.selected = None
102                 self.selectedColor = 8388608
103
104         def invalidate(self):
105                 self.l.invalidate()
106
107         def applySkin(self, desktop, parent):
108                 attribs = [ ] 
109                 if self.skinAttributes is not None:
110                         for (attrib, value) in self.skinAttributes:
111                                 if attrib == "font":
112                                         self.l.setFont(0, parseFont(value, ((1,1),(1,1))))
113                                 elif attrib == "itemHeight":
114                                         self.l.setItemHeight(int(value))
115                                 elif attrib == "selectedColor":
116                                         self.selectedColor = int(parseColor(value))
117                                 else:
118                                         attribs.append((attrib, value))
119                 self.skinAttributes = attribs
120                 return MenuList.applySkin(self, desktop, parent)
121
122         def buildListboxEntry(self, *menu):
123                 size = self.l.getItemSize()
124                 height = size.height()
125                 width = size.width()
126
127                 l = [
128                         None,
129                         (eListboxPythonMultiContent.TYPE_TEXT, 0, 0, width, height, 0, RT_HALIGN_LEFT|RT_WRAP, menu[0]),
130                 ]
131                 if menu[0] == self.selected:
132                         l.insert(1, (eListboxPythonMultiContent.TYPE_TEXT, 0, 0, width, height, 0, RT_HALIGN_LEFT|RT_WRAP, '',  None, None, None, self.selectedColor, None, None))
133                 return l
134
135 class SortableMenu(Menu):
136         skin = """<screen name="SortableMenu" position="center,center" size="210,285">
137                 <widget source="title" render="Label" position="5,10" size="200,35" font="Regular;23" />
138                 <widget name="menu" position="5,55" size="200,225" scrollbarMode="showOnDemand" font="Regular;23" />
139                 </screen>"""
140         def __init__(self, *args, **kwargs):
141                 Menu.__init__(self, *args, **kwargs)
142                 self.skinName = "SortableMenu"
143
144                 self["menu"] = SortableMenuList(self["menu"].list) # XXX: not nice, but makes our life a little easier
145
146                 self["WizardActions"] = ActionMap(["WizardActions"],
147                         {
148                                 "left": boundFunction(self.doMove, self["menu"].pageUp),
149                                 "right": boundFunction(self.doMove, self["menu"].pageDown),
150                                 "up": boundFunction(self.doMove, self["menu"].up),
151                                 "down": boundFunction(self.doMove, self["menu"].down),
152                         }, -1
153                 )
154
155                 self["MenuSortActions"] = ActionMap(["MenuSortActions"],
156                         {
157                                 "ignore": lambda: None, # we need to overwrite some regular actions :-)
158                                 "toggleSelection": self.toggleSelection,
159                                 "selectEntry": self.okbuttonClick,
160                         }, -1
161                 )
162                 self.selected = -1
163
164         def createSummary(self):
165                 return None
166
167         # copied from original Menu for simplicity
168         def addMenu(self, destList, node):
169                 requires = node.get("requires")
170                 if requires:
171                         if requires[0] == '!':
172                                 if SystemInfo.get(requires[1:], False):
173                                         return
174                         elif not SystemInfo.get(requires, False):
175                                 return
176                 MenuTitle = _(node.get("text", "??").encode("UTF-8"))
177                 entryID = node.get("entryID", "undefined")
178                 weight = node.get("weight", 50)
179                 x = node.get("flushConfigOnClose")
180                 if x:
181                         a = boundFunction(self.session.openWithCallback, self.menuClosedWithConfigFlush, SortableMenu, node)
182                 else:
183                         a = boundFunction(self.session.openWithCallback, self.menuClosed, SortableMenu, node)
184                 #TODO add check if !empty(node.childNodes)
185                 destList.append((MenuTitle, a, entryID, weight))
186
187         def close(self, *args, **kwargs):
188                 for entry in self["menu"].list:
189                         menuWeights.set(entry)
190                 menuWeights.save()
191                 Menu.close(self, *args, **kwargs)
192
193         def doMove(self, func):
194                 if self.selected != -1:
195                         l = self["menu"].list
196                         oldpos = self["menu"].getSelectedIndex()
197                         func()
198                         entry = l.pop(oldpos)
199                         newpos = self["menu"].getSelectedIndex()
200                         l.insert(newpos, entry)
201                         self["menu"].setList(l)
202                 else:
203                         func()
204
205         def toggleSelection(self):
206                 selected = self.selected
207                 if selected != -1:
208                         l = self["menu"].list
209                         Len = len(l)
210                         newpos = self["menu"].getSelectedIndex()
211                         entry = l[newpos]
212
213                         # we moved up, increase weight of plugins after us
214                         if newpos < selected:
215                                 print "[MenuSort]", entry[0], "moved up"
216                                 i = newpos + 1
217                                 # since we moved up, there has to be an entry after this one
218                                 diff = abs(int(l[i][3]) - int(l[newpos][3])) + 1
219                                 print "[MenuSort] Using weight from %d (%d) and %d (%d) to calculate diff (%d)" % (i, int(l[i][3]), newpos, int(l[newpos][3]), diff)
220                                 while i < Len:
221                                         if DEBUG: print "[MenuSort] INCREASE WEIGHT OF", l[i][0], "BY", diff
222                                         l[i] = (l[i][0], l[i][1], l[i][2], int(l[i][3]) + diff)
223                                         i += 1
224                         # we moved down, decrease weight of plugins before us
225                         elif newpos > selected:
226                                 print "[MenuSort]", entry[0], "moved down"
227                                 i = newpos - 1
228                                 # since we moved up, there has to be an entry before this one
229                                 diff = abs(int(l[i][3]) - int(l[newpos][3])) + 1
230                                 print "[MenuSort] Using weight from %d (%d) and %d (%d) to calculate diff (%d)" % (newpos, int(l[newpos][3]), i, int(l[i][3]), diff)
231                                 while i > -1:
232                                         if DEBUG: print "[MenuSort] DECREASE WEIGHT OF", l[i][0], "BY", diff
233                                         l[i] = (l[i][0], l[i][1], l[i][2], int(l[i][3]) - diff)
234                                         i -= 1
235                         else:
236                                 if DEBUG: print "[MenuSort]", entry[0], "did not move (%d to %d)?" % (selected, newpos)
237
238                         if DEBUG: print "[MenuSort] NEW LIST:", l
239                         self["menu"].setList(l)
240                         self.selected = -1
241                         self["menu"].selected = None
242                 else:
243                         sel = self["menu"].getCurrent()
244                         if sel:
245                                 self["menu"].selected = sel[0]
246                                 self.selected = self["menu"].getSelectedIndex()
247                                 self["menu"].invalidate()
248                         else:
249                                 self.selected = -1
250
251         def keyNumberGlobal(self, number):
252                 pass
253
254 def autostart(reason, *args, **kwargs):
255         if reason == 0:
256                 try:
257                         baseMethods.Menu__init__
258                 except AttributeError, ae:
259                         pass
260                 else:
261                         print "[MenuSort] Initialized more than once, ignoring request."
262                         return
263
264                 baseMethods.Menu__init__ = Menu.__init__
265                 Menu.__init__ = Menu__init__
266         else:
267                 Menu.__init__ = baseMethods.Menu__init__
268
269 def main(session, *args, **kwargs):
270         session.open(SortableMenu, mdom.getroot())
271
272 def Plugins(**kwargs):
273         return [
274                 PluginDescriptor(
275                         where=PluginDescriptor.WHERE_AUTOSTART,
276                         fnc=autostart,
277                         needsRestart=False,
278                 ),
279                 PluginDescriptor(
280                         where=PluginDescriptor.WHERE_PLUGINMENU,
281                         name="MenuSort",
282                         description=_("Sort main menu"),
283                         fnc=main,
284                         needsRestart=False,
285                 ),
286         ]