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