1 from skin import queryColor
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.Sources.Clock import Clock
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 enigma import eEPGCache, eListbox, eListboxPythonMultiContent, gFont, loadPNG, \
20 RT_HALIGN_LEFT, RT_HALIGN_CENTER, RT_VALIGN_CENTER, RT_WRAP, eRect, eTimer
22 from time import localtime, time, strftime
24 class EPGList(HTMLComponent, GUIComponent):
25 def __init__(self, selChangedCB=None, timer = None, time_epoch = 120, overjump_empty=True):
27 self.cur_service = None
30 self.onSelChanged = [ ]
31 if selChangedCB is not None:
32 self.onSelChanged.append(selChangedCB)
33 GUIComponent.__init__(self)
34 self.l = eListboxPythonMultiContent()
35 self.l.setItemHeight(54);
36 self.l.setBuildFunc(self.buildEntry)
38 self.l.setSelectableFunc(self.isSelectable)
39 self.epgcache = eEPGCache.getInstance()
40 self.clock_pixmap = loadPNG(resolveFilename(SCOPE_SKIN_IMAGE, 'epgclock-fs8.png'))
42 self.time_epoch = time_epoch
44 self.event_rect = None
47 col = queryColor("GraphEpg.Foreground")
48 self.foreColor = col and col.argb()
50 col = queryColor("GraphEpg.Border")
51 self.borderColor = col and col.argb()
53 col = queryColor("GraphEpg.Background")
55 self.backColor = 0x586d88
57 self.backColor = col.argb()
59 col = queryColor("GraphEpg.BackgroundSelected")
61 self.backColorSelected = 0x808080
63 self.backColorSelected = col.argb()
65 def isSelectable(self, service, sname, event_list):
66 return (event_list and len(event_list) and True) or False
68 def setEpoch(self, epoch):
69 if self.cur_event is not None and self.cur_service is not None:
71 self.time_epoch = epoch
72 self.fillMultiEPG(None) # refill
74 def getEventFromId(self, service, eventid):
76 if self.epgcache is not None and eventid is not None:
77 event = self.epgcache.lookupEventId(service.ref, eventid)
81 if self.cur_service is None or self.cur_event is None:
83 old_service = self.cur_service #(service, service_name, events)
84 events = self.cur_service[2]
85 refstr = self.cur_service[0]
86 if not events or not len(events):
88 event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
90 service = ServiceReference(refstr)
91 event = self.getEventFromId(service, eventid)
92 return ( event, service )
94 def connectSelectionChanged(func):
95 if not self.onSelChanged.count(func):
96 self.onSelChanged.append(func)
98 def disconnectSelectionChanged(func):
99 self.onSelChanged.remove(func)
101 def serviceChanged(self):
102 cur_sel = self.l.getCurrentSelection()
106 def findBestEvent(self):
107 old_service = self.cur_service #(service, service_name, events)
108 cur_service = self.cur_service = self.l.getCurrentSelection()
110 if old_service and self.cur_event is not None:
111 events = old_service[2]
112 cur_event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
113 last_time = cur_event[2]
116 events = cur_service[2]
117 if events and len(events):
120 best = len(events) #set invalid
122 for event in events: #iterate all events
123 diff = abs(event[2]-last_time)
124 if (best == len(events)) or (diff < best_diff):
128 if best != len(events):
129 self.cur_event = best
131 self.cur_event = None
134 def selectionChanged(self):
135 for x in self.onSelChanged:
140 print "FIXME in EPGList.selectionChanged"
143 GUI_WIDGET = eListbox
145 def postWidgetCreate(self, instance):
146 instance.setWrapAround(True)
147 instance.selectionChanged.get().append(self.serviceChanged)
148 instance.setContent(self.l)
150 def recalcEntrySize(self):
151 esize = self.l.getItemSize()
152 self.l.setFont(0, gFont("Regular", 20))
153 self.l.setFont(1, gFont("Regular", 14))
154 width = esize.width()
155 height = esize.height()
158 self.service_rect = Rect(xpos, 0, w-10, height)
161 self.event_rect = Rect(xpos, 0, w, height)
162 self.l.setSelectionClip(eRect(0,0,0,0), False)
164 def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
165 xpos = (stime - start) * width / (end - start)
166 ewidth = (stime + duration - start) * width / (end - start)
171 if (xpos+ewidth) > width:
172 ewidth = width - xpos
175 def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
176 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
177 return xpos+event_rect.left(), width
179 def buildEntry(self, service, service_name, events):
182 res = [ None ] # no private data needed
183 res.append((eListboxPythonMultiContent.TYPE_TEXT, r1.left(), r1.top(), r1.width(), r1.height(), 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, service_name))
186 start = self.time_base+self.offs*self.time_epoch*60
187 end = start + self.time_epoch * 60
192 foreColor = self.foreColor
193 backColor = self.backColor
194 backColorSelected = self.backColorSelected
195 borderColor = self.borderColor
197 for ev in events: #(event_id, event_title, begin_time, duration)
198 rec=self.timer.isInTimer(ev[0], ev[2], ev[3], service) > ((ev[3]/10)*8)
199 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
200 if self.borderColor is None:
201 res.append((eListboxPythonMultiContent.TYPE_TEXT, left+xpos, top, ewidth, height, 1, RT_HALIGN_CENTER|RT_VALIGN_CENTER|RT_WRAP, ev[1], foreColor, backColor, backColorSelected, 1))
203 res.append((eListboxPythonMultiContent.TYPE_TEXT, left+xpos, top, ewidth, height, 1, RT_HALIGN_CENTER|RT_VALIGN_CENTER|RT_WRAP, ev[1], foreColor, backColor, backColorSelected, 1, borderColor))
204 if rec and ewidth > 23:
205 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, left+xpos+ewidth-22, top+height-22, 21, 21, self.clock_pixmap, backColor, backColorSelected))
208 def selEntry(self, dir, visible=True):
209 cur_service = self.cur_service #(service, service_name, events)
210 if not self.event_rect:
211 self.recalcEntrySize()
212 if cur_service and self.cur_event is not None:
214 entries = cur_service[2]
215 if dir == 0: #current
217 elif dir == +1: #next
218 if self.cur_event+1 < len(entries):
222 self.fillMultiEPG(None) # refill
224 elif dir == -1: #prev
225 if self.cur_event-1 >= 0:
229 self.fillMultiEPG(None) # refill
231 entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
232 time_base = self.time_base+self.offs*self.time_epoch*60
233 xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
234 self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
236 self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
237 self.selectionChanged()
240 def queryEPG(self, list, buildFunc=None):
241 if self.epgcache is not None:
242 if buildFunc is not None:
243 return self.epgcache.lookupEvent(list, buildFunc)
245 return self.epgcache.lookupEvent(list)
248 def fillMultiEPG(self, services, stime=-1):
250 time_base = self.time_base+self.offs*self.time_epoch*60
251 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
253 self.cur_event = None
254 self.cur_service = None
255 self.time_base = int(stime)
256 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
257 test.insert(0, 'RnITBD')
258 epg_data = self.queryEPG(test)
266 if tmp_list is not None:
267 self.list.append((service, sname, tmp_list[0][0] and tmp_list))
271 tmp_list.append((x[2], x[3], x[4], x[5]))
272 if tmp_list and len(tmp_list):
273 self.list.append((service, sname, tmp_list[0][0] and tmp_list))
275 self.l.setList(self.list)
278 def getEventRect(self):
280 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
282 def getTimeEpoch(self):
283 return self.time_epoch
285 def getTimeBase(self):
286 return self.time_base + (self.offs * self.time_epoch * 60)
288 def resetOffset(self):
291 class TimelineText(HTMLComponent, GUIComponent):
293 GUIComponent.__init__(self)
294 self.l = eListboxPythonMultiContent()
295 self.l.setSelectionClip(eRect(0,0,0,0))
296 self.l.setItemHeight(25);
297 self.l.setFont(0, gFont("Regular", 20))
299 GUI_WIDGET = eListbox
301 def postWidgetCreate(self, instance):
302 instance.setContent(self.l)
304 def setEntries(self, entries):
305 res = [ None ] # no private data needed
309 str = strftime("%H:%M", localtime(tm))
310 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
311 self.l.setList([res])
313 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
314 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
316 class GraphMultiEPG(Screen):
317 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
318 Screen.__init__(self, session)
319 self.bouquetChangeCB = bouquetChangeCB
322 self.ask_time = now - tmp
323 self.closeRecursive = False
324 self["key_red"] = Button("")
325 self["key_green"] = Button(_("Add timer"))
326 self["timeline_text"] = TimelineText()
327 self["Event"] = Event()
328 self["Clock"] = Clock()
329 self.time_lines = [ ]
330 for x in (0,1,2,3,4,5):
332 self.time_lines.append(pm)
333 self["timeline%d"%(x)] = pm
334 self["timeline_now"] = Pixmap()
335 self.services = services
336 self.zapFunc = zapFunc
338 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
340 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
342 "cancel": self.closeScreen,
343 "ok": self.eventSelected,
344 "timerAdd": self.timerAdd,
345 "info": self.infoKeyPressed,
347 "input_date_time": self.enterDateTime,
348 "nextBouquet": self.nextBouquet,
349 "prevBouquet": self.prevBouquet,
351 self["actions"].csel = self
353 self["input_actions"] = ActionMap(["InputActions"],
355 "left": self.leftPressed,
356 "right": self.rightPressed,
364 self.updateTimelineTimer = eTimer()
365 self.updateTimelineTimer.timeout.get().append(self.moveTimeLines)
366 self.updateTimelineTimer.start(60*1000)
367 self.onLayoutFinish.append(self.onCreate)
369 def leftPressed(self):
372 def rightPressed(self):
375 def nextEvent(self, visible=True):
376 ret = self["list"].selEntry(+1, visible)
378 self.moveTimeLines(True)
380 def prevEvent(self, visible=True):
381 ret = self["list"].selEntry(-1, visible)
383 self.moveTimeLines(True)
386 self["list"].setEpoch(60)
387 config.misc.graph_mepg_prev_time_period.value = 60
391 self["list"].setEpoch(120)
392 config.misc.graph_mepg_prev_time_period.value = 120
396 self["list"].setEpoch(180)
397 config.misc.graph_mepg_prev_time_period.value = 180
401 self["list"].setEpoch(240)
402 config.misc.graph_mepg_prev_time_period.value = 240
406 self["list"].setEpoch(300)
407 config.misc.graph_mepg_prev_time_period.value = 300
410 def nextBouquet(self):
411 if self.bouquetChangeCB:
412 self.bouquetChangeCB(1, self)
414 def prevBouquet(self):
415 if self.bouquetChangeCB:
416 self.bouquetChangeCB(-1, self)
418 def enterDateTime(self):
419 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
421 def onDateTimeInputClosed(self, ret):
427 l.fillMultiEPG(self.services, ret[1])
428 self.moveTimeLines(True)
430 def closeScreen(self):
431 self.close(self.closeRecursive)
433 def infoKeyPressed(self):
434 cur = self["list"].getCurrent()
437 if event is not None:
438 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
440 def openSimilarList(self, eventid, refstr):
441 self.session.open(EPGSelection, refstr, None, eventid)
443 def setServices(self, services):
444 self.services = services
447 #just used in multipeg
449 self["list"].fillMultiEPG(self.services, self.ask_time)
452 def eventViewCallback(self, setEvent, setService, val):
456 self.prevEvent(False)
458 self.nextEvent(False)
460 if cur[0] is None and cur[1].ref != old[1].ref:
461 self.eventViewCallback(setEvent, setService, val)
467 if self.zapFunc and self["key_red"].getText() == "Zap":
468 self.closeRecursive = True
469 ref = self["list"].getCurrent()[1]
470 self.zapFunc(ref.ref)
472 def eventSelected(self):
473 self.infoKeyPressed()
476 cur = self["list"].getCurrent()
481 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
482 self.session.openWithCallback(self.timerEditFinished, TimerEntry, newEntry)
484 def timerEditFinished(self, answer):
486 self.session.nav.RecordTimer.record(answer[1])
488 print "Timeredit aborted"
490 def onSelectionChanged(self):
491 evt = self["list"].getCurrent()
492 self["Event"].newEvent(evt and evt[0])
496 start = evt.getBeginTime()
497 end = start + evt.getDuration()
498 if now >= start and now <= end:
499 self["key_red"].setText("Zap")
501 self["key_red"].setText("")
503 def moveTimeLines(self, force=False):
505 event_rect = l.getEventRect()
506 time_epoch = l.getTimeEpoch()
507 time_base = l.getTimeBase()
508 if event_rect is None or time_epoch is None or time_base is None:
510 time_steps = time_epoch > 180 and 60 or 30
511 num_lines = time_epoch/time_steps
512 incWidth=event_rect.width()/num_lines
513 pos=event_rect.left()
514 timeline_entries = [ ]
517 for line in self.time_lines:
518 old_pos = line.position
519 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
520 if not x or x >= num_lines:
523 if old_pos != new_pos:
524 line.setPosition(new_pos[0], new_pos[1])
527 if not x or line.visible:
528 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
532 if changecount or force:
533 self["timeline_text"].setEntries(timeline_entries)
536 timeline_now = self["timeline_now"]
537 if now >= time_base and now < (time_base + time_epoch * 60):
538 bla = (event_rect.width() * 1000) / time_epoch
539 xpos = ((now/60) - (time_base/60)) * bla / 1000
540 old_pos = timeline_now.position
541 new_pos = (xpos+event_rect.left(), old_pos[1])
542 if old_pos != new_pos:
543 timeline_now.setPosition(new_pos[0], new_pos[1])
544 timeline_now.visible = True
546 timeline_now.visible = False