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 Components.TimerList import TimerList
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 Screens.TimerEdit import TimerSanityConflict
18 from Tools.Directories import resolveFilename, SCOPE_SKIN_IMAGE
19 from RecordTimer import RecordTimerEntry, parseEvent
20 from ServiceReference import ServiceReference
21 from Tools.LoadPixmap import LoadPixmap
22 from enigma import eEPGCache, eListbox, gFont, eListboxPythonMultiContent, \
23 RT_HALIGN_LEFT, RT_HALIGN_CENTER, RT_VALIGN_CENTER, RT_WRAP, eRect, eTimer
25 from time import localtime, time, strftime
27 class EPGList(HTMLComponent, GUIComponent):
28 def __init__(self, selChangedCB=None, timer = None, time_epoch = 120, overjump_empty=True):
30 self.cur_service = None
33 self.onSelChanged = [ ]
34 if selChangedCB is not None:
35 self.onSelChanged.append(selChangedCB)
36 GUIComponent.__init__(self)
37 self.l = eListboxPythonMultiContent()
38 self.l.setItemHeight(54);
39 self.l.setBuildFunc(self.buildEntry)
41 self.l.setSelectableFunc(self.isSelectable)
42 self.epgcache = eEPGCache.getInstance()
43 self.clock_pixmap = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, 'skin_default/icons/epgclock.png'))
45 self.time_epoch = time_epoch
47 self.event_rect = None
50 self.foreColorSelected = None
51 self.borderColor = None
52 self.backColor = 0x586d88
53 self.backColorSelected = 0x808080
54 self.foreColorService = None
55 self.backColorService = None
57 def applySkin(self, desktop, screen):
58 if self.skinAttributes is not None:
60 for (attrib, value) in self.skinAttributes:
61 if attrib == "EntryForegroundColor":
62 self.foreColor = parseColor(value).argb()
63 elif attrib == "EntryForegroundColorSelected":
64 self.foreColorSelected = parseColor(value).argb()
65 elif attrib == "EntryBorderColor":
66 self.borderColor = parseColor(value).argb()
67 elif attrib == "EntryBackgroundColor":
68 self.backColor = parseColor(value).argb()
69 elif attrib == "EntryBackgroundColorSelected":
70 self.backColorSelected = parseColor(value).argb()
71 elif attrib == "ServiceNameForegroundColor":
72 self.foreColorService = parseColor(value).argb()
73 elif attrib == "ServiceNameBackgroundColor":
74 self.backColorService = parseColor(value).argb()
76 attribs.append((attrib,value))
77 self.skinAttributes = attribs
78 return GUIComponent.applySkin(self, desktop, screen)
80 def isSelectable(self, service, sname, event_list):
81 return (event_list and len(event_list) and True) or False
83 def setEpoch(self, epoch):
84 if self.cur_event is not None and self.cur_service is not None:
86 self.time_epoch = epoch
87 self.fillMultiEPG(None) # refill
89 def getEventFromId(self, service, eventid):
91 if self.epgcache is not None and eventid is not None:
92 event = self.epgcache.lookupEventId(service.ref, eventid)
96 if self.cur_service is None:
98 old_service = self.cur_service #(service, service_name, events)
99 events = self.cur_service[2]
100 refstr = self.cur_service[0]
101 if self.cur_event is None or not events or not len(events):
102 return ( None, ServiceReference(refstr) )
103 event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
105 service = ServiceReference(refstr)
106 event = self.getEventFromId(service, eventid)
107 return ( event, service )
109 def connectSelectionChanged(func):
110 if not self.onSelChanged.count(func):
111 self.onSelChanged.append(func)
113 def disconnectSelectionChanged(func):
114 self.onSelChanged.remove(func)
116 def serviceChanged(self):
117 cur_sel = self.l.getCurrentSelection()
121 def findBestEvent(self):
122 old_service = self.cur_service #(service, service_name, events)
123 cur_service = self.cur_service = self.l.getCurrentSelection()
125 time_base = self.getTimeBase()
126 if old_service and self.cur_event is not None:
127 events = old_service[2]
128 cur_event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
129 last_time = cur_event[2]
130 if last_time < time_base:
131 last_time = time_base
134 events = cur_service[2]
135 if events and len(events):
138 best = len(events) #set invalid
140 for event in events: #iterate all events
142 if ev_time < time_base:
144 diff = abs(ev_time-last_time)
145 if (best == len(events)) or (diff < best_diff):
149 if best != len(events):
150 self.cur_event = best
152 self.cur_event = None
155 def selectionChanged(self):
156 for x in self.onSelChanged:
162 # print "FIXME in EPGList.selectionChanged"
165 GUI_WIDGET = eListbox
167 def postWidgetCreate(self, instance):
168 instance.setWrapAround(True)
169 instance.selectionChanged.get().append(self.serviceChanged)
170 instance.setContent(self.l)
171 self.l.setFont(0, gFont("Regular", 20))
172 self.l.setFont(1, gFont("Regular", 14))
173 self.l.setSelectionClip(eRect(0,0,0,0), False)
175 def preWidgetRemove(self, instance):
176 instance.selectionChanged.get().remove(self.serviceChanged)
177 instance.setContent(None)
179 def recalcEntrySize(self):
180 esize = self.l.getItemSize()
181 width = esize.width()
182 height = esize.height()
185 self.service_rect = Rect(xpos, 0, w-10, height)
188 self.event_rect = Rect(xpos, 0, w, height)
190 def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
191 xpos = (stime - start) * width / (end - start)
192 ewidth = (stime + duration - start) * width / (end - start)
197 if (xpos+ewidth) > width:
198 ewidth = width - xpos
201 def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
202 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
203 return xpos+event_rect.left(), width
205 def buildEntry(self, service, service_name, events):
208 res = [ None, MultiContentEntryText(
209 pos = (r1.left(),r1.top()),
210 size = (r1.width(), r1.height()),
211 font = 0, flags = RT_HALIGN_LEFT | RT_VALIGN_CENTER,
213 color = self.foreColorService,
214 backcolor = self.backColorService) ]
217 start = self.time_base+self.offs*self.time_epoch*60
218 end = start + self.time_epoch * 60
223 foreColor = self.foreColor
224 foreColorSelected = self.foreColorSelected
225 backColor = self.backColor
226 backColorSelected = self.backColorSelected
227 borderColor = self.borderColor
229 for ev in events: #(event_id, event_title, begin_time, duration)
230 rec=ev[2] and self.timer.isInTimer(ev[0], ev[2], ev[3], service) > ((ev[3]/10)*8)
231 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
232 res.append(MultiContentEntryText(
233 pos = (left+xpos, top), size = (ewidth, height),
234 font = 1, flags = RT_HALIGN_CENTER | RT_VALIGN_CENTER | RT_WRAP,
235 text = ev[1], color = foreColor, color_sel = foreColorSelected,
236 backcolor = backColor, backcolor_sel = backColorSelected, border_width = 1, border_color = borderColor))
237 if rec and ewidth > 23:
238 res.append(MultiContentEntryPixmapAlphaTest(
239 pos = (left+xpos+ewidth-22, top+height-22), size = (21, 21),
240 png = self.clock_pixmap, backcolor = backColor,
241 backcolor_sel = backColorSelected))
244 def selEntry(self, dir, visible=True):
245 cur_service = self.cur_service #(service, service_name, events)
246 self.recalcEntrySize()
247 valid_event = self.cur_event is not None
250 entries = cur_service[2]
251 if dir == 0: #current
253 elif dir == +1: #next
254 if valid_event and self.cur_event+1 < len(entries):
258 self.fillMultiEPG(None) # refill
260 elif dir == -1: #prev
261 if valid_event and self.cur_event-1 >= 0:
265 self.fillMultiEPG(None) # refill
267 if cur_service and valid_event:
268 entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
269 time_base = self.time_base+self.offs*self.time_epoch*60
270 xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
271 self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
273 self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
274 self.selectionChanged()
277 def queryEPG(self, list, buildFunc=None):
278 if self.epgcache is not None:
279 if buildFunc is not None:
280 return self.epgcache.lookupEvent(list, buildFunc)
282 return self.epgcache.lookupEvent(list)
285 def fillMultiEPG(self, services, stime=-1):
287 time_base = self.time_base+self.offs*self.time_epoch*60
288 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
290 self.cur_event = None
291 self.cur_service = None
292 self.time_base = int(stime)
293 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
294 test.insert(0, 'XRnITBD')
298 epg_data = self.queryEPG(test)
308 if tmp_list is not None:
309 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
313 tmp_list.append((x[2], x[3], x[4], x[5]))
314 if tmp_list and len(tmp_list):
315 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
317 self.l.setList(self.list)
320 def getEventRect(self):
322 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
324 def getTimeEpoch(self):
325 return self.time_epoch
327 def getTimeBase(self):
328 return self.time_base + (self.offs * self.time_epoch * 60)
330 def resetOffset(self):
333 class TimelineText(HTMLComponent, GUIComponent):
335 GUIComponent.__init__(self)
336 self.l = eListboxPythonMultiContent()
337 self.l.setSelectionClip(eRect(0,0,0,0))
338 self.l.setItemHeight(25);
339 self.l.setFont(0, gFont("Regular", 20))
341 GUI_WIDGET = eListbox
343 def postWidgetCreate(self, instance):
344 instance.setContent(self.l)
346 def setEntries(self, entries):
347 res = [ None ] # no private data needed
351 str = strftime("%H:%M", localtime(tm))
352 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
353 self.l.setList([res])
355 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
356 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
358 class GraphMultiEPG(Screen):
359 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
360 Screen.__init__(self, session)
361 self.bouquetChangeCB = bouquetChangeCB
364 self.ask_time = now - tmp
365 self.closeRecursive = False
366 self["key_red"] = Button("")
367 self["key_green"] = Button(_("Add timer"))
368 self["timeline_text"] = TimelineText()
369 self["Event"] = Event()
370 self.time_lines = [ ]
371 for x in (0,1,2,3,4,5):
373 self.time_lines.append(pm)
374 self["timeline%d"%(x)] = pm
375 self["timeline_now"] = Pixmap()
376 self.services = services
377 self.zapFunc = zapFunc
379 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
381 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
383 "cancel": self.closeScreen,
384 "ok": self.eventSelected,
385 "timerAdd": self.timerAdd,
386 "info": self.infoKeyPressed,
388 "input_date_time": self.enterDateTime,
389 "nextBouquet": self.nextBouquet,
390 "prevBouquet": self.prevBouquet,
392 self["actions"].csel = self
394 self["input_actions"] = ActionMap(["InputActions"],
396 "left": self.leftPressed,
397 "right": self.rightPressed,
405 self.updateTimelineTimer = eTimer()
406 self.updateTimelineTimer.callback.append(self.moveTimeLines)
407 self.updateTimelineTimer.start(60*1000)
408 self.onLayoutFinish.append(self.onCreate)
410 def leftPressed(self):
413 def rightPressed(self):
416 def nextEvent(self, visible=True):
417 ret = self["list"].selEntry(+1, visible)
419 self.moveTimeLines(True)
421 def prevEvent(self, visible=True):
422 ret = self["list"].selEntry(-1, visible)
424 self.moveTimeLines(True)
427 self["list"].setEpoch(60)
428 config.misc.graph_mepg_prev_time_period.value = 60
432 self["list"].setEpoch(120)
433 config.misc.graph_mepg_prev_time_period.value = 120
437 self["list"].setEpoch(180)
438 config.misc.graph_mepg_prev_time_period.value = 180
442 self["list"].setEpoch(240)
443 config.misc.graph_mepg_prev_time_period.value = 240
447 self["list"].setEpoch(300)
448 config.misc.graph_mepg_prev_time_period.value = 300
451 def nextBouquet(self):
452 if self.bouquetChangeCB:
453 self.bouquetChangeCB(1, self)
455 def prevBouquet(self):
456 if self.bouquetChangeCB:
457 self.bouquetChangeCB(-1, self)
459 def enterDateTime(self):
460 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
462 def onDateTimeInputClosed(self, ret):
468 l.fillMultiEPG(self.services, ret[1])
469 self.moveTimeLines(True)
471 def closeScreen(self):
472 self.close(self.closeRecursive)
474 def infoKeyPressed(self):
475 cur = self["list"].getCurrent()
478 if event is not None:
479 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
481 def openSimilarList(self, eventid, refstr):
482 self.session.open(EPGSelection, refstr, None, eventid)
484 def setServices(self, services):
485 self.services = services
488 #just used in multipeg
490 self["list"].fillMultiEPG(self.services, self.ask_time)
493 def eventViewCallback(self, setEvent, setService, val):
497 self.prevEvent(False)
499 self.nextEvent(False)
501 if cur[0] is None and cur[1].ref != old[1].ref:
502 self.eventViewCallback(setEvent, setService, val)
508 if self.zapFunc and self["key_red"].getText() == "Zap":
509 self.closeRecursive = True
510 ref = self["list"].getCurrent()[1]
512 self.zapFunc(ref.ref)
514 def eventSelected(self):
515 self.infoKeyPressed()
518 cur = self["list"].getCurrent()
523 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
524 self.session.openWithCallback(self.finishedAdd, TimerEntry, newEntry)
526 def finishedAdd(self, answer):
530 simulTimerList = self.session.nav.RecordTimer.record(entry)
531 if simulTimerList is not None:
532 if (len(simulTimerList) == 2) and (simulTimerList[1].dontSave) and (simulTimerList[1].autoincrease):
533 simulTimerList[1].end = entry.begin - 30
534 self.session.nav.RecordTimer.timeChanged(simulTimerList[1])
535 self.session.nav.RecordTimer.record(entry)
537 self.session.openWithCallback(self.finishSanityCorrection, TimerSanityConflict, simulTimerList)
539 print "Timeredit aborted"
541 def finishSanityCorrection(self, answer):
542 self.finishedAdd(answer)
544 def onSelectionChanged(self):
545 evt = self["list"].getCurrent()
546 self["Event"].newEvent(evt and evt[0])
550 start = evt.getBeginTime()
551 end = start + evt.getDuration()
552 if now >= start and now <= end:
553 self["key_red"].setText("Zap")
555 self["key_red"].setText("")
557 def moveTimeLines(self, force=False):
559 event_rect = l.getEventRect()
560 time_epoch = l.getTimeEpoch()
561 time_base = l.getTimeBase()
562 if event_rect is None or time_epoch is None or time_base is None:
564 time_steps = time_epoch > 180 and 60 or 30
565 num_lines = time_epoch/time_steps
566 incWidth=event_rect.width()/num_lines
567 pos=event_rect.left()
568 timeline_entries = [ ]
571 for line in self.time_lines:
572 old_pos = line.position
573 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
574 if not x or x >= num_lines:
577 if old_pos != new_pos:
578 line.setPosition(new_pos[0], new_pos[1])
581 if not x or line.visible:
582 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
586 if changecount or force:
587 self["timeline_text"].setEntries(timeline_entries)
590 timeline_now = self["timeline_now"]
591 if now >= time_base and now < (time_base + time_epoch * 60):
592 bla = (event_rect.width() * 1000) / time_epoch
593 xpos = ((now/60) - (time_base/60)) * bla / 1000
594 old_pos = timeline_now.position
595 new_pos = (xpos+event_rect.left(), old_pos[1])
596 if old_pos != new_pos:
597 timeline_now.setPosition(new_pos[0], new_pos[1])
598 timeline_now.visible = True
600 timeline_now.visible = False