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.MultiContent import MultiContentEntryText, MultiContentEntryPixmapAlphaTest
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 Tools.LoadPixmap import LoadPixmap
20 from enigma import eEPGCache, eListbox, gFont, 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 = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, 'skin_default/icons/epgclock.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, screen):
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, screen)
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 preWidgetRemove(self, instance):
173 instance.selectionChanged.get().remove(self.serviceChanged)
174 instance.setContent(None)
176 def recalcEntrySize(self):
177 esize = self.l.getItemSize()
178 width = esize.width()
179 height = esize.height()
182 self.service_rect = Rect(xpos, 0, w-10, height)
185 self.event_rect = Rect(xpos, 0, w, height)
187 def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
188 xpos = (stime - start) * width / (end - start)
189 ewidth = (stime + duration - start) * width / (end - start)
194 if (xpos+ewidth) > width:
195 ewidth = width - xpos
198 def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
199 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
200 return xpos+event_rect.left(), width
202 def buildEntry(self, service, service_name, events):
205 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) ]
208 start = self.time_base+self.offs*self.time_epoch*60
209 end = start + self.time_epoch * 60
214 foreColor = self.foreColor
215 foreColorSelected = self.foreColorSelected
216 backColor = self.backColor
217 backColorSelected = self.backColorSelected
218 borderColor = self.borderColor
220 for ev in events: #(event_id, event_title, begin_time, duration)
221 rec=ev[2] and self.timer.isInTimer(ev[0], ev[2], ev[3], service) > ((ev[3]/10)*8)
222 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
223 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))
224 if rec and ewidth > 23:
225 res.append(MultiContentEntryPixmapAlphaTest(pos = (left+xpos+ewidth-22, top+height-22), size = (21, 21), png = self.clock_pixmap, backcolor = backColor, backcolor_sel = backColorSelected))
228 def selEntry(self, dir, visible=True):
229 cur_service = self.cur_service #(service, service_name, events)
230 self.recalcEntrySize()
231 valid_event = self.cur_event is not None
234 entries = cur_service[2]
235 if dir == 0: #current
237 elif dir == +1: #next
238 if valid_event and self.cur_event+1 < len(entries):
242 self.fillMultiEPG(None) # refill
244 elif dir == -1: #prev
245 if valid_event and self.cur_event-1 >= 0:
249 self.fillMultiEPG(None) # refill
251 if cur_service and valid_event:
252 entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
253 time_base = self.time_base+self.offs*self.time_epoch*60
254 xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
255 self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
257 self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
258 self.selectionChanged()
261 def queryEPG(self, list, buildFunc=None):
262 if self.epgcache is not None:
263 if buildFunc is not None:
264 return self.epgcache.lookupEvent(list, buildFunc)
266 return self.epgcache.lookupEvent(list)
269 def fillMultiEPG(self, services, stime=-1):
271 time_base = self.time_base+self.offs*self.time_epoch*60
272 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
274 self.cur_event = None
275 self.cur_service = None
276 self.time_base = int(stime)
277 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
278 test.insert(0, 'XRnITBD')
279 epg_data = self.queryEPG(test)
287 if tmp_list is not None:
288 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
292 tmp_list.append((x[2], x[3], x[4], x[5]))
293 if tmp_list and len(tmp_list):
294 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
296 self.l.setList(self.list)
299 def getEventRect(self):
301 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
303 def getTimeEpoch(self):
304 return self.time_epoch
306 def getTimeBase(self):
307 return self.time_base + (self.offs * self.time_epoch * 60)
309 def resetOffset(self):
312 class TimelineText(HTMLComponent, GUIComponent):
314 GUIComponent.__init__(self)
315 self.l = eListboxPythonMultiContent()
316 self.l.setSelectionClip(eRect(0,0,0,0))
317 self.l.setItemHeight(25);
318 self.l.setFont(0, gFont("Regular", 20))
320 GUI_WIDGET = eListbox
322 def postWidgetCreate(self, instance):
323 instance.setContent(self.l)
325 def setEntries(self, entries):
326 res = [ None ] # no private data needed
330 str = strftime("%H:%M", localtime(tm))
331 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
332 self.l.setList([res])
334 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
335 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
337 class GraphMultiEPG(Screen):
338 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
339 Screen.__init__(self, session)
340 self.bouquetChangeCB = bouquetChangeCB
343 self.ask_time = now - tmp
344 self.closeRecursive = False
345 self["key_red"] = Button("")
346 self["key_green"] = Button(_("Add timer"))
347 self["timeline_text"] = TimelineText()
348 self["Event"] = Event()
349 self.time_lines = [ ]
350 for x in (0,1,2,3,4,5):
352 self.time_lines.append(pm)
353 self["timeline%d"%(x)] = pm
354 self["timeline_now"] = Pixmap()
355 self.services = services
356 self.zapFunc = zapFunc
358 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
360 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
362 "cancel": self.closeScreen,
363 "ok": self.eventSelected,
364 "timerAdd": self.timerAdd,
365 "info": self.infoKeyPressed,
367 "input_date_time": self.enterDateTime,
368 "nextBouquet": self.nextBouquet,
369 "prevBouquet": self.prevBouquet,
371 self["actions"].csel = self
373 self["input_actions"] = ActionMap(["InputActions"],
375 "left": self.leftPressed,
376 "right": self.rightPressed,
384 self.updateTimelineTimer = eTimer()
385 self.updateTimelineTimer.callback.append(self.moveTimeLines)
386 self.updateTimelineTimer.start(60*1000)
387 self.onLayoutFinish.append(self.onCreate)
389 def leftPressed(self):
392 def rightPressed(self):
395 def nextEvent(self, visible=True):
396 ret = self["list"].selEntry(+1, visible)
398 self.moveTimeLines(True)
400 def prevEvent(self, visible=True):
401 ret = self["list"].selEntry(-1, visible)
403 self.moveTimeLines(True)
406 self["list"].setEpoch(60)
407 config.misc.graph_mepg_prev_time_period.value = 60
411 self["list"].setEpoch(120)
412 config.misc.graph_mepg_prev_time_period.value = 120
416 self["list"].setEpoch(180)
417 config.misc.graph_mepg_prev_time_period.value = 180
421 self["list"].setEpoch(240)
422 config.misc.graph_mepg_prev_time_period.value = 240
426 self["list"].setEpoch(300)
427 config.misc.graph_mepg_prev_time_period.value = 300
430 def nextBouquet(self):
431 if self.bouquetChangeCB:
432 self.bouquetChangeCB(1, self)
434 def prevBouquet(self):
435 if self.bouquetChangeCB:
436 self.bouquetChangeCB(-1, self)
438 def enterDateTime(self):
439 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
441 def onDateTimeInputClosed(self, ret):
447 l.fillMultiEPG(self.services, ret[1])
448 self.moveTimeLines(True)
450 def closeScreen(self):
451 self.close(self.closeRecursive)
453 def infoKeyPressed(self):
454 cur = self["list"].getCurrent()
457 if event is not None:
458 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
460 def openSimilarList(self, eventid, refstr):
461 self.session.open(EPGSelection, refstr, None, eventid)
463 def setServices(self, services):
464 self.services = services
467 #just used in multipeg
469 self["list"].fillMultiEPG(self.services, self.ask_time)
472 def eventViewCallback(self, setEvent, setService, val):
476 self.prevEvent(False)
478 self.nextEvent(False)
480 if cur[0] is None and cur[1].ref != old[1].ref:
481 self.eventViewCallback(setEvent, setService, val)
487 if self.zapFunc and self["key_red"].getText() == "Zap":
488 self.closeRecursive = True
489 ref = self["list"].getCurrent()[1]
490 self.zapFunc(ref.ref)
492 def eventSelected(self):
493 self.infoKeyPressed()
496 cur = self["list"].getCurrent()
501 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, dirname = config.movielist.last_timer_videodir.value, *parseEvent(event))
502 self.session.openWithCallback(self.timerEditFinished, TimerEntry, newEntry)
504 def timerEditFinished(self, answer):
506 self.session.nav.RecordTimer.record(answer[1])
508 print "Timeredit aborted"
510 def onSelectionChanged(self):
511 evt = self["list"].getCurrent()
512 self["Event"].newEvent(evt and evt[0])
516 start = evt.getBeginTime()
517 end = start + evt.getDuration()
518 if now >= start and now <= end:
519 self["key_red"].setText("Zap")
521 self["key_red"].setText("")
523 def moveTimeLines(self, force=False):
525 event_rect = l.getEventRect()
526 time_epoch = l.getTimeEpoch()
527 time_base = l.getTimeBase()
528 if event_rect is None or time_epoch is None or time_base is None:
530 time_steps = time_epoch > 180 and 60 or 30
531 num_lines = time_epoch/time_steps
532 incWidth=event_rect.width()/num_lines
533 pos=event_rect.left()
534 timeline_entries = [ ]
537 for line in self.time_lines:
538 old_pos = line.position
539 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
540 if not x or x >= num_lines:
543 if old_pos != new_pos:
544 line.setPosition(new_pos[0], new_pos[1])
547 if not x or line.visible:
548 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
552 if changecount or force:
553 self["timeline_text"].setEntries(timeline_entries)
556 timeline_now = self["timeline_now"]
557 if now >= time_base and now < (time_base + time_epoch * 60):
558 bla = (event_rect.width() * 1000) / time_epoch
559 xpos = ((now/60) - (time_base/60)) * bla / 1000
560 old_pos = timeline_now.position
561 new_pos = (xpos+event_rect.left(), old_pos[1])
562 if old_pos != new_pos:
563 timeline_now.setPosition(new_pos[0], new_pos[1])
564 timeline_now.visible = True
566 timeline_now.visible = False