1 from skin import queryColor
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.Clock import Clock
11 from Screens.Screen import Screen
12 from Screens.EventView import EventViewSimple
13 from Screens.TimeDateInput import TimeDateInput
14 from Screens.TimerEntry import TimerEntry
15 from Screens.EpgSelection import EPGSelection
16 from Tools.Directories import resolveFilename, SCOPE_SKIN_IMAGE
17 from RecordTimer import RecordTimerEntry, parseEvent
18 from ServiceReference import ServiceReference
19 from enigma import eEPGCache, eListbox, eListboxPythonMultiContent, gFont, loadPNG, \
20 RT_HALIGN_LEFT, RT_HALIGN_CENTER, RT_VALIGN_CENTER, RT_WRAP, eRect, eTimer
22 from time import localtime, time, strftime
24 class EPGList(HTMLComponent, GUIComponent):
25 def __init__(self, selChangedCB=None, timer = None, time_epoch = 120, overjump_empty=True):
27 self.cur_service = None
30 self.onSelChanged = [ ]
31 if selChangedCB is not None:
32 self.onSelChanged.append(selChangedCB)
33 GUIComponent.__init__(self)
34 self.l = eListboxPythonMultiContent()
35 self.l.setItemHeight(54);
36 self.l.setBuildFunc(self.buildEntry)
38 self.l.setSelectableFunc(self.isSelectable)
39 self.epgcache = eEPGCache.getInstance()
40 self.clock_pixmap = loadPNG(resolveFilename(SCOPE_SKIN_IMAGE, 'epgclock-fs8.png'))
42 self.time_epoch = time_epoch
44 self.event_rect = None
47 col = queryColor("GraphEpg.Foreground")
48 self.foreColor = col and col.argb()
50 col = queryColor("GraphEpg.Border")
51 self.borderColor = col and col.argb()
53 col = queryColor("GraphEpg.Background")
55 self.backColor = 0x586d88
57 self.backColor = col.argb()
59 col = queryColor("GraphEpg.BackgroundSelected")
61 self.backColorSelected = 0x808080
63 self.backColorSelected = col.argb()
65 print "foreColor", self.foreColor, "borderColor", self.borderColor, "backColor", self.backColor, "backColorSel", self.backColorSelected
67 def isSelectable(self, service, sname, event_list):
68 return (event_list and len(event_list) and True) or False
70 def setEpoch(self, epoch):
71 if self.cur_event is not None and self.cur_service is not None:
73 self.time_epoch = epoch
74 self.fillMultiEPG(None) # refill
76 def getEventFromId(self, service, eventid):
78 if self.epgcache is not None and eventid is not None:
79 event = self.epgcache.lookupEventId(service.ref, eventid)
83 if self.cur_service is None or self.cur_event is None:
85 old_service = self.cur_service #(service, service_name, events)
86 events = self.cur_service[2]
87 refstr = self.cur_service[0]
88 if not events or not len(events):
90 event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
92 service = ServiceReference(refstr)
93 event = self.getEventFromId(service, eventid)
94 return ( event, service )
96 def connectSelectionChanged(func):
97 if not self.onSelChanged.count(func):
98 self.onSelChanged.append(func)
100 def disconnectSelectionChanged(func):
101 self.onSelChanged.remove(func)
103 def serviceChanged(self):
104 cur_sel = self.l.getCurrentSelection()
108 def findBestEvent(self):
109 old_service = self.cur_service #(service, service_name, events)
110 cur_service = self.cur_service = self.l.getCurrentSelection()
112 if old_service and self.cur_event is not None:
113 events = old_service[2]
114 cur_event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
115 last_time = cur_event[2]
118 events = cur_service[2]
119 if events and len(events):
122 best = len(events) #set invalid
124 for event in events: #iterate all events
125 diff = abs(event[2]-last_time)
126 if (best == len(events)) or (diff < best_diff):
130 if best != len(events):
131 self.cur_event = best
133 self.cur_event = None
136 def selectionChanged(self):
137 for x in self.onSelChanged:
142 print "FIXME in EPGList.selectionChanged"
145 GUI_WIDGET = eListbox
147 def postWidgetCreate(self, instance):
148 instance.setWrapAround(True)
149 instance.selectionChanged.get().append(self.serviceChanged)
150 instance.setContent(self.l)
152 def recalcEntrySize(self):
153 esize = self.l.getItemSize()
154 self.l.setFont(0, gFont("Regular", 20))
155 self.l.setFont(1, gFont("Regular", 14))
156 width = esize.width()
157 height = esize.height()
160 self.service_rect = Rect(xpos, 0, w-10, height)
163 self.event_rect = Rect(xpos, 0, w, height)
164 self.l.setSelectionClip(eRect(xpos, 0, w, height), False)
166 def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
167 xpos = (stime - start) * width / (end - start)
168 ewidth = (stime + duration - start) * width / (end - start)
173 if (xpos+ewidth) > width:
174 ewidth = width - xpos
177 def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
178 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
179 return xpos+event_rect.left(), width
181 def buildEntry(self, service, service_name, events):
184 res = [ None ] # no private data needed
185 res.append((eListboxPythonMultiContent.TYPE_TEXT, r1.left(), r1.top(), r1.width(), r1.height(), 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, service_name))
188 start = self.time_base+self.offs*self.time_epoch*60
189 end = start + self.time_epoch * 60
194 foreColor = self.foreColor
195 backColor = self.backColor
196 backColorSelected = self.backColorSelected
197 borderColor = self.borderColor
199 for ev in events: #(event_id, event_title, begin_time, duration)
200 rec=self.timer.isInTimer(ev[0], ev[2], ev[3], service) > ((ev[3]/10)*8)
201 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
202 if self.borderColor is None:
203 res.append((eListboxPythonMultiContent.TYPE_TEXT, left+xpos, top, ewidth, height, 1, RT_HALIGN_CENTER|RT_VALIGN_CENTER|RT_WRAP, ev[1], foreColor, backColor, backColorSelected, 1))
205 res.append((eListboxPythonMultiContent.TYPE_TEXT, left+xpos, top, ewidth, height, 1, RT_HALIGN_CENTER|RT_VALIGN_CENTER|RT_WRAP, ev[1], foreColor, backColor, backColorSelected, 1, borderColor))
206 if rec and ewidth > 23:
207 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, left+xpos+ewidth-22, top+height-22, 21, 21, self.clock_pixmap, backColor, backColorSelected))
210 def selEntry(self, dir, visible=True):
211 cur_service = self.cur_service #(service, service_name, events)
212 if not self.event_rect:
213 self.recalcEntrySize()
214 if cur_service and self.cur_event is not None:
216 entries = cur_service[2]
217 if dir == 0: #current
219 elif dir == +1: #next
220 if self.cur_event+1 < len(entries):
224 self.fillMultiEPG(None) # refill
226 elif dir == -1: #prev
227 if self.cur_event-1 >= 0:
231 self.fillMultiEPG(None) # refill
233 entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
234 time_base = self.time_base+self.offs*self.time_epoch*60
235 xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
236 self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
238 self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
239 self.selectionChanged()
242 def queryEPG(self, list, buildFunc=None):
243 if self.epgcache is not None:
244 if buildFunc is not None:
245 return self.epgcache.lookupEvent(list, buildFunc)
247 return self.epgcache.lookupEvent(list)
250 def fillMultiEPG(self, services, stime=-1):
252 time_base = self.time_base+self.offs*self.time_epoch*60
253 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
255 self.cur_event = None
256 self.cur_service = None
257 self.time_base = int(stime)
258 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
259 test.insert(0, 'RnITBD')
260 epg_data = self.queryEPG(test)
268 if tmp_list is not None:
269 self.list.append((service, sname, tmp_list[0][0] and tmp_list))
273 tmp_list.append((x[2], x[3], x[4], x[5]))
274 if tmp_list and len(tmp_list):
275 self.list.append((service, sname, tmp_list[0][0] and tmp_list))
277 self.l.setList(self.list)
280 def getEventRect(self):
281 return self.event_rect
283 def getTimeEpoch(self):
284 return self.time_epoch
286 def getTimeBase(self):
287 return self.time_base + (self.offs * self.time_epoch * 60)
289 def resetOffset(self):
292 class TimelineText(HTMLComponent, GUIComponent):
294 GUIComponent.__init__(self)
295 self.l = eListboxPythonMultiContent()
296 self.l.setSelectionClip(eRect(0,0,0,0))
297 self.l.setItemHeight(25);
298 self.l.setFont(0, gFont("Regular", 20))
300 GUI_WIDGET = eListbox
302 def postWidgetCreate(self, instance):
303 instance.setContent(self.l)
305 def setEntries(self, entries):
306 res = [ None ] # no private data needed
310 str = strftime("%H:%M", localtime(tm))
311 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
312 self.l.setList([res])
314 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
315 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
317 class GraphMultiEPG(Screen):
318 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
319 Screen.__init__(self, session)
320 self.bouquetChangeCB = bouquetChangeCB
323 self.ask_time = now - tmp
324 self.closeRecursive = False
325 self["key_red"] = Button("")
326 self["key_green"] = Button(_("Add timer"))
327 self["timeline_text"] = TimelineText()
328 self["Event"] = Event()
329 self["Clock"] = Clock()
330 self.time_lines = [ ]
331 for x in (0,1,2,3,4,5):
333 self.time_lines.append(pm)
334 self["timeline%d"%(x)] = pm
335 self["timeline_now"] = Pixmap()
336 self.services = services
337 self.zapFunc = zapFunc
339 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
341 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
343 "cancel": self.closeScreen,
344 "ok": self.eventSelected,
345 "timerAdd": self.timerAdd,
346 "info": self.infoKeyPressed,
348 "input_date_time": self.enterDateTime,
349 "nextBouquet": self.nextBouquet,
350 "prevBouquet": self.prevBouquet,
352 self["actions"].csel = self
354 self["input_actions"] = ActionMap(["InputActions"],
356 "left": self.leftPressed,
357 "right": self.rightPressed,
365 self.updateTimelineTimer = eTimer()
366 self.updateTimelineTimer.timeout.get().append(self.moveTimeLines)
367 self.updateTimelineTimer.start(60*1000)
368 self.onLayoutFinish.append(self.onCreate)
370 def leftPressed(self):
373 def rightPressed(self):
376 def nextEvent(self, visible=True):
377 ret = self["list"].selEntry(+1, visible)
379 self.moveTimeLines(True)
381 def prevEvent(self, visible=True):
382 ret = self["list"].selEntry(-1, visible)
384 self.moveTimeLines(True)
387 self["list"].setEpoch(60)
388 config.misc.graph_mepg_prev_time_period.value = 60
392 self["list"].setEpoch(120)
393 config.misc.graph_mepg_prev_time_period.value = 120
397 self["list"].setEpoch(180)
398 config.misc.graph_mepg_prev_time_period.value = 180
402 self["list"].setEpoch(240)
403 config.misc.graph_mepg_prev_time_period.value = 240
407 self["list"].setEpoch(300)
408 config.misc.graph_mepg_prev_time_period.value = 300
411 def nextBouquet(self):
412 if self.bouquetChangeCB:
413 self.bouquetChangeCB(1, self)
415 def prevBouquet(self):
416 if self.bouquetChangeCB:
417 self.bouquetChangeCB(-1, self)
419 def enterDateTime(self):
420 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
422 def onDateTimeInputClosed(self, ret):
428 l.fillMultiEPG(self.services, ret[1])
429 self.moveTimeLines(True)
431 def closeScreen(self):
432 self.close(self.closeRecursive)
434 def infoKeyPressed(self):
435 cur = self["list"].getCurrent()
438 if event is not None:
439 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
441 def openSimilarList(self, eventid, refstr):
442 self.session.open(EPGSelection, refstr, None, eventid)
444 def setServices(self, services):
445 self.services = services
448 #just used in multipeg
450 self["list"].fillMultiEPG(self.services, self.ask_time)
453 def eventViewCallback(self, setEvent, setService, val):
457 self.prevEvent(False)
459 self.nextEvent(False)
461 if cur[0] is None and cur[1].ref != old[1].ref:
462 self.eventViewCallback(setEvent, setService, val)
468 if self.zapFunc and self["key_red"].getText() == "Zap":
469 self.closeRecursive = True
470 ref = self["list"].getCurrent()[1]
471 self.zapFunc(ref.ref)
473 def eventSelected(self):
474 self.infoKeyPressed()
477 cur = self["list"].getCurrent()
482 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
483 self.session.openWithCallback(self.timerEditFinished, TimerEntry, newEntry)
485 def timerEditFinished(self, answer):
487 self.session.nav.RecordTimer.record(answer[1])
489 print "Timeredit aborted"
491 def onSelectionChanged(self):
492 evt = self["list"].getCurrent()
493 self["Event"].newEvent(evt and evt[0])
497 start = evt.getBeginTime()
498 end = start + evt.getDuration()
499 if now >= start and now <= end:
500 self["key_red"].setText("Zap")
502 self["key_red"].setText("")
504 def moveTimeLines(self, force=False):
506 event_rect = l.getEventRect()
507 time_epoch = l.getTimeEpoch()
508 time_base = l.getTimeBase()
509 if event_rect is None or time_epoch is None or time_base is None:
511 time_steps = time_epoch > 180 and 60 or 30
512 num_lines = time_epoch/time_steps
513 incWidth=event_rect.width()/num_lines
514 pos=event_rect.left()
515 timeline_entries = [ ]
518 for line in self.time_lines:
519 old_pos = line.position
520 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
521 if not x or x >= num_lines:
524 if old_pos != new_pos:
525 line.setPosition(new_pos[0], new_pos[1])
528 if not x or line.visible:
529 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
533 if changecount or force:
534 self["timeline_text"].setEntries(timeline_entries)
537 timeline_now = self["timeline_now"]
538 if now >= time_base and now < (time_base + time_epoch * 60):
539 bla = (event_rect.width() * 1000) / time_epoch
540 xpos = ((now/60) - (time_base/60)) * bla / 1000
541 old_pos = timeline_now.position
542 new_pos = (xpos+event_rect.left(), old_pos[1])
543 if old_pos != new_pos:
544 timeline_now.setPosition(new_pos[0], new_pos[1])
545 timeline_now.visible = True
547 timeline_now.visible = False