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 preWidgetRemove(self, instance):
174 instance.selectionChanged.get().remove(self.serviceChanged)
175 instance.setContent(None)
177 def recalcEntrySize(self):
178 esize = self.l.getItemSize()
179 width = esize.width()
180 height = esize.height()
183 self.service_rect = Rect(xpos, 0, w-10, height)
186 self.event_rect = Rect(xpos, 0, w, height)
188 def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
189 xpos = (stime - start) * width / (end - start)
190 ewidth = (stime + duration - start) * width / (end - start)
195 if (xpos+ewidth) > width:
196 ewidth = width - xpos
199 def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
200 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
201 return xpos+event_rect.left(), width
203 def buildEntry(self, service, service_name, events):
206 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) ]
209 start = self.time_base+self.offs*self.time_epoch*60
210 end = start + self.time_epoch * 60
215 foreColor = self.foreColor
216 foreColorSelected = self.foreColorSelected
217 backColor = self.backColor
218 backColorSelected = self.backColorSelected
219 borderColor = self.borderColor
221 for ev in events: #(event_id, event_title, begin_time, duration)
222 rec=ev[2] and self.timer.isInTimer(ev[0], ev[2], ev[3], service) > ((ev[3]/10)*8)
223 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
224 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))
225 if rec and ewidth > 23:
226 res.append(MultiContentEntryPixmapAlphaTest(pos = (left+xpos+ewidth-22, top+height-22), size = (21, 21), png = self.clock_pixmap, backcolor = backColor, backcolor_sel = backColorSelected))
229 def selEntry(self, dir, visible=True):
230 cur_service = self.cur_service #(service, service_name, events)
231 self.recalcEntrySize()
232 valid_event = self.cur_event is not None
235 entries = cur_service[2]
236 if dir == 0: #current
238 elif dir == +1: #next
239 if valid_event and self.cur_event+1 < len(entries):
243 self.fillMultiEPG(None) # refill
245 elif dir == -1: #prev
246 if valid_event and self.cur_event-1 >= 0:
250 self.fillMultiEPG(None) # refill
252 if cur_service and valid_event:
253 entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
254 time_base = self.time_base+self.offs*self.time_epoch*60
255 xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
256 self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
258 self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
259 self.selectionChanged()
262 def queryEPG(self, list, buildFunc=None):
263 if self.epgcache is not None:
264 if buildFunc is not None:
265 return self.epgcache.lookupEvent(list, buildFunc)
267 return self.epgcache.lookupEvent(list)
270 def fillMultiEPG(self, services, stime=-1):
272 time_base = self.time_base+self.offs*self.time_epoch*60
273 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
275 self.cur_event = None
276 self.cur_service = None
277 self.time_base = int(stime)
278 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
279 test.insert(0, 'XRnITBD')
280 epg_data = self.queryEPG(test)
288 if tmp_list is not None:
289 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
293 tmp_list.append((x[2], x[3], x[4], x[5]))
294 if tmp_list and len(tmp_list):
295 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
297 self.l.setList(self.list)
300 def getEventRect(self):
302 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
304 def getTimeEpoch(self):
305 return self.time_epoch
307 def getTimeBase(self):
308 return self.time_base + (self.offs * self.time_epoch * 60)
310 def resetOffset(self):
313 class TimelineText(HTMLComponent, GUIComponent):
315 GUIComponent.__init__(self)
316 self.l = eListboxPythonMultiContent()
317 self.l.setSelectionClip(eRect(0,0,0,0))
318 self.l.setItemHeight(25);
319 self.l.setFont(0, gFont("Regular", 20))
321 GUI_WIDGET = eListbox
323 def postWidgetCreate(self, instance):
324 instance.setContent(self.l)
326 def setEntries(self, entries):
327 res = [ None ] # no private data needed
331 str = strftime("%H:%M", localtime(tm))
332 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
333 self.l.setList([res])
335 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
336 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
338 class GraphMultiEPG(Screen):
339 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
340 Screen.__init__(self, session)
341 self.bouquetChangeCB = bouquetChangeCB
344 self.ask_time = now - tmp
345 self.closeRecursive = False
346 self["key_red"] = Button("")
347 self["key_green"] = Button(_("Add timer"))
348 self["timeline_text"] = TimelineText()
349 self["Event"] = Event()
350 self["Clock"] = ObsoleteSource(new_source = "global.CurrentTime", removal_date = "2008-01")
351 self.time_lines = [ ]
352 for x in (0,1,2,3,4,5):
354 self.time_lines.append(pm)
355 self["timeline%d"%(x)] = pm
356 self["timeline_now"] = Pixmap()
357 self.services = services
358 self.zapFunc = zapFunc
360 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
362 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
364 "cancel": self.closeScreen,
365 "ok": self.eventSelected,
366 "timerAdd": self.timerAdd,
367 "info": self.infoKeyPressed,
369 "input_date_time": self.enterDateTime,
370 "nextBouquet": self.nextBouquet,
371 "prevBouquet": self.prevBouquet,
373 self["actions"].csel = self
375 self["input_actions"] = ActionMap(["InputActions"],
377 "left": self.leftPressed,
378 "right": self.rightPressed,
386 self.updateTimelineTimer = eTimer()
387 self.updateTimelineTimer.callback.append(self.moveTimeLines)
388 self.updateTimelineTimer.start(60*1000)
389 self.onLayoutFinish.append(self.onCreate)
391 def leftPressed(self):
394 def rightPressed(self):
397 def nextEvent(self, visible=True):
398 ret = self["list"].selEntry(+1, visible)
400 self.moveTimeLines(True)
402 def prevEvent(self, visible=True):
403 ret = self["list"].selEntry(-1, visible)
405 self.moveTimeLines(True)
408 self["list"].setEpoch(60)
409 config.misc.graph_mepg_prev_time_period.value = 60
413 self["list"].setEpoch(120)
414 config.misc.graph_mepg_prev_time_period.value = 120
418 self["list"].setEpoch(180)
419 config.misc.graph_mepg_prev_time_period.value = 180
423 self["list"].setEpoch(240)
424 config.misc.graph_mepg_prev_time_period.value = 240
428 self["list"].setEpoch(300)
429 config.misc.graph_mepg_prev_time_period.value = 300
432 def nextBouquet(self):
433 if self.bouquetChangeCB:
434 self.bouquetChangeCB(1, self)
436 def prevBouquet(self):
437 if self.bouquetChangeCB:
438 self.bouquetChangeCB(-1, self)
440 def enterDateTime(self):
441 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
443 def onDateTimeInputClosed(self, ret):
449 l.fillMultiEPG(self.services, ret[1])
450 self.moveTimeLines(True)
452 def closeScreen(self):
453 self.close(self.closeRecursive)
455 def infoKeyPressed(self):
456 cur = self["list"].getCurrent()
459 if event is not None:
460 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
462 def openSimilarList(self, eventid, refstr):
463 self.session.open(EPGSelection, refstr, None, eventid)
465 def setServices(self, services):
466 self.services = services
469 #just used in multipeg
471 self["list"].fillMultiEPG(self.services, self.ask_time)
474 def eventViewCallback(self, setEvent, setService, val):
478 self.prevEvent(False)
480 self.nextEvent(False)
482 if cur[0] is None and cur[1].ref != old[1].ref:
483 self.eventViewCallback(setEvent, setService, val)
489 if self.zapFunc and self["key_red"].getText() == "Zap":
490 self.closeRecursive = True
491 ref = self["list"].getCurrent()[1]
492 self.zapFunc(ref.ref)
494 def eventSelected(self):
495 self.infoKeyPressed()
498 cur = self["list"].getCurrent()
503 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
504 self.session.openWithCallback(self.timerEditFinished, TimerEntry, newEntry)
506 def timerEditFinished(self, answer):
508 self.session.nav.RecordTimer.record(answer[1])
510 print "Timeredit aborted"
512 def onSelectionChanged(self):
513 evt = self["list"].getCurrent()
514 self["Event"].newEvent(evt and evt[0])
518 start = evt.getBeginTime()
519 end = start + evt.getDuration()
520 if now >= start and now <= end:
521 self["key_red"].setText("Zap")
523 self["key_red"].setText("")
525 def moveTimeLines(self, force=False):
527 event_rect = l.getEventRect()
528 time_epoch = l.getTimeEpoch()
529 time_base = l.getTimeBase()
530 if event_rect is None or time_epoch is None or time_base is None:
532 time_steps = time_epoch > 180 and 60 or 30
533 num_lines = time_epoch/time_steps
534 incWidth=event_rect.width()/num_lines
535 pos=event_rect.left()
536 timeline_entries = [ ]
539 for line in self.time_lines:
540 old_pos = line.position
541 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
542 if not x or x >= num_lines:
545 if old_pos != new_pos:
546 line.setPosition(new_pos[0], new_pos[1])
549 if not x or line.visible:
550 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
554 if changecount or force:
555 self["timeline_text"].setEntries(timeline_entries)
558 timeline_now = self["timeline_now"]
559 if now >= time_base and now < (time_base + time_epoch * 60):
560 bla = (event_rect.width() * 1000) / time_epoch
561 xpos = ((now/60) - (time_base/60)) * bla / 1000
562 old_pos = timeline_now.position
563 new_pos = (xpos+event_rect.left(), old_pos[1])
564 if old_pos != new_pos:
565 timeline_now.setPosition(new_pos[0], new_pos[1])
566 timeline_now.visible = True
568 timeline_now.visible = False