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