2 # Generic Screen to select a path/filename combination
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
13 from Tools.BoundFunction import boundFunction
14 from Tools.Directories import *
15 from Components.config import config
19 from Tools.NumericalTextInput import NumericalTextInput
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
30 from enigma import eTimer
32 class LocationBox(Screen, NumericalTextInput, HelpableScreen):
33 """Simple Class similar to MessageBox / ChoiceBox but used to choose a folder/pathname combination"""
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" />
51 def __init__(self, session, text = "", filename = "", currDir = None, bookmarks = None, userMode = False, windowTitle = _("Select Location"), minFree = None, autoAdd = False, editDir = False, inhibitDirs = [], inhibitMounts = []):
53 Screen.__init__(self, session)
54 NumericalTextInput.__init__(self, handleTimeout = False)
55 HelpableScreen.__init__(self)
58 self.setUseableChars(u'1234567890abcdefghijklmnopqrstuvwxyz')
61 self.qs_timer = eTimer()
62 self.qs_timer.callback.append(self.timeout)
63 self.qs_timer_type = 0
65 # Initialize Quickselect
70 self["text"] = Label(text)
71 self["textbook"] = Label(_("Bookmarks"))
73 # Save parameters locally
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
85 self["filelist"] = FileList(currDir, showDirectories = True, showFiles = False, inhibitMounts = inhibitMounts, inhibitDirs = inhibitDirs)
88 self["booklist"] = MenuList(self.bookmarks)
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"))
96 # Background for Buttons
97 self["green"] = Pixmap()
98 self["yellow"] = Pixmap()
99 self["blue"] = Pixmap()
100 self["red"] = Pixmap()
103 self["target"] = Label()
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)
114 def action(self, contexts, action):
116 self.box.timeout(force = True)
118 return HelpableActionMap.action(self, contexts, action)
120 # Actions that will reset quickselect
121 self["WizardActions"] = LocationBoxActionMap(self, "WizardActions",
127 "ok": (self.ok, _("select")),
128 "back": (self.cancel, _("Cancel")),
131 self["ColorActions"] = LocationBoxActionMap(self, "ColorActions",
134 "green": self.select,
135 "yellow": self.changeName,
136 "blue": self.addRemoveBookmark,
139 self["EPGSelectActions"] = LocationBoxActionMap(self, "EPGSelectActions",
141 "prevBouquet": (self.switchToBookList, _("switch to bookmarks")),
142 "nextBouquet": (self.switchToFileList, _("switch to filelist")),
145 self["MenuActions"] = LocationBoxActionMap(self, "MenuActions",
147 "menu": (self.showMenu, _("menu")),
150 # Actions used by quickselect
151 self["NumberActions"] = NumberActionMap(["NumberActions"],
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
165 # Run some functions when shown
166 self.onShown.extend([
167 boundFunction(self.setTitle, windowTitle),
172 self.onLayoutFinish.append(self.switchToFileListOnStart)
174 # Make sure we remove our callback
175 self.onClose.append(self.disableTimer)
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))
184 self.switchToFileList()
186 def disableTimer(self):
187 self.qs_timer.callback.remove(self.timeout)
189 def showHideRename(self):
190 # Don't allow renaming when filename is empty
191 if self.filename == "":
192 self["key_yellow"].hide()
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")
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")
209 def addRemoveBookmark(self):
210 if self.currList == "filelist":
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)
219 if not self.userMode:
220 name = self["booklist"].getCurrent()
221 self.session.openWithCallback(
222 boundFunction(self.removeBookmark, name),
224 _("Do you really want to remove your bookmark of %s?") % (name),
227 def removeBookmark(self, name, ret):
230 if name in self.bookmarks:
231 self.bookmarks.remove(name)
232 self["booklist"].setList(self.bookmarks)
235 if self["filelist"].current_directory != None:
236 self.session.openWithCallback(
237 self.createDirCallback,
239 title = _("Please enter name of the new directory"),
243 def createDirCallback(self, res):
244 if res is not None and len(res):
245 path = os.path.join(self["filelist"].current_directory, res)
246 if not pathExists(path):
247 if not createDir(path):
250 _("Creating directory %s failed.") % (path),
251 type = MessageBox.TYPE_ERROR,
254 self["filelist"].refresh()
258 _("The path %s already exists.") % (path),
259 type = MessageBox.TYPE_ERROR,
264 sel = self["filelist"].getSelection()
265 if sel and pathExists(sel[0]):
266 self.session.openWithCallback(
267 boundFunction(self.removeDirCallback, sel[0]),
269 _("Do you really want to remove directory %s from the disk?") % (sel[0]),
270 type = MessageBox.TYPE_YESNO
275 _("Invalid directory selected: %s") % (sel[0]),
276 type = MessageBox.TYPE_ERROR,
280 def removeDirCallback(self, name, res):
282 if not removeDir(name):
285 _("Removing directory %s failed. (Maybe not empty.)") % (name),
286 type = MessageBox.TYPE_ERROR,
290 self["filelist"].refresh()
291 self.removeBookmark(name, True)
294 self[self.currList].up()
298 self[self.currList].down()
302 self[self.currList].pageUp()
306 self[self.currList].pageDown()
310 if self.currList == "filelist":
311 if self["filelist"].canDescent():
312 self["filelist"].descent()
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]
325 return self["booklist"].getCurrent()
327 def selectConfirmed(self, 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()
335 if self.bookmarks != self.realBookmarks.value:
336 self.realBookmarks.value = self.bookmarks
337 self.realBookmarks.save()
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
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)
355 # Ask User if he really wants to select this folder
356 self.session.openWithCallback(
357 self.selectConfirmed,
359 _("There might not be enough Space on the selected Partition.\nDo you really want to continue?"),
360 type = MessageBox.TYPE_YESNO
362 # No minimum free Space means we can safely close
364 self.selectConfirmed(True)
366 def changeName(self):
367 if self.filename != "":
368 # TODO: Add Information that changing extension is bad? disallow?
369 self.session.openWithCallback(
372 title = _("Please enter a new filename"),
376 def nameChanged(self, res):
384 _("An empty filename is illegal."),
385 type = MessageBox.TYPE_ERROR,
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
396 self["target"].setText(_("Invalid Location"))
399 if not self.userMode and self.realBookmarks:
401 if self.currList == "filelist":
402 menu.append((_("switch to bookmarks"), self.switchToBookList))
403 menu.append((_("add bookmark"), self.addRemoveBookmark))
405 menu.append((_("create directory"), self.createDir))
406 menu.append((_("remove directory"), self.removeDir))
408 menu.append((_("switch to filelist"), self.switchToFileList))
409 menu.append((_("remove bookmark"), self.addRemoveBookmark))
411 self.session.openWithCallback(
418 def menuCallback(self, choice):
422 def usermodeOn(self):
423 self.switchToBookList()
424 self["filelist"].hide()
425 self["key_blue"].hide()
427 def keyNumberGlobal(self, number):
431 # See if another key was pressed before
432 if number != self.lastKey:
433 # Reset lastKey again so NumericalTextInput triggers its keychange
436 # Try to select what was typed
442 # Get char and append to text
443 char = self.getKey(number)
444 self.quickselect = self.quickselect[:self.curr_pos] + unicode(char)
447 self.qs_timer_type = 0
448 self.qs_timer.start(1000, 1)
450 def selectByStart(self):
451 # Don't do anything on initial call
452 if not len(self.quickselect):
455 # Don't select if no dir
456 if self["filelist"].getCurrentDirectory():
457 # TODO: implement proper method in Components.FileList
458 files = self["filelist"].getFileList()
463 # We select by filename which is absolute
464 lookfor = self["filelist"].getCurrentDirectory() + self.quickselect
466 # Select file starting with generated text
468 if file[0][0] and file[0][0].lower().startswith(lookfor):
469 self["filelist"].instance.moveSelectionTo(idx)
473 def timeout(self, force = False):
475 if not force and self.qs_timer_type == 0:
476 # Try to select what was typed
483 self.qs_timer_type = 1
485 # Start timeout again
486 self.qs_timer.start(1000, 1)
487 # Timeout Quickselect
489 # Eventually stop Timer
495 self.quickselect = ""
498 return str(type(self)) + "(" + self.text + ")"
500 class MovieLocationBox(LocationBox):
501 skinName = "LocationBox"
503 def __init__(self, session, text, dir, minFree = None):
504 inhibitDirs = ["/bin", "/boot", "/dev", "/etc", "/lib", "/proc", "/sbin", "/sys", "/usr", "/var"]
505 LocationBox.__init__(self, session, text = text, currDir = dir, bookmarks = config.movielist.videodirs, autoAdd = True, editDir = True, inhibitDirs = inhibitDirs, minFree = minFree)
507 class TimeshiftLocationBox(LocationBox):
509 skinName = "LocationBox" # XXX: though we could use a custom skin or inherit the hardcoded one we stick with the original :-)
511 def __init__(self, session):
512 inhibitDirs = ["/bin", "/boot", "/dev", "/etc", "/lib", "/proc", "/sbin", "/sys", "/usr", "/var"]
513 LocationBox.__init__(
516 text = _("Where to save temporary timeshift recordings?"),
517 currDir = config.usage.timeshift_path.value,
518 bookmarks = config.usage.allowed_timeshift_paths,
521 inhibitDirs = inhibitDirs,
522 minFree = 1024 # XXX: the same requirement is hardcoded in servicedvb.cpp
526 config.usage.timeshift_path.cancel()
527 LocationBox.cancel(self)
529 def selectConfirmed(self, ret):
531 config.usage.timeshift_path.value = self.getPreferredFolder()
532 config.usage.timeshift_path.save()
533 LocationBox.selectConfirmed(self, ret)