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 Screens.MessageBox import MessageBox
19 from Tools.Directories import resolveFilename, SCOPE_SKIN_IMAGE
20 from RecordTimer import RecordTimerEntry, parseEvent, AFTEREVENT
21 from ServiceReference import ServiceReference
22 from Tools.LoadPixmap import LoadPixmap
23 from enigma import eEPGCache, eListbox, gFont, eListboxPythonMultiContent, \
24 RT_HALIGN_LEFT, RT_HALIGN_CENTER, RT_VALIGN_CENTER, RT_WRAP, eRect, eTimer
26 from time import localtime, time, strftime
28 class EPGList(HTMLComponent, GUIComponent):
29 def __init__(self, selChangedCB=None, timer = None, time_epoch = 120, overjump_empty=True):
31 self.cur_service = None
34 self.onSelChanged = [ ]
35 if selChangedCB is not None:
36 self.onSelChanged.append(selChangedCB)
37 GUIComponent.__init__(self)
38 self.l = eListboxPythonMultiContent()
39 self.l.setItemHeight(54);
40 self.l.setBuildFunc(self.buildEntry)
42 self.l.setSelectableFunc(self.isSelectable)
43 self.epgcache = eEPGCache.getInstance()
44 self.clock_pixmap = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, 'skin_default/icons/epgclock.png'))
45 self.clock_add_pixmap = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, 'skin_default/icons/epgclock_add.png'))
46 self.clock_pre_pixmap = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, 'skin_default/icons/epgclock_pre.png'))
47 self.clock_post_pixmap = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, 'skin_default/icons/epgclock_post.png'))
48 self.clock_prepost_pixmap = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, 'skin_default/icons/epgclock_prepost.png'))
50 self.time_epoch = time_epoch
52 self.event_rect = None
55 self.foreColorSelected = None
56 self.borderColor = None
57 self.backColor = 0x586d88
58 self.backColorSelected = 0x808080
59 self.foreColorService = None
60 self.backColorService = None
62 def applySkin(self, desktop, screen):
63 if self.skinAttributes is not None:
65 for (attrib, value) in self.skinAttributes:
66 if attrib == "EntryForegroundColor":
67 self.foreColor = parseColor(value).argb()
68 elif attrib == "EntryForegroundColorSelected":
69 self.foreColorSelected = parseColor(value).argb()
70 elif attrib == "EntryBorderColor":
71 self.borderColor = parseColor(value).argb()
72 elif attrib == "EntryBackgroundColor":
73 self.backColor = parseColor(value).argb()
74 elif attrib == "EntryBackgroundColorSelected":
75 self.backColorSelected = parseColor(value).argb()
76 elif attrib == "ServiceNameForegroundColor":
77 self.foreColorService = parseColor(value).argb()
78 elif attrib == "ServiceNameBackgroundColor":
79 self.backColorService = parseColor(value).argb()
81 attribs.append((attrib,value))
82 self.skinAttributes = attribs
83 return GUIComponent.applySkin(self, desktop, screen)
85 def isSelectable(self, service, sname, event_list):
86 return (event_list and len(event_list) and True) or False
88 def setEpoch(self, epoch):
89 if self.cur_event is not None and self.cur_service is not None:
91 self.time_epoch = epoch
92 self.fillMultiEPG(None) # refill
94 def getEventFromId(self, service, eventid):
96 if self.epgcache is not None and eventid is not None:
97 event = self.epgcache.lookupEventId(service.ref, eventid)
100 def moveToService(self,serviceref):
101 for x in range(len(self.list)):
102 if self.list[x][0] == serviceref.toString():
103 self.instance.moveSelectionTo(x)
106 def getIndexFromService(self, serviceref):
107 for x in range(len(self.list)):
108 if self.list[x][0] == serviceref.toString():
112 def setCurrentIndex(self, index):
113 if self.instance is not None:
114 self.instance.moveSelectionTo(index)
116 def getCurrent(self):
117 if self.cur_service is None:
118 return ( None, None )
119 old_service = self.cur_service #(service, service_name, events)
120 events = self.cur_service[2]
121 refstr = self.cur_service[0]
122 if self.cur_event is None or not events or not len(events):
123 return ( None, ServiceReference(refstr) )
124 event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
126 service = ServiceReference(refstr)
127 event = self.getEventFromId(service, eventid)
128 return ( event, service )
130 def connectSelectionChanged(func):
131 if not self.onSelChanged.count(func):
132 self.onSelChanged.append(func)
134 def disconnectSelectionChanged(func):
135 self.onSelChanged.remove(func)
137 def serviceChanged(self):
138 cur_sel = self.l.getCurrentSelection()
142 def findBestEvent(self):
143 old_service = self.cur_service #(service, service_name, events)
144 cur_service = self.cur_service = self.l.getCurrentSelection()
146 time_base = self.getTimeBase()
147 if old_service and self.cur_event is not None:
148 events = old_service[2]
149 cur_event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
150 last_time = cur_event[2]
151 if last_time < time_base:
152 last_time = time_base
155 events = cur_service[2]
156 if events and len(events):
159 best = len(events) #set invalid
161 for event in events: #iterate all events
163 if ev_time < time_base:
165 diff = abs(ev_time-last_time)
166 if (best == len(events)) or (diff < best_diff):
170 if best != len(events):
171 self.cur_event = best
173 self.cur_event = None
176 def selectionChanged(self):
177 for x in self.onSelChanged:
183 # print "FIXME in EPGList.selectionChanged"
186 GUI_WIDGET = eListbox
188 def postWidgetCreate(self, instance):
189 instance.setWrapAround(True)
190 instance.selectionChanged.get().append(self.serviceChanged)
191 instance.setContent(self.l)
192 self.l.setFont(0, gFont("Regular", 20))
193 self.l.setFont(1, gFont("Regular", 14))
194 self.l.setSelectionClip(eRect(0,0,0,0), False)
196 def preWidgetRemove(self, instance):
197 instance.selectionChanged.get().remove(self.serviceChanged)
198 instance.setContent(None)
200 def recalcEntrySize(self):
201 esize = self.l.getItemSize()
202 width = esize.width()
203 height = esize.height()
206 self.service_rect = Rect(xpos, 0, w-10, height)
209 self.event_rect = Rect(xpos, 0, w, height)
211 def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
212 xpos = (stime - start) * width / (end - start)
213 ewidth = (stime + duration - start) * width / (end - start)
218 if (xpos+ewidth) > width:
219 ewidth = width - xpos
222 def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
223 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
224 return xpos+event_rect.left(), width
226 def buildEntry(self, service, service_name, events):
229 res = [ None, MultiContentEntryText(
230 pos = (r1.left(),r1.top()),
231 size = (r1.width(), r1.height()),
232 font = 0, flags = RT_HALIGN_LEFT | RT_VALIGN_CENTER,
234 color = self.foreColorService,
235 backcolor = self.backColorService) ]
238 start = self.time_base+self.offs*self.time_epoch*60
239 end = start + self.time_epoch * 60
244 foreColor = self.foreColor
245 foreColorSelected = self.foreColorSelected
246 backColor = self.backColor
247 backColorSelected = self.backColorSelected
248 borderColor = self.borderColor
250 for ev in events: #(event_id, event_title, begin_time, duration)
251 rec=ev[2] and self.timer.isInTimer(ev[0], ev[2], ev[3], service)
252 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
253 res.append(MultiContentEntryText(
254 pos = (left+xpos, top), size = (ewidth, height),
255 font = 1, flags = RT_HALIGN_CENTER | RT_VALIGN_CENTER | RT_WRAP,
256 text = ev[1], color = foreColor, color_sel = foreColorSelected,
257 backcolor = backColor, backcolor_sel = backColorSelected, border_width = 1, border_color = borderColor))
258 if rec and ewidth > 23:
259 res.append(MultiContentEntryPixmapAlphaTest(
260 pos = (left+xpos+ewidth-22, top+height-22), size = (21, 21),
261 png = self.getClockPixmap(service, ev[2], ev[3], ev[0]),
262 backcolor = backColor,
263 backcolor_sel = backColorSelected))
266 def selEntry(self, dir, visible=True):
267 cur_service = self.cur_service #(service, service_name, events)
268 self.recalcEntrySize()
269 valid_event = self.cur_event is not None
272 entries = cur_service[2]
273 if dir == 0: #current
275 elif dir == +1: #next
276 if valid_event and self.cur_event+1 < len(entries):
280 self.fillMultiEPG(None) # refill
282 elif dir == -1: #prev
283 if valid_event and self.cur_event-1 >= 0:
287 self.fillMultiEPG(None) # refill
289 if cur_service and valid_event:
290 entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
291 time_base = self.time_base+self.offs*self.time_epoch*60
292 xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
293 self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
295 self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
296 self.selectionChanged()
299 def queryEPG(self, list, buildFunc=None):
300 if self.epgcache is not None:
301 if buildFunc is not None:
302 return self.epgcache.lookupEvent(list, buildFunc)
304 return self.epgcache.lookupEvent(list)
307 def fillMultiEPG(self, services, stime=-1):
309 time_base = self.time_base+self.offs*self.time_epoch*60
310 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
312 self.cur_event = None
313 self.cur_service = None
314 self.time_base = int(stime)
315 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
316 test.insert(0, 'XRnITBD')
320 epg_data = self.queryEPG(test)
330 if tmp_list is not None:
331 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
335 tmp_list.append((x[2], x[3], x[4], x[5]))
336 if tmp_list and len(tmp_list):
337 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
339 self.l.setList(self.list)
342 def getEventRect(self):
344 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
346 def getTimeEpoch(self):
347 return self.time_epoch
349 def getTimeBase(self):
350 return self.time_base + (self.offs * self.time_epoch * 60)
352 def resetOffset(self):
355 def getClockPixmap(self, refstr, beginTime, duration, eventId):
359 endTime = beginTime + duration
360 for x in self.timer.timer_list:
361 if x.service_ref.ref.toString() == refstr:
363 return self.clock_pixmap
366 if beginTime > beg and beginTime < end and endTime > end:
367 clock_type |= pre_clock
368 elif beginTime < beg and endTime > beg and endTime < end:
369 clock_type |= post_clock
371 return self.clock_add_pixmap
372 elif clock_type == pre_clock:
373 return self.clock_pre_pixmap
374 elif clock_type == post_clock:
375 return self.clock_post_pixmap
377 return self.clock_prepost_pixmap
379 class TimelineText(HTMLComponent, GUIComponent):
381 GUIComponent.__init__(self)
382 self.l = eListboxPythonMultiContent()
383 self.l.setSelectionClip(eRect(0,0,0,0))
384 self.l.setItemHeight(25);
385 self.l.setFont(0, gFont("Regular", 20))
387 GUI_WIDGET = eListbox
389 def postWidgetCreate(self, instance):
390 instance.setContent(self.l)
392 def setEntries(self, entries):
393 res = [ None ] # no private data needed
397 str = strftime("%H:%M", localtime(tm))
398 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
399 self.l.setList([res])
401 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
402 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
404 class GraphMultiEPG(Screen):
411 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
412 Screen.__init__(self, session)
413 self.bouquetChangeCB = bouquetChangeCB
416 self.ask_time = now - tmp
417 self.closeRecursive = False
418 self["key_red"] = Button("")
419 self["key_green"] = Button("")
420 self.key_green_choice = self.EMPTY
421 self.key_red_choice = self.EMPTY
422 self["timeline_text"] = TimelineText()
423 self["Event"] = Event()
424 self.time_lines = [ ]
425 for x in (0,1,2,3,4,5):
427 self.time_lines.append(pm)
428 self["timeline%d"%(x)] = pm
429 self["timeline_now"] = Pixmap()
430 self.services = services
431 self.zapFunc = zapFunc
433 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
435 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
437 "cancel": self.closeScreen,
438 "ok": self.eventSelected,
439 "timerAdd": self.timerAdd,
440 "info": self.infoKeyPressed,
442 "input_date_time": self.enterDateTime,
443 "nextBouquet": self.nextBouquet,
444 "prevBouquet": self.prevBouquet,
446 self["actions"].csel = self
448 self["input_actions"] = ActionMap(["InputActions"],
450 "left": self.leftPressed,
451 "right": self.rightPressed,
459 self.updateTimelineTimer = eTimer()
460 self.updateTimelineTimer.callback.append(self.moveTimeLines)
461 self.updateTimelineTimer.start(60*1000)
462 self.onLayoutFinish.append(self.onCreate)
464 def leftPressed(self):
467 def rightPressed(self):
470 def nextEvent(self, visible=True):
471 ret = self["list"].selEntry(+1, visible)
473 self.moveTimeLines(True)
475 def prevEvent(self, visible=True):
476 ret = self["list"].selEntry(-1, visible)
478 self.moveTimeLines(True)
481 self["list"].setEpoch(60)
482 config.misc.graph_mepg_prev_time_period.value = 60
486 self["list"].setEpoch(120)
487 config.misc.graph_mepg_prev_time_period.value = 120
491 self["list"].setEpoch(180)
492 config.misc.graph_mepg_prev_time_period.value = 180
496 self["list"].setEpoch(240)
497 config.misc.graph_mepg_prev_time_period.value = 240
501 self["list"].setEpoch(300)
502 config.misc.graph_mepg_prev_time_period.value = 300
505 def nextBouquet(self):
506 if self.bouquetChangeCB:
507 self.bouquetChangeCB(1, self)
509 def prevBouquet(self):
510 if self.bouquetChangeCB:
511 self.bouquetChangeCB(-1, self)
513 def enterDateTime(self):
514 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
516 def onDateTimeInputClosed(self, ret):
522 l.fillMultiEPG(self.services, ret[1])
523 self.moveTimeLines(True)
525 def closeScreen(self):
526 self.close(self.closeRecursive)
528 def infoKeyPressed(self):
529 cur = self["list"].getCurrent()
532 if event is not None:
533 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
535 def openSimilarList(self, eventid, refstr):
536 self.session.open(EPGSelection, refstr, None, eventid)
538 def setServices(self, services):
539 self.services = services
542 #just used in multipeg
544 self["list"].fillMultiEPG(self.services, self.ask_time)
545 self["list"].moveToService(self.session.nav.getCurrentlyPlayingServiceReference())
548 def eventViewCallback(self, setEvent, setService, val):
552 self.prevEvent(False)
554 self.nextEvent(False)
556 if cur[0] is None and cur[1].ref != old[1].ref:
557 self.eventViewCallback(setEvent, setService, val)
563 if self.zapFunc and self.key_red_choice == self.ZAP:
564 self.closeRecursive = True
565 ref = self["list"].getCurrent()[1]
567 self.zapFunc(ref.ref)
569 def eventSelected(self):
570 self.infoKeyPressed()
572 def removeTimer(self, timer):
573 timer.afterEvent = AFTEREVENT.NONE
574 self.session.nav.RecordTimer.removeEntry(timer)
575 self["key_green"].setText(_("Add timer"))
576 self.key_green_choice = self.ADD_TIMER
579 cur = self["list"].getCurrent()
584 eventid = event.getEventId()
585 refstr = serviceref.ref.toString()
586 for timer in self.session.nav.RecordTimer.timer_list:
587 if timer.eit == eventid and timer.service_ref.ref.toString() == refstr:
588 cb_func = lambda ret : not ret or self.removeTimer(timer)
589 self.session.openWithCallback(cb_func, MessageBox, _("Do you really want to delete %s?") % event.getEventName())
592 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
593 self.session.openWithCallback(self.finishedAdd, TimerEntry, newEntry)
595 def finishedAdd(self, answer):
599 simulTimerList = self.session.nav.RecordTimer.record(entry)
600 if simulTimerList is not None:
601 if (len(simulTimerList) == 2) and (simulTimerList[1].dontSave) and (simulTimerList[1].autoincrease):
602 simulTimerList[1].end = entry.begin - 30
603 self.session.nav.RecordTimer.timeChanged(simulTimerList[1])
604 self.session.nav.RecordTimer.record(entry)
606 self.session.openWithCallback(self.finishSanityCorrection, TimerSanityConflict, simulTimerList)
607 self["key_green"].setText(_("Remove timer"))
608 self.key_green_choice = self.REMOVE_TIMER
610 self["key_green"].setText(_("Add timer"))
611 self.key_green_choice = self.ADD_TIMER
612 print "Timeredit aborted"
614 def finishSanityCorrection(self, answer):
615 self.finishedAdd(answer)
617 def onSelectionChanged(self):
618 cur = self["list"].getCurrent()
620 if self.key_green_choice != self.EMPTY:
621 self["key_green"].setText("")
622 self.key_green_choice = self.EMPTY
623 if self.key_red_choice != self.EMPTY:
624 self["key_red"].setText("")
625 self.key_red_choice = self.EMPTY
629 self["Event"].newEvent(event)
631 if cur[1] is None or cur[1].getServiceName() == "":
632 if self.key_green_choice != self.EMPTY:
633 self["key_green"].setText("")
634 self.key_green_choice = self.EMPTY
635 if self.key_red_choice != self.EMPTY:
636 self["key_red"].setText("")
637 self.key_red_choice = self.EMPTY
639 elif self.key_red_choice != self.ZAP:
640 self["key_red"].setText("Zap")
641 self.key_red_choice = self.ZAP
644 if self.key_green_choice != self.EMPTY:
645 self["key_green"].setText("")
646 self.key_green_choice = self.EMPTY
650 eventid = event.getEventId()
651 refstr = serviceref.ref.toString()
652 isRecordEvent = False
653 for timer in self.session.nav.RecordTimer.timer_list:
654 if timer.eit == eventid and timer.service_ref.ref.toString() == refstr:
657 if isRecordEvent and self.key_green_choice != self.REMOVE_TIMER:
658 self["key_green"].setText(_("Remove timer"))
659 self.key_green_choice = self.REMOVE_TIMER
660 elif not isRecordEvent and self.key_green_choice != self.ADD_TIMER:
661 self["key_green"].setText(_("Add timer"))
662 self.key_green_choice = self.ADD_TIMER
664 def moveTimeLines(self, force=False):
666 event_rect = l.getEventRect()
667 time_epoch = l.getTimeEpoch()
668 time_base = l.getTimeBase()
669 if event_rect is None or time_epoch is None or time_base is None:
671 time_steps = time_epoch > 180 and 60 or 30
672 num_lines = time_epoch/time_steps
673 incWidth=event_rect.width()/num_lines
674 pos=event_rect.left()
675 timeline_entries = [ ]
678 for line in self.time_lines:
679 old_pos = line.position
680 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
681 if not x or x >= num_lines:
684 if old_pos != new_pos:
685 line.setPosition(new_pos[0], new_pos[1])
688 if not x or line.visible:
689 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
693 if changecount or force:
694 self["timeline_text"].setEntries(timeline_entries)
697 timeline_now = self["timeline_now"]
698 if now >= time_base and now < (time_base + time_epoch * 60):
699 bla = (event_rect.width() * 1000) / time_epoch
700 xpos = ((now/60) - (time_base/60)) * bla / 1000
701 old_pos = timeline_now.position
702 new_pos = (xpos+event_rect.left(), old_pos[1])
703 if old_pos != new_pos:
704 timeline_now.setPosition(new_pos[0], new_pos[1])
705 timeline_now.visible = True
707 timeline_now.visible = False