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):
282 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
284 def getTimeEpoch(self):
285 return self.time_epoch
287 def getTimeBase(self):
288 return self.time_base + (self.offs * self.time_epoch * 60)
290 def resetOffset(self):
293 class TimelineText(HTMLComponent, GUIComponent):
295 GUIComponent.__init__(self)
296 self.l = eListboxPythonMultiContent()
297 self.l.setSelectionClip(eRect(0,0,0,0))
298 self.l.setItemHeight(25);
299 self.l.setFont(0, gFont("Regular", 20))
301 GUI_WIDGET = eListbox
303 def postWidgetCreate(self, instance):
304 instance.setContent(self.l)
306 def setEntries(self, entries):
307 res = [ None ] # no private data needed
311 str = strftime("%H:%M", localtime(tm))
312 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
313 self.l.setList([res])
315 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
316 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
318 class GraphMultiEPG(Screen):
319 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
320 Screen.__init__(self, session)
321 self.bouquetChangeCB = bouquetChangeCB
324 self.ask_time = now - tmp
325 self.closeRecursive = False
326 self["key_red"] = Button("")
327 self["key_green"] = Button(_("Add timer"))
328 self["timeline_text"] = TimelineText()
329 self["Event"] = Event()
330 self["Clock"] = Clock()
331 self.time_lines = [ ]
332 for x in (0,1,2,3,4,5):
334 self.time_lines.append(pm)
335 self["timeline%d"%(x)] = pm
336 self["timeline_now"] = Pixmap()
337 self.services = services
338 self.zapFunc = zapFunc
340 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
342 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
344 "cancel": self.closeScreen,
345 "ok": self.eventSelected,
346 "timerAdd": self.timerAdd,
347 "info": self.infoKeyPressed,
349 "input_date_time": self.enterDateTime,
350 "nextBouquet": self.nextBouquet,
351 "prevBouquet": self.prevBouquet,
353 self["actions"].csel = self
355 self["input_actions"] = ActionMap(["InputActions"],
357 "left": self.leftPressed,
358 "right": self.rightPressed,
366 self.updateTimelineTimer = eTimer()
367 self.updateTimelineTimer.timeout.get().append(self.moveTimeLines)
368 self.updateTimelineTimer.start(60*1000)
369 self.onLayoutFinish.append(self.onCreate)
371 def leftPressed(self):
374 def rightPressed(self):
377 def nextEvent(self, visible=True):
378 ret = self["list"].selEntry(+1, visible)
380 self.moveTimeLines(True)
382 def prevEvent(self, visible=True):
383 ret = self["list"].selEntry(-1, visible)
385 self.moveTimeLines(True)
388 self["list"].setEpoch(60)
389 config.misc.graph_mepg_prev_time_period.value = 60
393 self["list"].setEpoch(120)
394 config.misc.graph_mepg_prev_time_period.value = 120
398 self["list"].setEpoch(180)
399 config.misc.graph_mepg_prev_time_period.value = 180
403 self["list"].setEpoch(240)
404 config.misc.graph_mepg_prev_time_period.value = 240
408 self["list"].setEpoch(300)
409 config.misc.graph_mepg_prev_time_period.value = 300
412 def nextBouquet(self):
413 if self.bouquetChangeCB:
414 self.bouquetChangeCB(1, self)
416 def prevBouquet(self):
417 if self.bouquetChangeCB:
418 self.bouquetChangeCB(-1, self)
420 def enterDateTime(self):
421 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
423 def onDateTimeInputClosed(self, ret):
429 l.fillMultiEPG(self.services, ret[1])
430 self.moveTimeLines(True)
432 def closeScreen(self):
433 self.close(self.closeRecursive)
435 def infoKeyPressed(self):
436 cur = self["list"].getCurrent()
439 if event is not None:
440 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
442 def openSimilarList(self, eventid, refstr):
443 self.session.open(EPGSelection, refstr, None, eventid)
445 def setServices(self, services):
446 self.services = services
449 #just used in multipeg
451 self["list"].fillMultiEPG(self.services, self.ask_time)
454 def eventViewCallback(self, setEvent, setService, val):
458 self.prevEvent(False)
460 self.nextEvent(False)
462 if cur[0] is None and cur[1].ref != old[1].ref:
463 self.eventViewCallback(setEvent, setService, val)
469 if self.zapFunc and self["key_red"].getText() == "Zap":
470 self.closeRecursive = True
471 ref = self["list"].getCurrent()[1]
472 self.zapFunc(ref.ref)
474 def eventSelected(self):
475 self.infoKeyPressed()
478 cur = self["list"].getCurrent()
483 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
484 self.session.openWithCallback(self.timerEditFinished, TimerEntry, newEntry)
486 def timerEditFinished(self, answer):
488 self.session.nav.RecordTimer.record(answer[1])
490 print "Timeredit aborted"
492 def onSelectionChanged(self):
493 evt = self["list"].getCurrent()
494 self["Event"].newEvent(evt and evt[0])
498 start = evt.getBeginTime()
499 end = start + evt.getDuration()
500 if now >= start and now <= end:
501 self["key_red"].setText("Zap")
503 self["key_red"].setText("")
505 def moveTimeLines(self, force=False):
507 event_rect = l.getEventRect()
508 time_epoch = l.getTimeEpoch()
509 time_base = l.getTimeBase()
510 if event_rect is None or time_epoch is None or time_base is None:
512 time_steps = time_epoch > 180 and 60 or 30
513 num_lines = time_epoch/time_steps
514 incWidth=event_rect.width()/num_lines
515 pos=event_rect.left()
516 timeline_entries = [ ]
519 for line in self.time_lines:
520 old_pos = line.position
521 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
522 if not x or x >= num_lines:
525 if old_pos != new_pos:
526 line.setPosition(new_pos[0], new_pos[1])
529 if not x or line.visible:
530 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
534 if changecount or force:
535 self["timeline_text"].setEntries(timeline_entries)
538 timeline_now = self["timeline_now"]
539 if now >= time_base and now < (time_base + time_epoch * 60):
540 bla = (event_rect.width() * 1000) / time_epoch
541 xpos = ((now/60) - (time_base/60)) * bla / 1000
542 old_pos = timeline_now.position
543 new_pos = (xpos+event_rect.left(), old_pos[1])
544 if old_pos != new_pos:
545 timeline_now.setPosition(new_pos[0], new_pos[1])
546 timeline_now.visible = True
548 timeline_now.visible = False