2 new commits in StationPlaylist:
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/d0c7b955adc0/
Changeset: d0c7b955adc0
Branch: None
User: josephsl
Date: 2016-01-10 00:36:23+00:00
Summary: Update copyright years, remove unnecessary code.
Affected #: 6 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 7b253a7..c52b3c6 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1,6 +1,6 @@
# StationPlaylist Studio
# An app module and global plugin package for NVDA
-# Copyright 2011, 2013-2015, Geoff Shang, Joseph Lee and others, released
under GPL.
+# Copyright 2011, 2013-2016, Geoff Shang, Joseph Lee and others, released
under GPL.
# The primary function of this appModule is to provide meaningful feedback to
users of SplStudio
# by allowing speaking of items which cannot be easily found.
# Version 0.01 - 7 April 2011:
@@ -67,10 +67,6 @@ libScanT = None
# Blacklisted versions of Studio where library scanning functionality is
broken.
noLibScanMonitor = []
-# List of known window style values to check for track items in Studio 5.0x..
-known50styles = (1442938953, 1443987529, 1446084681)
-known51styles = (1443991625, 1446088777)
-
# Braille and play a sound in response to an alarm or an event.
def messageSound(wavFile, message):
nvwave.playWaveFile(wavFile)
@@ -450,8 +446,6 @@ class AppModule(appModuleHandler.AppModule):
# Keep an eye on library scans in insert tracks window.
libraryScanning = False
scanCount = 0
- # For SPL 5.10: take care of some object child constant changes across
builds.
- spl510used = False
# For 5.0X and earlier: prevent NVDA from announcing scheduled time
multiple times.
scheduledTimeCache = ""
# Track Dial (A.K.A. enhanced arrow keys)
@@ -576,7 +570,7 @@ class AppModule(appModuleHandler.AppModule):
# Perform extra action in specific situations (mic alarm, for example).
def doExtraAction(self, status):
- micAlarm =
int(splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarm"])
+ micAlarm = splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarm"]
if self.cartExplorer:
if status == "Cart Edit On":
# Translators: Presented when cart edit mode is
toggled on while cart explorer is on.
@@ -778,7 +772,7 @@ class AppModule(appModuleHandler.AppModule):
wx.CallAfter(gui.messageBox, _("The add-on settings
dialog is opened. Please close the settings dialog first."), _("Error"),
wx.OK|wx.ICON_ERROR)
return
try:
- rampVal =
long(splconfig.SPLConfig["IntroOutroAlarms"]["SongRampTime"])
+ rampVal =
splconfig.SPLConfig["IntroOutroAlarms"]["SongRampTime"]
d = splconfig.SPLAlarmDialog(gui.mainFrame,
"SongRampTime", "SaySongRamp",
# Translators: The title of song intro alarm dialog.
_("Song intro alarm"),
@@ -897,7 +891,6 @@ class AppModule(appModuleHandler.AppModule):
# Column is a list of columns to be searched (if none, it'll be artist
and title).
def _trackLocator(self, text, obj=api.getFocusObject(),
directionForward=True, columns=None):
nextTrack = "next" if directionForward else "previous"
- t = time.time()
while obj is not None:
# Do not use column content attribute, because
sometimes NVDA will say it isn't a track item when in fact it is.
# If this happens, use the module level version of
column content getter.
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 4032c6d..61be9ec 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -418,7 +418,6 @@ def shouldSave(profile):
if isinstance(profile[section], dict):
for key in profile[section]:
if profile[section][key] !=
_SPLCache[tree][section][key]:
- print key
return True # Setting modified.
return False
@@ -917,7 +916,6 @@ class SPLConfigDialog(gui.SettingsDialog):
# It also causes NVDA to display wrong label for switch button.
if self.switchProfile is None:
SPLPrevProfile = None
- global _configDialogOpened
_configDialogOpened = False
# 7.0: Perform extra action such as restarting auto update
timer.
self.onCloseExtraAction()
@@ -935,8 +933,7 @@ class SPLConfigDialog(gui.SettingsDialog):
# Return to normal profile by merging the first profile
in the config pool.
mergeSections(0)
_configDialogOpened = False
- #super(SPLConfigDialog, self).onCancel(evt)
- self.Destroy()
+ super(SPLConfigDialog, self).onCancel(evt)
# Perform extra action when closing this dialog such as restarting
update timer.
def onCloseExtraAction(self):
diff --git a/addon/appModules/splstudio/splmisc.py
b/addon/appModules/splstudio/splmisc.py
index 43f5d37..cb8b928 100755
--- a/addon/appModules/splstudio/splmisc.py
+++ b/addon/appModules/splstudio/splmisc.py
@@ -1,6 +1,6 @@
# SPL Studio Miscellaneous User Interfaces and internal services
# An app module and global plugin package for NVDA
-# Copyright 2015 Joseph Lee and others, released under GPL.
+# Copyright 2015-2016 Joseph Lee and others, released under GPL.
# Miscellaneous functions and user interfaces
# Split from config module in 2015.
diff --git a/addon/appModules/tracktool.py b/addon/appModules/tracktool.py
index e649eb7..419a58b 100755
--- a/addon/appModules/tracktool.py
+++ b/addon/appModules/tracktool.py
@@ -1,6 +1,6 @@
# StationPlaylist Track Tool
# An app module for NVDA
-# Copyright 2014-2015 Joseph Lee and contributors, released under gPL.
+# Copyright 2014-2016 Joseph Lee and contributors, released under gPL.
# Functionality is based on JFW scripts for SPL Track Tool by Brian Hartgen.
import appModuleHandler
diff --git a/addon/globalPlugins/SPLStudioUtils/__init__.py
b/addon/globalPlugins/SPLStudioUtils/__init__.py
index 5cec1ee..f1d1fa7 100755
--- a/addon/globalPlugins/SPLStudioUtils/__init__.py
+++ b/addon/globalPlugins/SPLStudioUtils/__init__.py
@@ -1,6 +1,6 @@
# StationPlaylist Utilities
# Author: Joseph Lee
-# Copyright 2013-2015, released under GPL.
+# Copyright 2013-2016, released under GPL.
# Adds a few utility features such as switching focus to the SPL Studio window
and some global scripts.
# For encoder support, see the encoders package.
diff --git a/addon/globalPlugins/SPLStudioUtils/encoders.py
b/addon/globalPlugins/SPLStudioUtils/encoders.py
index 5a9d874..42af2f8 100755
--- a/addon/globalPlugins/SPLStudioUtils/encoders.py
+++ b/addon/globalPlugins/SPLStudioUtils/encoders.py
@@ -1,6 +1,6 @@
# StationPlaylist encoders support
# Author: Joseph Lee
-# Copyright 2015, released under GPL.
+# Copyright 2015-2016, released under GPL.
# Split from main global plugin in 2015.
import threading
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/8e95d02fbebb/
Changeset: 8e95d02fbebb
Branch: master
User: josephsl
Date: 2016-01-10 03:40:58+00:00
Summary: Merge branch '7.0/blueProfileTriggers'
Affected #: 3 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index c52b3c6..b6c877b 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1576,7 +1576,7 @@ class AppModule(appModuleHandler.AppModule):
script_trackTimeAnalysis.__doc__=_("Announces total length of tracks
between analysis start marker and the current track")
def script_switchProfiles(self, gesture):
- splconfig.instantProfileSwitch()
+ splconfig.triggerProfileSwitch() if
splconfig._SPLTriggerEndTimer.IsRunning() else splconfig.instantProfileSwitch()
def script_setPlaceMarker(self, gesture):
obj = api.getFocusObject()
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 61be9ec..3fc8c99 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -10,6 +10,10 @@ from configobj import ConfigObj
from validate import Validator
import weakref
import time
+import datetime
+import cPickle
+import calendar
+import math
import globalVars
import ui
import api
@@ -18,6 +22,7 @@ import wx
from winUser import user32
import tones
import splupdate
+from splmisc import SPLCountdownTimer
# Configuration management
SPLIni = os.path.join(globalVars.appArgs.configPath, "splstudio.ini")
@@ -211,6 +216,8 @@ def initConfig():
messages.append("{profileName}:
{errorMessage}".format(profileName = profile, errorMessage = error))
_configLoadStatus.clear()
runConfigErrorDialog("\n".join(messages), title)
+ # Fire up profile triggers.
+ initProfileTriggers()
# Unlock (load) profiles from files.
def unlockConfig(path, profileName=None, prefill=False):
@@ -310,6 +317,140 @@ def _cacheConfig(conf):
# Column inclusion only.
_SPLCache[key]["ColumnAnnouncement"]["IncludedColumns"] =
list(conf["ColumnAnnouncement"]["IncludedColumns"])
+# Record profile triggers.
+# Each record (profile name) consists of seven fields organized as a list:
+# A bit vector specifying which days should this profile be active, the first
five fields needed for constructing a datetime.datetime object used to look up
when to trigger this profile, and an integer specifying the duration in minutes.
+profileTriggers = {} # Using a pickle is quite elegant.
+# Profile triggers pickle.
+SPLTriggersFile = os.path.join(globalVars.appArgs.configPath,
"spltriggers.pickle")
+# Trigger timer.
+triggerTimer = None
+
+# Prepare the triggers dictionary and other runtime support.
+def initProfileTriggers():
+ global profileTriggers, SPLTriggerProfile, triggerTimer
+ try:
+ profileTriggers = cPickle.load(file(SPLTriggersFile, "r"))
+ except IOError:
+ pass
+ triggerStart()
+
+# Locate time-based profiles if any.
+# A 3-tuple will be returned, containing the next trigger time (for time delta
calculation), the profile name for this trigger time and whether an immediate
switch is necessary.
+# For now, the third field will be ignored (always set to False).
+def nextTimedProfile(current=None):
+ if current is None: current = datetime.datetime.now()
+ # No need to proceed if no timed profiles are defined.
+ if not len(profileTriggers): return None
+ possibleTriggers = []
+ for profile in profileTriggers.keys():
+ shouldBeSwitched = False
+ entry = list(profileTriggers[profile])
+ # Construct the comparison datetime (see the profile triggers
spec).
+ triggerTime = datetime.datetime(entry[1], entry[2], entry[3],
entry[4], entry[5])
+ # Hopefully the trigger should be ready before the show, but
sometimes it isn't.
+ if current > triggerTime:
+ print "time passed"
+ profileTriggers[profile] = setNextTimedProfile(profile,
entry[0], datetime.time(entry[4], entry[5]), date=current)
+ if (current-triggerTime).seconds < entry[6]*60:
+ shouldBeSwitched = True
+ possibleTriggers.append((triggerTime, profile,
shouldBeSwitched))
+ if len(possibleTriggers):
+ d = min(possibleTriggers)[0] - current
+ print d.days
+ print d.seconds
+ return min(possibleTriggers) if len(possibleTriggers) else None
+
+# Some helpers used in locating next air date/time.
+
+# Locate the trigger given a different date.
+# this is used if one misses a profile switch.
+def findNextAirDate(bits, date, dayIndex, hhmm):
+ triggerCandidate = 64 >> dayIndex
+ # Case 1: This is a weekly show.
+ if bits == triggerCandidate:
+ delta = 7
+ else:
+ # Scan the bit vector until finding the correct date and
calculate the resulting delta (dayIndex modulo 7).
+ # Take away the current trigger bit as this function is called
past the switch time.
+ days = bits-triggerCandidate if bits & triggerCandidate else
bits
+ currentDay = int(math.log(triggerCandidate, 2))
+ nextDay = int(math.log(days, 2))
+ # Hoping the resulting vector will have some bits set to 1...
+ if triggerCandidate > days:
+ delta = currentDay-nextDay
+ else:
+ triggerBit = -1
+ for bit in xrange(currentDay-1, -1, -1):
+ if 2 ** bit & days:
+ triggerBit = bit
+ break
+ if triggerBit > -1:
+ delta = currentDay-triggerBit
+ else:
+ delta = 7-(nextDay-currentDay)
+ date += datetime.timedelta(delta)
+ return [bits, date.year, date.month, date.day, hhmm.hour, hhmm.minute,
0]
+
+# Set the next timed profile.
+# Bits indicate the trigger days vector, hhmm is time, with the optional date
being a specific date otherwise current date.
+def setNextTimedProfile(profile, bits, switchTime, date=None):
+ if date is None: date = datetime.datetime.now()
+ dayIndex = date.weekday()
+ currentTime = datetime.time(date.hour, date.minute, date.second,
date.microsecond)
+ if (bits & (64 >> dayIndex)) and currentTime < switchTime:
+ return [bits, date.year, date.month, date.day, switchTime.hour,
switchTime.minute, 0]
+ else: return findNextAirDate(bits, date, dayIndex, switchTime)
+
+# Find if another profile is occupying the specified time slot.
+def duplicateExists(map, profile, bits, hour, min, duration):
+ if len(map) == 0 or (len(map) == 1 and profile in map): return False
+ # Convdrt hours and minutes to an integer for faster comparison.
+ start1 = (hour*60) + min
+ end1 = start1+duration
+ # A possible duplicate may exist simply because of bits.
+ for item in filter(lambda p: p != profile, map.keys()):
+ if map[item][0] == bits:
+ entry = map[item]
+ start2 = (entry[4] * 60) + entry[5]
+ end2 = start2+entry[6]
+ if start1 <= start2 <= end1 or start2 <= start1 <= end2:
+ return True
+ return False
+
+# Start the trigger timer based on above information.
+# Can be restarted if needed.
+def triggerStart(restart=False):
+ global SPLTriggerProfile, triggerTimer
+ # Restart the timer when called from triggers dialog in order to
prevent multiple timers from running.
+ if triggerTimer is not None and triggerTimer.IsRunning() and restart:
+ triggerTimer.Stop()
+ triggerTimer = None
+ queuedProfile = nextTimedProfile()
+ if queuedProfile is not None:
+ try:
+ SPLTriggerProfile = queuedProfile[1]
+ except ValueError:
+ SPLTriggerProfile = None
+ # We are in the midst of a show, so switch now.
+ if queuedProfile[2]:
+ triggerProfileSwitch()
+ else:
+ switchAfter = (queuedProfile[0] -
datetime.datetime.now())
+ if switchAfter.days == 0 and switchAfter.seconds <=
3600:
+ time.sleep((switchAfter.microseconds+1000) /
1000000.0)
+ triggerTimer =
SPLCountdownTimer(switchAfter.seconds, triggerProfileSwitch, 15)
+ triggerTimer.Start()
+
+# Dump profile triggers pickle away.
+def saveProfileTriggers():
+ global triggerTimer, profileTriggers
+ if triggerTimer is not None and triggerTimer.IsRunning():
+ triggerTimer.Stop()
+ triggerTimer = None
+ cPickle.dump(profileTriggers, file(SPLTriggersFile, "wb"))
+ profileTriggers = None
+
# Instant profile switch helpers.
# A number of helper functions assisting instant switch profile routine and
others, including sorting and locating the needed profile upon request.
@@ -355,6 +496,9 @@ def getProfileFlags(name):
if name == SPLSwitchProfile:
# Translators: A flag indicating the broadcast profile is an
instant switch profile.
flags.append(_("instant switch"))
+ if name in profileTriggers:
+ # Translators: A flag indicating the time-based triggers
profile.
+ flags.append(_("time-based"))
return name if len(flags) == 0 else "{0} <{1}>".format(name, ",
".join(flags))
# Is the config pool itself sorted?
@@ -429,6 +573,8 @@ def saveConfig():
# 7.0: Turn off auto update check timer.
if splupdate._SPLUpdateT is not None and
splupdate._SPLUpdateT.IsRunning(): splupdate._SPLUpdateT.Stop()
splupdate._SPLUpdateT = None
+ # Close profile triggers dictionary.
+ saveProfileTriggers()
# Save profile-specific settings to appropriate dictionary if this is
the case.
activeIndex = SPLConfig["ActiveIndex"]
del SPLConfig["ActiveIndex"]
@@ -488,6 +634,19 @@ def saveConfig():
SPLActiveProfile = None
SPLPrevProfile = None
SPLSwitchProfile = None
+SPLTriggerProfile = None
+
+# A general-purpose profile switcher.
+# Allows the add-on to switch between profiles as a result of manual
intervention or through profile trigger timer.
+# Instant profile switching is just a special case of this function.
+def switchProfile(activeProfile, newProfile):
+ global SPLConfig, SPLActiveProfile
+ mergeSections(newProfile)
+ SPLActiveProfile = SPLConfigPool[newProfile].name
+ SPLConfig["ActiveIndex"] = newProfile
+ # Use the focus.appModule's metadata reminder method if told to do so
now.
+ if SPLConfig["General"]["MetadataReminder"] in ("startup", "instant"):
+ api.getFocusObject().appModule._metadataAnnouncer(reminder=True)
# Called from within the app module.
def instantProfileSwitch():
@@ -509,30 +668,71 @@ def instantProfileSwitch():
switchProfileIndex =
getProfileIndexByName(SPLSwitchProfile)
# 6.1: Do to referencing nature of Python, use the
profile index function to locate the index for the soon to be deactivated
profile.
SPLPrevProfile = getProfileIndexByName(SPLActiveProfile)
- mergeSections(switchProfileIndex)
- SPLActiveProfile =
SPLConfigPool[switchProfileIndex].name
- SPLConfig["ActiveIndex"] = switchProfileIndex
+ # Pass in the prev profile, which will be None for
instant profile switch.
+ switchProfile(SPLPrevProfile, switchProfileIndex)
# Translators: Presented when switch to instant switch
profile was successful.
ui.message(_("Switching profiles"))
- # Use the focus.appModule's metadata reminder method if
told to do so now.
- if SPLConfig["General"]["MetadataReminder"] in
("startup", "instant"):
-
api.getFocusObject().appModule._metadataAnnouncer(reminder=True)
# Pause automatic update checking.
if SPLConfig["Update"]["AutoUpdateCheck"]:
if splupdate._SPLUpdateT is not None and
splupdate._SPLUpdateT.IsRunning: splupdate._SPLUpdateT.Stop()
else:
- mergeSections(SPLPrevProfile)
- SPLConfig["ActiveIndex"] = SPLPrevProfile
- SPLActiveProfile = SPLConfigPool[SPLPrevProfile].name
+ switchProfile(None, SPLPrevProfile)
SPLPrevProfile = None
# Translators: Presented when switching from instant
switch profile to a previous profile.
ui.message(_("Returning to previous profile"))
- # 6.1: Don't forget to switch streaming status around.
- if SPLConfig["General"]["MetadataReminder"] in
("startup", "instant"):
-
api.getFocusObject().appModule._metadataAnnouncer(reminder=True)
# Resume auto update checker if told to do so.
if SPLConfig["Update"]["AutoUpdateCheck"]: updateInit()
+# The triggers version of the above function.
+# 7.0: Try consolidating this into one or some more functions.
+_SPLTriggerEndTimer = None
+
+def triggerProfileSwitch():
+ global SPLPrevProfile, SPLConfig, SPLActiveProfile, triggerTimer,
_SPLTriggerEndTimer
+ if _configDialogOpened:
+ # Translators: Presented when trying to switch profiles when
add-on settings dialog is active.
+ ui.message(_("Add-on settings dialog is open, cannot switch
profiles"))
+ return
+ if SPLTriggerProfile is None:
+ # Technically a dead code, but for now...
+ # Translators: Presented when trying to switch to an instant
switch profile when the instant switch profile is not defined.
+ ui.message(_("No profile triggers defined"))
+ else:
+ if SPLPrevProfile is None:
+ if SPLActiveProfile == SPLTriggerProfile:
+ # Translators: Presented when trying to switch
to an instant switch profile when one is already using the instant switch
profile.
+ ui.message(_("A profile trigger is already
active"))
+ return
+ # Switch to the given profile.
+ triggerProfileIndex =
getProfileIndexByName(SPLTriggerProfile)
+ SPLPrevProfile = getProfileIndexByName(SPLActiveProfile)
+ # Pass in the prev profile, which will be None for
instant profile switch.
+ switchProfile(SPLPrevProfile, triggerProfileIndex)
+ # Translators: Presented when switch to instant switch
profile was successful.
+ ui.message(_("Switching profiles"))
+ # Pause automatic update checking.
+ if SPLConfig["Update"]["AutoUpdateCheck"]:
+ if splupdate._SPLUpdateT is not None and
splupdate._SPLUpdateT.IsRunning: splupdate._SPLUpdateT.Stop()
+ # Set the next trigger date and time.
+ triggerSettings = profileTriggers[SPLTriggerProfile]
+ # Set next trigger if no duration is specified.
+ if triggerSettings[6] == 0:
+ profileTriggers[SPLTriggerProfile] =
setNextTimedProfile(SPLTriggerProfile, triggerSettings[0],
datetime.time(triggerSettings[4], triggerSettings[5]))
+ else:
+ _SPLTriggerEndTimer =
wx.PyTimer(triggerProfileSwitch)
+ _SPLTriggerEndTimer.Start(triggerSettings[6] *
60 * 1000, True)
+ else:
+ switchProfile(None, SPLPrevProfile)
+ SPLPrevProfile = None
+ # Translators: Presented when switching from instant
switch profile to a previous profile.
+ ui.message(_("Returning to previous profile"))
+ # Resume auto update checker if told to do so.
+ if SPLConfig["Update"]["AutoUpdateCheck"]: updateInit()
+ # Stop the ending timer.
+ if _SPLTriggerEndTimer is not None and
_SPLTriggerEndTimer.IsRunning():
+ _SPLTriggerEndTimer.Stop()
+ _SPLTriggerEndTimer = None
+
# Automatic update checker.
@@ -608,6 +808,12 @@ class SPLConfigDialog(gui.SettingsDialog):
item = self.deleteButton = wx.Button(self, label=_("&Delete"))
item.Bind(wx.EVT_BUTTON, self.onDelete)
sizer.Add(item)
+ # Have a copy of the triggers dictionary.
+ self._profileTriggersConfig = dict(profileTriggers)
+ # Translators: The label of a button to manage show profile
triggers.
+ item = self.triggerButton = wx.Button(self,
label=_("&Triggers..."))
+ item.Bind(wx.EVT_BUTTON, self.onTriggers)
+ sizer.Add(item)
# Translators: The label of a button to toggle instant profile
switching on and off.
if SPLSwitchProfile is None: switchLabel = _("Enable instant
profile switching")
else:
@@ -624,6 +830,7 @@ class SPLConfigDialog(gui.SettingsDialog):
if SPLConfig["ActiveIndex"] == 0:
self.renameButton.Disable()
self.deleteButton.Disable()
+ self.triggerButton.Disable()
self.instantSwitchButton.Disable()
settingsSizer.Add(sizer)
@@ -879,7 +1086,7 @@ class SPLConfigDialog(gui.SettingsDialog):
self.profiles.SetFocus()
def onOk(self, evt):
- global SPLConfig, SPLActiveProfile, _configDialogOpened,
SPLSwitchProfile, SPLPrevProfile
+ global SPLConfig, SPLActiveProfile, _configDialogOpened,
SPLSwitchProfile, SPLPrevProfile, profileTriggers
selectedProfile = self.profiles.GetStringSelection().split("
<")[0]
profileIndex = getProfileIndexByName(selectedProfile)
SPLConfig["General"]["BeepAnnounce"] =
self.beepAnnounceCheckbox.Value
@@ -919,6 +1126,11 @@ class SPLConfigDialog(gui.SettingsDialog):
_configDialogOpened = False
# 7.0: Perform extra action such as restarting auto update
timer.
self.onCloseExtraAction()
+ # Apply changes to profile triggers.
+ profileTriggers = dict(self._profileTriggersConfig)
+ self._profileTriggersConfig.clear()
+ self._profileTriggersConfig = None
+ triggerStart(restart=True)
super(SPLConfigDialog, self).onOk(evt)
def onCancel(self, evt):
@@ -926,6 +1138,10 @@ class SPLConfigDialog(gui.SettingsDialog):
# 6.1: Discard changes to included columns set.
self.includedColumns.clear()
self.includedColumns = None
+ # Remove profile triggers as well.
+ self._profileTriggersConfig.clear()
+ self._profileTriggersConfig = None
+ triggerStart(restart=True)
SPLActiveProfile = self.activeProfile
if self.switchProfileRenamed or self.switchProfileDeleted:
SPLSwitchProfile = self.switchProfile
@@ -988,10 +1204,12 @@ class SPLConfigDialog(gui.SettingsDialog):
if selection == 0:
self.renameButton.Disable()
self.deleteButton.Disable()
+ self.triggerButton.Disable()
self.instantSwitchButton.Disable()
else:
self.renameButton.Enable()
self.deleteButton.Enable()
+ self.triggerButton.Enable()
if selectedProfile != self.switchProfile:
self.instantSwitchButton.Label = _("Enable
instant profile switching")
else:
@@ -1098,6 +1316,10 @@ class SPLConfigDialog(gui.SettingsDialog):
self.onProfileSelection(None)
self.profiles.SetFocus()
+ def onTriggers(self, evt):
+ self.Disable()
+ TriggersDialog(self, self.profiles.GetStringSelection()).Show()
+
def onInstantSwitch(self, evt):
selection = self.profiles.GetSelection()
selectedDisplayName = self.profiles.GetStringSelection()
@@ -1265,6 +1487,101 @@ class NewProfileDialog(wx.Dialog):
self.Parent.Enable()
self.Destroy()
+# Broadcast profile triggers dialog.
+# This dialog is similar to NVDA Core's profile triggers dialog and allows one
to configure when to trigger this profile.
+class TriggersDialog(wx.Dialog):
+
+ def __init__(self, parent, profile):
+ flagList = profile.split(" <")
+ self.profileFlags = set() if "<" not in profile else
set(flagList[1][:-1].split(", "))
+ profile = flagList[0]
+ # Translators: The title of the broadcast profile triggers
dialog.
+ super(TriggersDialog, self).__init__(parent, title=_("Profile
triggers for {profileName}").format(profileName = profile))
+ self.profile = profile
+ # When referencing profile triggers, use the dictionary stored
in the main add-on settings.
+ # This is needed in order to discard changes when cancel button
is clicked from the parent dialog.
+ if profile in self.Parent._profileTriggersConfig:
+ t = self.Parent._profileTriggersConfig[profile]
+ d = "-".join([str(t[1]), str(t[2]).zfill(2),
str(t[3]).zfill(2)])
+ t = ":".join([str(t[4]).zfill(2), str(t[5]).zfill(2)])
+ triggerText = "The next trigger is scheduled on {0} at
{1}.".format(d, t)
+ else:
+ triggerText = "No triggers defined."
+
+ mainSizer = wx.BoxSizer(wx.VERTICAL)
+ label = wx.StaticText(self, wx.ID_ANY, label=triggerText)
+ mainSizer.Add(label)
+
+ daysSizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY,
_("Day")), wx.HORIZONTAL)
+ self.triggerDays = []
+ for day in xrange(len(calendar.day_name)):
+ triggerDay=wx.CheckBox(self,
wx.NewId(),label=calendar.day_name[day])
+ value = (64 >> day &
self.Parent._profileTriggersConfig[profile][0]) if profile in
self.Parent._profileTriggersConfig else 0
+ triggerDay.SetValue(value)
+ self.triggerDays.append(triggerDay)
+ for day in self.triggerDays:
+ daysSizer.Add(day)
+ mainSizer.Add(daysSizer,border=20,flag=wx.LEFT|wx.RIGHT|wx.TOP)
+
+ timeSizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY,
_("Time")), wx.HORIZONTAL)
+ prompt = wx.StaticText(self, wx.ID_ANY, label="Hour")
+ timeSizer.Add(prompt)
+ self.hourEntry = wx.SpinCtrl(self, wx.ID_ANY, min=0, max=23)
+
self.hourEntry.SetValue(self.Parent._profileTriggersConfig[profile][4] if
profile in self.Parent._profileTriggersConfig else 0)
+ self.hourEntry.SetSelection(-1, -1)
+ timeSizer.Add(self.hourEntry)
+ prompt = wx.StaticText(self, wx.ID_ANY, label="Minute")
+ timeSizer.Add(prompt)
+ self.minEntry = wx.SpinCtrl(self, wx.ID_ANY, min=0, max=59)
+
self.minEntry.SetValue(self.Parent._profileTriggersConfig[profile][5] if
profile in self.Parent._profileTriggersConfig else 0)
+ self.minEntry.SetSelection(-1, -1)
+ timeSizer.Add(self.minEntry)
+ prompt = wx.StaticText(self, wx.ID_ANY, label="Duration in
minutes")
+ timeSizer.Add(prompt)
+ self.durationEntry = wx.SpinCtrl(self, wx.ID_ANY, min=0,
max=1440)
+
self.durationEntry.SetValue(self.Parent._profileTriggersConfig[profile][6] if
profile in self.Parent._profileTriggersConfig else 0)
+ self.durationEntry.SetSelection(-1, -1)
+ timeSizer.Add(self.durationEntry)
+
mainSizer.Add(timeSizer,border=20,flag=wx.LEFT|wx.RIGHT|wx.BOTTOM)
+
+ mainSizer.Add(self.CreateButtonSizer(wx.OK | wx.CANCEL))
+ self.Bind(wx.EVT_BUTTON, self.onOk, id=wx.ID_OK)
+ self.Bind(wx.EVT_BUTTON, self.onCancel, id=wx.ID_CANCEL)
+ mainSizer.Fit(self)
+ self.SetSizer(mainSizer)
+ self.Center(wx.BOTH | wx.CENTER_ON_SCREEN)
+ self.triggerDays[0].SetFocus()
+
+ def onOk(self, evt):
+ global SPLTriggerProfile, triggerTimer
+ bit = 0
+ for day in self.triggerDays:
+ if day.Value: bit+=64 >> self.triggerDays.index(day)
+ if bit:
+ hour, min = self.hourEntry.GetValue(),
self.minEntry.GetValue()
+ duration = self.durationEntry.GetValue()
+ if duplicateExists(self.Parent._profileTriggersConfig,
self.profile, bit, hour, min, duration):
+ gui.messageBox(_("A profile trigger already
exists for the entered time slot. Please choose a different date or time."),
+ _("Error"), wx.OK | wx.ICON_ERROR, self)
+ return
+ self.Parent._profileTriggersConfig[self.profile] =
setNextTimedProfile(self.profile, bit, datetime.time(hour, min))
+ self.Parent._profileTriggersConfig[self.profile][6] =
duration
+ elif bit == 0 and self.profile in
self.Parent._profileTriggersConfig:
+ del self.Parent._profileTriggersConfig[self.profile]
+ self.profileFlags.add("time-based") if self.profile in
self.Parent._profileTriggersConfig else self.profileFlags.discard("time-based")
+ if len(self.profileFlags):
+ flagText = "<{flags}>".format(flags = ",
".join(self.profileFlags))
+ self.profile = " ".join([self.profile, flagText])
+
self.Parent.profiles.SetString(self.Parent.profiles.GetSelection(),
self.profile)
+ self.Parent.profiles.SetFocus()
+ self.Parent.Enable()
+ self.Destroy()
+ return
+
+ def onCancel(self, evt):
+ self.Parent.Enable()
+ self.Destroy()
+
# Metadata reminder controller.
# Select notification/streaming URL's for metadata streaming.
_metadataDialogOpened = False
diff --git a/addon/appModules/splstudio/splmisc.py
b/addon/appModules/splstudio/splmisc.py
index cb8b928..8462ea7 100755
--- a/addon/appModules/splstudio/splmisc.py
+++ b/addon/appModules/splstudio/splmisc.py
@@ -12,6 +12,7 @@ import os
from csv import reader # For cart explorer.
import gui
import wx
+import ui
from NVDAObjects.IAccessible import sysListView32
from winUser import user32, sendMessage
import winKernel
@@ -301,3 +302,38 @@ def cartExplorerInit(StudioTitle, cartFiles=None):
carts["faultyCarts"] = faultyCarts
return carts
+
+# Countdown timer.
+# This is utilized by many services, chiefly profile triggers routine.
+
+class SPLCountdownTimer(object):
+
+ def __init__(self, duration, func, threshold):
+ # Threshold is used to instruct this timer when to start
countdown announcement.
+ self.duration = duration
+ self.total = duration
+ self.func = func
+ self.threshold = threshold
+
+ def Start(self):
+ self.timer = wx.PyTimer(self.countdown)
+ ui.message("Countdown started")
+ self.timer.Start(1000)
+
+ def Stop(self):
+ self.timer.Stop()
+
+ def IsRunning(self):
+ return self.timer.IsRunning()
+
+ def countdown(self):
+ self.duration -= 1
+ if self.duration == 0:
+ ui.message("Timer complete")
+ if self.func is not None:
+ self.func()
+ self.stop()
+ elif 0 < self.duration <= self.threshold:
+ ui.message(str(self.duration))
+
+
Repository URL: https://bitbucket.org/nvdaaddonteam/stationplaylist/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.