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