1 from skin import parseColor
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.Source import ObsoleteSource
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 self.borderColor = None
48 self.backColor = 0x586d88
49 self.backColorSelected = 0x808080
51 def applySkin(self, desktop):
52 if self.skinAttributes is not None:
54 for (attrib, value) in self.skinAttributes:
55 if attrib == "EntryForegroundColor":
56 self.foreColor = parseColor(value).argb()
57 elif attrib == "EntryBorderColor":
58 self.borderColor = parseColor(value).argb()
59 elif attrib == "EntryBackgroundColor":
60 self.backColor = parseColor(value).argb()
61 elif attrib == "EntryBackgroundColorSelected":
62 self.backColorSelected = parseColor(value).argb()
64 attribs.append((attrib,value))
65 self.skinAttributes = attribs
66 return GUIComponent.applySkin(self, desktop)
68 def isSelectable(self, service, sname, event_list):
69 return (event_list and len(event_list) and True) or False
71 def setEpoch(self, epoch):
72 if self.cur_event is not None and self.cur_service is not None:
74 self.time_epoch = epoch
75 self.fillMultiEPG(None) # refill
77 def getEventFromId(self, service, eventid):
79 if self.epgcache is not None and eventid is not None:
80 event = self.epgcache.lookupEventId(service.ref, eventid)
84 if self.cur_service is None or self.cur_event is None:
86 old_service = self.cur_service #(service, service_name, events)
87 events = self.cur_service[2]
88 refstr = self.cur_service[0]
89 if not events or not len(events):
91 event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
93 service = ServiceReference(refstr)
94 event = self.getEventFromId(service, eventid)
95 return ( event, service )
97 def connectSelectionChanged(func):
98 if not self.onSelChanged.count(func):
99 self.onSelChanged.append(func)
101 def disconnectSelectionChanged(func):
102 self.onSelChanged.remove(func)
104 def serviceChanged(self):
105 cur_sel = self.l.getCurrentSelection()
109 def findBestEvent(self):
110 old_service = self.cur_service #(service, service_name, events)
111 cur_service = self.cur_service = self.l.getCurrentSelection()
113 if old_service and self.cur_event is not None:
114 events = old_service[2]
115 cur_event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
116 last_time = cur_event[2]
119 events = cur_service[2]
120 if events and len(events):
123 best = len(events) #set invalid
125 for event in events: #iterate all events
126 diff = abs(event[2]-last_time)
127 if (best == len(events)) or (diff < best_diff):
131 if best != len(events):
132 self.cur_event = best
134 self.cur_event = None
137 def selectionChanged(self):
138 for x in self.onSelChanged:
143 print "FIXME in EPGList.selectionChanged"
146 GUI_WIDGET = eListbox
148 def postWidgetCreate(self, instance):
149 instance.setWrapAround(True)
150 instance.selectionChanged.get().append(self.serviceChanged)
151 instance.setContent(self.l)
153 def recalcEntrySize(self):
154 esize = self.l.getItemSize()
155 self.l.setFont(0, gFont("Regular", 20))
156 self.l.setFont(1, gFont("Regular", 14))
157 width = esize.width()
158 height = esize.height()
161 self.service_rect = Rect(xpos, 0, w-10, height)
164 self.event_rect = Rect(xpos, 0, w, height)
165 self.l.setSelectionClip(eRect(0,0,0,0), False)
167 def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
168 xpos = (stime - start) * width / (end - start)
169 ewidth = (stime + duration - start) * width / (end - start)
174 if (xpos+ewidth) > width:
175 ewidth = width - xpos
178 def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
179 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
180 return xpos+event_rect.left(), width
182 def buildEntry(self, service, service_name, events):
185 res = [ None ] # no private data needed
186 res.append((eListboxPythonMultiContent.TYPE_TEXT, r1.left(), r1.top(), r1.width(), r1.height(), 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, service_name))
189 start = self.time_base+self.offs*self.time_epoch*60
190 end = start + self.time_epoch * 60
195 foreColor = self.foreColor
196 backColor = self.backColor
197 backColorSelected = self.backColorSelected
198 borderColor = self.borderColor
200 for ev in events: #(event_id, event_title, begin_time, duration)
201 rec=self.timer.isInTimer(ev[0], ev[2], ev[3], service) > ((ev[3]/10)*8)
202 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
203 if self.borderColor is None:
204 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))
206 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))
207 if rec and ewidth > 23:
208 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, left+xpos+ewidth-22, top+height-22, 21, 21, self.clock_pixmap, backColor, backColorSelected))
211 def selEntry(self, dir, visible=True):
212 cur_service = self.cur_service #(service, service_name, events)
213 if not self.event_rect:
214 self.recalcEntrySize()
215 if cur_service and self.cur_event is not None:
217 entries = cur_service[2]
218 if dir == 0: #current
220 elif dir == +1: #next
221 if self.cur_event+1 < len(entries):
225 self.fillMultiEPG(None) # refill
227 elif dir == -1: #prev
228 if self.cur_event-1 >= 0:
232 self.fillMultiEPG(None) # refill
234 entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
235 time_base = self.time_base+self.offs*self.time_epoch*60
236 xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
237 self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
239 self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
240 self.selectionChanged()
243 def queryEPG(self, list, buildFunc=None):
244 if self.epgcache is not None:
245 if buildFunc is not None:
246 return self.epgcache.lookupEvent(list, buildFunc)
248 return self.epgcache.lookupEvent(list)
251 def fillMultiEPG(self, services, stime=-1):
253 time_base = self.time_base+self.offs*self.time_epoch*60
254 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
256 self.cur_event = None
257 self.cur_service = None
258 self.time_base = int(stime)
259 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
260 test.insert(0, 'RnITBD')
261 epg_data = self.queryEPG(test)
269 if tmp_list is not None:
270 self.list.append((service, sname, tmp_list[0][0] and tmp_list))
274 tmp_list.append((x[2], x[3], x[4], x[5]))
275 if tmp_list and len(tmp_list):
276 self.list.append((service, sname, tmp_list[0][0] and tmp_list))
278 self.l.setList(self.list)
281 def getEventRect(self):
283 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
285 def getTimeEpoch(self):
286 return self.time_epoch
288 def getTimeBase(self):
289 return self.time_base + (self.offs * self.time_epoch * 60)
291 def resetOffset(self):
294 class TimelineText(HTMLComponent, GUIComponent):
296 GUIComponent.__init__(self)
297 self.l = eListboxPythonMultiContent()
298 self.l.setSelectionClip(eRect(0,0,0,0))
299 self.l.setItemHeight(25);
300 self.l.setFont(0, gFont("Regular", 20))
302 GUI_WIDGET = eListbox
304 def postWidgetCreate(self, instance):
305 instance.setContent(self.l)
307 def setEntries(self, entries):
308 res = [ None ] # no private data needed
312 str = strftime("%H:%M", localtime(tm))
313 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
314 self.l.setList([res])
316 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
317 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
319 class GraphMultiEPG(Screen):
320 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
321 Screen.__init__(self, session)
322 self.bouquetChangeCB = bouquetChangeCB
325 self.ask_time = now - tmp
326 self.closeRecursive = False
327 self["key_red"] = Button("")
328 self["key_green"] = Button(_("Add timer"))
329 self["timeline_text"] = TimelineText()
330 self["Event"] = Event()
331 self["Clock"] = ObsoleteSource(new_source = "global.CurrentTime", removal_date = "2008-01")
332 self.time_lines = [ ]
333 for x in (0,1,2,3,4,5):
335 self.time_lines.append(pm)
336 self["timeline%d"%(x)] = pm
337 self["timeline_now"] = Pixmap()
338 self.services = services
339 self.zapFunc = zapFunc
341 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
343 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
345 "cancel": self.closeScreen,
346 "ok": self.eventSelected,
347 "timerAdd": self.timerAdd,
348 "info": self.infoKeyPressed,
350 "input_date_time": self.enterDateTime,
351 "nextBouquet": self.nextBouquet,
352 "prevBouquet": self.prevBouquet,
354 self["actions"].csel = self
356 self["input_actions"] = ActionMap(["InputActions"],
358 "left": self.leftPressed,
359 "right": self.rightPressed,
367 self.updateTimelineTimer = eTimer()
368 self.updateTimelineTimer.timeout.get().append(self.moveTimeLines)
369 self.updateTimelineTimer.start(60*1000)
370 self.onLayoutFinish.append(self.onCreate)
372 def leftPressed(self):
375 def rightPressed(self):
378 def nextEvent(self, visible=True):
379 ret = self["list"].selEntry(+1, visible)
381 self.moveTimeLines(True)
383 def prevEvent(self, visible=True):
384 ret = self["list"].selEntry(-1, visible)
386 self.moveTimeLines(True)
389 self["list"].setEpoch(60)
390 config.misc.graph_mepg_prev_time_period.value = 60
394 self["list"].setEpoch(120)
395 config.misc.graph_mepg_prev_time_period.value = 120
399 self["list"].setEpoch(180)
400 config.misc.graph_mepg_prev_time_period.value = 180
404 self["list"].setEpoch(240)
405 config.misc.graph_mepg_prev_time_period.value = 240
409 self["list"].setEpoch(300)
410 config.misc.graph_mepg_prev_time_period.value = 300
413 def nextBouquet(self):
414 if self.bouquetChangeCB:
415 self.bouquetChangeCB(1, self)
417 def prevBouquet(self):
418 if self.bouquetChangeCB:
419 self.bouquetChangeCB(-1, self)
421 def enterDateTime(self):
422 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
424 def onDateTimeInputClosed(self, ret):
430 l.fillMultiEPG(self.services, ret[1])
431 self.moveTimeLines(True)
433 def closeScreen(self):
434 self.close(self.closeRecursive)
436 def infoKeyPressed(self):
437 cur = self["list"].getCurrent()
440 if event is not None:
441 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
443 def openSimilarList(self, eventid, refstr):
444 self.session.open(EPGSelection, refstr, None, eventid)
446 def setServices(self, services):
447 self.services = services
450 #just used in multipeg
452 self["list"].fillMultiEPG(self.services, self.ask_time)
455 def eventViewCallback(self, setEvent, setService, val):
459 self.prevEvent(False)
461 self.nextEvent(False)
463 if cur[0] is None and cur[1].ref != old[1].ref:
464 self.eventViewCallback(setEvent, setService, val)
470 if self.zapFunc and self["key_red"].getText() == "Zap":
471 self.closeRecursive = True
472 ref = self["list"].getCurrent()[1]
473 self.zapFunc(ref.ref)
475 def eventSelected(self):
476 self.infoKeyPressed()
479 cur = self["list"].getCurrent()
484 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
485 self.session.openWithCallback(self.timerEditFinished, TimerEntry, newEntry)
487 def timerEditFinished(self, answer):
489 self.session.nav.RecordTimer.record(answer[1])
491 print "Timeredit aborted"
493 def onSelectionChanged(self):
494 evt = self["list"].getCurrent()
495 self["Event"].newEvent(evt and evt[0])
499 start = evt.getBeginTime()
500 end = start + evt.getDuration()
501 if now >= start and now <= end:
502 self["key_red"].setText("Zap")
504 self["key_red"].setText("")
506 def moveTimeLines(self, force=False):
508 event_rect = l.getEventRect()
509 time_epoch = l.getTimeEpoch()
510 time_base = l.getTimeBase()
511 if event_rect is None or time_epoch is None or time_base is None:
513 time_steps = time_epoch > 180 and 60 or 30
514 num_lines = time_epoch/time_steps
515 incWidth=event_rect.width()/num_lines
516 pos=event_rect.left()
517 timeline_entries = [ ]
520 for line in self.time_lines:
521 old_pos = line.position
522 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
523 if not x or x >= num_lines:
526 if old_pos != new_pos:
527 line.setPosition(new_pos[0], new_pos[1])
530 if not x or line.visible:
531 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
535 if changecount or force:
536 self["timeline_text"].setEntries(timeline_entries)
539 timeline_now = self["timeline_now"]
540 if now >= time_base and now < (time_base + time_epoch * 60):
541 bla = (event_rect.width() * 1000) / time_epoch
542 xpos = ((now/60) - (time_base/60)) * bla / 1000
543 old_pos = timeline_now.position
544 new_pos = (xpos+event_rect.left(), old_pos[1])
545 if old_pos != new_pos:
546 timeline_now.setPosition(new_pos[0], new_pos[1])
547 timeline_now.visible = True
549 timeline_now.visible = False