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