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 or self.cur_event 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 not events or not len(events):
102 return ( None, None )
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:
161 print "FIXME in EPGList.selectionChanged"
164 GUI_WIDGET = eListbox
166 def postWidgetCreate(self, instance):
167 instance.setWrapAround(True)
168 instance.selectionChanged.get().append(self.serviceChanged)
169 instance.setContent(self.l)
170 self.l.setFont(0, gFont("Regular", 20))
171 self.l.setFont(1, gFont("Regular", 14))
172 self.l.setSelectionClip(eRect(0,0,0,0), False)
174 def preWidgetRemove(self, instance):
175 instance.selectionChanged.get().remove(self.serviceChanged)
176 instance.setContent(None)
178 def recalcEntrySize(self):
179 esize = self.l.getItemSize()
180 width = esize.width()
181 height = esize.height()
184 self.service_rect = Rect(xpos, 0, w-10, height)
187 self.event_rect = Rect(xpos, 0, w, height)
189 def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
190 xpos = (stime - start) * width / (end - start)
191 ewidth = (stime + duration - start) * width / (end - start)
196 if (xpos+ewidth) > width:
197 ewidth = width - xpos
200 def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
201 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
202 return xpos+event_rect.left(), width
204 def buildEntry(self, service, service_name, events):
207 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) ]
210 start = self.time_base+self.offs*self.time_epoch*60
211 end = start + self.time_epoch * 60
216 foreColor = self.foreColor
217 foreColorSelected = self.foreColorSelected
218 backColor = self.backColor
219 backColorSelected = self.backColorSelected
220 borderColor = self.borderColor
222 for ev in events: #(event_id, event_title, begin_time, duration)
223 rec=ev[2] and self.timer.isInTimer(ev[0], ev[2], ev[3], service) > ((ev[3]/10)*8)
224 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
225 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))
226 if rec and ewidth > 23:
227 res.append(MultiContentEntryPixmapAlphaTest(pos = (left+xpos+ewidth-22, top+height-22), size = (21, 21), png = self.clock_pixmap, backcolor = backColor, backcolor_sel = backColorSelected))
230 def selEntry(self, dir, visible=True):
231 cur_service = self.cur_service #(service, service_name, events)
232 self.recalcEntrySize()
233 valid_event = self.cur_event is not None
236 entries = cur_service[2]
237 if dir == 0: #current
239 elif dir == +1: #next
240 if valid_event and self.cur_event+1 < len(entries):
244 self.fillMultiEPG(None) # refill
246 elif dir == -1: #prev
247 if valid_event and self.cur_event-1 >= 0:
251 self.fillMultiEPG(None) # refill
253 if cur_service and valid_event:
254 entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
255 time_base = self.time_base+self.offs*self.time_epoch*60
256 xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
257 self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
259 self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
260 self.selectionChanged()
263 def queryEPG(self, list, buildFunc=None):
264 if self.epgcache is not None:
265 if buildFunc is not None:
266 return self.epgcache.lookupEvent(list, buildFunc)
268 return self.epgcache.lookupEvent(list)
271 def fillMultiEPG(self, services, stime=-1):
273 time_base = self.time_base+self.offs*self.time_epoch*60
274 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
276 self.cur_event = None
277 self.cur_service = None
278 self.time_base = int(stime)
279 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
280 test.insert(0, 'XRnITBD')
281 epg_data = self.queryEPG(test)
289 if tmp_list is not None:
290 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
294 tmp_list.append((x[2], x[3], x[4], x[5]))
295 if tmp_list and len(tmp_list):
296 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
298 self.l.setList(self.list)
301 def getEventRect(self):
303 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
305 def getTimeEpoch(self):
306 return self.time_epoch
308 def getTimeBase(self):
309 return self.time_base + (self.offs * self.time_epoch * 60)
311 def resetOffset(self):
314 class TimelineText(HTMLComponent, GUIComponent):
316 GUIComponent.__init__(self)
317 self.l = eListboxPythonMultiContent()
318 self.l.setSelectionClip(eRect(0,0,0,0))
319 self.l.setItemHeight(25);
320 self.l.setFont(0, gFont("Regular", 20))
322 GUI_WIDGET = eListbox
324 def postWidgetCreate(self, instance):
325 instance.setContent(self.l)
327 def setEntries(self, entries):
328 res = [ None ] # no private data needed
332 str = strftime("%H:%M", localtime(tm))
333 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
334 self.l.setList([res])
336 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
337 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
339 class GraphMultiEPG(Screen):
340 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
341 Screen.__init__(self, session)
342 self.bouquetChangeCB = bouquetChangeCB
345 self.ask_time = now - tmp
346 self.closeRecursive = False
347 self["key_red"] = Button("")
348 self["key_green"] = Button(_("Add timer"))
349 self["timeline_text"] = TimelineText()
350 self["Event"] = Event()
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.finishedAdd, TimerEntry, newEntry)
506 def finishedAdd(self, answer):
510 simulTimerList = self.session.nav.RecordTimer.record(entry)
511 if simulTimerList is not None:
512 if (len(simulTimerList) == 2) and (simulTimerList[1].dontSave) and (simulTimerList[1].autoincrease):
513 simulTimerList[1].end = entry.begin - 30
514 self.session.nav.RecordTimer.timeChanged(simulTimerList[1])
515 self.session.nav.RecordTimer.record(entry)
517 self.session.openWithCallback(self.finishSanityCorrection, TimerSanityConflict, simulTimerList)
519 print "Timeredit aborted"
521 def finishSanityCorrection(self, answer):
522 self.finishedAdd(answer)
524 def onSelectionChanged(self):
525 evt = self["list"].getCurrent()
526 self["Event"].newEvent(evt and evt[0])
530 start = evt.getBeginTime()
531 end = start + evt.getDuration()
532 if now >= start and now <= end:
533 self["key_red"].setText("Zap")
535 self["key_red"].setText("")
537 def moveTimeLines(self, force=False):
539 event_rect = l.getEventRect()
540 time_epoch = l.getTimeEpoch()
541 time_base = l.getTimeBase()
542 if event_rect is None or time_epoch is None or time_base is None:
544 time_steps = time_epoch > 180 and 60 or 30
545 num_lines = time_epoch/time_steps
546 incWidth=event_rect.width()/num_lines
547 pos=event_rect.left()
548 timeline_entries = [ ]
551 for line in self.time_lines:
552 old_pos = line.position
553 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
554 if not x or x >= num_lines:
557 if old_pos != new_pos:
558 line.setPosition(new_pos[0], new_pos[1])
561 if not x or line.visible:
562 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
566 if changecount or force:
567 self["timeline_text"].setEntries(timeline_entries)
570 timeline_now = self["timeline_now"]
571 if now >= time_base and now < (time_base + time_epoch * 60):
572 bla = (event_rect.width() * 1000) / time_epoch
573 xpos = ((now/60) - (time_base/60)) * bla / 1000
574 old_pos = timeline_now.position
575 new_pos = (xpos+event_rect.left(), old_pos[1])
576 if old_pos != new_pos:
577 timeline_now.setPosition(new_pos[0], new_pos[1])
578 timeline_now.visible = True
580 timeline_now.visible = False