From ba4c219da50c06c732ea9899d2b4e3679066aecc Mon Sep 17 00:00:00 2001 From: Felix Domke Date: Wed, 20 Feb 2008 00:36:00 +0000 Subject: [PATCH] timer_select.patch by Moritz Venn, with minor fixes --- data/skin_default.xml | 6 +- lib/python/Screens/LocationBox.py | 292 ++++++++++++++++++++++++++++++ lib/python/Screens/Makefile.am | 2 +- lib/python/Screens/TimerEntry.py | 45 ++++- lib/python/Tools/Directories.py | 9 +- 5 files changed, 345 insertions(+), 9 deletions(-) create mode 100644 lib/python/Screens/LocationBox.py diff --git a/data/skin_default.xml b/data/skin_default.xml index 0288337e..d0eabca0 100644 --- a/data/skin_default.xml +++ b/data/skin_default.xml @@ -769,9 +769,11 @@ self.instance.move(ePoint(orgpos.x() + (orgwidth - newwidth)/2, orgpos.y())) + - - + + + diff --git a/lib/python/Screens/LocationBox.py b/lib/python/Screens/LocationBox.py new file mode 100644 index 00000000..b15fece5 --- /dev/null +++ b/lib/python/Screens/LocationBox.py @@ -0,0 +1,292 @@ +# +# Generic Screen to select a path/filename combination +# + +# GUI (Screens) +from Screens.Screen import Screen +from Screens.MessageBox import MessageBox +from Screens.InputBox import InputBox + +# Generic +from Tools.BoundFunction import boundFunction + +# Quickselect +from Tools.NumericalTextInput import NumericalTextInput + +# GUI (Components) +from Components.ActionMap import NumberActionMap +from Components.Label import Label +from Components.Pixmap import Pixmap +from Components.Button import Button +from Components.FileList import FileList + +# Timer +from enigma import eTimer + +class LocationBox(Screen, NumericalTextInput): + """Simple Class similar to MessageBox / ChoiceBox but used to choose a folder/pathname combination""" + + skin = """ + + + + + + + + """ + + def __init__(self, session, text = "", filename = "", currDir = None, windowTitle = _("Select Location"), minFree = None): + # Init parents + Screen.__init__(self, session) + NumericalTextInput.__init__(self, handleTimeout = False) + + # Set useable chars + self.setUseableChars(u'1234567890abcdefghijklmnopqrstuvwxyz') + + # Quickselect Timer + self.qs_timer = eTimer() + self.qs_timer.callback.append(self.timeout) + self.qs_timer_type = 0 + + # Initialize Quickselect + self.curr_pos = -1 + self.quickselect = "" + + # Set Text + self["text"] = Label(text) + + # Save parameters locally + self.text = text + self.filename = filename + self.minFree = minFree + + # Initialize FileList + self["filelist"] = FileList(currDir, showDirectories = True, showFiles = False) + + # Buttons + self["key_green"] = Button(_("Confirm")) + self["key_yellow"] = Button(_("Rename")) + + # Background for Buttons + self["green"] = Pixmap() + self["yellow"] = Pixmap() + + # Initialize Target + self["target"] = Label() + + # Custom Action Handler + class LocationBoxActionMap(NumberActionMap): + def __init__(self, box, contexts = [ ], actions = { }, prio=0): + NumberActionMap.__init__(self, contexts, actions, prio) + self.box = box + + def action(self, contexts, action): + # Reset Quickselect + self.box.timeout(force = True) + + return NumberActionMap.action(self, contexts, action) + + # Actions that will reset quickselect + self["actions"] = LocationBoxActionMap(self, ["WizardActions", "ColorActions"], + { + "ok": self.ok, + "back": self.cancel, + "green": self.select, + "yellow": self.changeName, + "left": self.left, + "right": self.right, + "up": self.up, + "down": self.down, + }, -2) + + # Actions used by quickselect + self["NumberActions"] = NumberActionMap(["NumberActions"], + { + "1": self.keyNumberGlobal, + "2": self.keyNumberGlobal, + "3": self.keyNumberGlobal, + "4": self.keyNumberGlobal, + "5": self.keyNumberGlobal, + "6": self.keyNumberGlobal, + "7": self.keyNumberGlobal, + "8": self.keyNumberGlobal, + "9": self.keyNumberGlobal, + "0": self.keyNumberGlobal + }) + + # Run some functions when shown + self.onShown.extend([ + boundFunction(self.setTitle, windowTitle), + self.updateTarget, + self.showHideRename + ]) + + # Make sure we remove our callback + self.onClose.append(self.disableTimer) + + def disableTimer(self): + self.qs_timer.callback.remove(self.timeout) + + def showHideRename(self): + # Don't allow renaming when filename is empty + if self.filename == "": + self["yellow"].hide() + self["key_yellow"].hide() + + def up(self): + self["filelist"].up() + + def down(self): + self["filelist"].down() + + def left(self): + self["filelist"].pageUp() + + def right(self): + self["filelist"].pageDown() + + def ok(self): + if self["filelist"].canDescent(): + self["filelist"].descent() + self.updateTarget() + + def cancel(self): + self.close(None) + + def selectConfirmed(self, res): + if res: + self.close(''.join([self["filelist"].getCurrentDirectory(), self.filename])) + + def select(self): + # Do nothing unless current Directory is valid + if self["filelist"].getCurrentDirectory() is not None: + # Check if we need to have a minimum of free Space available + if self.minFree is not None: + # Try to read fs stats + try: + from os import statvfs + + s = statvfs(self["filelist"].getCurrentDirectory()) + if (s.f_bavail * s.f_bsize) / 1000000 > self.minFree: + # Automatically confirm if we have enough free disk Space available + return self.selectConfirmed(True) + except OSError: + pass + + # Ask User if he really wants to select this folder + self.session.openWithCallback( + self.selectConfirmed, + MessageBox, + _("There might not be enough Space on the selected Partition.\nDo you really want to continue?"), + type = MessageBox.TYPE_YESNO + ) + # No minimum free Space means we can safely close + else: + self.selectConfirmed(True) + + def changeName(self): + if self.filename != "": + # TODO: Add Information that changing extension is bad? disallow? + # TODO: decide if using an inputbox is ok - we could also keep this in here + self.session.openWithCallback( + self.nameChanged, + InputBox, + title = _("Please enter a new filename"), + text = self.filename + ) + + def nameChanged(self, res): + if res is not None: + if len(res): + self.filename = res + self.updateTarget() + else: + self.session.open( + MessageBox, + _("An empty filename is illegal."), + type = MessageBox.TYPE_ERROR, + timeout = 5 + ) + + def updateTarget(self): + # Write Combination of Folder & Filename when Folder is valid + if self["filelist"].getCurrentDirectory() is not None: + self["target"].setText(''.join([self["filelist"].getCurrentDirectory(), self.filename])) + # Warning else + else: + self["target"].setText(_("Invalid Location")) + + def keyNumberGlobal(self, number): + # Cancel Timeout + self.qs_timer.stop() + + # See if another key was pressed before + if number != self.lastKey: + # Reset lastKey again so NumericalTextInput triggers its keychange + self.nextKey() + + # Try to select what was typed + self.selectByStart() + + # Increment position + self.curr_pos += 1 + + # Get char and append to text + char = self.getKey(number) + self.quickselect = self.quickselect[:self.curr_pos] + unicode(char) + + # Start Timeout + self.qs_timer_type = 0 + self.qs_timer.start(1000, 1) + + def selectByStart(self): + # Don't do anything on initial call + if not len(self.quickselect): + return + + # Don't select if no dir + if self["filelist"].getCurrentDirectory(): + # TODO: implement proper method in Components.FileList + files = self["filelist"].getFileList() + + # Initialize index + idx = 0 + + # We select by filename which is absolute + lookfor = self["filelist"].getCurrentDirectory() + self.quickselect + + # Select file starting with generated text + for file in files: + if file[0][0] and file[0][0].lower().startswith(lookfor): + self["filelist"].instance.moveSelectionTo(idx) + break + idx += 1 + + def timeout(self, force = False): + # Timeout Key + if not force and self.qs_timer_type == 0: + # Try to select what was typed + self.selectByStart() + + # Reset Key + self.lastKey = -1 + + # Change type + self.qs_timer_type = 1 + + # Start timeout again + self.qs_timer.start(1000, 1) + # Timeout Quickselect + else: + # Eventually stop Timer + self.qs_timer.stop() + + # Invalidate + self.lastKey = -1 + self.curr_pos = -1 + self.quickselect = "" + + def __repr__(self): + return str(type(self)) + "(" + self.text + ")" + diff --git a/lib/python/Screens/Makefile.am b/lib/python/Screens/Makefile.am index aeff3151..068ce442 100644 --- a/lib/python/Screens/Makefile.am +++ b/lib/python/Screens/Makefile.am @@ -12,4 +12,4 @@ install_PYTHON = \ Console.py InputBox.py ChoiceBox.py SimpleSummary.py ImageWizard.py \ TimerSelection.py PictureInPicture.py TimeDateInput.py \ SubtitleDisplay.py SubservicesQuickzap.py ParentalControlSetup.py NumericalTextInputHelpDialog.py \ - SleepTimerEdit.py Ipkg.py RdsDisplay.py Globals.py SessionGlobals.py + SleepTimerEdit.py Ipkg.py RdsDisplay.py Globals.py SessionGlobals.py LocationBox.py diff --git a/lib/python/Screens/TimerEntry.py b/lib/python/Screens/TimerEntry.py index 3541d36d..d11c5c2a 100644 --- a/lib/python/Screens/TimerEntry.py +++ b/lib/python/Screens/TimerEntry.py @@ -1,7 +1,8 @@ from Screen import Screen +from LocationBox import LocationBox import ChannelSelection from ServiceReference import ServiceReference -from Components.config import ConfigSelection, ConfigText, ConfigSubList, ConfigDateTime, ConfigClock, ConfigYesNo, getConfigListEntry +from Components.config import config, ConfigSelection, ConfigText, ConfigSubList, ConfigDateTime, ConfigClock, ConfigYesNo, getConfigListEntry from Components.ActionMap import NumberActionMap from Components.ConfigList import ConfigListScreen from Components.MenuList import MenuList @@ -25,22 +26,32 @@ class TimerEntry(Screen, ConfigListScreen): self["oktext"] = Label(_("OK")) self["canceltext"] = Label(_("Cancel")) + self["locationtext"] = Label(_("Choose Location")) self["ok"] = Pixmap() self["cancel"] = Pixmap() + self["location"] = Pixmap() self.createConfig() - self["actions"] = NumberActionMap(["SetupActions"], + self["actions"] = NumberActionMap(["SetupActions", "ColorActions"], { "ok": self.keySelect, "save": self.keyGo, "cancel": self.keyCancel, + "yellow": self.selectPath, }, -2) self.list = [] ConfigListScreen.__init__(self, self.list, session = session) self.createSetup("config") + self.onLayoutFinish.append(self.handleLocation) + + def handleLocation(self): + if config.usage.setup_level.index < 2: # -expert + self["locationtext"].hide() + self["location"].hide() + def createConfig(self): justplay = self.timer.justplay @@ -95,6 +106,8 @@ class TimerEntry(Screen, ConfigListScreen): self.timerentry_enddate = ConfigDateTime(default = self.timer.end, formatstring = _("%d.%B %Y"), increment = 86400) self.timerentry_endtime = ConfigClock(default = self.timer.end) + self.timerentry_dirname = ConfigSelection(choices = [self.timer.dirname or "/hdd/movie/"]) + self.timerentry_repeatedbegindate = ConfigDateTime(default = self.timer.repeatedbegindate, formatstring = _("%d.%B %Y"), increment = 86400) self.timerentry_weekday = ConfigSelection(default = weekday_table[weekday], choices = [("mon",_("Monday")), ("tue", _("Tuesday")), ("wed",_("Wednesday")), ("thu", _("Thursday")), ("fri", _("Friday")), ("sat", _("Saturday")), ("sun", _("Sunday"))]) @@ -177,6 +190,8 @@ class TimerEntry(Screen, ConfigListScreen): self.list.append(getConfigListEntry(_("EndTime"), self.timerentry_endtime)) if self.timerentry_justplay.value != "zap": + if config.usage.setup_level.index >= 2: # expert+ + self.list.append(getConfigListEntry(_("Location"), self.timerentry_dirname)) self.list.append(getConfigListEntry(_("After event"), self.timerentry_afterevent)) self.channelEntry = getConfigListEntry(_("Channel"), self.timerentry_service) @@ -255,6 +270,12 @@ class TimerEntry(Screen, ConfigListScreen): self.timer.afterEvent = {"nothing": AFTEREVENT.NONE, "deepstandby": AFTEREVENT.DEEPSTANDBY, "standby": AFTEREVENT.STANDBY}[self.timerentry_afterevent.value] self.timer.service_ref = self.timerentry_service_ref + # TODO: fix that thing with none (this might as well just be ignored) + if self.timerentry_dirname.value == "/hdd/movie/": + self.timer.dirname = None + else: + self.timer.dirname = self.timerentry_dirname.value + if self.timerentry_type.value == "once": self.timer.begin, self.timer.end = self.getBeginEnd() if self.timerentry_type.value == "repeated": @@ -313,7 +334,25 @@ class TimerEntry(Screen, ConfigListScreen): def keyCancel(self): self.close((False,)) - + + def selectPath(self): + if config.usage.setup_level.index < 2: #-expert + return + self.session.openWithCallback( + self.pathSelected, + LocationBox, + text = _("Choose target folder"), + filename = "", + currDir = None, # TODO: fix FileList to correctly determine mountpoint + minFree = 100 + ) + + def pathSelected(self, res): + if res is not None: + self.timerentry_dirname.choices.append(res) + self.timerentry_dirname.description[res] = res + self.timerentry_dirname.value = res + class TimerLog(Screen): def __init__(self, session, timer): Screen.__init__(self, session) diff --git a/lib/python/Tools/Directories.py b/lib/python/Tools/Directories.py index 975d3ade..0096f3f4 100644 --- a/lib/python/Tools/Directories.py +++ b/lib/python/Tools/Directories.py @@ -114,8 +114,8 @@ def fileExists(f): exists = 1 return exists -def getRecordingFilename(basename): - # filter out non-allowed characters +def getRecordingFilename(basename, dirname = None): + # filter out non-allowed characters non_allowed_characters = "/.\\:*?<>|\"" filename = "" @@ -125,7 +125,10 @@ def getRecordingFilename(basename): if c in non_allowed_characters: c = "_" filename += c - + + if dirname is not None: + filename = ''.join([dirname, filename]) + i = 0 while True: path = resolveFilename(SCOPE_HDD, filename) -- 2.30.2