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