[MerlinEPGCenter] fix for dreamos templated timerlist (that fixes the bug reported...
[enigma2-plugins.git] / merlinepgcenter / src / EpgCenterList.py
1 #
2 #  MerlinEPGCenter E2 Plugin
3 #
4 #  $Id: EpgCenterList.py,v 1.0 2011-02-14 21:53:00 shaderman Exp $
5 #
6 #  Coded by Shaderman (c) 2011
7 #  Support: www.dreambox-tools.info
8 #
9 #  This plugin is licensed under the Creative Commons 
10 #  Attribution-NonCommercial-ShareAlike 3.0 Unported 
11 #  License. To view a copy of this license, visit
12 #  http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative
13 #  Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
14 #
15 #  Alternatively, this plugin may be distributed and executed on hardware which
16 #  is licensed by Dream Multimedia GmbH.
17
18 #  This plugin is NOT free software. It is open source, you are allowed to
19 #  modify it (if you keep the license), but it may not be commercially 
20 #  distributed other than under the conditions noted above.
21 #
22
23
24 # for localized messages
25 from . import _
26
27 # PYTHON IMPORTS
28 from datetime import datetime
29 from time import localtime, strftime, time
30
31 # ENIGMA IMPORTS
32 from Components.config import config
33 from Components.GUIComponent import GUIComponent
34 from Components.TimerList import TimerList
35 from enigma import eEPGCache, eServiceReference, eServiceCenter, eListbox, eListboxPythonMultiContent, gFont, RT_HALIGN_LEFT, RT_HALIGN_CENTER, RT_HALIGN_RIGHT, RT_VALIGN_CENTER, RT_VALIGN_TOP, RT_VALIGN_BOTTOM, getDesktop
36 from math import fabs
37 import NavigationInstance
38 from RecordTimer import RecordTimerEntry
39 from ServiceReference import ServiceReference
40 from skin import parseColor
41 from timer import TimerEntry
42 from Tools.Directories import resolveFilename, SCOPE_CURRENT_PLUGIN
43 from Tools.LoadPixmap import LoadPixmap
44
45 # OWN IMPORTS
46 from ConfigTabs import KEEP_OUTDATED_TIME, STYLE_SIMPLE_BAR, STYLE_PIXMAP_BAR, STYLE_MULTI_PIXMAP, STYLE_PERCENT_TEXT, STYLE_SIMPLE_BAR_LIST_OFF, STYLE_PIXMAP_BAR_LIST_OFF, STYLE_MULTI_PIXMAP_LIST_OFF, STYLE_PERCENT_TEXT_LIST_OFF
47 from HelperFunctions import getFuzzyDay, LIST_TYPE_EPG, LIST_TYPE_UPCOMING, TimerListObject
48 from MerlinEPGCenter import STYLE_SINGLE_LINE, STYLE_SHORT_DESCRIPTION
49
50
51 MODE_FHD = 0
52 MODE_HD = 1
53 MODE_XD = 2
54 MODE_SD = 3
55
56 MULTI_EPG_NOW = 0
57 MULTI_EPG_NEXT = 1
58 SINGLE_EPG = 2
59 MULTI_EPG_PRIMETIME = 3
60 TIMERLIST = 4
61 EPGSEARCH_HISTORY = 5
62 EPGSEARCH_RESULT = 6
63 EPGSEARCH_MANUAL = 7
64 UPCOMING = 8
65
66 TIMER_TYPE_EID_MATCH = 1
67 TIMER_TYPE_COVERS_FULL = 2
68 TIMER_TYPE_COVERS_END = 4
69 TIMER_TYPE_COVERS_BEGIN = 8
70 TIMER_TYPE_EID_REPEATED = 16
71 TIMER_TYPE_INSIDE_EVENT = 32
72 TIMER_TYPE_ADD = 64
73 TIMER_TYPE_ADD_INSIDE_EVENT = 128
74 TIMER_TYPE_ADD_COVERS_FULL = 256
75 TIMER_TYPE_ADD_COVERS_END = 512
76 TIMER_TYPE_ADD_COVERS_BEGIN = 1024
77
78
79 class EpgCenterList(GUIComponent):
80         # some static stuff used by ["list"] and ["upcoming"] widgets
81         infoBarInstance = None
82         eServiceCenterInstance = None
83         bouquetList = []
84         bouquetServices = []
85         currentBouquetIndex = 0
86         bouquetIndexRanges = []
87         allServicesNameDict = {}
88         recordTimer = None
89         lenChannelDigits = 0
90         
91         def __init__(self, blinkTimer, listType, videoMode, piconLoader, bouquetList, currentIndex, piconSize, listStyle, epgList):
92                 self.blinkTimer = blinkTimer
93                 self.listType = listType
94                 self.videoMode = videoMode
95                 self.piconLoader = piconLoader
96                 self.piconSize = piconSize
97                 self.baseHeight = self.piconSize.height()
98                 self.listStyle = listStyle
99                 self.epgList = epgList
100                 
101                 GUIComponent.__init__(self)
102                 
103                 from Screens.InfoBar import InfoBar
104                 EpgCenterList.infoBarInstance = InfoBar.instance
105                 EpgCenterList.eServiceCenterInstance = eServiceCenter.getInstance()
106                 
107                 self.l = eListboxPythonMultiContent()
108                 self.l.setBuildFunc(self.buildEpgEntry)
109                 self.onSelectionChanged = [ ]
110                 
111                 if self.videoMode == MODE_SD or self.videoMode == MODE_XD:
112                         self.overallFontHeight = 36
113                 elif self.videoMode == MODE_HD:
114                         self.overallFontHeight = 44
115                 elif self.videoMode == MODE_FHD:
116                         self.overallFontHeight = 66
117                         
118                 #initialize
119                 self.list = []
120                 self.mode = None
121                 self.similarShown = False
122                 
123                 config.plugins.merlinEpgCenter.listItemHeight.addNotifier(self.changeHeight, initial_call = True)
124                 config.plugins.merlinEpgCenter.adjustFontSize.addNotifier(self.setFontSizes, initial_call = True)
125                 
126                 if listType == LIST_TYPE_EPG:
127                         EpgCenterList.bouquetList = bouquetList
128                         EpgCenterList.currentBouquetIndex = currentIndex
129                         EpgCenterList.updateBouquetServices()
130                         EpgCenterList.recordTimer = NavigationInstance.instance.RecordTimer
131                         
132                 # zap timer pixmaps
133                 self.zap_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/zap.png"))
134                 self.zap_pre_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/zap_pre.png"))
135                 self.zap_post_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/zap_post.png"))
136                 self.zap_event_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/zap_event.png"))
137                 self.zap_repeated_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/zap_repeated.png"))
138                 self.zap_add_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/zap_add.png"))
139                 
140                 # record timer pixmaps
141                 self.timer_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/timer.png"))
142                 self.timer_pre_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/timer_pre.png"))
143                 self.timer_post_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/timer_post.png"))
144                 self.timer_event_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/timer_event.png"))
145                 self.timer_repeated_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/timer_repeated.png"))
146                 self.timer_add_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/timer_add.png"))
147                 
148                 # progress pixmaps
149                 self.progressPixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/Progress.png"))
150                 self.progressPixmap_1 = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/Progress_1.png"))
151                 self.progressPixmap_2 = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/Progress_2.png"))
152                 self.progressPixmap_3 = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/Progress_3.png"))
153                 self.progressPixmap_4 = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/Progress_4.png"))
154                 self.progressPixmapWidth = self.progressPixmap.size().width()
155                 
156                 self.epgcache = eEPGCache.getInstance()
157                 
158                 self.blinkTimer.callbacks.append(self.invalidateList)
159                 
160         def onShow(self):
161                 self.maxWidth = self.l.getItemSize().width()
162                 
163         def setFontSizes(self, configElement = None):
164                 diff = configElement.getValue()
165                 
166                 if self.videoMode == MODE_SD:
167                         self.l.setFont(0, gFont("Regular", 18 + diff))
168                         self.l.setFont(1, gFont("Regular", 16 + diff))
169                         self.l.setFont(2, gFont("Regular", 14 + diff))
170                         self.l.setFont(3, gFont("Regular", 12 + diff))
171                 elif self.videoMode == MODE_XD:
172                         self.l.setFont(0, gFont("Regular", 18 + diff))
173                         self.l.setFont(1, gFont("Regular", 16 + diff))
174                         self.l.setFont(2, gFont("Regular", 14 + diff))
175                         self.l.setFont(3, gFont("Regular", 12 + diff))
176                 elif self.videoMode == MODE_HD:
177                         self.l.setFont(0, gFont("Regular", 22 + diff))
178                         self.l.setFont(1, gFont("Regular", 20 + diff))
179                         self.l.setFont(2, gFont("Regular", 18 + diff))
180                         self.l.setFont(3, gFont("Regular", 16 + diff))
181                 elif self.videoMode == MODE_FHD:
182                         self.l.setFont(0, gFont("Regular", 33 + diff))
183                         self.l.setFont(1, gFont("Regular", 30 + diff))
184                         self.l.setFont(2, gFont("Regular", 27 + diff))
185                         self.l.setFont(3, gFont("Regular", 24 + diff))
186                         
187         def setMaxWidth(self, newSize):
188                 self.maxWidth = newSize.width()
189                 
190         def changeHeight(self, configElement = None):
191                 self.listStyle = config.plugins.merlinEpgCenter.listStyle.value
192                 if self.listStyle == STYLE_SINGLE_LINE:
193                         self.singleLineBorder = 4
194                 else:
195                         self.singleLineBorder = 0
196                         
197                 if self.listStyle == STYLE_SHORT_DESCRIPTION or (self.listStyle == STYLE_SINGLE_LINE and (self.mode == SINGLE_EPG or self.mode == EPGSEARCH_RESULT or self.similarShown)):
198                         if self.overallFontHeight > self.baseHeight:
199                                 self.itemHeight = self.overallFontHeight + int(config.plugins.merlinEpgCenter.listItemHeight.value)
200                         else:
201                                 self.itemHeight = self.baseHeight + int(config.plugins.merlinEpgCenter.listItemHeight.value)
202                 elif self.videoMode == MODE_HD and config.plugins.merlinEpgCenter.listProgressStyle.value == STYLE_PERCENT_TEXT: # HD skin adjustment for text size
203                         self.itemHeight = self.baseHeight + int(config.plugins.merlinEpgCenter.listItemHeight.value) + 4
204                 elif self.videoMode == MODE_FHD and config.plugins.merlinEpgCenter.listProgressStyle.value == STYLE_PERCENT_TEXT: # FullHD skin adjustment for text size
205                         self.itemHeight = self.baseHeight + int(config.plugins.merlinEpgCenter.listItemHeight.value) + 4
206                 else:
207                         self.itemHeight = self.baseHeight + int(config.plugins.merlinEpgCenter.listItemHeight.value)
208                 self.halfItemHeight = self.itemHeight / 2
209                 self.l.setItemHeight(self.itemHeight)
210                 
211         def buildEpgEntry(self, ignoreMe, eventid, sRef, begin, duration, title, short, desc):
212                 columnSpace = config.plugins.merlinEpgCenter.columnSpace.getValue()
213                 progressPixmap = None
214                 offsetLeft = 5
215                 offsetRight = self.maxWidth - 5 - 8 # 8 = timer pixmap width, 5 = border
216                 secondLineColor = 0x00909090 # grey
217                 border = int(config.plugins.merlinEpgCenter.listItemHeight.value) / 2
218                 percent = 0
219                 
220                 if begin != None and duration != None:
221                         timeString = strftime("%H:%M", localtime(begin)) + "-" + strftime("%H:%M", localtime(begin + duration))
222                         now = int(time())
223                         if now > begin:
224                                 percent = (now - begin) * 100 / duration
225                                 
226                         if self.mode == MULTI_EPG_NOW:
227                                 timeValue = (begin + duration - now) / 60 +1
228                         elif self.mode == MULTI_EPG_NEXT or self.mode == UPCOMING:
229                                 timeValue = (now - begin) /  60
230                         elif self.mode == MULTI_EPG_PRIMETIME or self.mode == EPGSEARCH_RESULT:
231                                 if now >= begin:
232                                         timeValue = (begin + duration - now) /  60 + 1
233                                 else:
234                                         timeValue = (now - begin) /  60
235                         elif self.mode == SINGLE_EPG:
236                                 if self.instance.getCurrentIndex() == 0:
237                                         timeValue = (begin + duration - now) /  60 + 1
238                                 else:
239                                         timeValue = (now - begin) /  60
240                                         
241                         if config.plugins.merlinEpgCenter.showBeginRemainTime.value:
242                                 if (KEEP_OUTDATED_TIME == 0 and (begin + duration) > now) or (KEEP_OUTDATED_TIME != 0 and (begin + duration) > now):
243                                         if config.plugins.merlinEpgCenter.showDuration.value:
244                                                 remainBeginString = " I "
245                                         else:
246                                                 remainBeginString = ""
247                                         
248                                         if timeValue >= 0:
249                                                 remainBeginString += "+"
250                                         if fabs(timeValue) >= 120 and fabs(timeValue) < 1440:
251                                                 timeValue /= 60
252                                                 remainBeginString += "%0dh" % timeValue
253                                         elif fabs(timeValue) >= 1440:
254                                                 timeValue = (timeValue / 1440) +1
255                                                 remainBeginString += "%02dd" % timeValue
256                                         else:
257                                                 if timeValue < 0:
258                                                         remainBeginString += "%03d" % timeValue
259                                                 else:
260                                                         remainBeginString += "%02d" % timeValue
261                                 else:
262                                         if config.plugins.merlinEpgCenter.showDuration.value:
263                                                 remainBeginString = " I <->"
264                                         else:
265                                                 remainBeginString = "<->"
266                         else:
267                                 remainBeginString = ""
268                                 
269                         if config.plugins.merlinEpgCenter.showDuration.value:
270                                 duraString = "%d" % (duration / 60)
271                                 
272                         if self.mode == MULTI_EPG_NOW:
273                                 if config.plugins.merlinEpgCenter.listProgressStyle.value == STYLE_MULTI_PIXMAP:
274                                         part = int(round(percent / 25)) + 1
275                                         progressPixmap = eval('self.progressPixmap_' + str(part))
276                                 elif config.plugins.merlinEpgCenter.listProgressStyle.value == STYLE_PIXMAP_BAR:
277                                         progressPixmap = self.progressPixmap
278                 else:
279                         timeString = ""
280                         duraString = ""
281                         remainBeginString = ""
282                         
283                 if remainBeginString.endswith('>'): # KEEP_OUTDATED_TIME
284                         outdated = True
285                         try:
286                                 progColor = parseColor("eventNotAvailable").argb()
287                         except:
288                                 progColor = 0x777777
289                 elif config.plugins.merlinEpgCenter.showBeginRemainTime.value and config.plugins.merlinEpgCenter.showColoredEpgTimes.value:
290                         outdated = False
291                         if remainBeginString.endswith('h'): # begins in... hours
292                                 progColor = 0x00ef7f1a # brown
293                         elif remainBeginString.endswith('d'): # begins in... days
294                                 progColor = 0x00e31e24 # red
295                         elif remainBeginString.startswith(' I +') or remainBeginString.startswith('+'): # already running
296                                 progColor = 0x0074de0a # green
297                         elif remainBeginString.startswith(' I -') or remainBeginString.startswith('-'): # begins in... minutes
298                                 progColor = 0x00ffed00 # yellow
299                         else: # undefined, shouldn't happen
300                                 progColor = 0x00ffffff # white
301                 else:
302                         outdated = False
303                         progColor = 0x00ffed00 # yellow
304                         
305                 if outdated:
306                         textColor = progColor
307                 elif self.epgList != None:
308                         textColor = self.epgList.getColorEventAvailable(sRef, begin, duration)
309                 else:
310                         textColor = None
311                         
312                 res = [ None ]
313                 
314                 if config.plugins.merlinEpgCenter.showListNumbers.value:
315                         if ((self.mode == SINGLE_EPG and not self.similarShown) or self.mode == UPCOMING) and self.instance.getCurrentIndex() != 0:
316                                 chNumber = ""
317                         else:
318                                 # check if the service is found in our bouquets (or don't show the channel number if not found)
319                                 if self.mode == EPGSEARCH_RESULT:
320                                         if sRef in EpgCenterList.allServicesNameDict:
321                                                 i = 0
322                                                 while i < len(EpgCenterList.bouquetServices):
323                                                         if sRef in EpgCenterList.bouquetServices[i]:
324                                                                 chOffset = EpgCenterList.bouquetIndexRanges[i]
325                                                                 chNumber = str(EpgCenterList.bouquetServices[i].index(sRef) + chOffset)
326                                                                 break
327                                                         i += 1
328                                         else:
329                                                 chNumber = ""
330                                 else:
331                                         if sRef in EpgCenterList.bouquetServices[EpgCenterList.currentBouquetIndex]:
332                                                 chOffset = EpgCenterList.bouquetIndexRanges[EpgCenterList.currentBouquetIndex]
333                                                 chNumber = str(EpgCenterList.bouquetServices[EpgCenterList.currentBouquetIndex].index(sRef) + chOffset)
334                                         else:
335                                                 chNumber = ""
336                                                 
337                         if EpgCenterList.lenChannelDigits < 3:
338                                 width = self.maxWidth * 3 / 100
339                         else:
340                                 width = self.maxWidth * 4 / 100
341                         res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, 0, width, self.itemHeight, 1, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, chNumber))
342                         offsetLeft = offsetLeft + width + columnSpace
343                         
344                 if config.plugins.merlinEpgCenter.showPicons.value:
345                         if ((self.mode == SINGLE_EPG and not self.similarShown) or self.mode == UPCOMING) and self.instance.getCurrentIndex() != 0:
346                                 picon = None
347                         else:
348                                 picon = self.piconLoader.getPicon(sRef)
349                                 
350                         width = self.piconSize.width()
351                         if picon:
352                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetLeft, (self.itemHeight - self.baseHeight) / 2, width, self.itemHeight, picon))
353                         offsetLeft = offsetLeft + width + columnSpace
354                         
355                 if config.plugins.merlinEpgCenter.showServiceName.value:
356                         extraWidth = int(config.plugins.merlinEpgCenter.serviceNameWidth.value)
357                         if self.videoMode == MODE_SD:
358                                 width = self.maxWidth * (12 + extraWidth) / 100
359                         elif self.videoMode == MODE_XD:
360                                 width = self.maxWidth * (14 + extraWidth) / 100
361                         elif self.videoMode == MODE_HD:
362                                 width = self.maxWidth * (16 + extraWidth) / 100
363                         elif self.videoMode == MODE_FHD:
364                                 width = self.maxWidth * (24 + extraWidth) / 100
365                                 
366                         if not (((self.mode == SINGLE_EPG and not self.similarShown) or self.mode == UPCOMING) and self.instance.getCurrentIndex() != 0):
367                                 if sRef in EpgCenterList.allServicesNameDict:
368                                         serviceName = EpgCenterList.allServicesNameDict[sRef]
369                                 else:
370                                         serviceName = ServiceReference(sRef).getServiceName()
371                                         
372                                 res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, 0, width, self.itemHeight, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, serviceName))
373                         offsetLeft = offsetLeft + width + columnSpace
374                         
375                 if self.mode == MULTI_EPG_NOW and not self.similarShown:
376                         extraWidth = int(config.plugins.merlinEpgCenter.adjustFontSize.value)
377                         if extraWidth < 0:
378                                 extraWidth = 0
379                         if self.videoMode == MODE_SD:
380                                 width = self.maxWidth * (18 + extraWidth) / 100
381                         else:
382                                 width = self.maxWidth * (14 + extraWidth) / 100
383                         progressHeight = 6
384                         
385                         if config.plugins.merlinEpgCenter.listProgressStyle.value < STYLE_SIMPLE_BAR_LIST_OFF: # show progress in lists
386                                 res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, border, width, self.halfItemHeight - border + (self.singleLineBorder * 2), 1, RT_HALIGN_CENTER|RT_VALIGN_TOP, timeString))
387                         else: # don't show progress in lists
388                                 res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, 0, width, self.itemHeight, 1, RT_HALIGN_CENTER|RT_VALIGN_CENTER, timeString))
389                                 
390                         if config.plugins.merlinEpgCenter.listProgressStyle.value == STYLE_MULTI_PIXMAP and progressPixmap is not None:
391                                 if width > self.progressPixmapWidth:
392                                         progressOffset = int((width - self.progressPixmapWidth) / 2)
393                                 else:
394                                         progressOffset = 0
395                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetLeft + progressOffset, self.halfItemHeight + (self.halfItemHeight - progressHeight) / 2 + self.singleLineBorder, width, progressHeight, progressPixmap))
396                         elif config.plugins.merlinEpgCenter.listProgressStyle.value == STYLE_SIMPLE_BAR:
397                                 res.append((eListboxPythonMultiContent.TYPE_PROGRESS, offsetLeft, self.halfItemHeight + (self.halfItemHeight - progressHeight) / 2 + self.singleLineBorder, width, progressHeight, percent, 1, secondLineColor))
398                         elif config.plugins.merlinEpgCenter.listProgressStyle.value == STYLE_PIXMAP_BAR and progressPixmap is not None:
399                                 if width > self.progressPixmapWidth:
400                                         progressOffset = int((width - self.progressPixmapWidth) / 2)
401                                 else:
402                                         progressOffset = 0
403                                 res.append((eListboxPythonMultiContent.TYPE_PROGRESS_PIXMAP, offsetLeft + progressOffset, self.halfItemHeight + (self.halfItemHeight - progressHeight) / 2 + self.singleLineBorder, width, progressHeight, percent, progressPixmap, 0))
404                         elif config.plugins.merlinEpgCenter.listProgressStyle.value == STYLE_PERCENT_TEXT:
405                                 if self.videoMode == MODE_SD: # we need a bigger font for SD skins
406                                         font = 2
407                                 else:
408                                         font = 3
409                                 res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, self.halfItemHeight, width, self.halfItemHeight - border, font, RT_HALIGN_CENTER|RT_VALIGN_TOP, str(percent) + "%", secondLineColor))
410                                 
411                         offsetLeft = offsetLeft + width + columnSpace
412                 else:
413                         extraWidth = int(config.plugins.merlinEpgCenter.adjustFontSize.value)
414                         if extraWidth < 0:
415                                 extraWidth = 0
416                         if self.videoMode == MODE_SD:
417                                 width = self.maxWidth * (18 + extraWidth) / 100
418                         else:
419                                 width = self.maxWidth * (14 + extraWidth) / 100
420                         if self.mode == SINGLE_EPG or self.mode == EPGSEARCH_RESULT or self.similarShown:
421                                 fd = getFuzzyDay(begin)
422                                 res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, border, width, self.halfItemHeight - border, 1, RT_HALIGN_CENTER|RT_VALIGN_TOP, timeString, textColor))
423                                 res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, self.halfItemHeight, width, self.halfItemHeight - border, 2, RT_HALIGN_CENTER|RT_VALIGN_TOP, fd, secondLineColor))
424                         else:
425                                 res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, 0, width, self.itemHeight, 1, RT_HALIGN_CENTER|RT_VALIGN_CENTER, timeString, textColor))
426                         offsetLeft = offsetLeft + width + columnSpace
427                         
428                 if begin != None and duration != None:
429                         (timerPixmaps, zapPixmaps, isRunning) = self.getTimerPixmapsForEntry(sRef, eventid, begin, duration)
430                 else:
431                         timerPixmaps = 0
432                         zapPixmaps = 0
433                         isRunning = 0
434                         
435                 idx = self.instance.getCurrentIndex()
436                 self.blinkTimer.updateEntry(self.listType, idx, isRunning)
437                 
438                 if zapPixmaps:
439                         if (zapPixmaps & TIMER_TYPE_EID_MATCH) or (zapPixmaps & TIMER_TYPE_COVERS_FULL) or (zapPixmaps & TIMER_TYPE_EID_REPEATED) or (zapPixmaps & TIMER_TYPE_ADD_COVERS_FULL):
440                                 posY = 2
441                                 height = self.itemHeight - 4
442                                 if (zapPixmaps & TIMER_TYPE_EID_MATCH):
443                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.zap_event_pixmap))
444                                 elif (zapPixmaps & TIMER_TYPE_COVERS_FULL):
445                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.zap_pixmap))
446                                 elif (zapPixmaps & TIMER_TYPE_EID_REPEATED):
447                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.zap_repeated_pixmap))
448                                 elif (zapPixmaps & TIMER_TYPE_ADD_COVERS_FULL):
449                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.zap_add_pixmap))
450                         elif (zapPixmaps & TIMER_TYPE_INSIDE_EVENT) or (zapPixmaps & TIMER_TYPE_ADD_INSIDE_EVENT):
451                                 posY = self.itemHeight / 2 - 6
452                                 height = 12
453                                 if (zapPixmaps & TIMER_TYPE_INSIDE_EVENT):
454                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.zap_pixmap))
455                                 elif (zapPixmaps & TIMER_TYPE_ADD_INSIDE_EVENT):
456                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.zap_add_pixmap))
457                         else:
458                                 if zapPixmaps & TIMER_TYPE_COVERS_END:
459                                         posY = self.itemHeight / 2 + 2
460                                         height = self.itemHeight - posY - 2
461                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.zap_pre_pixmap))
462                                 elif zapPixmaps & TIMER_TYPE_ADD_COVERS_END:
463                                         posY = self.itemHeight / 2 + 2
464                                         height = self.itemHeight - posY - 2
465                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.zap_add_pixmap))
466                                 if zapPixmaps & TIMER_TYPE_COVERS_BEGIN:
467                                         posY = 2
468                                         height = self.itemHeight / 2 - 2
469                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.zap_post_pixmap))
470                                 elif zapPixmaps & TIMER_TYPE_ADD_COVERS_BEGIN:
471                                         posY = 2
472                                         height = self.itemHeight / 2 - 2
473                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.zap_add_pixmap))
474                                 if zapPixmaps & TIMER_TYPE_ADD:
475                                         posY = 2
476                                         height = self.itemHeight - 4
477                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.zap_add_pixmap))
478                                         
479                 offsetRight -= 10
480                 
481                 if timerPixmaps:
482                         if (timerPixmaps & TIMER_TYPE_EID_MATCH) or (timerPixmaps & TIMER_TYPE_COVERS_FULL) or (timerPixmaps & TIMER_TYPE_EID_REPEATED) or (timerPixmaps & TIMER_TYPE_ADD_COVERS_FULL):
483                                 posY = 2
484                                 height = self.itemHeight - 4
485                                 if (timerPixmaps & TIMER_TYPE_EID_MATCH):
486                                         if (isRunning & TIMER_TYPE_EID_MATCH) and not self.blinkTimer.getBlinkState():
487                                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, None))
488                                         else:
489                                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.timer_event_pixmap))
490                                 elif (timerPixmaps & TIMER_TYPE_COVERS_FULL):
491                                         if (isRunning & TIMER_TYPE_COVERS_FULL) and not self.blinkTimer.getBlinkState():
492                                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, None))
493                                         else:
494                                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.timer_pixmap))
495                                 elif (timerPixmaps & TIMER_TYPE_EID_REPEATED):
496                                         if (isRunning & TIMER_TYPE_EID_REPEATED) and not self.blinkTimer.getBlinkState():
497                                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, None))
498                                         else:
499                                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.timer_repeated_pixmap))
500                                 elif (timerPixmaps & TIMER_TYPE_ADD_COVERS_FULL):
501                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.timer_add_pixmap))
502                         elif (timerPixmaps & TIMER_TYPE_INSIDE_EVENT) or (timerPixmaps & TIMER_TYPE_ADD_INSIDE_EVENT):
503                                 posY = self.itemHeight / 2 - 6
504                                 height = 12
505                                 if (timerPixmaps & TIMER_TYPE_INSIDE_EVENT):
506                                         if (isRunning & TIMER_TYPE_INSIDE_EVENT) and not self.blinkTimer.getBlinkState():
507                                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, None))
508                                         else:
509                                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.timer_pixmap))
510                                 elif (timerPixmaps & TIMER_TYPE_ADD_INSIDE_EVENT):
511                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.timer_add_pixmap))
512                         else:
513                                 if timerPixmaps & TIMER_TYPE_COVERS_END:
514                                         posY = self.itemHeight / 2 + 2
515                                         height = self.itemHeight - posY - 2
516                                         if (isRunning & TIMER_TYPE_COVERS_END) and not self.blinkTimer.getBlinkState():
517                                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, None))
518                                         else:
519                                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.timer_pre_pixmap))
520                                 elif timerPixmaps & TIMER_TYPE_ADD_COVERS_END:
521                                         posY = self.itemHeight / 2 + 2
522                                         height = self.itemHeight - posY - 2
523                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.timer_add_pixmap))
524                                 if timerPixmaps & TIMER_TYPE_COVERS_BEGIN:
525                                         posY = 2
526                                         height = self.itemHeight / 2 - 2
527                                         if (isRunning & TIMER_TYPE_COVERS_BEGIN) and not self.blinkTimer.getBlinkState():
528                                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, None))
529                                         else:
530                                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.timer_post_pixmap))
531                                 elif timerPixmaps & TIMER_TYPE_ADD_COVERS_BEGIN:
532                                         posY = 2
533                                         height = self.itemHeight / 2 - 2
534                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.timer_add_pixmap))
535                                 if timerPixmaps & TIMER_TYPE_ADD:
536                                         posY = 2
537                                         height = self.itemHeight - 4
538                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetRight, posY, 8, height, self.timer_add_pixmap))
539                                         
540                 if config.plugins.merlinEpgCenter.showBeginRemainTime.value and config.plugins.merlinEpgCenter.showDuration.value:
541                         width = self.maxWidth * 8 / 100
542                         offsetRight = offsetRight - width
543                         res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetRight, 0, width, self.itemHeight, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, remainBeginString, progColor))
544                 elif config.plugins.merlinEpgCenter.showBeginRemainTime.value:
545                         width = self.maxWidth * 6 / 100
546                         offsetRight = offsetRight - width
547                         res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetRight, 0, width, self.itemHeight, 1, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, remainBeginString, progColor))
548                         
549                 if config.plugins.merlinEpgCenter.showDuration.value:
550                         width = self.maxWidth * 6 / 100
551                         offsetRight = offsetRight - width
552                 elif not config.plugins.merlinEpgCenter.showDuration.value and not config.plugins.merlinEpgCenter.showBeginRemainTime.value:
553                         width = self.maxWidth * 1 / 100
554                         offsetRight = offsetRight - width
555                         
556                 titleWidth = offsetRight - offsetLeft - columnSpace
557                 if self.listStyle == STYLE_SINGLE_LINE:
558                         res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, 0, titleWidth, self.itemHeight, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, title, config.plugins.merlinEpgCenter.titleColor.value, config.plugins.merlinEpgCenter.titleColorSelected.value))
559                 elif self.listStyle == STYLE_SHORT_DESCRIPTION:
560                         if short and title != short:
561                                 res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, border, titleWidth, self.halfItemHeight - border, 1, RT_HALIGN_LEFT|RT_VALIGN_TOP, title, textColor))
562                                 res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, self.halfItemHeight, titleWidth, self.halfItemHeight - border, 2, RT_HALIGN_LEFT|RT_VALIGN_TOP, short, secondLineColor))
563                         else:
564                                 res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, 0, titleWidth, self.itemHeight, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, title, textColor))
565                         
566                 if config.plugins.merlinEpgCenter.showDuration.value:
567                         res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetRight, 0, width, self.itemHeight, 1, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, duraString, textColor))
568                 
569                 return res
570         
571         GUI_WIDGET = eListbox
572         
573         def selectionChanged(self):
574                 for x in self.onSelectionChanged:
575                         x()
576                         
577         def postWidgetCreate(self, instance):
578                 instance.setWrapAround(True)
579                 instance.setContent(self.l)
580                 self.selectionChanged_conn = instance.selectionChanged.connect(self.selectionChanged)
581
582         def preWidgetRemove(self, instance):
583                 instance.setContent(None)
584                 self.selectionChanged_conn = None
585                 config.plugins.merlinEpgCenter.listItemHeight.removeNotifier(self.changeHeight)
586                 config.plugins.merlinEpgCenter.adjustFontSize.removeNotifier(self.setFontSizes)
587                 self.blinkTimer.callbacks.remove(self.invalidateList)
588                 
589         def moveToIndex(self, index):
590                 self.instance.moveSelectionTo(index)
591                 
592         def moveUp(self):
593                 self.instance.moveSelection(self.instance.moveUp)
594                 
595         def moveDown(self):
596                 self.instance.moveSelection(self.instance.moveDown)
597                 
598         def pageUp(self):
599                 if self.instance is not None:
600                         self.instance.moveSelection(self.instance.pageUp)
601                         
602         def pageDown(self):
603                 if self.instance is not None:
604                         self.instance.moveSelection(self.instance.pageDown)
605                         
606         def getCurrent(self):
607                 return self.l.getCurrentSelection()
608
609         def invalidate(self, entry):
610                 # when the entry to invalidate does not exist, just ignore the request.
611                 # this eases up conditional setup screens a lot.
612                 if entry in self.list:
613                         self.l.invalidateEntry(self.list.index(entry))
614                         
615         def invalidateList(self):
616                 self.l.invalidate()
617                 
618         def setList(self, l):
619                 self.list = l
620                 self.l.setList(self.list)
621         
622         def queryEPG(self, servicelist):
623                 if self.epgcache is not None:
624                         return self.epgcache.lookupEvent(servicelist)
625                 return [ ]
626
627         def fillMultiEPG(self, bouquet, bouquetIndex, mode, stime=-1):
628                 EpgCenterList.currentBouquetIndex = bouquetIndex
629                 
630                 # 1. if oldmode is MULTI_EPG_NOW and new mode is MULTI_EPG_NEXT --> use old list for querying EPG (speed up! :-) )
631                 # 2. otherwise build servicelist from bouquet, and
632                 #       a) if MULTI_EPG_NOW/MULTI_EPG_PRIMETIME --> query epg
633                 #       b) if MULTI_EPG_NEXT --> build query-list with servicelist for epg      
634                 oldmode = self.mode
635                 self.mode = mode
636                 self.similarShown = False
637                 
638                 if ((mode == MULTI_EPG_NOW or mode == MULTI_EPG_PRIMETIME) or (oldmode != MULTI_EPG_NOW and mode == MULTI_EPG_NEXT)):
639                         servicelist = EpgCenterList.getServiceList(bouquet, stime)
640                         
641                 returnTuples = '0IRBDTSEX'
642                 if mode == MULTI_EPG_NOW or mode == MULTI_EPG_PRIMETIME:
643                         servicelist.insert(0, returnTuples)
644                 else:
645                         if oldmode != MULTI_EPG_NOW:
646                                 servicelist.insert(0, returnTuples)
647                                 tmpList = self.queryEPG(servicelist)
648                         else:
649                                 tmpList = self.list
650
651                         servicelist = [ x[3] and (x[2], 1, x[3]) or (x[2], 1, 0) for x in tmpList ] # build servicelist with "event after given start_time" and set the start time
652                         servicelist.insert(0, returnTuples)
653                         
654                 if self.listStyle == STYLE_SINGLE_LINE:
655                         self.changeHeight()
656                 self.list = self.queryEPG(servicelist)
657                 self.l.setList(self.list)
658                 
659         def fillSingleEPG(self, bouquet, bouquetIndex, mode, sRef, showOutdated):
660                 self.mode = mode
661                 EpgCenterList.currentBouquetIndex = bouquetIndex
662                 EpgCenterList.getServiceList(bouquet)
663                 self.similarShown = False
664                 
665                 if sRef:
666                         if showOutdated:
667                                 now = time()
668                                 queryString = [ '0IRBDTSE', (sRef, 0, now - KEEP_OUTDATED_TIME * 60, KEEP_OUTDATED_TIME) ]
669                         else:
670                                 queryString = [ '0IRBDTSE', (sRef, 0, -1, -1) ]
671                         self.list = self.queryEPG(queryString)
672                         
673                 if self.listStyle == STYLE_SINGLE_LINE:
674                         self.changeHeight()
675                 if showOutdated:
676                         self.list.sort(key = lambda x: x[3], reverse = True) # sort by time
677                 self.l.setList(self.list)
678                 
679         def fillSimilar(self, sRef, eventId):
680                 if eventId is None:
681                         return
682                         
683                 self.similarShown = True
684                 self.list = self.epgcache.search(('0IRBDTSE', 1024, eEPGCache.SIMILAR_BROADCASTINGS_SEARCH, sRef, eventId))
685                 if self.list is not None:
686                         if config.plugins.merlinEpgCenter.limitSearchToBouquetServices.value:
687                                 for item in self.list[:]:
688                                         if not item[2] in EpgCenterList.allServicesNameDict:
689                                                 self.list.remove(item)
690                                                 
691                         if self.listStyle == STYLE_SINGLE_LINE:
692                                 self.changeHeight()
693                                 
694                         self.list.sort(key = lambda x: x[3]) # sort by time
695                         self.l.setList(self.list)
696                         
697         def fillEpgSearch(self, searchString, mode):
698                 self.mode = mode
699                 self.similarShown = False
700                 
701                 if searchString == None:
702                         self.list = []
703                 else:
704                         searchString = searchString.decode('utf-8').encode("iso-8859-1","replace")
705                         self.list = self.epgcache.search(('0IRBDTSE', 1024, eEPGCache.PARTIAL_TITLE_SEARCH, searchString, eEPGCache.NO_CASE_CHECK)) or []
706                         if config.plugins.merlinEpgCenter.limitSearchToBouquetServices.value:
707                                 for item in self.list[:]:
708                                         if not item[2] in EpgCenterList.allServicesNameDict:
709                                                 self.list.remove(item)
710                         self.list.sort(key = lambda x: x[3]) # sort by time
711                         
712                 if self.listStyle == STYLE_SINGLE_LINE:
713                         self.changeHeight()
714                 self.l.setList(self.list)
715                 
716         @staticmethod
717         def getServiceList(bouquet, stime=-1, sRefOnly = False):
718                 services = [ ]
719                 servicelist = eServiceCenter.getInstance().list(bouquet)
720                 if not servicelist is None:
721                         while True:
722                                 service = servicelist.getNext()
723                                 if not service.valid(): # check if end of list
724                                         break
725                                 if service.flags & (eServiceReference.isDirectory | eServiceReference.isMarker): # ignore non playable services
726                                         continue
727                                 # alternative service?
728                                 if service.flags & (eServiceReference.isGroup):
729                                         altRoot = eServiceReference(service.toCompareString())
730                                         altList = EpgCenterList.eServiceCenterInstance.list(altRoot)
731                                         if altList:
732                                                 while True:
733                                                         nextService = altList.getNext()
734                                                         if not nextService.valid():
735                                                                 break
736                                                         service = nextService
737                                                         break
738                                                         
739                                 if sRefOnly:
740                                         services.append(service.toCompareString())
741                                 else:
742                                         services.append((service.toCompareString(), 0, stime))
743                 return services
744                 
745         # get a list of all services in all bouquets
746         @staticmethod
747         def getAllServices():
748                 allServices = {}
749                 index = 1
750                 EpgCenterList.lenChannelDigits = 0
751                 totalServices = 0 # the number of services in all bouquets
752                 for bouquetEntry in EpgCenterList.bouquetList:
753                         servicelist = eServiceCenter.getInstance().list(bouquetEntry[1])
754                         if not servicelist is None:
755                                 numServices = 0
756                                 while True:
757                                         service = servicelist.getNext()
758                                         if not service.valid(): # check if end of list
759                                                 break
760                                         if service.flags & (eServiceReference.isDirectory | eServiceReference.isMarker): # ignore non playable services
761                                                 continue
762                                         info = EpgCenterList.eServiceCenterInstance.info(service)
763                                         serviceName = info.getName(service) or ServiceReference(service).getServiceName() or ""
764                                         allServices[service.toCompareString()] = serviceName
765                                         numServices += 1
766                                 indexEntry = index
767                                 index += numServices
768                                 totalServices += numServices
769                                 EpgCenterList.bouquetIndexRanges.append(indexEntry)
770                 EpgCenterList.lenChannelDigits = len(str(totalServices))
771                 return allServices
772                                 
773         @staticmethod
774         def updateBouquetServices():
775                 EpgCenterList.bouquetIndexRanges = []
776                 EpgCenterList.allServicesNameDict = EpgCenterList.getAllServices()
777                 EpgCenterList.bouquetServices = []
778
779                 for bouquet in EpgCenterList.bouquetList:
780                         EpgCenterList.bouquetServices.append(EpgCenterList.getServiceList(bouquet[1], sRefOnly = True))
781                         
782         def selectionEnabled(self, enabled):
783                 if self.instance is not None:
784                         self.instance.setSelectionEnable(enabled)
785                         
786         def getTimerPixmapsForEntry(self, sRef, eventId, begin, duration):
787                 timerPixmaps = 0
788                 zapPixmaps = 0
789                 isRunning = 0
790                 
791                 for timer in self.recordTimer.timer_list:
792                         if timer.service_ref.ref.toString() == sRef:
793                                 end = begin + duration
794                                 if timer.begin > begin and timer.end < end: # the timer is inside the events bounds
795                                         if timer.justplay:
796                                                 zapPixmaps |= TIMER_TYPE_INSIDE_EVENT
797                                         else:
798                                                 timerPixmaps |= TIMER_TYPE_INSIDE_EVENT
799                                                 if timer.isRunning():
800                                                         isRunning |= TIMER_TYPE_INSIDE_EVENT
801                                 elif end >= timer.begin and begin <= timer.end: # this event touches the timer
802                                         if eventId == timer.eit: # exact event match
803                                                 if timer.repeated:
804                                                         if timer.justplay:
805                                                                 zapPixmaps |= TIMER_TYPE_EID_REPEATED
806                                                         else:
807                                                                 timerPixmaps |= TIMER_TYPE_EID_REPEATED
808                                                                 if timer.isRunning():
809                                                                         isRunning |= TIMER_TYPE_EID_REPEATED
810                                                 else:
811                                                         if timer.justplay:
812                                                                 zapPixmaps |= TIMER_TYPE_EID_MATCH
813                                                         else:
814                                                                 timerPixmaps |= TIMER_TYPE_EID_MATCH
815                                                                 if timer.isRunning():
816                                                                         isRunning |= TIMER_TYPE_EID_MATCH
817                                         elif begin < timer.begin and end > timer.begin: # this event overlaps the end of the timer
818                                                 if timer.justplay:
819                                                         zapPixmaps |= TIMER_TYPE_COVERS_END
820                                                 else:
821                                                         timerPixmaps |= TIMER_TYPE_COVERS_END
822                                                         if timer.isRunning():
823                                                                 isRunning |= TIMER_TYPE_COVERS_END
824                                         elif end > timer.end and begin < timer.end: # this event overlaps the begin of the timer
825                                                 if timer.justplay:
826                                                         zapPixmaps |= TIMER_TYPE_COVERS_BEGIN
827                                                 else:
828                                                         timerPixmaps |= TIMER_TYPE_COVERS_BEGIN
829                                                         if timer.isRunning():
830                                                                 isRunning |= TIMER_TYPE_COVERS_BEGIN
831                                         elif end > timer.begin and begin < timer.end: # this event fully overlaps the timer but itsn't nor the timer event
832                                                 if timer.justplay:
833                                                         zapPixmaps |= TIMER_TYPE_COVERS_FULL
834                                                 else:
835                                                         timerPixmaps |= TIMER_TYPE_COVERS_FULL
836                                                         if timer.isRunning():
837                                                                 isRunning |= TIMER_TYPE_COVERS_FULL
838                                 elif timerPixmaps == 0 and zapPixmaps == 0 and self.recordTimer.isInTimer(eventId, begin, duration, sRef): # timer repetition
839                                         # TODO do we need to care about local times?
840                                         
841                                         timerBegin = datetime.fromtimestamp(timer.begin).time()
842                                         timerEnd = datetime.fromtimestamp(timer.end).time()
843                                         netTimerBegin = datetime.fromtimestamp(int(timer.begin) + 60 * config.recording.margin_before.value).time()
844                                         netTimerEnd = datetime.fromtimestamp(int(timer.end) - 60 * config.recording.margin_after.value).time()
845                                         eventBegin = datetime.fromtimestamp(begin).time()
846                                         eventEnd = datetime.fromtimestamp(end).time()
847                                         
848                                         if netTimerBegin == eventBegin and netTimerEnd == eventEnd: # the main timer entry
849                                                 if timer.justplay:
850                                                         zapPixmaps |= TIMER_TYPE_ADD
851                                                 else:
852                                                         timerPixmaps |= TIMER_TYPE_ADD
853                                         elif netTimerBegin >= eventBegin and netTimerEnd <= eventEnd: # the timer is inside the events bounds
854                                                 if timer.justplay:
855                                                         zapPixmaps |= TIMER_TYPE_ADD_INSIDE_EVENT
856                                                 else:
857                                                         timerPixmaps |= TIMER_TYPE_ADD_INSIDE_EVENT
858                                         elif eventBegin < timerBegin and eventEnd > timerBegin: # this event overlaps the end of the timer
859                                                 if timer.justplay:
860                                                         zapPixmaps |= TIMER_TYPE_ADD_COVERS_END
861                                                 else:
862                                                         timerPixmaps |= TIMER_TYPE_ADD_COVERS_END
863                                         elif eventEnd > timerEnd and eventBegin < timerEnd: # this event overlaps the begin of the timer
864                                                 if timer.justplay:
865                                                         zapPixmaps |= TIMER_TYPE_ADD_COVERS_BEGIN
866                                                 else:
867                                                         timerPixmaps |= TIMER_TYPE_ADD_COVERS_BEGIN
868                                         elif eventEnd > timerBegin and eventBegin < timerEnd: # this event fully overlaps the timer but itsn't nor the timer event
869                                                 if timer.justplay:
870                                                         zapPixmaps |= TIMER_TYPE_ADD_COVERS_FULL
871                                                 else:
872                                                         timerPixmaps |= TIMER_TYPE_ADD_COVERS_FULL
873                                                         
874                 return timerPixmaps, zapPixmaps, isRunning
875                 
876 class EpgCenterTimerlist(TimerList):
877         def __init__(self, list, videoMode, piconLoader, piconSize, listStyle):
878                 self.videoMode = videoMode
879                 self.piconLoader = piconLoader
880                 self.piconSize = piconSize
881                 self.baseHeight = self.piconSize.height()
882                 self.listStyle = listStyle
883                 
884                 GUIComponent.__init__(self)
885                 
886                 self.l = eListboxPythonMultiContent()
887                 self.l.setBuildFunc(self.buildTimerEntry)
888                 self.onSelectionChanged = [ ]
889                 
890                 if self.videoMode == MODE_SD or self.videoMode == MODE_XD:
891                         self.overallFontHeight = 36
892                 elif self.videoMode == MODE_HD:
893                         self.overallFontHeight = 44
894                 elif self.videoMode == MODE_FHD:
895                         self.overallFontHeight = 66
896                         
897                 self.l.setList(list)
898                 config.plugins.merlinEpgCenter.listItemHeight.addNotifier(self.changeHeight, initial_call = True)
899                 config.plugins.merlinEpgCenter.adjustFontSize.addNotifier(self.setFontSizes, initial_call = True)
900                 
901                 self.autoTimerPixmap = LoadPixmap(cached=False, path=resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/MerlinEPGCenter/images/AutoTimerSmall.png"))
902                 
903         def applySkin(self, desktop, parent):
904                 GUIComponent.applySkin(self, desktop, parent)
905
906         def onShow(self):
907                 self.maxWidth = self.l.getItemSize().width()
908                 
909         def setFontSizes(self, configElement = None):
910                 diff = configElement.getValue()
911                 
912                 if self.videoMode == MODE_SD:
913                         self.l.setFont(0, gFont("Regular", 18 + diff))
914                         self.l.setFont(1, gFont("Regular", 16 + diff))
915                         self.l.setFont(2, gFont("Regular", 14 + diff))
916                         self.l.setFont(3, gFont("Regular", 12 + diff))
917                 elif self.videoMode == MODE_XD:
918                         self.l.setFont(0, gFont("Regular", 18 + diff))
919                         self.l.setFont(1, gFont("Regular", 16 + diff))
920                         self.l.setFont(2, gFont("Regular", 14 + diff))
921                         self.l.setFont(3, gFont("Regular", 12 + diff))
922                 elif self.videoMode == MODE_HD:
923                         self.l.setFont(0, gFont("Regular", 22 + diff))
924                         self.l.setFont(1, gFont("Regular", 20 + diff))
925                         self.l.setFont(2, gFont("Regular", 18 + diff))
926                         self.l.setFont(3, gFont("Regular", 16 + diff))
927                 elif self.videoMode == MODE_FHD:
928                         self.l.setFont(0, gFont("Regular", 33 + diff))
929                         self.l.setFont(1, gFont("Regular", 30 + diff))
930                         self.l.setFont(2, gFont("Regular", 27 + diff))
931                         self.l.setFont(3, gFont("Regular", 24 + diff))
932                         
933         def setMaxWidth(self, newSize):
934                 self.maxWidth = newSize.width()
935                 
936         def changeHeight(self, configElement = None):
937                 if self.overallFontHeight > self.baseHeight:
938                         self.itemHeight = self.overallFontHeight + int(config.plugins.merlinEpgCenter.listItemHeight.value)
939                 else:
940                         self.itemHeight = self.baseHeight + int(config.plugins.merlinEpgCenter.listItemHeight.value)
941                 self.halfItemHeight = self.itemHeight / 2
942                 self.l.setItemHeight(self.itemHeight)
943                 
944         def selectionChanged(self):
945                 for x in self.onSelectionChanged:
946                         x()
947                         
948         def postWidgetCreate(self, instance):
949                 instance.setWrapAround(True)
950                 instance.setContent(self.l)
951                 self.selectionChanged_conn = instance.selectionChanged.connect(self.selectionChanged)
952
953         def preWidgetRemove(self, instance):
954                 instance.setContent(None)
955                 self.selectionChanged_conn = None
956                 config.plugins.merlinEpgCenter.listItemHeight.removeNotifier(self.changeHeight)
957                 config.plugins.merlinEpgCenter.adjustFontSize.removeNotifier(self.setFontSizes)
958                 
959         def buildTimerEntry(self, timer, processed):
960                 columnSpace = config.plugins.merlinEpgCenter.columnSpace.getValue()
961                 width = self.l.getItemSize().width()
962                 offsetLeft = 5 # 5 = left border
963                 offsetRight = self.maxWidth - 5 # 5 = right border
964                 secondLineColor = 0x00909090 # grey
965                 border = int(config.plugins.merlinEpgCenter.listItemHeight.value) / 2
966                 timeString = strftime("%H:%M", localtime(timer.begin)) + "-" + strftime("%H:%M", localtime(timer.end))
967
968                 if not processed:
969                         if timer.state == TimerEntry.StateWaiting:
970                                 state = _("waiting")
971                                 color = 0x00ffed00 # yellow
972                         elif timer.state == TimerEntry.StatePrepared:
973                                 state = _("about to start")
974                                 color = parseColor("red").argb()
975                         elif timer.state == TimerEntry.StateRunning:
976                                 if timer.justplay:
977                                         state = _("zapped")
978                                 else:
979                                         state = _("recording...")
980                                 color = parseColor("red").argb()
981                         elif timer.state == TimerEntry.StateEnded:
982                                 state = _("done!")
983                                 color = parseColor("green").argb()
984                         else:
985                                 state = _("<unknown>")
986                                 color = parseColor("red").argb()
987                 else:
988                         state = _("done!")
989                         color = parseColor("green").argb()
990                 
991                 if timer.disabled:
992                         state = _("disabled")
993                         color = 0x009a9a9a
994                         
995                 if timer.justplay:
996                         state = "(ZAP) " + state
997                         
998                 res = [ None ]
999                 
1000                 if config.plugins.merlinEpgCenter.showListNumbers.value:
1001                         number = str(self.instance.getCurrentIndex() + 1)
1002                         width = self.maxWidth * 3 / 100
1003                         # 30 breite
1004                         res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, 0, width, self.itemHeight, 1, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, number))
1005                         offsetLeft = offsetLeft + width + columnSpace
1006                         
1007                 if config.plugins.merlinEpgCenter.showPicons.value:
1008                         width = self.piconSize.width()
1009                         height = self.piconSize.height()
1010                         
1011                         if isinstance(timer, TimerListObject) and self.autoTimerPixmap:
1012                                 picon = self.autoTimerPixmap
1013                         else:
1014                                 picon = self.piconLoader.getPicon(str(timer.service_ref))
1015                         if picon:
1016                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHABLEND, offsetLeft, (self.itemHeight - self.baseHeight) / 2, width, height, picon))
1017                         offsetLeft = offsetLeft + width + columnSpace
1018                         
1019                 if config.plugins.merlinEpgCenter.showServiceName.value:
1020                         extraWidth = int(config.plugins.merlinEpgCenter.serviceNameWidth.value)
1021                         if self.videoMode == MODE_SD:
1022                                 width = self.maxWidth * (12 + extraWidth) / 100
1023                         elif self.videoMode == MODE_XD:
1024                                 width = self.maxWidth * (14 + extraWidth) / 100
1025                         elif self.videoMode == MODE_HD:
1026                                 width = self.maxWidth * (16 + extraWidth) / 100
1027                         elif self.videoMode == MODE_FHD:
1028                                 width = self.maxWidth * (24 + extraWidth) / 100
1029                                 
1030                         if isinstance(timer, RecordTimerEntry):
1031                                 res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, 0, width, self.itemHeight, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, timer.service_ref.getServiceName()))
1032                         else: # AutoTimer entry
1033                                 res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, 0, width, self.itemHeight, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, "AutoTimer"))
1034                         offsetLeft = offsetLeft + width + columnSpace
1035                         
1036                 extraWidth = int(config.plugins.merlinEpgCenter.adjustFontSize.value)
1037                 if extraWidth < 0:
1038                         extraWidth = 0
1039                 if self.videoMode == MODE_SD:
1040                         width = self.maxWidth * (18 + extraWidth) / 100
1041                 else:
1042                         width = self.maxWidth * (14 + extraWidth) / 100
1043                         
1044                 if isinstance(timer, RecordTimerEntry):
1045                         fd = getFuzzyDay(timer.begin)
1046                 
1047                         res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, border, width, self.halfItemHeight - border, 1, RT_HALIGN_LEFT|RT_VALIGN_TOP, timeString))
1048                         res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, self.halfItemHeight, width, self.halfItemHeight - border, 2, RT_HALIGN_CENTER|RT_VALIGN_TOP, fd, secondLineColor))
1049                 else: # AutoTimer entry
1050                         res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, 0, width, self.itemHeight, 1, RT_HALIGN_CENTER|RT_VALIGN_CENTER, timeString))
1051                         
1052                 offsetLeft = offsetLeft + width + columnSpace
1053                 
1054                 width = self.maxWidth * 22 / 100
1055                 offsetRight = offsetRight - width
1056                 res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetLeft, 0, offsetRight - offsetLeft, self.itemHeight, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, timer.name, config.plugins.merlinEpgCenter.titleColor.value, config.plugins.merlinEpgCenter.titleColorSelected.value))
1057                 res.append((eListboxPythonMultiContent.TYPE_TEXT, offsetRight, 0, width, self.itemHeight, 1, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, state, color))
1058                 
1059                 return res