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