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)
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:
400 if self.currList == "filelist":
402 (_("switch to bookmarks"), self.switchToBookList),
403 (_("add bookmark"), self.addRemoveBookmark)
407 (_("create directory"), self.createDir),
408 (_("remove directory"), self.removeDir)
412 (_("switch to filelist"), self.switchToFileList),
413 (_("remove bookmark"), self.addRemoveBookmark)
416 self.session.openWithCallback(
423 def menuCallback(self, choice):
427 def usermodeOn(self):
428 self.switchToBookList()
429 self["filelist"].hide()
430 self["key_blue"].hide()
432 def keyNumberGlobal(self, number):
436 # See if another key was pressed before
437 if number != self.lastKey:
438 # Reset lastKey again so NumericalTextInput triggers its keychange
441 # Try to select what was typed
447 # Get char and append to text
448 char = self.getKey(number)
449 self.quickselect = self.quickselect[:self.curr_pos] + unicode(char)
452 self.qs_timer_type = 0
453 self.qs_timer.start(1000, 1)
455 def selectByStart(self):
456 # Don't do anything on initial call
457 if not self.quickselect:
460 # Don't select if no dir
461 if self["filelist"].getCurrentDirectory():
462 # TODO: implement proper method in Components.FileList
463 files = self["filelist"].getFileList()
468 # We select by filename which is absolute
469 lookfor = self["filelist"].getCurrentDirectory() + self.quickselect
471 # Select file starting with generated text
473 if file[0][0] and file[0][0].lower().startswith(lookfor):
474 self["filelist"].instance.moveSelectionTo(idx)
478 def timeout(self, force = False):
480 if not force and self.qs_timer_type == 0:
481 # Try to select what was typed
488 self.qs_timer_type = 1
490 # Start timeout again
491 self.qs_timer.start(1000, 1)
492 # Timeout Quickselect
494 # Eventually stop Timer
500 self.quickselect = ""
503 return str(type(self)) + "(" + self.text + ")"
505 class MovieLocationBox(LocationBox):
506 def __init__(self, session, text, dir, minFree = None):
507 inhibitDirs = ["/bin", "/boot", "/dev", "/etc", "/lib", "/proc", "/sbin", "/sys", "/usr", "/var"]
508 LocationBox.__init__(self, session, text = text, currDir = dir, bookmarks = config.movielist.videodirs, autoAdd = True, editDir = True, inhibitDirs = inhibitDirs, minFree = minFree)
509 self.skinName = "LocationBox"
511 class TimeshiftLocationBox(LocationBox):
512 def __init__(self, session):
513 inhibitDirs = ["/bin", "/boot", "/dev", "/etc", "/lib", "/proc", "/sbin", "/sys", "/usr", "/var"]
514 LocationBox.__init__(
517 text = _("Where to save temporary timeshift recordings?"),
518 currDir = config.usage.timeshift_path.value,
519 bookmarks = config.usage.allowed_timeshift_paths,
522 inhibitDirs = inhibitDirs,
523 minFree = 1024 # the same requirement is hardcoded in servicedvb.cpp
525 self.skinName = "LocationBox"
528 config.usage.timeshift_path.cancel()
529 LocationBox.cancel(self)
531 def selectConfirmed(self, ret):
533 config.usage.timeshift_path.value = self.getPreferredFolder()
534 config.usage.timeshift_path.save()
535 LocationBox.selectConfirmed(self, ret)