fix copy/paste bug in similar epg for FullHD
[openblackhole/openblackhole-enigma2.git] / lib / python / Components / EpgList.py
1 from HTMLComponent import HTMLComponent
2 from GUIComponent import GUIComponent
3
4 from enigma import eEPGCache, eListbox, eListboxPythonMultiContent, gFont, \
5         RT_HALIGN_LEFT, RT_HALIGN_RIGHT, RT_HALIGN_CENTER, RT_VALIGN_CENTER
6
7 from Tools.Alternatives import CompareWithAlternatives
8 from Tools.LoadPixmap import LoadPixmap
9
10 from time import localtime, time
11 from Components.config import config
12 from ServiceReference import ServiceReference
13 from Tools.Directories import resolveFilename, SCOPE_CURRENT_SKIN
14 from skin import parseFont
15
16 EPG_TYPE_SINGLE = 0
17 EPG_TYPE_MULTI = 1
18 EPG_TYPE_SIMILAR = 2
19
20 class Rect:
21         def __init__(self, x, y, width, height):
22                 self.x = x
23                 self.y = y
24                 self.w = width
25                 self.h = height
26
27         # silly, but backward compatible
28         def left(self):
29                 return self.x
30
31         def top(self):
32                 return self.y
33
34         def height(self):
35                 return self.h
36
37         def width(self):
38                 return self.w
39
40 class EPGList(HTMLComponent, GUIComponent):
41         def __init__(self, type=EPG_TYPE_SINGLE, selChangedCB=None, timer = None):
42                 self.days = (_("Mon"), _("Tue"), _("Wed"), _("Thu"), _("Fri"), _("Sat"), _("Sun"))
43                 self.timer = timer
44                 self.onSelChanged = [ ]
45                 if selChangedCB is not None:
46                         self.onSelChanged.append(selChangedCB)
47                 GUIComponent.__init__(self)
48                 self.type=type
49                 self.l = eListboxPythonMultiContent()
50                 self.eventItemFont = gFont("Regular", 22)
51                 self.eventTimeFont = gFont("Regular", 16)
52                 self.iconSize = 21
53                 self.iconDistance = 2
54                 self.colGap = 10
55                 self.skinColumns = False
56                 self.tw = 90
57                 self.dy = 0
58
59                 if type == EPG_TYPE_SINGLE:
60                         self.l.setBuildFunc(self.buildSingleEntry)
61                 elif type == EPG_TYPE_MULTI:
62                         self.l.setBuildFunc(self.buildMultiEntry)
63                 else:
64                         assert(type == EPG_TYPE_SIMILAR)
65                         self.l.setBuildFunc(self.buildSimilarEntry)
66                 self.epgcache = eEPGCache.getInstance()
67                 self.clocks = [ LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_add.png')),
68                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_pre.png')),
69                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock.png')),
70                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_prepost.png')),
71                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_post.png')),
72                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zapclock_add.png')),
73                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zapclock_pre.png')),
74                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zapclock.png')),
75                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zapclock_prepost.png')),
76                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zapclock_post.png')),
77                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zaprecclock_add.png')),
78                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zaprecclock_pre.png')),
79                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zaprecclock.png')),
80                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zaprecclock_prepost.png')),
81                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zaprecclock_post.png')),
82                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repepgclock_add.png')),
83                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repepgclock_pre.png')),
84                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repepgclock.png')),
85                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repepgclock_prepost.png')),
86                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repepgclock_post.png')),
87                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzapclock_add.png')),
88                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzapclock_pre.png')),
89                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzapclock.png')),
90                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzapclock_prepost.png')),
91                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzapclock_post.png')),
92                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzaprecclock_add.png')),
93                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzaprecclock_pre.png')),
94                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzaprecclock.png')),
95                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzaprecclock_prepost.png')),
96                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzaprecclock_post.png')) ]
97
98         def getEventFromId(self, service, eventid):
99                 event = None
100                 if self.epgcache is not None and eventid is not None:
101                         event = self.epgcache.lookupEventId(service.ref, eventid)
102                 return event
103
104         def getCurrentChangeCount(self):
105                 if self.type == EPG_TYPE_MULTI and self.l.getCurrentSelection() is not None:
106                         return self.l.getCurrentSelection()[0]
107                 return 0
108
109         def getCurrent(self):
110                 idx=0
111                 if self.type == EPG_TYPE_MULTI:
112                         idx += 1
113                 tmp = self.l.getCurrentSelection()
114                 if tmp is None:
115                         return ( None, None )
116                 eventid = tmp[idx+1]
117                 service = ServiceReference(tmp[idx])
118                 event = self.getEventFromId(service, eventid)
119                 return ( event, service )
120
121         def moveUp(self):
122                 self.instance.moveSelection(self.instance.moveUp)
123
124         def moveDown(self):
125                 self.instance.moveSelection(self.instance.moveDown)
126
127         def connectSelectionChanged(func):
128                 if not self.onSelChanged.count(func):
129                         self.onSelChanged.append(func)
130
131         def disconnectSelectionChanged(func):
132                 self.onSelChanged.remove(func)
133
134         def selectionChanged(self):
135                 for x in self.onSelChanged:
136                         if x is not None:
137                                 x()
138 #                               try:
139 #                                       x()
140 #                               except: # FIXME!!!
141 #                                       print "FIXME in EPGList.selectionChanged"
142 #                                       pass
143
144         GUI_WIDGET = eListbox
145
146         def postWidgetCreate(self, instance):
147                 instance.setWrapAround(True)
148                 instance.selectionChanged.get().append(self.selectionChanged)
149                 instance.setContent(self.l)
150
151         def preWidgetRemove(self, instance):
152                 instance.selectionChanged.get().remove(self.selectionChanged)
153                 instance.setContent(None)
154
155         def recalcEntrySize(self):
156                 esize = self.l.getItemSize()
157                 width = esize.width()
158                 height = esize.height()
159                 try:
160                         self.iconSize = self.clocks[0].size().height()
161                 except:
162                         pass
163                 self.space = self.iconSize + self.iconDistance
164                 self.dy = int((height - self.iconSize)/2.)
165
166                 if self.type == EPG_TYPE_SINGLE:
167                         if self.skinColumns:
168                                 x = 0
169                                 self.weekday_rect = Rect(0, 0, self.gap(self.col[0]), height)
170                                 x += self.col[0]
171                                 self.datetime_rect = Rect(x, 0, self.gap(self.col[1]), height)
172                                 x += self.col[1]
173                                 self.descr_rect = Rect(x, 0, width-x, height)
174                         else:
175                                 self.weekday_rect = Rect(0, 0, width/20*2-10, height)
176                                 self.datetime_rect = Rect(width/20*2, 0, width/20*5-15, height)
177                                 self.descr_rect = Rect(width/20*7, 0, width/20*13, height)
178                 elif self.type == EPG_TYPE_MULTI:
179                         if self.skinColumns:
180                                 x = 0
181                                 self.service_rect = Rect(x, 0, self.gap(self.col[0]), height)
182                                 x += self.col[0]
183                                 self.progress_rect = Rect(x, 8, self.gap(self.col[1]), height-16)
184                                 self.start_end_rect = Rect(x, 0, self.gap(self.col[1]), height)
185                                 x += self.col[1]
186                                 self.descr_rect = Rect(x, 0, width-x, height)
187                         else:
188                                 xpos = 0;
189                                 w = width/10*3;
190                                 self.service_rect = Rect(xpos, 0, w-10, height)
191                                 xpos += w;
192                                 w = width/10*2;
193                                 self.start_end_rect = Rect(xpos, 0, w-10, height)
194                                 self.progress_rect = Rect(xpos, 4, w-10, height-8)
195                                 xpos += w
196                                 w = width/10*5;
197                                 self.descr_rect = Rect(xpos, 0, width, height)
198                 else: # EPG_TYPE_SIMILAR
199                         if self.skinColumns:
200                                 x = 0
201                                 self.weekday_rect = Rect(0, 0, self.gap(self.col[0]), height)
202                                 x += self.col[0]
203                                 self.datetime_rect = Rect(x, 0, self.gap(self.col[1]), height)
204                                 x += self.col[1]
205                                 self.service_rect = Rect(x, 0, width-x, height)
206                         else:
207                                 self.weekday_rect = Rect(0, 0, width/20*2-10, height)
208                                 self.datetime_rect = Rect(width/20*2, 0, width/20*5-15, height)
209                                 self.service_rect = Rect(width/20*7, 0, width/20*13, height)
210
211         def gap(self, width):
212                 return width - self.colGap
213
214         def getClockTypesForEntry(self, service, eventId, beginTime, duration):
215                 if not beginTime:
216                         return None
217                 rec = self.timer.isInTimer(eventId, beginTime, duration, service)
218                 if rec is not None:
219                         return rec[1]
220                 else:
221                         return None
222
223         def buildSingleEntry(self, service, eventId, beginTime, duration, EventName):
224                 clock_types = self.getClockTypesForEntry(service, eventId, beginTime, duration)
225                 r1=self.weekday_rect
226                 r2=self.datetime_rect
227                 r3=self.descr_rect
228                 t = localtime(beginTime)
229                 res = [
230                         None, # no private data needed
231                         (eListboxPythonMultiContent.TYPE_TEXT, r1.x, r1.y, r1.w, r1.h, 0, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, self.days[t[6]]),
232                         (eListboxPythonMultiContent.TYPE_TEXT, r2.x, r2.y, r2.w, r1.h, 0, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, "%2d.%02d, %02d:%02d"%(t[2],t[1],t[3],t[4]))
233                 ]
234                 if clock_types:
235                         for i in range(len(clock_types)):
236                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, r3.x + i * self.space, r3.y + self.dy, self.iconSize, self.iconSize, self.clocks[clock_types[i]]))
237                         res.append((eListboxPythonMultiContent.TYPE_TEXT, r3.x + (i + 1) * self.space, r3.y, r3.w, r3.h, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, EventName))
238                 else:
239                         res.append((eListboxPythonMultiContent.TYPE_TEXT, r3.x, r3.y, r3.w, r3.h, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, EventName))
240                 return res
241
242         def buildSimilarEntry(self, service, eventId, beginTime, service_name, duration):
243                 clock_types = self.getClockTypesForEntry(service, eventId, beginTime, duration)
244                 r1=self.weekday_rect
245                 r2=self.datetime_rect
246                 r3=self.service_rect
247                 t = localtime(beginTime)
248                 res = [
249                         None,  # no private data needed
250                         (eListboxPythonMultiContent.TYPE_TEXT, r1.x, r1.y, r1.w, r1.h, 0, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, self.days[t[6]]),
251                         (eListboxPythonMultiContent.TYPE_TEXT, r2.x, r2.y, r2.w, r1.h, 0, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, "%2d.%02d, %02d:%02d"%(t[2],t[1],t[3],t[4]))
252                 ]
253                 if clock_types:
254                         for i in range(len(clock_types)):
255                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, r3.x + i * self.space, r3.y + self.dy, self.iconSize, self.iconSize, self.clocks[clock_types[i]]))
256                         res.append((eListboxPythonMultiContent.TYPE_TEXT, r3.x + (i + 1) * self.space, r3.y, r3.w, r3.h, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, service_name))
257                 else:
258                         res.append((eListboxPythonMultiContent.TYPE_TEXT, r3.x, r3.y, r3.w, r3.h, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, service_name))
259                 return res
260
261         def buildMultiEntry(self, changecount, service, eventId, beginTime, duration, EventName, nowTime, service_name):
262                 clock_types = self.getClockTypesForEntry(service, eventId, beginTime, duration)
263                 r1=self.service_rect
264                 r2=self.progress_rect
265                 r3=self.descr_rect
266                 r4=self.start_end_rect
267                 res = [ None ] # no private data needed
268                 if clock_types:
269                         res.append((eListboxPythonMultiContent.TYPE_TEXT, r1.x, r1.y, r1.w - self.space * len(clock_types), r1.h, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, service_name))
270                         for i in range(len(clock_types)):
271                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, r1.x + r1.w - self.space * (i + 1), r1.y + self.dy, self.iconSize, self.iconSize, self.clocks[clock_types[len(clock_types) - 1 - i]]))
272                 else:
273                         res.append((eListboxPythonMultiContent.TYPE_TEXT, r1.x, r1.y, r1.w, r1.h, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, service_name))
274                 if beginTime is not None:
275                         if nowTime < beginTime:
276                                 begin = localtime(beginTime)
277                                 end = localtime(beginTime+duration)
278                                 res.extend((
279                                         (eListboxPythonMultiContent.TYPE_TEXT, r4.x, r4.y, r4.w, r4.h, 1, RT_HALIGN_CENTER|RT_VALIGN_CENTER, "%02d.%02d - %02d.%02d"%(begin[3],begin[4],end[3],end[4])),
280                                         (eListboxPythonMultiContent.TYPE_TEXT, r3.x, r3.y, self.gap(self.tw), r3.h, 1, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, _("%d min") % (duration / 60)),
281                                         (eListboxPythonMultiContent.TYPE_TEXT, r3.x + self.tw, r3.y, r3.w, r3.h, 0, RT_HALIGN_LEFT, EventName)
282                                 ))
283                         else:
284                                 percent = (nowTime - beginTime) * 100 / duration
285                                 prefix = "+"
286                                 remaining = ((beginTime+duration) - int(time())) / 60
287                                 if remaining <= 0:
288                                         prefix = ""
289                                 res.extend((
290                                         (eListboxPythonMultiContent.TYPE_PROGRESS, r2.x, r2.y, r2.w, r2.h, percent),
291                                         (eListboxPythonMultiContent.TYPE_TEXT, r3.x, r3.y, self.gap(self.tw), r3.h, 1, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, _("%s%d min") % (prefix, remaining)),
292                                         (eListboxPythonMultiContent.TYPE_TEXT, r3.x + self.tw, r3.y, r3.w, r3.h, 0, RT_HALIGN_LEFT, EventName)
293                                 ))
294                 return res
295
296         def queryEPG(self, list, buildFunc=None):
297                 if self.epgcache is not None:
298                         if buildFunc is not None:
299                                 return self.epgcache.lookupEvent(list, buildFunc)
300                         else:
301                                 return self.epgcache.lookupEvent(list)
302                 return [ ]
303
304         def fillMultiEPG(self, services, stime=-1):
305                 #t = time()
306                 test = [ (service.ref.toString(), 0, stime) for service in services ]
307                 test.insert(0, 'X0RIBDTCn')
308                 self.list = self.queryEPG(test)
309                 self.l.setList(self.list)
310                 #print time() - t
311                 self.selectionChanged()
312
313         def updateMultiEPG(self, direction):
314                 #t = time()
315                 test = [ x[3] and (x[1], direction, x[3]) or (x[1], direction, 0) for x in self.list ]
316                 test.insert(0, 'XRIBDTCn')
317                 tmp = self.queryEPG(test)
318                 cnt=0
319                 for x in tmp:
320                         changecount = self.list[cnt][0] + direction
321                         if changecount >= 0:
322                                 if x[2] is not None:
323                                         self.list[cnt]=(changecount, x[0], x[1], x[2], x[3], x[4], x[5], x[6])
324                         cnt+=1
325                 self.l.setList(self.list)
326                 #print time() - t
327                 self.selectionChanged()
328
329         def fillSingleEPG(self, service):
330                 t = time()
331                 epg_time = t - config.epg.histminutes.getValue()*60
332                 test = [ 'RIBDT', (service.ref.toString(), 0, epg_time, -1) ]
333                 self.list = self.queryEPG(test)
334                 self.l.setList(self.list)
335                 if t != epg_time:
336                         idx = 0
337                         for x in self.list:
338                                 idx += 1
339                                 if t < x[2]+x[3]:
340                                         break
341                         self.instance.moveSelectionTo(idx-1)
342                 self.selectionChanged()
343
344         def sortSingleEPG(self, type):
345                 list = self.list
346                 if list:
347                         event_id = self.getSelectedEventId()
348                         if type == 1:
349                                 list.sort(key=lambda x: (x[4] and x[4].lower(), x[2]))
350                         else:
351                                 assert(type == 0)
352                                 list.sort(key=lambda x: x[2])
353                         self.l.invalidate()
354                         self.moveToEventId(event_id)
355
356         def getSelectedEventId(self):
357                 x = self.l.getCurrentSelection()
358                 return x and x[1]
359
360         def moveToService(self,serviceref):
361                 if not serviceref:
362                         return
363                 index = 0
364                 refstr = serviceref.toString()
365                 for x in self.list:
366                         if CompareWithAlternatives(x[1], refstr):
367                                 self.instance.moveSelectionTo(index)
368                                 break
369                         index += 1
370
371         def moveToEventId(self, eventId):
372                 if not eventId:
373                         return
374                 index = 0
375                 for x in self.list:
376                         if x[1] == eventId:
377                                 self.instance.moveSelectionTo(index)
378                                 break
379                         index += 1
380
381         def fillSimilarList(self, refstr, event_id):
382                 t = time()
383                 # search similar broadcastings
384                 if event_id is None:
385                         return
386                 l = self.epgcache.search(('RIBND', 1024, eEPGCache.SIMILAR_BROADCASTINGS_SEARCH, refstr, event_id))
387                 if l and len(l):
388                         l.sort(key=lambda x: x[2])
389                 self.l.setList(l)
390                 self.selectionChanged()
391                 print time() - t
392
393         def applySkin(self, desktop, parent):
394                 def warningWrongSkinParameter(string):
395                         print "[EPGList] wrong '%s' skin parameters" % string
396                 def setEventItemFont(value):
397                         self.eventItemFont = parseFont(value, ((1,1),(1,1)))
398                 def setEventTimeFont(value):
399                         self.eventTimeFont = parseFont(value, ((1,1),(1,1)))
400                 def setIconDistance(value):
401                         self.iconDistance = int(value)
402                 def setIconShift(value):
403                         self.dy = int(value)
404                 def setTimeWidth(value):
405                         self.tw = int(value)
406                 def setColWidths(value):
407                         self.col = map(int, value.split(','))
408                         if len(self.col) == 2:
409                                 self.skinColumns = True
410                         else:
411                                 warningWrongSkinParameter(attrib)
412                 def setColGap(value):
413                         self.colGap = int(value)
414                 for (attrib, value) in self.skinAttributes[:]:
415                         try:
416                                 locals().get(attrib)(value)
417                                 self.skinAttributes.remove((attrib, value))
418                         except:
419                                 pass
420                 self.l.setFont(0, self.eventItemFont)
421                 self.l.setFont(1, self.eventTimeFont)
422                 return GUIComponent.applySkin(self, desktop, parent)