580f4ba26b3db3a1561e2b3d8fd0cde4866af100
[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, repeat=False, record=False):
1080                 if repeat:
1081                         if record:
1082                                 title_text = _("Repeating event currently recording.\nWhat do you want to do?")
1083                                 menu = [(_("Stop current event but not coming events"), "stoponlycurrent"),(_("Stop current event and disable coming events"), "stopall")]
1084                                 if not timer.disabled:
1085                                         menu.append((_("Don't stop current event but disable coming events"), "stoponlycoming"))
1086                         else:
1087                                 title_text = _("Attention, this is repeated timer!\nWhat do you want to do?")
1088                                 menu = [(_("Disable current event but not coming events"), "nextonlystop"),(_("Disable timer"), "simplestop")]
1089                         self.session.openWithCallback(boundFunction(self.runningEventCallback, timer), ChoiceBox, title=title_text, list=menu)
1090                 else:
1091                         timer.disable()
1092                         self.session.nav.RecordTimer.timeChanged(timer)
1093                         self["key_green"].setText(_("Add timer"))
1094                         self.key_green_choice = self.ADD_TIMER
1095
1096         def runningEventCallback(self, t, result):
1097                 if result is not None:
1098                         findNextRunningEvent = True
1099                         findEventNext = False
1100                         if result[1] == "nextonlystop":
1101                                 findEventNext = True
1102                                 t.disable()
1103                                 self.session.nav.RecordTimer.timeChanged(t)
1104                                 t.processRepeated(findNextEvent=True)
1105                                 t.enable()
1106                         if result[1] in ("stoponlycurrent", "stopall"):
1107                                 findNextRunningEvent = False
1108                                 t.enable()
1109                                 t.processRepeated(findRunningEvent=False)
1110                                 self.session.nav.RecordTimer.doActivate(t)
1111                         if result[1] in ("stoponlycoming", "stopall", "simplestop"):
1112                                 findNextRunningEvent = True
1113                                 t.disable()
1114                         self.session.nav.RecordTimer.timeChanged(t)
1115                         t.findRunningEvent = findNextRunningEvent
1116                         t.findNextEvent = findEventNext
1117                         if result[1] in ("stoponlycurrent", "stopall", "simplestop", "nextonlystop"):
1118                                 self["key_green"].setText(_("Add timer"))
1119                                 self.key_green_choice = self.ADD_TIMER
1120
1121         def timerAdd(self):
1122                 cur = self["list"].getCurrent()
1123                 event = cur[0]
1124                 serviceref = cur[1]
1125                 if event is None:
1126                         return
1127                 isRecordEvent = isRepeat = firstNextRepeatEvent = False
1128                 eventid = event.getEventId()
1129                 begin = event.getBeginTime()
1130                 end = begin + event.getDuration()
1131                 refstr = ':'.join(serviceref.ref.toString().split(':')[:11])
1132                 for timer in self.session.nav.RecordTimer.timer_list:
1133                         needed_ref = ':'.join(timer.service_ref.ref.toString().split(':')[:11]) == refstr
1134                         if needed_ref and timer.eit == eventid and (begin < timer.begin <= end or timer.begin <= begin <= timer.end):
1135                                 isRecordEvent = True
1136                                 break
1137                         elif needed_ref and timer.repeated and self.session.nav.RecordTimer.isInRepeatTimer(timer, event):
1138                                 isRecordEvent = True
1139                                 break
1140                 if isRecordEvent:
1141                         isRepeat = timer.repeated
1142                         title_text = isRepeat and _("Attention, this is repeated timer!\n") or ""
1143                         firstNextRepeatEvent = isRepeat and (begin < timer.begin <= end or timer.begin <= begin <= timer.end) and not timer.justplay 
1144                         menu = [(_("Delete timer"), "delete"),(_("Edit timer"), "edit")]
1145                         buttons = ["red", "green"]
1146                         if not timer.isRunning():
1147                                 if firstNextRepeatEvent and timer.isFindRunningEvent() and not timer.isFindNextEvent():
1148                                         menu.append((_("Options disable timer"), "disablerepeat"))
1149                                 else:
1150                                         menu.append((_("Disable timer"), "disable"))
1151                                 buttons.append("yellow")
1152                         elif firstNextRepeatEvent:
1153                                 menu.append((_("Options disable timer"), "disablerepeatrunning"))
1154                                 buttons.append("yellow")
1155                         menu.append((_("Timer Overview"), "timereditlist"))
1156                         def timerAction(choice):
1157                                 if choice is not None:
1158                                         if choice[1] == "delete":
1159                                                 self.removeTimer(timer)
1160                                         elif choice[1] == "edit":
1161                                                 self.session.open(TimerEntry, timer)
1162                                         elif choice[1] == "disable":
1163                                                 self.disableTimer(timer)
1164                                         elif choice[1] == "timereditlist":
1165                                                 self.session.open(TimerEditList)
1166                                         elif choice[1] == "disablerepeatrunning":
1167                                                 self.disableTimer(timer, repeat=True, record=True)
1168                                         elif choice[1] == "disablerepeat":
1169                                                 self.disableTimer(timer, repeat=True)
1170                         self.session.openWithCallback(timerAction, ChoiceBox, title=title_text + _("Select action for timer '%s'.") % timer.name, list=menu, keys=buttons)
1171                 else:
1172                         newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
1173                         self.session.openWithCallback(self.finishedTimerAdd, TimerEntry, newEntry)
1174
1175         def finishedTimerAdd(self, answer):
1176                 print "finished add"
1177                 if answer[0]:
1178                         entry = answer[1]
1179                         simulTimerList = self.session.nav.RecordTimer.record(entry)
1180                         if simulTimerList is not None:
1181                                 for x in simulTimerList:
1182                                         if x.setAutoincreaseEnd(entry):
1183                                                 self.session.nav.RecordTimer.timeChanged(x)
1184                                 simulTimerList = self.session.nav.RecordTimer.record(entry)
1185                                 if simulTimerList is not None:
1186                                         if not entry.repeated and not config.recording.margin_before.value and not config.recording.margin_after.value and len(simulTimerList) > 1:
1187                                                 change_time = False
1188                                                 conflict_begin = simulTimerList[1].begin
1189                                                 conflict_end = simulTimerList[1].end
1190                                                 if conflict_begin == entry.end:
1191                                                         entry.end -= 30
1192                                                         change_time = True
1193                                                 elif entry.begin == conflict_end:
1194                                                         entry.begin += 30
1195                                                         change_time = True
1196                                                 if change_time:
1197                                                         simulTimerList = self.session.nav.RecordTimer.record(entry)
1198                                         if simulTimerList is not None:
1199                                                 self.session.openWithCallback(self.finishSanityCorrection, TimerSanityConflict, simulTimerList)
1200                                                 return
1201                         cur = self["list"].getCurrent()
1202                         event = cur and cur[0]
1203                         if event:
1204                                 begin = event.getBeginTime()
1205                                 end = begin + event.getDuration()
1206                                 if begin < entry.begin <= end or entry.begin <= begin <= entry.end:
1207                                         self["key_green"].setText(_("Change timer"))
1208                                         self.key_green_choice = self.REMOVE_TIMER
1209                 else:
1210                         self["key_green"].setText(_("Add timer"))
1211                         self.key_green_choice = self.ADD_TIMER
1212                         print "Timeredit aborted"
1213
1214         def finishSanityCorrection(self, answer):
1215                 self.finishedTimerAdd(answer)
1216
1217         def onSelectionChanged(self):
1218                 cur = self["list"].getCurrent()
1219                 event = cur[0]
1220                 self["Event"].newEvent(event)
1221
1222                 if cur[1] is None or cur[1].getServiceName() == "":
1223                         if self.key_green_choice != self.EMPTY:
1224                                 self["key_green"].setText("")
1225                                 self.key_green_choice = self.EMPTY
1226                         if self.key_red_choice != self.EMPTY:
1227                                 self["key_red"].setText("")
1228                                 self.key_red_choice = self.EMPTY
1229                         return
1230
1231                 servicerefref = cur[1].ref
1232                 self["Service"].newService(servicerefref)
1233
1234                 if self.key_red_choice != self.ZAP:
1235                         self["key_red"].setText(_("Zap"))
1236                         self.key_red_choice = self.ZAP
1237
1238                 if not event:
1239                         if self.key_green_choice != self.EMPTY:
1240                                 self["key_green"].setText("")
1241                                 self.key_green_choice = self.EMPTY
1242                         return
1243
1244                 eventid = event.getEventId()
1245                 begin = event.getBeginTime()
1246                 end = begin + event.getDuration()
1247                 refstr = ':'.join(servicerefref.toString().split(':')[:11])
1248                 isRecordEvent = False
1249                 for timer in self.session.nav.RecordTimer.timer_list:
1250                         needed_ref = ':'.join(timer.service_ref.ref.toString().split(':')[:11]) == refstr
1251                         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)):
1252                                 isRecordEvent = True
1253                                 break
1254                 if isRecordEvent and self.key_green_choice != self.REMOVE_TIMER:
1255                         self["key_green"].setText(_("Change timer"))
1256                         self.key_green_choice = self.REMOVE_TIMER
1257                 elif not isRecordEvent and self.key_green_choice != self.ADD_TIMER:
1258                         self["key_green"].setText(_("Add timer"))
1259                         self.key_green_choice = self.ADD_TIMER
1260
1261         def moveTimeLines(self, force=False):
1262                 self.updateTimelineTimer.start((60 - (int(time()) % 60)) * 1000)        #keep syncronised
1263                 self["timeline_text"].setEntries(self["list"], self["timeline_now"], self.time_lines, force)
1264                 self["list"].l.invalidate() # not needed when the zPosition in the skin is correct! ?????