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