[RecordTimer] fix repeat timer issue.
[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                                 val = self.realBookmarks and self.realBookmarks.value
293                                 if val and name in val:
294                                         val.remove(name)
295                                         self.realBookmarks.value = val
296                                         self.realBookmarks.save()
297
298         def up(self):
299                 self[self.currList].up()
300                 self.updateTarget()
301
302         def down(self):
303                 self[self.currList].down()
304                 self.updateTarget()
305
306         def left(self):
307                 self[self.currList].pageUp()
308                 self.updateTarget()
309
310         def right(self):
311                 self[self.currList].pageDown()
312                 self.updateTarget()
313
314         def ok(self):
315                 if self.currList == "filelist":
316                         if self["filelist"].canDescent():
317                                 self["filelist"].descent()
318                                 self.updateTarget()
319                 else:
320                         self.select()
321
322         def cancel(self):
323                 self.close(None)
324
325         def getPreferredFolder(self):
326                 if self.currList == "filelist":
327                         # XXX: We might want to change this for parent folder...
328                         return self["filelist"].getSelection()[0]
329                 else:
330                         return self["booklist"].getCurrent()
331
332         def selectConfirmed(self, ret):
333                 if ret:
334                         ret = ''.join((self.getPreferredFolder(), self.filename))
335                         if self.realBookmarks:
336                                 if self.autoAdd and not ret in self.bookmarks:
337                                         self.bookmarks.append(self.getPreferredFolder())
338                                         self.bookmarks.sort()
339
340                                 if self.bookmarks != self.realBookmarks.value:
341                                         self.realBookmarks.value = self.bookmarks
342                                         self.realBookmarks.save()
343                         self.close(ret)
344
345         def select(self):
346                 currentFolder = self.getPreferredFolder()
347                 # Do nothing unless current Directory is valid
348                 if currentFolder is not None:
349                         # Check if we need to have a minimum of free Space available
350                         if self.minFree is not None:
351                                 # Try to read fs stats
352                                 try:
353                                         s = os.statvfs(currentFolder)
354                                         if (s.f_bavail * s.f_bsize) / 1000000 > self.minFree:
355                                                 # Automatically confirm if we have enough free disk Space available
356                                                 return self.selectConfirmed(True)
357                                 except OSError:
358                                         pass
359
360                                 # Ask User if he really wants to select this folder
361                                 self.session.openWithCallback(
362                                         self.selectConfirmed,
363                                         MessageBox,
364                                         _("There might not be enough Space on the selected Partition.\nDo you really want to continue?"),
365                                         type = MessageBox.TYPE_YESNO
366                                 )
367                         # No minimum free Space means we can safely close
368                         else:
369                                 self.selectConfirmed(True)
370
371         def changeName(self):
372                 if self.filename != "":
373                         # TODO: Add Information that changing extension is bad? disallow?
374                         self.session.openWithCallback(
375                                 self.nameChanged,
376                                 InputBox,
377                                 title = _("Please enter a new filename"),
378                                 text = self.filename
379                         )
380
381         def nameChanged(self, res):
382                 if res is not None:
383                         if len(res):
384                                 self.filename = res
385                                 self.updateTarget()
386                         else:
387                                 self.session.open(
388                                         MessageBox,
389                                         _("An empty filename is illegal."),
390                                         type = MessageBox.TYPE_ERROR,
391                                         timeout = 5
392                                 )
393
394         def updateTarget(self):
395                 # Write Combination of Folder & Filename when Folder is valid
396                 currFolder = self.getPreferredFolder()
397                 if currFolder is not None:
398                         self["target"].setText(''.join((currFolder, self.filename)))
399                 # Display a Warning otherwise
400                 else:
401                         self["target"].setText(_("Invalid Location"))
402
403         def showMenu(self):
404                 if not self.userMode and self.realBookmarks:
405                         if self.currList == "filelist":
406                                 menu = [
407                                         (_("switch to bookmarks"), self.switchToBookList),
408                                         (_("add bookmark"), self.addRemoveBookmark)
409                                 ]
410                                 if self.editDir:
411                                         menu.extend((
412                                                 (_("create directory"), self.createDir),
413                                                 (_("remove directory"), self.removeDir)
414                                         ))
415                         else:
416                                 menu = (
417                                         (_("switch to filelist"), self.switchToFileList),
418                                         (_("remove bookmark"), self.addRemoveBookmark)
419                                 )
420
421                         self.session.openWithCallback(
422                                 self.menuCallback,
423                                 ChoiceBox,
424                                 title = "",
425                                 list = menu
426                         )
427
428         def menuCallback(self, choice):
429                 if choice:
430                         choice[1]()
431                         
432         def usermodeOn(self):
433                 self.switchToBookList()
434                 self["filelist"].hide()
435                 self["key_blue"].hide()
436
437         def keyNumberGlobal(self, number):
438                 # Cancel Timeout
439                 self.qs_timer.stop()
440
441                 # See if another key was pressed before
442                 if number != self.lastKey:
443                         # Reset lastKey again so NumericalTextInput triggers its keychange
444                         self.nextKey()
445
446                         # Try to select what was typed
447                         self.selectByStart()
448
449                         # Increment position
450                         self.curr_pos += 1
451
452                 # Get char and append to text
453                 char = self.getKey(number)
454                 self.quickselect = self.quickselect[:self.curr_pos] + unicode(char)
455
456                 # Start Timeout
457                 self.qs_timer_type = 0
458                 self.qs_timer.start(1000, 1)
459
460         def selectByStart(self):
461                 # Don't do anything on initial call
462                 if not self.quickselect:
463                         return
464
465                 # Don't select if no dir
466                 if self["filelist"].getCurrentDirectory():
467                         # TODO: implement proper method in Components.FileList
468                         files = self["filelist"].getFileList()
469
470                         # Initialize index
471                         idx = 0
472
473                         # We select by filename which is absolute
474                         lookfor = self["filelist"].getCurrentDirectory() + self.quickselect
475
476                         # Select file starting with generated text
477                         for file in files:
478                                 if file[0][0] and file[0][0].lower().startswith(lookfor):
479                                         self["filelist"].instance.moveSelectionTo(idx)
480                                         break
481                                 idx += 1
482
483         def timeout(self, force = False):
484                 # Timeout Key
485                 if not force and self.qs_timer_type == 0:
486                         # Try to select what was typed
487                         self.selectByStart()
488
489                         # Reset Key
490                         self.lastKey = -1
491
492                         # Change type
493                         self.qs_timer_type = 1
494
495                         # Start timeout again
496                         self.qs_timer.start(1000, 1)
497                 # Timeout Quickselect
498                 else:
499                         # Eventually stop Timer
500                         self.qs_timer.stop()
501
502                         # Invalidate
503                         self.lastKey = -1
504                         self.curr_pos = -1
505                         self.quickselect = ""
506
507         def __repr__(self):
508                 return str(type(self)) + "(" + self.text + ")"
509
510 class MovieLocationBox(LocationBox):
511         def __init__(self, session, text, dir, minFree = None):
512                 inhibitDirs = ["/bin", "/boot", "/dev", "/etc", "/lib", "/proc", "/sbin", "/sys", "/usr", "/var"]
513                 LocationBox.__init__(self, session, text = text, currDir = dir, bookmarks = config.movielist.videodirs, autoAdd = True, editDir = True, inhibitDirs = inhibitDirs, minFree = minFree)
514                 self.skinName = "LocationBox"
515
516 class TimeshiftLocationBox(LocationBox):
517         def __init__(self, session):
518                 inhibitDirs = ["/bin", "/boot", "/dev", "/etc", "/lib", "/proc", "/sbin", "/sys", "/usr", "/var"]
519                 LocationBox.__init__(
520                                 self,
521                                 session,
522                                 text = _("Where to save temporary timeshift recordings?"),
523                                 currDir = config.usage.timeshift_path.value,
524                                 bookmarks = config.usage.allowed_timeshift_paths,
525                                 autoAdd = True,
526                                 editDir = True,
527                                 inhibitDirs = inhibitDirs,
528                                 minFree = 1024 # the same requirement is hardcoded in servicedvb.cpp
529                 )
530                 self.skinName = "LocationBox"
531
532         def cancel(self):
533                 config.usage.timeshift_path.cancel()
534                 LocationBox.cancel(self)
535
536         def selectConfirmed(self, ret):
537                 if ret:
538                         config.usage.timeshift_path.value = self.getPreferredFolder()
539                         config.usage.timeshift_path.save()
540                         LocationBox.selectConfirmed(self, ret)
541