po/de.po: - update german locale, thx racer_571 and JackDaniel
[vuplus_dvbapp] / lib / python / Screens / LocationBox.py
1 #
2 # Generic Screen to select a path/filename combination
3 #
4
5 # GUI (Screens)
6 from Screens.Screen import Screen
7 from Screens.MessageBox import MessageBox
8 from Screens.InputBox import InputBox
9 from Screens.HelpMenu import HelpableScreen
10 from Screens.ChoiceBox import ChoiceBox
11
12 # Generic
13 from Tools.BoundFunction import boundFunction
14 from Tools.Directories import *
15 from Components.config import config
16 import os
17
18 # Quickselect
19 from Tools.NumericalTextInput import NumericalTextInput
20
21 # GUI (Components)
22 from Components.ActionMap import NumberActionMap, HelpableActionMap
23 from Components.Label import Label
24 from Components.Pixmap import Pixmap
25 from Components.Button import Button
26 from Components.FileList import FileList
27 from Components.MenuList import MenuList
28
29 # Timer
30 from enigma import eTimer
31
32 class LocationBox(Screen, NumericalTextInput, HelpableScreen):
33         """Simple Class similar to MessageBox / ChoiceBox but used to choose a folder/pathname combination"""
34
35         skin = """<screen name="LocationBox" position="100,75" size="540,460" >
36                         <widget name="text" position="0,2" size="540,22" font="Regular;22" />
37                         <widget name="target" position="0,23" size="540,22" valign="center" font="Regular;22" />
38                         <widget name="filelist" position="0,55" zPosition="1" size="540,210" scrollbarMode="showOnDemand" selectionDisabled="1" />
39                         <widget name="textbook" position="0,272" size="540,22" font="Regular;22" />
40                         <widget name="booklist" position="5,302" zPosition="2" size="535,100" scrollbarMode="showOnDemand" />
41                         <widget name="red" position="0,415" zPosition="1" size="135,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
42                         <widget name="key_red" position="0,415" zPosition="2" size="135,40" halign="center" valign="center" font="Regular;22" transparent="1" shadowColor="black" shadowOffset="-1,-1" />   
43                         <widget name="green" position="135,415" zPosition="1" size="135,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
44                         <widget name="key_green" position="135,415" zPosition="2" size="135,40" halign="center" valign="center" font="Regular;22" transparent="1" shadowColor="black" shadowOffset="-1,-1" />
45                         <widget name="yellow" position="270,415" zPosition="1" size="135,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
46                         <widget name="key_yellow" position="270,415" zPosition="2" size="135,40" halign="center" valign="center" font="Regular;22" transparent="1" shadowColor="black" shadowOffset="-1,-1" />
47                         <widget name="blue" position="405,415" zPosition="1" size="135,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
48                         <widget name="key_blue" position="405,415" zPosition="2" size="135,40" halign="center" valign="center" font="Regular;22" transparent="1" shadowColor="black" shadowOffset="-1,-1" />            
49                 </screen>"""
50
51         def __init__(self, session, text = "", filename = "", currDir = None, bookmarks = None, userMode = False, windowTitle = _("Select Location"), minFree = None, autoAdd = False, editDir = False, inhibitDirs = [], inhibitMounts = []):
52                 # Init parents
53                 Screen.__init__(self, session)
54                 NumericalTextInput.__init__(self, handleTimeout = False)
55                 HelpableScreen.__init__(self)
56
57                 # Set useable chars
58                 self.setUseableChars(u'1234567890abcdefghijklmnopqrstuvwxyz')
59
60                 # Quickselect Timer
61                 self.qs_timer = eTimer()
62                 self.qs_timer.callback.append(self.timeout)
63                 self.qs_timer_type = 0
64
65                 # Initialize Quickselect
66                 self.curr_pos = -1
67                 self.quickselect = ""
68
69                 # Set Text
70                 self["text"] = Label(text)
71                 self["textbook"] = Label(_("Bookmarks"))
72
73                 # Save parameters locally
74                 self.text = text
75                 self.filename = filename
76                 self.minFree = minFree
77                 self.realBookmarks = bookmarks
78                 self.bookmarks = bookmarks and bookmarks.value[:] or []
79                 self.userMode = userMode
80                 self.autoAdd = autoAdd
81                 self.editDir = editDir
82                 self.inhibitDirs = inhibitDirs
83
84                 # Initialize FileList
85                 self["filelist"] = FileList(currDir, showDirectories = True, showFiles = False, inhibitMounts = inhibitMounts, inhibitDirs = inhibitDirs)
86
87                 # Initialize BookList
88                 self["booklist"] = MenuList(self.bookmarks)
89
90                 # Buttons
91                 self["key_green"] = Button(_("OK"))
92                 self["key_yellow"] = Button(_("Rename"))
93                 self["key_blue"] = Button(_("Remove Bookmark"))
94                 self["key_red"] = Button(_("Cancel"))
95
96                 # Background for Buttons
97                 self["green"] = Pixmap()
98                 self["yellow"] = Pixmap()
99                 self["blue"] = Pixmap()
100                 self["red"] = Pixmap()
101
102                 # Initialize Target
103                 self["target"] = Label()
104
105                 if self.userMode:
106                         self.usermodeOn()
107
108                 # Custom Action Handler
109                 class LocationBoxActionMap(HelpableActionMap):
110                         def __init__(self, parent, context, actions = { }, prio=0):
111                                 HelpableActionMap.__init__(self, parent, context, actions, prio)
112                                 self.box = parent
113
114                         def action(self, contexts, action):
115                                 # Reset Quickselect
116                                 self.box.timeout(force = True)
117
118                                 return HelpableActionMap.action(self, contexts, action)
119
120                 # Actions that will reset quickselect
121                 self["WizardActions"] = LocationBoxActionMap(self, "WizardActions",
122                         {
123                                 "left": self.left,
124                                 "right": self.right,
125                                 "up": self.up,
126                                 "down": self.down,
127                                 "ok": (self.ok, _("select")),
128                                 "back": (self.cancel, _("Cancel")),
129                         }, -2)
130
131                 self["ColorActions"] = LocationBoxActionMap(self, "ColorActions",
132                         {
133                                 "red": self.cancel,
134                                 "green": self.select,
135                                 "yellow": self.changeName,
136                                 "blue": self.addRemoveBookmark,
137                         }, -2)
138
139                 self["EPGSelectActions"] = LocationBoxActionMap(self, "EPGSelectActions",
140                         {
141                                 "prevBouquet": (self.switchToBookList, _("switch to bookmarks")),
142                                 "nextBouquet": (self.switchToFileList, _("switch to filelist")),
143                         }, -2)
144
145                 self["MenuActions"] = LocationBoxActionMap(self, "MenuActions",
146                         {
147                                 "menu": (self.showMenu, _("menu")),
148                         }, -2)
149
150                 # Actions used by quickselect
151                 self["NumberActions"] = NumberActionMap(["NumberActions"],
152                 {
153                         "1": self.keyNumberGlobal,
154                         "2": self.keyNumberGlobal,
155                         "3": self.keyNumberGlobal,
156                         "4": self.keyNumberGlobal,
157                         "5": self.keyNumberGlobal,
158                         "6": self.keyNumberGlobal,
159                         "7": self.keyNumberGlobal,
160                         "8": self.keyNumberGlobal,
161                         "9": self.keyNumberGlobal,
162                         "0": self.keyNumberGlobal
163                 })
164
165                 # Run some functions when shown
166                 self.onShown.extend((
167                         boundFunction(self.setTitle, windowTitle),
168                         self.updateTarget,
169                         self.showHideRename,
170                 ))
171
172                 self.onLayoutFinish.append(self.switchToFileListOnStart)
173  
174                 # Make sure we remove our callback
175                 self.onClose.append(self.disableTimer)
176
177         def switchToFileListOnStart(self):
178                 if self.realBookmarks and self.realBookmarks.value:
179                         self.currList = "booklist"
180                         currDir = self["filelist"].current_directory
181                         if currDir in self.bookmarks:
182                                 self["booklist"].moveToIndex(self.bookmarks.index(currDir))
183                 else:
184                         self.switchToFileList()
185
186         def disableTimer(self):
187                 self.qs_timer.callback.remove(self.timeout)
188
189         def showHideRename(self):
190                 # Don't allow renaming when filename is empty
191                 if self.filename == "":
192                         self["key_yellow"].hide()
193
194         def switchToFileList(self):
195                 if not self.userMode:
196                         self.currList = "filelist"
197                         self["filelist"].selectionEnabled(1)
198                         self["booklist"].selectionEnabled(0)
199                         self["key_blue"].text = _("Add Bookmark")
200                         self.updateTarget()
201
202         def switchToBookList(self):
203                 self.currList = "booklist"
204                 self["filelist"].selectionEnabled(0)
205                 self["booklist"].selectionEnabled(1)
206                 self["key_blue"].text = _("Remove Bookmark")
207                 self.updateTarget()
208
209         def addRemoveBookmark(self):
210                 if self.currList == "filelist":
211                         # add bookmark
212                         folder = self["filelist"].getSelection()[0]
213                         if folder is not None and not folder in self.bookmarks:
214                                 self.bookmarks.append(folder)
215                                 self.bookmarks.sort()
216                                 self["booklist"].setList(self.bookmarks)
217                 else:
218                         # remove bookmark
219                         if not self.userMode:
220                                 name = self["booklist"].getCurrent()
221                                 self.session.openWithCallback(
222                                         boundFunction(self.removeBookmark, name),
223                                         MessageBox,
224                                         _("Do you really want to remove your bookmark of %s?") % (name),
225                                 )
226
227         def removeBookmark(self, name, ret):
228                 if not ret:
229                         return
230                 if name in self.bookmarks:
231                         self.bookmarks.remove(name)
232                         self["booklist"].setList(self.bookmarks)
233
234         def createDir(self):
235                 if self["filelist"].current_directory != None:
236                         self.session.openWithCallback(
237                                 self.createDirCallback,
238                                 InputBox,
239                                 title = _("Please enter name of the new directory"),
240                                 text = ""
241                         )
242
243         def createDirCallback(self, res):
244                 if res:
245                         path = os.path.join(self["filelist"].current_directory, res)
246                         if not pathExists(path):
247                                 if not createDir(path):
248                                         self.session.open(
249                                                 MessageBox,
250                                                 _("Creating directory %s failed.") % (path),
251                                                 type = MessageBox.TYPE_ERROR,
252                                                 timeout = 5
253                                         )
254                                 self["filelist"].refresh()
255                         else:
256                                 self.session.open(
257                                         MessageBox,
258                                         _("The path %s already exists.") % (path),
259                                         type = MessageBox.TYPE_ERROR,
260                                         timeout = 5
261                                 )
262
263         def removeDir(self):
264                 sel = self["filelist"].getSelection()
265                 if sel and pathExists(sel[0]):
266                         self.session.openWithCallback(
267                                 boundFunction(self.removeDirCallback, sel[0]),
268                                 MessageBox,
269                                 _("Do you really want to remove directory %s from the disk?") % (sel[0]),
270                                 type = MessageBox.TYPE_YESNO
271                         )
272                 else:
273                         self.session.open(
274                                 MessageBox,
275                                 _("Invalid directory selected: %s") % (sel[0]),
276                                 type = MessageBox.TYPE_ERROR,
277                                 timeout = 5
278                         )
279
280         def removeDirCallback(self, name, res):
281                 if res:
282                         if not removeDir(name):
283                                 self.session.open(
284                                         MessageBox,
285                                         _("Removing directory %s failed. (Maybe not empty.)") % (name),
286                                         type = MessageBox.TYPE_ERROR,
287                                         timeout = 5
288                                 )
289                         else:
290                                 self["filelist"].refresh()
291                                 self.removeBookmark(name, True)
292
293         def up(self):
294                 self[self.currList].up()
295                 self.updateTarget()
296
297         def down(self):
298                 self[self.currList].down()
299                 self.updateTarget()
300
301         def left(self):
302                 self[self.currList].pageUp()
303                 self.updateTarget()
304
305         def right(self):
306                 self[self.currList].pageDown()
307                 self.updateTarget()
308
309         def ok(self):
310                 if self.currList == "filelist":
311                         if self["filelist"].canDescent():
312                                 self["filelist"].descent()
313                                 self.updateTarget()
314                 else:
315                         self.select()
316
317         def cancel(self):
318                 self.close(None)
319
320         def getPreferredFolder(self):
321                 if self.currList == "filelist":
322                         # XXX: We might want to change this for parent folder...
323                         return self["filelist"].getSelection()[0]
324                 else:
325                         return self["booklist"].getCurrent()
326
327         def selectConfirmed(self, ret):
328                 if ret:
329                         ret = ''.join((self.getPreferredFolder(), self.filename))
330                         if self.realBookmarks:
331                                 if self.autoAdd and not ret in self.bookmarks:
332                                         self.bookmarks.append(self.getPreferredFolder())
333                                         self.bookmarks.sort()
334
335                                 if self.bookmarks != self.realBookmarks.value:
336                                         self.realBookmarks.value = self.bookmarks
337                                         self.realBookmarks.save()
338                         self.close(ret)
339
340         def select(self):
341                 currentFolder = self.getPreferredFolder()
342                 # Do nothing unless current Directory is valid
343                 if currentFolder is not None:
344                         # Check if we need to have a minimum of free Space available
345                         if self.minFree is not None:
346                                 # Try to read fs stats
347                                 try:
348                                         s = os.statvfs(currentFolder)
349                                         if (s.f_bavail * s.f_bsize) / 1000000 > self.minFree:
350                                                 # Automatically confirm if we have enough free disk Space available
351                                                 return self.selectConfirmed(True)
352                                 except OSError:
353                                         pass
354
355                                 # Ask User if he really wants to select this folder
356                                 self.session.openWithCallback(
357                                         self.selectConfirmed,
358                                         MessageBox,
359                                         _("There might not be enough Space on the selected Partition.\nDo you really want to continue?"),
360                                         type = MessageBox.TYPE_YESNO
361                                 )
362                         # No minimum free Space means we can safely close
363                         else:
364                                 self.selectConfirmed(True)
365
366         def changeName(self):
367                 if self.filename != "":
368                         # TODO: Add Information that changing extension is bad? disallow?
369                         self.session.openWithCallback(
370                                 self.nameChanged,
371                                 InputBox,
372                                 title = _("Please enter a new filename"),
373                                 text = self.filename
374                         )
375
376         def nameChanged(self, res):
377                 if res is not None:
378                         if len(res):
379                                 self.filename = res
380                                 self.updateTarget()
381                         else:
382                                 self.session.open(
383                                         MessageBox,
384                                         _("An empty filename is illegal."),
385                                         type = MessageBox.TYPE_ERROR,
386                                         timeout = 5
387                                 )
388
389         def updateTarget(self):
390                 # Write Combination of Folder & Filename when Folder is valid
391                 currFolder = self.getPreferredFolder()
392                 if currFolder is not None:
393                         self["target"].setText(''.join((currFolder, self.filename)))
394                 # Display a Warning otherwise
395                 else:
396                         self["target"].setText(_("Invalid Location"))
397
398         def showMenu(self):
399                 if not self.userMode and self.realBookmarks:
400                         if self.currList == "filelist":
401                                 menu = [
402                                         (_("switch to bookmarks"), self.switchToBookList),
403                                         (_("add bookmark"), self.addRemoveBookmark)
404                                 ]
405                                 if self.editDir:
406                                         menu.extend((
407                                                 (_("create directory"), self.createDir),
408                                                 (_("remove directory"), self.removeDir)
409                                         ))
410                         else:
411                                 menu = (
412                                         (_("switch to filelist"), self.switchToFileList),
413                                         (_("remove bookmark"), self.addRemoveBookmark)
414                                 )
415
416                         self.session.openWithCallback(
417                                 self.menuCallback,
418                                 ChoiceBox,
419                                 title = "",
420                                 list = menu
421                         )
422
423         def menuCallback(self, choice):
424                 if choice:
425                         choice[1]()
426                         
427         def usermodeOn(self):
428                 self.switchToBookList()
429                 self["filelist"].hide()
430                 self["key_blue"].hide()
431
432         def keyNumberGlobal(self, number):
433                 # Cancel Timeout
434                 self.qs_timer.stop()
435
436                 # See if another key was pressed before
437                 if number != self.lastKey:
438                         # Reset lastKey again so NumericalTextInput triggers its keychange
439                         self.nextKey()
440
441                         # Try to select what was typed
442                         self.selectByStart()
443
444                         # Increment position
445                         self.curr_pos += 1
446
447                 # Get char and append to text
448                 char = self.getKey(number)
449                 self.quickselect = self.quickselect[:self.curr_pos] + unicode(char)
450
451                 # Start Timeout
452                 self.qs_timer_type = 0
453                 self.qs_timer.start(1000, 1)
454
455         def selectByStart(self):
456                 # Don't do anything on initial call
457                 if not self.quickselect:
458                         return
459
460                 # Don't select if no dir
461                 if self["filelist"].getCurrentDirectory():
462                         # TODO: implement proper method in Components.FileList
463                         files = self["filelist"].getFileList()
464
465                         # Initialize index
466                         idx = 0
467
468                         # We select by filename which is absolute
469                         lookfor = self["filelist"].getCurrentDirectory() + self.quickselect
470
471                         # Select file starting with generated text
472                         for file in files:
473                                 if file[0][0] and file[0][0].lower().startswith(lookfor):
474                                         self["filelist"].instance.moveSelectionTo(idx)
475                                         break
476                                 idx += 1
477
478         def timeout(self, force = False):
479                 # Timeout Key
480                 if not force and self.qs_timer_type == 0:
481                         # Try to select what was typed
482                         self.selectByStart()
483
484                         # Reset Key
485                         self.lastKey = -1
486
487                         # Change type
488                         self.qs_timer_type = 1
489
490                         # Start timeout again
491                         self.qs_timer.start(1000, 1)
492                 # Timeout Quickselect
493                 else:
494                         # Eventually stop Timer
495                         self.qs_timer.stop()
496
497                         # Invalidate
498                         self.lastKey = -1
499                         self.curr_pos = -1
500                         self.quickselect = ""
501
502         def __repr__(self):
503                 return str(type(self)) + "(" + self.text + ")"
504
505 class MovieLocationBox(LocationBox):
506         def __init__(self, session, text, dir, minFree = None):
507                 inhibitDirs = ["/bin", "/boot", "/dev", "/etc", "/lib", "/proc", "/sbin", "/sys", "/usr", "/var"]
508                 LocationBox.__init__(self, session, text = text, currDir = dir, bookmarks = config.movielist.videodirs, autoAdd = True, editDir = True, inhibitDirs = inhibitDirs, minFree = minFree)
509                 self.skinName = "LocationBox"
510
511 class TimeshiftLocationBox(LocationBox):
512         def __init__(self, session):
513                 inhibitDirs = ["/bin", "/boot", "/dev", "/etc", "/lib", "/proc", "/sbin", "/sys", "/usr", "/var"]
514                 LocationBox.__init__(
515                                 self,
516                                 session,
517                                 text = _("Where to save temporary timeshift recordings?"),
518                                 currDir = config.usage.timeshift_path.value,
519                                 bookmarks = config.usage.allowed_timeshift_paths,
520                                 autoAdd = True,
521                                 editDir = True,
522                                 inhibitDirs = inhibitDirs,
523                                 minFree = 1024 # the same requirement is hardcoded in servicedvb.cpp
524                 )
525                 self.skinName = "LocationBox"
526
527         def cancel(self):
528                 config.usage.timeshift_path.cancel()
529                 LocationBox.cancel(self)
530
531         def selectConfirmed(self, ret):
532                 if ret:
533                         config.usage.timeshift_path.value = self.getPreferredFolder()
534                         config.usage.timeshift_path.save()
535                         LocationBox.selectConfirmed(self, ret)
536