1 from skin import parseColor, parseFont, parseSize
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_CURRENT_SKIN
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.setBuildFunc(self.buildEntry)
41 self.l.setSelectableFunc(self.isSelectable)
42 self.epgcache = eEPGCache.getInstance()
43 self.clock_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock.png'))
44 self.clock_add_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_add.png'))
45 self.clock_pre_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_pre.png'))
46 self.clock_post_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_post.png'))
47 self.clock_prepost_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_prepost.png'))
49 self.time_epoch = time_epoch
51 self.event_rect = None
54 self.foreColorSelected = None
55 self.borderColor = None
56 self.backColor = 0x586d88
57 self.backColorSelected = 0x808080
58 self.foreColorService = None
59 self.backColorService = None
60 self.serviceFont = gFont("Regular", 20)
61 self.entryFont = gFont("Regular", 14)
64 def applySkin(self, desktop, screen):
65 def EntryForegroundColor(value):
66 self.foreColor = parseColor(value).argb()
67 def EntryForegroundColorSelected(value):
68 self.foreColorSelected = parseColor(value).argb()
69 def EntryBackgroundColor(value):
70 self.backColor = parseColor(value).argb()
71 def EntryBackgroundColorSelected(value):
72 self.backColorSelected = parseColor(value).argb()
73 def EntryBorderColor(value):
74 self.borderColor = parseColor(value).argb()
75 def EntryItemHeight(value):
76 self.itemHeight = int(value)
77 def ServiceNameForegroundColor(value):
78 self.foreColorService = parseColor(value).argb()
79 def ServiceNameBackgroundColor(value):
80 self.backColorService = parseColor(value).argb()
81 def ServiceFont(value):
82 self.serviceFont = parseFont(value, ((1,1),(1,1)) )
84 self.entryFont = parseFont(value, ((1,1),(1,1)) )
86 for (attrib, value) in list(self.skinAttributes):
88 locals().get(attrib)(value)
89 self.skinAttributes.remove((attrib, value))
92 self.l.setFont(0, self.serviceFont)
93 self.l.setFont(1, self.entryFont)
94 self.l.setItemHeight(self.itemHeight)
95 return GUIComponent.applySkin(self, desktop, screen)
97 def isSelectable(self, service, sname, event_list):
98 return (event_list and len(event_list) and True) or False
100 def setEpoch(self, epoch):
101 # if self.cur_event is not None and self.cur_service is not None:
103 self.time_epoch = epoch
104 self.fillMultiEPG(None) # refill
106 def getEventFromId(self, service, eventid):
108 if self.epgcache is not None and eventid is not None:
109 event = self.epgcache.lookupEventId(service.ref, eventid)
112 def moveToService(self,serviceref):
113 if serviceref is not None:
114 for x in range(len(self.list)):
115 if self.list[x][0] == serviceref.toString():
116 self.instance.moveSelectionTo(x)
119 def getIndexFromService(self, serviceref):
120 if serviceref is not None:
121 for x in range(len(self.list)):
122 if self.list[x][0] == serviceref.toString():
125 def setCurrentIndex(self, index):
126 if self.instance is not None:
127 self.instance.moveSelectionTo(index)
129 def getCurrent(self):
130 if self.cur_service is None:
131 return ( None, None )
132 old_service = self.cur_service #(service, service_name, events)
133 events = self.cur_service[2]
134 refstr = self.cur_service[0]
135 if self.cur_event is None or not events or not len(events):
136 return ( None, ServiceReference(refstr) )
137 event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
139 service = ServiceReference(refstr)
140 event = self.getEventFromId(service, eventid)
141 return ( event, service )
143 def connectSelectionChanged(func):
144 if not self.onSelChanged.count(func):
145 self.onSelChanged.append(func)
147 def disconnectSelectionChanged(func):
148 self.onSelChanged.remove(func)
150 def serviceChanged(self):
151 cur_sel = self.l.getCurrentSelection()
155 def findBestEvent(self):
156 old_service = self.cur_service #(service, service_name, events)
157 cur_service = self.cur_service = self.l.getCurrentSelection()
159 time_base = self.getTimeBase()
160 if old_service and self.cur_event is not None:
161 events = old_service[2]
162 cur_event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
163 last_time = cur_event[2]
164 if last_time < time_base:
165 last_time = time_base
168 events = cur_service[2]
169 if events and len(events):
172 best = len(events) #set invalid
174 for event in events: #iterate all events
176 if ev_time < time_base:
178 diff = abs(ev_time-last_time)
179 if (best == len(events)) or (diff < best_diff):
183 if best != len(events):
184 self.cur_event = best
186 self.cur_event = None
189 def selectionChanged(self):
190 for x in self.onSelChanged:
196 # print "FIXME in EPGList.selectionChanged"
199 GUI_WIDGET = eListbox
201 def postWidgetCreate(self, instance):
202 instance.setWrapAround(True)
203 instance.selectionChanged.get().append(self.serviceChanged)
204 instance.setContent(self.l)
205 self.l.setSelectionClip(eRect(0,0,0,0), False)
207 def preWidgetRemove(self, instance):
208 instance.selectionChanged.get().remove(self.serviceChanged)
209 instance.setContent(None)
211 def recalcEntrySize(self):
212 esize = self.l.getItemSize()
213 width = esize.width()
214 height = esize.height()
217 self.service_rect = Rect(xpos, 0, w-10, height)
220 self.event_rect = Rect(xpos, 0, w, height)
222 def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
223 xpos = (stime - start) * width / (end - start)
224 ewidth = (stime + duration - start) * width / (end - start)
229 if (xpos+ewidth) > width:
230 ewidth = width - xpos
233 def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
234 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
235 return xpos+event_rect.left(), width
237 def buildEntry(self, service, service_name, events):
240 res = [ None, MultiContentEntryText(
241 pos = (r1.left(),r1.top()),
242 size = (r1.width(), r1.height()),
243 font = 0, flags = RT_HALIGN_LEFT | RT_VALIGN_CENTER,
245 color = self.foreColorService,
246 backcolor = self.backColorService) ]
249 start = self.time_base+self.offs*self.time_epoch*60
250 end = start + self.time_epoch * 60
255 foreColor = self.foreColor
256 foreColorSelected = self.foreColorSelected
257 backColor = self.backColor
258 backColorSelected = self.backColorSelected
259 borderColor = self.borderColor
261 for ev in events: #(event_id, event_title, begin_time, duration)
262 rec=ev[2] and self.timer.isInTimer(ev[0], ev[2], ev[3], service)
263 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
264 res.append(MultiContentEntryText(
265 pos = (left+xpos, top), size = (ewidth, height),
266 font = 1, flags = RT_HALIGN_CENTER | RT_VALIGN_CENTER | RT_WRAP,
267 text = ev[1], color = foreColor, color_sel = foreColorSelected,
268 backcolor = backColor, backcolor_sel = backColorSelected, border_width = 1, border_color = borderColor))
269 if rec and ewidth > 23:
270 res.append(MultiContentEntryPixmapAlphaTest(
271 pos = (left+xpos+ewidth-22, top+height-22), size = (21, 21),
272 png = self.getClockPixmap(service, ev[2], ev[3], ev[0]),
273 backcolor = backColor,
274 backcolor_sel = backColorSelected))
277 def selEntry(self, dir, visible=True):
278 cur_service = self.cur_service #(service, service_name, events)
279 self.recalcEntrySize()
280 valid_event = self.cur_event is not None
283 entries = cur_service[2]
284 if dir == 0: #current
286 elif dir == +1: #next
287 if valid_event and self.cur_event+1 < len(entries):
291 self.fillMultiEPG(None) # refill
293 elif dir == -1: #prev
294 if valid_event and self.cur_event-1 >= 0:
298 self.fillMultiEPG(None) # refill
300 if cur_service and valid_event:
301 entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
302 time_base = self.time_base+self.offs*self.time_epoch*60
303 xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
304 self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
306 self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
307 self.selectionChanged()
310 def queryEPG(self, list, buildFunc=None):
311 if self.epgcache is not None:
312 if buildFunc is not None:
313 return self.epgcache.lookupEvent(list, buildFunc)
315 return self.epgcache.lookupEvent(list)
318 def fillMultiEPG(self, services, stime=-1):
320 time_base = self.time_base+self.offs*self.time_epoch*60
321 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
323 self.cur_event = None
324 self.cur_service = None
325 self.time_base = int(stime)
326 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
327 test.insert(0, 'XRnITBD')
331 epg_data = self.queryEPG(test)
341 if tmp_list is not None:
342 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
346 tmp_list.append((x[2], x[3], x[4], x[5]))
347 if tmp_list and len(tmp_list):
348 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
350 self.l.setList(self.list)
353 def getEventRect(self):
355 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
357 def getTimeEpoch(self):
358 return self.time_epoch
360 def getTimeBase(self):
361 return self.time_base + (self.offs * self.time_epoch * 60)
363 def resetOffset(self):
366 def getClockPixmap(self, refstr, beginTime, duration, eventId):
370 endTime = beginTime + duration
371 for x in self.timer.timer_list:
372 if x.service_ref.ref.toString() == refstr:
374 return self.clock_pixmap
377 if beginTime > beg and beginTime < end and endTime > end:
378 clock_type |= pre_clock
379 elif beginTime < beg and endTime > beg and endTime < end:
380 clock_type |= post_clock
382 return self.clock_add_pixmap
383 elif clock_type == pre_clock:
384 return self.clock_pre_pixmap
385 elif clock_type == post_clock:
386 return self.clock_post_pixmap
388 return self.clock_prepost_pixmap
390 class TimelineText(HTMLComponent, GUIComponent):
392 GUIComponent.__init__(self)
393 self.l = eListboxPythonMultiContent()
394 self.l.setSelectionClip(eRect(0,0,0,0))
395 self.foreColor = 0xffc000
396 self.backColor = 0x000000
397 self.font = gFont("Regular", 20)
400 GUI_WIDGET = eListbox
402 def applySkin(self, desktop, screen):
403 def foregroundColor(value):
404 self.foreColor = parseColor(value).argb()
405 def backgroundColor(value):
406 self.backColor = parseColor(value).argb()
408 self.font = parseFont(value, ((1, 1), (1, 1)) )
409 def itemHeight(value):
410 self.itemHeight = int(value)
411 for (attrib, value) in list(self.skinAttributes):
413 locals().get(attrib)(value)
414 self.skinAttributes.remove((attrib, value))
417 self.l.setFont(0, self.font)
418 self.l.setItemHeight(self.itemHeight);
419 return GUIComponent.applySkin(self, desktop, screen)
421 def postWidgetCreate(self, instance):
422 instance.setContent(self.l)
424 def setEntries(self, entries):
427 res = [ None ] # no private data needed
431 str = strftime("%H:%M", localtime(tm))
432 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, width, ih, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
433 self.l.setList([res])
435 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
436 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
438 class GraphMultiEPG(Screen):
445 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
446 Screen.__init__(self, session)
447 self.bouquetChangeCB = bouquetChangeCB
450 self.ask_time = now - tmp
451 self.closeRecursive = False
452 self["key_red"] = Button("")
453 self["key_green"] = Button("")
454 self.key_green_choice = self.EMPTY
455 self.key_red_choice = self.EMPTY
456 self["timeline_text"] = TimelineText()
457 self["Event"] = Event()
458 self.time_lines = [ ]
459 for x in (0,1,2,3,4,5):
461 self.time_lines.append(pm)
462 self["timeline%d"%(x)] = pm
463 self["timeline_now"] = Pixmap()
464 self.services = services
465 self.zapFunc = zapFunc
467 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
469 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
471 "cancel": self.closeScreen,
472 "ok": self.eventSelected,
473 "timerAdd": self.timerAdd,
474 "info": self.infoKeyPressed,
476 "input_date_time": self.enterDateTime,
477 "nextBouquet": self.nextBouquet,
478 "prevBouquet": self.prevBouquet,
480 self["actions"].csel = self
482 self["input_actions"] = ActionMap(["InputActions"],
484 "left": self.leftPressed,
485 "right": self.rightPressed,
493 self.updateTimelineTimer = eTimer()
494 self.updateTimelineTimer.callback.append(self.moveTimeLines)
495 self.updateTimelineTimer.start(60*1000)
496 self.onLayoutFinish.append(self.onCreate)
498 def leftPressed(self):
501 def rightPressed(self):
504 def nextEvent(self, visible=True):
505 ret = self["list"].selEntry(+1, visible)
507 self.moveTimeLines(True)
509 def prevEvent(self, visible=True):
510 ret = self["list"].selEntry(-1, visible)
512 self.moveTimeLines(True)
515 self["list"].setEpoch(60)
516 config.misc.graph_mepg_prev_time_period.value = 60
520 self["list"].setEpoch(120)
521 config.misc.graph_mepg_prev_time_period.value = 120
525 self["list"].setEpoch(180)
526 config.misc.graph_mepg_prev_time_period.value = 180
530 self["list"].setEpoch(240)
531 config.misc.graph_mepg_prev_time_period.value = 240
535 self["list"].setEpoch(300)
536 config.misc.graph_mepg_prev_time_period.value = 300
539 def nextBouquet(self):
540 if self.bouquetChangeCB:
541 self.bouquetChangeCB(1, self)
543 def prevBouquet(self):
544 if self.bouquetChangeCB:
545 self.bouquetChangeCB(-1, self)
547 def enterDateTime(self):
548 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
550 def onDateTimeInputClosed(self, ret):
556 l.fillMultiEPG(self.services, ret[1])
557 self.moveTimeLines(True)
559 def closeScreen(self):
560 self.close(self.closeRecursive)
562 def infoKeyPressed(self):
563 cur = self["list"].getCurrent()
566 if event is not None:
567 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
569 def openSimilarList(self, eventid, refstr):
570 self.session.open(EPGSelection, refstr, None, eventid)
572 def setServices(self, services):
573 self.services = services
576 #just used in multipeg
578 self["list"].fillMultiEPG(self.services, self.ask_time)
579 self["list"].moveToService(self.session.nav.getCurrentlyPlayingServiceReference())
582 def eventViewCallback(self, setEvent, setService, val):
586 self.prevEvent(False)
588 self.nextEvent(False)
590 if cur[0] is None and cur[1].ref != old[1].ref:
591 self.eventViewCallback(setEvent, setService, val)
597 if self.zapFunc and self.key_red_choice == self.ZAP:
598 self.closeRecursive = True
599 ref = self["list"].getCurrent()[1]
601 self.zapFunc(ref.ref)
603 def eventSelected(self):
604 self.infoKeyPressed()
606 def removeTimer(self, timer):
607 timer.afterEvent = AFTEREVENT.NONE
608 self.session.nav.RecordTimer.removeEntry(timer)
609 self["key_green"].setText(_("Add timer"))
610 self.key_green_choice = self.ADD_TIMER
613 cur = self["list"].getCurrent()
618 eventid = event.getEventId()
619 refstr = serviceref.ref.toString()
620 for timer in self.session.nav.RecordTimer.timer_list:
621 if timer.eit == eventid and timer.service_ref.ref.toString() == refstr:
622 cb_func = lambda ret : not ret or self.removeTimer(timer)
623 self.session.openWithCallback(cb_func, MessageBox, _("Do you really want to delete %s?") % event.getEventName())
626 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
627 self.session.openWithCallback(self.finishedAdd, TimerEntry, newEntry)
629 def finishedAdd(self, answer):
633 simulTimerList = self.session.nav.RecordTimer.record(entry)
634 if simulTimerList is not None:
635 for x in simulTimerList:
636 if x.setAutoincreaseEnd(entry):
637 self.session.nav.RecordTimer.timeChanged(x)
638 simulTimerList = self.session.nav.RecordTimer.record(entry)
639 if simulTimerList is not None:
640 self.session.openWithCallback(self.finishSanityCorrection, TimerSanityConflict, simulTimerList)
641 self["key_green"].setText(_("Remove timer"))
642 self.key_green_choice = self.REMOVE_TIMER
644 self["key_green"].setText(_("Add timer"))
645 self.key_green_choice = self.ADD_TIMER
646 print "Timeredit aborted"
648 def finishSanityCorrection(self, answer):
649 self.finishedAdd(answer)
651 def onSelectionChanged(self):
652 cur = self["list"].getCurrent()
654 if self.key_green_choice != self.EMPTY:
655 self["key_green"].setText("")
656 self.key_green_choice = self.EMPTY
657 if self.key_red_choice != self.EMPTY:
658 self["key_red"].setText("")
659 self.key_red_choice = self.EMPTY
663 self["Event"].newEvent(event)
665 if cur[1] is None or cur[1].getServiceName() == "":
666 if self.key_green_choice != self.EMPTY:
667 self["key_green"].setText("")
668 self.key_green_choice = self.EMPTY
669 if self.key_red_choice != self.EMPTY:
670 self["key_red"].setText("")
671 self.key_red_choice = self.EMPTY
673 elif self.key_red_choice != self.ZAP:
674 self["key_red"].setText("Zap")
675 self.key_red_choice = self.ZAP
678 if self.key_green_choice != self.EMPTY:
679 self["key_green"].setText("")
680 self.key_green_choice = self.EMPTY
684 eventid = event.getEventId()
685 refstr = serviceref.ref.toString()
686 isRecordEvent = False
687 for timer in self.session.nav.RecordTimer.timer_list:
688 if timer.eit == eventid and timer.service_ref.ref.toString() == refstr:
691 if isRecordEvent and self.key_green_choice != self.REMOVE_TIMER:
692 self["key_green"].setText(_("Remove timer"))
693 self.key_green_choice = self.REMOVE_TIMER
694 elif not isRecordEvent and self.key_green_choice != self.ADD_TIMER:
695 self["key_green"].setText(_("Add timer"))
696 self.key_green_choice = self.ADD_TIMER
698 def moveTimeLines(self, force=False):
699 self.updateTimelineTimer.start((60-(int(time())%60))*1000) #keep syncronised
701 event_rect = l.getEventRect()
702 time_epoch = l.getTimeEpoch()
703 time_base = l.getTimeBase()
704 if event_rect is None or time_epoch is None or time_base is None:
706 time_steps = time_epoch > 180 and 60 or 30
708 num_lines = time_epoch/time_steps
709 incWidth=event_rect.width()/num_lines
710 pos=event_rect.left()
711 timeline_entries = [ ]
714 for line in self.time_lines:
715 old_pos = line.position
716 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
717 if not x or x >= num_lines:
720 if old_pos != new_pos:
721 line.setPosition(new_pos[0], new_pos[1])
724 if not x or line.visible:
725 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
729 if changecount or force:
730 self["timeline_text"].setEntries(timeline_entries)
733 timeline_now = self["timeline_now"]
734 if now >= time_base and now < (time_base + time_epoch * 60):
735 xpos = int((((now - time_base) * event_rect.width()) / (time_epoch * 60))-(timeline_now.instance.size().width()/2))
736 old_pos = timeline_now.position
737 new_pos = (xpos+event_rect.left(), old_pos[1])
738 if old_pos != new_pos:
739 timeline_now.setPosition(new_pos[0], new_pos[1])
740 timeline_now.visible = True
742 timeline_now.visible = False
743 # here no l.l.invalidate() is needed when the zPosition in the skin is correct!