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