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 Tools.LoadPixmap import LoadPixmap
21 from enigma import eEPGCache, eListbox, gFont, eListboxPythonMultiContent, \
22 RT_HALIGN_LEFT, RT_HALIGN_CENTER, RT_VALIGN_CENTER, RT_WRAP, eRect, eTimer
24 from time import localtime, time, strftime
26 class EPGList(HTMLComponent, GUIComponent):
27 def __init__(self, selChangedCB=None, timer = None, time_epoch = 120, overjump_empty=True):
29 self.cur_service = None
32 self.onSelChanged = [ ]
33 if selChangedCB is not None:
34 self.onSelChanged.append(selChangedCB)
35 GUIComponent.__init__(self)
36 self.l = eListboxPythonMultiContent()
37 self.l.setItemHeight(54);
38 self.l.setBuildFunc(self.buildEntry)
40 self.l.setSelectableFunc(self.isSelectable)
41 self.epgcache = eEPGCache.getInstance()
42 self.clock_pixmap = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, 'epgclock-fs8.png'))
44 self.time_epoch = time_epoch
46 self.event_rect = None
49 self.foreColorSelected = None
50 self.borderColor = None
51 self.backColor = 0x586d88
52 self.backColorSelected = 0x808080
53 self.foreColorService = None
54 self.backColorService = None
56 def applySkin(self, desktop):
57 if self.skinAttributes is not None:
59 for (attrib, value) in self.skinAttributes:
60 if attrib == "EntryForegroundColor":
61 self.foreColor = parseColor(value).argb()
62 elif attrib == "EntryForegroundColorSelected":
63 self.foreColorSelected = parseColor(value).argb()
64 elif attrib == "EntryBorderColor":
65 self.borderColor = parseColor(value).argb()
66 elif attrib == "EntryBackgroundColor":
67 self.backColor = parseColor(value).argb()
68 elif attrib == "EntryBackgroundColorSelected":
69 self.backColorSelected = parseColor(value).argb()
70 elif attrib == "ServiceNameForegroundColor":
71 self.foreColorService = parseColor(value).argb()
72 elif attrib == "ServiceNameBackgroundColor":
73 self.backColorService = parseColor(value).argb()
75 attribs.append((attrib,value))
76 self.skinAttributes = attribs
77 return GUIComponent.applySkin(self, desktop)
79 def isSelectable(self, service, sname, event_list):
80 return (event_list and len(event_list) and True) or False
82 def setEpoch(self, epoch):
83 if self.cur_event is not None and self.cur_service is not None:
85 self.time_epoch = epoch
86 self.fillMultiEPG(None) # refill
88 def getEventFromId(self, service, eventid):
90 if self.epgcache is not None and eventid is not None:
91 event = self.epgcache.lookupEventId(service.ref, eventid)
95 if self.cur_service is None or self.cur_event is None:
97 old_service = self.cur_service #(service, service_name, events)
98 events = self.cur_service[2]
99 refstr = self.cur_service[0]
100 if not events or not len(events):
101 return ( None, None )
102 event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
104 service = ServiceReference(refstr)
105 event = self.getEventFromId(service, eventid)
106 return ( event, service )
108 def connectSelectionChanged(func):
109 if not self.onSelChanged.count(func):
110 self.onSelChanged.append(func)
112 def disconnectSelectionChanged(func):
113 self.onSelChanged.remove(func)
115 def serviceChanged(self):
116 cur_sel = self.l.getCurrentSelection()
120 def findBestEvent(self):
121 old_service = self.cur_service #(service, service_name, events)
122 cur_service = self.cur_service = self.l.getCurrentSelection()
124 time_base = self.getTimeBase()
125 if old_service and self.cur_event is not None:
126 events = old_service[2]
127 cur_event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
128 last_time = cur_event[2]
129 if last_time < time_base:
130 last_time = time_base
133 events = cur_service[2]
134 if events and len(events):
137 best = len(events) #set invalid
139 for event in events: #iterate all events
141 if ev_time < time_base:
143 diff = abs(ev_time-last_time)
144 if (best == len(events)) or (diff < best_diff):
148 if best != len(events):
149 self.cur_event = best
151 self.cur_event = None
154 def selectionChanged(self):
155 for x in self.onSelChanged:
160 print "FIXME in EPGList.selectionChanged"
163 GUI_WIDGET = eListbox
165 def postWidgetCreate(self, instance):
166 instance.setWrapAround(True)
167 instance.selectionChanged.get().append(self.serviceChanged)
168 instance.setContent(self.l)
169 self.l.setFont(0, gFont("Regular", 20))
170 self.l.setFont(1, gFont("Regular", 14))
171 self.l.setSelectionClip(eRect(0,0,0,0), False)
173 def recalcEntrySize(self):
174 esize = self.l.getItemSize()
175 width = esize.width()
176 height = esize.height()
179 self.service_rect = Rect(xpos, 0, w-10, height)
182 self.event_rect = Rect(xpos, 0, w, height)
184 def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
185 xpos = (stime - start) * width / (end - start)
186 ewidth = (stime + duration - start) * width / (end - start)
191 if (xpos+ewidth) > width:
192 ewidth = width - xpos
195 def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
196 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
197 return xpos+event_rect.left(), width
199 def buildEntry(self, service, service_name, events):
202 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) ]
205 start = self.time_base+self.offs*self.time_epoch*60
206 end = start + self.time_epoch * 60
211 foreColor = self.foreColor
212 foreColorSelected = self.foreColorSelected
213 backColor = self.backColor
214 backColorSelected = self.backColorSelected
215 borderColor = self.borderColor
217 for ev in events: #(event_id, event_title, begin_time, duration)
218 rec=ev[2] and self.timer.isInTimer(ev[0], ev[2], ev[3], service) > ((ev[3]/10)*8)
219 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
220 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))
221 if rec and ewidth > 23:
222 res.append(MultiContentEntryPixmapAlphaTest(pos = (left+xpos+ewidth-22, top+height-22), size = (21, 21), png = self.clock_pixmap, backcolor = backColor, backcolor_sel = backColorSelected))
225 def selEntry(self, dir, visible=True):
226 cur_service = self.cur_service #(service, service_name, events)
227 self.recalcEntrySize()
228 valid_event = self.cur_event is not None
231 entries = cur_service[2]
232 if dir == 0: #current
234 elif dir == +1: #next
235 if valid_event and self.cur_event+1 < len(entries):
239 self.fillMultiEPG(None) # refill
241 elif dir == -1: #prev
242 if valid_event and self.cur_event-1 >= 0:
246 self.fillMultiEPG(None) # refill
248 if cur_service and valid_event:
249 entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
250 time_base = self.time_base+self.offs*self.time_epoch*60
251 xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
252 self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
254 self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
255 self.selectionChanged()
258 def queryEPG(self, list, buildFunc=None):
259 if self.epgcache is not None:
260 if buildFunc is not None:
261 return self.epgcache.lookupEvent(list, buildFunc)
263 return self.epgcache.lookupEvent(list)
266 def fillMultiEPG(self, services, stime=-1):
268 time_base = self.time_base+self.offs*self.time_epoch*60
269 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
271 self.cur_event = None
272 self.cur_service = None
273 self.time_base = int(stime)
274 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
275 test.insert(0, 'RnITBD')
276 epg_data = self.queryEPG(test)
284 if tmp_list is not None:
285 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
289 tmp_list.append((x[2], x[3], x[4], x[5]))
290 if tmp_list and len(tmp_list):
291 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
293 self.l.setList(self.list)
296 def getEventRect(self):
298 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
300 def getTimeEpoch(self):
301 return self.time_epoch
303 def getTimeBase(self):
304 return self.time_base + (self.offs * self.time_epoch * 60)
306 def resetOffset(self):
309 class TimelineText(HTMLComponent, GUIComponent):
311 GUIComponent.__init__(self)
312 self.l = eListboxPythonMultiContent()
313 self.l.setSelectionClip(eRect(0,0,0,0))
314 self.l.setItemHeight(25);
315 self.l.setFont(0, gFont("Regular", 20))
317 GUI_WIDGET = eListbox
319 def postWidgetCreate(self, instance):
320 instance.setContent(self.l)
322 def setEntries(self, entries):
323 res = [ None ] # no private data needed
327 str = strftime("%H:%M", localtime(tm))
328 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
329 self.l.setList([res])
331 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
332 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
334 class GraphMultiEPG(Screen):
335 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
336 Screen.__init__(self, session)
337 self.bouquetChangeCB = bouquetChangeCB
340 self.ask_time = now - tmp
341 self.closeRecursive = False
342 self["key_red"] = Button("")
343 self["key_green"] = Button(_("Add timer"))
344 self["timeline_text"] = TimelineText()
345 self["Event"] = Event()
346 self["Clock"] = ObsoleteSource(new_source = "global.CurrentTime", removal_date = "2008-01")
347 self.time_lines = [ ]
348 for x in (0,1,2,3,4,5):
350 self.time_lines.append(pm)
351 self["timeline%d"%(x)] = pm
352 self["timeline_now"] = Pixmap()
353 self.services = services
354 self.zapFunc = zapFunc
356 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
358 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
360 "cancel": self.closeScreen,
361 "ok": self.eventSelected,
362 "timerAdd": self.timerAdd,
363 "info": self.infoKeyPressed,
365 "input_date_time": self.enterDateTime,
366 "nextBouquet": self.nextBouquet,
367 "prevBouquet": self.prevBouquet,
369 self["actions"].csel = self
371 self["input_actions"] = ActionMap(["InputActions"],
373 "left": self.leftPressed,
374 "right": self.rightPressed,
382 self.updateTimelineTimer = eTimer()
383 self.updateTimelineTimer.timeout.get().append(self.moveTimeLines)
384 self.updateTimelineTimer.start(60*1000)
385 self.onLayoutFinish.append(self.onCreate)
387 def leftPressed(self):
390 def rightPressed(self):
393 def nextEvent(self, visible=True):
394 ret = self["list"].selEntry(+1, visible)
396 self.moveTimeLines(True)
398 def prevEvent(self, visible=True):
399 ret = self["list"].selEntry(-1, visible)
401 self.moveTimeLines(True)
404 self["list"].setEpoch(60)
405 config.misc.graph_mepg_prev_time_period.value = 60
409 self["list"].setEpoch(120)
410 config.misc.graph_mepg_prev_time_period.value = 120
414 self["list"].setEpoch(180)
415 config.misc.graph_mepg_prev_time_period.value = 180
419 self["list"].setEpoch(240)
420 config.misc.graph_mepg_prev_time_period.value = 240
424 self["list"].setEpoch(300)
425 config.misc.graph_mepg_prev_time_period.value = 300
428 def nextBouquet(self):
429 if self.bouquetChangeCB:
430 self.bouquetChangeCB(1, self)
432 def prevBouquet(self):
433 if self.bouquetChangeCB:
434 self.bouquetChangeCB(-1, self)
436 def enterDateTime(self):
437 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
439 def onDateTimeInputClosed(self, ret):
445 l.fillMultiEPG(self.services, ret[1])
446 self.moveTimeLines(True)
448 def closeScreen(self):
449 self.close(self.closeRecursive)
451 def infoKeyPressed(self):
452 cur = self["list"].getCurrent()
455 if event is not None:
456 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
458 def openSimilarList(self, eventid, refstr):
459 self.session.open(EPGSelection, refstr, None, eventid)
461 def setServices(self, services):
462 self.services = services
465 #just used in multipeg
467 self["list"].fillMultiEPG(self.services, self.ask_time)
470 def eventViewCallback(self, setEvent, setService, val):
474 self.prevEvent(False)
476 self.nextEvent(False)
478 if cur[0] is None and cur[1].ref != old[1].ref:
479 self.eventViewCallback(setEvent, setService, val)
485 if self.zapFunc and self["key_red"].getText() == "Zap":
486 self.closeRecursive = True
487 ref = self["list"].getCurrent()[1]
488 self.zapFunc(ref.ref)
490 def eventSelected(self):
491 self.infoKeyPressed()
494 cur = self["list"].getCurrent()
499 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
500 self.session.openWithCallback(self.timerEditFinished, TimerEntry, newEntry)
502 def timerEditFinished(self, answer):
504 self.session.nav.RecordTimer.record(answer[1])
506 print "Timeredit aborted"
508 def onSelectionChanged(self):
509 evt = self["list"].getCurrent()
510 self["Event"].newEvent(evt and evt[0])
514 start = evt.getBeginTime()
515 end = start + evt.getDuration()
516 if now >= start and now <= end:
517 self["key_red"].setText("Zap")
519 self["key_red"].setText("")
521 def moveTimeLines(self, force=False):
523 event_rect = l.getEventRect()
524 time_epoch = l.getTimeEpoch()
525 time_base = l.getTimeBase()
526 if event_rect is None or time_epoch is None or time_base is None:
528 time_steps = time_epoch > 180 and 60 or 30
529 num_lines = time_epoch/time_steps
530 incWidth=event_rect.width()/num_lines
531 pos=event_rect.left()
532 timeline_entries = [ ]
535 for line in self.time_lines:
536 old_pos = line.position
537 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
538 if not x or x >= num_lines:
541 if old_pos != new_pos:
542 line.setPosition(new_pos[0], new_pos[1])
545 if not x or line.visible:
546 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
550 if changecount or force:
551 self["timeline_text"].setEntries(timeline_entries)
554 timeline_now = self["timeline_now"]
555 if now >= time_base and now < (time_base + time_epoch * 60):
556 bla = (event_rect.width() * 1000) / time_epoch
557 xpos = ((now/60) - (time_base/60)) * bla / 1000
558 old_pos = timeline_now.position
559 new_pos = (xpos+event_rect.left(), old_pos[1])
560 if old_pos != new_pos:
561 timeline_now.setPosition(new_pos[0], new_pos[1])
562 timeline_now.visible = True
564 timeline_now.visible = False