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)
168 self.l.setFont(0, gFont("Regular", 20))
169 self.l.setFont(1, gFont("Regular", 14))
170 self.l.setSelectionClip(eRect(0,0,0,0), False)
172 def recalcEntrySize(self):
173 esize = self.l.getItemSize()
174 width = esize.width()
175 height = esize.height()
178 self.service_rect = Rect(xpos, 0, w-10, height)
181 self.event_rect = Rect(xpos, 0, w, height)
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_sel = backColorSelected))
224 def selEntry(self, dir, visible=True):
225 cur_service = self.cur_service #(service, service_name, events)
226 self.recalcEntrySize()
227 valid_event = self.cur_event is not None
230 entries = cur_service[2]
231 if dir == 0: #current
233 elif dir == +1: #next
234 if valid_event and self.cur_event+1 < len(entries):
238 self.fillMultiEPG(None) # refill
240 elif dir == -1: #prev
241 if valid_event and self.cur_event-1 >= 0:
245 self.fillMultiEPG(None) # refill
247 if cur_service and valid_event:
248 entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
249 time_base = self.time_base+self.offs*self.time_epoch*60
250 xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
251 self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
253 self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
254 self.selectionChanged()
257 def queryEPG(self, list, buildFunc=None):
258 if self.epgcache is not None:
259 if buildFunc is not None:
260 return self.epgcache.lookupEvent(list, buildFunc)
262 return self.epgcache.lookupEvent(list)
265 def fillMultiEPG(self, services, stime=-1):
267 time_base = self.time_base+self.offs*self.time_epoch*60
268 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
270 self.cur_event = None
271 self.cur_service = None
272 self.time_base = int(stime)
273 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
274 test.insert(0, 'RnITBD')
275 epg_data = self.queryEPG(test)
283 if tmp_list is not None:
284 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
288 tmp_list.append((x[2], x[3], x[4], x[5]))
289 if tmp_list and len(tmp_list):
290 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
292 self.l.setList(self.list)
295 def getEventRect(self):
297 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
299 def getTimeEpoch(self):
300 return self.time_epoch
302 def getTimeBase(self):
303 return self.time_base + (self.offs * self.time_epoch * 60)
305 def resetOffset(self):
308 class TimelineText(HTMLComponent, GUIComponent):
310 GUIComponent.__init__(self)
311 self.l = eListboxPythonMultiContent()
312 self.l.setSelectionClip(eRect(0,0,0,0))
313 self.l.setItemHeight(25);
314 self.l.setFont(0, gFont("Regular", 20))
316 GUI_WIDGET = eListbox
318 def postWidgetCreate(self, instance):
319 instance.setContent(self.l)
321 def setEntries(self, entries):
322 res = [ None ] # no private data needed
326 str = strftime("%H:%M", localtime(tm))
327 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
328 self.l.setList([res])
330 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
331 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
333 class GraphMultiEPG(Screen):
334 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
335 Screen.__init__(self, session)
336 self.bouquetChangeCB = bouquetChangeCB
339 self.ask_time = now - tmp
340 self.closeRecursive = False
341 self["key_red"] = Button("")
342 self["key_green"] = Button(_("Add timer"))
343 self["timeline_text"] = TimelineText()
344 self["Event"] = Event()
345 self["Clock"] = ObsoleteSource(new_source = "global.CurrentTime", removal_date = "2008-01")
346 self.time_lines = [ ]
347 for x in (0,1,2,3,4,5):
349 self.time_lines.append(pm)
350 self["timeline%d"%(x)] = pm
351 self["timeline_now"] = Pixmap()
352 self.services = services
353 self.zapFunc = zapFunc
355 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
357 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
359 "cancel": self.closeScreen,
360 "ok": self.eventSelected,
361 "timerAdd": self.timerAdd,
362 "info": self.infoKeyPressed,
364 "input_date_time": self.enterDateTime,
365 "nextBouquet": self.nextBouquet,
366 "prevBouquet": self.prevBouquet,
368 self["actions"].csel = self
370 self["input_actions"] = ActionMap(["InputActions"],
372 "left": self.leftPressed,
373 "right": self.rightPressed,
381 self.updateTimelineTimer = eTimer()
382 self.updateTimelineTimer.timeout.get().append(self.moveTimeLines)
383 self.updateTimelineTimer.start(60*1000)
384 self.onLayoutFinish.append(self.onCreate)
386 def leftPressed(self):
389 def rightPressed(self):
392 def nextEvent(self, visible=True):
393 ret = self["list"].selEntry(+1, visible)
395 self.moveTimeLines(True)
397 def prevEvent(self, visible=True):
398 ret = self["list"].selEntry(-1, visible)
400 self.moveTimeLines(True)
403 self["list"].setEpoch(60)
404 config.misc.graph_mepg_prev_time_period.value = 60
408 self["list"].setEpoch(120)
409 config.misc.graph_mepg_prev_time_period.value = 120
413 self["list"].setEpoch(180)
414 config.misc.graph_mepg_prev_time_period.value = 180
418 self["list"].setEpoch(240)
419 config.misc.graph_mepg_prev_time_period.value = 240
423 self["list"].setEpoch(300)
424 config.misc.graph_mepg_prev_time_period.value = 300
427 def nextBouquet(self):
428 if self.bouquetChangeCB:
429 self.bouquetChangeCB(1, self)
431 def prevBouquet(self):
432 if self.bouquetChangeCB:
433 self.bouquetChangeCB(-1, self)
435 def enterDateTime(self):
436 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
438 def onDateTimeInputClosed(self, ret):
444 l.fillMultiEPG(self.services, ret[1])
445 self.moveTimeLines(True)
447 def closeScreen(self):
448 self.close(self.closeRecursive)
450 def infoKeyPressed(self):
451 cur = self["list"].getCurrent()
454 if event is not None:
455 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
457 def openSimilarList(self, eventid, refstr):
458 self.session.open(EPGSelection, refstr, None, eventid)
460 def setServices(self, services):
461 self.services = services
464 #just used in multipeg
466 self["list"].fillMultiEPG(self.services, self.ask_time)
469 def eventViewCallback(self, setEvent, setService, val):
473 self.prevEvent(False)
475 self.nextEvent(False)
477 if cur[0] is None and cur[1].ref != old[1].ref:
478 self.eventViewCallback(setEvent, setService, val)
484 if self.zapFunc and self["key_red"].getText() == "Zap":
485 self.closeRecursive = True
486 ref = self["list"].getCurrent()[1]
487 self.zapFunc(ref.ref)
489 def eventSelected(self):
490 self.infoKeyPressed()
493 cur = self["list"].getCurrent()
498 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
499 self.session.openWithCallback(self.timerEditFinished, TimerEntry, newEntry)
501 def timerEditFinished(self, answer):
503 self.session.nav.RecordTimer.record(answer[1])
505 print "Timeredit aborted"
507 def onSelectionChanged(self):
508 evt = self["list"].getCurrent()
509 self["Event"].newEvent(evt and evt[0])
513 start = evt.getBeginTime()
514 end = start + evt.getDuration()
515 if now >= start and now <= end:
516 self["key_red"].setText("Zap")
518 self["key_red"].setText("")
520 def moveTimeLines(self, force=False):
522 event_rect = l.getEventRect()
523 time_epoch = l.getTimeEpoch()
524 time_base = l.getTimeBase()
525 if event_rect is None or time_epoch is None or time_base is None:
527 time_steps = time_epoch > 180 and 60 or 30
528 num_lines = time_epoch/time_steps
529 incWidth=event_rect.width()/num_lines
530 pos=event_rect.left()
531 timeline_entries = [ ]
534 for line in self.time_lines:
535 old_pos = line.position
536 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
537 if not x or x >= num_lines:
540 if old_pos != new_pos:
541 line.setPosition(new_pos[0], new_pos[1])
544 if not x or line.visible:
545 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
549 if changecount or force:
550 self["timeline_text"].setEntries(timeline_entries)
553 timeline_now = self["timeline_now"]
554 if now >= time_base and now < (time_base + time_epoch * 60):
555 bla = (event_rect.width() * 1000) / time_epoch
556 xpos = ((now/60) - (time_base/60)) * bla / 1000
557 old_pos = timeline_now.position
558 new_pos = (xpos+event_rect.left(), old_pos[1])
559 if old_pos != new_pos:
560 timeline_now.setPosition(new_pos[0], new_pos[1])
561 timeline_now.visible = True
563 timeline_now.visible = False