PEP8: Fix whitespace
[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(_("PrimeTime"))
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["gmepgactions"] = HelpableActionMap(self, "GMEPGSelectActions",
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                                 "blue":        (self.togglePrimeNow, _("Goto primetime / now")),
857                                 "blue_long":   (self.enterDateTime,  _("Goto specific date/time")),
858                                 "yellow":      (self.swapMode,       _("Switch between normal mode and list mode")),
859                                 "menu":        (self.furtherOptions, _("Further Options")),
860                                 "nextBouquet": (self.nextBouquet, self.getKeyNextBouquetHelptext),
861                                 "prevBouquet": (self.prevBouquet, self.getKeyPrevBouquetHelptext),
862                                 "nextService": (self.nextPressed,    _("Goto next page of events")),
863                                 "prevService": (self.prevPressed,    _("Goto previous page of events")),
864                                 "preview":     (self.preview,        _("Preview selected channel")),
865                                 "nextDay":     (self.nextDay,        _("Goto next day of events")),
866                                 "prevDay":     (self.prevDay,        _("Goto previous day of events"))
867                         }, -1)
868                 self["gmepgactions"].csel = self
869
870                 self["inputactions"] = HelpableActionMap(self, "InputActions",
871                         {
872                                 "left":  (self.leftPressed,  _("Go to previous event")),
873                                 "right": (self.rightPressed, _("Go to next event")),
874                                 "1":     (self.key1,         _("Set time window to 1 hour")),
875                                 "2":     (self.key2,         _("Set time window to 2 hours")),
876                                 "3":     (self.key3,         _("Set time window to 3 hours")),
877                                 "4":     (self.key4,         _("Set time window to 4 hours")),
878                                 "5":     (self.key5,         _("Set time window to 5 hours")),
879                                 "6":     (self.key6,         _("Set time window to 6 hours")),
880                                 "7":     (self.prevPage,     _("Go to previous page of service")),
881                                 "9":     (self.nextPage,     _("Go to next page of service")),
882                                 "8":     (self.toTop,        _("Go to first service")),
883                                 "0":     (self.toEnd,        _("Go to last service"))
884                         }, -1)
885                 self["inputactions"].csel = self
886
887                 self.protectContextMenu = True
888                 self.updateTimelineTimer = eTimer()
889                 self.updateTimelineTimer.callback.append(self.moveTimeLines)
890                 self.updateTimelineTimer.start(60 * 1000)
891                 self.onLayoutFinish.append(self.onCreate)
892                 self.previousref = self.session.nav.getCurrentlyPlayingServiceOrGroup()
893
894         def prevPage(self):
895                 self["list"].moveTo(eListbox.pageUp)
896
897         def nextPage(self):
898                 self["list"].moveTo(eListbox.pageDown)
899
900         def toTop(self):
901                 self["list"].moveTo(eListbox.moveTop)
902
903         def toEnd(self):
904                 self["list"].moveTo(eListbox.moveEnd)
905
906         def prevPressed(self):
907                 self.updEvent(-2)
908
909         def nextPressed(self):
910                 self.updEvent(+2)
911
912         def leftPressed(self):
913                 self.updEvent(-1)
914
915         def rightPressed(self):
916                 self.updEvent(+1)
917
918         def prevDay(self):
919                 self.updEvent(-3)
920
921         def nextDay(self):
922                 self.updEvent(+3)
923
924         def updEvent(self, dir, visible = True):
925                 ret = self["list"].selEntry(dir, visible)
926                 if ret:
927                         if self["list"].offs > 0:
928                                 self.time_mode = self.TIME_CHANGE
929                         else:
930                                 self.time_mode = self.TIME_NOW
931                         self.moveTimeLines(True)
932
933         def updEpoch(self, mins):
934                 self["list"].setEpoch(mins)
935                 config.misc.graph_mepg.prev_time_period.value = mins
936                 self.moveTimeLines()
937
938         def key1(self):
939                 self.updEpoch(60)
940
941         def key2(self):
942                 self.updEpoch(120)
943
944         def key3(self):
945                 self.updEpoch(180)
946
947         def key4(self):
948                 self.updEpoch(240)
949
950         def key5(self):
951                 self.updEpoch(300)
952
953         def key6(self):
954                 self.updEpoch(360)
955
956         def getKeyNextBouquetHelptext(self):
957                 return config.misc.graph_mepg.silent_bouquet_change.value and _("Switch to next bouquet") or _("Show bouquet selection menu")
958
959         def getKeyPrevBouquetHelptext(self):
960                 return config.misc.graph_mepg.silent_bouquet_change.value and _("Switch to previous bouquet") or _("Show bouquet selection menu")
961
962         def nextBouquet(self):
963                 if self.bouquetChangeCB:
964                         self.bouquetChangeCB(1, self)
965
966         def prevBouquet(self):
967                 if self.bouquetChangeCB:
968                         self.bouquetChangeCB(-1, self)
969
970         def togglePrimeNow(self):
971                 if self.time_mode == self.TIME_NOW:
972                         self.setNewTime("prime_time")
973                 elif self.time_mode == self.TIME_PRIME or self.time_mode == self.TIME_CHANGE:
974                         self.setNewTime("now_time")
975
976         def enterDateTime(self):
977                 t = localtime(time())
978                 config.misc.graph_mepg.prev_time.value = [t.tm_hour, t.tm_min]
979                 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg.prev_time)
980
981         def onDateTimeInputClosed(self, ret):
982                 if len(ret) > 1:
983                         if ret[0]:
984                                 now = time() - config.epg.histminutes.getValue() * 60
985                                 self.ask_time = ret[1] if ret[1] >= now else now
986                                 self.ask_time = self.ask_time - self.ask_time % int(config.misc.graph_mepg.roundTo.getValue())
987                                 l = self["list"]
988                                 l.resetOffset()
989                                 l.fillMultiEPG(None, self.ask_time)
990                                 self.moveTimeLines(True)
991                                 self.time_mode = self.TIME_CHANGE
992                                 self["key_blue"].setText(_("Now"))
993
994         def setNewTime(self, type=''):
995                 if type:
996                         date = time() - config.epg.histminutes.getValue() * 60
997                         if type == "now_time":
998                                 self.time_mode = self.TIME_NOW
999                                 self["key_blue"].setText(_("PrimeTime"))
1000                         elif type == "prime_time":
1001                                 now = [x for x in localtime(date)]
1002                                 prime = config.misc.graph_mepg.prime_time.value
1003                                 date = mktime([now[0], now[1], now[2], prime[0], prime[1], 0, 0, 0, now[8]])
1004                                 if now[3] > prime[0] or (now[3] == prime[0] and now[4] > prime[1]):
1005                                         date = date + 60*60*24
1006                                 self.time_mode = self.TIME_PRIME
1007                                 self["key_blue"].setText(_("Now"))
1008                         l = self["list"]
1009                         self.ask_time = date - date % int(config.misc.graph_mepg.roundTo.getValue())
1010                         l.resetOffset()
1011                         l.fillMultiEPG(None, self.ask_time)
1012                         self.moveTimeLines(True)
1013
1014         def showSetup(self):
1015                 if self.protectContextMenu and config.ParentalControl.setuppinactive.value and config.ParentalControl.config_sections.context_menus.value:
1016                         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"))
1017                 else:
1018                         self.protectResult(True)
1019
1020         def protectResult(self, answer):
1021                 if answer:
1022                         self.session.openWithCallback(self.onSetupClose, GraphMultiEpgSetup)
1023                         self.protectContextMenu = False
1024                 elif answer is not None:
1025                         self.session.openWithCallback(self.close, MessageBox, _("The pin code you entered is wrong."), MessageBox.TYPE_ERROR)
1026
1027         def onSetupClose(self, ignore = -1):
1028                 l = self["list"]
1029                 l.setItemsPerPage()
1030                 l.setEventFontsize()
1031                 l.setEpoch(config.misc.graph_mepg.prev_time_period.value)
1032                 l.setOverjump_Empty(config.misc.graph_mepg.overjump.value)
1033                 l.setShowServiceMode(config.misc.graph_mepg.servicetitle_mode.value)
1034                 now = time() - config.epg.histminutes.getValue() * 60
1035                 self.ask_time = now - now % int(config.misc.graph_mepg.roundTo.getValue())
1036                 self["timeline_text"].setDateFormat(config.misc.graph_mepg.servicetitle_mode.value)
1037                 l.fillMultiEPG(None, self.ask_time)
1038                 self.moveTimeLines(True)
1039                 self.time_mode = self.TIME_NOW
1040                 self["key_blue"].setText(_("PrimeTime"))
1041
1042         def closeScreen(self):
1043                 self.zapFunc(None, zapback = True)
1044                 config.misc.graph_mepg.save()
1045                 self.close(False)
1046
1047         def furtherOptions(self):
1048                 menu = []
1049                 text = _("Select action")
1050                 event = self["list"].getCurrent()[0]
1051                 if event:
1052                         menu = [(p.name, boundFunction(self.runPlugin, p)) for p in plugins.getPlugins(where = PluginDescriptor.WHERE_EVENTINFO) \
1053                                 if 'selectedevent' in p.__call__.func_code.co_varnames]
1054                         if menu:
1055                                 text += _(": %s") % event.getEventName()
1056                 menu.append((_("Timer Overview"), self.openTimerOverview))
1057                 menu.append((_("Setup menu"), self.showSetup))
1058                 if len(menu) == 1:
1059                         menu and menu[0][1]()
1060                 elif len(menu) > 1:
1061                         def boxAction(choice):
1062                                 if choice:
1063                                         choice[1]()
1064                         self.session.openWithCallback(boxAction, ChoiceBox, title=text, list=menu, windowTitle=_("Further options"))
1065
1066         def runPlugin(self, plugin):
1067                 event = self["list"].getCurrent()
1068                 plugin(session=self.session, selectedevent=event)
1069
1070         def openTimerOverview(self):
1071                 self.session.open(TimerEditList)
1072
1073         def infoKeyPressed(self):
1074                 cur = self["list"].getCurrent()
1075                 event = cur[0]
1076                 service = cur[1]
1077                 if event is not None:
1078                         self.session.open(EventViewEPGSelect, event, service, self.eventViewCallback, self.openSingleServiceEPG, self.openMultiServiceEPG, self.openSimilarList)
1079
1080         def openSimilarList(self, eventid, refstr):
1081                 self.session.open(EPGSelection, refstr, None, eventid)
1082
1083         def openSingleServiceEPG(self):
1084                 ref = self["list"].getCurrent()[1].ref.toString()
1085                 if ref:
1086                         self.session.openWithCallback(self.doRefresh, EPGSelection, ref, self.zapFunc, serviceChangeCB=self["list"].moveToFromEPG)
1087
1088         def openMultiServiceEPG(self):
1089                 if self.services:
1090                         self.session.openWithCallback(self.doRefresh, EPGSelection, self.services, self.zapFunc, None, self.bouquetChangeCB)
1091
1092         def setServices(self, services):
1093                 self.services = services
1094                 self["list"].resetOffset()
1095                 self.onCreate()
1096
1097         def doRefresh(self, answer):
1098                 serviceref = Screens.InfoBar.InfoBar.instance.servicelist.getCurrentSelection()
1099                 l = self["list"]
1100                 l.moveToService(serviceref)
1101                 l.setCurrentlyPlaying(serviceref)
1102                 self.moveTimeLines()
1103
1104         def onCreate(self):
1105                 serviceref = Screens.InfoBar.InfoBar.instance.servicelist.getCurrentSelection()
1106                 l = self["list"]
1107                 l.setShowServiceMode(config.misc.graph_mepg.servicetitle_mode.value)
1108                 self["timeline_text"].setDateFormat(config.misc.graph_mepg.servicetitle_mode.value)
1109                 l.fillMultiEPG(self.services, self.ask_time)
1110                 l.moveToService(serviceref)
1111                 l.setCurrentlyPlaying(serviceref)
1112                 self.moveTimeLines()
1113
1114         def eventViewCallback(self, setEvent, setService, val):
1115                 l = self["list"]
1116                 old = l.getCurrent()
1117                 self.updEvent(val, False)
1118                 cur = l.getCurrent()
1119                 if cur[0] is None and cur[1].ref != old[1].ref:
1120                         self.eventViewCallback(setEvent, setService, val)
1121                 else:
1122                         setService(cur[1])
1123                         setEvent(cur[0])
1124
1125         def preview(self):
1126                 ref = self["list"].getCurrent()[1]
1127                 if ref:
1128                         self.zapFunc(ref.ref, preview = True)
1129                         self["list"].setCurrentlyPlaying(ref.ref)
1130                         self["list"].l.invalidate()
1131
1132         def zapTo(self):
1133                 if self.zapFunc and self.key_red_choice == self.ZAP:
1134                         ref = self["list"].getCurrent()[1]
1135                         if ref:
1136                                 from Components.ServiceEventTracker import InfoBarCount
1137                                 preview = InfoBarCount > 1
1138                                 self.zapFunc(ref.ref, preview)
1139                                 if self.previousref and self.previousref == ref.ref and not preview:
1140                                         config.misc.graph_mepg.save()
1141                                         self.close(True)
1142                                 self.previousref = ref.ref
1143                                 self["list"].setCurrentlyPlaying(ref.ref)
1144                                 self["list"].l.invalidate()
1145
1146         def swapMode(self):
1147                 global listscreen
1148                 listscreen = not listscreen
1149                 self.close(None)
1150
1151         def eventSelected(self):
1152                 if config.misc.graph_mepg.OKButton.value == "info":
1153                         self.infoKeyPressed()
1154                 else:
1155                         self.zapTo()
1156
1157         def removeTimer(self, timer):
1158                 timer.afterEvent = AFTEREVENT.NONE
1159                 self.session.nav.RecordTimer.removeEntry(timer)
1160                 self["key_green"].setText(_("Add timer"))
1161                 self.key_green_choice = self.ADD_TIMER
1162
1163         def disableTimer(self, timer, state, repeat=False, record=False):
1164                 if repeat:
1165                         if record:
1166                                 title_text = _("Repeating event currently recording.\nWhat do you want to do?")
1167                                 menu = [(_("Stop current event but not coming events"), "stoponlycurrent"),(_("Stop current event and disable coming events"), "stopall")]
1168                                 if not timer.disabled:
1169                                         menu.append((_("Don't stop current event but disable coming events"), "stoponlycoming"))
1170                         else:
1171                                 title_text = _("Attention, this is repeated timer!\nWhat do you want to do?")
1172                                 menu = [(_("Disable current event but not coming events"), "nextonlystop"),(_("Disable timer"), "simplestop")]
1173                         self.session.openWithCallback(boundFunction(self.runningEventCallback, timer, state), ChoiceBox, title=title_text, list=menu)
1174                 elif timer.state == state:
1175                         timer.disable()
1176                         self.session.nav.RecordTimer.timeChanged(timer)
1177                         self["key_green"].setText(_("Add timer"))
1178                         self.key_green_choice = self.ADD_TIMER
1179
1180         def runningEventCallback(self, t, state, result):
1181                 if result is not None and t.state == state:
1182                         findNextRunningEvent = True
1183                         findEventNext = False
1184                         if result[1] == "nextonlystop":
1185                                 findEventNext = True
1186                                 t.disable()
1187                                 self.session.nav.RecordTimer.timeChanged(t)
1188                                 t.processRepeated(findNextEvent=True)
1189                                 t.enable()
1190                         if result[1] in ("stoponlycurrent", "stopall"):
1191                                 findNextRunningEvent = False
1192                                 t.enable()
1193                                 t.processRepeated(findRunningEvent=False)
1194                                 self.session.nav.RecordTimer.doActivate(t)
1195                         if result[1] in ("stoponlycoming", "stopall", "simplestop"):
1196                                 findNextRunningEvent = True
1197                                 t.disable()
1198                         self.session.nav.RecordTimer.timeChanged(t)
1199                         t.findRunningEvent = findNextRunningEvent
1200                         t.findNextEvent = findEventNext
1201                         if result[1] in ("stoponlycurrent", "stopall", "simplestop", "nextonlystop"):
1202                                 self["key_green"].setText(_("Add timer"))
1203                                 self.key_green_choice = self.ADD_TIMER
1204
1205         def timerAdd(self):
1206                 cur = self["list"].getCurrent()
1207                 event = cur[0]
1208                 serviceref = cur[1]
1209                 if event is None:
1210                         return
1211                 isRecordEvent = isRepeat = firstNextRepeatEvent = isRunning = False
1212                 eventid = event.getEventId()
1213                 begin = event.getBeginTime()
1214                 end = begin + event.getDuration()
1215                 refstr = ':'.join(serviceref.ref.toString().split(':')[:11])
1216                 for timer in self.session.nav.RecordTimer.timer_list:
1217                         needed_ref = ':'.join(timer.service_ref.ref.toString().split(':')[:11]) == refstr
1218                         if needed_ref and timer.eit == eventid and (begin < timer.begin <= end or timer.begin <= begin <= timer.end):
1219                                 isRecordEvent = True
1220                                 break
1221                         elif needed_ref and timer.repeated and self.session.nav.RecordTimer.isInRepeatTimer(timer, event):
1222                                 isRecordEvent = True
1223                                 break
1224                 if isRecordEvent:
1225                         isRepeat = timer.repeated
1226                         prev_state = timer.state
1227                         isRunning = prev_state in (1, 2)
1228                         title_text = isRepeat and _("Attention, this is repeated timer!\n") or ""
1229                         firstNextRepeatEvent = isRepeat and (begin < timer.begin <= end or timer.begin <= begin <= timer.end) and not timer.justplay
1230                         menu = [(_("Delete timer"), "delete"),(_("Edit timer"), "edit")]
1231                         buttons = ["red", "green"]
1232                         if not isRunning:
1233                                 if firstNextRepeatEvent and timer.isFindRunningEvent() and not timer.isFindNextEvent():
1234                                         menu.append((_("Options disable timer"), "disablerepeat"))
1235                                 else:
1236                                         menu.append((_("Disable timer"), "disable"))
1237                                 buttons.append("yellow")
1238                         elif prev_state == 2 and firstNextRepeatEvent:
1239                                 menu.append((_("Options disable timer"), "disablerepeatrunning"))
1240                                 buttons.append("yellow")
1241                         menu.append((_("Timer Overview"), "timereditlist"))
1242                         def timerAction(choice):
1243                                 if choice is not None:
1244                                         if choice[1] == "delete":
1245                                                 self.removeTimer(timer)
1246                                         elif choice[1] == "edit":
1247                                                 self.session.openWithCallback(self.finishedEdit, TimerEntry, timer)
1248                                         elif choice[1] == "disable":
1249                                                 self.disableTimer(timer, prev_state)
1250                                         elif choice[1] == "timereditlist":
1251                                                 self.session.open(TimerEditList)
1252                                         elif choice[1] == "disablerepeatrunning":
1253                                                 self.disableTimer(timer, prev_state, repeat=True, record=True)
1254                                         elif choice[1] == "disablerepeat":
1255                                                 self.disableTimer(timer, prev_state, repeat=True)
1256                         self.session.openWithCallback(timerAction, ChoiceBox, title=title_text + _("Select action for timer '%s'.") % timer.name, list=menu, keys=buttons)
1257                 else:
1258                         newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
1259                         self.session.openWithCallback(self.finishedTimerAdd, TimerEntry, newEntry)
1260
1261         def finishedEdit(self, answer=None):
1262                 if answer[0]:
1263                         entry = answer[1]
1264                         simulTimerList = self.session.nav.RecordTimer.record(entry)
1265                         if simulTimerList is not None:
1266                                 for x in simulTimerList:
1267                                         if x.setAutoincreaseEnd(entry):
1268                                                 self.session.nav.RecordTimer.timeChanged(x)
1269                                 simulTimerList = self.session.nav.RecordTimer.record(entry)
1270                                 if simulTimerList is not None:
1271                                         self.session.openWithCallback(self.finishedEdit, TimerSanityConflict, simulTimerList)
1272                                         return
1273                                 else:
1274                                         self.session.nav.RecordTimer.timeChanged(entry)
1275                 self.onSelectionChanged()
1276
1277         def finishedTimerAdd(self, answer):
1278                 print "finished add"
1279                 if answer[0]:
1280                         entry = answer[1]
1281                         simulTimerList = self.session.nav.RecordTimer.record(entry)
1282                         if simulTimerList is not None:
1283                                 for x in simulTimerList:
1284                                         if x.setAutoincreaseEnd(entry):
1285                                                 self.session.nav.RecordTimer.timeChanged(x)
1286                                 simulTimerList = self.session.nav.RecordTimer.record(entry)
1287                                 if simulTimerList is not None:
1288                                         if not entry.repeated and not config.recording.margin_before.value and not config.recording.margin_after.value and len(simulTimerList) > 1:
1289                                                 change_time = False
1290                                                 conflict_begin = simulTimerList[1].begin
1291                                                 conflict_end = simulTimerList[1].end
1292                                                 if conflict_begin == entry.end:
1293                                                         entry.end -= 30
1294                                                         change_time = True
1295                                                 elif entry.begin == conflict_end:
1296                                                         entry.begin += 30
1297                                                         change_time = True
1298                                                 if change_time:
1299                                                         simulTimerList = self.session.nav.RecordTimer.record(entry)
1300                                         if simulTimerList is not None:
1301                                                 self.session.openWithCallback(self.finishSanityCorrection, TimerSanityConflict, simulTimerList)
1302                                                 return
1303                         cur = self["list"].getCurrent()
1304                         event = cur and cur[0]
1305                         if event:
1306                                 begin = event.getBeginTime()
1307                                 end = begin + event.getDuration()
1308                                 if begin < entry.begin <= end or entry.begin <= begin <= entry.end:
1309                                         self["key_green"].setText(_("Change timer"))
1310                                         self.key_green_choice = self.REMOVE_TIMER
1311                 else:
1312                         self["key_green"].setText(_("Add timer"))
1313                         self.key_green_choice = self.ADD_TIMER
1314                         print "Timeredit aborted"
1315
1316         def finishSanityCorrection(self, answer):
1317                 self.finishedTimerAdd(answer)
1318
1319         def onSelectionChanged(self):
1320                 cur = self["list"].getCurrent()
1321                 event = cur[0]
1322                 self["Event"].newEvent(event)
1323
1324                 if cur[1] is None or cur[1].getServiceName() == "":
1325                         if self.key_green_choice != self.EMPTY:
1326                                 self["key_green"].setText("")
1327                                 self.key_green_choice = self.EMPTY
1328                         if self.key_red_choice != self.EMPTY:
1329                                 self["key_red"].setText("")
1330                                 self.key_red_choice = self.EMPTY
1331                         return
1332
1333                 servicerefref = cur[1].ref
1334                 self["Service"].newService(servicerefref)
1335
1336                 if self.key_red_choice != self.ZAP:
1337                         self["key_red"].setText(_("Zap"))
1338                         self.key_red_choice = self.ZAP
1339
1340                 if not event:
1341                         if self.key_green_choice != self.EMPTY:
1342                                 self["key_green"].setText("")
1343                                 self.key_green_choice = self.EMPTY
1344                         return
1345
1346                 eventid = event.getEventId()
1347                 begin = event.getBeginTime()
1348                 end = begin + event.getDuration()
1349                 refstr = ':'.join(servicerefref.toString().split(':')[:11])
1350                 isRecordEvent = False
1351                 for timer in self.session.nav.RecordTimer.timer_list:
1352                         needed_ref = ':'.join(timer.service_ref.ref.toString().split(':')[:11]) == refstr
1353                         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)):
1354                                 isRecordEvent = True
1355                                 break
1356                 if isRecordEvent and self.key_green_choice != self.REMOVE_TIMER:
1357                         self["key_green"].setText(_("Change timer"))
1358                         self.key_green_choice = self.REMOVE_TIMER
1359                 elif not isRecordEvent and self.key_green_choice != self.ADD_TIMER:
1360                         self["key_green"].setText(_("Add timer"))
1361                         self.key_green_choice = self.ADD_TIMER
1362
1363         def moveTimeLines(self, force=False):
1364                 self.updateTimelineTimer.start((60 - (int(time()) % 60)) * 1000)        #keep syncronised
1365                 self["timeline_text"].setEntries(self["list"], self["timeline_now"], self.time_lines, force)
1366                 self["list"].l.invalidate() # not needed when the zPosition in the skin is correct! ?????