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