timer_select.patch by Moritz Venn, with minor fixes
[vuplus_dvbapp] / lib / python / Screens / LocationBox.py
diff --git a/lib/python/Screens/LocationBox.py b/lib/python/Screens/LocationBox.py
new file mode 100644 (file)
index 0000000..b15fece
--- /dev/null
@@ -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 = """<screen name="LocationBox" position="100,130" size="540,340" >
+                       <widget name="text" position="0,2" size="540,22" font="Regular;22" />
+                       <widget name="filelist" position="0,25" size="540,235" />
+                       <widget name="target" position="0,260" size="540,40" valign="center" font="Regular;22" />
+                       <widget name="yellow" position="260,300" zPosition="1" size="140,40" pixmap="skin_default/key-yellow.png" transparent="1" alphatest="on" />
+                       <widget name="key_yellow" position="260,300" zPosition="2" size="140,40" halign="center" valign="center" font="Regular;22" transparent="1" shadowColor="black" shadowOffset="-1,-1" />
+                       <widget name="green" position="400,300" zPosition="1" size="140,40" pixmap="skin_default/key-green.png" transparent="1" alphatest="on" />
+                       <widget name="key_green" position="400,300" zPosition="2" size="140,40" halign="center" valign="center" font="Regular;22" transparent="1" shadowColor="black" shadowOffset="-1,-1" />
+               </screen>"""
+
+       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 + ")"
+