Skip to content

Commit

Permalink
[DateTimeInput.py] Replace TimeDateInput with DateTimeInput (#3198)
Browse files Browse the repository at this point in the history
[DateTimeInput.py]
- This is a new module that offers a more concise user interface for setting times for instant recordings and EPG navigation.
- This new code is a subclass of Setup to reduce the amount of code copied from Setup.py and ConfigList.py.
- This code uses consistent navigation relative to other Setup based screens.
- The times entered now directly and accurately update the date *without* requiring an extra control with which users must interact.
- The range of times and dates the user may select is now limited to only valid values.  For example, instant recordings are now correctly limited to 24 hours and EPG jumps can be made forward in time as well as backwards in time to the extent of the EPG history that is enabled.
- HELP is now available in this screen.
- Button labels better reflect the actions to be performed when pressed.

[InfoBarGenerics.py]
- Switch from using TimeDateInput.py to DateTimeInput.py.
- Optimize some code.
- Improve some translation strings.
- Use f-strings to improve performance of the button press logging.

[EpgSelection.py]
- Switch from using TimeDateInput.py to DateTimeInput.py.
- Improve the action map that controls the DateTimeInput screen.
- Remove use of getSkinFactor as it is not used in openATV (openATV has fully integrated and automatic skin scaling)  and can cause skin issues.
- PEP8 correct some code indentation.
- Improve some translation strings.

---------

Co-authored-by: jbleyel <[email protected]>
  • Loading branch information
IanSav and jbleyel authored Dec 23, 2023
1 parent 437e353 commit 3324017
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 171 deletions.
85 changes: 85 additions & 0 deletions lib/python/Screens/DateTimeInput.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from time import time

from Components.ActionMap import HelpableActionMap
from Components.config import ConfigDateTime, config
from Screens.Setup import Setup


class DateTime(Setup):
def __init__(self, session, setupSection, default, smallStep=60):
self.dateTime = ConfigDateTime(default, f"{config.usage.date.daylong.value} {config.usage.time.short.value}", increment=smallStep)
Setup.__init__(self, session, setupSection)
self.skinName.insert(0, "SetupDateTime")
self["prevNextActions"] = HelpableActionMap(self, ["PreviousNextActions"], { # This is for users who do not use PREV/NEXT as first/last.
"previous": (self.keyFirst, _("Jump back one hour") if self.bigStep == 3600 else _("Jump back one day")),
"next": (self.keyLast, _("Jump forward one hour") if self.bigStep == 3600 else _("Jump forward one day"))
}, prio=0, description=_("Date Time Input Actions"))

def changedEntry(self): # This overrides the same class in Setup.py.
current = self["config"].getCurrent()
if current and current[1] == self.dateTime:
if self.dateTime.value < self.minLimit:
self.dateTime.value = self.minLimit
elif self.dateTime.value > self.maxLimit:
self.dateTime.value = self.maxLimit
self["config"].invalidateCurrent()
Setup.changedEntry(self)

def keyFirst(self): # This overrides the same class in ConfigList.py as part of Setup.py.
current = self["config"].getCurrent()
if current and current[1] == self.dateTime:
self.dateTime.value -= self.bigStep
if self.dateTime.value < self.minLimit:
self.dateTime.value = self.minLimit
self["config"].invalidateCurrent()

def keyLast(self): # This overrides the same class in ConfigList.py as part of Setup.py.
current = self["config"].getCurrent()
if current and current[1] == self.dateTime:
self.dateTime.value += self.bigStep
if self.dateTime.value > self.maxLimit:
self.dateTime.value = self.maxLimit
self["config"].invalidateCurrent()

def keySave(self): # This overrides the same class in ConfigList.py as part of Setup.py.
self.close((True, self.dateTime.value))

def keyCancel(self): # This overrides the same class in ConfigList.py as part of Setup.py.
self.close((False,))

def closeRecursive(self): # This overrides the same class in ConfigList.py as part of Setup.py.
self.close((True,))


class EPGJumpTime(DateTime):
def __init__(self, session, configElement, historyBuffer):
self.minLimit = int(time()) - (historyBuffer * 60) # Now - EPG history buffer length.
self.maxLimit = int(time()) + 2419200 # Now + 4 weeks.
self.smallStep = 3600 # 1 Hour.
self.bigStep = 86400 # 1 Day.
DateTime.__init__(self, session, "EPGJumpTime", default=int(time()), smallStep=self.smallStep)
self.setTitle(_("EPG Jump"))
self["key_green"].setText(_("Jump"))

def createSetup(self): # This overrides the same class in Setup.py.
configList = [
(_("Jump time"), self.dateTime, _("Select the time to which the EPG should be positioned. Press LEFT/RIGHT to decrease/increase the time by an hour. Press PREV/NEXT to decrease/increase the time by a day."))
]
self["config"].setList(configList)


class InstantRecordingEndTime(DateTime):
def __init__(self, session, endTime):
self.minLimit = int(time()) + 60 # Now + 1 minute.
self.maxLimit = int(time()) + 86400 # Now + 1 day.
self.smallStep = 60 # 1 Minute.
self.bigStep = 3600 # 1 Hour.
DateTime.__init__(self, session, "InstantRecordingEndTime", default=endTime, smallStep=self.smallStep)
self.setTitle(_("Instant Recording End Time"))
self["key_green"].setText(_("Set Time"))

def createSetup(self): # This overrides the same class in Setup.py.
configList = [
(_("End time"), self.dateTime, _("Select the time when this instant recording timer should end. Press LEFT/RIGHT to decrease/increase the time by a minute. Press PREV/NEXT to decrease/increase the time by an hour."))
]
self["config"].setList(configList)
104 changes: 52 additions & 52 deletions lib/python/Screens/EpgSelection.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from RecordTimer import AFTEREVENT, RecordTimerEntry, parseEvent
from ServiceReference import ServiceReference
from skin import getSkinFactor
from Components.ActionMap import HelpableActionMap, HelpableNumberActionMap
from Components.config import ConfigClock, config, configfile
from Components.EpgList import EPG_TYPE_ENHANCED, EPG_TYPE_GRAPH, EPG_TYPE_INFOBAR, EPG_TYPE_INFOBARGRAPH, EPG_TYPE_MULTI, EPG_TYPE_SIMILAR, EPG_TYPE_SINGLE, EPG_TYPE_VERTICAL, EPGBouquetList, EPGList, MAX_TIMELINES, TimelineText
Expand All @@ -16,13 +15,13 @@
from Components.Sources.StaticText import StaticText
from Components.UsageConfig import preferredTimerPath
from Screens.ChoiceBox import ChoiceBox
from Screens.DateTimeInput import EPGJumpTime
from Screens.EventView import EventViewEPGSelect, EventViewSimple
from Screens.HelpMenu import HelpableScreen
from Screens.MessageBox import MessageBox
from Screens.PictureInPicture import PictureInPicture
from Screens.Screen import Screen
from Screens.Setup import Setup
from Screens.TimeDateInput import TimeDateInput
from Screens.TimerEdit import TimerSanityConflict
from Screens.TimerEntry import InstantRecordTimerEntry, TimerEntry

Expand Down Expand Up @@ -347,16 +346,16 @@ def __init__(self, session, service=None, zapFunc=None, eventid=None, bouquetCha
self["bouquetokactions"].csel = self
self["bouquetokactions"].setEnabled(False)
self["input_actions"] = HelpableNumberActionMap(self, ["NumberActions"], {
"1": (self.keyNumberGlobal, _("Goto first channel")),
"2": (self.keyNumberGlobal, _("All events up")),
"3": (self.keyNumberGlobal, _("Goto last channel")),
"4": (self.keyNumberGlobal, _("Previous channel page")),
"0": (self.keyNumberGlobal, _("Goto current channel and now")),
"6": (self.keyNumberGlobal, _("Next channel page")),
"7": (self.keyNumberGlobal, _("Goto now")),
"8": (self.keyNumberGlobal, _("All events down")),
"9": (self.keyNumberGlobal, _("Goto Prime Time")),
"5": (self.keyNumberGlobal, _("Set Base Time"))
"1": (self.keyNumberGlobal, _("Goto first channel")),
"2": (self.keyNumberGlobal, _("All events up")),
"3": (self.keyNumberGlobal, _("Goto last channel")),
"4": (self.keyNumberGlobal, _("Previous channel page")),
"0": (self.keyNumberGlobal, _("Goto current channel and now")),
"6": (self.keyNumberGlobal, _("Next channel page")),
"7": (self.keyNumberGlobal, _("Goto now")),
"8": (self.keyNumberGlobal, _("All events down")),
"9": (self.keyNumberGlobal, _("Goto Prime Time")),
"5": (self.keyNumberGlobal, _("Set Base Time"))
}, prio=-1, description=_("EPG Other Actions"))
if self.type == EPG_TYPE_GRAPH:
time_epoch = int(config.epgselection.graph_prevtimeperiod.value)
Expand Down Expand Up @@ -864,41 +863,41 @@ def prevService(self):
self.serviceChangeCB(-1, self)

def enterDateTime(self):
global mepg_config_initialized
if self.type == EPG_TYPE_MULTI:
if not mepg_config_initialized:
config.misc.prev_mepg_time = ConfigClock(default=time())
mepg_config_initialized = True
self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.misc.prev_mepg_time)
elif self.type == EPG_TYPE_GRAPH:
self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.epgselection.graph_prevtime)
elif self.type == EPG_TYPE_INFOBARGRAPH:
self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.epgselection.infobar_prevtime)
elif self.type == EPG_TYPE_VERTICAL:
self.session.openWithCallback(self.onDateTimeInputClosed, TimeDateInput, config.epgselection.vertical_prevtime)

def onDateTimeInputClosed(self, ret):
if len(ret) > 1:
if ret[0]:
self.ask_time = ret[1]
def enterDateTimeCallback(result):
if len(result) > 1 and result[0]:
jumpTime = result[1]
if self.type == EPG_TYPE_MULTI:
self["list"].fillMultiEPG(self.services, self.ask_time)
elif self.type == EPG_TYPE_GRAPH or self.type == EPG_TYPE_INFOBARGRAPH:
self["list"].fillMultiEPG(self.services, jumpTime)
elif self.type in (EPG_TYPE_GRAPH, EPG_TYPE_INFOBARGRAPH):
if self.type == EPG_TYPE_GRAPH:
self.ask_time -= self.ask_time % (int(config.epgselection.graph_roundto.value) * 60)
elif self.type == EPG_TYPE_INFOBARGRAPH:
self.ask_time -= self.ask_time % (int(config.epgselection.infobar_roundto.value) * 60)
l = self["list"]
l.resetOffset()
l.fillGraphEPG(None, self.ask_time)
jumpTime -= jumpTime % (int(config.epgselection.graph_roundto.value) * 60)
else:
jumpTime -= jumpTime % (int(config.epgselection.infobar_roundto.value) * 60)
epgList = self["list"]
epgList.resetOffset()
epgList.fillGraphEPG(None, jumpTime)
self.moveTimeLines(True)
elif EPG_TYPE_VERTICAL:
if self.ask_time > time():
elif self.type == EPG_TYPE_VERTICAL:
if jumpTime > time():
self.updateVerticalEPG()
else:
self.ask_time = -1
if self.eventviewDialog and (self.type == EPG_TYPE_INFOBAR or self.type == EPG_TYPE_INFOBARGRAPH):
self.infoKeyPressed(True)
jumpTime = -1
self.ask_time = jumpTime
if self.eventviewDialog and self.type in (EPG_TYPE_INFOBAR, EPG_TYPE_INFOBARGRAPH):
self.infoKeyPressed(True)

if self.type == EPG_TYPE_GRAPH:
self.session.openWithCallback(enterDateTimeCallback, EPGJumpTime, config.epgselection.graph_prevtime, config.epg.histminutes.value)
elif self.type == EPG_TYPE_INFOBARGRAPH:
self.session.openWithCallback(enterDateTimeCallback, EPGJumpTime, config.epgselection.infobar_prevtime, config.epg.histminutes.value)
elif self.type == EPG_TYPE_MULTI:
global mepg_config_initialized
if not mepg_config_initialized:
config.misc.prev_mepg_time = ConfigClock(default=time())
mepg_config_initialized = True
self.session.openWithCallback(enterDateTimeCallback, EPGJumpTime, config.misc.prev_mepg_time, 0)
elif self.type == EPG_TYPE_VERTICAL:
self.session.openWithCallback(enterDateTimeCallback, EPGJumpTime, config.epgselection.vertical_prevtime, config.epg.histminutes.value)

def infoKeyPressed(self, eventviewopen=False):
cur = self["list" + str(self.activeList)].getCurrent()
Expand Down Expand Up @@ -1444,16 +1443,15 @@ def RecordTimerQuestion(self, manual=False):
self.session.openWithCallback(self.finishedAdd, TimerEntry, newEntry)

if title:
self.ChoiceBoxDialog = self.session.instantiateDialog(ChoiceBox, title=title, list=menu, keys=["red", "green", "yellow", "blue"], skin_name="RecordTimerQuestion")
serviceref = eServiceReference(str(self["list" + str(self.activeList)].getCurrent()[1]))
pos = self["list" + str(self.activeList)].getSelectionPosition(serviceref, self.activeList)
sf = getSkinFactor()
posx = max(self.instance.position().x() + pos[0] - self.ChoiceBoxDialog.instance.size().width() - 20 * sf, 0)
posy = self.instance.position().y() + pos[1]
posy += self["list" + str(self.activeList)].itemHeight - 2 * sf
if posy + self.ChoiceBoxDialog.instance.size().height() > 720 * sf:
posy -= self["list" + str(self.activeList)].itemHeight - 4 * sf + self.ChoiceBoxDialog.instance.size().height()
self.ChoiceBoxDialog.instance.move(ePoint(int(posx), int(posy)))
self.ChoiceBoxDialog = self.session.instantiateDialog(ChoiceBox, text=title, choiceList=menu, buttonList=["red", "green", "yellow", "blue"], skinName="RecordTimerQuestion")
serviceRef = eServiceReference(str(self[f"list{self.activeList}"].getCurrent()[1]))
pos = self[f"list{self.activeList}"].getSelectionPosition(serviceRef, self.activeList)
posX = max(self.instance.position().x() + pos[0] - self.ChoiceBoxDialog.instance.size().width(), 0)
posY = self.instance.position().y() + pos[1]
posY += self[f"list{self.activeList}"].itemHeight - 2
if posY + self.ChoiceBoxDialog.instance.size().height() > 720:
posY -= self[f"list{self.activeList}"].itemHeight - 4 + self.ChoiceBoxDialog.instance.size().height()
self.ChoiceBoxDialog.instance.move(ePoint(int(posX), int(posY)))
self.showChoiceBoxDialog()

def recButtonPressed(self):
Expand Down Expand Up @@ -1493,6 +1491,7 @@ def showChoiceBoxDialog(self):
self["epgactions"].setEnabled(False)
self["dialogactions"].setEnabled(True)
self.ChoiceBoxDialog["actions"].execBegin()
self.ChoiceBoxDialog["navigationActions"].execBegin()
self.ChoiceBoxDialog.show()
if "input_actions" in self:
self["input_actions"].setEnabled(False)
Expand All @@ -1501,6 +1500,7 @@ def closeChoiceBoxDialog(self):
self["dialogactions"].setEnabled(False)
if self.ChoiceBoxDialog:
self.ChoiceBoxDialog["actions"].execEnd()
self.ChoiceBoxDialog["navigationActions"].execEnd()
self.session.deleteDialog(self.ChoiceBoxDialog)
self["okactions"].setEnabled(True)
if "epgcursoractions" in self:
Expand Down
66 changes: 30 additions & 36 deletions lib/python/Screens/InfoBarGenerics.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from Plugins.Plugin import PluginDescriptor
from Screens.ChannelSelection import ChannelSelection, PiPZapSelection, BouquetSelector, SilentBouquetSelector, EpgBouquetSelector, service_types_tv
from Screens.ChoiceBox import ChoiceBox
from Screens.DateTimeInput import InstantRecordingEndTime
from Screens.Dish import Dish
from Screens.EpgSelection import EPGSelection
from Screens.EventView import EventViewEPGSelect, EventViewSimple
Expand All @@ -52,7 +53,6 @@
from Screens.Setup import Setup
import Screens.Standby
from Screens.Standby import Standby, TryQuitMainloop
from Screens.TimeDateInput import TimeDateInput
from Screens.Timers import RecordTimerEdit, RecordTimerOverview
from Screens.UnhandledKey import UnhandledKey
from Tools import Notifications
Expand Down Expand Up @@ -456,7 +456,7 @@ def __init__(self):
# 3 = Long.
# 4 = ASCII.
def processKeyA(self, key, flag): # This function is called on every key press!
print("[InfoBarGenerics] Key '%s' (%s) %s." % (KEYIDNAMES.get(key, _("Unknown")), key, KEYFLAGS.get(flag, _("Unknown"))))
print(f"[InfoBarGenerics] Key '{KEYIDNAMES.get(key, _('Unknown'))}' ({key}) {KEYFLAGS.get(flag, _('Unknown'))}.")
for callback in keyPressCallback:
callback()
if self.closeSecondInfoBar(key) and self.secondInfoBarScreen and self.secondInfoBarScreen.shown:
Expand Down Expand Up @@ -3939,11 +3939,11 @@ def startInstantRecording(self, limitEvent=False):
if recording.setAutoincreaseEnd():
self.session.nav.RecordTimer.record(recording)
self.recording.append(recording)
self.session.open(MessageBox, _("Record time limited due to conflicting timer: %s") % f"\n{name_date}", MessageBox.TYPE_INFO)
self.session.open(MessageBox, _("Record time limited due to conflicting timer:%s") % f"\n\t'{name_date}'", MessageBox.TYPE_INFO)
else:
self.session.open(MessageBox, _("Could not record due to conflicting timer: %s") % f"\n{name}", MessageBox.TYPE_INFO)
self.session.open(MessageBox, _("Could not record due to conflicting timer:%s") % f"\n\t'{name}'", MessageBox.TYPE_INFO)
else:
self.session.open(MessageBox, _("Could not record due to invalid service: %s") % f"\n{serviceref}", MessageBox.TYPE_INFO)
self.session.open(MessageBox, _("Could not record due to invalid service:%s") % f"\n\t'{serviceref}'", MessageBox.TYPE_INFO)
recording.autoincrease = False

def startRecordingCurrentEvent(self):
Expand Down Expand Up @@ -4008,40 +4008,36 @@ def recordQuestionCallback(self, answer):
self.saveTimeshiftEventPopupActive = False

def changeEndtime(self, entry):
def changeEndtimeCallback(result):
if len(result) > 1 and result[0]:
print(f"[InfoBarGenerics] Instant recording due to stop at {strftime('%F %T', localtime(result[1]))}.")
if recordingEntry.end != result[1]:
recordingEntry.autoincrease = False
recordingEntry.end = result[1]
recordingEntry.eventEnd = recordingEntry.end
recordingEntry.marginAfter = 0 # Why is this being done?
self.session.nav.RecordTimer.timeChanged(recordingEntry)

if entry is not None and entry >= 0:
self.selectedEntry = entry
self.endtime = ConfigClock(default=self.recording[self.selectedEntry].end)
dlg = self.session.openWithCallback(self.changeEndtimeCallback, TimeDateInput, self.endtime)
dlg.setTitle(_("Please change recording endtime"))
recordingEntry = self.recording[entry]
self.session.openWithCallback(changeEndtimeCallback, InstantRecordingEndTime, recordingEntry.eventEnd)

def changeEndtimeCallback(self, ret):
if len(ret) > 1 and ret[0]:
print(f"stop recording at {strftime('%F %T', localtime(ret[1]))}")
def changeDuration(self, entry):
def changeDurationCallback(value):
entry = self.recording[self.selectedEntry]
if entry.end != ret[1]:
entry.autoincrease = False
entry.end = ret[1]
entry.eventEnd = entry.end
entry.marginAfter = 0
self.session.nav.RecordTimer.timeChanged(self.recording[self.selectedEntry])
if value is not None:
value = int(value.replace(" ", "") or "0")
if value:
entry.autoincrease = False
print(f"[InfoBarGenerics] Instant recording due to stop after {value} minutes.")
entry.end = int(time()) + 60 * value
entry.eventEnd = entry.end
entry.marginAfter = 0
self.session.nav.RecordTimer.timeChanged(entry)

def changeDuration(self, entry):
if entry is not None and entry >= 0:
self.selectedEntry = entry
self.session.openWithCallback(self.changeDurationCallback, InputBox, title=_("How many minutes do you want to record?"), text="5 ", maxSize=True, type=Input.NUMBER)

def changeDurationCallback(self, value):
entry = self.recording[self.selectedEntry]
if value is not None:
value = int(value.replace(" ", "") or "0")
if value:
entry.autoincrease = False
print(f"stop recording after {value} minutes.")
entry.end = int(time()) + 60 * value
entry.eventBegin = entry.begin
entry.eventEnd = entry.end
entry.marginAfter = 0
self.session.nav.RecordTimer.timeChanged(entry)
self.session.openWithCallback(changeDurationCallback, InputBox, title=_("For how many minutes do you want to record?"), text="5 ", maxSize=True, type=Input.NUMBER)

def isTimerRecordRunning(self):
identical = timers = 0
Expand Down Expand Up @@ -4077,7 +4073,7 @@ def instantRecord(self, serviceRef=None):
commonRecord = []
commonTimeshift = []
if self.isInstantRecordRunning():
title = f'{_("A recording is currently running.")}\n{_("What do you want to do?")}'
title = f"{_('A recording is currently running.')}\n\n{_('What do you want to do?')}"
choiceList = [
(_("Stop recording"), "stop")
] + commonRecord + [
Expand All @@ -4097,8 +4093,6 @@ def instantRecord(self, serviceRef=None):
choiceList.append((_("Do not record"), "no"))
if choiceList:
self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, title=title, list=choiceList)
# else:
# return 0


class InfoBarAudioSelection:
Expand Down
Loading

1 comment on commit 3324017

@GhostofGeeeee
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a problem.
I initialize InstantRecordingEndTime with a time and 35 seconds deviation to the full minute.
If I change the time, I get the new displayed time +35 seconds. But I should get the full minute, because seconds are not displayed.
Except I do not make any changes, then it would be good to keep the 35 seconds.

Another ask!

class DateTime(Setup):
	def __init__(self, session, setupSection, default, smallStep=60):

Why "smallStep=60"? Then bigStep=3600 too!

Please sign in to comment.