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 Components.MultiContent import MultiContentEntryText, MultiContentEntryPixmapAlphaTest
12 from Screens.Screen import Screen
13 from Screens.EventView import EventViewSimple
14 from Screens.TimeDateInput import TimeDateInput
15 from Screens.TimerEntry import TimerEntry
16 from Screens.EpgSelection import EPGSelection
17 from Tools.Directories import resolveFilename, SCOPE_SKIN_IMAGE
18 from RecordTimer import RecordTimerEntry, parseEvent
19 from ServiceReference import ServiceReference
20 from enigma import eEPGCache, eListbox, gFont, loadPNG, eListboxPythonMultiContent, \
21 RT_HALIGN_LEFT, RT_HALIGN_CENTER, RT_VALIGN_CENTER, RT_WRAP, eRect, eTimer
23 from time import localtime, time, strftime
25 class EPGList(HTMLComponent, GUIComponent):
26 def __init__(self, selChangedCB=None, timer = None, time_epoch = 120, overjump_empty=True):
28 self.cur_service = None
31 self.onSelChanged = [ ]
32 if selChangedCB is not None:
33 self.onSelChanged.append(selChangedCB)
34 GUIComponent.__init__(self)
35 self.l = eListboxPythonMultiContent()
36 self.l.setItemHeight(54);
37 self.l.setBuildFunc(self.buildEntry)
39 self.l.setSelectableFunc(self.isSelectable)
40 self.epgcache = eEPGCache.getInstance()
41 self.clock_pixmap = loadPNG(resolveFilename(SCOPE_SKIN_IMAGE, 'epgclock-fs8.png'))
43 self.time_epoch = time_epoch
45 self.event_rect = None
48 self.foreColorSelected = None
49 self.borderColor = None
50 self.backColor = 0x586d88
51 self.backColorSelected = 0x808080
53 def applySkin(self, desktop):
54 if self.skinAttributes is not None:
56 for (attrib, value) in self.skinAttributes:
57 if attrib == "EntryForegroundColor":
58 self.foreColor = parseColor(value).argb()
59 elif attrib == "EntryForegroundColorSelected":
60 self.foreColorSelected = parseColor(value).argb()
61 elif attrib == "EntryBorderColor":
62 self.borderColor = parseColor(value).argb()
63 elif attrib == "EntryBackgroundColor":
64 self.backColor = parseColor(value).argb()
65 elif attrib == "EntryBackgroundColorSelected":
66 self.backColorSelected = parseColor(value).argb()
68 attribs.append((attrib,value))
69 self.skinAttributes = attribs
70 return GUIComponent.applySkin(self, desktop)
72 def isSelectable(self, service, sname, event_list):
73 return (event_list and len(event_list) and True) or False
75 def setEpoch(self, epoch):
76 if self.cur_event is not None and self.cur_service is not None:
78 self.time_epoch = epoch
79 self.fillMultiEPG(None) # refill
81 def getEventFromId(self, service, eventid):
83 if self.epgcache is not None and eventid is not None:
84 event = self.epgcache.lookupEventId(service.ref, eventid)
88 if self.cur_service is None or self.cur_event is None:
90 old_service = self.cur_service #(service, service_name, events)
91 events = self.cur_service[2]
92 refstr = self.cur_service[0]
93 if not events or not len(events):
95 event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
97 service = ServiceReference(refstr)
98 event = self.getEventFromId(service, eventid)
99 return ( event, service )
101 def connectSelectionChanged(func):
102 if not self.onSelChanged.count(func):
103 self.onSelChanged.append(func)
105 def disconnectSelectionChanged(func):
106 self.onSelChanged.remove(func)
108 def serviceChanged(self):
109 cur_sel = self.l.getCurrentSelection()
113 def findBestEvent(self):
114 old_service = self.cur_service #(service, service_name, events)
115 cur_service = self.cur_service = self.l.getCurrentSelection()
117 time_base = self.getTimeBase()
118 if old_service and self.cur_event is not None:
119 events = old_service[2]
120 cur_event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
121 last_time = cur_event[2]
122 if last_time < time_base:
123 last_time = time_base
126 events = cur_service[2]
127 if events and len(events):
130 best = len(events) #set invalid
132 for event in events: #iterate all events
134 if ev_time < time_base:
136 diff = abs(ev_time-last_time)
137 if (best == len(events)) or (diff < best_diff):
141 if best != len(events):
142 self.cur_event = best
144 self.cur_event = None
147 def selectionChanged(self):
148 for x in self.onSelChanged:
153 print "FIXME in EPGList.selectionChanged"
156 GUI_WIDGET = eListbox
158 def postWidgetCreate(self, instance):
159 instance.setWrapAround(True)
160 instance.selectionChanged.get().append(self.serviceChanged)
161 instance.setContent(self.l)
163 def recalcEntrySize(self):
164 esize = self.l.getItemSize()
165 self.l.setFont(0, gFont("Regular", 20))
166 self.l.setFont(1, gFont("Regular", 14))
167 width = esize.width()
168 height = esize.height()
171 self.service_rect = Rect(xpos, 0, w-10, height)
174 self.event_rect = Rect(xpos, 0, w, height)
175 self.l.setSelectionClip(eRect(0,0,0,0), False)
177 def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
178 xpos = (stime - start) * width / (end - start)
179 ewidth = (stime + duration - start) * width / (end - start)
184 if (xpos+ewidth) > width:
185 ewidth = width - xpos
188 def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
189 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
190 return xpos+event_rect.left(), width
192 def buildEntry(self, service, service_name, events):
195 res = [ None, MultiContentEntryText(pos = (r1.left(),r1.top()), size = (r1.width(), r1.height()), font = 0, flags = RT_HALIGN_LEFT | RT_VALIGN_CENTER, text = service_name) ]
198 start = self.time_base+self.offs*self.time_epoch*60
199 end = start + self.time_epoch * 60
204 foreColor = self.foreColor
205 foreColorSelected = self.foreColorSelected
206 backColor = self.backColor
207 backColorSelected = self.backColorSelected
208 borderColor = self.borderColor
210 for ev in events: #(event_id, event_title, begin_time, duration)
211 rec=self.timer.isInTimer(ev[0], ev[2], ev[3], service) > ((ev[3]/10)*8)
212 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
213 res.append(MultiContentEntryText(pos = (left+xpos, top), size = (ewidth, height), font = 1, flags = RT_HALIGN_CENTER | RT_VALIGN_CENTER | RT_WRAP, text = ev[1], color = foreColor, color_sel = foreColorSelected, backcolor = backColor, backcolor_sel = backColorSelected, border_width = 1, border_color = borderColor))
214 if rec and ewidth > 23:
215 res.append(MultiContentEntryPixmapAlphaTest(pos = (left+xpos+ewidth-22, top+height-22), size = (21, 21), png = self.clock_pixmap, backcolor = backColor, backcolor_selected = backColorSelected))
218 def selEntry(self, dir, visible=True):
219 cur_service = self.cur_service #(service, service_name, events)
220 if not self.event_rect:
221 self.recalcEntrySize()
222 if cur_service and self.cur_event is not None:
224 entries = cur_service[2]
225 if dir == 0: #current
227 elif dir == +1: #next
228 if self.cur_event+1 < len(entries):
232 self.fillMultiEPG(None) # refill
234 elif dir == -1: #prev
235 if self.cur_event-1 >= 0:
239 self.fillMultiEPG(None) # refill
241 entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
242 time_base = self.time_base+self.offs*self.time_epoch*60
243 xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
244 self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
246 self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
247 self.selectionChanged()
250 def queryEPG(self, list, buildFunc=None):
251 if self.epgcache is not None:
252 if buildFunc is not None:
253 return self.epgcache.lookupEvent(list, buildFunc)
255 return self.epgcache.lookupEvent(list)
258 def fillMultiEPG(self, services, stime=-1):
260 time_base = self.time_base+self.offs*self.time_epoch*60
261 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
263 self.cur_event = None
264 self.cur_service = None
265 self.time_base = int(stime)
266 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
267 test.insert(0, 'RnITBD')
268 epg_data = self.queryEPG(test)
276 if tmp_list is not None:
277 self.list.append((service, sname, tmp_list[0][0] and tmp_list))
281 tmp_list.append((x[2], x[3], x[4], x[5]))
282 if tmp_list and len(tmp_list):
283 self.list.append((service, sname, tmp_list[0][0] and tmp_list))
285 self.l.setList(self.list)
288 def getEventRect(self):
290 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
292 def getTimeEpoch(self):
293 return self.time_epoch
295 def getTimeBase(self):
296 return self.time_base + (self.offs * self.time_epoch * 60)
298 def resetOffset(self):
301 class TimelineText(HTMLComponent, GUIComponent):
303 GUIComponent.__init__(self)
304 self.l = eListboxPythonMultiContent()
305 self.l.setSelectionClip(eRect(0,0,0,0))
306 self.l.setItemHeight(25);
307 self.l.setFont(0, gFont("Regular", 20))
309 GUI_WIDGET = eListbox
311 def postWidgetCreate(self, instance):
312 instance.setContent(self.l)
314 def setEntries(self, entries):
315 res = [ None ] # no private data needed
319 str = strftime("%H:%M", localtime(tm))
320 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
321 self.l.setList([res])
323 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
324 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
326 class GraphMultiEPG(Screen):
327 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
328 Screen.__init__(self, session)
329 self.bouquetChangeCB = bouquetChangeCB
332 self.ask_time = now - tmp
333 self.closeRecursive = False
334 self["key_red"] = Button("")
335 self["key_green"] = Button(_("Add timer"))
336 self["timeline_text"] = TimelineText()
337 self["Event"] = Event()
338 self["Clock"] = ObsoleteSource(new_source = "global.CurrentTime", removal_date = "2008-01")
339 self.time_lines = [ ]
340 for x in (0,1,2,3,4,5):
342 self.time_lines.append(pm)
343 self["timeline%d"%(x)] = pm
344 self["timeline_now"] = Pixmap()
345 self.services = services
346 self.zapFunc = zapFunc
348 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
350 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
352 "cancel": self.closeScreen,
353 "ok": self.eventSelected,
354 "timerAdd": self.timerAdd,
355 "info": self.infoKeyPressed,
357 "input_date_time": self.enterDateTime,
358 "nextBouquet": self.nextBouquet,
359 "prevBouquet": self.prevBouquet,
361 self["actions"].csel = self
363 self["input_actions"] = ActionMap(["InputActions"],
365 "left": self.leftPressed,
366 "right": self.rightPressed,
374 self.updateTimelineTimer = eTimer()
375 self.updateTimelineTimer.timeout.get().append(self.moveTimeLines)
376 self.updateTimelineTimer.start(60*1000)
377 self.onLayoutFinish.append(self.onCreate)
379 def leftPressed(self):
382 def rightPressed(self):
385 def nextEvent(self, visible=True):
386 ret = self["list"].selEntry(+1, visible)
388 self.moveTimeLines(True)
390 def prevEvent(self, visible=True):
391 ret = self["list"].selEntry(-1, visible)
393 self.moveTimeLines(True)
396 self["list"].setEpoch(60)
397 config.misc.graph_mepg_prev_time_period.value = 60
401 self["list"].setEpoch(120)
402 config.misc.graph_mepg_prev_time_period.value = 120
406 self["list"].setEpoch(180)
407 config.misc.graph_mepg_prev_time_period.value = 180
411 self["list"].setEpoch(240)
412 config.misc.graph_mepg_prev_time_period.value = 240
416 self["list"].setEpoch(300)
417 config.misc.graph_mepg_prev_time_period.value = 300
420 def nextBouquet(self):
421 if self.bouquetChangeCB:
422 self.bouquetChangeCB(1, self)
424 def prevBouquet(self):
425 if self.bouquetChangeCB:
426 self.bouquetChangeCB(-1, self)
428 def enterDateTime(self):
429 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
431 def onDateTimeInputClosed(self, ret):
437 l.fillMultiEPG(self.services, ret[1])
438 self.moveTimeLines(True)
440 def closeScreen(self):
441 self.close(self.closeRecursive)
443 def infoKeyPressed(self):
444 cur = self["list"].getCurrent()
447 if event is not None:
448 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
450 def openSimilarList(self, eventid, refstr):
451 self.session.open(EPGSelection, refstr, None, eventid)
453 def setServices(self, services):
454 self.services = services
457 #just used in multipeg
459 self["list"].fillMultiEPG(self.services, self.ask_time)
462 def eventViewCallback(self, setEvent, setService, val):
466 self.prevEvent(False)
468 self.nextEvent(False)
470 if cur[0] is None and cur[1].ref != old[1].ref:
471 self.eventViewCallback(setEvent, setService, val)
477 if self.zapFunc and self["key_red"].getText() == "Zap":
478 self.closeRecursive = True
479 ref = self["list"].getCurrent()[1]
480 self.zapFunc(ref.ref)
482 def eventSelected(self):
483 self.infoKeyPressed()
486 cur = self["list"].getCurrent()
491 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
492 self.session.openWithCallback(self.timerEditFinished, TimerEntry, newEntry)
494 def timerEditFinished(self, answer):
496 self.session.nav.RecordTimer.record(answer[1])
498 print "Timeredit aborted"
500 def onSelectionChanged(self):
501 evt = self["list"].getCurrent()
502 self["Event"].newEvent(evt and evt[0])
506 start = evt.getBeginTime()
507 end = start + evt.getDuration()
508 if now >= start and now <= end:
509 self["key_red"].setText("Zap")
511 self["key_red"].setText("")
513 def moveTimeLines(self, force=False):
515 event_rect = l.getEventRect()
516 time_epoch = l.getTimeEpoch()
517 time_base = l.getTimeBase()
518 if event_rect is None or time_epoch is None or time_base is None:
520 time_steps = time_epoch > 180 and 60 or 30
521 num_lines = time_epoch/time_steps
522 incWidth=event_rect.width()/num_lines
523 pos=event_rect.left()
524 timeline_entries = [ ]
527 for line in self.time_lines:
528 old_pos = line.position
529 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
530 if not x or x >= num_lines:
533 if old_pos != new_pos:
534 line.setPosition(new_pos[0], new_pos[1])
537 if not x or line.visible:
538 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
542 if changecount or force:
543 self["timeline_text"].setEntries(timeline_entries)
546 timeline_now = self["timeline_now"]
547 if now >= time_base and now < (time_base + time_epoch * 60):
548 bla = (event_rect.width() * 1000) / time_epoch
549 xpos = ((now/60) - (time_base/60)) * bla / 1000
550 old_pos = timeline_now.position
551 new_pos = (xpos+event_rect.left(), old_pos[1])
552 if old_pos != new_pos:
553 timeline_now.setPosition(new_pos[0], new_pos[1])
554 timeline_now.visible = True
556 timeline_now.visible = False