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