save config entries after creating them for keyCancel to work properly
[enigma2.git] / lib / python / Screens / LocationBox.py
1 #
2 # Generic Screen to select a path/filename combination
3 #
4
5 # GUI (Screens)
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
11
12 # Generic
13 from Tools.BoundFunction import boundFunction
14 from Tools.Directories import *
15 from Components.config import config, configfile, ConfigSubList, ConfigSubsection, \
16                 ConfigText, ConfigNumber, ConfigBoolean
17 import os
18
19 # Quickselect
20 from Tools.NumericalTextInput import NumericalTextInput
21
22 # GUI (Components)
23 from Components.ActionMap import NumberActionMap, HelpableActionMap
24 from Components.Label import Label
25 from Components.Pixmap import Pixmap
26 from Components.Button import Button
27 from Components.FileList import FileList
28 from Components.MenuList import MenuList
29
30 # Timer
31 from enigma import eTimer
32
33 class LocationBox(Screen, NumericalTextInput, HelpableScreen):
34         """Simple Class similar to MessageBox / ChoiceBox but used to choose a folder/pathname combination"""
35
36         skin = """<screen name="LocationBox" position="100,75" size="540,460" >
37                         <widget name="text" position="0,2" size="540,22" font="Regular;22" />
38                         <widget name="target" position="0,23" size="540,22" valign="center" font="Regular;22" />
39                         <widget name="filelist" position="0,55" zPosition="1" size="540,210" scrollbarMode="showOnDemand" selectionDisabled="1" />
40                         <widget name="textbook" position="0,272" size="540,22" font="Regular;22" />
41                         <widget name="booklist" position="5,302" zPosition="2" size="535,100" scrollbarMode="showOnDemand" />
42                         <widget name="red" position="0,415" zPosition="1" size="135,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
43                         <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" />   
44                         <widget name="green" position="135,415" zPosition="1" size="135,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
45                         <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" />
46                         <widget name="yellow" position="270,415" zPosition="1" size="135,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
47                         <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" />
48                         <widget name="blue" position="405,415" zPosition="1" size="135,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
49                         <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" />            
50                 </screen>"""
51
52         def __init__(self, session, text = "", filename = "", currDir = None, bookmarks = None, userMode = False, windowTitle = _("Select Location"), minFree = None, autoAdd = False, editDir = False, inhibitDirs = [], inhibitMounts = []):
53                 # Init parents
54                 Screen.__init__(self, session)
55                 NumericalTextInput.__init__(self, handleTimeout = False)
56                 HelpableScreen.__init__(self)
57
58                 # Set useable chars
59                 self.setUseableChars(u'1234567890abcdefghijklmnopqrstuvwxyz')
60
61                 # Quickselect Timer
62                 self.qs_timer = eTimer()
63                 self.qs_timer.callback.append(self.timeout)
64                 self.qs_timer_type = 0
65
66                 # Initialize Quickselect
67                 self.curr_pos = -1
68                 self.quickselect = ""
69
70                 # Set Text
71                 self["text"] = Label(text)
72                 self["textbook"] = Label(_("Bookmarks"))
73
74                 # Save parameters locally
75                 self.text = text
76                 self.filename = filename
77                 self.minFree = minFree
78                 self.realBookmarks = bookmarks
79                 self.bookmarks = bookmarks and bookmarks.value[:] or []
80                 self.userMode = userMode
81                 self.autoAdd = autoAdd
82                 self.editDir = editDir
83                 self.inhibitDirs = inhibitDirs
84
85                 # Initialize FileList
86                 self["filelist"] = FileList(currDir, showDirectories = True, showFiles = False, inhibitMounts = inhibitMounts, inhibitDirs = inhibitDirs)
87
88                 # Initialize BookList
89                 self["booklist"] = MenuList(self.bookmarks)
90
91                 # Buttons
92                 self["key_green"] = Button(_("OK"))
93                 self["key_yellow"] = Button(_("Rename"))
94                 self["key_blue"] = Button(_("Remove Bookmark"))
95                 self["key_red"] = Button(_("Cancel"))
96
97                 # Background for Buttons
98                 self["green"] = Pixmap()
99                 self["yellow"] = Pixmap()
100                 self["blue"] = Pixmap()
101                 self["red"] = Pixmap()
102
103                 # Initialize Target
104                 self["target"] = Label()
105
106                 if self.userMode:
107                         self.usermodeOn()
108
109                 # Custom Action Handler
110                 class LocationBoxActionMap(HelpableActionMap):
111                         def __init__(self, parent, context, actions = { }, prio=0):
112                                 HelpableActionMap.__init__(self, parent, context, actions, prio)
113                                 self.box = parent
114
115                         def action(self, contexts, action):
116                                 # Reset Quickselect
117                                 self.box.timeout(force = True)
118
119                                 return HelpableActionMap.action(self, contexts, action)
120
121                 # Actions that will reset quickselect
122                 self["WizardActions"] = LocationBoxActionMap(self, "WizardActions",
123                         {
124                                 "left": self.left,
125                                 "right": self.right,
126                                 "up": self.up,
127                                 "down": self.down,
128                                 "ok": (self.ok, _("select")),
129                                 "back": (self.cancel, _("cancel")),
130                         }, -2)
131
132                 self["ColorActions"] = LocationBoxActionMap(self, "ColorActions",
133                         {
134                                 "red": self.cancel,
135                                 "green": self.select,
136                                 "yellow": self.changeName,
137                                 "blue": self.addRemoveBookmark,
138                         }, -2)
139
140                 self["EPGSelectActions"] = LocationBoxActionMap(self, "EPGSelectActions",
141                         {
142                                 "prevBouquet": (self.switchToBookList, _("switch to bookmarks")),
143                                 "nextBouquet": (self.switchToFileList, _("switch to filelist")),
144                         }, -2)
145
146                 self["MenuActions"] = LocationBoxActionMap(self, "MenuActions",
147                         {
148                                 "menu": (self.showMenu, _("menu")),
149                         }, -2)
150
151                 # Actions used by quickselect
152                 self["NumberActions"] = NumberActionMap(["NumberActions"],
153                 {
154                         "1": self.keyNumberGlobal,
155                         "2": self.keyNumberGlobal,
156                         "3": self.keyNumberGlobal,
157                         "4": self.keyNumberGlobal,
158                         "5": self.keyNumberGlobal,
159                         "6": self.keyNumberGlobal,
160                         "7": self.keyNumberGlobal,
161                         "8": self.keyNumberGlobal,
162                         "9": self.keyNumberGlobal,
163                         "0": self.keyNumberGlobal
164                 })
165
166                 # Run some functions when shown
167                 self.onShown.extend([
168                         boundFunction(self.setTitle, windowTitle),
169                         self.updateTarget,
170                         self.showHideRename,
171                 ])
172
173                 self.onLayoutFinish.append(self.switchToFileListOnStart)
174  
175                 # Make sure we remove our callback
176                 self.onClose.append(self.disableTimer)
177
178         def switchToFileListOnStart(self):
179                 if self.realBookmarks and self.realBookmarks.value:
180                         self.currList = "booklist"
181                         currDir = self["filelist"].current_directory
182                         if currDir in self.bookmarks:
183                                 self["booklist"].moveToIndex(self.bookmarks.index(currDir))
184                 else:
185                         self.switchToFileList()
186
187         def disableTimer(self):
188                 self.qs_timer.callback.remove(self.timeout)
189
190         def showHideRename(self):
191                 # Don't allow renaming when filename is empty
192                 if self.filename == "":
193                         self["key_yellow"].hide()
194
195         def switchToFileList(self):
196                 if not self.userMode:
197                         self.currList = "filelist"
198                         self["filelist"].selectionEnabled(1)
199                         self["booklist"].selectionEnabled(0)
200                         self["key_blue"].text = _("Add Bookmark")
201                         self.updateTarget()
202
203         def switchToBookList(self):
204                 self.currList = "booklist"
205                 self["filelist"].selectionEnabled(0)
206                 self["booklist"].selectionEnabled(1)
207                 self["key_blue"].text = _("Remove Bookmark")
208                 self.updateTarget()
209
210         def addRemoveBookmark(self):
211                 if self.currList == "filelist":
212                         # add bookmark
213                         folder = self["filelist"].getSelection()[0]
214                         if folder is not None and not folder in self.bookmarks:
215                                 self.bookmarks.append(folder)
216                                 self.bookmarks.sort()
217                                 self["booklist"].setList(self.bookmarks)
218                 else:
219                         # remove bookmark
220                         if not self.userMode:
221                                 name = self["booklist"].getCurrent()
222                                 self.session.openWithCallback(
223                                         boundFunction(self.removeBookmark, name),
224                                         MessageBox,
225                                         _("Do you really want to remove your bookmark of %s?") % (name),
226                                 )
227
228         def removeBookmark(self, name, ret):
229                 if not ret:
230                         return
231                 if name in self.bookmarks:
232                         self.bookmarks.remove(name)
233                         self["booklist"].setList(self.bookmarks)
234
235         def createDir(self):
236                 if self["filelist"].current_directory != None:
237                         self.session.openWithCallback(
238                                 self.createDirCallback,
239                                 InputBox,
240                                 title = _("Please enter name of the new directory"),
241                                 text = ""
242                         )
243
244         def createDirCallback(self, res):
245                 if res is not None and len(res):
246                         path = os.path.join(self["filelist"].current_directory, res)
247                         if not pathExists(path):
248                                 if not createDir(path):
249                                         self.session.open(
250                                                 MessageBox,
251                                                 _("Creating directory %s failed.") % (path),
252                                                 type = MessageBox.TYPE_ERROR,
253                                                 timeout = 5
254                                         )
255                                 self["filelist"].refresh()
256                         else:
257                                 self.session.open(
258                                         MessageBox,
259                                         _("The path %s already exists.") % (path),
260                                         type = MessageBox.TYPE_ERROR,
261                                         timeout = 5
262                                 )
263
264         def removeDir(self):
265                 sel = self["filelist"].getSelection()
266                 if sel and pathExists(sel[0]):
267                         self.session.openWithCallback(
268                                 boundFunction(self.removeDirCallback, sel[0]),
269                                 MessageBox,
270                                 _("Do you really want to remove directory %s from the disk?") % (sel[0]),
271                                 type = MessageBox.TYPE_YESNO
272                         )
273                 else:
274                         self.session.open(
275                                 MessageBox,
276                                 _("Invalid directory selected: %s") % (sel[0]),
277                                 type = MessageBox.TYPE_ERROR,
278                                 timeout = 5
279                         )
280
281         def removeDirCallback(self, name, res):
282                 if res:
283                         if not removeDir(name):
284                                 self.session.open(
285                                         MessageBox,
286                                         _("Removing directory %s failed. (Maybe not empty.)") % (name),
287                                         type = MessageBox.TYPE_ERROR,
288                                         timeout = 5
289                                 )
290                         else:
291                                 self["filelist"].refresh()
292                                 self.removeBookmark(name, True)
293
294         def up(self):
295                 self[self.currList].up()
296                 self.updateTarget()
297
298         def down(self):
299                 self[self.currList].down()
300                 self.updateTarget()
301
302         def left(self):
303                 self[self.currList].pageUp()
304                 self.updateTarget()
305
306         def right(self):
307                 self[self.currList].pageDown()
308                 self.updateTarget()
309
310         def ok(self):
311                 if self.currList == "filelist":
312                         if self["filelist"].canDescent():
313                                 self["filelist"].descent()
314                                 self.updateTarget()
315                 else:
316                         self.select()
317
318         def cancel(self):
319                 self.close(None)
320
321         def getPreferredFolder(self):
322                 if self.currList == "filelist":
323                         # XXX: We might want to change this for parent folder...
324                         return self["filelist"].getSelection()[0]
325                 else:
326                         return self["booklist"].getCurrent()
327
328         def selectConfirmed(self, ret):
329                 if ret:
330                         ret = ''.join([self.getPreferredFolder(), self.filename])
331                         if self.realBookmarks:
332                                 if self.autoAdd and not ret in self.bookmarks:
333                                         self.bookmarks.append(self.getPreferredFolder())
334                                         self.bookmarks.sort()
335
336                                 if self.bookmarks != self.realBookmarks.value:
337                                         self.realBookmarks.value = self.bookmarks
338                                         self.realBookmarks.save()
339                         self.close(ret)
340
341         def select(self):
342                 currentFolder = self.getPreferredFolder()
343                 # Do nothing unless current Directory is valid
344                 if currentFolder is not None:
345                         # Check if we need to have a minimum of free Space available
346                         if self.minFree is not None:
347                                 # Try to read fs stats
348                                 try:
349                                         s = os.statvfs(currentFolder)
350                                         if (s.f_bavail * s.f_bsize) / 1000000 > self.minFree:
351                                                 # Automatically confirm if we have enough free disk Space available
352                                                 return self.selectConfirmed(True)
353                                 except OSError:
354                                         pass
355
356                                 # Ask User if he really wants to select this folder
357                                 self.session.openWithCallback(
358                                         self.selectConfirmed,
359                                         MessageBox,
360                                         _("There might not be enough Space on the selected Partition.\nDo you really want to continue?"),
361                                         type = MessageBox.TYPE_YESNO
362                                 )
363                         # No minimum free Space means we can safely close
364                         else:
365                                 self.selectConfirmed(True)
366
367         def changeName(self):
368                 if self.filename != "":
369                         # TODO: Add Information that changing extension is bad? disallow?
370                         self.session.openWithCallback(
371                                 self.nameChanged,
372                                 InputBox,
373                                 title = _("Please enter a new filename"),
374                                 text = self.filename
375                         )
376
377         def nameChanged(self, res):
378                 if res is not None:
379                         if len(res):
380                                 self.filename = res
381                                 self.updateTarget()
382                         else:
383                                 self.session.open(
384                                         MessageBox,
385                                         _("An empty filename is illegal."),
386                                         type = MessageBox.TYPE_ERROR,
387                                         timeout = 5
388                                 )
389
390         def updateTarget(self):
391                 # Write Combination of Folder & Filename when Folder is valid
392                 currFolder = self.getPreferredFolder()
393                 if currFolder is not None:
394                         self["target"].setText(''.join([currFolder, self.filename]))
395                 # Display a Warning otherwise
396                 else:
397                         self["target"].setText(_("Invalid Location"))
398
399         def showMenu(self):
400                 if not self.userMode and self.realBookmarks:
401                         menu = []
402                         if self.currList == "filelist":
403                                 menu.append((_("switch to bookmarks"), self.switchToBookList))
404                                 menu.append((_("add bookmark"), self.addRemoveBookmark))
405                                 if self.editDir:
406                                         menu.append((_("create directory"), self.createDir))
407                                         menu.append((_("remove directory"), self.removeDir))
408                         else:
409                                 menu.append((_("switch to filelist"), self.switchToFileList))
410                                 menu.append((_("remove bookmark"), self.addRemoveBookmark))
411
412                         self.session.openWithCallback(
413                                 self.menuCallback,
414                                 ChoiceBox,
415                                 title = "",
416                                 list = menu
417                         )
418
419         def menuCallback(self, choice):
420                 if choice:
421                         choice[1]()
422                         
423         def usermodeOn(self):
424                 self.switchToBookList()
425                 self["filelist"].hide()
426                 self["key_blue"].hide()
427
428         def keyNumberGlobal(self, number):
429                 # Cancel Timeout
430                 self.qs_timer.stop()
431
432                 # See if another key was pressed before
433                 if number != self.lastKey:
434                         # Reset lastKey again so NumericalTextInput triggers its keychange
435                         self.nextKey()
436
437                         # Try to select what was typed
438                         self.selectByStart()
439
440                         # Increment position
441                         self.curr_pos += 1
442
443                 # Get char and append to text
444                 char = self.getKey(number)
445                 self.quickselect = self.quickselect[:self.curr_pos] + unicode(char)
446
447                 # Start Timeout
448                 self.qs_timer_type = 0
449                 self.qs_timer.start(1000, 1)
450
451         def selectByStart(self):
452                 # Don't do anything on initial call
453                 if not len(self.quickselect):
454                         return
455
456                 # Don't select if no dir
457                 if self["filelist"].getCurrentDirectory():
458                         # TODO: implement proper method in Components.FileList
459                         files = self["filelist"].getFileList()
460
461                         # Initialize index
462                         idx = 0
463
464                         # We select by filename which is absolute
465                         lookfor = self["filelist"].getCurrentDirectory() + self.quickselect
466
467                         # Select file starting with generated text
468                         for file in files:
469                                 if file[0][0] and file[0][0].lower().startswith(lookfor):
470                                         self["filelist"].instance.moveSelectionTo(idx)
471                                         break
472                                 idx += 1
473
474         def timeout(self, force = False):
475                 # Timeout Key
476                 if not force and self.qs_timer_type == 0:
477                         # Try to select what was typed
478                         self.selectByStart()
479
480                         # Reset Key
481                         self.lastKey = -1
482
483                         # Change type
484                         self.qs_timer_type = 1
485
486                         # Start timeout again
487                         self.qs_timer.start(1000, 1)
488                 # Timeout Quickselect
489                 else:
490                         # Eventually stop Timer
491                         self.qs_timer.stop()
492
493                         # Invalidate
494                         self.lastKey = -1
495                         self.curr_pos = -1
496                         self.quickselect = ""
497
498         def __repr__(self):
499                 return str(type(self)) + "(" + self.text + ")"
500
501 class MovieLocationBox(LocationBox):
502         def __init__(self, session, text, dir, minFree = None):
503                 inhibitDirs = ["/bin", "/boot", "/dev", "/etc", "/lib", "/proc", "/sbin", "/sys", "/usr", "/var"]
504                 LocationBox.__init__(self, session, text = text, currDir = dir, bookmarks = config.movielist.videodirs, autoAdd = True, editDir = True, inhibitDirs = inhibitDirs, minFree = minFree)