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