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 if old_service and self.cur_event is not None:
118 events = old_service[2]
119 cur_event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
120 last_time = cur_event[2]
123 events = cur_service[2]
124 if events and len(events):
127 best = len(events) #set invalid
129 for event in events: #iterate all events
130 diff = abs(event[2]-last_time)
131 if (best == len(events)) or (diff < best_diff):
135 if best != len(events):
136 self.cur_event = best
138 self.cur_event = None
141 def selectionChanged(self):
142 for x in self.onSelChanged:
147 print "FIXME in EPGList.selectionChanged"
150 GUI_WIDGET = eListbox
152 def postWidgetCreate(self, instance):
153 instance.setWrapAround(True)
154 instance.selectionChanged.get().append(self.serviceChanged)
155 instance.setContent(self.l)
157 def recalcEntrySize(self):
158 esize = self.l.getItemSize()
159 self.l.setFont(0, gFont("Regular", 20))
160 self.l.setFont(1, gFont("Regular", 14))
161 width = esize.width()
162 height = esize.height()
165 self.service_rect = Rect(xpos, 0, w-10, height)
168 self.event_rect = Rect(xpos, 0, w, height)
169 self.l.setSelectionClip(eRect(0,0,0,0), False)
171 def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
172 xpos = (stime - start) * width / (end - start)
173 ewidth = (stime + duration - start) * width / (end - start)
178 if (xpos+ewidth) > width:
179 ewidth = width - xpos
182 def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
183 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
184 return xpos+event_rect.left(), width
186 def buildEntry(self, service, service_name, events):
189 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) ]
192 start = self.time_base+self.offs*self.time_epoch*60
193 end = start + self.time_epoch * 60
198 foreColor = self.foreColor
199 foreColorSelected = self.foreColorSelected
200 backColor = self.backColor
201 backColorSelected = self.backColorSelected
202 borderColor = self.borderColor
204 for ev in events: #(event_id, event_title, begin_time, duration)
205 rec=self.timer.isInTimer(ev[0], ev[2], ev[3], service) > ((ev[3]/10)*8)
206 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
207 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))
208 if rec and ewidth > 23:
209 res.append(MultiContentEntryPixmapAlphaTest(pos = (left+xpos+ewidth-22, top+height-22), size = (21, 21), png = self.clock_pixmap, backcolor = backColor, backcolor_selected = backColorSelected))
212 def selEntry(self, dir, visible=True):
213 cur_service = self.cur_service #(service, service_name, events)
214 if not self.event_rect:
215 self.recalcEntrySize()
216 if cur_service and self.cur_event is not None:
218 entries = cur_service[2]
219 if dir == 0: #current
221 elif dir == +1: #next
222 if self.cur_event+1 < len(entries):
226 self.fillMultiEPG(None) # refill
228 elif dir == -1: #prev
229 if self.cur_event-1 >= 0:
233 self.fillMultiEPG(None) # refill
235 entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
236 time_base = self.time_base+self.offs*self.time_epoch*60
237 xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
238 self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
240 self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
241 self.selectionChanged()
244 def queryEPG(self, list, buildFunc=None):
245 if self.epgcache is not None:
246 if buildFunc is not None:
247 return self.epgcache.lookupEvent(list, buildFunc)
249 return self.epgcache.lookupEvent(list)
252 def fillMultiEPG(self, services, stime=-1):
254 time_base = self.time_base+self.offs*self.time_epoch*60
255 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
257 self.cur_event = None
258 self.cur_service = None
259 self.time_base = int(stime)
260 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
261 test.insert(0, 'RnITBD')
262 epg_data = self.queryEPG(test)
270 if tmp_list is not None:
271 self.list.append((service, sname, tmp_list[0][0] and tmp_list))
275 tmp_list.append((x[2], x[3], x[4], x[5]))
276 if tmp_list and len(tmp_list):
277 self.list.append((service, sname, tmp_list[0][0] and tmp_list))
279 self.l.setList(self.list)
282 def getEventRect(self):
284 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
286 def getTimeEpoch(self):
287 return self.time_epoch
289 def getTimeBase(self):
290 return self.time_base + (self.offs * self.time_epoch * 60)
292 def resetOffset(self):
295 class TimelineText(HTMLComponent, GUIComponent):
297 GUIComponent.__init__(self)
298 self.l = eListboxPythonMultiContent()
299 self.l.setSelectionClip(eRect(0,0,0,0))
300 self.l.setItemHeight(25);
301 self.l.setFont(0, gFont("Regular", 20))
303 GUI_WIDGET = eListbox
305 def postWidgetCreate(self, instance):
306 instance.setContent(self.l)
308 def setEntries(self, entries):
309 res = [ None ] # no private data needed
313 str = strftime("%H:%M", localtime(tm))
314 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
315 self.l.setList([res])
317 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
318 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
320 class GraphMultiEPG(Screen):
321 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
322 Screen.__init__(self, session)
323 self.bouquetChangeCB = bouquetChangeCB
326 self.ask_time = now - tmp
327 self.closeRecursive = False
328 self["key_red"] = Button("")
329 self["key_green"] = Button(_("Add timer"))
330 self["timeline_text"] = TimelineText()
331 self["Event"] = Event()
332 self["Clock"] = ObsoleteSource(new_source = "global.CurrentTime", removal_date = "2008-01")
333 self.time_lines = [ ]
334 for x in (0,1,2,3,4,5):
336 self.time_lines.append(pm)
337 self["timeline%d"%(x)] = pm
338 self["timeline_now"] = Pixmap()
339 self.services = services
340 self.zapFunc = zapFunc
342 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
344 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
346 "cancel": self.closeScreen,
347 "ok": self.eventSelected,
348 "timerAdd": self.timerAdd,
349 "info": self.infoKeyPressed,
351 "input_date_time": self.enterDateTime,
352 "nextBouquet": self.nextBouquet,
353 "prevBouquet": self.prevBouquet,
355 self["actions"].csel = self
357 self["input_actions"] = ActionMap(["InputActions"],
359 "left": self.leftPressed,
360 "right": self.rightPressed,
368 self.updateTimelineTimer = eTimer()
369 self.updateTimelineTimer.timeout.get().append(self.moveTimeLines)
370 self.updateTimelineTimer.start(60*1000)
371 self.onLayoutFinish.append(self.onCreate)
373 def leftPressed(self):
376 def rightPressed(self):
379 def nextEvent(self, visible=True):
380 ret = self["list"].selEntry(+1, visible)
382 self.moveTimeLines(True)
384 def prevEvent(self, visible=True):
385 ret = self["list"].selEntry(-1, visible)
387 self.moveTimeLines(True)
390 self["list"].setEpoch(60)
391 config.misc.graph_mepg_prev_time_period.value = 60
395 self["list"].setEpoch(120)
396 config.misc.graph_mepg_prev_time_period.value = 120
400 self["list"].setEpoch(180)
401 config.misc.graph_mepg_prev_time_period.value = 180
405 self["list"].setEpoch(240)
406 config.misc.graph_mepg_prev_time_period.value = 240
410 self["list"].setEpoch(300)
411 config.misc.graph_mepg_prev_time_period.value = 300
414 def nextBouquet(self):
415 if self.bouquetChangeCB:
416 self.bouquetChangeCB(1, self)
418 def prevBouquet(self):
419 if self.bouquetChangeCB:
420 self.bouquetChangeCB(-1, self)
422 def enterDateTime(self):
423 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
425 def onDateTimeInputClosed(self, ret):
431 l.fillMultiEPG(self.services, ret[1])
432 self.moveTimeLines(True)
434 def closeScreen(self):
435 self.close(self.closeRecursive)
437 def infoKeyPressed(self):
438 cur = self["list"].getCurrent()
441 if event is not None:
442 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
444 def openSimilarList(self, eventid, refstr):
445 self.session.open(EPGSelection, refstr, None, eventid)
447 def setServices(self, services):
448 self.services = services
451 #just used in multipeg
453 self["list"].fillMultiEPG(self.services, self.ask_time)
456 def eventViewCallback(self, setEvent, setService, val):
460 self.prevEvent(False)
462 self.nextEvent(False)
464 if cur[0] is None and cur[1].ref != old[1].ref:
465 self.eventViewCallback(setEvent, setService, val)
471 if self.zapFunc and self["key_red"].getText() == "Zap":
472 self.closeRecursive = True
473 ref = self["list"].getCurrent()[1]
474 self.zapFunc(ref.ref)
476 def eventSelected(self):
477 self.infoKeyPressed()
480 cur = self["list"].getCurrent()
485 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
486 self.session.openWithCallback(self.timerEditFinished, TimerEntry, newEntry)
488 def timerEditFinished(self, answer):
490 self.session.nav.RecordTimer.record(answer[1])
492 print "Timeredit aborted"
494 def onSelectionChanged(self):
495 evt = self["list"].getCurrent()
496 self["Event"].newEvent(evt and evt[0])
500 start = evt.getBeginTime()
501 end = start + evt.getDuration()
502 if now >= start and now <= end:
503 self["key_red"].setText("Zap")
505 self["key_red"].setText("")
507 def moveTimeLines(self, force=False):
509 event_rect = l.getEventRect()
510 time_epoch = l.getTimeEpoch()
511 time_base = l.getTimeBase()
512 if event_rect is None or time_epoch is None or time_base is None:
514 time_steps = time_epoch > 180 and 60 or 30
515 num_lines = time_epoch/time_steps
516 incWidth=event_rect.width()/num_lines
517 pos=event_rect.left()
518 timeline_entries = [ ]
521 for line in self.time_lines:
522 old_pos = line.position
523 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
524 if not x or x >= num_lines:
527 if old_pos != new_pos:
528 line.setPosition(new_pos[0], new_pos[1])
531 if not x or line.visible:
532 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
536 if changecount or force:
537 self["timeline_text"].setEntries(timeline_entries)
540 timeline_now = self["timeline_now"]
541 if now >= time_base and now < (time_base + time_epoch * 60):
542 bla = (event_rect.width() * 1000) / time_epoch
543 xpos = ((now/60) - (time_base/60)) * bla / 1000
544 old_pos = timeline_now.position
545 new_pos = (xpos+event_rect.left(), old_pos[1])
546 if old_pos != new_pos:
547 timeline_now.setPosition(new_pos[0], new_pos[1])
548 timeline_now.visible = True
550 timeline_now.visible = False