648d4f1135c0f9e1496e8b43a5acf3dcbe0e72ab
[vuplus_dvbapp] / lib / python / Plugins / Extensions / GraphMultiEPG / GraphMultiEpg.py
1 from skin import queryColor
2 from Components.config import config, ConfigClock, ConfigInteger
3 from Components.Pixmap import Pixmap
4 from Components.Button import Button
5 from Components.ActionMap import ActionMap
6 from Components.HTMLComponent import HTMLComponent
7 from Components.GUIComponent import GUIComponent
8 from Components.EpgList import Rect
9 from Components.Sources.Event import Event
10 from Components.Sources.Clock import Clock
11 from Screens.Screen import Screen
12 from Screens.EventView import EventViewSimple
13 from Screens.TimeDateInput import TimeDateInput
14 from Screens.TimerEntry import TimerEntry
15 from Screens.EpgSelection import EPGSelection
16 from Tools.Directories import resolveFilename, SCOPE_SKIN_IMAGE
17 from RecordTimer import RecordTimerEntry, parseEvent
18 from ServiceReference import ServiceReference
19 from enigma import eEPGCache, eListbox, eListboxPythonMultiContent, gFont, loadPNG, \
20         RT_HALIGN_LEFT, RT_HALIGN_CENTER, RT_VALIGN_CENTER, RT_WRAP, eRect, eTimer
21
22 from time import localtime, time, strftime
23
24 class EPGList(HTMLComponent, GUIComponent):
25         def __init__(self, selChangedCB=None, timer = None, time_epoch = 120, overjump_empty=True):
26                 self.cur_event = None
27                 self.cur_service = None
28                 self.offs = 0
29                 self.timer = timer
30                 self.onSelChanged = [ ]
31                 if selChangedCB is not None:
32                         self.onSelChanged.append(selChangedCB)
33                 GUIComponent.__init__(self)
34                 self.l = eListboxPythonMultiContent()
35                 self.l.setItemHeight(54);
36                 self.l.setBuildFunc(self.buildEntry)
37                 if overjump_empty:
38                         self.l.setSelectableFunc(self.isSelectable)
39                 self.epgcache = eEPGCache.getInstance()
40                 self.clock_pixmap = loadPNG(resolveFilename(SCOPE_SKIN_IMAGE, 'epgclock-fs8.png'))
41                 self.time_base = None
42                 self.time_epoch = time_epoch
43                 self.list = None
44                 self.event_rect = None
45
46                 #query skin colors
47                 col = queryColor("GraphEpg.Foreground")
48                 self.foreColor = col and col.argb()
49
50                 col = queryColor("GraphEpg.Border")
51                 self.borderColor = col and col.argb()
52
53                 col = queryColor("GraphEpg.Background")
54                 if col is None:
55                         self.backColor = 0x586d88
56                 else:
57                         self.backColor = col.argb()
58
59                 col = queryColor("GraphEpg.BackgroundSelected")
60                 if col is None:
61                         self.backColorSelected = 0x808080
62                 else:
63                         self.backColorSelected = col.argb()
64
65         def isSelectable(self, service, sname, event_list):
66                 return (event_list and len(event_list) and True) or False
67
68         def setEpoch(self, epoch):
69                 if self.cur_event is not None and self.cur_service is not None:
70                         self.offs = 0
71                         self.time_epoch = epoch
72                         self.fillMultiEPG(None) # refill
73
74         def getEventFromId(self, service, eventid):
75                 event = None
76                 if self.epgcache is not None and eventid is not None:
77                         event = self.epgcache.lookupEventId(service.ref, eventid)
78                 return event
79
80         def getCurrent(self):
81                 if self.cur_service is None or self.cur_event is None:
82                         return ( None, None )
83                 old_service = self.cur_service  #(service, service_name, events)
84                 events = self.cur_service[2]
85                 refstr = self.cur_service[0]
86                 if not events or not len(events):
87                         return ( None, None )
88                 event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
89                 eventid = event[0]
90                 service = ServiceReference(refstr)
91                 event = self.getEventFromId(service, eventid)
92                 return ( event, service )
93
94         def connectSelectionChanged(func):
95                 if not self.onSelChanged.count(func):
96                         self.onSelChanged.append(func)
97
98         def disconnectSelectionChanged(func):
99                 self.onSelChanged.remove(func)
100
101         def serviceChanged(self):
102                 cur_sel = self.l.getCurrentSelection()
103                 if cur_sel:
104                         self.findBestEvent()
105
106         def findBestEvent(self):
107                 old_service = self.cur_service  #(service, service_name, events)
108                 cur_service = self.cur_service = self.l.getCurrentSelection()
109                 last_time = 0;
110                 if old_service and self.cur_event is not None:
111                         events = old_service[2]
112                         cur_event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
113                         last_time = cur_event[2]
114                 if cur_service:
115                         self.cur_event = 0
116                         events = cur_service[2]
117                         if events and len(events):
118                                 if last_time:
119                                         best_diff = 0
120                                         best = len(events) #set invalid
121                                         idx = 0
122                                         for event in events: #iterate all events
123                                                 diff = abs(event[2]-last_time)
124                                                 if (best == len(events)) or (diff < best_diff):
125                                                         best = idx
126                                                         best_diff = diff
127                                                 idx += 1
128                                         if best != len(events):
129                                                 self.cur_event = best
130                         else:
131                                 self.cur_event = None
132                 self.selEntry(0)
133
134         def selectionChanged(self):
135                 for x in self.onSelChanged:
136                         if x is not None:
137                                 try:
138                                         x()
139                                 except: # FIXME!!!
140                                         print "FIXME in EPGList.selectionChanged"
141                                         pass
142
143         GUI_WIDGET = eListbox
144
145         def postWidgetCreate(self, instance):
146                 instance.setWrapAround(True)
147                 instance.selectionChanged.get().append(self.serviceChanged)
148                 instance.setContent(self.l)
149
150         def recalcEntrySize(self):
151                 esize = self.l.getItemSize()
152                 self.l.setFont(0, gFont("Regular", 20))
153                 self.l.setFont(1, gFont("Regular", 14))
154                 width = esize.width()
155                 height = esize.height()
156                 xpos = 0;
157                 w = width/10*2;
158                 self.service_rect = Rect(xpos, 0, w-10, height)
159                 xpos += w;
160                 w = width/10*8;
161                 self.event_rect = Rect(xpos, 0, w, height)
162                 self.l.setSelectionClip(eRect(0,0,0,0), False)
163
164         def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
165                 xpos = (stime - start) * width / (end - start)
166                 ewidth = (stime + duration - start) * width / (end - start)
167                 ewidth -= xpos;
168                 if xpos < 0:
169                         ewidth += xpos;
170                         xpos = 0;
171                 if (xpos+ewidth) > width:
172                         ewidth = width - xpos
173                 return xpos, ewidth
174
175         def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
176                 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
177                 return xpos+event_rect.left(), width
178
179         def buildEntry(self, service, service_name, events):
180                 r1=self.service_rect
181                 r2=self.event_rect
182                 res = [ None ] # no private data needed
183                 res.append((eListboxPythonMultiContent.TYPE_TEXT, r1.left(), r1.top(), r1.width(), r1.height(), 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, service_name))
184
185                 if events:
186                         start = self.time_base+self.offs*self.time_epoch*60
187                         end = start + self.time_epoch * 60
188                         left = r2.left()
189                         top = r2.top()
190                         width = r2.width()
191                         height = r2.height()
192                         foreColor = self.foreColor
193                         backColor = self.backColor
194                         backColorSelected = self.backColorSelected
195                         borderColor = self.borderColor
196
197                         for ev in events:  #(event_id, event_title, begin_time, duration)
198                                 rec=self.timer.isInTimer(ev[0], ev[2], ev[3], service) > ((ev[3]/10)*8)
199                                 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
200                                 if self.borderColor is None:
201                                         res.append((eListboxPythonMultiContent.TYPE_TEXT, left+xpos, top, ewidth, height, 1, RT_HALIGN_CENTER|RT_VALIGN_CENTER|RT_WRAP, ev[1], foreColor, backColor, backColorSelected, 1))
202                                 else:
203                                         res.append((eListboxPythonMultiContent.TYPE_TEXT, left+xpos, top, ewidth, height, 1, RT_HALIGN_CENTER|RT_VALIGN_CENTER|RT_WRAP, ev[1], foreColor, backColor, backColorSelected, 1, borderColor))
204                                 if rec and ewidth > 23:
205                                         res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, left+xpos+ewidth-22, top+height-22, 21, 21, self.clock_pixmap, backColor, backColorSelected))
206                 return res
207
208         def selEntry(self, dir, visible=True):
209                 cur_service = self.cur_service #(service, service_name, events)
210                 if not self.event_rect:
211                         self.recalcEntrySize()
212                 if cur_service and self.cur_event is not None:
213                         update = True
214                         entries = cur_service[2]
215                         if dir == 0: #current
216                                 update = False
217                         elif dir == +1: #next
218                                 if self.cur_event+1 < len(entries):
219                                         self.cur_event+=1
220                                 else:
221                                         self.offs += 1
222                                         self.fillMultiEPG(None) # refill
223                                         return True
224                         elif dir == -1: #prev
225                                 if self.cur_event-1 >= 0:
226                                         self.cur_event-=1
227                                 elif self.offs > 0:
228                                         self.offs -= 1
229                                         self.fillMultiEPG(None) # refill
230                                         return True
231                         entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
232                         time_base = self.time_base+self.offs*self.time_epoch*60
233                         xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
234                         self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
235                 else:
236                         self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
237                 self.selectionChanged()
238                 return False
239
240         def queryEPG(self, list, buildFunc=None):
241                 if self.epgcache is not None:
242                         if buildFunc is not None:
243                                 return self.epgcache.lookupEvent(list, buildFunc)
244                         else:
245                                 return self.epgcache.lookupEvent(list)
246                 return [ ]
247
248         def fillMultiEPG(self, services, stime=-1):
249                 if services is None:
250                         time_base = self.time_base+self.offs*self.time_epoch*60
251                         test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
252                 else:
253                         self.cur_event = None
254                         self.cur_service = None
255                         self.time_base = int(stime)
256                         test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
257                 test.insert(0, 'RnITBD')
258                 epg_data = self.queryEPG(test)
259
260                 self.list = [ ]
261                 tmp_list = None
262                 service = ""
263                 sname = ""
264                 for x in epg_data:
265                         if service != x[0]:
266                                 if tmp_list is not None:
267                                         self.list.append((service, sname, tmp_list[0][0] and tmp_list))
268                                 service = x[0]
269                                 sname = x[1]
270                                 tmp_list = [ ]
271                         tmp_list.append((x[2], x[3], x[4], x[5]))
272                 if tmp_list and len(tmp_list):
273                         self.list.append((service, sname, tmp_list[0][0] and tmp_list))
274
275                 self.l.setList(self.list)
276                 self.findBestEvent()
277
278         def getEventRect(self):
279                 rc = self.event_rect
280                 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
281
282         def getTimeEpoch(self):
283                 return self.time_epoch
284
285         def getTimeBase(self):
286                 return self.time_base + (self.offs * self.time_epoch * 60)
287
288         def resetOffset(self):
289                 self.offs = 0
290
291 class TimelineText(HTMLComponent, GUIComponent):
292         def __init__(self):
293                 GUIComponent.__init__(self)
294                 self.l = eListboxPythonMultiContent()
295                 self.l.setSelectionClip(eRect(0,0,0,0))
296                 self.l.setItemHeight(25);
297                 self.l.setFont(0, gFont("Regular", 20))
298
299         GUI_WIDGET = eListbox
300
301         def postWidgetCreate(self, instance):
302                 instance.setContent(self.l)
303
304         def setEntries(self, entries):
305                 res = [ None ] # no private data needed
306                 for x in entries:
307                         tm = x[0]
308                         xpos = x[1]
309                         str = strftime("%H:%M", localtime(tm))
310                         res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
311                 self.l.setList([res])
312
313 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
314 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
315
316 class GraphMultiEPG(Screen):
317         def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
318                 Screen.__init__(self, session)
319                 self.bouquetChangeCB = bouquetChangeCB
320                 now = time()
321                 tmp = now % 900
322                 self.ask_time = now - tmp
323                 self.closeRecursive = False
324                 self["key_red"] = Button("")
325                 self["key_green"] = Button(_("Add timer"))
326                 self["timeline_text"] = TimelineText()
327                 self["Event"] = Event()
328                 self["Clock"] = Clock()
329                 self.time_lines = [ ]
330                 for x in (0,1,2,3,4,5):
331                         pm = Pixmap()
332                         self.time_lines.append(pm)
333                         self["timeline%d"%(x)] = pm
334                 self["timeline_now"] = Pixmap()
335                 self.services = services
336                 self.zapFunc = zapFunc
337
338                 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
339
340                 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
341                         {
342                                 "cancel": self.closeScreen,
343                                 "ok": self.eventSelected,
344                                 "timerAdd": self.timerAdd,
345                                 "info": self.infoKeyPressed,
346                                 "red": self.zapTo,
347                                 "input_date_time": self.enterDateTime,
348                                 "nextBouquet": self.nextBouquet,
349                                 "prevBouquet": self.prevBouquet,
350                         })
351                 self["actions"].csel = self
352
353                 self["input_actions"] = ActionMap(["InputActions"],
354                         {
355                                 "left": self.leftPressed,
356                                 "right": self.rightPressed,
357                                 "1": self.key1,
358                                 "2": self.key2,
359                                 "3": self.key3,
360                                 "4": self.key4,
361                                 "5": self.key5,
362                         },-1)
363
364                 self.updateTimelineTimer = eTimer()
365                 self.updateTimelineTimer.timeout.get().append(self.moveTimeLines)
366                 self.updateTimelineTimer.start(60*1000)
367                 self.onLayoutFinish.append(self.onCreate)
368
369         def leftPressed(self):
370                 self.prevEvent()
371
372         def rightPressed(self):
373                 self.nextEvent()
374
375         def nextEvent(self, visible=True):
376                 ret = self["list"].selEntry(+1, visible)
377                 if ret:
378                         self.moveTimeLines(True)
379
380         def prevEvent(self, visible=True):
381                 ret = self["list"].selEntry(-1, visible)
382                 if ret:
383                         self.moveTimeLines(True)
384
385         def key1(self):
386                 self["list"].setEpoch(60)
387                 config.misc.graph_mepg_prev_time_period.value = 60
388                 self.moveTimeLines()
389
390         def key2(self):
391                 self["list"].setEpoch(120)
392                 config.misc.graph_mepg_prev_time_period.value = 120
393                 self.moveTimeLines()
394
395         def key3(self):
396                 self["list"].setEpoch(180)
397                 config.misc.graph_mepg_prev_time_period.value = 180
398                 self.moveTimeLines()
399
400         def key4(self):
401                 self["list"].setEpoch(240)
402                 config.misc.graph_mepg_prev_time_period.value = 240
403                 self.moveTimeLines()
404
405         def key5(self):
406                 self["list"].setEpoch(300)
407                 config.misc.graph_mepg_prev_time_period.value = 300
408                 self.moveTimeLines()
409
410         def nextBouquet(self):
411                 if self.bouquetChangeCB:
412                         self.bouquetChangeCB(1, self)
413
414         def prevBouquet(self):
415                 if self.bouquetChangeCB:
416                         self.bouquetChangeCB(-1, self)
417
418         def enterDateTime(self):
419                 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
420
421         def onDateTimeInputClosed(self, ret):
422                 if len(ret) > 1:
423                         if ret[0]:
424                                 self.ask_time=ret[1]
425                                 l = self["list"]
426                                 l.resetOffset()
427                                 l.fillMultiEPG(self.services, ret[1])
428                                 self.moveTimeLines(True)
429
430         def closeScreen(self):
431                 self.close(self.closeRecursive)
432
433         def infoKeyPressed(self):
434                 cur = self["list"].getCurrent()
435                 event = cur[0]
436                 service = cur[1]
437                 if event is not None:
438                         self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
439
440         def openSimilarList(self, eventid, refstr):
441                 self.session.open(EPGSelection, refstr, None, eventid)
442
443         def setServices(self, services):
444                 self.services = services
445                 self.onCreate()
446
447         #just used in multipeg
448         def onCreate(self):
449                 self["list"].fillMultiEPG(self.services, self.ask_time)
450                 self.moveTimeLines()
451
452         def eventViewCallback(self, setEvent, setService, val):
453                 l = self["list"]
454                 old = l.getCurrent()
455                 if val == -1:
456                         self.prevEvent(False)
457                 elif val == +1:
458                         self.nextEvent(False)
459                 cur = l.getCurrent()
460                 if cur[0] is None and cur[1].ref != old[1].ref:
461                         self.eventViewCallback(setEvent, setService, val)
462                 else:
463                         setService(cur[1])
464                         setEvent(cur[0])
465
466         def zapTo(self):
467                 if self.zapFunc and self["key_red"].getText() == "Zap":
468                         self.closeRecursive = True
469                         ref = self["list"].getCurrent()[1]
470                         self.zapFunc(ref.ref)
471
472         def eventSelected(self):
473                 self.infoKeyPressed()
474
475         def timerAdd(self):
476                 cur = self["list"].getCurrent()
477                 event = cur[0]
478                 serviceref = cur[1]
479                 if event is None:
480                         return
481                 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
482                 self.session.openWithCallback(self.timerEditFinished, TimerEntry, newEntry)
483
484         def timerEditFinished(self, answer):
485                 if answer[0]:
486                         self.session.nav.RecordTimer.record(answer[1])
487                 else:
488                         print "Timeredit aborted"
489
490         def onSelectionChanged(self):
491                 evt = self["list"].getCurrent()
492                 self["Event"].newEvent(evt and evt[0])
493                 if evt and evt[0]:
494                         evt = evt[0]
495                         now = time()
496                         start = evt.getBeginTime()
497                         end = start + evt.getDuration()
498                         if now >= start and now <= end:
499                                 self["key_red"].setText("Zap")
500                         else:
501                                 self["key_red"].setText("")
502
503         def moveTimeLines(self, force=False):
504                 l = self["list"]
505                 event_rect = l.getEventRect()
506                 time_epoch = l.getTimeEpoch()
507                 time_base = l.getTimeBase()
508                 if event_rect is None or time_epoch is None or time_base is None:
509                         return
510                 time_steps = time_epoch > 180 and 60 or 30
511                 num_lines = time_epoch/time_steps
512                 incWidth=event_rect.width()/num_lines
513                 pos=event_rect.left()
514                 timeline_entries = [ ]
515                 x = 0
516                 changecount = 0
517                 for line in self.time_lines:
518                         old_pos = line.position
519                         new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
520                         if not x or x >= num_lines:
521                                 line.visible = False
522                         else:
523                                 if old_pos != new_pos:
524                                         line.setPosition(new_pos[0], new_pos[1])
525                                         changecount += 1
526                                 line.visible = True
527                         if not x or line.visible:
528                                 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
529                         x += 1
530                         pos += incWidth
531
532                 if changecount or force:
533                         self["timeline_text"].setEntries(timeline_entries)
534
535                 now=time()
536                 timeline_now = self["timeline_now"]
537                 if now >= time_base and now < (time_base + time_epoch * 60):
538                         bla = (event_rect.width() * 1000) / time_epoch
539                         xpos = ((now/60) - (time_base/60)) * bla / 1000
540                         old_pos = timeline_now.position
541                         new_pos = (xpos+event_rect.left(), old_pos[1])
542                         if old_pos != new_pos:
543                                 timeline_now.setPosition(new_pos[0], new_pos[1])
544                         timeline_now.visible = True
545                 else:
546                         timeline_now.visible = False