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]
493 self.zapFunc(ref.ref)
495 def eventSelected(self):
496 self.infoKeyPressed()
499 cur = self["list"].getCurrent()
504 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
505 self.session.openWithCallback(self.finishedAdd, TimerEntry, newEntry)
507 def finishedAdd(self, answer):
511 simulTimerList = self.session.nav.RecordTimer.record(entry)
512 if simulTimerList is not None:
513 if (len(simulTimerList) == 2) and (simulTimerList[1].dontSave) and (simulTimerList[1].autoincrease):
514 simulTimerList[1].end = entry.begin - 30
515 self.session.nav.RecordTimer.timeChanged(simulTimerList[1])
516 self.session.nav.RecordTimer.record(entry)
518 self.session.openWithCallback(self.finishSanityCorrection, TimerSanityConflict, simulTimerList)
520 print "Timeredit aborted"
522 def finishSanityCorrection(self, answer):
523 self.finishedAdd(answer)
525 def onSelectionChanged(self):
526 evt = self["list"].getCurrent()
527 self["Event"].newEvent(evt and evt[0])
531 start = evt.getBeginTime()
532 end = start + evt.getDuration()
533 if now >= start and now <= end:
534 self["key_red"].setText("Zap")
536 self["key_red"].setText("")
538 def moveTimeLines(self, force=False):
540 event_rect = l.getEventRect()
541 time_epoch = l.getTimeEpoch()
542 time_base = l.getTimeBase()
543 if event_rect is None or time_epoch is None or time_base is None:
545 time_steps = time_epoch > 180 and 60 or 30
546 num_lines = time_epoch/time_steps
547 incWidth=event_rect.width()/num_lines
548 pos=event_rect.left()
549 timeline_entries = [ ]
552 for line in self.time_lines:
553 old_pos = line.position
554 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
555 if not x or x >= num_lines:
558 if old_pos != new_pos:
559 line.setPosition(new_pos[0], new_pos[1])
562 if not x or line.visible:
563 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
567 if changecount or force:
568 self["timeline_text"].setEntries(timeline_entries)
571 timeline_now = self["timeline_now"]
572 if now >= time_base and now < (time_base + time_epoch * 60):
573 bla = (event_rect.width() * 1000) / time_epoch
574 xpos = ((now/60) - (time_base/60)) * bla / 1000
575 old_pos = timeline_now.position
576 new_pos = (xpos+event_rect.left(), old_pos[1])
577 if old_pos != new_pos:
578 timeline_now.setPosition(new_pos[0], new_pos[1])
579 timeline_now.visible = True
581 timeline_now.visible = False