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
52 self.foreColorService = None
53 self.backColorService = None
55 def applySkin(self, desktop):
56 if self.skinAttributes is not None:
58 for (attrib, value) in self.skinAttributes:
59 if attrib == "EntryForegroundColor":
60 self.foreColor = parseColor(value).argb()
61 elif attrib == "EntryForegroundColorSelected":
62 self.foreColorSelected = parseColor(value).argb()
63 elif attrib == "EntryBorderColor":
64 self.borderColor = parseColor(value).argb()
65 elif attrib == "EntryBackgroundColor":
66 self.backColor = parseColor(value).argb()
67 elif attrib == "EntryBackgroundColorSelected":
68 self.backColorSelected = parseColor(value).argb()
69 elif attrib == "ServiceNameForegroundColor":
70 self.foreColorService = parseColor(value).argb()
71 elif attrib == "ServiceNameBackgroundColor":
72 self.backColorService = parseColor(value).argb()
74 attribs.append((attrib,value))
75 self.skinAttributes = attribs
76 return GUIComponent.applySkin(self, desktop)
78 def isSelectable(self, service, sname, event_list):
79 return (event_list and len(event_list) and True) or False
81 def setEpoch(self, epoch):
82 if self.cur_event is not None and self.cur_service is not None:
84 self.time_epoch = epoch
85 self.fillMultiEPG(None) # refill
87 def getEventFromId(self, service, eventid):
89 if self.epgcache is not None and eventid is not None:
90 event = self.epgcache.lookupEventId(service.ref, eventid)
94 if self.cur_service is None or self.cur_event is None:
96 old_service = self.cur_service #(service, service_name, events)
97 events = self.cur_service[2]
98 refstr = self.cur_service[0]
99 if not events or not len(events):
100 return ( None, None )
101 event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
103 service = ServiceReference(refstr)
104 event = self.getEventFromId(service, eventid)
105 return ( event, service )
107 def connectSelectionChanged(func):
108 if not self.onSelChanged.count(func):
109 self.onSelChanged.append(func)
111 def disconnectSelectionChanged(func):
112 self.onSelChanged.remove(func)
114 def serviceChanged(self):
115 cur_sel = self.l.getCurrentSelection()
119 def findBestEvent(self):
120 old_service = self.cur_service #(service, service_name, events)
121 cur_service = self.cur_service = self.l.getCurrentSelection()
123 time_base = self.getTimeBase()
124 if old_service and self.cur_event is not None:
125 events = old_service[2]
126 cur_event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
127 last_time = cur_event[2]
128 if last_time < time_base:
129 last_time = time_base
132 events = cur_service[2]
133 if events and len(events):
136 best = len(events) #set invalid
138 for event in events: #iterate all events
140 if ev_time < time_base:
142 diff = abs(ev_time-last_time)
143 if (best == len(events)) or (diff < best_diff):
147 if best != len(events):
148 self.cur_event = best
150 self.cur_event = None
153 def selectionChanged(self):
154 for x in self.onSelChanged:
159 print "FIXME in EPGList.selectionChanged"
162 GUI_WIDGET = eListbox
164 def postWidgetCreate(self, instance):
165 instance.setWrapAround(True)
166 instance.selectionChanged.get().append(self.serviceChanged)
167 instance.setContent(self.l)
169 def recalcEntrySize(self):
170 esize = self.l.getItemSize()
171 self.l.setFont(0, gFont("Regular", 20))
172 self.l.setFont(1, gFont("Regular", 14))
173 width = esize.width()
174 height = esize.height()
177 self.service_rect = Rect(xpos, 0, w-10, height)
180 self.event_rect = Rect(xpos, 0, w, height)
181 self.l.setSelectionClip(eRect(0,0,0,0), False)
183 def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
184 xpos = (stime - start) * width / (end - start)
185 ewidth = (stime + duration - start) * width / (end - start)
190 if (xpos+ewidth) > width:
191 ewidth = width - xpos
194 def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
195 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
196 return xpos+event_rect.left(), width
198 def buildEntry(self, service, service_name, events):
201 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, color = self.foreColorService, backcolor = self.backColorService) ]
204 start = self.time_base+self.offs*self.time_epoch*60
205 end = start + self.time_epoch * 60
210 foreColor = self.foreColor
211 foreColorSelected = self.foreColorSelected
212 backColor = self.backColor
213 backColorSelected = self.backColorSelected
214 borderColor = self.borderColor
216 for ev in events: #(event_id, event_title, begin_time, duration)
217 rec=self.timer.isInTimer(ev[0], ev[2], ev[3], service) > ((ev[3]/10)*8)
218 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
219 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))
220 if rec and ewidth > 23:
221 res.append(MultiContentEntryPixmapAlphaTest(pos = (left+xpos+ewidth-22, top+height-22), size = (21, 21), png = self.clock_pixmap, backcolor = backColor, backcolor_selected = backColorSelected))
224 def selEntry(self, dir, visible=True):
225 cur_service = self.cur_service #(service, service_name, events)
226 if not self.event_rect:
227 self.recalcEntrySize()
228 if cur_service and self.cur_event is not None:
230 entries = cur_service[2]
231 if dir == 0: #current
233 elif dir == +1: #next
234 if self.cur_event+1 < len(entries):
238 self.fillMultiEPG(None) # refill
240 elif dir == -1: #prev
241 if self.cur_event-1 >= 0:
245 self.fillMultiEPG(None) # refill
247 entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
248 time_base = self.time_base+self.offs*self.time_epoch*60
249 xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
250 self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
252 self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
253 self.selectionChanged()
256 def queryEPG(self, list, buildFunc=None):
257 if self.epgcache is not None:
258 if buildFunc is not None:
259 return self.epgcache.lookupEvent(list, buildFunc)
261 return self.epgcache.lookupEvent(list)
264 def fillMultiEPG(self, services, stime=-1):
266 time_base = self.time_base+self.offs*self.time_epoch*60
267 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
269 self.cur_event = None
270 self.cur_service = None
271 self.time_base = int(stime)
272 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
273 test.insert(0, 'RnITBD')
274 epg_data = self.queryEPG(test)
282 if tmp_list is not None:
283 self.list.append((service, sname, tmp_list[0][0] and tmp_list))
287 tmp_list.append((x[2], x[3], x[4], x[5]))
288 if tmp_list and len(tmp_list):
289 self.list.append((service, sname, tmp_list[0][0] and tmp_list))
291 self.l.setList(self.list)
294 def getEventRect(self):
296 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
298 def getTimeEpoch(self):
299 return self.time_epoch
301 def getTimeBase(self):
302 return self.time_base + (self.offs * self.time_epoch * 60)
304 def resetOffset(self):
307 class TimelineText(HTMLComponent, GUIComponent):
309 GUIComponent.__init__(self)
310 self.l = eListboxPythonMultiContent()
311 self.l.setSelectionClip(eRect(0,0,0,0))
312 self.l.setItemHeight(25);
313 self.l.setFont(0, gFont("Regular", 20))
315 GUI_WIDGET = eListbox
317 def postWidgetCreate(self, instance):
318 instance.setContent(self.l)
320 def setEntries(self, entries):
321 res = [ None ] # no private data needed
325 str = strftime("%H:%M", localtime(tm))
326 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
327 self.l.setList([res])
329 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
330 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
332 class GraphMultiEPG(Screen):
333 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
334 Screen.__init__(self, session)
335 self.bouquetChangeCB = bouquetChangeCB
338 self.ask_time = now - tmp
339 self.closeRecursive = False
340 self["key_red"] = Button("")
341 self["key_green"] = Button(_("Add timer"))
342 self["timeline_text"] = TimelineText()
343 self["Event"] = Event()
344 self["Clock"] = ObsoleteSource(new_source = "global.CurrentTime", removal_date = "2008-01")
345 self.time_lines = [ ]
346 for x in (0,1,2,3,4,5):
348 self.time_lines.append(pm)
349 self["timeline%d"%(x)] = pm
350 self["timeline_now"] = Pixmap()
351 self.services = services
352 self.zapFunc = zapFunc
354 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
356 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
358 "cancel": self.closeScreen,
359 "ok": self.eventSelected,
360 "timerAdd": self.timerAdd,
361 "info": self.infoKeyPressed,
363 "input_date_time": self.enterDateTime,
364 "nextBouquet": self.nextBouquet,
365 "prevBouquet": self.prevBouquet,
367 self["actions"].csel = self
369 self["input_actions"] = ActionMap(["InputActions"],
371 "left": self.leftPressed,
372 "right": self.rightPressed,
380 self.updateTimelineTimer = eTimer()
381 self.updateTimelineTimer.timeout.get().append(self.moveTimeLines)
382 self.updateTimelineTimer.start(60*1000)
383 self.onLayoutFinish.append(self.onCreate)
385 def leftPressed(self):
388 def rightPressed(self):
391 def nextEvent(self, visible=True):
392 ret = self["list"].selEntry(+1, visible)
394 self.moveTimeLines(True)
396 def prevEvent(self, visible=True):
397 ret = self["list"].selEntry(-1, visible)
399 self.moveTimeLines(True)
402 self["list"].setEpoch(60)
403 config.misc.graph_mepg_prev_time_period.value = 60
407 self["list"].setEpoch(120)
408 config.misc.graph_mepg_prev_time_period.value = 120
412 self["list"].setEpoch(180)
413 config.misc.graph_mepg_prev_time_period.value = 180
417 self["list"].setEpoch(240)
418 config.misc.graph_mepg_prev_time_period.value = 240
422 self["list"].setEpoch(300)
423 config.misc.graph_mepg_prev_time_period.value = 300
426 def nextBouquet(self):
427 if self.bouquetChangeCB:
428 self.bouquetChangeCB(1, self)
430 def prevBouquet(self):
431 if self.bouquetChangeCB:
432 self.bouquetChangeCB(-1, self)
434 def enterDateTime(self):
435 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
437 def onDateTimeInputClosed(self, ret):
443 l.fillMultiEPG(self.services, ret[1])
444 self.moveTimeLines(True)
446 def closeScreen(self):
447 self.close(self.closeRecursive)
449 def infoKeyPressed(self):
450 cur = self["list"].getCurrent()
453 if event is not None:
454 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
456 def openSimilarList(self, eventid, refstr):
457 self.session.open(EPGSelection, refstr, None, eventid)
459 def setServices(self, services):
460 self.services = services
463 #just used in multipeg
465 self["list"].fillMultiEPG(self.services, self.ask_time)
468 def eventViewCallback(self, setEvent, setService, val):
472 self.prevEvent(False)
474 self.nextEvent(False)
476 if cur[0] is None and cur[1].ref != old[1].ref:
477 self.eventViewCallback(setEvent, setService, val)
483 if self.zapFunc and self["key_red"].getText() == "Zap":
484 self.closeRecursive = True
485 ref = self["list"].getCurrent()[1]
486 self.zapFunc(ref.ref)
488 def eventSelected(self):
489 self.infoKeyPressed()
492 cur = self["list"].getCurrent()
497 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
498 self.session.openWithCallback(self.timerEditFinished, TimerEntry, newEntry)
500 def timerEditFinished(self, answer):
502 self.session.nav.RecordTimer.record(answer[1])
504 print "Timeredit aborted"
506 def onSelectionChanged(self):
507 evt = self["list"].getCurrent()
508 self["Event"].newEvent(evt and evt[0])
512 start = evt.getBeginTime()
513 end = start + evt.getDuration()
514 if now >= start and now <= end:
515 self["key_red"].setText("Zap")
517 self["key_red"].setText("")
519 def moveTimeLines(self, force=False):
521 event_rect = l.getEventRect()
522 time_epoch = l.getTimeEpoch()
523 time_base = l.getTimeBase()
524 if event_rect is None or time_epoch is None or time_base is None:
526 time_steps = time_epoch > 180 and 60 or 30
527 num_lines = time_epoch/time_steps
528 incWidth=event_rect.width()/num_lines
529 pos=event_rect.left()
530 timeline_entries = [ ]
533 for line in self.time_lines:
534 old_pos = line.position
535 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
536 if not x or x >= num_lines:
539 if old_pos != new_pos:
540 line.setPosition(new_pos[0], new_pos[1])
543 if not x or line.visible:
544 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
548 if changecount or force:
549 self["timeline_text"].setEntries(timeline_entries)
552 timeline_now = self["timeline_now"]
553 if now >= time_base and now < (time_base + time_epoch * 60):
554 bla = (event_rect.width() * 1000) / time_epoch
555 xpos = ((now/60) - (time_base/60)) * bla / 1000
556 old_pos = timeline_now.position
557 new_pos = (xpos+event_rect.left(), old_pos[1])
558 if old_pos != new_pos:
559 timeline_now.setPosition(new_pos[0], new_pos[1])
560 timeline_now.visible = True
562 timeline_now.visible = False