GMEPG - remove by mistake added my private local changes
[openblackhole/openblackhole-enigma2.git] / lib / python / Plugins / Extensions / GraphMultiEPG / GraphMultiEpg.py
1 from skin import parseColor, parseFont, parseSize
2 from Components.config import config, ConfigClock, ConfigInteger, ConfigSubsection, ConfigYesNo, ConfigSelection, ConfigSelectionNumber
3 from Components.Pixmap import Pixmap
4 from Components.Button import Button
5 from Components.ActionMap import HelpableActionMap
6 from Components.HTMLComponent import HTMLComponent
7 from Components.GUIComponent import GUIComponent
8 from Components.EpgList import Rect
9 from Components.Sources.Event import Event
10 from Components.MultiContent import MultiContentEntryText, MultiContentEntryPixmapAlphaTest
11 from Components.TimerList import TimerList
12 from Components.Renderer.Picon import getPiconName
13 from Components.Sources.ServiceEvent import ServiceEvent
14 import Screens.InfoBar
15 from Screens.Screen import Screen
16 from Screens.HelpMenu import HelpableScreen
17 from Screens.EventView import EventViewEPGSelect
18 from Screens.InputBox import PinInput
19 from Screens.TimeDateInput import TimeDateInput
20 from Screens.TimerEntry import TimerEntry
21 from Screens.EpgSelection import EPGSelection
22 from Screens.TimerEdit import TimerSanityConflict, TimerEditList
23 from Screens.MessageBox import MessageBox
24 from Screens.ChoiceBox import ChoiceBox
25 from Tools.Directories import resolveFilename, SCOPE_CURRENT_SKIN
26 from RecordTimer import RecordTimerEntry, parseEvent, AFTEREVENT
27 from ServiceReference import ServiceReference, isPlayableForCur
28 from Tools.LoadPixmap import LoadPixmap
29 from Tools.Alternatives import CompareWithAlternatives
30 from Tools import Notifications
31 from enigma import eEPGCache, eListbox, gFont, eListboxPythonMultiContent, RT_HALIGN_LEFT, RT_HALIGN_RIGHT, RT_HALIGN_CENTER,\
32         RT_VALIGN_CENTER, RT_WRAP, BT_SCALE, BT_KEEP_ASPECT_RATIO, eSize, eRect, eTimer, getBestPlayableServiceReference, loadPNG
33 from GraphMultiEpgSetup import GraphMultiEpgSetup
34 from time import localtime, time, strftime
35 from Components.PluginComponent import plugins
36 from Plugins.Plugin import PluginDescriptor
37 from Tools.BoundFunction import boundFunction
38
39 MAX_TIMELINES = 6
40
41 config.misc.graph_mepg = ConfigSubsection()
42 config.misc.graph_mepg.prev_time = ConfigClock(default = time())
43 config.misc.graph_mepg.prev_time_period = ConfigInteger(default = 120, limits = (60, 300))
44 config.misc.graph_mepg.ev_fontsize = ConfigSelectionNumber(default = 0, stepwidth = 1, min = -12, max = 12, wraparound = True)
45 config.misc.graph_mepg.items_per_page = ConfigSelectionNumber(min = 3, max = 40, stepwidth = 1, default = 6, wraparound = True)
46 config.misc.graph_mepg.items_per_page_listscreen = ConfigSelectionNumber(min = 3, max = 60, stepwidth = 1, default = 12, wraparound = True)
47 config.misc.graph_mepg.default_mode = ConfigYesNo(default = False)
48 config.misc.graph_mepg.overjump = ConfigYesNo(default = True)
49 config.misc.graph_mepg.center_timeline = ConfigYesNo(default = False)
50 config.misc.graph_mepg.servicetitle_mode = ConfigSelection(default = "picon+servicename", choices = [
51         ("servicename", _("Service name")),
52         ("picon", _("Picon")),
53         ("picon+servicename", _("Picon and service name")) ])
54 config.misc.graph_mepg.roundTo = ConfigSelection(default = "900", choices = [("900", _("%d minutes") % 15), ("1800", _("%d minutes") % 30), ("3600", _("%d minutes") % 60)])
55 config.misc.graph_mepg.OKButton = ConfigSelection(default = "info", choices = [("info", _("Show detailed event info")), ("zap", _("Zap to selected channel"))])
56 possibleAlignmentChoices = [
57         ( str(RT_HALIGN_LEFT   | RT_VALIGN_CENTER          ) , _("left")),
58         ( str(RT_HALIGN_CENTER | RT_VALIGN_CENTER          ) , _("centered")),
59         ( str(RT_HALIGN_RIGHT  | RT_VALIGN_CENTER          ) , _("right")),
60         ( str(RT_HALIGN_LEFT   | RT_VALIGN_CENTER | RT_WRAP) , _("left, wrapped")),
61         ( str(RT_HALIGN_CENTER | RT_VALIGN_CENTER | RT_WRAP) , _("centered, wrapped")),
62         ( str(RT_HALIGN_RIGHT  | RT_VALIGN_CENTER | RT_WRAP) , _("right, wrapped"))]
63 config.misc.graph_mepg.event_alignment = ConfigSelection(default = possibleAlignmentChoices[0][0], choices = possibleAlignmentChoices)
64 config.misc.graph_mepg.show_timelines = ConfigSelection(default = "all", choices = [("nothing", _("no")), ("all", _("all")), ("now", _("actual time only"))])
65 config.misc.graph_mepg.servicename_alignment = ConfigSelection(default = possibleAlignmentChoices[0][0], choices = possibleAlignmentChoices)
66 config.misc.graph_mepg.extension_menu = ConfigYesNo(default = False)
67 config.misc.graph_mepg.silent_bouquet_change = ConfigYesNo(default = True)
68
69 listscreen = config.misc.graph_mepg.default_mode.value
70
71 class EPGList(HTMLComponent, GUIComponent):
72         def __init__(self, selChangedCB = None, timer = None, time_epoch = 120, overjump_empty = True):
73                 GUIComponent.__init__(self)
74                 self.cur_event = None
75                 self.cur_service = None
76                 self.offs = 0
77                 self.timer = timer
78                 self.last_time = time()
79                 self.onSelChanged = [ ]
80                 if selChangedCB is not None:
81                         self.onSelChanged.append(selChangedCB)
82                 self.l = eListboxPythonMultiContent()
83                 self.l.setBuildFunc(self.buildEntry)
84                 self.setOverjump_Empty(overjump_empty)
85                 self.epgcache = eEPGCache.getInstance()
86                 self.clocks = [ LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_add.png')),
87                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_pre.png')),
88                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock.png')),
89                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_prepost.png')),
90                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_post.png')),
91                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zapclock_add.png')),
92                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zapclock_pre.png')),
93                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zapclock.png')),
94                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zapclock_prepost.png')),
95                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zapclock_post.png')),
96                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zaprecclock_add.png')),
97                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zaprecclock_pre.png')),
98                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zaprecclock.png')),
99                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zaprecclock_prepost.png')),
100                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zaprecclock_post.png')),
101                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repepgclock_add.png')),
102                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repepgclock_pre.png')),
103                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repepgclock.png')),
104                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repepgclock_prepost.png')),
105                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repepgclock_post.png')),
106                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzapclock_add.png')),
107                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzapclock_pre.png')),
108                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzapclock.png')),
109                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzapclock_prepost.png')),
110                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzapclock_post.png')),
111                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzaprecclock_add.png')),
112                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzaprecclock_pre.png')),
113                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzaprecclock.png')),
114                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzaprecclock_prepost.png')),
115                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzaprecclock_post.png')) ]
116                 self.time_base = None
117                 self.time_epoch = time_epoch
118                 self.list = None
119                 self.select_rect = None
120                 self.event_rect = None
121                 self.service_rect = None
122                 self.picon_size = None
123                 self.currentlyPlaying = None
124                 self.showPicon = False
125                 self.showServiceTitle = True
126                 self.nowEvPix = None
127                 self.othEvPix = None
128                 self.selEvPix = None
129                 self.recEvPix = None
130                 self.curSerPix = None
131
132                 self.foreColor = 0xffffff
133                 self.foreColorSelected = 0xffc000
134                 self.borderColor = 0x464445
135                 self.backColor = 0x595959
136                 self.backColorSelected = 0x808080
137                 self.foreColorService = 0xffffff
138                 self.foreColorServiceSelected = 0xffffff
139                 self.backColorService = 0x000000
140                 self.backColorServiceSelected = 0x508050
141                 self.borderColorService = 0x000000
142                 self.foreColorNow = 0xffffff
143                 self.backColorNow = 0x505080
144                 self.foreColorRec = 0xffffff
145                 self.backColorRec = 0x805050
146                 self.serviceFont = gFont("Regular", 20)
147                 self.entryFontName = "Regular"
148                 self.entryFontSize = 18
149
150                 self.listHeight = None
151                 self.listWidth = None
152                 self.serviceBorderVerWidth = 1
153                 self.serviceBorderHorWidth = 1
154                 self.serviceNamePadding = 0
155                 self.eventBorderVerWidth = 1
156                 self.eventBorderHorWidth = 1
157                 self.eventNamePadding = 0
158
159         def applySkin(self, desktop, screen):
160                 def EntryForegroundColor(value):
161                         self.foreColor = parseColor(value).argb()
162                 def EntryForegroundColorSelected(value):
163                         self.foreColorSelected = parseColor(value).argb()
164                 def EntryBackgroundColor(value):
165                         self.backColor = parseColor(value).argb()
166                 def EntryBackgroundColorSelected(value):
167                         self.backColorSelected = parseColor(value).argb()
168                 def EntryBorderColor(value):
169                         self.borderColor = parseColor(value).argb()
170                 def EntryFont(value):
171                         font = parseFont(value, ((1,1),(1,1)) )
172                         self.entryFontName = font.family
173                         self.entryFontSize = font.pointSize
174                 def ServiceForegroundColor(value):
175                         self.foreColorService = parseColor(value).argb()
176                 def ServiceNameForegroundColor(value):
177                         self.foreColorService = parseColor(value).argb()
178                 def ServiceForegroundColorSelected(value):
179                         self.foreColorServiceSelected = parseColor(value).argb()
180                 def ServiceBackgroundColor(value):
181                         self.backColorService = parseColor(value).argb()
182                 def ServiceNameBackgroundColor(value):
183                         self.backColorService = parseColor(value).argb()
184                 def ServiceBackgroundColorSelected(value):
185                         self.backColorServiceSelected = parseColor(value).argb()
186                 def ServiceBackgroundColorRecording(value):
187                         self.backColorRec = parseColor(value).argb()
188                 def ServiceNameBackgroundColor(value):
189                         self.backColorRec = parseColor(value).argb()
190                 def ServiceForegroundColorRecording(value):
191                         self.foreColorRec = parseColor(value).argb()
192                 def ServiceBorderColor(value):
193                         self.borderColorService = parseColor(value).argb()
194                 def ServiceFont(value):
195                         self.serviceFont = parseFont(value, ((1,1),(1,1)) )
196                 def EntryBackgroundColorNow(value):
197                         self.backColorNow = parseColor(value).argb()
198                 def EntryForegroundColorNow(value):
199                         self.foreColorNow = parseColor(value).argb()
200                 def ServiceBorderVerWidth(value):
201                         self.serviceBorderVerWidth = int(value)
202                 def ServiceBorderHorWidth(value):
203                         self.serviceBorderHorWidth = int(value)
204                 def ServiceNamePadding(value):
205                         self.serviceNamePadding = int(value)
206                 def EventBorderHorWidth(value):
207                         self.eventBorderHorWidth = int(value)
208                 def EventBorderVerWidth(value):
209                         self.eventBorderVerWidth = int(value)
210                 def EventNamePadding(value):
211                         self.eventNamePadding = int(value)
212                 for (attrib, value) in list(self.skinAttributes):
213                         try:
214                                 locals().get(attrib)(value)
215                                 self.skinAttributes.remove((attrib, value))
216                         except:
217                                 pass
218                 self.l.setFont(0, self.serviceFont)
219                 self.setEventFontsize()
220                 rc = GUIComponent.applySkin(self, desktop, screen)
221                 # now we know our size and can safely set items per page
222                 self.listHeight = self.instance.size().height()
223                 self.listWidth = self.instance.size().width()
224                 self.setItemsPerPage()
225                 return rc
226
227         def isSelectable(self, service, service_name, events, picon):
228                 return (events and len(events) and True) or False
229
230         def setShowServiceMode(self, value):
231                 self.showServiceTitle = "servicename" in value
232                 self.showPicon = "picon" in value
233                 self.recalcEntrySize()
234                 self.selEntry(0) #Select entry again so that the clipping region gets updated if needed
235
236         def setOverjump_Empty(self, overjump_empty):
237                 if overjump_empty:
238                         self.l.setSelectableFunc(self.isSelectable)
239                 else:
240                         self.l.setSelectableFunc(None)
241
242         def setEpoch(self, epoch):
243                 self.offs = 0
244                 self.time_epoch = epoch
245                 self.fillMultiEPG(None) # refill
246
247         def setCurrentlyPlaying(self, serviceref):
248                 self.currentlyPlaying = serviceref
249
250         def getEventFromId(self, service, eventid):
251                 event = None
252                 if self.epgcache is not None and eventid is not None:
253                         event = self.epgcache.lookupEventId(service.ref, eventid)
254                 return event
255
256         def getIndexFromService(self, serviceref):
257                 if serviceref is not None:
258                         for x in range(len(self.list)):
259                                 if CompareWithAlternatives(self.list[x][0], serviceref.toString()):
260                                         return x
261                 return None
262
263         def moveToService(self, serviceref):
264                 newIdx = self.getIndexFromService(serviceref)
265                 if newIdx is None:
266                         newIdx = 0
267                 self.setCurrentIndex(newIdx)
268
269         def setCurrentIndex(self, index):
270                 if self.instance:
271                         self.instance.moveSelectionTo(index)
272
273         def moveTo(self, dir):
274                 if self.instance:
275                         self.instance.moveSelection(dir)
276
277         def moveToFromEPG(self, dir, epg):
278                 self.moveTo(dir==1 and eListbox.moveDown or eListbox.moveUp)
279                 if self.cur_service:
280                         epg.setService(ServiceReference(self.cur_service[0]))
281
282         def getCurrent(self):
283                 if self.cur_service is None:
284                         return (None, None)
285                 old_service = self.cur_service  #(service, service_name, events, picon)
286                 events = self.cur_service[2]
287                 refstr = self.cur_service[0]
288                 if self.cur_event is None or not events or not len(events):
289                         return (None, ServiceReference(refstr))
290                 event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
291                 eventid = event[0]
292                 service = ServiceReference(refstr)
293                 event = self.getEventFromId(service, eventid) # get full event info
294                 return (event, service)
295
296         def connectSelectionChanged(func):
297                 if not self.onSelChanged.count(func):
298                         self.onSelChanged.append(func)
299
300         def disconnectSelectionChanged(func):
301                 self.onSelChanged.remove(func)
302
303         def serviceChanged(self):
304                 cur_sel = self.l.getCurrentSelection()
305                 if cur_sel:
306                         self.findBestEvent()
307
308         def findBestEvent(self):
309                 old_service = self.cur_service  #(service, service_name, events, picon)
310                 cur_service = self.cur_service = self.l.getCurrentSelection()
311                 time_base = self.getTimeBase()
312                 now = time()
313                 if old_service and self.cur_event is not None:
314                         events = old_service[2]
315                         cur_event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
316                         if self.last_time < cur_event[2] or cur_event[2]+cur_event[3] < self.last_time:
317                                 self.last_time = cur_event[2]
318                 if now > self.last_time:
319                         self.last_time = now
320                 if cur_service:
321                         self.cur_event = None
322                         events = cur_service[2]
323                         if events and len(events):
324                                 self.cur_event = idx = 0
325                                 for event in events: #iterate all events
326                                         if event[2] <= self.last_time and event[2]+event[3] > self.last_time:
327                                                 self.cur_event = idx
328                                                 break
329                                         idx += 1
330                 self.selEntry(0)
331
332         def selectionChanged(self):
333                 for x in self.onSelChanged:
334                         if x is not None:
335                                 x()
336
337         GUI_WIDGET = eListbox
338
339         def setItemsPerPage(self):
340                 global listscreen
341                 if self.listHeight > 0:
342                         if listscreen:
343                                 itemHeight = self.listHeight / config.misc.graph_mepg.items_per_page_listscreen.getValue()
344                         else:
345                                 itemHeight = self.listHeight / config.misc.graph_mepg.items_per_page.getValue()
346                 else:
347                         itemHeight = 54 # some default (270/5)
348                 if listscreen:
349                         self.instance.resize(eSize(self.listWidth, itemHeight * config.misc.graph_mepg.items_per_page_listscreen.getValue()))
350                 else:
351                         self.instance.resize(eSize(self.listWidth, itemHeight * config.misc.graph_mepg.items_per_page.getValue()))
352                 self.l.setItemHeight(itemHeight)
353
354                 self.nowEvPix = loadPNG(resolveFilename(SCOPE_CURRENT_SKIN, 'epg/CurrentEvent.png'))
355                 self.othEvPix = loadPNG(resolveFilename(SCOPE_CURRENT_SKIN, 'epg/OtherEvent.png'))
356                 self.selEvPix = loadPNG(resolveFilename(SCOPE_CURRENT_SKIN, 'epg/SelectedEvent.png'))
357                 self.recEvPix = loadPNG(resolveFilename(SCOPE_CURRENT_SKIN, 'epg/RecordingEvent.png'))
358                 self.curSerPix = loadPNG(resolveFilename(SCOPE_CURRENT_SKIN, 'epg/CurrentService.png'))
359
360         def setEventFontsize(self):
361                 self.l.setFont(1, gFont(self.entryFontName, self.entryFontSize + config.misc.graph_mepg.ev_fontsize.getValue()))
362
363         def postWidgetCreate(self, instance):
364                 instance.setWrapAround(True)
365                 instance.selectionChanged.get().append(self.serviceChanged)
366                 instance.setContent(self.l)
367                 self.l.setSelectionClip(eRect(0, 0, 0, 0), False)
368
369         def preWidgetRemove(self, instance):
370                 instance.selectionChanged.get().remove(self.serviceChanged)
371                 instance.setContent(None)
372
373         def recalcEntrySize(self):
374                 esize = self.l.getItemSize()
375                 width = esize.width()
376                 height = esize.height()
377                 if self.showServiceTitle:
378                         w = width / 10 * 2;
379                 else:     # if self.showPicon:    # this must be set if showServiceTitle is None
380                         w = 2 * height - 2 * self.serviceBorderVerWidth  # FIXME: could do better...
381                 self.service_rect = Rect(0, 0, w, height)
382                 self.event_rect = Rect(w, 0, width - w, height)
383                 piconHeight = height - 2 * self.serviceBorderHorWidth
384                 piconWidth = 2 * piconHeight  # FIXME: could do better...
385                 if piconWidth > w - 2 * self.serviceBorderVerWidth:
386                         piconWidth = w - 2 * self.serviceBorderVerWidth
387                 self.picon_size = eSize(piconWidth, piconHeight)
388
389         def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
390                 xpos = (stime - start) * width / (end - start)
391                 ewidth = (stime + duration - start) * width / (end - start)
392                 ewidth -= xpos;
393                 if xpos < 0:
394                         ewidth += xpos;
395                         xpos = 0;
396                 if (xpos + ewidth) > width:
397                         ewidth = width - xpos
398                 return xpos, ewidth
399
400         def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
401                 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
402                 return xpos + event_rect.left(), width
403
404         def buildEntry(self, service, service_name, events, picon):
405                 r1 = self.service_rect
406                 r2 = self.event_rect
407                 selected = self.cur_service[0] == service
408
409                 # Picon and Service name
410                 if CompareWithAlternatives(service, self.currentlyPlaying and self.currentlyPlaying.toString()):
411                         serviceForeColor = self.foreColorServiceSelected
412                         serviceBackColor = self.backColorServiceSelected
413                         bgpng = self.curSerPix or self.nowEvPix
414                         currentservice = True
415                 else:
416                         serviceForeColor = self.foreColorService
417                         serviceBackColor = self.backColorService
418                         bgpng = self.othEvPix
419                         currentservice = False
420
421                 res = [ None ]
422                 if bgpng is not None:    # bacground for service rect
423                         res.append(MultiContentEntryPixmapAlphaTest(
424                                         pos = (r1.x + self.serviceBorderVerWidth, r1.y + self.serviceBorderHorWidth),
425                                         size = (r1.w - 2 * self.serviceBorderVerWidth, r1.h - 2 * self.serviceBorderHorWidth),
426                                         png = bgpng,
427                                         flags = BT_SCALE))
428                 else:
429                         res.append(MultiContentEntryText(
430                                         pos  = (r1.x, r1.y),
431                                         size = (r1.w, r1.h),
432                                         font = 0, flags = RT_HALIGN_LEFT | RT_VALIGN_CENTER,
433                                         text = "",
434                                         color = serviceForeColor, color_sel = serviceForeColor,
435                                         backcolor = serviceBackColor, backcolor_sel = serviceBackColor))
436
437                 displayPicon = None
438                 if self.showPicon:
439                         if picon is None: # go find picon and cache its location
440                                 picon = getPiconName(service)
441                                 curIdx = self.l.getCurrentSelectionIndex()
442                                 self.list[curIdx] = (service, service_name, events, picon)
443                         piconWidth = self.picon_size.width()
444                         piconHeight = self.picon_size.height()
445                         if picon != "":
446                                 displayPicon = loadPNG(picon)
447                         if displayPicon is not None:
448                                 res.append(MultiContentEntryPixmapAlphaTest(
449                                         pos = (r1.x + self.serviceBorderVerWidth, r1.y + self.serviceBorderHorWidth),
450                                         size = (piconWidth, piconHeight),
451                                         png = displayPicon,
452                                         backcolor = None, backcolor_sel = None, flags = BT_SCALE | BT_KEEP_ASPECT_RATIO))
453                         elif not self.showServiceTitle:
454                                 # no picon so show servicename anyway in picon space
455                                 namefont = 1
456                                 namefontflag = int(config.misc.graph_mepg.servicename_alignment.value)
457                                 namewidth = piconWidth
458                                 piconWidth = 0
459                 else:
460                         piconWidth = 0
461
462                 if self.showServiceTitle: # we have more space so reset parms
463                         namefont = 0
464                         namefontflag = int(config.misc.graph_mepg.servicename_alignment.value)
465                         namewidth = r1.w - piconWidth
466
467                 if self.showServiceTitle or displayPicon is None:
468                         res.append(MultiContentEntryText(
469                                 pos = (r1.x + piconWidth + self.serviceBorderVerWidth + self.serviceNamePadding,
470                                         r1.y + self.serviceBorderHorWidth),
471                                 size = (namewidth - 2 * (self.serviceBorderVerWidth + self.serviceNamePadding),
472                                         r1.h - 2 * self.serviceBorderHorWidth),
473                                 font = namefont, flags = namefontflag,
474                                 text = service_name,
475                                 color = serviceForeColor, color_sel = serviceForeColor,
476                                 backcolor = None, backcolor_sel = None))
477
478                 # Events for service
479                 backColorSel = self.backColorSelected
480                 if events:
481                         start = self.time_base + self.offs * self.time_epoch * 60
482                         end = start + self.time_epoch * 60
483                         left = r2.x
484                         top = r2.y
485                         width = r2.w
486                         height = r2.h
487
488                         now = time()
489                         for ev in events:  #(event_id, event_title, begin_time, duration)
490                                 stime = ev[2]
491                                 duration = ev[3]
492                                 xpos, ewidth = self.calcEntryPosAndWidthHelper(stime, duration, start, end, width)
493                                 rec = self.timer.isInTimer(ev[0], stime, duration, service)
494
495                                 # event box background
496                                 foreColorSelected = foreColor = self.foreColor
497                                 if stime <= now and now < stime + duration:
498                                         backColor = self.backColorNow
499                                         if isPlayableForCur(ServiceReference(service).ref):
500                                                 foreColor = self.foreColorNow
501                                                 foreColorSelected = self.foreColorSelected
502                                 else:
503                                         backColor = self.backColor
504
505                                 if selected and self.select_rect.x == xpos + left and self.selEvPix:
506                                         bgpng = self.selEvPix
507                                         backColorSel = None
508                                 elif rec is not None and rec[1][-1] in (2, 12, 17, 27):
509                                         bgpng = self.recEvPix
510                                         foreColor = self.foreColorRec
511                                         backColor = self.backColorRec
512                                 elif stime <= now and now < stime + duration:
513                                         bgpng = self.nowEvPix
514                                 elif currentservice:
515                                         bgpng = self.curSerPix or self.othEvPix
516                                         backColor = self.backColorServiceSelected
517                                 else:
518                                         bgpng = self.othEvPix
519
520                                 if bgpng is not None:
521                                         res.append(MultiContentEntryPixmapAlphaTest(
522                                                 pos = (left + xpos + self.eventBorderVerWidth, top + self.eventBorderHorWidth),
523                                                 size = (ewidth - 2 * self.eventBorderVerWidth, height - 2 * self.eventBorderHorWidth),
524                                                 png = bgpng,
525                                                 flags = BT_SCALE))
526                                 else:
527                                         res.append(MultiContentEntryText(
528                                                 pos = (left + xpos, top), size = (ewidth, height),
529                                                 font = 1, flags = int(config.misc.graph_mepg.event_alignment.value),
530                                                 text = "", color = None, color_sel = None,
531                                                 backcolor = backColor, backcolor_sel = backColorSel))
532
533                                 # event text
534                                 evX = left + xpos + self.eventBorderVerWidth + self.eventNamePadding
535                                 evY = top + self.eventBorderHorWidth
536                                 evW = ewidth - 2 * (self.eventBorderVerWidth + self.eventNamePadding)
537                                 evH = height - 2 * self.eventBorderHorWidth
538                                 if evW > 0:
539                                         res.append(MultiContentEntryText(
540                                                 pos = (evX, evY),
541                                                 size = (evW, evH),
542                                                 font = 1,
543                                                 flags = int(config.misc.graph_mepg.event_alignment.value),
544                                                 text = ev[1],
545                                                 color = foreColor,
546                                                 color_sel = foreColorSelected))
547                                 # recording icons
548                                 if rec is not None:
549                                         for i in range(len(rec[1])):
550                                                 if ewidth < (i + 1) * 22:
551                                                         break
552                                                 res.append(MultiContentEntryPixmapAlphaTest(
553                                                         pos = (left + xpos + ewidth - (i + 1) * 22, top + height - 22), size = (21, 21),
554                                                         png = self.clocks[rec[1][len(rec[1]) - 1 - i]]))
555
556                 else:
557                         if selected and self.selEvPix:
558                                 res.append(MultiContentEntryPixmapAlphaTest(
559                                         pos = (r2.x + self.eventBorderVerWidth, r2.y + self.eventBorderHorWidth),
560                                         size = (r2.w - 2 * self.eventBorderVerWidth, r2.h - 2 * self.eventBorderHorWidth),
561                                         png = self.selEvPix,
562                                         flags = BT_SCALE))
563                 return res
564
565         def selEntry(self, dir, visible = True):
566                 cur_service = self.cur_service    #(service, service_name, events, picon)
567                 self.recalcEntrySize()
568                 valid_event = self.cur_event is not None
569                 if cur_service:
570                         update = True
571                         entries = cur_service[2]
572                         if dir == 0: #current
573                                 update = False
574                         elif dir == +1: #next
575                                 if valid_event and self.cur_event + 1 < len(entries):
576                                         self.cur_event += 1
577                                 else:
578                                         self.offs += 1
579                                         self.fillMultiEPG(None) # refill
580                                         return True
581                         elif dir == -1: #prev
582                                 if valid_event and self.cur_event - 1 >= 0:
583                                         self.cur_event -= 1
584                                 elif self.offs > 0:
585                                         self.offs -= 1
586                                         self.fillMultiEPG(None) # refill
587                                         return True
588                         elif dir == +2: #next page
589                                 self.offs += 1
590                                 self.fillMultiEPG(None) # refill
591                                 return True
592                         elif dir == -2: #prev
593                                 if self.offs > 0:
594                                         self.offs -= 1
595                                         self.fillMultiEPG(None) # refill
596                                         return True
597                         elif dir == +3: #next day
598                                 self.offs += 60 * 24 / self.time_epoch
599                                 self.fillMultiEPG(None) # refill
600                                 return True
601                         elif dir == -3: #prev day
602                                 self.offs -= 60 * 24 / self.time_epoch
603                                 if self.offs < 0:
604                                         self.offs = 0;
605                                 self.fillMultiEPG(None) # refill
606                                 return True
607                 if cur_service and valid_event:
608                         entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
609                         time_base = self.time_base + self.offs*self.time_epoch * 60
610                         xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
611                         self.select_rect = Rect(xpos ,0, width, self.event_rect.height)
612                         self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.h), visible and update)
613                 else:
614                         self.select_rect = self.event_rect
615                         self.l.setSelectionClip(eRect(self.event_rect.x, self.event_rect.y, self.event_rect.w, self.event_rect.h), False)
616                 self.selectionChanged()
617                 return False
618
619         def fillMultiEPG(self, services, stime = None):
620                 if stime is not None:
621                         self.time_base = int(stime)
622                 if services is None:
623                         time_base = self.time_base + self.offs * self.time_epoch * 60
624                         test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
625                         serviceList = self.list
626                         piconIdx = 3
627                 else:
628                         self.cur_event = None
629                         self.cur_service = None
630                         test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
631                         serviceList = services
632                         piconIdx = 0
633
634                 test.insert(0, 'XRnITBD') #return record, service ref, service name, event id, event title, begin time, duration
635                 epg_data = [] if self.epgcache is None else self.epgcache.lookupEvent(test)
636                 self.list = [ ]
637                 tmp_list = None
638                 service = ""
639                 sname = ""
640
641                 serviceIdx = 0
642                 for x in epg_data:
643                         if service != x[0]:
644                                 if tmp_list is not None:
645                                         picon = None if piconIdx == 0 else serviceList[serviceIdx][piconIdx]
646                                         self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None, picon))
647                                         serviceIdx += 1
648                                 service = x[0]
649                                 sname = x[1]
650                                 tmp_list = [ ]
651                         tmp_list.append((x[2], x[3], x[4], x[5])) #(event_id, event_title, begin_time, duration)
652                 if tmp_list and len(tmp_list):
653                         picon = None if piconIdx == 0 else serviceList[serviceIdx][piconIdx]
654                         self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None, picon))
655                         serviceIdx += 1
656
657                 self.l.setList(self.list)
658                 self.findBestEvent()
659
660         def getEventRect(self):
661                 rc = self.event_rect
662                 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
663
664         def getServiceRect(self):
665                 rc = self.service_rect
666                 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
667
668         def getTimeEpoch(self):
669                 return self.time_epoch
670
671         def getTimeBase(self):
672                 return self.time_base + (self.offs * self.time_epoch * 60)
673
674         def resetOffset(self):
675                 self.offs = 0
676
677 class TimelineText(HTMLComponent, GUIComponent):
678         def __init__(self):
679                 GUIComponent.__init__(self)
680                 self.l = eListboxPythonMultiContent()
681                 self.l.setSelectionClip(eRect(0, 0, 0, 0))
682                 self.l.setItemHeight(25);
683                 self.foreColor = 0xffc000
684                 self.backColor = 0x000000
685                 self.time_base = 0
686                 self.time_epoch = 0
687                 self.font = gFont("Regular", 20)
688
689         GUI_WIDGET = eListbox
690
691         def applySkin(self, desktop, screen):
692                 def foregroundColor(value):
693                         self.foreColor = parseColor(value).argb()
694                 def backgroundColor(value):
695                         self.backColor = parseColor(value).argb()
696                 def font(value):
697                         self.font = parseFont(value,  ((1, 1), (1, 1)) )
698                 for (attrib, value) in list(self.skinAttributes):
699                         try:
700                                 locals().get(attrib)(value)
701                                 self.skinAttributes.remove((attrib, value))
702                         except:
703                                 pass
704                 self.l.setFont(0, self.font)
705                 return GUIComponent.applySkin(self, desktop, screen)
706
707         def postWidgetCreate(self, instance):
708                 instance.setContent(self.l)
709
710         def setDateFormat(self, value):
711                 if "servicename" in value:
712                         self.datefmt = _("%A %d %B")
713                 elif "picon" in value:
714                         self.datefmt = _("%d-%m")
715
716         def setEntries(self, l, timeline_now, time_lines, force):
717                 event_rect = l.getEventRect()
718                 time_epoch = l.getTimeEpoch()
719                 time_base = l.getTimeBase()
720
721                 if event_rect is None or time_epoch is None or time_base is None:
722                         return
723
724                 eventLeft = event_rect.left()
725                 res = [ None ]
726
727                 # Note: event_rect and service_rect are relative to the timeline_text position
728                 #       while the time lines are relative to the GraphEPG screen position!
729                 if self.time_base != time_base or self.time_epoch != time_epoch or force:
730                         service_rect = l.getServiceRect()
731                         itemHeight = self.l.getItemSize().height()
732                         time_steps = 60 if time_epoch > 180 else 30
733                         num_lines = time_epoch / time_steps
734                         timeStepsCalc = time_steps * 60
735                         incWidth = event_rect.width() / num_lines
736                         if int(config.misc.graph_mepg.center_timeline.value):
737                                 tlMove = incWidth / 2
738                                 tlFlags = RT_HALIGN_CENTER | RT_VALIGN_CENTER
739                         else:
740                                 tlMove = 0
741                                 tlFlags = RT_HALIGN_LEFT | RT_VALIGN_CENTER
742
743                                 res.append( MultiContentEntryText(
744                                         pos = (0, 0),
745                                         size = (service_rect.width(), itemHeight),
746                                         font = 0, flags = RT_HALIGN_LEFT | RT_VALIGN_CENTER,
747                                         text = strftime(self.datefmt, localtime(time_base)),
748                                         color = self.foreColor, color_sel = self.foreColor,
749                                         backcolor = self.backColor, backcolor_sel = self.backColor) )
750
751                         xpos = 0 # eventLeft
752                         for x in range(0, num_lines):
753                                 res.append( MultiContentEntryText(
754                                         pos = (service_rect.width() + xpos-tlMove, 0),
755                                         size = (incWidth, itemHeight),
756                                         font = 0, flags = tlFlags,
757                                         text = strftime("%H:%M", localtime( time_base + x*timeStepsCalc )),
758                                         color = self.foreColor, color_sel = self.foreColor,
759                                         backcolor = self.backColor, backcolor_sel = self.backColor) )
760                                 line = time_lines[x]
761                                 old_pos = line.position
762                                 line.setPosition(xpos + eventLeft, old_pos[1])
763                                 line.visible = config.misc.graph_mepg.show_timelines.value is "all"
764                                 xpos += incWidth
765                         for x in range(num_lines, MAX_TIMELINES):
766                                 time_lines[x].visible = False
767                         self.l.setList([res])
768                         self.time_base = time_base
769                         self.time_epoch = time_epoch
770
771                 now = time()
772                 if now >= time_base and now < (time_base + time_epoch * 60):
773                         xpos = int((((now - time_base) * event_rect.width()) / (time_epoch * 60)) - (timeline_now.instance.size().width() / 2))
774                         old_pos = timeline_now.position
775                         new_pos = (xpos + eventLeft, old_pos[1])
776                         if old_pos != new_pos:
777                                 timeline_now.setPosition(new_pos[0], new_pos[1])
778                         timeline_now.visible = config.misc.graph_mepg.show_timelines.value in ("all", "now")
779                 else:
780                         timeline_now.visible = False
781
782 class GraphMultiEPG(Screen, HelpableScreen):
783         EMPTY = 0
784         ADD_TIMER = 1
785         REMOVE_TIMER = 2
786
787         ZAP = 1
788
789         def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None, bouquetname=""):
790                 Screen.__init__(self, session)
791                 self.bouquetChangeCB = bouquetChangeCB
792                 now = time() - config.epg.histminutes.getValue() * 60
793                 self.ask_time = now - now % int(config.misc.graph_mepg.roundTo.getValue())
794                 self["key_red"] = Button("")
795                 self["key_green"] = Button("")
796
797                 global listscreen
798                 if listscreen:
799                         self["key_yellow"] = Button(_("Normal mode"))
800                         self.skinName="GraphMultiEPGList"
801                 else:
802                         self["key_yellow"] = Button(_("List mode"))
803
804                 self["key_blue"] = Button(_("Goto"))
805
806                 self.key_green_choice = self.EMPTY
807                 self.key_red_choice = self.EMPTY
808                 self["timeline_text"] = TimelineText()
809                 self["Service"] = ServiceEvent()
810                 self["Event"] = Event()
811                 self.time_lines = [ ]
812                 for x in range(0, MAX_TIMELINES):
813                         pm = Pixmap()
814                         self.time_lines.append(pm)
815                         self["timeline%d"%(x)] = pm
816                 self["timeline_now"] = Pixmap()
817                 self.services = services
818                 self.zapFunc = zapFunc
819                 if bouquetname != "":
820                         Screen.setTitle(self, bouquetname)
821
822                 self["list"] = EPGList( selChangedCB = self.onSelectionChanged,
823                                         timer = self.session.nav.RecordTimer,
824                                         time_epoch = config.misc.graph_mepg.prev_time_period.value,
825                                         overjump_empty = config.misc.graph_mepg.overjump.value)
826
827                 HelpableScreen.__init__(self)
828                 self["okactions"] = HelpableActionMap(self, "OkCancelActions",
829                         {
830                                 "cancel": (self.closeScreen,   _("Exit EPG")),
831                                 "ok":     (self.eventSelected, _("Zap to selected channel, or show detailed event info (depends on configuration)"))
832                         }, -1)
833                 self["okactions"].csel = self
834                 self["epgactions"] = HelpableActionMap(self, "EPGSelectActions",
835                         {
836                                 "timerAdd":    (self.timerAdd,       _("Add/remove change timer for current event")),
837                                 "info":        (self.infoKeyPressed, _("Show detailed event info")),
838                                 "red":         (self.zapTo,          _("Zap to selected channel")),
839                                 "yellow":      (self.swapMode,       _("Switch between normal mode and list mode")),
840                                 "blue":        (self.enterDateTime,  _("Goto specific date/time")),
841                                 "menu":        (self.furtherOptions, _("Further Options")),
842                                 "nextBouquet": (self.nextBouquet, self.getKeyNextBouquetHelptext),
843                                 "prevBouquet": (self.prevBouquet, self.getKeyPrevBouquetHelptext),
844                                 "nextService": (self.nextPressed,    _("Goto next page of events")),
845                                 "prevService": (self.prevPressed,    _("Goto previous page of events")),
846                                 "preview":     (self.preview,        _("Preview selected channel")),
847                                 "nextDay":     (self.nextDay,        _("Goto next day of events")),
848                                 "prevDay":     (self.prevDay,        _("Goto previous day of events"))
849                         }, -1)
850                 self["epgactions"].csel = self
851
852                 self["inputactions"] = HelpableActionMap(self, "InputActions",
853                         {
854                                 "left":  (self.leftPressed,  _("Go to previous event")),
855                                 "right": (self.rightPressed, _("Go to next event")),
856                                 "1":     (self.key1,         _("Set time window to 1 hour")),
857                                 "2":     (self.key2,         _("Set time window to 2 hours")),
858                                 "3":     (self.key3,         _("Set time window to 3 hours")),
859                                 "4":     (self.key4,         _("Set time window to 4 hours")),
860                                 "5":     (self.key5,         _("Set time window to 5 hours")),
861                                 "6":     (self.key6,         _("Set time window to 6 hours")),
862                                 "7":     (self.prevPage,     _("Go to previous page of service")),
863                                 "9":     (self.nextPage,     _("Go to next page of service")),
864                                 "8":     (self.toTop,        _("Go to first service")),
865                                 "0":     (self.toEnd,        _("Go to last service"))
866                         }, -1)
867                 self["inputactions"].csel = self
868
869                 self.protectContextMenu = True
870                 self.updateTimelineTimer = eTimer()
871                 self.updateTimelineTimer.callback.append(self.moveTimeLines)
872                 self.updateTimelineTimer.start(60 * 1000)
873                 self.onLayoutFinish.append(self.onCreate)
874                 self.previousref = self.session.nav.getCurrentlyPlayingServiceOrGroup()
875
876         def prevPage(self):
877                 self["list"].moveTo(eListbox.pageUp)
878
879         def nextPage(self):
880                 self["list"].moveTo(eListbox.pageDown)
881
882         def toTop(self):
883                 self["list"].moveTo(eListbox.moveTop)
884
885         def toEnd(self):
886                 self["list"].moveTo(eListbox.moveEnd)
887
888         def prevPressed(self):
889                 self.updEvent(-2)
890
891         def nextPressed(self):
892                 self.updEvent(+2)
893
894         def leftPressed(self):
895                 self.updEvent(-1)
896
897         def rightPressed(self):
898                 self.updEvent(+1)
899
900         def prevDay(self):
901                 self.updEvent(-3)
902
903         def nextDay(self):
904                 self.updEvent(+3)
905
906         def updEvent(self, dir, visible = True):
907                 ret = self["list"].selEntry(dir, visible)
908                 if ret:
909                         self.moveTimeLines(True)
910
911         def updEpoch(self, mins):
912                 self["list"].setEpoch(mins)
913                 config.misc.graph_mepg.prev_time_period.value = mins
914                 self.moveTimeLines()
915
916         def key1(self):
917                 self.updEpoch(60)
918
919         def key2(self):
920                 self.updEpoch(120)
921
922         def key3(self):
923                 self.updEpoch(180)
924
925         def key4(self):
926                 self.updEpoch(240)
927
928         def key5(self):
929                 self.updEpoch(300)
930
931         def key6(self):
932                 self.updEpoch(360)
933
934         def getKeyNextBouquetHelptext(self):
935                 return config.misc.graph_mepg.silent_bouquet_change.value and _("Switch to next bouquet") or _("Show bouquet selection menu")
936
937         def getKeyPrevBouquetHelptext(self):
938                 return config.misc.graph_mepg.silent_bouquet_change.value and _("Switch to previous bouquet") or _("Show bouquet selection menu")
939
940         def nextBouquet(self):
941                 if self.bouquetChangeCB:
942                         self.bouquetChangeCB(1, self)
943
944         def prevBouquet(self):
945                 if self.bouquetChangeCB:
946                         self.bouquetChangeCB(-1, self)
947
948         def enterDateTime(self):
949                 t = localtime(time())
950                 config.misc.graph_mepg.prev_time.value = [t.tm_hour, t.tm_min]
951                 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg.prev_time)
952
953         def onDateTimeInputClosed(self, ret):
954                 if len(ret) > 1:
955                         if ret[0]:
956                                 now = time() - config.epg.histminutes.getValue() * 60
957                                 self.ask_time = ret[1] if ret[1] >= now else now
958                                 self.ask_time = self.ask_time - self.ask_time % int(config.misc.graph_mepg.roundTo.getValue())
959                                 l = self["list"]
960                                 l.resetOffset()
961                                 l.fillMultiEPG(None, self.ask_time)
962                                 self.moveTimeLines(True)
963
964         def showSetup(self):
965                 if self.protectContextMenu and config.ParentalControl.setuppinactive.value and config.ParentalControl.config_sections.context_menus.value:
966                         self.session.openWithCallback(self.protectResult, PinInput, pinList=[x.value for x in config.ParentalControl.servicepin], triesEntry=config.ParentalControl.retries.servicepin, title=_("Please enter the correct pin code"), windowTitle=_("Enter pin code"))
967                 else:
968                         self.protectResult(True)
969
970         def protectResult(self, answer):
971                 if answer:
972                         self.session.openWithCallback(self.onSetupClose, GraphMultiEpgSetup)
973                         self.protectContextMenu = False
974                 elif answer is not None:
975                         self.session.openWithCallback(self.close, MessageBox, _("The pin code you entered is wrong."), MessageBox.TYPE_ERROR)
976
977         def onSetupClose(self, ignore = -1):
978                 l = self["list"]
979                 l.setItemsPerPage()
980                 l.setEventFontsize()
981                 l.setEpoch(config.misc.graph_mepg.prev_time_period.value)
982                 l.setOverjump_Empty(config.misc.graph_mepg.overjump.value)
983                 l.setShowServiceMode(config.misc.graph_mepg.servicetitle_mode.value)
984                 now = time() - config.epg.histminutes.getValue() * 60
985                 self.ask_time = now - now % int(config.misc.graph_mepg.roundTo.getValue())
986                 self["timeline_text"].setDateFormat(config.misc.graph_mepg.servicetitle_mode.value)
987                 l.fillMultiEPG(None, self.ask_time)
988                 self.moveTimeLines(True)
989
990         def closeScreen(self):
991                 self.zapFunc(None, zapback = True)
992                 config.misc.graph_mepg.save()
993                 self.close(False)
994
995         def furtherOptions(self):
996                 menu = []
997                 text = _("Select action")
998                 event = self["list"].getCurrent()[0]
999                 if event:
1000                         menu = [(p.name, boundFunction(self.runPlugin, p)) for p in plugins.getPlugins(where = PluginDescriptor.WHERE_EVENTINFO) \
1001                                 if 'selectedevent' in p.__call__.func_code.co_varnames]
1002                         if menu:
1003                                 text += _(": %s") % event.getEventName()
1004                 menu.append((_("Timer Overview"), self.openTimerOverview))
1005                 menu.append((_("Setup menu"), self.showSetup))
1006                 if len(menu) == 1:
1007                         menu and menu[0][1]()
1008                 elif len(menu) > 1:
1009                         def boxAction(choice):
1010                                 if choice:
1011                                         choice[1]()
1012                         self.session.openWithCallback(boxAction, ChoiceBox, title=text, list=menu)
1013
1014         def runPlugin(self, plugin):
1015                 event = self["list"].getCurrent()
1016                 plugin(session=self.session, selectedevent=event)
1017
1018         def openTimerOverview(self):
1019                 self.session.open(TimerEditList)
1020
1021         def infoKeyPressed(self):
1022                 cur = self["list"].getCurrent()
1023                 event = cur[0]
1024                 service = cur[1]
1025                 if event is not None:
1026                         self.session.open(EventViewEPGSelect, event, service, self.eventViewCallback, self.openSingleServiceEPG, self.openMultiServiceEPG, self.openSimilarList)
1027
1028         def openSimilarList(self, eventid, refstr):
1029                 self.session.open(EPGSelection, refstr, None, eventid)
1030
1031         def openSingleServiceEPG(self):
1032                 ref = self["list"].getCurrent()[1].ref.toString()
1033                 if ref:
1034                         self.session.openWithCallback(self.doRefresh, EPGSelection, ref, self.zapFunc, serviceChangeCB=self["list"].moveToFromEPG)
1035
1036         def openMultiServiceEPG(self):
1037                 if self.services:
1038                         self.session.openWithCallback(self.doRefresh, EPGSelection, self.services, self.zapFunc, None, self.bouquetChangeCB)
1039
1040         def setServices(self, services):
1041                 self.services = services
1042                 self["list"].resetOffset()
1043                 self.onCreate()
1044
1045         def doRefresh(self, answer):
1046                 serviceref = Screens.InfoBar.InfoBar.instance.servicelist.getCurrentSelection()
1047                 l = self["list"]
1048                 l.moveToService(serviceref)
1049                 l.setCurrentlyPlaying(serviceref)
1050                 self.moveTimeLines()
1051
1052         def onCreate(self):
1053                 serviceref = Screens.InfoBar.InfoBar.instance.servicelist.getCurrentSelection()
1054                 l = self["list"]
1055                 l.setShowServiceMode(config.misc.graph_mepg.servicetitle_mode.value)
1056                 self["timeline_text"].setDateFormat(config.misc.graph_mepg.servicetitle_mode.value)
1057                 l.fillMultiEPG(self.services, self.ask_time)
1058                 l.moveToService(serviceref)
1059                 l.setCurrentlyPlaying(serviceref)
1060                 self.moveTimeLines()
1061
1062         def eventViewCallback(self, setEvent, setService, val):
1063                 l = self["list"]
1064                 old = l.getCurrent()
1065                 self.updEvent(val, False)
1066                 cur = l.getCurrent()
1067                 if cur[0] is None and cur[1].ref != old[1].ref:
1068                         self.eventViewCallback(setEvent, setService, val)
1069                 else:
1070                         setService(cur[1])
1071                         setEvent(cur[0])
1072
1073         def preview(self):
1074                 ref = self["list"].getCurrent()[1]
1075                 if ref:
1076                         self.zapFunc(ref.ref, preview = True)
1077                         self["list"].setCurrentlyPlaying(ref.ref)
1078                         self["list"].l.invalidate()
1079
1080         def zapTo(self):
1081                 if self.zapFunc and self.key_red_choice == self.ZAP:
1082                         ref = self["list"].getCurrent()[1]
1083                         if ref:
1084                                 from Components.ServiceEventTracker import InfoBarCount
1085                                 preview = InfoBarCount > 1
1086                                 self.zapFunc(ref.ref, preview)
1087                                 if self.previousref and self.previousref == ref.ref and not preview:
1088                                         config.misc.graph_mepg.save()
1089                                         self.close(True)
1090                                 self.previousref = ref.ref
1091                                 self["list"].setCurrentlyPlaying(ref.ref)
1092                                 self["list"].l.invalidate()
1093
1094         def swapMode(self):
1095                 global listscreen
1096                 listscreen = not listscreen
1097                 self.close(None)
1098
1099         def eventSelected(self):
1100                 if config.misc.graph_mepg.OKButton.value == "info":
1101                         self.infoKeyPressed()
1102                 else:
1103                         self.zapTo()
1104
1105         def removeTimer(self, timer):
1106                 timer.afterEvent = AFTEREVENT.NONE
1107                 self.session.nav.RecordTimer.removeEntry(timer)
1108                 self["key_green"].setText(_("Add timer"))
1109                 self.key_green_choice = self.ADD_TIMER
1110
1111         def disableTimer(self, timer, state, repeat=False, record=False):
1112                 if repeat:
1113                         if record:
1114                                 title_text = _("Repeating event currently recording.\nWhat do you want to do?")
1115                                 menu = [(_("Stop current event but not coming events"), "stoponlycurrent"),(_("Stop current event and disable coming events"), "stopall")]
1116                                 if not timer.disabled:
1117                                         menu.append((_("Don't stop current event but disable coming events"), "stoponlycoming"))
1118                         else:
1119                                 title_text = _("Attention, this is repeated timer!\nWhat do you want to do?")
1120                                 menu = [(_("Disable current event but not coming events"), "nextonlystop"),(_("Disable timer"), "simplestop")]
1121                         self.session.openWithCallback(boundFunction(self.runningEventCallback, timer, state), ChoiceBox, title=title_text, list=menu)
1122                 elif timer.state == state:
1123                         timer.disable()
1124                         self.session.nav.RecordTimer.timeChanged(timer)
1125                         self["key_green"].setText(_("Add timer"))
1126                         self.key_green_choice = self.ADD_TIMER
1127
1128         def runningEventCallback(self, t, state, result):
1129                 if result is not None and t.state == state:
1130                         findNextRunningEvent = True
1131                         findEventNext = False
1132                         if result[1] == "nextonlystop":
1133                                 findEventNext = True
1134                                 t.disable()
1135                                 self.session.nav.RecordTimer.timeChanged(t)
1136                                 t.processRepeated(findNextEvent=True)
1137                                 t.enable()
1138                         if result[1] in ("stoponlycurrent", "stopall"):
1139                                 findNextRunningEvent = False
1140                                 t.enable()
1141                                 t.processRepeated(findRunningEvent=False)
1142                                 self.session.nav.RecordTimer.doActivate(t)
1143                         if result[1] in ("stoponlycoming", "stopall", "simplestop"):
1144                                 findNextRunningEvent = True
1145                                 t.disable()
1146                         self.session.nav.RecordTimer.timeChanged(t)
1147                         t.findRunningEvent = findNextRunningEvent
1148                         t.findNextEvent = findEventNext
1149                         if result[1] in ("stoponlycurrent", "stopall", "simplestop", "nextonlystop"):
1150                                 self["key_green"].setText(_("Add timer"))
1151                                 self.key_green_choice = self.ADD_TIMER
1152
1153         def timerAdd(self):
1154                 cur = self["list"].getCurrent()
1155                 event = cur[0]
1156                 serviceref = cur[1]
1157                 if event is None:
1158                         return
1159                 isRecordEvent = isRepeat = firstNextRepeatEvent = isRunning = False
1160                 eventid = event.getEventId()
1161                 begin = event.getBeginTime()
1162                 end = begin + event.getDuration()
1163                 refstr = ':'.join(serviceref.ref.toString().split(':')[:11])
1164                 for timer in self.session.nav.RecordTimer.timer_list:
1165                         needed_ref = ':'.join(timer.service_ref.ref.toString().split(':')[:11]) == refstr
1166                         if needed_ref and timer.eit == eventid and (begin < timer.begin <= end or timer.begin <= begin <= timer.end):
1167                                 isRecordEvent = True
1168                                 break
1169                         elif needed_ref and timer.repeated and self.session.nav.RecordTimer.isInRepeatTimer(timer, event):
1170                                 isRecordEvent = True
1171                                 break
1172                 if isRecordEvent:
1173                         isRepeat = timer.repeated
1174                         prev_state = timer.state
1175                         isRunning = prev_state in (1, 2)
1176                         title_text = isRepeat and _("Attention, this is repeated timer!\n") or ""
1177                         firstNextRepeatEvent = isRepeat and (begin < timer.begin <= end or timer.begin <= begin <= timer.end) and not timer.justplay 
1178                         menu = [(_("Delete timer"), "delete"),(_("Edit timer"), "edit")]
1179                         buttons = ["red", "green"]
1180                         if not isRunning:
1181                                 if firstNextRepeatEvent and timer.isFindRunningEvent() and not timer.isFindNextEvent():
1182                                         menu.append((_("Options disable timer"), "disablerepeat"))
1183                                 else:
1184                                         menu.append((_("Disable timer"), "disable"))
1185                                 buttons.append("yellow")
1186                         elif prev_state == 2 and firstNextRepeatEvent:
1187                                 menu.append((_("Options disable timer"), "disablerepeatrunning"))
1188                                 buttons.append("yellow")
1189                         menu.append((_("Timer Overview"), "timereditlist"))
1190                         def timerAction(choice):
1191                                 if choice is not None:
1192                                         if choice[1] == "delete":
1193                                                 self.removeTimer(timer)
1194                                         elif choice[1] == "edit":
1195                                                 self.session.open(TimerEntry, timer)
1196                                         elif choice[1] == "disable":
1197                                                 self.disableTimer(timer, prev_state)
1198                                         elif choice[1] == "timereditlist":
1199                                                 self.session.open(TimerEditList)
1200                                         elif choice[1] == "disablerepeatrunning":
1201                                                 self.disableTimer(timer, prev_state, repeat=True, record=True)
1202                                         elif choice[1] == "disablerepeat":
1203                                                 self.disableTimer(timer, prev_state, repeat=True)
1204                         self.session.openWithCallback(timerAction, ChoiceBox, title=title_text + _("Select action for timer '%s'.") % timer.name, list=menu, keys=buttons)
1205                 else:
1206                         newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
1207                         self.session.openWithCallback(self.finishedTimerAdd, TimerEntry, newEntry)
1208
1209         def finishedTimerAdd(self, answer):
1210                 print "finished add"
1211                 if answer[0]:
1212                         entry = answer[1]
1213                         simulTimerList = self.session.nav.RecordTimer.record(entry)
1214                         if simulTimerList is not None:
1215                                 for x in simulTimerList:
1216                                         if x.setAutoincreaseEnd(entry):
1217                                                 self.session.nav.RecordTimer.timeChanged(x)
1218                                 simulTimerList = self.session.nav.RecordTimer.record(entry)
1219                                 if simulTimerList is not None:
1220                                         if not entry.repeated and not config.recording.margin_before.value and not config.recording.margin_after.value and len(simulTimerList) > 1:
1221                                                 change_time = False
1222                                                 conflict_begin = simulTimerList[1].begin
1223                                                 conflict_end = simulTimerList[1].end
1224                                                 if conflict_begin == entry.end:
1225                                                         entry.end -= 30
1226                                                         change_time = True
1227                                                 elif entry.begin == conflict_end:
1228                                                         entry.begin += 30
1229                                                         change_time = True
1230                                                 if change_time:
1231                                                         simulTimerList = self.session.nav.RecordTimer.record(entry)
1232                                         if simulTimerList is not None:
1233                                                 self.session.openWithCallback(self.finishSanityCorrection, TimerSanityConflict, simulTimerList)
1234                                                 return
1235                         cur = self["list"].getCurrent()
1236                         event = cur and cur[0]
1237                         if event:
1238                                 begin = event.getBeginTime()
1239                                 end = begin + event.getDuration()
1240                                 if begin < entry.begin <= end or entry.begin <= begin <= entry.end:
1241                                         self["key_green"].setText(_("Change timer"))
1242                                         self.key_green_choice = self.REMOVE_TIMER
1243                 else:
1244                         self["key_green"].setText(_("Add timer"))
1245                         self.key_green_choice = self.ADD_TIMER
1246                         print "Timeredit aborted"
1247
1248         def finishSanityCorrection(self, answer):
1249                 self.finishedTimerAdd(answer)
1250
1251         def onSelectionChanged(self):
1252                 cur = self["list"].getCurrent()
1253                 event = cur[0]
1254                 self["Event"].newEvent(event)
1255
1256                 if cur[1] is None or cur[1].getServiceName() == "":
1257                         if self.key_green_choice != self.EMPTY:
1258                                 self["key_green"].setText("")
1259                                 self.key_green_choice = self.EMPTY
1260                         if self.key_red_choice != self.EMPTY:
1261                                 self["key_red"].setText("")
1262                                 self.key_red_choice = self.EMPTY
1263                         return
1264
1265                 servicerefref = cur[1].ref
1266                 self["Service"].newService(servicerefref)
1267
1268                 if self.key_red_choice != self.ZAP:
1269                         self["key_red"].setText(_("Zap"))
1270                         self.key_red_choice = self.ZAP
1271
1272                 if not event:
1273                         if self.key_green_choice != self.EMPTY:
1274                                 self["key_green"].setText("")
1275                                 self.key_green_choice = self.EMPTY
1276                         return
1277
1278                 eventid = event.getEventId()
1279                 begin = event.getBeginTime()
1280                 end = begin + event.getDuration()
1281                 refstr = ':'.join(servicerefref.toString().split(':')[:11])
1282                 isRecordEvent = False
1283                 for timer in self.session.nav.RecordTimer.timer_list:
1284                         needed_ref = ':'.join(timer.service_ref.ref.toString().split(':')[:11]) == refstr
1285                         if needed_ref and (timer.eit == eventid and (begin < timer.begin <= end or timer.begin <= begin <= timer.end) or timer.repeated and self.session.nav.RecordTimer.isInRepeatTimer(timer, event)):
1286                                 isRecordEvent = True
1287                                 break
1288                 if isRecordEvent and self.key_green_choice != self.REMOVE_TIMER:
1289                         self["key_green"].setText(_("Change timer"))
1290                         self.key_green_choice = self.REMOVE_TIMER
1291                 elif not isRecordEvent and self.key_green_choice != self.ADD_TIMER:
1292                         self["key_green"].setText(_("Add timer"))
1293                         self.key_green_choice = self.ADD_TIMER
1294
1295         def moveTimeLines(self, force=False):
1296                 self.updateTimelineTimer.start((60 - (int(time()) % 60)) * 1000)        #keep syncronised
1297                 self["timeline_text"].setEntries(self["list"], self["timeline_now"], self.time_lines, force)
1298                 self["list"].l.invalidate() # not needed when the zPosition in the skin is correct! ?????