45efd7cd6ba9ecf126e4adbd2fd2518bfb6e87f1
[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.dy = 0
53                 if type == EPG_TYPE_SINGLE:
54                         self.l.setBuildFunc(self.buildSingleEntry)
55                 elif type == EPG_TYPE_MULTI:
56                         self.l.setBuildFunc(self.buildMultiEntry)
57                 else:
58                         assert(type == EPG_TYPE_SIMILAR)
59                         self.l.setBuildFunc(self.buildSimilarEntry)
60                 self.epgcache = eEPGCache.getInstance()
61                 self.clocks = [ LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_add.png')),
62                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_pre.png')),
63                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock.png')),
64                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_prepost.png')),
65                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_post.png')),
66                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zapclock_add.png')),
67                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zapclock_pre.png')),
68                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zapclock.png')),
69                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zapclock_prepost.png')),
70                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zapclock_post.png')),
71                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zaprecclock_add.png')),
72                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zaprecclock_pre.png')),
73                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zaprecclock.png')),
74                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zaprecclock_prepost.png')),
75                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/zaprecclock_post.png')),
76                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repepgclock_add.png')),
77                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repepgclock_pre.png')),
78                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repepgclock.png')),
79                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repepgclock_prepost.png')),
80                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repepgclock_post.png')),
81                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzapclock_add.png')),
82                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzapclock_pre.png')),
83                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzapclock.png')),
84                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzapclock_prepost.png')),
85                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzapclock_post.png')),
86                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzaprecclock_add.png')),
87                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzaprecclock_pre.png')),
88                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzaprecclock.png')),
89                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzaprecclock_prepost.png')),
90                                 LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/repzaprecclock_post.png')) ]
91
92         def getEventFromId(self, service, eventid):
93                 event = None
94                 if self.epgcache is not None and eventid is not None:
95                         event = self.epgcache.lookupEventId(service.ref, eventid)
96                 return event
97
98         def getCurrentChangeCount(self):
99                 if self.type == EPG_TYPE_MULTI and self.l.getCurrentSelection() is not None:
100                         return self.l.getCurrentSelection()[0]
101                 return 0
102
103         def getCurrent(self):
104                 idx=0
105                 if self.type == EPG_TYPE_MULTI:
106                         idx += 1
107                 tmp = self.l.getCurrentSelection()
108                 if tmp is None:
109                         return ( None, None )
110                 eventid = tmp[idx+1]
111                 service = ServiceReference(tmp[idx])
112                 event = self.getEventFromId(service, eventid)
113                 return ( event, service )
114
115         def moveUp(self):
116                 self.instance.moveSelection(self.instance.moveUp)
117
118         def moveDown(self):
119                 self.instance.moveSelection(self.instance.moveDown)
120
121         def connectSelectionChanged(func):
122                 if not self.onSelChanged.count(func):
123                         self.onSelChanged.append(func)
124
125         def disconnectSelectionChanged(func):
126                 self.onSelChanged.remove(func)
127
128         def selectionChanged(self):
129                 for x in self.onSelChanged:
130                         if x is not None:
131                                 x()
132 #                               try:
133 #                                       x()
134 #                               except: # FIXME!!!
135 #                                       print "FIXME in EPGList.selectionChanged"
136 #                                       pass
137
138         GUI_WIDGET = eListbox
139
140         def postWidgetCreate(self, instance):
141                 instance.setWrapAround(True)
142                 instance.selectionChanged.get().append(self.selectionChanged)
143                 instance.setContent(self.l)
144
145         def preWidgetRemove(self, instance):
146                 instance.selectionChanged.get().remove(self.selectionChanged)
147                 instance.setContent(None)
148
149         def recalcEntrySize(self):
150                 esize = self.l.getItemSize()
151                 width = esize.width()
152                 height = esize.height()
153                 self.dy = (height - 21)/2
154
155                 if self.type == EPG_TYPE_SINGLE:
156                         self.weekday_rect = Rect(0, 0, width/20*2-10, height)
157                         self.datetime_rect = Rect(width/20*2, 0, width/20*5-15, height)
158                         self.descr_rect = Rect(width/20*7, 0, width/20*13, height)
159                 elif self.type == EPG_TYPE_MULTI:
160                         xpos = 0;
161                         w = width/10*3;
162                         self.service_rect = Rect(xpos, 0, w-10, height)
163                         xpos += w;
164                         w = width/10*2;
165                         self.start_end_rect = Rect(xpos, 0, w-10, height)
166                         self.progress_rect = Rect(xpos, 4, w-10, height-8)
167                         xpos += w
168                         w = width/10*5;
169                         self.descr_rect = Rect(xpos, 0, width, height)
170                 else: # EPG_TYPE_SIMILAR
171                         self.weekday_rect = Rect(0, 0, width/20*2-10, height)
172                         self.datetime_rect = Rect(width/20*2, 0, width/20*5-15, height)
173                         self.service_rect = Rect(width/20*7, 0, width/20*13, height)
174
175         def getClockTypesForEntry(self, service, eventId, beginTime, duration):
176                 if not beginTime:
177                         return None
178                 rec = self.timer.isInTimer(eventId, beginTime, duration, service)
179                 if rec is not None:
180                         return rec[1]
181                 else:
182                         return None
183
184         def buildSingleEntry(self, service, eventId, beginTime, duration, EventName):
185                 clock_types = self.getClockTypesForEntry(service, eventId, beginTime, duration)
186                 r1=self.weekday_rect
187                 r2=self.datetime_rect
188                 r3=self.descr_rect
189                 t = localtime(beginTime)
190                 res = [
191                         None, # no private data needed
192                         (eListboxPythonMultiContent.TYPE_TEXT, r1.x, r1.y, r1.w, r1.h, 0, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, self.days[t[6]]),
193                         (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]))
194                 ]
195                 if clock_types:
196                         for i in range(len(clock_types)):
197                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, r3.x + i * 23, r3.y + self.dy, 21, 21, self.clocks[clock_types[i]]))
198                         res.append((eListboxPythonMultiContent.TYPE_TEXT, r3.x + (i + 1) * 23, r3.y, r3.w, r3.h, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, EventName))
199                 else:
200                         res.append((eListboxPythonMultiContent.TYPE_TEXT, r3.x, r3.y, r3.w, r3.h, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, EventName))
201                 return res
202
203         def buildSimilarEntry(self, service, eventId, beginTime, service_name, duration):
204                 clock_types = self.getClockTypesForEntry(service, eventId, beginTime, duration)
205                 r1=self.weekday_rect
206                 r2=self.datetime_rect
207                 r3=self.service_rect
208                 t = localtime(beginTime)
209                 res = [
210                         None,  # no private data needed
211                         (eListboxPythonMultiContent.TYPE_TEXT, r1.x, r1.y, r1.w, r1.h, 0, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, self.days[t[6]]),
212                         (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]))
213                 ]
214                 if clock_types:
215                         for i in range(len(clock_types)):
216                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, r3.x + i * 23, r3.y + self.dy, 21, 21, self.clocks[clock_types[i]]))
217                         res.append((eListboxPythonMultiContent.TYPE_TEXT, r3.x + (i + 1) * 23, r3.y, r3.w, r3.h, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, service_name))
218                 else:
219                         res.append((eListboxPythonMultiContent.TYPE_TEXT, r3.x, r3.y, r3.w, r3.h, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, service_name))
220                 return res
221
222         def buildMultiEntry(self, changecount, service, eventId, beginTime, duration, EventName, nowTime, service_name):
223                 clock_types = self.getClockTypesForEntry(service, eventId, beginTime, duration)
224                 r1=self.service_rect
225                 r2=self.progress_rect
226                 r3=self.descr_rect
227                 r4=self.start_end_rect
228                 res = [ None ] # no private data needed
229                 if clock_types:
230                         res.append((eListboxPythonMultiContent.TYPE_TEXT, r1.x, r1.y, r1.w - 23 * len(clock_types), r1.h, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, service_name))
231                         for i in range(len(clock_types)):
232                                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, r1.x + r1.w - 23 * (i + 1), r1.y + self.dy, 21, 21, self.clocks[clock_types[len(clock_types) - 1 - i]]))
233                 else:
234                         res.append((eListboxPythonMultiContent.TYPE_TEXT, r1.x, r1.y, r1.w, r1.h, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, service_name))
235                 if beginTime is not None:
236                         if nowTime < beginTime:
237                                 begin = localtime(beginTime)
238                                 end = localtime(beginTime+duration)
239                                 res.extend((
240                                         (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])),
241                                         (eListboxPythonMultiContent.TYPE_TEXT, r3.x, r3.y, 80, r3.h, 1, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, _("%d min") % (duration / 60)),
242                                         (eListboxPythonMultiContent.TYPE_TEXT, r3.x + 90, r3.y, r3.w, r3.h, 0, RT_HALIGN_LEFT, EventName)
243                                 ))
244                         else:
245                                 percent = (nowTime - beginTime) * 100 / duration
246                                 prefix = "+"
247                                 remaining = ((beginTime+duration) - int(time())) / 60
248                                 if remaining <= 0:
249                                         prefix = ""
250                                 res.extend((
251                                         (eListboxPythonMultiContent.TYPE_PROGRESS, r2.x, r2.y, r2.w, r2.h, percent),
252                                         (eListboxPythonMultiContent.TYPE_TEXT, r3.x, r3.y, 80, r3.h, 1, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, _("%s%d min") % (prefix, remaining)),
253                                         (eListboxPythonMultiContent.TYPE_TEXT, r3.x + 90, r3.y, r3.w, r3.h, 0, RT_HALIGN_LEFT, EventName)
254                                 ))
255                 return res
256
257         def queryEPG(self, list, buildFunc=None):
258                 if self.epgcache is not None:
259                         if buildFunc is not None:
260                                 return self.epgcache.lookupEvent(list, buildFunc)
261                         else:
262                                 return self.epgcache.lookupEvent(list)
263                 return [ ]
264
265         def fillMultiEPG(self, services, stime=-1):
266                 #t = time()
267                 test = [ (service.ref.toString(), 0, stime) for service in services ]
268                 test.insert(0, 'X0RIBDTCn')
269                 self.list = self.queryEPG(test)
270                 self.l.setList(self.list)
271                 #print time() - t
272                 self.selectionChanged()
273
274         def updateMultiEPG(self, direction):
275                 #t = time()
276                 test = [ x[3] and (x[1], direction, x[3]) or (x[1], direction, 0) for x in self.list ]
277                 test.insert(0, 'XRIBDTCn')
278                 tmp = self.queryEPG(test)
279                 cnt=0
280                 for x in tmp:
281                         changecount = self.list[cnt][0] + direction
282                         if changecount >= 0:
283                                 if x[2] is not None:
284                                         self.list[cnt]=(changecount, x[0], x[1], x[2], x[3], x[4], x[5], x[6])
285                         cnt+=1
286                 self.l.setList(self.list)
287                 #print time() - t
288                 self.selectionChanged()
289
290         def fillSingleEPG(self, service):
291                 t = time()
292                 epg_time = t - config.epg.histminutes.getValue()*60
293                 test = [ 'RIBDT', (service.ref.toString(), 0, epg_time, -1) ]
294                 self.list = self.queryEPG(test)
295                 self.l.setList(self.list)
296                 if t != epg_time:
297                         idx = 0
298                         for x in self.list:
299                                 idx += 1
300                                 if t < x[2]+x[3]:
301                                         break
302                         self.instance.moveSelectionTo(idx-1)
303                 self.selectionChanged()
304
305         def sortSingleEPG(self, type):
306                 list = self.list
307                 if list:
308                         event_id = self.getSelectedEventId()
309                         if type == 1:
310                                 list.sort(key=lambda x: (x[4] and x[4].lower(), x[2]))
311                         else:
312                                 assert(type == 0)
313                                 list.sort(key=lambda x: x[2])
314                         self.l.invalidate()
315                         self.moveToEventId(event_id)
316
317         def getSelectedEventId(self):
318                 x = self.l.getCurrentSelection()
319                 return x and x[1]
320
321         def moveToService(self,serviceref):
322                 if not serviceref:
323                         return
324                 index = 0
325                 refstr = serviceref.toString()
326                 for x in self.list:
327                         if CompareWithAlternatives(x[1], refstr):
328                                 self.instance.moveSelectionTo(index)
329                                 break
330                         index += 1
331
332         def moveToEventId(self, eventId):
333                 if not eventId:
334                         return
335                 index = 0
336                 for x in self.list:
337                         if x[1] == eventId:
338                                 self.instance.moveSelectionTo(index)
339                                 break
340                         index += 1
341
342         def fillSimilarList(self, refstr, event_id):
343                 t = time()
344                 # search similar broadcastings
345                 if event_id is None:
346                         return
347                 l = self.epgcache.search(('RIBND', 1024, eEPGCache.SIMILAR_BROADCASTINGS_SEARCH, refstr, event_id))
348                 if l and len(l):
349                         l.sort(key=lambda x: x[2])
350                 self.l.setList(l)
351                 self.selectionChanged()
352                 print time() - t
353
354         def applySkin(self, desktop, parent):
355                 def setEventItemFont(value):
356                         self.eventItemFont = parseFont(value, ((1,1),(1,1)))
357                 def setEventTimeFont(value):
358                         self.eventTimeFont = parseFont(value, ((1,1),(1,1)))
359                 for (attrib, value) in [x for x in self.skinAttributes if x[0] in dir() and callable(locals().get(x[0]))]:
360                         locals().get(attrib)(value)
361                         self.skinAttributes.remove((attrib, value))
362                 self.l.setFont(0, self.eventItemFont)
363                 self.l.setFont(1, self.eventTimeFont)
364                 return GUIComponent.applySkin(self, desktop, parent)