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