pluginsort: add plugin specific translation support
[enigma2-plugins.git] / pluginsort / src / plugin.py
1 from . import _
2
3 # Plugin definition
4 from Plugins.Plugin import PluginDescriptor
5
6 from Components.config import config, ConfigSubsection, ConfigSet
7 from Screens import PluginBrowser
8 from Screens.MessageBox import MessageBox
9 from Screens.ChoiceBox import ChoiceBox
10 from Components.PluginComponent import PluginComponent, plugins
11 from Components.PluginList import PluginEntryComponent
12 from Tools.Directories import resolveFilename, fileExists, SCOPE_SKIN_IMAGE, SCOPE_PLUGINS
13
14 from Components.ActionMap import ActionMap, NumberActionMap
15 from operator import attrgetter # python 2.5+
16
17 from Components.MultiContent import MultiContentEntryText, MultiContentEntryPixmapAlphaTest
18
19 from enigma import eListboxPythonMultiContent, gFont
20 from Tools.LoadPixmap import LoadPixmap
21
22 from xml.etree.cElementTree import parse as cet_parse
23 try:
24         from xml.etree.cElementTree import ParseError
25 except ImportError, ie:
26         ParseError = SyntaxError
27
28 from shutil import copyfile, Error
29
30 XML_CONFIG = "/etc/enigma2/pluginsort.xml"
31
32 def SelectedPluginEntryComponent(plugin):
33         if plugin.icon is None:
34                 png = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, "skin_default/icons/plugin.png"))
35         else:
36                 png = plugin.icon
37
38         return [
39                 plugin,
40                 MultiContentEntryText(pos=(120, 5), size=(320, 25), font=0, text=plugin.name, backcolor_sel=1234566),
41                 MultiContentEntryText(pos=(120, 26), size=(320, 17), font=1, text=plugin.description, backcolor_sel=1234566),
42                 MultiContentEntryPixmapAlphaTest(pos=(10, 5), size=(100, 40), png = png, backcolor_sel=1234566)
43         ]
44
45 WHEREMAP = {}
46 pdict = PluginDescriptor.__dict__
47 for where in pdict:
48         if where.startswith('WHERE_'):
49                 WHEREMAP[where] = pdict[where]
50 del pdict
51 reverse = lambda map: dict(zip(map.values(), map.keys()))
52
53 class PluginWeights:
54         def __init__(self):
55                 self.plugins = {}
56                 self.load()
57
58         def load(self):
59                 if not fileExists(XML_CONFIG):
60                         return
61
62                 try:
63                         config = cet_parse(XML_CONFIG).getroot()
64                 except ParseError, pe:
65                         from time import time
66                         print "[PluginSort] Parse Error occured in configuration, backing it up and starting from scratch!"
67                         try:
68                                 copyfile(XML_CONFIG, "/etc/enigma2/pluginsort.xml.%d" % (int(time()),))
69                         except Error, she:
70                                 print "[PluginSort] Uh oh, failed to create the backup... I hope you have one anyway :D"
71                         return
72
73                 for wheresection in config.findall('where'):
74                         where = wheresection.get('type')
75                         whereid = WHEREMAP.get(where, None)
76                         whereplugins = wheresection.findall('plugin')
77                         if not whereid or not whereplugins:
78                                 print "[PluginSort] Ignoring section %s because of invalid id (%s) or no plugins" % (where, repr(whereid))
79                                 continue
80
81                         for plugin in whereplugins:
82                                 name = plugin.get('name')
83                                 try:
84                                         weight = int(plugin.get('weight'))
85                                 except ValueError, ve:
86                                         print "[PluginSort] Invalid weight of %s received for plugin %s, ignoring" % (repr(plugin.get('weight')), repr(name))
87                                 else:
88                                         self.plugins.setdefault(whereid, {})[name] = weight
89
90         def save(self):
91                 list = ['<?xml version="1.0" ?>\n<pluginsort>\n\n']
92                 append = list.append
93                 extend = list.extend
94
95                 idmap = reverse(WHEREMAP)
96                 for key in self.plugins.keys():
97                         whereplugins = self.plugins.get(key, None)
98                         if not whereplugins:
99                                 continue
100
101                         where = idmap[key]
102                         extend((' <where type="', str(where), '">\n'))
103                         for key, value in whereplugins.iteritems():
104                                 extend(('  <plugin name="', str(key), '" weight="', str(value), '" />\n'))
105                         append((' </where>\n'))
106                 append('\n</pluginsort>\n')
107                 
108                 file = open(XML_CONFIG, 'w')
109                 file.writelines(list)
110                 file.close()
111
112         def get(self, plugin):
113                 for x in plugin.where:
114                         whereplugins = self.plugins.get(x, None)
115                         weight = whereplugins and whereplugins.get(plugin.name, None)
116                         if weight is not None:
117                                 return weight
118                 return plugin.weight
119
120         def set(self, plugin):
121                 for x in plugin.where:
122                         whereplugins = self.plugins.get(x, None)
123                         if whereplugins:
124                                 whereplugins[plugin.name] = plugin.weight
125                         else:
126                                 self.plugins[x] = {plugin.name: plugin.weight}
127
128 pluginWeights = PluginWeights()
129
130 def PluginComponent_addPlugin(self, plugin, *args, **kwargs):
131         newWeight = pluginWeights.get(plugin)
132         print "[PluginSort] Setting weight of %s from %d to %d" % (plugin.name, plugin.weight, newWeight)
133         plugin.weight = newWeight
134         PluginComponent.pluginSorter_baseAddPlugin(self, plugin, *args, **kwargs)
135
136 OriginalPluginBrowser = PluginBrowser.PluginBrowser
137 class SortingPluginBrowser(OriginalPluginBrowser):
138         def __init__(self, *args, **kwargs):
139                 OriginalPluginBrowser.__init__(self, *args, **kwargs)
140                 self.skinName = ["SortingPluginBrowser", "PluginBrowser"]
141
142                 self.movemode = False
143                 self.selected = -1
144
145                 self["MenuActions"] = ActionMap(["MenuActions"],
146                         {
147                                 "menu": self.openMenu,
148                         }, -1
149                 )
150                 self["ColorActions"] = ActionMap(["ColorActions"],
151                         {
152                                 "green": self.toggleMoveMode,
153                         }, -1
154                 )
155                 self["ColorActions"].setEnabled(False)
156
157                 self["WizardActions"] = ActionMap(["WizardActions"],
158                         {
159                                 "left": self.left,
160                                 "right": self.right,
161                                 "up": self.up,
162                                 "down": self.down,
163                         }, -2
164                 )
165                 # TODO: allow to select first 10 plugins by number (1-9, 0)
166                 self["NumberActions"] = NumberActionMap(["NumberActions"],
167                         {
168                                 "1": self.keyNumberGlobal,
169                                 "2": self.keyNumberGlobal,
170                                 "3": self.keyNumberGlobal,
171                                 "4": self.keyNumberGlobal,
172                                 "5": self.keyNumberGlobal,
173                                 "6": self.keyNumberGlobal,
174                                 "7": self.keyNumberGlobal,
175                                 "8": self.keyNumberGlobal,
176                                 "9": self.keyNumberGlobal,
177                                 "0": self.keyNumberGlobal,
178                         }, -1
179                 )
180
181         def keyNumberGlobal(self, number):
182                 if not self.movemode:
183                         realnumber = (number - 1) % 10
184                         if realnumber < len(self.list):
185                                 self["list"].moveToIndex(realnumber)
186                                 self.save()
187
188         def close(self, *args, **kwargs):
189                 if self.movemode:
190                         self.toggleMoveMode()
191                 OriginalPluginBrowser.close(self, *args, **kwargs)
192
193         # copied from PluginBrowser because we redo pretty much anything :-)
194         def updateList(self):
195                 self.pluginlist = plugins.getPlugins(PluginDescriptor.WHERE_PLUGINMENU)
196                 self.pluginlist.sort(key=attrgetter('weight', 'name')) # sort first by weight, then by name; we get pretty much a weight sorted but otherwise random list
197                 self.list = [PluginEntryComponent(plugin) for plugin in self.pluginlist]
198                 self["list"].l.setList(self.list)
199                 if fileExists(resolveFilename(SCOPE_PLUGINS, "SystemPlugins/SoftwareManager/plugin.py")):
200                         # TRANSLATORS: leaving this empty is encouraged to not cause any confusion (this string was taken directly from the standard PluginBrowser)
201                         self["red"].setText(_("Manage extensions"))
202                         self["green"].setText(_("Sort") if not self.movemode else _("End Sort"))
203                         self["SoftwareActions"].setEnabled(True)
204                         self["PluginDownloadActions"].setEnabled(False)
205                         self["ColorActions"].setEnabled(True)
206                 else:
207                         # TRANSLATORS: leaving this empty is encouraged to not cause any confusion (this string was taken directly from the standard PluginBrowser)
208                         self["red"].setText(_("Remove Plugins"))
209                         # TRANSLATORS: leaving this empty is encouraged to not cause any confusion (this string was taken directly from the standard PluginBrowser)
210                         self["green"].setText(_("Download Plugins"))
211                         self["SoftwareActions"].setEnabled(False)
212                         self["PluginDownloadActions"].setEnabled(True)
213                         self["ColorActions"].setEnabled(False)
214
215         def doMove(self, func):
216                 if self.selected != -1:
217                         oldpos = self["list"].getSelectedIndex()
218                         func()
219                         entry = self.list.pop(oldpos)
220                         newpos = self["list"].getSelectedIndex()
221                         self.list.insert(newpos, entry)
222                         self["list"].l.setList(self.list)
223                 else:
224                         func()
225
226         def left(self):
227                 self.doMove(self["list"].pageUp)
228
229         def right(self):
230                 self.doMove(self["list"].pageDown)
231
232         def up(self):
233                 self.doMove(self["list"].up)
234
235         def down(self):
236                 self.doMove(self["list"].down)
237
238         def save(self):
239                 selected = self.selected
240                 if not self.movemode:
241                         OriginalPluginBrowser.save(self)
242                 elif selected != -1:
243                         Len = len(self.pluginlist)
244                         newpos = self["list"].getSelectedIndex()
245                         entry = self.pluginlist[selected]
246                         self.pluginlist.remove(entry)
247                         self.pluginlist.insert(newpos, entry)
248
249                         # we moved up, increase weight of plugins after us
250                         if newpos < selected:
251                                 print "[PluginSort]", entry.name, "moved up"
252                                 i = newpos + 1
253                                 # since we moved up, there has to be an entry after this one
254                                 diff = abs(self.pluginlist[i].weight - self.pluginlist[newpos].weight) + 1
255                                 print "[PluginSort] Using weight from %d (%d) and %d (%d) to calculate diff (%d)" % (i, self.pluginlist[i].weight, newpos, self.pluginlist[newpos].weight, diff)
256                                 while i < Len:
257                                         print "[PluginSort] INCREASE WEIGHT OF", self.pluginlist[i].name, "BY", diff
258                                         self.pluginlist[i].weight += diff
259                                         i += 1
260                         # we moved down, decrease weight of plugins before us
261                         elif newpos > selected:
262                                 print "[PluginSort]", entry.name, "moved down"
263                                 i = newpos - 1
264                                 # since we moved up, there has to be an entry before this one
265                                 diff = abs(self.pluginlist[newpos].weight - self.pluginlist[i].weight) + 1
266                                 print "[PluginSort] Using weight from %d (%d) and %d (%d) to calculate diff (%d)" % (newpos, self.pluginlist[newpos].weight, i, self.pluginlist[i].weight, diff)
267                                 while i > -1:
268                                         print "[PluginSort] DECREASE WEIGHT OF", self.pluginlist[i].name, "BY", diff
269                                         self.pluginlist[i].weight -= diff
270                                         i -= 1
271                         else:
272                                 print "[PluginSort]", entry.name, "did not move (%d to %d)?" % (selected, newpos)
273
274                         self.list = [PluginEntryComponent(plugin) for plugin in self.pluginlist]
275                         print "[PluginSort] NEW LIST:", [(plugin.name, plugin.weight) for plugin in self.pluginlist]
276                         self["list"].l.setList(self.list)
277                         self.selected = -1
278                 else:
279                         self.selected = self["list"].getSelectedIndex()
280                         self.list[self.selected] = SelectedPluginEntryComponent(self.pluginlist[self.selected])
281                         self["list"].l.setList(self.list)
282         
283         def openMenu(self):
284                 if fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/PluginHider/plugin.py")):
285                         if self.movemode:
286                                 # TRANSLATORS: there is no need to translate this string, as it was reused from e2 core
287                                 moveString = _("disable move mode")
288                         else:
289                                 # TRANSLATORS: there is no need to translate this string, as it was reused from e2 core
290                                 moveString = _("enable move mode")
291                         list = [
292                                 (_("hide selected plugin"), self.hidePlugin),
293                                 (moveString, self.toggleMoveMode),
294                         ]
295                         self.session.openWithCallback(
296                                 self.menuCallback,
297                                 ChoiceBox,
298                                 list = list,
299                         )
300                 else:
301                         self.toggleMoveMode()
302
303         def menuCallback(self, ret):
304                 ret and ret[1]()
305
306         def hidePlugin(self):
307                 try:
308                         from Plugins.Extensions.PluginHider.plugin import hidePlugin
309                 except Exception, e:
310                         self.session.open(MessageBox, _("Unable to load PluginHider"), MessageBox.TYPE_ERROR)
311                 else:
312                         hidePlugin(self["list"].l.getCurrentSelection()[0])
313
314                         # we were actually in move mode, so save the current position
315                         if self.selected != -1:
316                                 self.save()
317                         self.updateList()
318
319         def toggleMoveMode(self):
320                 if self.movemode:
321                         if self.selected != -1:
322                                 self.save()
323                         if fileExists(resolveFilename(SCOPE_PLUGINS, "SystemPlugins/SoftwareManager/plugin.py")):
324                                 self["green"].setText(_("Sort"))
325
326                         for plugin in self.pluginlist:
327                                 pluginWeights.set(plugin)
328                         pluginWeights.save()
329                 else:
330                         if fileExists(resolveFilename(SCOPE_PLUGINS, "SystemPlugins/SoftwareManager/plugin.py")):
331                                 self["green"].setText(_("End Sort"))
332                 self.movemode = not self.movemode
333
334 def autostart(reason, *args, **kwargs):
335         if reason == 0:
336                 PluginComponent.pluginSorter_baseAddPlugin = PluginComponent.addPlugin
337                 PluginComponent.addPlugin = PluginComponent_addPlugin
338
339                 # "fix" weight of plugins already added to list, future ones will be fixed automatically
340                 for plugin in plugins.getPlugins(PluginDescriptor.WHERE_PLUGINMENU):
341                         # enigma2 older than 3.3.2011 does not know plugin weights, so default them to 0 manually
342                         try:
343                                 newWeight = pluginWeights.get(plugin)
344                         except AttributeError, ae:
345                                 plugin.weight = 0
346                                 newWeight = 0
347                                 PluginDescriptor.weight = 0
348                                 print "[PluginSort] Introduced weight attribute to PluginDescriptor for old enigma2 (this message may show multiple times)"
349
350                         print "[PluginSort] Fixing weight for %s (was %d, now %d)" % (plugin.name, plugin.weight, newWeight)
351                         plugin.weight = newWeight
352
353                 PluginBrowser.PluginBrowser = SortingPluginBrowser
354         else:
355                 PluginComponent.addPlugin = PluginComponent.pluginSorter_baseAddPlugin
356                 PluginBrowser.PluginBrowser = OriginalPluginBrowser
357
358 def Plugins(**kwargs):
359         return [
360                 PluginDescriptor(
361                         where=PluginDescriptor.WHERE_AUTOSTART,
362                         fnc=autostart,
363                         needsRestart=False, # TODO: check this!
364                 ),
365         ]