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:
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(
208 pos = (r1.left(),r1.top()),
209 size = (r1.width(), r1.height()),
210 font = 0, flags = RT_HALIGN_LEFT | RT_VALIGN_CENTER,
212 color = self.foreColorService,
213 backcolor = self.backColorService) ]
216 start = self.time_base+self.offs*self.time_epoch*60
217 end = start + self.time_epoch * 60
222 foreColor = self.foreColor
223 foreColorSelected = self.foreColorSelected
224 backColor = self.backColor
225 backColorSelected = self.backColorSelected
226 borderColor = self.borderColor
228 for ev in events: #(event_id, event_title, begin_time, duration)
229 rec=ev[2] and self.timer.isInTimer(ev[0], ev[2], ev[3], service) > ((ev[3]/10)*8)
230 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
231 res.append(MultiContentEntryText(
232 pos = (left+xpos, top), size = (ewidth, height),
233 font = 1, flags = RT_HALIGN_CENTER | RT_VALIGN_CENTER | RT_WRAP,
234 text = ev[1], color = foreColor, color_sel = foreColorSelected,
235 backcolor = backColor, backcolor_sel = backColorSelected, border_width = 1, border_color = borderColor))
236 if rec and ewidth > 23:
237 res.append(MultiContentEntryPixmapAlphaTest(
238 pos = (left+xpos+ewidth-22, top+height-22), size = (21, 21),
239 png = self.clock_pixmap, backcolor = backColor,
240 backcolor_sel = backColorSelected))
243 def selEntry(self, dir, visible=True):
244 cur_service = self.cur_service #(service, service_name, events)
245 self.recalcEntrySize()
246 valid_event = self.cur_event is not None
249 entries = cur_service[2]
250 if dir == 0: #current
252 elif dir == +1: #next
253 if valid_event and self.cur_event+1 < len(entries):
257 self.fillMultiEPG(None) # refill
259 elif dir == -1: #prev
260 if valid_event and self.cur_event-1 >= 0:
264 self.fillMultiEPG(None) # refill
266 if cur_service and valid_event:
267 entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
268 time_base = self.time_base+self.offs*self.time_epoch*60
269 xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
270 self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
272 self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
273 self.selectionChanged()
276 def queryEPG(self, list, buildFunc=None):
277 if self.epgcache is not None:
278 if buildFunc is not None:
279 return self.epgcache.lookupEvent(list, buildFunc)
281 return self.epgcache.lookupEvent(list)
284 def fillMultiEPG(self, services, stime=-1):
286 time_base = self.time_base+self.offs*self.time_epoch*60
287 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
289 self.cur_event = None
290 self.cur_service = None
291 self.time_base = int(stime)
292 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
293 test.insert(0, 'XRnITBD')
297 epg_data = self.queryEPG(test)
307 if tmp_list is not None:
308 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
312 tmp_list.append((x[2], x[3], x[4], x[5]))
313 if tmp_list and len(tmp_list):
314 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
316 self.l.setList(self.list)
319 def getEventRect(self):
321 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
323 def getTimeEpoch(self):
324 return self.time_epoch
326 def getTimeBase(self):
327 return self.time_base + (self.offs * self.time_epoch * 60)
329 def resetOffset(self):
332 class TimelineText(HTMLComponent, GUIComponent):
334 GUIComponent.__init__(self)
335 self.l = eListboxPythonMultiContent()
336 self.l.setSelectionClip(eRect(0,0,0,0))
337 self.l.setItemHeight(25);
338 self.l.setFont(0, gFont("Regular", 20))
340 GUI_WIDGET = eListbox
342 def postWidgetCreate(self, instance):
343 instance.setContent(self.l)
345 def setEntries(self, entries):
346 res = [ None ] # no private data needed
350 str = strftime("%H:%M", localtime(tm))
351 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
352 self.l.setList([res])
354 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
355 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
357 class GraphMultiEPG(Screen):
358 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
359 Screen.__init__(self, session)
360 self.bouquetChangeCB = bouquetChangeCB
363 self.ask_time = now - tmp
364 self.closeRecursive = False
365 self["key_red"] = Button("")
366 self["key_green"] = Button(_("Add timer"))
367 self["timeline_text"] = TimelineText()
368 self["Event"] = Event()
369 self.time_lines = [ ]
370 for x in (0,1,2,3,4,5):
372 self.time_lines.append(pm)
373 self["timeline%d"%(x)] = pm
374 self["timeline_now"] = Pixmap()
375 self.services = services
376 self.zapFunc = zapFunc
378 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
380 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
382 "cancel": self.closeScreen,
383 "ok": self.eventSelected,
384 "timerAdd": self.timerAdd,
385 "info": self.infoKeyPressed,
387 "input_date_time": self.enterDateTime,
388 "nextBouquet": self.nextBouquet,
389 "prevBouquet": self.prevBouquet,
391 self["actions"].csel = self
393 self["input_actions"] = ActionMap(["InputActions"],
395 "left": self.leftPressed,
396 "right": self.rightPressed,
404 self.updateTimelineTimer = eTimer()
405 self.updateTimelineTimer.callback.append(self.moveTimeLines)
406 self.updateTimelineTimer.start(60*1000)
407 self.onLayoutFinish.append(self.onCreate)
409 def leftPressed(self):
412 def rightPressed(self):
415 def nextEvent(self, visible=True):
416 ret = self["list"].selEntry(+1, visible)
418 self.moveTimeLines(True)
420 def prevEvent(self, visible=True):
421 ret = self["list"].selEntry(-1, visible)
423 self.moveTimeLines(True)
426 self["list"].setEpoch(60)
427 config.misc.graph_mepg_prev_time_period.value = 60
431 self["list"].setEpoch(120)
432 config.misc.graph_mepg_prev_time_period.value = 120
436 self["list"].setEpoch(180)
437 config.misc.graph_mepg_prev_time_period.value = 180
441 self["list"].setEpoch(240)
442 config.misc.graph_mepg_prev_time_period.value = 240
446 self["list"].setEpoch(300)
447 config.misc.graph_mepg_prev_time_period.value = 300
450 def nextBouquet(self):
451 if self.bouquetChangeCB:
452 self.bouquetChangeCB(1, self)
454 def prevBouquet(self):
455 if self.bouquetChangeCB:
456 self.bouquetChangeCB(-1, self)
458 def enterDateTime(self):
459 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
461 def onDateTimeInputClosed(self, ret):
467 l.fillMultiEPG(self.services, ret[1])
468 self.moveTimeLines(True)
470 def closeScreen(self):
471 self.close(self.closeRecursive)
473 def infoKeyPressed(self):
474 cur = self["list"].getCurrent()
477 if event is not None:
478 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
480 def openSimilarList(self, eventid, refstr):
481 self.session.open(EPGSelection, refstr, None, eventid)
483 def setServices(self, services):
484 self.services = services
487 #just used in multipeg
489 self["list"].fillMultiEPG(self.services, self.ask_time)
492 def eventViewCallback(self, setEvent, setService, val):
496 self.prevEvent(False)
498 self.nextEvent(False)
500 if cur[0] is None and cur[1].ref != old[1].ref:
501 self.eventViewCallback(setEvent, setService, val)
507 if self.zapFunc and self["key_red"].getText() == "Zap":
508 self.closeRecursive = True
509 ref = self["list"].getCurrent()[1]
511 self.zapFunc(ref.ref)
513 def eventSelected(self):
514 self.infoKeyPressed()
517 cur = self["list"].getCurrent()
522 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
523 self.session.openWithCallback(self.finishedAdd, TimerEntry, newEntry)
525 def finishedAdd(self, answer):
529 simulTimerList = self.session.nav.RecordTimer.record(entry)
530 if simulTimerList is not None:
531 if (len(simulTimerList) == 2) and (simulTimerList[1].dontSave) and (simulTimerList[1].autoincrease):
532 simulTimerList[1].end = entry.begin - 30
533 self.session.nav.RecordTimer.timeChanged(simulTimerList[1])
534 self.session.nav.RecordTimer.record(entry)
536 self.session.openWithCallback(self.finishSanityCorrection, TimerSanityConflict, simulTimerList)
538 print "Timeredit aborted"
540 def finishSanityCorrection(self, answer):
541 self.finishedAdd(answer)
543 def onSelectionChanged(self):
544 evt = self["list"].getCurrent()
545 self["Event"].newEvent(evt and evt[0])
549 start = evt.getBeginTime()
550 end = start + evt.getDuration()
551 if now >= start and now <= end:
552 self["key_red"].setText("Zap")
554 self["key_red"].setText("")
556 def moveTimeLines(self, force=False):
558 event_rect = l.getEventRect()
559 time_epoch = l.getTimeEpoch()
560 time_base = l.getTimeBase()
561 if event_rect is None or time_epoch is None or time_base is None:
563 time_steps = time_epoch > 180 and 60 or 30
564 num_lines = time_epoch/time_steps
565 incWidth=event_rect.width()/num_lines
566 pos=event_rect.left()
567 timeline_entries = [ ]
570 for line in self.time_lines:
571 old_pos = line.position
572 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
573 if not x or x >= num_lines:
576 if old_pos != new_pos:
577 line.setPosition(new_pos[0], new_pos[1])
580 if not x or line.visible:
581 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
585 if changecount or force:
586 self["timeline_text"].setEntries(timeline_entries)
589 timeline_now = self["timeline_now"]
590 if now >= time_base and now < (time_base + time_epoch * 60):
591 bla = (event_rect.width() * 1000) / time_epoch
592 xpos = ((now/60) - (time_base/60)) * bla / 1000
593 old_pos = timeline_now.position
594 new_pos = (xpos+event_rect.left(), old_pos[1])
595 if old_pos != new_pos:
596 timeline_now.setPosition(new_pos[0], new_pos[1])
597 timeline_now.visible = True
599 timeline_now.visible = False