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
21 from time import localtime, time, strftime
23 class EPGList(HTMLComponent, GUIComponent):
24 def __init__(self, selChangedCB=None, timer = None, time_epoch = 120, overjump_empty=True):
26 self.cur_service = None
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)
37 self.l.setSelectableFunc(self.isSelectable)
38 self.epgcache = eEPGCache.getInstance()
39 self.clock_pixmap = loadPNG(resolveFilename(SCOPE_SKIN_IMAGE, 'epgclock-fs8.png'))
41 self.time_epoch = time_epoch
43 self.event_rect = None
45 def isSelectable(self, service, sname, event_list):
46 return (event_list and len(event_list) and True) or False
48 def setEpoch(self, epoch):
49 if self.cur_event is not None and self.cur_service is not None:
51 self.time_epoch = epoch
52 self.fillMultiEPG(None) # refill
54 def getEventFromId(self, service, eventid):
56 if self.epgcache is not None and eventid is not None:
57 event = self.epgcache.lookupEventId(service.ref, eventid)
61 if self.cur_service is None or self.cur_event is 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):
68 event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
70 service = ServiceReference(refstr)
71 event = self.getEventFromId(service, eventid)
72 return ( event, service )
74 def connectSelectionChanged(func):
75 if not self.onSelChanged.count(func):
76 self.onSelChanged.append(func)
78 def disconnectSelectionChanged(func):
79 self.onSelChanged.remove(func)
81 def serviceChanged(self):
82 cur_sel = self.l.getCurrentSelection()
86 def findBestEvent(self):
87 old_service = self.cur_service #(service, service_name, events)
88 cur_service = self.cur_service = self.l.getCurrentSelection()
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]
96 events = cur_service[2]
97 if events and len(events):
100 best = len(events) #set invalid
102 for event in events: #iterate all events
103 diff = abs(event[2]-last_time)
104 if (best == len(events)) or (diff < best_diff):
108 if best != len(events):
109 self.cur_event = best
111 self.cur_event = None
114 def selectionChanged(self):
115 for x in self.onSelChanged:
120 print "FIXME in EPGList.selectionChanged"
123 GUI_WIDGET = eListbox
125 def postWidgetCreate(self, instance):
126 instance.setWrapAround(True)
127 instance.selectionChanged.get().append(self.serviceChanged)
128 instance.setContent(self.l)
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()
138 self.service_rect = Rect(xpos, 0, w-10, height)
141 self.event_rect = Rect(xpos, 0, w, height)
142 self.l.setSelectionClip(eRect(xpos, 0, w, height), False)
144 def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
145 xpos = (stime - start) * width / (end - start)
146 ewidth = (stime + duration - start) * width / (end - start)
151 if (xpos+ewidth) > width:
152 ewidth = width - xpos
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
159 def buildEntry(self, service, service_name, events):
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
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))
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:
185 entries = cur_service[2]
186 if dir == 0: #current
188 elif dir == +1: #next
189 if self.cur_event+1 < len(entries):
193 self.fillMultiEPG(None) # refill
195 elif dir == -1: #prev
196 if self.cur_event-1 >= 0:
200 self.fillMultiEPG(None) # refill
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)
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()
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)
216 return self.epgcache.lookupEvent(list)
219 def fillMultiEPG(self, services, stime=-1):
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 ]
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)
237 if tmp_list is not None:
238 self.list.append((service, sname, tmp_list[0][0] and 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))
246 self.l.setList(self.list)
249 def getEventRect(self):
250 return self.event_rect
252 def getTimeEpoch(self):
253 return self.time_epoch
255 def getTimeBase(self):
256 return self.time_base + (self.offs * self.time_epoch * 60)
258 def resetOffset(self):
261 class TimelineText(HTMLComponent, GUIComponent):
263 GUIComponent.__init__(self)
264 self.l = eListboxPythonMultiContent()
265 self.l.setSelectionClip(eRect(0,0,0,0))
266 self.l.setItemHeight(25);
267 self.l.setFont(0, gFont("Regular", 20))
269 GUI_WIDGET = eListbox
271 def postWidgetCreate(self, instance):
272 instance.setContent(self.l)
274 def setEntries(self, entries):
275 res = [ None ] # no private data needed
279 str = strftime("%H:%M", localtime(tm))
280 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
281 self.l.setList([res])
283 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
284 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
286 class GraphMultiEPG(Screen):
287 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
288 Screen.__init__(self, session)
289 self.bouquetChangeCB = bouquetChangeCB
292 self.ask_time = now - tmp
293 self.closeRecursive = False
294 self["key_red"] = Button("")
295 self["key_green"] = Button(_("Add timer"))
296 self["timeline_text"] = TimelineText()
297 self["Event"] = Event()
298 self["Clock"] = Clock()
299 self.time_lines = [ ]
300 for x in (0,1,2,3,4,5):
302 self.time_lines.append(pm)
303 self["timeline%d"%(x)] = pm
304 self["timeline_now"] = Pixmap()
305 self.services = services
306 self.zapFunc = zapFunc
308 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
310 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
312 "cancel": self.closeScreen,
313 "ok": self.eventSelected,
314 "timerAdd": self.timerAdd,
315 "info": self.infoKeyPressed,
317 "input_date_time": self.enterDateTime,
318 "nextBouquet": self.nextBouquet,
319 "prevBouquet": self.prevBouquet,
321 self["actions"].csel = self
323 self["input_actions"] = ActionMap(["InputActions"],
325 "left": self.leftPressed,
326 "right": self.rightPressed,
334 self.updateTimelineTimer = eTimer()
335 self.updateTimelineTimer.timeout.get().append(self.moveTimeLines)
336 self.updateTimelineTimer.start(60*1000)
337 self.onLayoutFinish.append(self.onCreate)
339 def leftPressed(self):
342 def rightPressed(self):
345 def nextEvent(self, visible=True):
346 ret = self["list"].selEntry(+1, visible)
348 self.moveTimeLines(True)
350 def prevEvent(self, visible=True):
351 ret = self["list"].selEntry(-1, visible)
353 self.moveTimeLines(True)
356 self["list"].setEpoch(60)
357 config.misc.graph_mepg_prev_time_period.value = 60
361 self["list"].setEpoch(120)
362 config.misc.graph_mepg_prev_time_period.value = 120
366 self["list"].setEpoch(180)
367 config.misc.graph_mepg_prev_time_period.value = 180
371 self["list"].setEpoch(240)
372 config.misc.graph_mepg_prev_time_period.value = 240
376 self["list"].setEpoch(300)
377 config.misc.graph_mepg_prev_time_period.value = 300
380 def nextBouquet(self):
381 if self.bouquetChangeCB:
382 self.bouquetChangeCB(1, self)
384 def prevBouquet(self):
385 if self.bouquetChangeCB:
386 self.bouquetChangeCB(-1, self)
388 def enterDateTime(self):
389 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
391 def onDateTimeInputClosed(self, ret):
397 l.fillMultiEPG(self.services, ret[1])
398 self.moveTimeLines(True)
400 def closeScreen(self):
401 self.close(self.closeRecursive)
403 def infoKeyPressed(self):
404 cur = self["list"].getCurrent()
407 if event is not None:
408 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
410 def openSimilarList(self, eventid, refstr):
411 self.session.open(EPGSelection, refstr, None, eventid)
413 def setServices(self, services):
414 self.services = services
417 #just used in multipeg
419 self["list"].fillMultiEPG(self.services, self.ask_time)
422 def eventViewCallback(self, setEvent, setService, val):
426 self.prevEvent(False)
428 self.nextEvent(False)
430 if cur[0] is None and cur[1].ref != old[1].ref:
431 self.eventViewCallback(setEvent, setService, val)
437 if self.zapFunc and self["key_red"].getText() == "Zap":
438 self.closeRecursive = True
439 ref = self["list"].getCurrent()[1]
440 self.zapFunc(ref.ref)
442 def eventSelected(self):
443 self.infoKeyPressed()
446 cur = self["list"].getCurrent()
451 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
452 self.session.openWithCallback(self.timerEditFinished, TimerEntry, newEntry)
454 def timerEditFinished(self, answer):
456 self.session.nav.RecordTimer.record(answer[1])
458 print "Timeredit aborted"
460 def onSelectionChanged(self):
461 evt = self["list"].getCurrent()
462 self["Event"].newEvent(evt and evt[0])
466 start = evt.getBeginTime()
467 end = start + evt.getDuration()
468 if now >= start and now <= end:
469 self["key_red"].setText("Zap")
471 self["key_red"].setText("")
473 def moveTimeLines(self, force=False):
475 event_rect = l.getEventRect()
476 time_epoch = l.getTimeEpoch()
477 time_base = l.getTimeBase()
478 if event_rect is None or time_epoch is None or time_base is None:
480 time_steps = time_epoch > 180 and 60 or 30
481 num_lines = time_epoch/time_steps
482 incWidth=event_rect.width()/num_lines
483 pos=event_rect.left()
484 timeline_entries = [ ]
487 for line in self.time_lines:
488 old_pos = line.position
489 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
490 if not x or x >= num_lines:
493 if old_pos != new_pos:
494 line.setPosition(new_pos[0], new_pos[1])
497 if not x or line.visible:
498 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
502 if changecount or force:
503 self["timeline_text"].setEntries(timeline_entries)
506 timeline_now = self["timeline_now"]
507 if now >= time_base and now < (time_base + time_epoch * 60):
508 bla = (event_rect.width() * 1000) / time_epoch
509 xpos = ((now/60) - (time_base/60)) * bla / 1000
510 old_pos = timeline_now.position
511 new_pos = (xpos+event_rect.left(), old_pos[1])
512 if old_pos != new_pos:
513 timeline_now.setPosition(new_pos[0], new_pos[1])
514 timeline_now.visible = True
516 timeline_now.visible = False