small cleanup
[enigma2.git] / lib / python / Plugins / Extensions / DVDPlayer / plugin.py
1 from os import path as os_path, remove as os_remove, listdir as os_listdir, system
2 from time import strftime
3 from enigma import eTimer, iPlayableService, eServiceCenter, iServiceInformation, eServiceReference, iServiceKeys
4 from Screens.Screen import Screen
5 from Screens.MessageBox import MessageBox
6 from Screens.ChoiceBox import ChoiceBox
7 from Screens.InputBox import InputBox
8 from Screens.HelpMenu import HelpableScreen
9 from Screens.InfoBarGenerics import InfoBarSeek, InfoBarPVRState, InfoBarCueSheetSupport, InfoBarShowHide, InfoBarNotifications
10 from Components.ActionMap import ActionMap, NumberActionMap, HelpableActionMap
11 from Components.Label import Label
12 from Components.FileList import FileList
13 from Components.ServiceEventTracker import ServiceEventTracker
14 from Components.config import config
15 from Components.ProgressBar import ProgressBar
16 from ServiceReference import ServiceReference
17 from Tools.Directories import pathExists, fileExists
18
19 import random
20 import servicedvd # load c++ part of dvd player plugin
21
22 lastpath = ""
23
24 class FileBrowser(Screen):
25         skin = """
26         <screen name="FileBrowser" position="100,100" size="520,376" title="DVD File Browser" >
27                 <widget name="filelist" position="0,0" size="520,376" scrollbarMode="showOnDemand" />
28         </screen>"""
29         def __init__(self, session):
30                 Screen.__init__(self, session)
31                 global lastpath
32                 if lastpath is not None:
33                         currDir = lastpath + "/"
34                 else:
35                         currDir = "/media/dvd/"
36                 if not pathExists(currDir):
37                         currDir = "/"
38                 #else:
39                         #print system("mount "+currDir)
40                 self.filelist = FileList(currDir, matchingPattern = "(?i)^.*\.(iso)", useServiceRef = True)
41                 self["filelist"] = self.filelist
42
43                 self["FilelistActions"] = ActionMap(["OkCancelActions"],
44                         {
45                                 "ok": self.ok,
46                                 "cancel": self.exit
47                         })
48
49         def ok(self):
50                 global lastpath
51                 filename = self["filelist"].getFilename()
52                 if filename is not None:
53                         lastpath = filename[0:filename.rfind("/")]
54                         if filename.upper().endswith("VIDEO_TS/"):
55                                 print "dvd structure found, trying to open..."
56                                 self.close(filename[0:-9])
57                 if self["filelist"].canDescent(): # isDir
58                         self["filelist"].descent()
59                 else:
60                         self.close(filename)
61
62         def exit(self):
63                 self.close(None)
64                 
65 class DVDSummary(Screen):
66         skin = """
67         <screen position="0,0" size="132,64">
68                 <widget source="session.CurrentService" render="Label" position="5,4" size="120,28" font="Regular;12" transparent="1" >
69                         <convert type="ServiceName">Name</convert>
70                 </widget>
71                 <widget name="DVDPlayer" position="5,30" size="66,16" font="Regular;12" transparent="1" />
72                 <widget name="Chapter" position="72,30" size="54,16" font="Regular;12" transparent="1" halign="right" />
73                 <widget source="session.CurrentService" render="Label" position="66,46" size="60,18" font="Regular;16" transparent="1" halign="right" >
74                         <convert type="ServicePosition">Position</convert>
75                 </widget>
76                 <widget source="session.CurrentService" render="Progress" position="6,46" size="60,18" borderWidth="1" >
77                         <convert type="ServicePosition">Position</convert>
78                 </widget>
79         </screen>"""
80
81         def __init__(self, session, parent):
82                 Screen.__init__(self, session, parent)
83
84                 self["DVDPlayer"] = Label("DVD Player")
85                 self["Title"] = Label("")
86                 self["Time"] = Label("")
87                 self["Chapter"] = Label("")
88
89         def updateChapter(self, chapter):
90                 self["Chapter"].setText(chapter)
91
92         def setTitle(self, title):
93                 self["Title"].setText(title)
94
95 class DVDOverlay(Screen):
96         skin = """<screen name="DVDOverlay" position="0,0" size="720,576" flags="wfNoBorder" zPosition="-1" backgroundColor="transparent" />"""
97         def __init__(self, session, args = None):
98                 Screen.__init__(self, session)
99                 
100 class ChapterZap(Screen):
101         skin = """
102         <screen name="ChapterZap" position="235,255" size="250,60" title="Chapter" >
103                 <widget name="chapter" position="35,15" size="110,25" font="Regular;23" />
104                 <widget name="number" position="145,15" size="80,25" halign="right" font="Regular;23" />
105         </screen>"""
106         
107         def quit(self):
108                 self.Timer.stop()
109                 self.close(0)
110
111         def keyOK(self):
112                 self.Timer.stop()
113                 self.close(int(self["number"].getText()))
114
115         def keyNumberGlobal(self, number):
116                 self.Timer.start(3000, True)            #reset timer
117                 self.field = self.field + str(number)
118                 self["number"].setText(self.field)
119                 if len(self.field) >= 4:
120                         self.keyOK()
121
122         def __init__(self, session, number):
123                 Screen.__init__(self, session)
124                 self.field = str(number)
125
126                 self["chapter"] = Label(_("Chapter:"))
127
128                 self["number"] = Label(self.field)
129
130                 self["actions"] = NumberActionMap( [ "SetupActions" ],
131                         {
132                                 "cancel": self.quit,
133                                 "ok": self.keyOK,
134                                 "1": self.keyNumberGlobal,
135                                 "2": self.keyNumberGlobal,
136                                 "3": self.keyNumberGlobal,
137                                 "4": self.keyNumberGlobal,
138                                 "5": self.keyNumberGlobal,
139                                 "6": self.keyNumberGlobal,
140                                 "7": self.keyNumberGlobal,
141                                 "8": self.keyNumberGlobal,
142                                 "9": self.keyNumberGlobal,
143                                 "0": self.keyNumberGlobal
144                         })
145
146                 self.Timer = eTimer()
147                 self.Timer.callback.append(self.keyOK)
148                 self.Timer.start(3000, True)
149
150 class DVDPlayer(Screen, InfoBarNotifications, InfoBarSeek, InfoBarCueSheetSupport, InfoBarPVRState, InfoBarShowHide, HelpableScreen):
151         ALLOW_SUSPEND = True
152         ENABLE_RESUME_SUPPORT = True
153         
154         skin = """
155         <screen name="DVDPlayer" flags="wfNoBorder" position="0,380" size="720,160" title="InfoBar" backgroundColor="transparent" >
156                 <!-- Background -->
157                 <ePixmap position="0,0" zPosition="-2" size="720,160" pixmap="skin_default/info-bg_mp.png" alphatest="off" />
158                 <ePixmap position="29,40" zPosition="0" size="665,104" pixmap="skin_default/screws_mp.png" alphatest="on" transparent="1" />
159                 <!-- colorbuttons -->
160                 <ePixmap position="48,70" zPosition="0" size="108,13" pixmap="skin_default/icons/mp_buttons.png" alphatest="on" />
161                 <!-- Servicename -->
162                 <ePixmap pixmap="skin_default/icons/icon_event.png" position="207,78" zPosition="1" size="15,10" alphatest="on" />
163                 <widget source="session.CurrentService" render="Label" position="230,73" size="300,22" font="Regular;20" backgroundColor="#263c59" shadowColor="#1d354c" shadowOffset="-1,-1" transparent="1" noWrap="1">
164                         <convert type="ServiceName">Name</convert>
165                 </widget>
166                 <!-- Chapter info -->
167                 <widget name="chapterLabel" position="230,96" size="360,22" font="Regular;20" foregroundColor="#c3c3c9" backgroundColor="#263c59" transparent="1" />
168                 <!-- Audio track info -->
169                 <ePixmap pixmap="skin_default/icons/icon_dolby.png" position="540,73" zPosition="1" size="26,16" alphatest="on"/>
170                 <widget name="audioLabel" position="570,73" size="130,22" font="Regular;18" backgroundColor="#263c59" shadowColor="#1d354c" shadowOffset="-1,-1" transparent="1" />
171                 <!-- Subtitle track info -->
172                 <widget source="session.CurrentService" render="Pixmap" pixmap="skin_default/icons/icon_txt.png" position="540,96" zPosition="1" size="26,16" alphatest="on" >
173                         <convert type="ServiceInfo">HasTelext</convert>
174                         <convert type="ConditionalShowHide" />
175                 </widget>
176                 <widget name="subtitleLabel" position="570,96" size="130,22" font="Regular;18" backgroundColor="#263c59" shadowColor="#1d354c" shadowOffset="-1,-1" transparent="1" />
177                 <!-- Elapsed time -->
178                 <widget source="session.CurrentService" render="Label" position="205,129" size="100,20" font="Regular;18" halign="center" valign="center" backgroundColor="#06224f" shadowColor="#1d354c" shadowOffset="-1,-1" transparent="1" >
179                         <convert type="ServicePosition">Position,ShowHours</convert>
180                 </widget>
181                 <!-- Progressbar (movie position)-->
182                 <widget source="session.CurrentService" render="PositionGauge" position="300,133" size="270,10" zPosition="2" pointer="skin_default/position_pointer.png:540,0" transparent="1" >
183                         <convert type="ServicePosition">Gauge</convert>
184                 </widget>
185                 <!-- Remaining time -->
186                 <widget source="session.CurrentService" render="Label" position="576,129" size="100,20" font="Regular;18" halign="center" valign="center" backgroundColor="#06224f" shadowColor="#1d354c" shadowOffset="-1,-1" transparent="1" >
187                         <convert type="ServicePosition">Remaining,Negate,ShowHours</convert>
188                 </widget>
189         </screen>"""
190
191         def save_infobar_seek_config(self):
192                 self.saved_config_speeds_forward = config.seek.speeds_forward.value
193                 self.saved_config_speeds_backward = config.seek.speeds_backward.value
194                 self.saved_config_enter_forward = config.seek.enter_forward.value
195                 self.saved_config_enter_backward = config.seek.enter_backward.value
196                 self.saved_config_seek_stepwise_minspeed = config.seek.stepwise_minspeed.value
197                 self.saved_config_seek_stepwise_repeat = config.seek.stepwise_repeat.value
198                 self.saved_config_seek_on_pause = config.seek.on_pause.value
199                 self.saved_config_seek_speeds_slowmotion = config.seek.speeds_slowmotion.value
200
201         def change_infobar_seek_config(self):
202                 config.seek.speeds_forward.value = [2, 4, 8, 16, 32, 64]
203                 config.seek.speeds_backward.value = [8, 16, 32, 64]
204                 config.seek.speeds_slowmotion.value = [ ]
205                 config.seek.enter_forward.value = "2"
206                 config.seek.enter_backward.value = "2"
207                 config.seek.stepwise_minspeed.value = "Never"
208                 config.seek.stepwise_repeat.value = "3"
209                 config.seek.on_pause.value = "play"
210
211         def restore_infobar_seek_config(self):
212                 config.seek.speeds_forward.value = self.saved_config_speeds_forward
213                 config.seek.speeds_backward.value = self.saved_config_speeds_backward
214                 config.seek.speeds_slowmotion.value = self.saved_config_seek_speeds_slowmotion
215                 config.seek.enter_forward.value = self.saved_config_enter_forward
216                 config.seek.enter_backward.value = self.saved_config_enter_backward
217                 config.seek.stepwise_minspeed.value = self.saved_config_seek_stepwise_minspeed
218                 config.seek.stepwise_repeat.value = self.saved_config_seek_stepwise_repeat
219                 config.seek.on_pause.value = self.saved_config_seek_on_pause
220
221         def __init__(self, session, args = None):
222                 Screen.__init__(self, session)
223                 InfoBarNotifications.__init__(self)
224                 InfoBarCueSheetSupport.__init__(self, actionmap = "MediaPlayerCueSheetActions")
225                 InfoBarShowHide.__init__(self)
226                 HelpableScreen.__init__(self)
227                 self.save_infobar_seek_config()
228                 self.change_infobar_seek_config()
229                 InfoBarSeek.__init__(self)
230                 InfoBarPVRState.__init__(self)
231                 self.dvdScreen = self.session.instantiateDialog(DVDOverlay)
232
233                 self.oldService = self.session.nav.getCurrentlyPlayingServiceReference()
234                 self.session.nav.stopService()
235                 self["audioLabel"] = Label("1")
236                 self["subtitleLabel"] = Label("")
237                 self["chapterLabel"] = Label("")
238                 self.totalChapters = 0
239                 self.currentChapter = 0
240                 self.totalTitles = 0
241                 self.currentTitle = 0
242
243                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
244                         {
245                                 iPlayableService.evUser: self.__timeUpdated,
246                                 iPlayableService.evUser+1: self.__statePlay,
247                                 iPlayableService.evUser+2: self.__statePause,
248                                 iPlayableService.evUser+3: self.__osdFFwdInfoAvail,
249                                 iPlayableService.evUser+4: self.__osdFBwdInfoAvail,
250                                 iPlayableService.evUser+5: self.__osdStringAvail,
251                                 iPlayableService.evUser+6: self.__osdAudioInfoAvail,
252                                 iPlayableService.evUser+7: self.__osdSubtitleInfoAvail,
253                                 iPlayableService.evUser+8: self.__chapterUpdated,
254                                 iPlayableService.evUser+9: self.__titleUpdated,
255                                 #iPlayableService.evUser+10: self.__initializeDVDinfo,
256                                 iPlayableService.evUser+11: self.__menuOpened,
257                                 iPlayableService.evUser+12: self.__menuClosed
258                         })
259
260                 self["DVDPlayerDirectionActions"] = HelpableActionMap(self, "DirectionActions",
261                         {
262                                 #MENU KEY DOWN ACTIONS
263                                 "left": (self.keyLeft, _("DVD left key")),
264                                 "right": (self.keyRight, _("DVD right key")),
265                                 "up": (self.keyUp, _("DVD up key")),
266                                 "down": (self.keyDown, _("DVD down key")),
267
268                                 #MENU KEY REPEATED ACTIONS
269                                 "leftRepeated": self.doNothing,
270                                 "rightRepeated": self.doNothing,
271                                 "upRepeated": self.doNothing,
272                                 "downRepeated": self.doNothing,
273
274                                 #MENU KEY UP ACTIONS
275                                 "leftUp": self.doNothing,
276                                 "rightUp": self.doNothing,
277                                 "upUp": self.doNothing,
278                                 "downUp": self.doNothing,
279                         }, -2)
280
281                 self["OkCancelActions"] = HelpableActionMap(self, "OkCancelActions",
282                         {
283                                 "ok": (self.keyOk, _("DVD ENTER key")),
284                                 "cancel": self.keyCancel,
285                         }, -2)
286
287                 self["DVDPlayerPlaybackActions"] = HelpableActionMap(self, "DVDPlayerActions",
288                         {
289                                 #PLAYER ACTIONS
290                                 "dvdMenu": (self.enterDVDMenu, _("show DVD main menu")),
291                                 "toggleInfo": (self.toggleInfo, _("toggle time, chapter, audio, subtitle info")),
292                                 "nextChapter": (self.nextChapter, _("forward to the next chapter")),
293                                 "prevChapter": (self.prevChapter, _("rewind to the previous chapter")),
294                                 "nextTitle": (self.nextTitle, _("jump forward to the next title")),
295                                 "prevTitle": (self.prevTitle, _("jump back to the previous title")),
296                                 "tv": (self.askLeavePlayer, _("exit DVD player or return to file browser")),
297                                 "dvdAudioMenu": (self.enterDVDAudioMenu, _("(show optional DVD audio menu)")),
298                                 "nextAudioTrack": (self.nextAudioTrack, _("switch to the next audio track")),
299                                 "nextSubtitleTrack": (self.nextSubtitleTrack, _("switch to the next subtitle language")),
300                                 "seekBeginning": (self.seekBeginning, _("Jump to video title 1 (play movie from start)")),
301                         }, -2)
302                         
303                 self["NumberActions"] = NumberActionMap( [ "NumberActions"],
304                         {
305                                 "1": self.keyNumberGlobal,
306                                 "2": self.keyNumberGlobal,
307                                 "3": self.keyNumberGlobal,
308                                 "4": self.keyNumberGlobal,
309                                 "5": self.keyNumberGlobal,
310                                 "6": self.keyNumberGlobal,
311                                 "7": self.keyNumberGlobal,
312                                 "8": self.keyNumberGlobal,
313                                 "9": self.keyNumberGlobal,
314                                 "0": self.keyNumberGlobal,
315                         })
316
317                 self.onClose.append(self.__onClose)
318                 self.onFirstExecBegin.append(self.showFileBrowser)
319                 self.service = None
320                 self.in_menu = False
321                 
322         def keyNumberGlobal(self, number):
323                 print "You pressed number " + str(number)
324                 self.session.openWithCallback(self.numberEntered, ChapterZap, number)
325
326         def numberEntered(self, retval):
327 #               print self.servicelist
328                 if retval > 0:
329                         self.zapToNumber(retval)
330
331         def serviceStarted(self): #override InfoBarShowHide function
332                 pass
333
334         def doEofInternal(self, playing):
335                 if self.in_menu:
336                         self.hide()
337
338         def __menuOpened(self):
339                 self.hide()
340                 self.in_menu = True
341
342         def __menuClosed(self):
343                 self.show()
344                 self.in_menu = False
345
346         def setChapterLabel(self):
347                 chapterLCD = "Menu"
348                 chapterOSD = "DVD Menu"
349                 if self.currentTitle > 0:
350                         chapterLCD = "%s %d" % (_("Chap."), self.currentChapter)
351                         chapterOSD = "DVD %s %d/%d" % (_("Chapter"), self.currentChapter, self.totalChapters)
352                         chapterOSD += " (%s %d/%d)" % (_("Title"), self.currentTitle, self.totalTitles)
353                 self["chapterLabel"].setText(chapterOSD)
354                 try:
355                         self.session.summary.updateChapter(chapterLCD)
356                 except:
357                         pass
358
359         def doNothing(self):
360                 pass
361
362         def toggleInfo(self):
363                 if not self.in_menu:
364                         self.toggleShow()
365                         print "toggleInfo"
366
367         def __timeUpdated(self):
368                 print "timeUpdated"
369
370         def __statePlay(self):
371                 print "statePlay"
372
373         def __statePause(self):
374                 print "statePause"
375
376         def __osdFFwdInfoAvail(self):
377                 self.setChapterLabel()
378                 print "FFwdInfoAvail"
379
380         def __osdFBwdInfoAvail(self):
381                 self.setChapterLabel()
382                 print "FBwdInfoAvail"
383
384         def __osdStringAvail(self):
385                 print "StringAvail"
386
387         def __osdAudioInfoAvail(self):
388                 audioString = self.service.info().getInfoString(iPlayableService.evUser+6)
389                 print "AudioInfoAvail "+audioString
390                 self["audioLabel"].setText(audioString)
391                 if not self.in_menu:
392                         self.doShow()
393
394         def __osdSubtitleInfoAvail(self):
395                 subtitleString = self.service.info().getInfoString(iPlayableService.evUser+7)
396                 print "SubtitleInfoAvail "+subtitleString
397                 self["subtitleLabel"].setText(subtitleString)
398                 if not self.in_menu:
399                         self.doShow()
400
401         def __chapterUpdated(self):
402                 self.currentChapter = self.service.info().getInfo(iPlayableService.evUser+8)
403                 self.totalChapters = self.service.info().getInfo(iPlayableService.evUser+80)
404                 self.setChapterLabel()
405                 print "__chapterUpdated: %d/%d" % (self.currentChapter, self.totalChapters)
406
407         def __titleUpdated(self):
408                 self.currentTitle = self.service.info().getInfo(iPlayableService.evUser+9)
409                 self.totalTitles = self.service.info().getInfo(iPlayableService.evUser+90)
410                 self.setChapterLabel()
411                 print "__titleUpdated: %d/%d" % (self.currentTitle, self.totalTitles)
412                 if not self.in_menu:
413                         self.doShow()
414                 
415         #def __initializeDVDinfo(self):
416                 #self.__osdAudioInfoAvail()
417                 #self.__osdSubtitleInfoAvail()
418
419         def askLeavePlayer(self):
420                 self.session.openWithCallback(self.exitCB, ChoiceBox, title=_("Leave DVD Player?"), list=[(_("Exit"), "exit"), (_("Return to file browser"), "browser"), (_("Continue playing"), "play")])
421
422         def nextAudioTrack(self):
423                 if self.service:
424                         self.service.keys().keyPressed(iServiceKeys.keyUser)
425
426         def nextSubtitleTrack(self):
427                 if self.service:
428                         self.service.keys().keyPressed(iServiceKeys.keyUser+1)
429
430         def enterDVDAudioMenu(self):
431                 if self.service:
432                         self.service.keys().keyPressed(iServiceKeys.keyUser+2)
433
434         def nextChapter(self):
435                 if self.service:
436                         self.service.keys().keyPressed(iServiceKeys.keyUser+3)
437
438         def prevChapter(self):
439                 if self.service:
440                         self.service.keys().keyPressed(iServiceKeys.keyUser+4)
441
442         def nextTitle(self):
443                 if self.service:
444                         self.service.keys().keyPressed(iServiceKeys.keyUser+5)
445
446         def prevTitle(self):
447                 if self.service:
448                         self.service.keys().keyPressed(iServiceKeys.keyUser+6)
449
450         def enterDVDMenu(self):
451                 if self.service:
452                         self.service.keys().keyPressed(iServiceKeys.keyUser+7)
453                         
454         def seekBeginning(self):
455                 if self.service:
456                         seekable = self.getSeek()
457                         if seekable is not None:
458                                 seekable.seekTo(0)
459                                 
460         def zapToNumber(self, number):
461                 if self.service:
462                         seekable = self.getSeek()
463                         if seekable is not None:
464                                 print "seek to chapter %d" % number
465                                 seekable.seekChapter(number)
466
467 #       MENU ACTIONS
468         def keyRight(self):
469                 if self.service:
470                         self.service.keys().keyPressed(iServiceKeys.keyRight)
471
472         def keyLeft(self):
473                 if self.service:
474                         self.service.keys().keyPressed(iServiceKeys.keyLeft)
475
476         def keyUp(self):
477                 if self.service:
478                         self.service.keys().keyPressed(iServiceKeys.keyUp)
479
480         def keyDown(self):
481                 if self.service:
482                         self.service.keys().keyPressed(iServiceKeys.keyDown)
483
484         def keyOk(self):
485                 if self.service:
486                         if not self.in_menu:
487                                 self.toggleInfo()
488                         self.service.keys().keyPressed(iServiceKeys.keyOk)
489
490         def keyCancel(self):
491                 self.askLeavePlayer()
492
493         def showFileBrowser(self):
494                 self.session.openWithCallback(self.FileBrowserClosed, FileBrowser)
495
496         def FileBrowserClosed(self, val):
497                 curref = self.session.nav.getCurrentlyPlayingServiceReference()
498                 print "FileBrowserClosed", val
499                 if val is None:
500                         self.askLeavePlayer()
501                 else:
502                         newref = eServiceReference(4369, 0, val)
503                         print "play", newref.toString()
504                         if curref is None or curref != newref:
505                                 self.session.nav.playService(newref)
506                                 self.service = self.session.nav.getCurrentService()
507                                 print "self.service", self.service
508                                 print "cur_dlg", self.session.current_dialog
509                                 self.dvdScreen.show()
510                                 self.service.subtitle().enableSubtitles(self.dvdScreen.instance, None)
511
512         def exitCB(self, answer):
513                 if answer is not None:
514                         if answer[1] == "exit":
515                                 if self.service:
516                                         self.dvdScreen.hide()
517                                         self.service.subtitle().disableSubtitles(self.session.current_dialog.instance)
518                                         self.service = None
519                                 self.close()
520                         if answer[1] == "browser":
521                                 #TODO check here if a paused dvd playback is already running... then re-start it...
522                                 #else
523                                 self.showFileBrowser()
524                         else:
525                                 pass
526
527         def __onClose(self):
528                 self.restore_infobar_seek_config()
529                 self.session.nav.playService(self.oldService)
530
531         def showAfterCuesheetOperation(self):
532                 if not self.in_menu:
533                         self.show()
534
535         def createSummary(self):
536                 print "DVDCreateSummary"
537                 return DVDSummary
538
539 def main(session, **kwargs):
540         session.open(DVDPlayer)
541
542 def menu(menuid, **kwargs):
543         if menuid == "mainmenu":
544                 return [(_("DVD Player"), main, "dvd_player", 46)]
545         return []
546
547 from Plugins.Plugin import PluginDescriptor
548 def Plugins(**kwargs):
549         return [PluginDescriptor(name = "DVDPlayer", description = "Play DVDs", where = PluginDescriptor.WHERE_MENU, fnc = menu)]