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