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.entry_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.entry_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 class TimelineText(HTMLComponent, GUIComponent):
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))
266 GUI_WIDGET = eListbox
268 def postWidgetCreate(self, instance):
269 instance.setContent(self.l)
271 def setEntries(self, entries):
272 res = [ None ] # no private data needed
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])
280 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
281 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
283 class GraphMultiEPG(Screen):
284 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
285 Screen.__init__(self, session)
286 self.bouquetChangeCB = bouquetChangeCB
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):
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
305 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
307 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
309 "cancel": self.closeScreen,
310 "ok": self.eventSelected,
311 "timerAdd": self.timerAdd,
312 "info": self.infoKeyPressed,
314 "input_date_time": self.enterDateTime,
315 "nextBouquet": self.nextBouquet,
316 "prevBouquet": self.prevBouquet,
318 self["actions"].csel = self
320 self["input_actions"] = ActionMap(["InputActions"],
322 "left": self.leftPressed,
323 "right": self.rightPressed,
331 self.updateTimelineTimer = eTimer()
332 self.updateTimelineTimer.timeout.get().append(self.moveTimeLines)
333 self.updateTimelineTimer.start(60*1000)
334 self.onLayoutFinish.append(self.onCreate)
336 def leftPressed(self):
339 def rightPressed(self):
342 def nextEvent(self, visible=True):
343 ret = self["list"].selEntry(+1, visible)
345 self.moveTimeLines(True)
347 def prevEvent(self, visible=True):
348 ret = self["list"].selEntry(-1, visible)
350 self.moveTimeLines(True)
353 self["list"].setEpoch(60)
354 config.misc.graph_mepg_prev_time_period.value = 60
358 self["list"].setEpoch(120)
359 config.misc.graph_mepg_prev_time_period.value = 120
363 self["list"].setEpoch(180)
364 config.misc.graph_mepg_prev_time_period.value = 180
368 self["list"].setEpoch(240)
369 config.misc.graph_mepg_prev_time_period.value = 240
373 self["list"].setEpoch(300)
374 config.misc.graph_mepg_prev_time_period.value = 300
377 def nextBouquet(self):
378 if self.bouquetChangeCB:
379 self.bouquetChangeCB(1, self)
381 def prevBouquet(self):
382 if self.bouquetChangeCB:
383 self.bouquetChangeCB(-1, self)
385 def enterDateTime(self):
386 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
388 def onDateTimeInputClosed(self, ret):
392 self["list"].fillMultiEPG(self.services, ret[1])
395 def closeScreen(self):
396 self.close(self.closeRecursive)
398 def infoKeyPressed(self):
399 cur = self["list"].getCurrent()
402 if event is not None:
403 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
405 def openSimilarList(self, eventid, refstr):
406 self.session.open(EPGSelection, refstr, None, eventid)
408 def setServices(self, services):
409 self.services = services
412 #just used in multipeg
414 self["list"].fillMultiEPG(self.services, self.ask_time)
417 def eventViewCallback(self, setEvent, setService, val):
421 self.prevEvent(False)
423 self.nextEvent(False)
425 if cur[0] is None and cur[1].ref != old[1].ref:
426 self.eventViewCallback(setEvent, setService, val)
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)
437 def eventSelected(self):
438 self.infoKeyPressed()
441 cur = self["list"].getCurrent()
446 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
447 self.session.openWithCallback(self.timerEditFinished, TimerEntry, newEntry)
449 def timerEditFinished(self, answer):
451 self.session.nav.RecordTimer.record(answer[1])
453 print "Timeredit aborted"
455 def onSelectionChanged(self):
456 evt = self["list"].getCurrent()
457 self["Event"].newEvent(evt and evt[0])
461 start = evt.getBeginTime()
462 end = start + evt.getDuration()
463 if now >= start and now <= end:
464 self["key_red"].setText("Zap")
466 self["key_red"].setText("")
468 def moveTimeLines(self, force=False):
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:
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 = [ ]
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:
488 if old_pos != new_pos:
489 line.setPosition(new_pos[0], new_pos[1])
492 if not x or line.visible:
493 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
497 if changecount or force:
498 self["timeline_text"].setEntries(timeline_entries)
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
511 timeline_now.visible = False