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 Tools.Directories import resolveFilename, SCOPE_SKIN_IMAGE
15 from RecordTimer import RecordTimerEntry, parseEvent
16 from ServiceReference import ServiceReference
17 from enigma import eEPGCache, eListbox, eListboxPythonMultiContent, gFont, loadPNG, \
18 RT_HALIGN_LEFT, RT_HALIGN_CENTER, RT_VALIGN_CENTER, RT_WRAP, eRect, eTimer
20 from time import localtime, time, strftime
22 class EPGList(HTMLComponent, GUIComponent):
23 def __init__(self, selChangedCB=None, timer = None, time_epoch = 120, overjump_empty=True):
25 self.cur_service = None
28 self.onSelChanged = [ ]
29 if selChangedCB is not None:
30 self.onSelChanged.append(selChangedCB)
31 GUIComponent.__init__(self)
32 self.l = eListboxPythonMultiContent()
33 self.l.setItemHeight(54);
34 self.l.setBuildFunc(self.buildEntry)
36 self.l.setSelectableFunc(self.isSelectable)
37 self.epgcache = eEPGCache.getInstance()
38 self.clock_pixmap = loadPNG(resolveFilename(SCOPE_SKIN_IMAGE, 'epgclock-fs8.png'))
40 self.time_epoch = time_epoch
42 self.entry_rect = None
44 def isSelectable(self, service, sname, event_list):
45 return (event_list and len(event_list) and True) or False
47 def setEpoch(self, epoch):
48 if self.cur_event is not None and self.cur_service is not None:
50 self.time_epoch = epoch
51 self.fillMultiEPG(None) # refill
53 def getEventFromId(self, service, eventid):
55 if self.epgcache is not None and eventid is not None:
56 event = self.epgcache.lookupEventId(service.ref, eventid)
60 if self.cur_service is None or self.cur_event is None:
62 old_service = self.cur_service #(service, service_name, events)
63 events = self.cur_service[2]
64 refstr = self.cur_service[0]
65 if not events or not len(events):
67 event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
69 service = ServiceReference(refstr)
70 event = self.getEventFromId(service, eventid)
71 return ( event, service )
73 def connectSelectionChanged(func):
74 if not self.onSelChanged.count(func):
75 self.onSelChanged.append(func)
77 def disconnectSelectionChanged(func):
78 self.onSelChanged.remove(func)
80 def serviceChanged(self):
81 cur_sel = self.l.getCurrentSelection()
85 def findBestEvent(self):
86 old_service = self.cur_service #(service, service_name, events)
87 cur_service = self.cur_service = self.l.getCurrentSelection()
89 if old_service and self.cur_event is not None:
90 events = old_service[2]
91 cur_event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
92 last_time = cur_event[2]
95 events = cur_service[2]
96 if events and len(events):
99 best = len(events) #set invalid
101 for event in events: #iterate all events
102 diff = abs(event[2]-last_time)
103 if (best == len(events)) or (diff < best_diff):
107 if best != len(events):
108 self.cur_event = best
110 self.cur_event = None
113 def selectionChanged(self):
114 for x in self.onSelChanged:
119 print "FIXME in EPGList.selectionChanged"
122 GUI_WIDGET = eListbox
124 def postWidgetCreate(self, instance):
125 instance.setWrapAround(True)
126 instance.selectionChanged.get().append(self.serviceChanged)
127 instance.setContent(self.l)
129 def recalcEntrySize(self):
130 esize = self.l.getItemSize()
131 self.l.setFont(0, gFont("Regular", 20))
132 self.l.setFont(1, gFont("Regular", 14))
133 width = esize.width()
134 height = esize.height()
137 self.service_rect = Rect(xpos, 0, w-10, height)
140 self.event_rect = Rect(xpos, 0, w, height)
141 self.l.setSelectionClip(eRect(xpos, 0, w, height), False)
143 def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
144 xpos = (stime - start) * width / (end - start)
145 ewidth = (stime + duration - start) * width / (end - start)
150 if (xpos+ewidth) > width:
151 ewidth = width - xpos
154 def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
155 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
156 return xpos+event_rect.left(), width
158 def buildEntry(self, service, service_name, events):
161 res = [ None ] # no private data needed
162 res.append((eListboxPythonMultiContent.TYPE_TEXT, r1.left(), r1.top(), r1.width(), r1.height(), 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, service_name))
163 start = self.time_base+self.offs*self.time_epoch*60
164 end = start + self.time_epoch * 60
170 for ev in events: #(event_id, event_title, begin_time, duration)
171 rec=self.timer.isInTimer(ev[0], ev[2], ev[3], service) > ((ev[3]/10)*8)
172 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
173 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))
174 if rec and ewidth > 23:
175 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, left+xpos+ewidth-22, top+height-22, 21, 21, self.clock_pixmap, 0x586d88, 0x808080))
178 def selEntry(self, dir):
179 cur_service = self.cur_service #(service, service_name, events)
180 if not self.entry_rect:
181 self.recalcEntrySize()
182 if cur_service and self.cur_event is not None:
184 entries = cur_service[2]
185 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()), 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()
210 def queryEPG(self, list, buildFunc=None):
211 if self.epgcache is not None:
212 if buildFunc is not None:
213 return self.epgcache.lookupEvent(list, buildFunc)
215 return self.epgcache.lookupEvent(list)
218 def fillMultiEPG(self, services, stime=-1):
220 time_base = self.time_base+self.offs*self.time_epoch*60
221 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
223 self.cur_event = None
224 self.cur_service = None
225 self.time_base = int(stime)
226 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
227 test.insert(0, 'RnITBD')
228 epg_data = self.queryEPG(test)
236 if tmp_list is not None:
237 self.list.append((service, sname, tmp_list[0][0] and tmp_list))
241 tmp_list.append((x[2], x[3], x[4], x[5]))
242 if tmp_list and len(tmp_list):
243 self.list.append((service, sname, tmp_list[0][0] and tmp_list))
245 self.l.setList(self.list)
248 def getEventRect(self):
249 return self.event_rect
251 def getTimeEpoch(self):
252 return self.time_epoch
254 def getTimeBase(self):
255 return self.time_base
257 class TimelineText(HTMLComponent, GUIComponent):
259 GUIComponent.__init__(self)
260 self.l = eListboxPythonMultiContent()
261 self.l.setSelectionClip(eRect(0,0,0,0))
262 self.l.setItemHeight(25);
263 self.l.setFont(0, gFont("Regular", 20))
265 GUI_WIDGET = eListbox
267 def postWidgetCreate(self, instance):
268 instance.setContent(self.l)
270 def setEntries(self, entries):
271 res = [ None ] # no private data needed
275 str = strftime("%H:%M", localtime(tm))
276 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
277 self.l.setList([res])
279 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
280 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
282 class GraphMultiEPG(Screen):
283 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
284 Screen.__init__(self, session)
285 self.bouquetChangeCB = bouquetChangeCB
288 self.ask_time = now - tmp
289 self.closeRecursive = False
290 self["key_red"] = Button("")
291 self["key_green"] = Button(_("Add timer"))
292 self["timeline_text"] = TimelineText()
293 self["Event"] = Event()
294 self["Clock"] = Clock()
295 self.time_lines = [ ]
296 for x in (0,1,2,3,4,5):
298 self.time_lines.append(pm)
299 self["timeline%d"%(x)] = pm
300 self["timeline_now"] = Pixmap()
301 self.services = services
302 self.zapFunc = zapFunc
304 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
306 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
308 "cancel": self.closeScreen,
309 "ok": self.eventSelected,
310 "timerAdd": self.timerAdd,
311 "info": self.infoKeyPressed,
313 "input_date_time": self.enterDateTime,
314 "nextBouquet": self.nextBouquet,
315 "prevBouquet": self.prevBouquet,
317 self["actions"].csel = self
319 self["input_actions"] = ActionMap(["InputActions"],
321 "left": self.prevEvent,
322 "right": self.nextEvent,
330 self.updateTimelineTimer = eTimer()
331 self.updateTimelineTimer.timeout.get().append(self.moveTimeLines)
332 self.updateTimelineTimer.start(60*1000)
333 self.onLayoutFinish.append(self.onCreate)
336 self["list"].selEntry(+1)
339 self["list"].selEntry(-1)
342 self["list"].setEpoch(60)
343 config.misc.graph_mepg_prev_time_period.value = 60
347 self["list"].setEpoch(120)
348 config.misc.graph_mepg_prev_time_period.value = 120
352 self["list"].setEpoch(180)
353 config.misc.graph_mepg_prev_time_period.value = 180
357 self["list"].setEpoch(240)
358 config.misc.graph_mepg_prev_time_period.value = 240
362 self["list"].setEpoch(300)
363 config.misc.graph_mepg_prev_time_period.value = 300
366 def nextBouquet(self):
367 if self.bouquetChangeCB:
368 self.bouquetChangeCB(1, self)
370 def prevBouquet(self):
371 if self.bouquetChangeCB:
372 self.bouquetChangeCB(-1, self)
374 def enterDateTime(self):
375 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
377 def onDateTimeInputClosed(self, ret):
381 self["list"].fillMultiEPG(self.services, ret[1])
384 def closeScreen(self):
385 self.close(self.closeRecursive)
387 def infoKeyPressed(self):
388 cur = self["list"].getCurrent()
391 if event is not None:
392 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
394 def openSimilarList(self, eventid, refstr):
395 self.session.open(EPGSelection, refstr, None, eventid)
397 def setServices(self, services):
398 self.services = services
401 #just used in multipeg
403 self["list"].fillMultiEPG(self.services, self.ask_time)
406 def eventViewCallback(self, setEvent, setService, val):
414 if cur[0] is None and cur[1].ref != old[1].ref:
415 self.eventViewCallback(setEvent, setService, val)
421 if self.zapFunc and self["key_red"].getText() == "Zap":
422 self.closeRecursive = True
423 ref = self["list"].getCurrent()[1]
424 self.zapFunc(ref.ref)
426 def eventSelected(self):
427 self.infoKeyPressed()
430 cur = self["list"].getCurrent()
435 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
436 self.session.openWithCallback(self.timerEditFinished, TimerEntry, newEntry)
438 def timerEditFinished(self, answer):
440 self.session.nav.RecordTimer.record(answer[1])
442 print "Timeredit aborted"
444 def onSelectionChanged(self):
445 evt = self["list"].getCurrent()
446 self["Event"].newEvent(evt and evt[0])
450 start = evt.getBeginTime()
451 end = start + evt.getDuration()
452 if now >= start and now <= end:
453 self["key_red"].setText("Zap")
455 self["key_red"].setText("")
457 def moveTimeLines(self):
459 event_rect = l.getEventRect()
460 time_epoch = l.getTimeEpoch()
461 time_base = l.getTimeBase()
462 if event_rect is None or time_epoch is None or time_base is None:
464 time_steps = time_epoch > 180 and 60 or 30
465 num_lines = time_epoch/time_steps
466 incWidth=event_rect.width()/num_lines
467 pos=event_rect.left()
468 timeline_entries = [ ]
471 for line in self.time_lines:
472 old_pos = line.position
473 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
474 if not x or x >= num_lines:
477 if old_pos != new_pos:
478 line.setPosition(new_pos[0], new_pos[1])
481 if not x or line.visible:
482 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
487 self["timeline_text"].setEntries(timeline_entries)
490 timeline_now = self["timeline_now"]
491 if now >= time_base and now < (time_base + time_epoch * 60):
492 bla = (event_rect.width() * 1000) / time_epoch
493 xpos = ((now/60) - (time_base/60)) * bla / 1000
494 old_pos = timeline_now.position
495 new_pos = (xpos+event_rect.left(), old_pos[1])
496 if old_pos != new_pos:
497 timeline_now.setPosition(new_pos[0], new_pos[1])
498 timeline_now.visible = True
500 timeline_now.visible = False