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_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.setItemHeight(54);
40 self.l.setBuildFunc(self.buildEntry)
42 self.l.setSelectableFunc(self.isSelectable)
43 self.epgcache = eEPGCache.getInstance()
44 self.clock_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock.png'))
45 self.clock_add_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_add.png'))
46 self.clock_pre_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_pre.png'))
47 self.clock_post_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, 'skin_default/icons/epgclock_post.png'))
48 self.clock_prepost_pixmap = LoadPixmap(cached=True, path=resolveFilename(SCOPE_CURRENT_SKIN, '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 if serviceref is not None:
102 for x in range(len(self.list)):
103 if self.list[x][0] == serviceref.toString():
104 self.instance.moveSelectionTo(x)
107 def getIndexFromService(self, serviceref):
108 if serviceref is not None:
109 for x in range(len(self.list)):
110 if self.list[x][0] == serviceref.toString():
113 def setCurrentIndex(self, index):
114 if self.instance is not None:
115 self.instance.moveSelectionTo(index)
117 def getCurrent(self):
118 if self.cur_service is None:
119 return ( None, None )
120 old_service = self.cur_service #(service, service_name, events)
121 events = self.cur_service[2]
122 refstr = self.cur_service[0]
123 if self.cur_event is None or not events or not len(events):
124 return ( None, ServiceReference(refstr) )
125 event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
127 service = ServiceReference(refstr)
128 event = self.getEventFromId(service, eventid)
129 return ( event, service )
131 def connectSelectionChanged(func):
132 if not self.onSelChanged.count(func):
133 self.onSelChanged.append(func)
135 def disconnectSelectionChanged(func):
136 self.onSelChanged.remove(func)
138 def serviceChanged(self):
139 cur_sel = self.l.getCurrentSelection()
143 def findBestEvent(self):
144 old_service = self.cur_service #(service, service_name, events)
145 cur_service = self.cur_service = self.l.getCurrentSelection()
147 time_base = self.getTimeBase()
148 if old_service and self.cur_event is not None:
149 events = old_service[2]
150 cur_event = events[self.cur_event] #(event_id, event_title, begin_time, duration)
151 last_time = cur_event[2]
152 if last_time < time_base:
153 last_time = time_base
156 events = cur_service[2]
157 if events and len(events):
160 best = len(events) #set invalid
162 for event in events: #iterate all events
164 if ev_time < time_base:
166 diff = abs(ev_time-last_time)
167 if (best == len(events)) or (diff < best_diff):
171 if best != len(events):
172 self.cur_event = best
174 self.cur_event = None
177 def selectionChanged(self):
178 for x in self.onSelChanged:
184 # print "FIXME in EPGList.selectionChanged"
187 GUI_WIDGET = eListbox
189 def postWidgetCreate(self, instance):
190 instance.setWrapAround(True)
191 instance.selectionChanged.get().append(self.serviceChanged)
192 instance.setContent(self.l)
193 self.l.setFont(0, gFont("Regular", 20))
194 self.l.setFont(1, gFont("Regular", 14))
195 self.l.setSelectionClip(eRect(0,0,0,0), False)
197 def preWidgetRemove(self, instance):
198 instance.selectionChanged.get().remove(self.serviceChanged)
199 instance.setContent(None)
201 def recalcEntrySize(self):
202 esize = self.l.getItemSize()
203 width = esize.width()
204 height = esize.height()
207 self.service_rect = Rect(xpos, 0, w-10, height)
210 self.event_rect = Rect(xpos, 0, w, height)
212 def calcEntryPosAndWidthHelper(self, stime, duration, start, end, width):
213 xpos = (stime - start) * width / (end - start)
214 ewidth = (stime + duration - start) * width / (end - start)
219 if (xpos+ewidth) > width:
220 ewidth = width - xpos
223 def calcEntryPosAndWidth(self, event_rect, time_base, time_epoch, ev_start, ev_duration):
224 xpos, width = self.calcEntryPosAndWidthHelper(ev_start, ev_duration, time_base, time_base + time_epoch * 60, event_rect.width())
225 return xpos+event_rect.left(), width
227 def buildEntry(self, service, service_name, events):
230 res = [ None, MultiContentEntryText(
231 pos = (r1.left(),r1.top()),
232 size = (r1.width(), r1.height()),
233 font = 0, flags = RT_HALIGN_LEFT | RT_VALIGN_CENTER,
235 color = self.foreColorService,
236 backcolor = self.backColorService) ]
239 start = self.time_base+self.offs*self.time_epoch*60
240 end = start + self.time_epoch * 60
245 foreColor = self.foreColor
246 foreColorSelected = self.foreColorSelected
247 backColor = self.backColor
248 backColorSelected = self.backColorSelected
249 borderColor = self.borderColor
251 for ev in events: #(event_id, event_title, begin_time, duration)
252 rec=ev[2] and self.timer.isInTimer(ev[0], ev[2], ev[3], service)
253 xpos, ewidth = self.calcEntryPosAndWidthHelper(ev[2], ev[3], start, end, width)
254 res.append(MultiContentEntryText(
255 pos = (left+xpos, top), size = (ewidth, height),
256 font = 1, flags = RT_HALIGN_CENTER | RT_VALIGN_CENTER | RT_WRAP,
257 text = ev[1], color = foreColor, color_sel = foreColorSelected,
258 backcolor = backColor, backcolor_sel = backColorSelected, border_width = 1, border_color = borderColor))
259 if rec and ewidth > 23:
260 res.append(MultiContentEntryPixmapAlphaTest(
261 pos = (left+xpos+ewidth-22, top+height-22), size = (21, 21),
262 png = self.getClockPixmap(service, ev[2], ev[3], ev[0]),
263 backcolor = backColor,
264 backcolor_sel = backColorSelected))
267 def selEntry(self, dir, visible=True):
268 cur_service = self.cur_service #(service, service_name, events)
269 self.recalcEntrySize()
270 valid_event = self.cur_event is not None
273 entries = cur_service[2]
274 if dir == 0: #current
276 elif dir == +1: #next
277 if valid_event and self.cur_event+1 < len(entries):
281 self.fillMultiEPG(None) # refill
283 elif dir == -1: #prev
284 if valid_event and self.cur_event-1 >= 0:
288 self.fillMultiEPG(None) # refill
290 if cur_service and valid_event:
291 entry = entries[self.cur_event] #(event_id, event_title, begin_time, duration)
292 time_base = self.time_base+self.offs*self.time_epoch*60
293 xpos, width = self.calcEntryPosAndWidth(self.event_rect, time_base, self.time_epoch, entry[2], entry[3])
294 self.l.setSelectionClip(eRect(xpos, 0, width, self.event_rect.height()), visible and update)
296 self.l.setSelectionClip(eRect(self.event_rect.left(), self.event_rect.top(), self.event_rect.width(), self.event_rect.height()), False)
297 self.selectionChanged()
300 def queryEPG(self, list, buildFunc=None):
301 if self.epgcache is not None:
302 if buildFunc is not None:
303 return self.epgcache.lookupEvent(list, buildFunc)
305 return self.epgcache.lookupEvent(list)
308 def fillMultiEPG(self, services, stime=-1):
310 time_base = self.time_base+self.offs*self.time_epoch*60
311 test = [ (service[0], 0, time_base, self.time_epoch) for service in self.list ]
313 self.cur_event = None
314 self.cur_service = None
315 self.time_base = int(stime)
316 test = [ (service.ref.toString(), 0, self.time_base, self.time_epoch) for service in services ]
317 test.insert(0, 'XRnITBD')
321 epg_data = self.queryEPG(test)
331 if tmp_list is not None:
332 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
336 tmp_list.append((x[2], x[3], x[4], x[5]))
337 if tmp_list and len(tmp_list):
338 self.list.append((service, sname, tmp_list[0][0] is not None and tmp_list or None))
340 self.l.setList(self.list)
343 def getEventRect(self):
345 return Rect( rc.left() + (self.instance and self.instance.position().x() or 0), rc.top(), rc.width(), rc.height() )
347 def getTimeEpoch(self):
348 return self.time_epoch
350 def getTimeBase(self):
351 return self.time_base + (self.offs * self.time_epoch * 60)
353 def resetOffset(self):
356 def getClockPixmap(self, refstr, beginTime, duration, eventId):
360 endTime = beginTime + duration
361 for x in self.timer.timer_list:
362 if x.service_ref.ref.toString() == refstr:
364 return self.clock_pixmap
367 if beginTime > beg and beginTime < end and endTime > end:
368 clock_type |= pre_clock
369 elif beginTime < beg and endTime > beg and endTime < end:
370 clock_type |= post_clock
372 return self.clock_add_pixmap
373 elif clock_type == pre_clock:
374 return self.clock_pre_pixmap
375 elif clock_type == post_clock:
376 return self.clock_post_pixmap
378 return self.clock_prepost_pixmap
380 class TimelineText(HTMLComponent, GUIComponent):
382 GUIComponent.__init__(self)
383 self.l = eListboxPythonMultiContent()
384 self.l.setSelectionClip(eRect(0,0,0,0))
385 self.l.setItemHeight(25);
386 self.l.setFont(0, gFont("Regular", 20))
388 GUI_WIDGET = eListbox
390 def postWidgetCreate(self, instance):
391 instance.setContent(self.l)
393 def setEntries(self, entries):
394 res = [ None ] # no private data needed
398 str = strftime("%H:%M", localtime(tm))
399 res.append((eListboxPythonMultiContent.TYPE_TEXT, xpos-30, 0, 60, 25, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, str))
400 self.l.setList([res])
402 config.misc.graph_mepg_prev_time=ConfigClock(default = time())
403 config.misc.graph_mepg_prev_time_period=ConfigInteger(default=120, limits=(60,300))
405 class GraphMultiEPG(Screen):
412 def __init__(self, session, services, zapFunc=None, bouquetChangeCB=None):
413 Screen.__init__(self, session)
414 self.bouquetChangeCB = bouquetChangeCB
417 self.ask_time = now - tmp
418 self.closeRecursive = False
419 self["key_red"] = Button("")
420 self["key_green"] = Button("")
421 self.key_green_choice = self.EMPTY
422 self.key_red_choice = self.EMPTY
423 self["timeline_text"] = TimelineText()
424 self["Event"] = Event()
425 self.time_lines = [ ]
426 for x in (0,1,2,3,4,5):
428 self.time_lines.append(pm)
429 self["timeline%d"%(x)] = pm
430 self["timeline_now"] = Pixmap()
431 self.services = services
432 self.zapFunc = zapFunc
434 self["list"] = EPGList(selChangedCB = self.onSelectionChanged, timer = self.session.nav.RecordTimer, time_epoch = config.misc.graph_mepg_prev_time_period.value )
436 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions"],
438 "cancel": self.closeScreen,
439 "ok": self.eventSelected,
440 "timerAdd": self.timerAdd,
441 "info": self.infoKeyPressed,
443 "input_date_time": self.enterDateTime,
444 "nextBouquet": self.nextBouquet,
445 "prevBouquet": self.prevBouquet,
447 self["actions"].csel = self
449 self["input_actions"] = ActionMap(["InputActions"],
451 "left": self.leftPressed,
452 "right": self.rightPressed,
460 self.updateTimelineTimer = eTimer()
461 self.updateTimelineTimer.callback.append(self.moveTimeLines)
462 self.updateTimelineTimer.start(60*1000)
463 self.onLayoutFinish.append(self.onCreate)
465 def leftPressed(self):
468 def rightPressed(self):
471 def nextEvent(self, visible=True):
472 ret = self["list"].selEntry(+1, visible)
474 self.moveTimeLines(True)
476 def prevEvent(self, visible=True):
477 ret = self["list"].selEntry(-1, visible)
479 self.moveTimeLines(True)
482 self["list"].setEpoch(60)
483 config.misc.graph_mepg_prev_time_period.value = 60
487 self["list"].setEpoch(120)
488 config.misc.graph_mepg_prev_time_period.value = 120
492 self["list"].setEpoch(180)
493 config.misc.graph_mepg_prev_time_period.value = 180
497 self["list"].setEpoch(240)
498 config.misc.graph_mepg_prev_time_period.value = 240
502 self["list"].setEpoch(300)
503 config.misc.graph_mepg_prev_time_period.value = 300
506 def nextBouquet(self):
507 if self.bouquetChangeCB:
508 self.bouquetChangeCB(1, self)
510 def prevBouquet(self):
511 if self.bouquetChangeCB:
512 self.bouquetChangeCB(-1, self)
514 def enterDateTime(self):
515 self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.graph_mepg_prev_time )
517 def onDateTimeInputClosed(self, ret):
523 l.fillMultiEPG(self.services, ret[1])
524 self.moveTimeLines(True)
526 def closeScreen(self):
527 self.close(self.closeRecursive)
529 def infoKeyPressed(self):
530 cur = self["list"].getCurrent()
533 if event is not None:
534 self.session.open(EventViewSimple, event, service, self.eventViewCallback, self.openSimilarList)
536 def openSimilarList(self, eventid, refstr):
537 self.session.open(EPGSelection, refstr, None, eventid)
539 def setServices(self, services):
540 self.services = services
543 #just used in multipeg
545 self["list"].fillMultiEPG(self.services, self.ask_time)
546 self["list"].moveToService(self.session.nav.getCurrentlyPlayingServiceReference())
549 def eventViewCallback(self, setEvent, setService, val):
553 self.prevEvent(False)
555 self.nextEvent(False)
557 if cur[0] is None and cur[1].ref != old[1].ref:
558 self.eventViewCallback(setEvent, setService, val)
564 if self.zapFunc and self.key_red_choice == self.ZAP:
565 self.closeRecursive = True
566 ref = self["list"].getCurrent()[1]
568 self.zapFunc(ref.ref)
570 def eventSelected(self):
571 self.infoKeyPressed()
573 def removeTimer(self, timer):
574 timer.afterEvent = AFTEREVENT.NONE
575 self.session.nav.RecordTimer.removeEntry(timer)
576 self["key_green"].setText(_("Add timer"))
577 self.key_green_choice = self.ADD_TIMER
580 cur = self["list"].getCurrent()
585 eventid = event.getEventId()
586 refstr = serviceref.ref.toString()
587 for timer in self.session.nav.RecordTimer.timer_list:
588 if timer.eit == eventid and timer.service_ref.ref.toString() == refstr:
589 cb_func = lambda ret : not ret or self.removeTimer(timer)
590 self.session.openWithCallback(cb_func, MessageBox, _("Do you really want to delete %s?") % event.getEventName())
593 newEntry = RecordTimerEntry(serviceref, checkOldTimers = True, *parseEvent(event))
594 self.session.openWithCallback(self.finishedAdd, TimerEntry, newEntry)
596 def finishedAdd(self, answer):
600 simulTimerList = self.session.nav.RecordTimer.record(entry)
601 if simulTimerList is not None:
602 for x in simulTimerList:
603 if x.setAutoincreaseEnd(entry):
604 self.session.nav.RecordTimer.timeChanged(x)
605 simulTimerList = self.session.nav.RecordTimer.record(entry)
606 if simulTimerList is not None:
607 self.session.openWithCallback(self.finishSanityCorrection, TimerSanityConflict, simulTimerList)
608 self["key_green"].setText(_("Remove timer"))
609 self.key_green_choice = self.REMOVE_TIMER
611 self["key_green"].setText(_("Add timer"))
612 self.key_green_choice = self.ADD_TIMER
613 print "Timeredit aborted"
615 def finishSanityCorrection(self, answer):
616 self.finishedAdd(answer)
618 def onSelectionChanged(self):
619 cur = self["list"].getCurrent()
621 if self.key_green_choice != self.EMPTY:
622 self["key_green"].setText("")
623 self.key_green_choice = self.EMPTY
624 if self.key_red_choice != self.EMPTY:
625 self["key_red"].setText("")
626 self.key_red_choice = self.EMPTY
630 self["Event"].newEvent(event)
632 if cur[1] is None or cur[1].getServiceName() == "":
633 if self.key_green_choice != self.EMPTY:
634 self["key_green"].setText("")
635 self.key_green_choice = self.EMPTY
636 if self.key_red_choice != self.EMPTY:
637 self["key_red"].setText("")
638 self.key_red_choice = self.EMPTY
640 elif self.key_red_choice != self.ZAP:
641 self["key_red"].setText("Zap")
642 self.key_red_choice = self.ZAP
645 if self.key_green_choice != self.EMPTY:
646 self["key_green"].setText("")
647 self.key_green_choice = self.EMPTY
651 eventid = event.getEventId()
652 refstr = serviceref.ref.toString()
653 isRecordEvent = False
654 for timer in self.session.nav.RecordTimer.timer_list:
655 if timer.eit == eventid and timer.service_ref.ref.toString() == refstr:
658 if isRecordEvent and self.key_green_choice != self.REMOVE_TIMER:
659 self["key_green"].setText(_("Remove timer"))
660 self.key_green_choice = self.REMOVE_TIMER
661 elif not isRecordEvent and self.key_green_choice != self.ADD_TIMER:
662 self["key_green"].setText(_("Add timer"))
663 self.key_green_choice = self.ADD_TIMER
665 def moveTimeLines(self, force=False):
666 self.updateTimelineTimer.start((60-(int(time())%60))*1000) #keep syncronised
668 event_rect = l.getEventRect()
669 time_epoch = l.getTimeEpoch()
670 time_base = l.getTimeBase()
671 if event_rect is None or time_epoch is None or time_base is None:
673 time_steps = time_epoch > 180 and 60 or 30
675 num_lines = time_epoch/time_steps
676 incWidth=event_rect.width()/num_lines
677 pos=event_rect.left()
678 timeline_entries = [ ]
681 for line in self.time_lines:
682 old_pos = line.position
683 new_pos = (x == num_lines and event_rect.left()+event_rect.width() or pos, old_pos[1])
684 if not x or x >= num_lines:
687 if old_pos != new_pos:
688 line.setPosition(new_pos[0], new_pos[1])
691 if not x or line.visible:
692 timeline_entries.append((time_base + x * time_steps * 60, new_pos[0]))
696 if changecount or force:
697 self["timeline_text"].setEntries(timeline_entries)
700 timeline_now = self["timeline_now"]
701 if now >= time_base and now < (time_base + time_epoch * 60):
702 xpos = int((((now - time_base) * event_rect.width()) / (time_epoch * 60))-(timeline_now.instance.size().width()/2))
703 old_pos = timeline_now.position
704 new_pos = (xpos+event_rect.left(), old_pos[1])
705 if old_pos != new_pos:
706 timeline_now.setPosition(new_pos[0], new_pos[1])
707 timeline_now.visible = True
709 timeline_now.visible = False
710 # here no l.l.invalidate() is needed when the zPosition in the skin is correct!