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):
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)
292 val = self.realBookmarks and self.realBookmarks.value
293 if val and name in val:
295 self.realBookmarks.value = val
296 self.realBookmarks.save()
299 self[self.currList].up()
303 self[self.currList].down()
307 self[self.currList].pageUp()
311 self[self.currList].pageDown()
315 if self.currList == "filelist":
316 if self["filelist"].canDescent():
317 self["filelist"].descent()
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]
330 return self["booklist"].getCurrent()
332 def selectConfirmed(self, 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()
340 if self.bookmarks != self.realBookmarks.value:
341 self.realBookmarks.value = self.bookmarks
342 self.realBookmarks.save()
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
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)
360 # Ask User if he really wants to select this folder
361 self.session.openWithCallback(
362 self.selectConfirmed,
364 _("There might not be enough Space on the selected Partition.\nDo you really want to continue?"),
365 type = MessageBox.TYPE_YESNO
367 # No minimum free Space means we can safely close
369 self.selectConfirmed(True)
371 def changeName(self):
372 if self.filename != "":
373 # TODO: Add Information that changing extension is bad? disallow?
374 self.session.openWithCallback(
377 title = _("Please enter a new filename"),
381 def nameChanged(self, res):
389 _("An empty filename is illegal."),
390 type = MessageBox.TYPE_ERROR,
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
401 self["target"].setText(_("Invalid Location"))
404 if not self.userMode and self.realBookmarks:
405 if self.currList == "filelist":
407 (_("switch to bookmarks"), self.switchToBookList),
408 (_("add bookmark"), self.addRemoveBookmark)
412 (_("create directory"), self.createDir),
413 (_("remove directory"), self.removeDir)
417 (_("switch to filelist"), self.switchToFileList),
418 (_("remove bookmark"), self.addRemoveBookmark)
421 self.session.openWithCallback(
428 def menuCallback(self, choice):
432 def usermodeOn(self):
433 self.switchToBookList()
434 self["filelist"].hide()
435 self["key_blue"].hide()
437 def keyNumberGlobal(self, number):
441 # See if another key was pressed before
442 if number != self.lastKey:
443 # Reset lastKey again so NumericalTextInput triggers its keychange
446 # Try to select what was typed
452 # Get char and append to text
453 char = self.getKey(number)
454 self.quickselect = self.quickselect[:self.curr_pos] + unicode(char)
457 self.qs_timer_type = 0
458 self.qs_timer.start(1000, 1)
460 def selectByStart(self):
461 # Don't do anything on initial call
462 if not self.quickselect:
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()
473 # We select by filename which is absolute
474 lookfor = self["filelist"].getCurrentDirectory() + self.quickselect
476 # Select file starting with generated text
478 if file[0][0] and file[0][0].lower().startswith(lookfor):
479 self["filelist"].instance.moveSelectionTo(idx)
483 def timeout(self, force = False):
485 if not force and self.qs_timer_type == 0:
486 # Try to select what was typed
493 self.qs_timer_type = 1
495 # Start timeout again
496 self.qs_timer.start(1000, 1)
497 # Timeout Quickselect
499 # Eventually stop Timer
505 self.quickselect = ""
508 return str(type(self)) + "(" + self.text + ")"
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"
516 class TimeshiftLocationBox(LocationBox):
517 def __init__(self, session):
518 inhibitDirs = ["/bin", "/boot", "/dev", "/etc", "/lib", "/proc", "/sbin", "/sys", "/usr", "/var"]
519 LocationBox.__init__(
522 text = _("Where to save temporary timeshift recordings?"),
523 currDir = config.usage.timeshift_path.value,
524 bookmarks = config.usage.allowed_timeshift_paths,
527 inhibitDirs = inhibitDirs,
528 minFree = 1024 # the same requirement is hardcoded in servicedvb.cpp
530 self.skinName = "LocationBox"
533 config.usage.timeshift_path.cancel()
534 LocationBox.cancel(self)
536 def selectConfirmed(self, ret):
538 config.usage.timeshift_path.value = self.getPreferredFolder()
539 config.usage.timeshift_path.save()
540 LocationBox.selectConfirmed(self, ret)