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