81 new commits in StationPlaylist:
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/f526698d1391/
Changeset: f526698d1391
Branch: None
User: josephsl
Date: 2016-12-15 19:06:10+00:00
Summary: Playlist snapshots (17.1-dev): Laying the foundation for Playlist
Snapshots, gathering statistics about a playlist.
Plalist snapshots is used to gather and present statistics about playlists.
This includes total duration of tracks, minimum and maximum and other goodies
(all configurable).
For now, total duration of remaining times in the playlist will be returned
using the new playlist snapshots function (destined for 17.1).
Affected #: 1 file
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 037b666..eaac413 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1362,22 +1362,24 @@ class AppModule(appModuleHandler.AppModule):
# Translators: Input help mode message for a command in Station
Playlist Studio.
script_manageMetadataStreams.__doc__=_("Opens a dialog to quickly
enable or disable metadata streaming.")
- # Track time analysis
+ # Track time analysis/Playlist snapshots
# Return total length of the selected tracks upon request.
# Analysis command (SPL Assistant) will be assignable.
+ # Also gather various data about the playlist.
_analysisMarker = None
- # Trakc time analysis requires main playlist viewer to be the
foreground window.
+ # Trakc time analysis and playlist snapshots require main playlist
viewer to be the foreground window.
def _trackAnalysisAllowed(self):
if api.getForegroundObject().windowClassName != "TStudioForm":
# Translators: Presented when track time anlaysis
cannot be performed because user is not focused on playlist viewer.
- ui.message(_("Not in playlist viewer, cannot perform
track time analysis"))
+ ui.message(_("Not in playlist viewer, cannot perform
track time analysis or gather playlist snapshot statistics"))
return False
return True
# Return total duration of a range of tracks.
# This is used in track time analysis when multiple tracks are selected.
# This is also called from playlist duration scripts.
+ # To be replaced by general track duration script, with the difference
being start and end location.
def totalTime(self, start, end):
# Take care of errors such as the following.
if start < 0 or end > statusAPI(0, 124, ret=True)-1:
@@ -1393,6 +1395,28 @@ class AppModule(appModuleHandler.AppModule):
totalLength+=statusAPI(filename, 30, ret=True)
return totalLength
+ # Playlist snapshots
+ # Data to be gathered comes from a set of flags.
+ def playlistSnapshots(self, obj, end, snapshotFlags=None):
+ # Track count and total duration are always included.
+ snapshot = {"TrackCount":statusAPI(0, 124, ret=True)}
+ duration = obj.indexOf("Duration")
+ title = obj.indexOf("Title")
+ totalDuration = 0
+ titleDuration = []
+ while obj not in (None, end):
+ # Technically segue.
+ segue = obj._getColumnContent(duration)
+ trackTitle = obj._getColumnContent(title)
+ titleDuration.append((trackTitle, segue))
+ if segue not in (None, "00:00"):
+ hms = segue.split(":")
+ totalDuration += (int(hms[-2])*60) +
int(hms[-1])
+ if len(hms) == 3: totalDuration +=
int(hms[0])*3600
+ obj = obj.next
+ snapshot["DurationTotal"] = totalDuration
+ return snapshot["DurationTotal"]
+
# Some handlers for native commands.
# In Studio 5.0x, when deleting a track, NVDA announces wrong track
item due to focus bouncing (not the case in 5.10 and later).
@@ -1586,16 +1610,7 @@ class AppModule(appModuleHandler.AppModule):
if obj.role == controlTypes.ROLE_LIST:
ui.message("00:00")
return
- col = obj.indexOf("Duration")
- totalDuration = 0
- while obj is not None:
- segue = obj._getColumnContent(col)
- if segue not in (None, "00:00"):
- hms = segue.split(":")
- totalDuration += (int(hms[-2])*60) +
int(hms[-1])
- if len(hms) == 3: totalDuration +=
int(hms[0])*3600
- obj = obj.next
- self.announceTime(totalDuration, ms=False)
+ self.announceTime(self.playlistSnapshots(obj, None), ms=False)
def script_sayPlaylistModified(self, gesture):
try:
@@ -1745,6 +1760,16 @@ class AppModule(appModuleHandler.AppModule):
# Translators: Input help mode message for a command in Station
Playlist Studio.
script_trackTimeAnalysis.__doc__=_("Announces total length of tracks
between analysis start marker and the current track")
+ def script_takePlaylistSnapshots(self, gesture):
+ obj = api.getFocusObject() if
api.getForegroundObject().windowClassName == "TStudioForm" else
self._focusedTrack
+ if obj is None:
+ ui.message("Please return to playlist viewer before
invoking this command.")
+ return
+ if obj.role == controlTypes.ROLE_LIST:
+ ui.message("00:00")
+ return
+ self.announceTime(self.playlistSnapshots(obj, None), ms=False)
+
def script_switchProfiles(self, gesture):
splconfig.triggerProfileSwitch() if
splconfig._triggerProfileActive else splconfig.instantProfileSwitch()
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/9bf532c1cfe5/
Changeset: 9bf532c1cfe5
Branch: None
User: josephsl
Date: 2016-12-17 06:02:12+00:00
Summary: Merge branch 'master' into plSnaps
Affected #: 5 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index eaac413..5d7ceca 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -962,12 +962,12 @@ 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:
- d = splconfig.SPLAlarmDialog(gui.mainFrame, level)
+ d = splconfui.AlarmsCenter(gui.mainFrame, level)
gui.mainFrame.prePopup()
d.Raise()
d.Show()
gui.mainFrame.postPopup()
- splconfig._alarmDialogOpened = True
+ splconfui._alarmDialogOpened = True
except RuntimeError:
wx.CallAfter(splconfig._alarmError)
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index b83144e..3351125 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -851,117 +851,9 @@ def _shouldBuildDescriptionPieces():
or len(SPLConfig["ColumnAnnouncement"]["IncludedColumns"]) != 17))
-# Additional configuration dialogs
+# Additional configuration and miscellaneous dialogs
# See splconfui module for basic configuration dialogs.
-# A common alarm dialog
-# Based on NVDA core's find dialog code (implemented by the author of this
add-on).
-# Extended in 2016 to handle microphone alarms.
-# Only one instance can be active at a given moment (code borrowed from GUI's
exit dialog routine).
-_alarmDialogOpened = False
-
-# A common alarm error dialog.
-def _alarmError():
- # Translators: Text of the dialog when another alarm dialog is open.
- gui.messageBox(_("Another alarm dialog is
open."),_("Error"),style=wx.OK | wx.ICON_ERROR)
-
-class SPLAlarmDialog(wx.Dialog):
- """A dialog providing common alarm settings.
- This dialog contains a number entry field for alarm duration and a
check box to enable or disable the alarm.
- For one particular case, it consists of two number entry fields.
- """
-
- # The following comes from exit dialog class from GUI package (credit:
NV Access and Zahari from Bulgaria).
- _instance = None
-
- def __new__(cls, parent, *args, **kwargs):
- # Make this a singleton and prompt an error dialog if it isn't.
- if _alarmDialogOpened:
- raise RuntimeError("An instance of alarm dialog is
opened")
- inst = cls._instance() if cls._instance else None
- if not inst:
- return super(cls, cls).__new__(cls, parent, *args,
**kwargs)
- return inst
-
- def __init__(self, parent, level=0):
- inst = SPLAlarmDialog._instance() if SPLAlarmDialog._instance
else None
- if inst:
- return
- # Use a weakref so the instance can die.
- import weakref
- SPLAlarmDialog._instance = weakref.ref(self)
-
- # Now the actual alarm dialog code.
- # 8.0: Apart from level 0 (all settings shown), levels change
title.
- titles = (_("Alarms Center"), _("End of track alarm"), _("Song
intro alarm"), _("Microphone alarm"))
- super(SPLAlarmDialog, self).__init__(parent, wx.ID_ANY,
titles[level])
- self.level = level
- mainSizer = wx.BoxSizer(wx.VERTICAL)
- # 17.1: Utilize various enhancements from GUI helper (added in
NVDA 2016.4).
- contentSizerHelper = gui.guiHelper.BoxSizerHelper(self,
orientation=wx.VERTICAL)
-
- if level in (0, 1):
- timeVal =
SPLConfig["IntroOutroAlarms"]["EndOfTrackTime"]
- alarmLabel = _("Enter &end of track alarm time in
seconds (currently {curAlarmSec})").format(curAlarmSec = timeVal)
- self.outroAlarmEntry =
contentSizerHelper.addLabeledControl(alarmLabel,
gui.nvdaControls.SelectOnFocusSpinCtrl, min=1, max=59, initial=timeVal)
-
self.outroToggleCheckBox=contentSizerHelper.addItem(wx.CheckBox(self,
label=_("&Notify when end of track is approaching")))
-
self.outroToggleCheckBox.SetValue(SPLConfig["IntroOutroAlarms"]["SayEndOfTrack"])
-
- if level in (0, 2):
- rampVal = SPLConfig["IntroOutroAlarms"]["SongRampTime"]
- alarmLabel = _("Enter song &intro alarm time in seconds
(currently {curRampSec})").format(curRampSec = rampVal)
- self.introAlarmEntry =
contentSizerHelper.addLabeledControl(alarmLabel,
gui.nvdaControls.SelectOnFocusSpinCtrl, min=1, max=9, initial=rampVal)
-
self.introToggleCheckBox=contentSizerHelper.addItem(wx.CheckBox(self,
label=_("&Notify when end of introduction is approaching")))
-
self.introToggleCheckBox.SetValue(SPLConfig["IntroOutroAlarms"]["SaySongRamp"])
-
- if level in (0, 3):
- micAlarm = SPLConfig["MicrophoneAlarm"]["MicAlarm"]
- micAlarmInterval =
SPLConfig["MicrophoneAlarm"]["MicAlarmInterval"]
- if micAlarm:
- # Translators: A dialog message to set
microphone active alarm (curAlarmSec is the current mic monitoring alarm in
seconds).
- timeMSG = _("Enter microphone alarm time in
seconds (currently {curAlarmSec}, 0 disables the alarm)").format(curAlarmSec =
micAlarm)
- else:
- # Translators: A dialog message when microphone
alarm is disabled (set to 0).
- timeMSG = _("Enter microphone alarm time in
seconds (currently disabled, 0 disables the alarm)")
- micIntervalMSG = _("Microphone alarm interval")
- self.micAlarmEntry =
contentSizerHelper.addLabeledControl(timeMSG,
gui.nvdaControls.SelectOnFocusSpinCtrl, min=0, max=7200, initial=micAlarm)
- self.micIntervalEntry =
contentSizerHelper.addLabeledControl(micIntervalMSG,
gui.nvdaControls.SelectOnFocusSpinCtrl, min=0, max=60, initial=micAlarmInterval)
-
-
contentSizerHelper.addDialogDismissButtons(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.Add(contentSizerHelper.sizer,
border=gui.guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL)
- mainSizer.Fit(self)
- self.SetSizer(mainSizer)
- self.Center(wx.BOTH | wx.CENTER_ON_SCREEN)
- if level in (0, 1): self.outroAlarmEntry.SetFocus()
- elif level == 2: self.introAlarmEntry.SetFocus()
- elif level == 3: self.micAlarmEntry.SetFocus()
-
- def onOk(self, evt):
- global SPLConfig, _alarmDialogOpened
- # Optimization: don't bother if Studio is dead and if the same
value has been entered.
- import winUser
- if winUser.user32.FindWindowA("SPLStudio", None):
- # Gather settings to be applied in section/key format.
- if self.level in (0, 1):
- SPLConfig["IntroOutroAlarms"]["EndOfTrackTime"]
= self.outroAlarmEntry.GetValue()
- SPLConfig["IntroOutroAlarms"]["SayEndOfTrack"]
= self.outroToggleCheckBox.GetValue()
- elif self.level in (0, 2):
- SPLConfig["IntroOutroAlarms"]["SongRampTime"] =
self.introAlarmEntry.GetValue()
- SPLConfig["IntroOutroAlarms"]["SaySongRamp"] =
self.introToggleCheckBox.GetValue()
- elif self.level in (0, 3):
- SPLConfig["MicrophoneAlarm"]["MicAlarm"] =
self.micAlarmEntry.GetValue()
-
SPLConfig["MicrophoneAlarm"]["MicAlarmInterval"] =
self.micIntervalEntry.GetValue()
- self.Destroy()
- _alarmDialogOpened = False
-
- def onCancel(self, evt):
- self.Destroy()
- global _alarmDialogOpened
- _alarmDialogOpened = False
-
-
# Startup dialogs.
# Audio ducking reminder (NVDA 2016.1 and later).
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index 32d41bb..5d6d0df 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -92,40 +92,15 @@ class SPLConfigDialog(gui.SettingsDialog):
except:
pass
- self.outroSizer = wx.BoxSizer(wx.HORIZONTAL)
- # Check box hiding method comes from Alberto Buffolino's
Columns Review add-on.
- # Translators: Label for a check box in SPL add-on settings to
notify when end of track (outro) is approaching.
- self.outroCheckBox=wx.CheckBox(self,wx.NewId(),label=_("&Notify
when end of track is approaching"))
-
self.outroCheckBox.SetValue(splconfig.SPLConfig["IntroOutroAlarms"]["SayEndOfTrack"])
- self.outroCheckBox.Bind(wx.EVT_CHECKBOX, self.onOutroCheck)
- self.outroSizer.Add(self.outroCheckBox,
border=10,flag=wx.BOTTOM)
-
- # Translators: The label for a setting in SPL Add-on settings
to specify end of track (outro) alarm.
- self.outroAlarmLabel = wx.StaticText(self, wx.ID_ANY,
label=_("&End of track alarm in seconds"))
- self.outroSizer.Add(self.outroAlarmLabel)
- self.endOfTrackAlarm = wx.SpinCtrl(self, wx.ID_ANY, min=1,
max=59)
-
self.endOfTrackAlarm.SetValue(long(splconfig.SPLConfig["IntroOutroAlarms"]["EndOfTrackTime"]))
- self.endOfTrackAlarm.SetSelection(-1, -1)
- self.outroSizer.Add(self.endOfTrackAlarm)
- self.onOutroCheck(None)
- SPLConfigHelper.addItem(self.outroSizer)
-
- self.introSizer = wx.BoxSizer(wx.HORIZONTAL)
- # Translators: Label for a check box in SPL add-on settings to
notify when end of intro is approaching.
- self.introCheckBox=wx.CheckBox(self,wx.NewId(),label=_("&Notify
when end of introduction is approaching"))
-
self.introCheckBox.SetValue(splconfig.SPLConfig["IntroOutroAlarms"]["SaySongRamp"])
- self.introCheckBox.Bind(wx.EVT_CHECKBOX, self.onIntroCheck)
- self.introSizer.Add(self.introCheckBox,
border=10,flag=wx.BOTTOM)
-
- # Translators: The label for a setting in SPL Add-on settings
to specify track intro alarm.
- self.introAlarmLabel = wx.StaticText(self, wx.ID_ANY,
label=_("&Track intro alarm in seconds"))
- self.introSizer.Add(self.introAlarmLabel)
- self.songRampAlarm = wx.SpinCtrl(self, wx.ID_ANY, min=1, max=9)
-
self.songRampAlarm.SetValue(long(splconfig.SPLConfig["IntroOutroAlarms"]["SongRampTime"]))
- self.songRampAlarm.SetSelection(-1, -1)
- self.introSizer.Add(self.songRampAlarm)
- self.onIntroCheck(None)
- SPLConfigHelper.addItem(self.introSizer)
+ self.endOfTrackTime =
splconfig.SPLConfig["IntroOutroAlarms"]["EndOfTrackTime"]
+ self.sayEndOfTrack =
splconfig.SPLConfig["IntroOutroAlarms"]["SayEndOfTrack"]
+ self.songRampTime =
splconfig.SPLConfig["IntroOutroAlarms"]["SongRampTime"]
+ self.saySongRamp =
splconfig.SPLConfig["IntroOutroAlarms"]["SaySongRamp"]
+ self.micAlarm =
splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarm"]
+ self.micAlarmInterval =
splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarmInterval"]
+ # Translators: The label of a button to open a dialog to
configure various alarms.
+ alarmsCenterButton = SPLConfigHelper.addItem(wx.Button(self,
label=_("&Alarms Center...")))
+ alarmsCenterButton.Bind(wx.EVT_BUTTON, self.onAlarmsCenter)
self.brailleTimerValues=[("off",_("Off")),
# Translators: One of the braille timer settings.
@@ -143,13 +118,6 @@ class SPLConfigDialog(gui.SettingsDialog):
except:
pass
- sizer = gui.guiHelper.BoxSizerHelper(self,
orientation=wx.HORIZONTAL)
- # Translators: The label for a setting in SPL Add-on settings
to change microphone alarm setting.
- self.micAlarm = sizer.addLabeledControl(_("&Microphone alarm in
seconds"), gui.nvdaControls.SelectOnFocusSpinCtrl, min=0, max=7200,
initial=splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarm"])
- # Translators: The label for a setting in SPL Add-on settings
to specify mic alarm interval.
- self.micAlarmInterval = sizer.addLabeledControl(_("Microphone
alarm &interval in seconds"), gui.nvdaControls.SelectOnFocusSpinCtrl, min=0,
max=60, initial=splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarmInterval"])
- SPLConfigHelper.addItem(sizer)
-
# Translators: One of the alarm notification options.
self.alarmAnnounceValues=[("beep",_("beep")),
# Translators: One of the alarm notification options.
@@ -299,13 +267,13 @@ class SPLConfigDialog(gui.SettingsDialog):
splconfig.SPLConfig.swapProfiles(splconfig.SPLConfig.activeProfile,
selectedProfile)
splconfig.SPLConfig["General"]["BeepAnnounce"] =
self.beepAnnounceCheckbox.Value
splconfig.SPLConfig["General"]["MessageVerbosity"] =
self.verbosityLevels[self.verbosityList.GetSelection()][0]
- splconfig.SPLConfig["IntroOutroAlarms"]["SayEndOfTrack"] =
self.outroCheckBox.Value
- splconfig.SPLConfig["IntroOutroAlarms"]["EndOfTrackTime"] =
self.endOfTrackAlarm.Value
- splconfig.SPLConfig["IntroOutroAlarms"]["SaySongRamp"] =
self.introCheckBox.Value
- splconfig.SPLConfig["IntroOutroAlarms"]["SongRampTime"] =
self.songRampAlarm.Value
+ splconfig.SPLConfig["IntroOutroAlarms"]["EndOfTrackTime"] =
self.endOfTrackTime
+ splconfig.SPLConfig["IntroOutroAlarms"]["SayEndOfTrack"] =
self.sayEndOfTrack
+ splconfig.SPLConfig["IntroOutroAlarms"]["SongRampTime"] =
self.songRampTime
+ splconfig.SPLConfig["IntroOutroAlarms"]["SaySongRamp"] =
self.saySongRamp
+ splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarm"] =
self.micAlarm
+ splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarmInterval"] =
self.micAlarmInterval
splconfig.SPLConfig["General"]["BrailleTimer"] =
self.brailleTimerValues[self.brailleTimerList.GetSelection()][0]
- splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarm"] =
self.micAlarm.Value
- splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarmInterval"] =
self.micAlarmInterval.Value
splconfig.SPLConfig["General"]["AlarmAnnounce"] =
self.alarmAnnounceValues[self.alarmAnnounceList.GetSelection()][0]
splconfig.SPLConfig["General"]["LibraryScanAnnounce"] =
self.libScanValues[self.libScanList.GetSelection()][0]
splconfig.SPLConfig["General"]["TimeHourAnnounce"] =
self.hourAnnounceCheckbox.Value
@@ -391,25 +359,6 @@ class SPLConfigDialog(gui.SettingsDialog):
else:
if splupdate._SPLUpdateT is None: splconfig.updateInit()
- # Check events for outro and intro alarms, respectively.
- def onOutroCheck(self, evt):
- if not self.outroCheckBox.IsChecked():
- self.outroSizer.Hide(self.outroAlarmLabel)
- self.outroSizer.Hide(self.endOfTrackAlarm)
- else:
- self.outroSizer.Show(self.outroAlarmLabel)
- self.outroSizer.Show(self.endOfTrackAlarm)
- self.Fit()
-
- def onIntroCheck(self, evt):
- if not self.introCheckBox.IsChecked():
- self.introSizer.Hide(self.introAlarmLabel)
- self.introSizer.Hide(self.songRampAlarm)
- else:
- self.introSizer.Show(self.introAlarmLabel)
- self.introSizer.Show(self.songRampAlarm)
- self.Fit()
-
# Include profile flags such as instant profile string for display
purposes.
def displayProfiles(self, profiles):
for index in xrange(len(profiles)):
@@ -435,14 +384,12 @@ class SPLConfigDialog(gui.SettingsDialog):
self.deleteButton.Enable()
self.triggerButton.Enable()
curProfile = splconfig.getProfileByName(selectedProfile)
-
self.outroCheckBox.SetValue(curProfile["IntroOutroAlarms"]["SayEndOfTrack"])
-
self.endOfTrackAlarm.SetValue(long(curProfile["IntroOutroAlarms"]["EndOfTrackTime"]))
- self.onOutroCheck(None)
-
self.introCheckBox.SetValue(curProfile["IntroOutroAlarms"]["SaySongRamp"])
-
self.songRampAlarm.SetValue(long(curProfile["IntroOutroAlarms"]["SongRampTime"]))
- self.onIntroCheck(None)
-
self.micAlarm.SetValue(long(curProfile["MicrophoneAlarm"]["MicAlarm"]))
-
self.micAlarmInterval.SetValue(long(curProfile["MicrophoneAlarm"]["MicAlarmInterval"]))
+ self.endOfTrackTime =
curProfile["IntroOutroAlarms"]["EndOfTrackTime"]
+ self.sayEndOfTrack =
curProfile["IntroOutroAlarms"]["SayEndOfTrack"]
+ self.songRampTime =
curProfile["IntroOutroAlarms"]["SongRampTime"]
+ self.saySongRamp = curProfile["IntroOutroAlarms"]["SaySongRamp"]
+ self.micAlarm = curProfile["MicrophoneAlarm"]["MicAlarm"]
+ self.micAlarmInterval =
curProfile["MicrophoneAlarm"]["MicAlarmInterval"]
# 6.1: Take care of profile-specific column and metadata
settings.
self.metadataStreams =
curProfile["MetadataStreaming"]["MetadataEnabled"]
self.columnOrderCheckbox.SetValue(curProfile["ColumnAnnouncement"]["UseScreenColumnOrder"])
@@ -561,7 +508,12 @@ class SPLConfigDialog(gui.SettingsDialog):
action(flag)
self.profiles.SetString(index, profile if not len(flags) else
"{0} <{1}>".format(profile, ", ".join(flags)))
- # Manage metadata streaming.
+ # Alarms Center.
+ def onAlarmsCenter(self, evt):
+ self.Disable()
+ AlarmsCenter(self).Show()
+
+ # Manage metadata streaming.
def onManageMetadata(self, evt):
self.Disable()
MetadataStreamingDialog(self).Show()
@@ -602,7 +554,7 @@ class SPLConfigDialog(gui.SettingsDialog):
# Open the above dialog upon request.
def onConfigDialog(evt):
# 5.2: Guard against alarm dialogs.
- if splconfig._alarmDialogOpened or _metadataDialogOpened:
+ if _alarmDialogOpened or _metadataDialogOpened:
# Translators: Presented when an alarm dialog is opened.
wx.CallAfter(gui.messageBox, _("Another add-on settings dialog
is open. Please close the previously opened dialog first."), _("Error"),
wx.OK|wx.ICON_ERROR)
else: gui.mainFrame._popupSettingsDialog(SPLConfigDialog)
@@ -815,6 +767,115 @@ class TriggersDialog(wx.Dialog):
prompt.Enable() if self.timeSwitchCheckbox.IsChecked()
else prompt.Disable()
self.Fit()
+# A common alarm dialog (Alarms Center)
+# Based on NVDA core's find dialog code (implemented by the author of this
add-on).
+# Extended in 2016 to handle microphone alarms.
+# Only one instance can be active at a given moment (code borrowed from GUI's
exit dialog routine).
+_alarmDialogOpened = False
+
+# A common alarm error dialog.
+def _alarmError():
+ # Translators: Text of the dialog when another alarm dialog is open.
+ gui.messageBox(_("Another alarm dialog is
open."),_("Error"),style=wx.OK | wx.ICON_ERROR)
+
+class AlarmsCenter(wx.Dialog):
+ """A dialog providing common alarm settings.
+ This dialog contains a number entry field for alarm duration and a
check box to enable or disable the alarm.
+ For one particular case, it consists of two number entry fields.
+ """
+
+ # The following comes from exit dialog class from GUI package (credit:
NV Access and Zahari from Bulgaria).
+ _instance = None
+
+ def __new__(cls, parent, *args, **kwargs):
+ # Make this a singleton and prompt an error dialog if it isn't.
+ if _alarmDialogOpened:
+ raise RuntimeError("An instance of alarm dialog is
opened")
+ inst = cls._instance() if cls._instance else None
+ if not inst:
+ return super(cls, cls).__new__(cls, parent, *args,
**kwargs)
+ return inst
+
+ def __init__(self, parent, level=0):
+ inst = AlarmsCenter._instance() if AlarmsCenter._instance else
None
+ if inst:
+ return
+ # Use a weakref so the instance can die.
+ import weakref
+ AlarmsCenter._instance = weakref.ref(self)
+
+ # Now the actual alarm dialog code.
+ # 8.0: Apart from level 0 (all settings shown), levels change
title.
+ titles = (_("Alarms Center"), _("End of track alarm"), _("Song
intro alarm"), _("Microphone alarm"))
+ super(AlarmsCenter, self).__init__(parent, wx.ID_ANY,
titles[level])
+ self.level = level
+ mainSizer = wx.BoxSizer(wx.VERTICAL)
+ alarmsCenterHelper = gui.guiHelper.BoxSizerHelper(self,
orientation=wx.VERTICAL)
+
+ if level in (0, 1):
+ timeVal = parent.endOfTrackTime if level == 0 else
splconfig.SPLConfig["IntroOutroAlarms"]["EndOfTrackTime"]
+ self.outroAlarmEntry =
alarmsCenterHelper.addLabeledControl(_("&End of track alarm in seconds"),
gui.nvdaControls.SelectOnFocusSpinCtrl, min=1, max=59, initial=timeVal)
+
self.outroToggleCheckBox=alarmsCenterHelper.addItem(wx.CheckBox(self,
label=_("&Notify when end of track is approaching")))
+ self.outroToggleCheckBox.SetValue(parent.sayEndOfTrack
if level == 0 else splconfig.SPLConfig["IntroOutroAlarms"]["SayEndOfTrack"])
+
+ if level in (0, 2):
+ rampVal = parent.songRampTime if level == 0 else
splconfig.SPLConfig["IntroOutroAlarms"]["SongRampTime"]
+ self.introAlarmEntry =
alarmsCenterHelper.addLabeledControl(_("&Track intro alarm in seconds"),
gui.nvdaControls.SelectOnFocusSpinCtrl, min=1, max=9, initial=rampVal)
+
self.introToggleCheckBox=alarmsCenterHelper.addItem(wx.CheckBox(self,
label=_("&Notify when end of introduction is approaching")))
+ self.introToggleCheckBox.SetValue(parent.saySongRamp if
level == 0 else splconfig.SPLConfig["IntroOutroAlarms"]["SaySongRamp"])
+
+ if level in (0, 3):
+ micAlarm =
splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarm"] if level == 3 else
parent.micAlarm
+ micAlarmInterval =
splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarmInterval"] if level == 3 else
parent.micAlarmInterval
+ # Translators: A dialog message to set microphone
active alarm.
+ self.micAlarmEntry =
alarmsCenterHelper.addLabeledControl(_("&Microphone alarm in seconds (0
disables the alarm)"), gui.nvdaControls.SelectOnFocusSpinCtrl, min=0, max=7200,
initial=micAlarm)
+ self.micIntervalEntry =
alarmsCenterHelper.addLabeledControl(_("Microphone alarm &interval in
seconds"), gui.nvdaControls.SelectOnFocusSpinCtrl, min=0, max=60,
initial=micAlarmInterval)
+
+
alarmsCenterHelper.addDialogDismissButtons(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.Add(alarmsCenterHelper.sizer,
border=gui.guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL)
+ mainSizer.Fit(self)
+ self.SetSizer(mainSizer)
+ self.Center(wx.BOTH | wx.CENTER_ON_SCREEN)
+ if level in (0, 1): self.outroAlarmEntry.SetFocus()
+ elif level == 2: self.introAlarmEntry.SetFocus()
+ elif level == 3: self.micAlarmEntry.SetFocus()
+
+ def onOk(self, evt):
+ global _alarmDialogOpened
+ # Optimization: don't bother if Studio is dead and if the same
value has been entered (only when standalone versions are opened).
+ if self.level > 0 and user32.FindWindowA("SPLStudio", None):
+ # Gather settings to be applied in section/key format.
+ if self.level == 1:
+ SPLConfig["IntroOutroAlarms"]["EndOfTrackTime"]
= self.outroAlarmEntry.GetValue()
+ SPLConfig["IntroOutroAlarms"]["SayEndOfTrack"]
= self.outroToggleCheckBox.GetValue()
+ elif self.level == 2:
+ SPLConfig["IntroOutroAlarms"]["SongRampTime"] =
self.introAlarmEntry.GetValue()
+ SPLConfig["IntroOutroAlarms"]["SaySongRamp"] =
self.introToggleCheckBox.GetValue()
+ elif self.level == 3:
+ SPLConfig["MicrophoneAlarm"]["MicAlarm"] =
self.micAlarmEntry.GetValue()
+
SPLConfig["MicrophoneAlarm"]["MicAlarmInterval"] =
self.micIntervalEntry.GetValue()
+ elif self.level == 0:
+ parent = self.Parent
+ parent.endOfTrackTime = self.outroAlarmEntry.GetValue()
+ parent.sayEndOfTrack =
self.outroToggleCheckBox.GetValue()
+ parent.songRampTime = self.introAlarmEntry.GetValue()
+ parent.saySongRamp = self.introToggleCheckBox.GetValue()
+ parent.micAlarm = self.micAlarmEntry.GetValue()
+ parent.micAlarmInterval =
self.micIntervalEntry.GetValue()
+ self.Parent.profiles.SetFocus()
+ self.Parent.Enable()
+ self.Destroy()
+ _alarmDialogOpened = False
+
+ def onCancel(self, evt):
+ if self.level == 0:
+ self.Parent.Enable()
+ self.Destroy()
+ global _alarmDialogOpened
+ _alarmDialogOpened = False
+
# Metadata reminder controller.
# Select notification/streaming URL's for metadata streaming.
_metadataDialogOpened = False
diff --git a/addon/globalPlugins/SPLStudioUtils/__init__.py
b/addon/globalPlugins/SPLStudioUtils/__init__.py
index faa4f39..1ac7977 100755
--- a/addon/globalPlugins/SPLStudioUtils/__init__.py
+++ b/addon/globalPlugins/SPLStudioUtils/__init__.py
@@ -40,6 +40,7 @@ SPLWin = 0 # A handle to studio window.
SPLMSG = winUser.WM_USER
# Various SPL IPC tags.
+SPLVersion = 2
SPLPlay = 12
SPLStop = 13
SPLPause = 15
@@ -48,6 +49,7 @@ SPLMic = 17
SPLLineIn = 18
SPLLibraryScanCount = 32
SPLListenerCount = 35
+SPLStatusInfo = 39 #Studio 5.20 and later.
SPL_TrackPlaybackStatus = 104
SPLCurTrackPlaybackTime = 105
@@ -73,6 +75,7 @@ S: Stop with fade.
T: Instant stop.
E: Announce if any encoders are being monitored.
I: Announce listener count.
+Q: Announce Studio status information.
R: Remaining time for the playing track.
Shift+R: Library scan progress.""")
@@ -264,6 +267,31 @@ class GlobalPlugin(globalPluginHandler.GlobalPlugin):
encoders.announceNumMonitoringEncoders()
self.finish()
+ def script_statusInfo(self, gesture):
+ # For consistency reasons (because of the Studio status bar),
messages in this method will remain in English.
+ statusInfo = []
+ # 17.1: For Studio 5.10 and up, announce playback and
automation status.
+ playingNow = winUser.sendMessage(SPLWin, SPLMSG, 0,
SPL_TrackPlaybackStatus)
+ statusInfo.append("Play status: playing" if playingNow else
"Play status: stopped")
+ # For automation, Studio 5.11 and earlier does not have an easy
way to detect this flag, thus resort to using playback status.
+ if winUser.sendMessage(SPLWin, SPLMSG, 0, SPLVersion) < 520:
+ statusInfo.append("Automation on" if playingNow == 2
else "Automation off")
+ else:
+ statusInfo.append("Automation on" if
winUser.sendMessage(SPLWin, SPLMSG, 1, SPLStatusInfo) else "Automation off")
+ # 5.20 and later.
+ statusInfo.append("Microphone on" if
winUser.sendMessage(SPLWin, SPLMSG, 2, SPLStatusInfo) else "Microphone off")
+ statusInfo.append("Line-inon" if
winUser.sendMessage(SPLWin, SPLMSG, 3, SPLStatusInfo) else "Line-in off")
+ statusInfo.append("Record to file on" if
winUser.sendMessage(SPLWin, SPLMSG, 4, SPLStatusInfo) else "Record to file off")
+ cartEdit = winUser.sendMessage(SPLWin, SPLMSG, 5,
SPLStatusInfo)
+ cartInsert = winUser.sendMessage(SPLWin, SPLMSG, 6,
SPLStatusInfo)
+ if not cartEdit and not cartInsert:
statusInfo.append("Cart edit off")
+ elif cartEdit and not cartInsert:
statusInfo.append("Cart edit on")
+ elif not cartEdit and cartInsert:
statusInfo.append("Cart insert on")
+ ui.message("; ".join(statusInfo))
+ self.finish()
+ # Translators: Input help message for a SPL Controller command.
+ script_statusInfo.__doc__ = _("Announces Studio status such as track
playback status from other programs")
+
def script_conHelp(self, gesture):
# Translators: The title for SPL Controller help dialog.
wx.CallAfter(gui.messageBox, SPLConHelp, _("SPL Controller
help"))
@@ -286,6 +314,7 @@ class GlobalPlugin(globalPluginHandler.GlobalPlugin):
"kb:r":"remainingTime",
"kb:e":"announceNumMonitoringEncoders",
"kb:i":"listenerCount",
+ "kb:q":"statusInfo",
"kb:f1":"conHelp"
}
diff --git a/readme.md b/readme.md
index d9285a0..6038fd4 100755
--- a/readme.md
+++ b/readme.md
@@ -9,7 +9,7 @@ This add-on package provides improved usage of StationPlaylist
Studio, as well a
For more information about the add-on, read the [add-on guide][4]. For
developers seeking to know how to build the add-on, see buildInstructions.txt
located at the root of the add-on source code repository.
-IMPORTANT: This add-on requires NVDA 2016.4 or later and StationPlaylist
Studio 5.10 or later. If you have installed NVDA 2016.1 or later on Windows 8
and later, disable audio ducking mode. Also, add-on 8.0/16.10 requires Studio
5.10 and later, and for broadcasters using Studio 5.0x, a long-term support
version (15.x) is available.
+IMPORTANT: This add-on requires NVDA 2016.4 or later and StationPlaylist
Studio 5.10 or later. If using Windows 8 or later, for best experience, disable
audio ducking mode. Also, add-on 8.0/16.10 requires Studio 5.10 and later, and
for broadcasters using Studio 5.0x, a long-term support version (15.x) is
available.
## Shortcut keys
@@ -36,6 +36,7 @@ The following commands are not assigned by default; if you
wish to assign them,
* Switching to SPL Studio window from any program.
* SPL Controller layer.
+* Announcing Studio status such as track playback from other programs.
* SPL Assistant layer from SPL Studio.
* Announce time including seconds from SPL Studio.
* Announcing temperature.
@@ -127,6 +128,7 @@ The available SPL Controller commands are:
* Press Shift+R to get a report on library scan progress.
* Press E to get count and labels for encoders being monitored.
* Press I to obtain listener count.
+* Press Q to obtain various status information about Studio including whether
a track is playing, microphone is on and others.
* Press F1 to show a help dialog which lists available commands.
## Track alarms
@@ -170,7 +172,10 @@ If you are using Studio on a touchscreen computer running
Windows 8 or later and
* Improvements to presentation of various add-on dialogs thanks to NVDA 2016.4
features.
* Added ability to press Control+Alt+up or down arrow keys to move between
tracks (specifically, track columns) vertically just as one is moving to next
or previous row in a table.
* Added a combo box in add-on settings dialog to set which column should be
announced when moving through columns vertically.
+* Moved end of track , intro and microphone alarm controls from add-on
settings to the new Alarms Center.
+* In Alarms Center, end of track and track intro edit fields are always shown
regardless of state of alarm notification checkboxes.
* Removed Track Dial (NVDA's version of enhanced arrow keys), replaced by
Columns explorer and Column Navigator/table navigation commands). This affects
Studio and Track Tool.
+* Added a new command in SPL Controller layer to announce Studio status such
as track playback and microphone status (Q).
## Version 16.12.1
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/5c200bf729ab/
Changeset: 5c200bf729ab
Branch: None
User: josephsl
Date: 2016-12-17 17:50:18+00:00
Summary: Playlist snapshots (17.1-dev): formally introduce Playlist
Snapshots.
SPL Assistant, F8 is used to obtain playlist snapshots (track count, total
duration, shortest, longest and average track lengths, top categhories). SPL
Assistant, F11 would have been ideal, but it conflicts with Remote add-on,
hence F8 will be used for now.
This is reserved for add-on 17.1.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 5d7ceca..9f9ad00 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1379,8 +1379,23 @@ class AppModule(appModuleHandler.AppModule):
# Return total duration of a range of tracks.
# This is used in track time analysis when multiple tracks are selected.
# This is also called from playlist duration scripts.
- # To be replaced by general track duration script, with the difference
being start and end location.
- def totalTime(self, start, end):
+ def playlistDuration(self, start=None, end=None):
+ if start is None: start = api.getFocusObject()
+ duration = start.indexOf("Duration")
+ totalDuration = 0
+ obj = start
+ while obj not in (None, end):
+ # Technically segue.
+ segue = obj._getColumnContent(duration)
+ if segue not in (None, "00:00"):
+ hms = segue.split(":")
+ totalDuration += (int(hms[-2])*60) +
int(hms[-1])
+ if len(hms) == 3: totalDuration +=
int(hms[0])*3600
+ obj = obj.next
+ return totalDuration
+
+ # Segue version of this will be used in some places (the below is the
raw duration).)
+ def playlistDurationRaw(self, start, end):
# Take care of errors such as the following.
if start < 0 or end > statusAPI(0, 124, ret=True)-1:
raise ValueError("Track range start or end position out
of range")
@@ -1399,23 +1414,73 @@ class AppModule(appModuleHandler.AppModule):
# Data to be gathered comes from a set of flags.
def playlistSnapshots(self, obj, end, snapshotFlags=None):
# Track count and total duration are always included.
- snapshot = {"TrackCount":statusAPI(0, 124, ret=True)}
+ snapshot = {}
+ if snapshotFlags is None:
+ snapshotFlags = ["PlaylistDurationMinMax",
"PlaylistCategoryCount", "PlaylistDurationAverage"]
duration = obj.indexOf("Duration")
title = obj.indexOf("Title")
+ min, max = None, None
+ minTitle, maxTitle = None, None
+ category = obj.indexOf("Category")
totalDuration = 0
- titleDuration = []
+ categories = []
+ # A specific version of the playlist duration loop is needed in
order to gather statistics.
while obj not in (None, end):
- # Technically segue.
segue = obj._getColumnContent(duration)
trackTitle = obj._getColumnContent(title)
- titleDuration.append((trackTitle, segue))
+ categories.append(obj._getColumnContent(category))
+ # Shortest and longest tracks.
+ if min is None: min = segue
+ if segue and segue < min:
+ min = segue
+ minTitle = trackTitle
+ if segue and segue > max:
+ max = segue
+ maxTitle = trackTitle
if segue not in (None, "00:00"):
hms = segue.split(":")
totalDuration += (int(hms[-2])*60) +
int(hms[-1])
if len(hms) == 3: totalDuration +=
int(hms[0])*3600
obj = obj.next
- snapshot["DurationTotal"] = totalDuration
- return snapshot["DurationTotal"]
+ if end is None: snapshot["PlaylistTrackCount"] = statusAPI(0,
124, ret=True)
+ snapshot["PlaylistDurationTotal"] =
self._ms2time(totalDuration, ms=False)
+ if "PlaylistDurationMinMax" in snapshotFlags:
+ snapshot["PlaylistDurationMin"] = "%s (%s)"%(minTitle,
min)
+ snapshot["PlaylistDurationMax"] = "%s (%s)"%(maxTitle,
max)
+ if "PlaylistDurationAverage" in snapshotFlags:
+ snapshot["PlaylistDurationAverage"] =
self._ms2time(totalDuration/snapshot["PlaylistTrackCount"], ms=False)
+ if "PlaylistCategoryCount" in snapshotFlags:
+ import collections
+ snapshot["PlaylistCategoryCount"] =
collections.Counter(categories)
+ return snapshot
+
+# Output formatter for playlist snapshots.
+# Pressed once will speak and/or braille it, pressing twice or more will
output this info to an HTML file.
+ def playlistSnapshotOutput(self, snapshot, scriptCount):
+ scriptCount = 1
+ statusInfo = ["Tracks: %s"%snapshot["PlaylistTrackCount"]]
+ statusInfo.append("Duration:
%s"%snapshot["PlaylistDurationTotal"])
+ if "PlaylistDurationMin" in snapshot:
+ statusInfo.append("Shortest:
%s"%snapshot["PlaylistDurationMin"])
+ statusInfo.append("Longest:
%s"%snapshot["PlaylistDurationMax"])
+ if "PlaylistDurationAverage" in snapshot:
+ statusInfo.append("Average:
%s"%snapshot["PlaylistDurationAverage"])
+ if "PlaylistCategoryCount" in snapshot:
+ categories =
snapshot["PlaylistCategoryCount"].most_common()
+ if scriptCount == 0:
+ statusInfo.append("Top category: %s
(%s)"%(categories[0]))
+ else:
+ categoryList = []
+ for item in categories:
+ category, count = item
+ category = category.replace("<", "")
+ category = category.replace(">", "")
+ categoryList.append("<li>%s
(%s)</li>"%(category, count))
+ statusInfo.append("".join(["Categories:<ol>",
"\n".join(categoryList), "</ol>"]))
+ if scriptCount == 0:
+ ui.message(", ".join(statusInfo))
+ else:
+
ui.browseableMessage("<p>".join(statusInfo),title="Playlist snapshot",
isHtml=True)
# Some handlers for native commands.
@@ -1610,7 +1675,7 @@ class AppModule(appModuleHandler.AppModule):
if obj.role == controlTypes.ROLE_LIST:
ui.message("00:00")
return
- self.announceTime(self.playlistSnapshots(obj, None), ms=False)
+ self.announceTime(self.playlistDuration(start=obj), ms=False)
def script_sayPlaylistModified(self, gesture):
try:
@@ -1751,7 +1816,7 @@ class AppModule(appModuleHandler.AppModule):
analysisBegin = min(self._analysisMarker, trackPos)
analysisEnd = max(self._analysisMarker, trackPos)
analysisRange = analysisEnd-analysisBegin+1
- totalLength = self.totalTime(analysisBegin, analysisEnd)
+ totalLength = self.playlistDurationRaw(analysisBegin,
analysisEnd)
if analysisRange == 1:
self.announceTime(totalLength)
else:
@@ -1766,9 +1831,10 @@ class AppModule(appModuleHandler.AppModule):
ui.message("Please return to playlist viewer before
invoking this command.")
return
if obj.role == controlTypes.ROLE_LIST:
- ui.message("00:00")
+ ui.message(_("You need to add tracks before invoking
this command"))
return
- self.announceTime(self.playlistSnapshots(obj, None), ms=False)
+ # Speak and braille on the first press, display a decorated
HTML message for subsequent presses.
+
self.playlistSnapshotOutput(self.playlistSnapshots(obj.parent.firstChild,
None), scriptHandler.getLastScriptRepeatCount())
def script_switchProfiles(self, gesture):
splconfig.triggerProfileSwitch() if
splconfig._triggerProfileActive else splconfig.instantProfileSwitch()
@@ -1886,6 +1952,7 @@ class AppModule(appModuleHandler.AppModule):
"kb:shift+s":"sayScheduledToPlay",
"kb:shift+p":"sayTrackPitch",
"kb:shift+r":"libraryScanMonitor",
+ "kb:f8":"takePlaylistSnapshots",
"kb:f9":"markTrackForAnalysis",
"kb:f10":"trackTimeAnalysis",
"kb:f12":"switchProfiles",
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/f6ac03cff7bf/
Changeset: f6ac03cff7bf
Branch: None
User: josephsl
Date: 2016-12-23 23:23:31+00:00
Summary: Merge branch 'master' into plSnaps
Affected #: 2 files
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index 5d6d0df..5e0511e 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -102,6 +102,21 @@ class SPLConfigDialog(gui.SettingsDialog):
alarmsCenterButton = SPLConfigHelper.addItem(wx.Button(self,
label=_("&Alarms Center...")))
alarmsCenterButton.Bind(wx.EVT_BUTTON, self.onAlarmsCenter)
+ # Translators: One of the alarm notification options.
+ self.alarmAnnounceValues=[("beep",_("beep")),
+ # Translators: One of the alarm notification options.
+ ("message",_("message")),
+ # Translators: One of the alarm notification options.
+ ("both",_("both beep and message"))]
+ # Translators: The label for a setting in SPL
add-on dialog to control alarm announcement type.
+ self.alarmAnnounceList =
SPLConfigHelper.addLabeledControl(_("&Alarm notification:"), wx.Choice,
choices=[x[1] for x in self.alarmAnnounceValues])
+
alarmAnnounceCurValue=splconfig.SPLConfig["General"]["AlarmAnnounce"]
+ selection = (x for x,y in enumerate(self.alarmAnnounceValues)
if y[0]==alarmAnnounceCurValue).next()
+ try:
+ self.alarmAnnounceList.SetSelection(selection)
+ except:
+ pass
+
self.brailleTimerValues=[("off",_("Off")),
# Translators: One of the braille timer settings.
("outro",_("Track ending")),
@@ -118,21 +133,6 @@ class SPLConfigDialog(gui.SettingsDialog):
except:
pass
- # Translators: One of the alarm notification options.
- self.alarmAnnounceValues=[("beep",_("beep")),
- # Translators: One of the alarm notification options.
- ("message",_("message")),
- # Translators: One of the alarm notification options.
- ("both",_("both beep and message"))]
- # Translators: The label for a setting in SPL
add-on dialog to control alarm announcement type.
- self.alarmAnnounceList =
SPLConfigHelper.addLabeledControl(_("&Alarm notification:"), wx.Choice,
choices=[x[1] for x in self.alarmAnnounceValues])
-
alarmAnnounceCurValue=splconfig.SPLConfig["General"]["AlarmAnnounce"]
- selection = (x for x,y in enumerate(self.alarmAnnounceValues)
if y[0]==alarmAnnounceCurValue).next()
- try:
- self.alarmAnnounceList.SetSelection(selection)
- except:
- pass
-
self.libScanValues=[("off",_("Off")),
# Translators: One of the library scan announcement settings.
("ending",_("Start and end only")),
@@ -273,8 +273,8 @@ class SPLConfigDialog(gui.SettingsDialog):
splconfig.SPLConfig["IntroOutroAlarms"]["SaySongRamp"] =
self.saySongRamp
splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarm"] =
self.micAlarm
splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarmInterval"] =
self.micAlarmInterval
- splconfig.SPLConfig["General"]["BrailleTimer"] =
self.brailleTimerValues[self.brailleTimerList.GetSelection()][0]
splconfig.SPLConfig["General"]["AlarmAnnounce"] =
self.alarmAnnounceValues[self.alarmAnnounceList.GetSelection()][0]
+ splconfig.SPLConfig["General"]["BrailleTimer"] =
self.brailleTimerValues[self.brailleTimerList.GetSelection()][0]
splconfig.SPLConfig["General"]["LibraryScanAnnounce"] =
self.libScanValues[self.libScanList.GetSelection()][0]
splconfig.SPLConfig["General"]["TimeHourAnnounce"] =
self.hourAnnounceCheckbox.Value
splconfig.SPLConfig["General"]["CategorySounds"] =
self.categorySoundsCheckbox.Value
diff --git a/addon/globalPlugins/SPLStudioUtils/__init__.py
b/addon/globalPlugins/SPLStudioUtils/__init__.py
index 1ac7977..9701601 100755
--- a/addon/globalPlugins/SPLStudioUtils/__init__.py
+++ b/addon/globalPlugins/SPLStudioUtils/__init__.py
@@ -284,9 +284,9 @@ class GlobalPlugin(globalPluginHandler.GlobalPlugin):
statusInfo.append("Record to file on" if
winUser.sendMessage(SPLWin, SPLMSG, 4, SPLStatusInfo) else "Record to file off")
cartEdit = winUser.sendMessage(SPLWin, SPLMSG, 5,
SPLStatusInfo)
cartInsert = winUser.sendMessage(SPLWin, SPLMSG, 6,
SPLStatusInfo)
- if not cartEdit and not cartInsert:
statusInfo.append("Cart edit off")
- elif cartEdit and not cartInsert:
statusInfo.append("Cart edit on")
+ if cartEdit: statusInfo.append("Cart edit on")
elif not cartEdit and cartInsert:
statusInfo.append("Cart insert on")
+ else: statusInfo.append("Cart edit off")
ui.message("; ".join(statusInfo))
self.finish()
# Translators: Input help message for a SPL Controller command.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/3575064f0116/
Changeset: 3575064f0116
Branch: None
User: josephsl
Date: 2016-12-30 01:26:38+00:00
Summary: Merge branch 'master' into plSnaps
Affected #: 20 files
diff --git a/addon/appModules/splcreator.py b/addon/appModules/splcreator.py
new file mode 100755
index 0000000..31b99da
--- /dev/null
+++ b/addon/appModules/splcreator.py
@@ -0,0 +1,15 @@
+# StationPlaylist Creator
+# An app module and global plugin package for NVDA
+# Copyright 2016-2017 Joseph Lee and others, released under GPL.
+
+# Basic support for StationPlaylist Creator.
+
+import appModuleHandler
+
+
+class AppModule(appModuleHandler.AppModule):
+
+ def chooseNVDAObjectOverlayClasses(self, obj, clsList):
+ if obj.windowClassName in ("TDemoRegForm", "TAboutForm"):
+ from NVDAObjects.behaviors import Dialog
+ clsList.insert(0, Dialog)
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 9f9ad00..f076683 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-2016, Geoff Shang, Joseph Lee and others, released
under GPL.
+# Copyright 2011, 2013-2017, 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:
@@ -17,10 +17,7 @@ import threading
import controlTypes
import appModuleHandler
import api
-import review
-import eventHandler
import scriptHandler
-import queueHandler
import ui
import nvwave
import speech
@@ -157,7 +154,7 @@ class SPLTrackItem(IAccessible):
if splconfig.SPLConfig["General"]["TrackCommentAnnounce"] !=
"off":
self.announceTrackComment(0)
# 6.3: Catch an unusual case where screen order is off yet
column order is same as screen order and NvDA is told to announce all columns.
- # 17.1: Even if vertical column commands are performed, build
description pieces for consistency.
+ # 17.04: Even if vertical column commands are performed, build
description pieces for consistency.
if splconfig._shouldBuildDescriptionPieces():
descriptionPieces = []
columnsToInclude =
splconfig.SPLConfig["ColumnAnnouncement"]["IncludedColumns"]
@@ -202,7 +199,7 @@ class SPLTrackItem(IAccessible):
# Announce column content if any.
# 7.0: Add an optional header in order to announce correct header
information in columns explorer.
- # 17.1: Allow checked status in 5.1x and later to be announced if this
is such a case (vertical column navigation).)
+ # 17.04: Allow checked status in 5.1x and later to be announced if this
is such a case (vertical column navigation).)
def announceColumnContent(self, colNumber, header=None,
reportStatus=False):
columnHeader = header if header is not None else
self.appModule._columnHeaderNames[colNumber]
columnContent =
self._getColumnContent(self.indexOf(columnHeader))
@@ -574,6 +571,7 @@ class AppModule(appModuleHandler.AppModule):
# Announce status changes while using other programs.
# This requires NVDA core support and will be available in 6.0
and later (cannot be ported to earlier versions).
# For now, handle all background events, but in the end, make
this configurable.
+ import eventHandler
if hasattr(eventHandler, "requestEvents"):
eventHandler.requestEvents(eventName="nameChange",
processId=self.processID, windowClassName="TStatusBar")
eventHandler.requestEvents(eventName="nameChange",
processId=self.processID, windowClassName="TStaticText")
@@ -591,6 +589,7 @@ class AppModule(appModuleHandler.AppModule):
# LTS: Only do this if channel hasn't changed.
if splconfig.SPLConfig["Update"]["AutoUpdateCheck"] or
splupdate._updateNow:
# 7.0: Have a timer call the update function indirectly.
+ import queueHandler
queueHandler.queueFunction(queueHandler.eventQueue,
splconfig.updateInit)
# Display startup dialogs if any.
wx.CallAfter(splconfig.showStartupDialogs)
@@ -628,6 +627,7 @@ class AppModule(appModuleHandler.AppModule):
obj.role = controlTypes.ROLE_GROUPING
# In certain edit fields and combo boxes, the field name is
written to the screen, and there's no way to fetch the object for this text.
Thus use review position text.
elif obj.windowClassName in ("TEdit", "TComboBox") and not
obj.name:
+ import review
fieldName, fieldObj = review.getScreenPosition(obj)
fieldName.expand(textInfos.UNIT_LINE)
if obj.windowClassName == "TComboBox":
@@ -694,8 +694,7 @@ class AppModule(appModuleHandler.AppModule):
if self.scanCount%100 == 0:
self._libraryScanAnnouncer(obj.name[1:obj.name.find("]")],
splconfig.SPLConfig["General"]["LibraryScanAnnounce"])
if not self.libraryScanning:
- if self.productVersion not in
noLibScanMonitor:
- if not
self.backgroundStatusMonitor: self.libraryScanning = True
+ if self.productVersion not in
noLibScanMonitor: self.libraryScanning = True
elif "match" in obj.name:
if
splconfig.SPLConfig["General"]["LibraryScanAnnounce"] != "off" and
self.libraryScanning:
if
splconfig.SPLConfig["General"]["BeepAnnounce"]: tones.beep(370, 100)
@@ -773,14 +772,19 @@ class AppModule(appModuleHandler.AppModule):
# Perform extra action in specific situations (mic alarm, for example).
def doExtraAction(self, status):
+ # Be sure to only deal with cart mode changes if Cart Explorer
is on.
+ # Optimization: Return early if the below condition is true.
+ if self.cartExplorer and status.startswith("Cart"):
+ # 17.01: The best way to detect Cart Edit off is
consulting file modification time.
+ # Automatically reload cart information if this is the
case.
+ studioTitle = api.getForegroundObject().name
+ if splmisc.shouldCartExplorerRefresh(studioTitle):
+ self.carts =
splmisc.cartExplorerInit(studioTitle)
+ # Translators: Presented when cart edit mode is toggled
on while cart explorer is on.
+ ui.message(_("Cart explorer is active"))
+ return
+ # Microphone alarm and alarm interval if defined.
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.
- ui.message(_("Cart explorer is active"))
- elif status == "Cart Edit Off":
- # Translators: Presented when cart edit mode is
toggled off while cart explorer is on.
- ui.message(_("Please reenter cart explorer to
view updated cart assignments"))
if micAlarm:
# Play an alarm sound (courtesy of Jerry Mader from
Mader Radio).
global micAlarmT, micAlarmT2
@@ -853,9 +857,9 @@ class AppModule(appModuleHandler.AppModule):
# 6.3: Memory leak results if encoder flag sets and other
encoder support maps aren't cleaned up.
# This also could have allowed a hacker to modify the flags set
(highly unlikely) so NvDA could get confused next time Studio loads.
import sys
- if "globalPlugins.SPLStudioUtils.encoders" in sys.modules:
- import globalPlugins.SPLStudioUtils.encoders
- globalPlugins.SPLStudioUtils.encoders.cleanup()
+ if "globalPlugins.splUtils.encoders" in sys.modules:
+ import globalPlugins.splUtils.encoders
+ globalPlugins.splUtils.encoders.cleanup()
splconfig.saveConfig()
# Delete focused track reference.
self._focusedTrack = None
@@ -868,6 +872,8 @@ class AppModule(appModuleHandler.AppModule):
# Manually clear the following dictionaries.
self.carts.clear()
self._cachedStatusObjs.clear()
+ # Don't forget to reset timestamps for cart files.
+ splmisc._cartEditTimestamps = [0, 0, 0, 0]
# Just to make sure:
global _SPLWin
if _SPLWin: _SPLWin = None
@@ -1256,40 +1262,40 @@ class AppModule(appModuleHandler.AppModule):
global libScanT
if libScanT and libScanT.isAlive() and
api.getForegroundObject().windowClassName == "TTrackInsertForm":
return
- countA = statusAPI(1, 32, ret=True)
- if countA == 0:
+ if statusAPI(1, 32, ret=True) < 0:
self.libraryScanning = False
return
time.sleep(0.1)
if api.getForegroundObject().windowClassName ==
"TTrackInsertForm" and self.productVersion in noLibScanMonitor:
self.libraryScanning = False
return
- # Sometimes, a second call is needed to obtain the real scan
count in Studio 5.10 and later.
- countB = statusAPI(1, 32, ret=True)
- if countA == countB:
+ # 17.04: Library scan may have finished while this thread was
sleeping.
+ if statusAPI(1, 32, ret=True) < 0:
self.libraryScanning = False
- countB = statusAPI(0, 32, ret=True)
# Translators: Presented when library scanning is
finished.
- ui.message(_("{itemCount} items in the
library").format(itemCount = countB))
+ ui.message(_("{itemCount} items in the
library").format(itemCount = statusAPI(0, 32, ret=True)))
else:
- libScanT =
threading.Thread(target=self.libraryScanReporter, args=(_SPLWin, countA,
countB, 1))
+ libScanT =
threading.Thread(target=self.libraryScanReporter)
libScanT.daemon = True
libScanT.start()
- def libraryScanReporter(self, _SPLWin, countA, countB, parem):
+ def libraryScanReporter(self):
scanIter = 0
- while countA != countB:
+ # 17.04: Use the constant directly, as 5.10 and later provides
a convenient method to detect completion of library scans.
+ scanCount = statusAPI(1, 32, ret=True)
+ while scanCount >= 0:
if not self.libraryScanning: return
- countA = countB
time.sleep(1)
# Do not continue if we're back on insert tracks form
or library scan is finished.
if api.getForegroundObject().windowClassName ==
"TTrackInsertForm" or not self.libraryScanning:
return
- countB, scanIter = statusAPI(parem, 32, ret=True),
scanIter+1
- if countB < 0:
+ # Scan count may have changed during sleep.
+ scanCount = statusAPI(1, 32, ret=True)
+ if scanCount < 0:
break
+ scanIter+=1
if scanIter%5 == 0 and
splconfig.SPLConfig["General"]["LibraryScanAnnounce"] not in ("off", "ending"):
- self._libraryScanAnnouncer(countB,
splconfig.SPLConfig["General"]["LibraryScanAnnounce"])
+ self._libraryScanAnnouncer(scanCount,
splconfig.SPLConfig["General"]["LibraryScanAnnounce"])
self.libraryScanning = False
if self.backgroundStatusMonitor: return
if splconfig.SPLConfig["General"]["LibraryScanAnnounce"] !=
"off":
@@ -1297,7 +1303,7 @@ class AppModule(appModuleHandler.AppModule):
tones.beep(370, 100)
else:
# Translators: Presented after library scan is
done.
- ui.message(_("Scan complete with {itemCount}
items").format(itemCount = countB))
+ ui.message(_("Scan complete with {itemCount}
items").format(itemCount = statusAPI(0, 32, ret=True)))
# Take care of library scanning announcement.
def _libraryScanAnnouncer(self, count, announcementType):
@@ -1769,10 +1775,8 @@ class AppModule(appModuleHandler.AppModule):
def script_libraryScanMonitor(self, gesture):
if not self.libraryScanning:
- scanning = statusAPI(1, 32, ret=True)
- if scanning < 0:
- items = statusAPI(0, 32, ret=True)
- ui.message(_("{itemCount} items in the
library").format(itemCount = items))
+ if statusAPI(1, 32, ret=True) < 0:
+ ui.message(_("{itemCount} items in the
library").format(itemCount = statusAPI(0, 32, ret=True)))
return
self.libraryScanning = True
# Translators: Presented when attempting to start
library scan.
@@ -2056,5 +2060,4 @@ class AppModule(appModuleHandler.AppModule):
"kb:Shift+numpadDelete":"deleteTrack",
"kb:escape":"escape",
"kb:control+nvda+-":"sendFeedbackEmail",
- #"kb:control+nvda+`":"SPLAssistantToggle"
}
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 3351125..a5b448e 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -1,6 +1,6 @@
# SPL Studio Configuration Manager
# An app module and global plugin package for NVDA
-# Copyright 2015-2016 Joseph Lee and others, released under GPL.
+# Copyright 2015-2017 Joseph Lee and others, released under GPL.
# Provides the configuration management package for SPL Studio app module.
# For miscellaneous dialogs and tool, see SPLMisc module.
# For UI surrounding this module, see splconfui module.
@@ -12,10 +12,8 @@ from validate import Validator
import time
import datetime
import cPickle
-import copy
import globalVars
import ui
-import api
import gui
import wx
import splupdate
@@ -77,7 +75,6 @@ UpdateInterval = integer(min=1, max=30, default=7)
[Startup]
AudioDuckingReminder = boolean(default=true)
WelcomeDialog = boolean(default=true)
-Studio500 = boolean(default=true)
"""), encoding="UTF-8", list_values=False)
confspec7.newlines = "\r\n"
SPLConfig = None
@@ -111,6 +108,12 @@ class ConfigHub(ChainMap):
self.profileNames.append(None) # Signifying normal profile.
# Always cache normal profile upon startup.
self._cacheConfig(self.maps[0])
+ # Remove deprecated keys.
+ # This action must be performed after caching, otherwise the
newly modified profile will not be saved.
+ deprecatedKeys = {"General":"TrackDial", "Startup":"Studio500"}
+ for section, key in deprecatedKeys.iteritems():
+ if key in section: del self.maps[0][section][key]
+ # Moving onto broadcast profiles if any.
try:
profiles = filter(lambda fn: os.path.splitext(fn)[-1]
== ".ini", os.listdir(SPLProfiles))
for profile in profiles:
@@ -236,6 +239,7 @@ class ConfigHub(ChainMap):
def _cacheConfig(self, conf):
global _SPLCache
+ import copy
if _SPLCache is None: _SPLCache = {}
key = None if conf.filename == SPLIni else conf.name
_SPLCache[key] = {}
@@ -475,7 +479,7 @@ def _extraInitSteps(conf, profileName=None):
else:
_configLoadStatus[profileName] = "metadataReset"
conf["MetadataStreaming"]["MetadataEnabled"] = [False, False,
False, False, False]
- # 17.1: If vertical column announcement value is "None", transform this
to NULL.
+ # 17.04: If vertical column announcement value is "None", transform
this to NULL.
if conf["General"]["VerticalColumnAnnounce"] == "None":
conf["General"]["VerticalColumnAnnounce"] = None
@@ -944,9 +948,10 @@ Thank you.""")
self.Destroy()
# Old version reminder.
-class OldVersionReminder(wx.Dialog):
- """A dialog shown when using add-on 8.x under Studio 5.0x.
- """
+# Only used when there is a LTS version.
+"""class OldVersionReminder(wx.Dialog):
+ #A dialog shown when using add-on 8.x under Studio 5.0x.
+ #
def __init__(self, parent):
# Translators: Title of a dialog displayed when the add-on
starts reminding broadcasters about old Studio releases.
@@ -961,7 +966,7 @@ class OldVersionReminder(wx.Dialog):
sizer = wx.BoxSizer(wx.HORIZONTAL)
# Translators: A checkbox to turn off old version reminder
message.
self.oldVersionReminder=wx.CheckBox(self,wx.NewId(),label=_("Do
not show this message again"))
- self.oldVersionReminder.SetValue(not
SPLConfig["Startup"]["Studio500"])
+ self.oldVersionReminder.SetValue(not
SPLConfig["Startup"]["OldSPLVersionReminder"])
sizer.Add(self.oldVersionReminder, border=10,flag=wx.TOP)
mainSizer.Add(sizer, border=10, flag=wx.BOTTOM)
@@ -975,15 +980,16 @@ class OldVersionReminder(wx.Dialog):
def onOk(self, evt):
global SPLConfig
if self.oldVersionReminder.Value:
- SPLConfig["Startup"]["Studio500"] = not
self.oldVersionReminder.Value
- self.Destroy()
+ SPLConfig["Startup"]["OldSPLVersionReminder"] = not
self.oldVersionReminder.Value
+ self.Destroy()"""
# And to open the above dialog and any other dialogs.
def showStartupDialogs(oldVer=False):
- if oldVer and SPLConfig["Startup"]["Studio500"]:
- gui.mainFrame.prePopup()
- OldVersionReminder(gui.mainFrame).Show()
- gui.mainFrame.postPopup()
+ # Old version reminder if this is such a case.
+ #if oldVer and SPLConfig["Startup"]["OldSPLVersionReminder"]:
+ #gui.mainFrame.prePopup()
+ #OldVersionReminder(gui.mainFrame).Show()
+ #gui.mainFrame.postPopup()
if SPLConfig["Startup"]["WelcomeDialog"]:
gui.mainFrame.prePopup()
WelcomeDialog(gui.mainFrame).Show()
@@ -993,14 +999,11 @@ def showStartupDialogs(oldVer=False):
#if gui.messageBox("The next major version of the add-on (15.x)
will be the last version to support Studio versions earlier than 5.10, with
add-on 15.x being designated as a long-term support version. Would you like to
switch to long-term support release?", "Long-Term Support version", wx.YES |
wx.NO | wx.CANCEL | wx.CENTER | wx.ICON_QUESTION) == wx.YES:
#splupdate.SPLUpdateChannel = "lts"
#os.remove(os.path.join(globalVars.appArgs.configPath,
"addons", "stationPlaylist", "ltsprep"))
- try:
- import audioDucking
- if SPLConfig["Startup"]["AudioDuckingReminder"] and
audioDucking.isAudioDuckingSupported():
- gui.mainFrame.prePopup()
- AudioDuckingReminder(gui.mainFrame).Show()
- gui.mainFrame.postPopup()
- except ImportError:
- pass
+ import audioDucking
+ if SPLConfig["Startup"]["AudioDuckingReminder"] and
audioDucking.isAudioDuckingSupported():
+ gui.mainFrame.prePopup()
+ AudioDuckingReminder(gui.mainFrame).Show()
+ gui.mainFrame.postPopup()
# Message verbosity pool.
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index 5e0511e..743aab1 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -1,15 +1,12 @@
# SPL Studio Configuration user interfaces
# An app module and global plugin package for NVDA
-# Copyright 2016 Joseph Lee and others, released under GPL.
+# Copyright 2016-2017 Joseph Lee and others, released under GPL.
# Split from SPL config module in 2016.
# Provides the configuration management UI package for SPL Studio app module.
# For code which provides foundation for code in this module, see splconfig
module.
import os
import weakref
-import datetime
-import calendar
-import ui
import api
import gui
import wx
@@ -666,6 +663,7 @@ class TriggersDialog(wx.Dialog):
daysSizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY,
_("Day")), wx.HORIZONTAL)
self.triggerDays = []
+ import calendar
for day in xrange(len(calendar.day_name)):
triggerDay=wx.CheckBox(self,
wx.NewId(),label=calendar.day_name[day])
triggerDay.SetValue((64 >> day &
self.Parent._profileTriggersConfig[profile][0]) if profile in
self.Parent._profileTriggersConfig else 0)
@@ -738,6 +736,7 @@ class TriggersDialog(wx.Dialog):
# Otherwise trigger flag will be added each
time this is called (either this handler or the add-on settings' flags
retriever must retrieve the flags set).
if not self.profile in
parent._profileTriggersConfig:
parent.setProfileFlags(self.selection,
"add", _("time-based"))
+ import datetime
parent._profileTriggersConfig[self.profile] =
splconfig.setNextTimedProfile(self.profile, bit, datetime.time(hour, min))
parent._profileTriggersConfig[self.profile][6]
= duration
else:
@@ -913,7 +912,7 @@ class MetadataStreamingDialog(wx.Dialog):
# WX's CheckListBox isn't user friendly.
# Therefore use checkboxes laid out across the top.
- # 17.1: instead of two loops, just use one loop, with labels
deriving from the below tuple.
+ # 17.04: instead of two loops, just use one loop, with labels
deriving from the below tuple.
# Only one loop is needed as helper.addLabelControl returns the
checkbox itself and that can be appended.
streamLabels = ("DSP encoder", "URL 1", "URL 2", "URL 3", "URL
4")
self.checkedStreams = []
@@ -983,7 +982,7 @@ class ColumnAnnouncementsDialog(wx.Dialog):
# Same as metadata dialog (wx.CheckListBox isn't user friendly).
# Gather values for checkboxes except artist and title.
# 6.1: Split these columns into rows.
- # 17.1: Gather items into a single list instead of three.
+ # 17.04: Gather items into a single list instead of three.
self.checkedColumns = []
sizer = gui.guiHelper.BoxSizerHelper(self,
orientation=wx.HORIZONTAL)
for column in ("Duration", "Intro", "Category", "Filename"):
@@ -1003,7 +1002,7 @@ class ColumnAnnouncementsDialog(wx.Dialog):
# WXPython Phoenix contains RearrangeList to allow item orders
to be changed automatically.
# Because WXPython Classic doesn't include this, work around by
using a variant of list box and move up/down buttons.
- # 17.1: The label for the list below is above the list, so move
move up/down buttons to the right of the list box.
+ # 17.04: The label for the list below is above the list, so
move move up/down buttons to the right of the list box.
# Translators: The label for a setting in SPL add-on dialog to
select column announcement order.
self.trackColumns =
colAnnouncementsHelper.addLabeledControl(_("Column &order:"), wx.ListBox,
choices=parent.columnOrder)
self.trackColumns.Bind(wx.EVT_LISTBOX,self.onColumnSelection)
@@ -1101,7 +1100,7 @@ class ColumnsExplorerDialog(wx.Dialog):
colExplorerHelper = gui.guiHelper.BoxSizerHelper(self,
orientation=wx.VERTICAL)
# 7.0: Studio 5.0x columns.
- # 17.1: Five by two grid layout as 5.0x is no longer supported.
+ # 17.04: Five by two grid layout as 5.0x is no longer supported.
sizer = gui.guiHelper.BoxSizerHelper(self,
orientation=wx.HORIZONTAL)
for slot in xrange(5):
# Translators: The label for a setting in SPL add-on
dialog to select column for this column slot.
@@ -1329,9 +1328,9 @@ class ResetDialog(wx.Dialog):
if self.resetEncodersCheckbox.Value:
if
os.path.exists(os.path.join(globalVars.appArgs.configPath,
"splStreamLabels.ini")):
os.remove(os.path.join(globalVars.appArgs.configPath, "splStreamLabels.ini"))
- if "globalPlugins.SPLStudioUtils.encoders" in
sys.modules:
- import
globalPlugins.SPLStudioUtils.encoders
-
globalPlugins.SPLStudioUtils.encoders.cleanup()
+ if "globalPlugins.splUtils.encoders" in
sys.modules:
+ import globalPlugins.splUtils.encoders
+
globalPlugins.splUtils.encoders.cleanup()
_configDialogOpened = False
# Translators: A dialog message shown when settings
were reset to defaults.
wx.CallAfter(gui.messageBox, _("Successfully applied
default add-on settings."),
diff --git a/addon/appModules/splstudio/splmisc.py
b/addon/appModules/splstudio/splmisc.py
index d7a9940..e3355bc 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-2016 Joseph Lee and others, released under GPL.
+# Copyright 2015-2017 Joseph Lee and others, released under GPL.
# Miscellaneous functions and user interfaces
# Split from config module in 2015.
@@ -13,7 +13,6 @@ from csv import reader # For cart explorer.
import gui
import wx
import ui
-from NVDAObjects.IAccessible import sysListView32
from winUser import user32, sendMessage
# Locate column content.
@@ -23,6 +22,7 @@ from winUser import user32, sendMessage
# In track finder, this is used when encountering the track item but NVDA says
otherwise.
def _getColumnContent(obj, col):
import winKernel
+ from NVDAObjects.IAccessible import sysListView32
# Borrowed from SysListView32 implementation.
buffer=None
processHandle=obj.processHandle
@@ -262,9 +262,12 @@ def _populateCarts(carts, cartlst, modifier,
standardEdition=False):
else: cart = "%s+%s"%(modifier, identifier)
carts[cart] = cartName
-# Initialize Cart Explorer i.e. fetch carts.
+# Cart file timestamps.
+_cartEditTimestamps = [0, 0, 0, 0]
+ # Initialize Cart Explorer i.e. fetch carts.
# Cart files list is for future use when custom cart names are used.
def cartExplorerInit(StudioTitle, cartFiles=None):
+ global _cartEditTimestamps
# Use cart files in SPL's data folder to build carts dictionary.
# use a combination of SPL user name and static cart location to locate
cart bank files.
# Once the cart banks are located, use the routines in the populate
method above to assign carts.
@@ -294,10 +297,31 @@ def cartExplorerInit(StudioTitle, cartFiles=None):
continue
with open(cartFile) as cartInfo:
cl = [row for row in reader(cartInfo)]
+ # 17.01: Look up file modification date to signal the
app module that Cart Explorer reentry should occur.
+ _cartEditTimestamps[cartFiles.index(f)] =
os.path.getmtime(cartFile)
_populateCarts(carts, cl[1], mod,
standardEdition=carts["standardLicense"]) # See the comment for _populate
method above.
carts["faultyCarts"] = faultyCarts
return carts
+# See if cart files were modified.
+# This is needed in order to announce Cart Explorer reentry command.
+def shouldCartExplorerRefresh(StudioTitle):
+ global _cartEditTimestamps
+ cartsDataPath =
os.path.join(os.environ["PROGRAMFILES"],"StationPlaylist","Data") # Provided
that Studio was installed using default path.
+ userNameIndex = StudioTitle.find("-")
+ # Until NVDA core moves to Python 3, assume that file names aren't
unicode.
+ cartFiles = [u"main carts.cart", u"shift carts.cart", u"ctrl
carts.cart", u"alt carts.cart"]
+ if userNameIndex >= 0:
+ cartFiles = [StudioTitle[userNameIndex+2:]+" "+cartFile for
cartFile in cartFiles]
+ for f in cartFiles:
+ # No need to check for faulty carts here, as Cart Explorer
activation checked it already.
+ timestamp = os.path.getmtime(os.path.join(cartsDataPath,f))
+ # 17.01: Look up file modification date to signal the app
module that Cart Explorer reentry should occur.
+ # Optimization: Short-circuit if even one cart file has been
modified.
+ if _cartEditTimestamps[cartFiles.index(f)] != timestamp:
+ return True
+ return False
+
# Countdown timer.
# This is utilized by many services, chiefly profile triggers routine.
@@ -349,7 +373,7 @@ def _metadataAnnouncer(reminder=False, handle=None):
# DSP is treated specially.
dsp = sendMessage(handle, 1024, 0, 36)
# For others, a simple list.append will do.
- # 17.1: Use a conditional list comprehension.
+ # 17.04: Use a conditional list comprehension.
streamCount = [str(pos) for pos in xrange(1, 5) if sendMessage(handle,
1024, pos, 36)]
# Announce streaming status when told to do so.
status = None
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index c9e3a3a..218d98a 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -1,17 +1,13 @@
# StationPlaylist Studio update checker
# A support module for SPL add-on
-# Copyright 2015-2016, Joseph Lee, released under GPL.
+# Copyright 2015-2017 Joseph Lee, released under GPL.
# Provides update check facility, basics borrowed from NVDA Core's update
checker class.
-import urllib
import os # Essentially, update download is no different than file downloads.
import cPickle
-import threading
import gui
import wx
-import tones
-import time
import addonHandler
import globalVars
@@ -101,6 +97,7 @@ def updateCheck(auto=False, continuous=False,
confUpdateInterval=1):
return
global _SPLUpdateT, SPLAddonCheck, _retryAfterFailure, _progressDialog,
_updateNow
if _updateNow: _updateNow = False
+ import time
# Regardless of whether it is an auto check, update the check time.
# However, this shouldnt' be done if this is a retry after a failed
attempt.
if not _retryAfterFailure: SPLAddonCheck = time.time()
@@ -112,6 +109,7 @@ def updateCheck(auto=False, continuous=False,
confUpdateInterval=1):
updateCandidate = False
updateURL = SPLUpdateURL if SPLUpdateChannel not in channels else
channels[SPLUpdateChannel]
try:
+ import urllib
# Look up the channel if different from the default.
url = urllib.urlopen(updateURL)
url.close()
diff --git a/addon/appModules/tracktool.py b/addon/appModules/tracktool.py
index df79410..ce45e94 100755
--- a/addon/appModules/tracktool.py
+++ b/addon/appModules/tracktool.py
@@ -1,15 +1,11 @@
# StationPlaylist Track Tool
# An app module for NVDA
-# Copyright 2014-2016 Joseph Lee and contributors, released under gPL.
+# Copyright 2014-2017 Joseph Lee and contributors, released under gPL.
# Functionality is based on JFW scripts for SPL Track Tool by Brian Hartgen.
import appModuleHandler
import addonHandler
-import api
import tones
-import speech
-import braille
-from controlTypes import ROLE_LISTITEM
import ui
from NVDAObjects.IAccessible import IAccessible
from splstudio import splconfig
@@ -60,6 +56,7 @@ class TrackToolItem(IAccessible):
# Translators: Presented when some info is not
defined for a track in Track Tool (example: cue not found)
ui.message(_("{header} not
found").format(header = columnHeader))
else:
+ import speech, braille
speech.speakMessage(_("{header}:
blank").format(header = columnHeader))
braille.handler.message(_("{header}:
()").format(header = columnHeader))
@@ -85,8 +82,7 @@ class TrackToolItem(IAccessible):
def script_columnExplorer(self, gesture):
# Just like the main app module, due to the below formula,
columns explorer will be restricted to number commands.
- columnPos = int(gesture.displayName.split("+")[-1])-1
- header =
splconfig.SPLConfig["General"]["ExploreColumnsTT"][columnPos]
+ header =
splconfig.SPLConfig["General"]["ExploreColumnsTT"][int(gesture.displayName.split("+")[-1])-1]
# Several corner cases.
# Look up track name if artist is the header name.
if header == "Artist":
@@ -102,8 +98,7 @@ class TrackToolItem(IAccessible):
ui.message(_("Introduction not set"))
else:
try:
- pos =
indexOf(self.appModule.productVersion).index(header)
- self.announceColumnContent(pos,
columnHeader=header)
+
self.announceColumnContent(indexOf(self.appModule.productVersion).index(header),
columnHeader=header)
except ValueError:
# Translators: Presented when some info is not
defined for a track in Track Tool (example: cue not found)
ui.message(_("{headerText} not
found").format(headerText = header))
@@ -119,6 +114,6 @@ class AppModule(appModuleHandler.AppModule):
SPLColNumber = 0
def chooseNVDAObjectOverlayClasses(self, obj, clsList):
- if obj.windowClassName in ("TListView",
"TTntListView.UnicodeClass") and obj.role == ROLE_LISTITEM:
+ import controlTypes
+ if obj.windowClassName in ("TListView",
"TTntListView.UnicodeClass") and obj.role == controlTypes.ROLE_LISTITEM:
clsList.insert(0, TrackToolItem)
-
diff --git a/addon/doc/ar/readme.md b/addon/doc/ar/readme.md
index d38cd83..83680a9 100644
--- a/addon/doc/ar/readme.md
+++ b/addon/doc/ar/readme.md
@@ -249,6 +249,11 @@ broadcast profiles.
استخدم لمسة ب3 أصابع للانتقال لنمط اللمس, ثم استخدم أوامر اللمس المسرودة
أعلاه لأداء المهام.
+## Version 16.12.1
+
+* Corrected user interface presentation for SPL add-on settings dialog.
+* ترجمة الإضافة لمزيد من اللغات
+
## Version 16.12/15.4-LTS
* More work on supporting Studio 5.20, including announcing cart insert mode
diff --git a/addon/doc/es/readme.md b/addon/doc/es/readme.md
index 3e9db61..72e3e18 100644
--- a/addon/doc/es/readme.md
+++ b/addon/doc/es/readme.md
@@ -287,6 +287,11 @@ realizar algunas órdenes de Studio desde la pantalla
táctil. Primero utiliza
un toque con tres dedos para cambiar a modo SPL, entonces utiliza las
órdenes táctiles listadas arriba para llevar a cabo tareas.
+## Version 16.12.1
+
+* Corrected user interface presentation for SPL add-on settings dialog.
+* Traducciones actualizadas.
+
## Versión 16.12/15.4-LTS
* Más trabajo sobre el soporte de Studio 5.20, incluyendo el anunciado del
diff --git a/addon/doc/fr/readme.md b/addon/doc/fr/readme.md
index 2d68631..c496723 100644
--- a/addon/doc/fr/readme.md
+++ b/addon/doc/fr/readme.md
@@ -297,6 +297,11 @@ un écran tactile. Tout d'abord utiliser une tape à trois
doigts pour
basculer en mode SPL, puis utilisez les commandes tactile énumérées
ci-dessus pour exécuter des commandes.
+## Version 16.12.1
+
+* Corrected user interface presentation for SPL add-on settings dialog.
+* Mises à jour des traductions.
+
## Version 16.12/15.4-LTS
* More work on supporting Studio 5.20, including announcing cart insert mode
diff --git a/addon/doc/gl/readme.md b/addon/doc/gl/readme.md
index 8933f44..8ebec55 100644
--- a/addon/doc/gl/readme.md
+++ b/addon/doc/gl/readme.md
@@ -279,6 +279,11 @@ realizar algunhas ordes do Studio dende a pantalla tactil.
Primeiro usa un
toque con tgres dedos para cambiar a modo SPL, logo usa as ordes tactiles
listadas arriba para realizar ordes.
+## Version 16.12.1
+
+* Corrected user interface presentation for SPL add-on settings dialog.
+* Traducións actualizadas.
+
## Versión 16.12/15.4-LTS
* Máis traballo no soporte do Studio 5.20, incluindo o anunciado do estado
diff --git a/addon/doc/hu/readme.md b/addon/doc/hu/readme.md
index 5ddcfac..4e65bd7 100644
--- a/addon/doc/hu/readme.md
+++ b/addon/doc/hu/readme.md
@@ -260,6 +260,11 @@ Amennyiben érintőképernyős számítógépen használja a
Studiot Windows 8,
parancsokat végrehajthat az érintőképernyőn is. Először 3 ujjas koppintással
váltson SPL módra, és utána már használhatók az alább felsorolt parancsok.
+## Version 16.12.1
+
+* Corrected user interface presentation for SPL add-on settings dialog.
+* Fordítások frissítése
+
## Version 16.12/15.4-LTS
* More work on supporting Studio 5.20, including announcing cart insert mode
diff --git a/addon/globalPlugins/SPLStudioUtils/__init__.py
b/addon/globalPlugins/SPLStudioUtils/__init__.py
deleted file mode 100755
index 9701601..0000000
--- a/addon/globalPlugins/SPLStudioUtils/__init__.py
+++ /dev/null
@@ -1,339 +0,0 @@
-# StationPlaylist Utilities
-# Author: Joseph Lee
-# 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.
-
-from functools import wraps
-import os
-import globalPluginHandler
-import api
-from controlTypes import ROLE_LISTITEM
-import ui
-import globalVars
-from NVDAObjects.IAccessible import getNVDAObjectFromEvent
-import winUser
-import tones
-import nvwave
-import gui
-import wx
-import addonHandler
-addonHandler.initTranslation()
-
-# Layer environment: same as the app module counterpart.
-
-def finally_(func, final):
- """Calls final after func, even if it fails."""
- def wrap(f):
- @wraps(f)
- def new(*args, **kwargs):
- try:
- func(*args, **kwargs)
- finally:
- final()
- return new
- return wrap(final)
-
-# SPL Studio uses WM messages to send and receive data, similar to Winamp (see
NVDA sources/appModules/winamp.py for more information).
-user32 = winUser.user32 # user32.dll.
-SPLWin = 0 # A handle to studio window.
-SPLMSG = winUser.WM_USER
-
-# Various SPL IPC tags.
-SPLVersion = 2
-SPLPlay = 12
-SPLStop = 13
-SPLPause = 15
-SPLAutomate = 16
-SPLMic = 17
-SPLLineIn = 18
-SPLLibraryScanCount = 32
-SPLListenerCount = 35
-SPLStatusInfo = 39 #Studio 5.20 and later.
-SPL_TrackPlaybackStatus = 104
-SPLCurTrackPlaybackTime = 105
-
-
-# On/off toggle wave files.
-onFile = os.path.join(os.path.dirname(__file__), "..", "..", "appModules",
"splstudio", "SPL_on.wav")
-offFile = os.path.join(os.path.dirname(__file__), "..", "..", "appModules",
"splstudio", "SPL_off.wav")
-
-# Help message for SPL Controller
-# Translators: the dialog text for SPL Controller help.
-SPLConHelp=_("""
-After entering SPL Controller, press:
-A: Turn automation on.
-Shift+A: Turn automation off.
-M: Turn microphone on.
-Shift+M: Turn microphone off.
-N: Turn microphone on without fade.
-L: Turn line in on.
-Shift+L: Turn line in off.
-P: Play.
-U: Pause.
-S: Stop with fade.
-T: Instant stop.
-E: Announce if any encoders are being monitored.
-I: Announce listener count.
-Q: Announce Studio status information.
-R: Remaining time for the playing track.
-Shift+R: Library scan progress.""")
-
-# Try to see if SPL foreground object can be fetched. This is used for
switching to SPL Studio window from anywhere and to switch to Studio window
from SAM encoder window.
-
-def fetchSPLForegroundWindow():
- # Turns out NVDA core does have a method to fetch desktop objects, so
use this to find SPL window from among its children.
- dt = api.getDesktopObject()
- fg = None
- fgCount = 0
- for possibleFG in dt.children:
- if "splstudio" in possibleFG.appModule.appModuleName:
- fg = possibleFG
- fgCount+=1
- # Just in case the window is really minimized (not to the system tray)
- if fgCount == 1:
- fg = getNVDAObjectFromEvent(user32.FindWindowA("TStudioForm",
None), winUser.OBJID_CLIENT, 0)
- return fg
-
-
-class GlobalPlugin(globalPluginHandler.GlobalPlugin):
-
- # Translators: Script category for Station Playlist commands in input
gestures dialog.
- scriptCategory = _("StationPlaylist Studio")
-
- #Global layer environment (see the app module for more information).
- SPLController = False # Control SPL from anywhere.
-
- def getScript(self, gesture):
- if not self.SPLController:
- return globalPluginHandler.GlobalPlugin.getScript(self,
gesture)
- script = globalPluginHandler.GlobalPlugin.getScript(self,
gesture)
- if not script:
- script = finally_(self.script_error, self.finish)
- return finally_(script, self.finish)
-
- def finish(self):
- self.SPLController = False
- self.clearGestureBindings()
- self.bindGestures(self.__gestures)
-
- def script_error(self, gesture):
- tones.beep(120, 100)
-
- # Switch focus to SPL Studio window from anywhere.
- def script_focusToSPLWindow(self, gesture):
- # 7.4: Forget it if this is the case like the following.
- if globalVars.appArgs.secure: return
- # Don't do anything if we're already focus on SPL Studio.
- if "splstudio" in
api.getForegroundObject().appModule.appModuleName: return
- else:
- SPLHwnd = user32.FindWindowA("SPLStudio", None) # Used
ANSI version, as Wide char version always returns 0.
- if SPLHwnd == 0: ui.message(_("SPL Studio is not
running."))
- else:
- SPLFG = fetchSPLForegroundWindow()
- if SPLFG == None:
- # Translators: Presented when Studio is
minimized to system tray (notification area).
- ui.message(_("SPL minimized to system
tray."))
- else: SPLFG.setFocus()
- # Translators: Input help mode message for a command to switch to
Station Playlist Studio from any program.
- script_focusToSPLWindow.__doc__=_("Moves to SPL Studio window from
other programs.")
-
- # The SPL Controller:
- # This layer set allows the user to control various aspects of SPL
Studio from anywhere.
- def script_SPLControllerPrefix(self, gesture):
- # 7.4: Red flag...
- if globalVars.appArgs.secure: return
- global SPLWin
- # Error checks:
- # 1. If SPL Studio is not running, print an error message.
- # 2. If we're already in SPL, ask the app module if SPL
Assistant can be invoked with this command.
- if "splstudio" in
api.getForegroundObject().appModule.appModuleName:
- if not
api.getForegroundObject().appModule.SPLConPassthrough():
- # Translators: Presented when NVDA cannot enter
SPL Controller layer since SPL Studio is focused.
- ui.message(_("You are already in SPL Studio
window. For status commands, use SPL Assistant commands."))
- self.finish()
- return
- else:
-
api.getForegroundObject().appModule.script_SPLAssistantToggle(gesture)
- return
- SPLWin = user32.FindWindowA("SPLStudio", None)
- if SPLWin == 0:
- # Translators: Presented when Station Playlist Studio
is not running.
- ui.message(_("SPL Studio is not running."))
- self.finish()
- return
- # No errors, so continue.
- if not self.SPLController:
- self.bindGestures(self.__SPLControllerGestures)
- self.SPLController = True
- # Translators: The name of a layer command set for
Station Playlist Studio.
- # Hint: it is better to translate it as "SPL Control
Panel."
- ui.message(_("SPL Controller"))
- else:
- self.script_error(gesture)
- self.finish()
- # Translators: Input help mode message for a layer command in Station
Playlist Studio.
- script_SPLControllerPrefix.__doc__=_("SPl Controller layer command. See
add-on guide for available commands.")
-
- # The layer commands themselves. Calls user32.SendMessage method for
each script.
-
- def script_automateOn(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,1,SPLAutomate)
- self.finish()
-
- def script_automateOff(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,0,SPLAutomate)
- self.finish()
-
- def script_micOn(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,1,SPLMic)
- nvwave.playWaveFile(onFile)
- self.finish()
-
- def script_micOff(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,0,SPLMic)
- nvwave.playWaveFile(offFile)
- self.finish()
-
- def script_micNoFade(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,2,SPLMic)
- self.finish()
-
- def script_lineInOn(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,1,SPLLineIn)
- self.finish()
-
- def script_lineInOff(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,0,SPLLineIn)
- self.finish()
-
- def script_stopFade(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,0,SPLStop)
- self.finish()
-
- def script_stopInstant(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,1,SPLStop)
- self.finish()
-
- def script_play(self, gesture):
- winUser.sendMessage(SPLWin, SPLMSG, 0, SPLPlay)
- self.finish()
-
- def script_pause(self, gesture):
- playingNow = winUser.sendMessage(SPLWin, SPLMSG, 0,
SPL_TrackPlaybackStatus)
- # Translators: Presented when no track is playing in Station
Playlist Studio.
- if not playingNow: ui.message(_("There is no track playing. Try
pausing while a track is playing."))
- elif playingNow == 3: winUser.sendMessage(SPLWin, SPLMSG, 0,
SPLPause)
- else: winUser.sendMessage(SPLWin, SPLMSG, 1, SPLPause)
- self.finish()
-
- def script_libraryScanProgress(self, gesture):
- scanned = winUser.sendMessage(SPLWin, SPLMSG, 0,
SPLLibraryScanCount)
- # Translators: Announces number of items in the Studio's track
library (example: 1000 items scanned).
- ui.message(_("{itemCount} items scanned").format(itemCount =
scanned))
- self.finish()
-
- def script_listenerCount(self, gesture):
- count = winUser.sendMessage(SPLWin, SPLMSG, 0, SPLListenerCount)
- # Translators: Announces number of stream listeners.
- ui.message(_("Listener count:
{listenerCount}").format(listenerCount = count))
- self.finish()
-
- def script_remainingTime(self, gesture):
- remainingTime = winUser.sendMessage(SPLWin, SPLMSG, 3,
SPLCurTrackPlaybackTime)
- # Translators: Presented when no track is playing in Station
Playlist Studio.
- if remainingTime < 0: ui.message(_("There is no track
playing."))
- else:
- # 7.0: Present remaining time in hh:mm:ss format for
enhanced experience (borrowed from the app module).
- remainingTime = (remainingTime/1000)+1
- if remainingTime == 0: ui.message("00:00")
- elif 1 <= remainingTime <= 59:
ui.message("00:{0}".format(str(remainingTime).zfill(2)))
- else:
- mm, ss = divmod(remainingTime, 60)
- if mm > 59:
- hh, mm = divmod(mm, 60)
- t0 = str(hh).zfill(2)
- t1 = str(mm).zfill(2)
- t2 = str(ss).zfill(2)
- ui.message(":".join([t0, t1, t2]))
- else:
- t1 = str(mm).zfill(2)
- t2 = str(ss).zfill(2)
- ui.message(":".join([t1, t2]))
- self.finish()
-
- def script_announceNumMonitoringEncoders(self, gesture):
- import encoders
- encoders.announceNumMonitoringEncoders()
- self.finish()
-
- def script_statusInfo(self, gesture):
- # For consistency reasons (because of the Studio status bar),
messages in this method will remain in English.
- statusInfo = []
- # 17.1: For Studio 5.10 and up, announce playback and
automation status.
- playingNow = winUser.sendMessage(SPLWin, SPLMSG, 0,
SPL_TrackPlaybackStatus)
- statusInfo.append("Play status: playing" if playingNow else
"Play status: stopped")
- # For automation, Studio 5.11 and earlier does not have an easy
way to detect this flag, thus resort to using playback status.
- if winUser.sendMessage(SPLWin, SPLMSG, 0, SPLVersion) < 520:
- statusInfo.append("Automation on" if playingNow == 2
else "Automation off")
- else:
- statusInfo.append("Automation on" if
winUser.sendMessage(SPLWin, SPLMSG, 1, SPLStatusInfo) else "Automation off")
- # 5.20 and later.
- statusInfo.append("Microphone on" if
winUser.sendMessage(SPLWin, SPLMSG, 2, SPLStatusInfo) else "Microphone off")
- statusInfo.append("Line-inon" if
winUser.sendMessage(SPLWin, SPLMSG, 3, SPLStatusInfo) else "Line-in off")
- statusInfo.append("Record to file on" if
winUser.sendMessage(SPLWin, SPLMSG, 4, SPLStatusInfo) else "Record to file off")
- cartEdit = winUser.sendMessage(SPLWin, SPLMSG, 5,
SPLStatusInfo)
- cartInsert = winUser.sendMessage(SPLWin, SPLMSG, 6,
SPLStatusInfo)
- if cartEdit: statusInfo.append("Cart edit on")
- elif not cartEdit and cartInsert:
statusInfo.append("Cart insert on")
- else: statusInfo.append("Cart edit off")
- ui.message("; ".join(statusInfo))
- self.finish()
- # Translators: Input help message for a SPL Controller command.
- script_statusInfo.__doc__ = _("Announces Studio status such as track
playback status from other programs")
-
- def script_conHelp(self, gesture):
- # Translators: The title for SPL Controller help dialog.
- wx.CallAfter(gui.messageBox, SPLConHelp, _("SPL Controller
help"))
- self.finish()
-
-
- __SPLControllerGestures={
- "kb:p":"play",
- "kb:a":"automateOn",
- "kb:shift+a":"automateOff",
- "kb:m":"micOn",
- "kb:shift+m":"micOff",
- "kb:n":"micNoFade",
- "kb:l":"lineInOn",
- "kb:shift+l":"lineInOff",
- "kb:shift+r":"libraryScanProgress",
- "kb:s":"stopFade",
- "kb:t":"stopInstant",
- "kb:u":"pause",
- "kb:r":"remainingTime",
- "kb:e":"announceNumMonitoringEncoders",
- "kb:i":"listenerCount",
- "kb:q":"statusInfo",
- "kb:f1":"conHelp"
- }
-
-
- __gestures={
- #"kb:nvda+shift+`":"focusToSPLWindow",
- #"kb:nvda+`":"SPLControllerPrefix"
- }
-
- # Support for Encoders
- # Each encoder is an overlay class, thus makes it easier to add
encoders in the future by implementing overlay objects.
- # Each encoder, at a minimum, must support connection monitoring
routines.
-
- def chooseNVDAObjectOverlayClasses(self, obj, clsList):
- if obj.appModule.appName in ("splengine", "splstreamer"):
- import encoders
- if obj.windowClassName == "TListView":
- clsList.insert(0, encoders.SAMEncoder)
- elif obj.windowClassName == "SysListView32":
- if obj.role == ROLE_LISTITEM:
- clsList.insert(0, encoders.SPLEncoder)
-
diff --git a/addon/globalPlugins/SPLStudioUtils/encoders.py
b/addon/globalPlugins/SPLStudioUtils/encoders.py
deleted file mode 100755
index b24eb40..0000000
--- a/addon/globalPlugins/SPLStudioUtils/encoders.py
+++ /dev/null
@@ -1,856 +0,0 @@
-# StationPlaylist encoders support
-# Author: Joseph Lee
-# Copyright 2015-2016, released under GPL.
-# Split from main global plugin in 2015.
-
-import threading
-import os
-import time
-import weakref
-from configobj import ConfigObj
-import api
-import ui
-import speech
-import globalVars
-import scriptHandler
-from NVDAObjects.IAccessible import IAccessible, getNVDAObjectFromEvent
-import winUser
-import winKernel
-import tones
-import gui
-import wx
-
-
-# SPL Studio uses WM messages to send and receive data, similar to Winamp (see
NVDA sources/appModules/winamp.py for more information).
-user32 = winUser.user32 # user32.dll.
-SPLWin = 0 # A handle to studio window.
-SPLMSG = winUser.WM_USER
-
-# Various SPL IPC tags.
-SPLPlay = 12
-SPL_TrackPlaybackStatus = 104
-
-# Needed in Encoder support:
-SPLFocusToStudio = set() # Whether to focus to Studio or not.
-SPLPlayAfterConnecting = set()
-SPLBackgroundMonitor = set()
-SPLNoConnectionTone = set()
-
-# Customized for each encoder type.
-SAMStreamLabels= {} # A dictionary to store custom labels for each stream.
-SPLStreamLabels= {} # Same as above but optimized for SPL encoders (Studio
5.00 and later).
-SAMMonitorThreads = {}
-SPLMonitorThreads = {}
-encoderMonCount = {"SAM":0, "SPL":0}
-
-# Configuration management.
-streamLabels = None
-
-# Load stream labels (and possibly other future goodies) from a file-based
database.
-def loadStreamLabels():
- global streamLabels, SAMStreamLabels, SPLStreamLabels,
SPLFocusToStudio, SPLPlayAfterConnecting, SPLBackgroundMonitor,
SPLNoConnectionTone
- streamLabels = ConfigObj(os.path.join(globalVars.appArgs.configPath,
"splStreamLabels.ini"))
- # Read stream labels.
- try:
- SAMStreamLabels = dict(streamLabels["SAMEncoders"])
- except KeyError:
- SAMStreamLabels = {}
- try:
- SPLStreamLabels = dict(streamLabels["SPLEncoders"])
- except KeyError:
- SPLStreamLabels = {}
- # Read other settings.
- if "FocusToStudio" in streamLabels:
- SPLFocusToStudio = set(streamLabels["FocusToStudio"])
- if "PlayAfterConnecting" in streamLabels:
- SPLPlayAfterConnecting =
set(streamLabels["PlayAfterConnecting"])
- if "BackgroundMonitor" in streamLabels:
- SPLBackgroundMonitor = set(streamLabels["BackgroundMonitor"])
- if "ConnectionTone" in streamLabels:
- SPLNoConnectionTone = set(streamLabels["NoConnectionTone"])
-
-# Report number of encoders being monitored.
-# 6.0: Refactor the below function to use the newer encoder config format.
-def getStreamLabel(identifier):
- encoderType, id = identifier.split()
- # 5.2: Use a static map.
- # 6.0: Look up the encoder type.
- if encoderType == "SAM": labels = SAMStreamLabels
- elif encoderType == "SPL": labels = SPLStreamLabels
- if id in labels: return labels[id]
- return None
-
-def announceNumMonitoringEncoders():
- monitorCount = len(SPLBackgroundMonitor)
- if not monitorCount:
- # Translators: Message presented when there are no encoders
being monitored.
- ui.message(_("No encoders are being monitored"))
- else:
- # Locate stream labels if any.
- labels = []
- for identifier in SPLBackgroundMonitor:
- label = getStreamLabel(identifier)
- if label is None: labels.append(identifier)
- else: labels.append("{encoderID}
({streamLabel})".format(encoderID = identifier, streamLabel=label))
- # Translators: Announces number of encoders being monitored in
the background.
- ui.message(_("Number of encoders monitored: {numberOfEncoders}:
{streamLabels}").format(numberOfEncoders = monitorCount, streamLabels=",
".join(labels)))
-
-# Remove encoder ID from various settings maps.
-# This is a private module level function in order for it to be invoked by
humans alone.
-_encoderConfigRemoved = None
-def _removeEncoderID(encoderType, pos):
- global _encoderConfigRemoved
- # For now, store the key to map.
- # This might become a module-level constant if other functions require
this dictionary.
- key2map = {"FocusToStudio":SPLFocusToStudio,
"PlayAfterConnecting":SPLPlayAfterConnecting,
"BackgroundMonitor":SPLBackgroundMonitor,
"NoConnectionTone":SPLNoConnectionTone}
- encoderID = " ".join([encoderType, pos])
- # Go through each feature map, remove the encoder ID and manipulate
encoder positions in these sets.
- # For each set, have a list of set items handy, otherwise set
cardinality error (RuntimeError) will occur if items are removed on the fly.
- for key in key2map:
- map = key2map[key]
- if encoderID in map:
- map.remove(encoderID)
- _encoderConfigRemoved = True
- # If not sorted, encoders will appear in random order (a
downside of using sets, as their ordering is quite unpredictable).
- currentEncoders = sorted(filter(lambda x:
x.startswith(encoderType), map))
- if len(currentEncoders) and encoderID < currentEncoders[-1]:
- # Same algorithm as stream label remover.
- start = 0
- if encoderID > currentEncoders[0]:
- for candidate in currentEncoders:
- if encoderID < candidate:
- start =
currentEncoders.index(candidate)
- # Do set entry manipulations (remove first, then add).
- for item in currentEncoders[start:]:
- map.remove(item)
- map.add(" ".join([encoderType,
"%s"%(int(item.split()[-1])-1)]))
- _encoderConfigRemoved = True
- if len(map): streamLabels[key] = list(map)
- else:
- try:
- del streamLabels[key]
- except KeyError:
- pass
-
-# Nullify various flag sets, otherwise memory leak occurs.
-def cleanup():
- global streamLabels, SAMStreamLabels, SPLStreamLabels,
SPLFocusToStudio, SPLPlayAfterConnecting, SPLBackgroundMonitor,
SPLNoConnectionTone, encoderMonCount, SAMMonitorThreads, SPLMonitorThreads
- for map in [streamLabels, SAMStreamLabels, SPLStreamLabels,
SPLFocusToStudio, SPLPlayAfterConnecting, SPLBackgroundMonitor,
SPLNoConnectionTone, SAMMonitorThreads, SPLMonitorThreads]:
- if map is not None: map.clear()
- # Nullify stream labels.
- streamLabels = None
- # Without resetting monitor count, we end up with higher and higher
value for this.
- # 7.0: Destroy threads also.
- encoderMonCount = {"SAM":0, "SPL":0}
-
-
-# Try to see if SPL foreground object can be fetched. This is used for
switching to SPL Studio window from anywhere and to switch to Studio window
from SAM encoder window.
-
-def fetchSPLForegroundWindow():
- # Turns out NVDA core does have a method to fetch desktop objects, so
use this to find SPL window from among its children.
- dt = api.getDesktopObject()
- fg = None
- fgCount = 0
- for possibleFG in dt.children:
- if "splstudio" in possibleFG.appModule.appModuleName:
- fg = possibleFG
- fgCount+=1
- # Just in case the window is really minimized (not to the system tray)
- if fgCount == 1:
- fg = getNVDAObjectFromEvent(user32.FindWindowA("TStudioForm",
None), winUser.OBJID_CLIENT, 0)
- return fg
-
-
-# Encoder configuration dialog.
-_configDialogOpened = False
-
-# Presented if the config dialog for another encoder is opened.
-def _configDialogError():
- # Translators: Text of the dialog when another alarm dialog is open.
- gui.messageBox(_("Another encoder settings dialog is
open."),_("Error"),style=wx.OK | wx.ICON_ERROR)
-
-class EncoderConfigDialog(wx.Dialog):
-
- # The following comes from exit dialog class from GUI package (credit:
NV Access and Zahari from Bulgaria).
- _instance = None
-
- def __new__(cls, parent, *args, **kwargs):
- # Make this a singleton and prompt an error dialog if it isn't.
- if _configDialogOpened:
- raise RuntimeError("An instance of encoder settings
dialog is opened")
- inst = cls._instance() if cls._instance else None
- if not inst:
- return super(cls, cls).__new__(cls, parent, *args,
**kwargs)
- return inst
-
- def __init__(self, parent, obj):
- inst = EncoderConfigDialog._instance() if
EncoderConfigDialog._instance else None
- if inst:
- return
- # Use a weakref so the instance can die.
- EncoderConfigDialog._instance = weakref.ref(self)
-
- self.obj = obj
- self.curStreamLabel, title = obj.getStreamLabel(getTitle=True)
- # Translators: The title of the encoder settings dialog
(example: Encoder settings for SAM 1").
- super(EncoderConfigDialog, self).__init__(parent, wx.ID_ANY,
_("Encoder settings for {name}").format(name = title))
- mainSizer = wx.BoxSizer(wx.VERTICAL)
- encoderConfigHelper = gui.guiHelper.BoxSizerHelper(self,
orientation=wx.VERTICAL)
-
- # Translators: An edit field in encoder settings to set stream
label for this encoder.
- self.streamLabel =
encoderConfigHelper.addLabeledControl(_("Stream &label"), wx.TextCtrl)
- self.streamLabel.SetValue(self.curStreamLabel if
self.curStreamLabel is not None else "")
-
- # Translators: A checkbox in encoder settings to set if NvDA
should switch focus to Studio window when connected.
- self.focusToStudio =
encoderConfigHelper.addItem(wx.CheckBox(self, label=_("&Focus to Studio when
connected")))
- self.focusToStudio.SetValue(obj.getEncoderId() in
SPLFocusToStudio)
- # Translators: A checkbox in encoder settings to set if NvDA
should play the next track when connected.
- self.playAfterConnecting =
encoderConfigHelper.addItem(wx.CheckBox(self, label=_("&Play first track when
connected")))
- self.playAfterConnecting.SetValue(obj.getEncoderId() in
SPLPlayAfterConnecting)
- # Translators: A checkbox in encoder settings to set if NvDA
should monitor the status of this encoder in the background.
- self.backgroundMonitor =
encoderConfigHelper.addItem(wx.CheckBox(self, label=_("Enable background
connection &monitoring")))
- self.backgroundMonitor.SetValue(obj.getEncoderId() in
SPLBackgroundMonitor)
- # Translators: A checkbox in encoder settings to set if NvDA
should play connection progress tone.
- self.noConnectionTone =
encoderConfigHelper.addItem(wx.CheckBox(self, label=_("Play connection status
&beep while connecting")))
- self.noConnectionTone.SetValue(obj.getEncoderId() not in
SPLNoConnectionTone)
-
-
encoderConfigHelper.addDialogDismissButtons(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.Add(encoderConfigHelper.sizer, border =
gui.guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL)
- mainSizer.Fit(self)
- self.SetSizer(mainSizer)
- self.Center(wx.BOTH | wx.CENTER_ON_SCREEN)
- self.streamLabel.SetFocus()
-
- def onOk(self, evt):
- self.obj._set_Flags(self.obj.getEncoderId(),
self.focusToStudio.Value, SPLFocusToStudio, "FocusToStudio", save=False)
- self.obj._set_Flags(self.obj.getEncoderId(),
self.playAfterConnecting.Value, SPLPlayAfterConnecting, "PlayAfterConnecting",
save=False)
- self.obj._set_Flags(self.obj.getEncoderId(),
self.backgroundMonitor.Value, SPLBackgroundMonitor, "BackgroundMonitor",
save=False)
- # Invert the following only.
- self.obj._set_Flags(self.obj.getEncoderId(), not
self.noConnectionTone.Value, SPLNoConnectionTone, "NoConnectionTone",
save=False)
- newStreamLabel = self.streamLabel.Value
- if newStreamLabel is None: newStreamLabel = ""
- if newStreamLabel == self.curStreamLabel:
- streamLabels.write() # Only flag(s) have changed.
- else: self.obj.setStreamLabel(newStreamLabel)
- self.Destroy()
-
- def onCancel(self, evt):
- self.Destroy()
-
-
-# Support for various encoders.
-# Each encoder must support connection routines.
-
-class Encoder(IAccessible):
- """Represents an encoder from within StationPlaylist Studio or Streamer.
- This base encoder provides scripts for all encoders such as stream
labeler and toggling focusing to Studio when connected.
- Subclasses must provide scripts to handle encoder connection and
connection announcement routines.
- In addition, they must implement the required actions to set options
such as focusing to Studio, storing stream labels and so on, as each subclass
relies on a feature map.
- For example, for SAM encoder class, the feature map is SAM* where *
denotes the feature in question.
- Lastly, each encoder class must provide a unique identifying string to
identify the type of the encoder (e.g. SAM for SAM encoder).
- """
-
- # Few useful variables for encoder list:
- focusToStudio = False # If true, Studio will gain focus after encoder
connects.
- playAfterConnecting = False # When connected, the first track will be
played.
- backgroundMonitor = False # Monitor this encoder for connection status
changes.
- connectionTone = True # Play connection tone while connecting.
-
-
- # Some helper functions
-
- # Get the encoder identifier.
- # This consists of two or three letter abbreviations for the encoder
and the child ID.
- def getEncoderId(self):
- return " ".join([self.encoderType,
str(self.IAccessibleChildID)])
-
- # Format the status message to prepare for monitoring multiple encoders.
- def encoderStatusMessage(self, message, id):
- if encoderMonCount[self.encoderType] > 1:
- # Translators: Status message for encoder monitoring.
- ui.message(_("{encoder} {encoderNumber}:
{status}").format(encoder = self.encoderType, encoderNumber = id, status =
message))
- else:
- ui.message(message)
-
- # A master flag setter.
- # Set or clear a given flag for the encoder given its ID, flag and flag
container (currently a feature set).
- # Also take in the flag key for storing it into the settings file.
- # The flag will then be written to the configuration file.
- # 7.0: Don't dump flags to disk unless told.
- def _set_Flags(self, encoderId, flag, flagMap, flagKey, save=True):
- if flag and not encoderId in flagMap:
- flagMap.add(encoderId)
- elif not flag and encoderId in flagMap:
- flagMap.remove(encoderId)
- # No need to store an empty flag map.
- if len(flagMap): streamLabels[flagKey] = list(flagMap)
- else:
- try:
- del streamLabels[flagKey]
- except KeyError:
- pass
- if save: streamLabels.write()
-
- # Now the flag configuration scripts.
- # Project Rainbow: a new way to configure these will be created.
-
- def script_toggleFocusToStudio(self, gesture):
- if not self.focusToStudio:
- self.focusToStudio = True
- # Translators: Presented when toggling the setting to
switch to Studio when connected to a streaming server.
- ui.message(_("Switch to Studio after connecting"))
- else:
- self.focusToStudio = False
- # Translators: Presented when toggling the setting to
switch to Studio when connected to a streaming server.
- ui.message(_("Do not switch to Studio after
connecting"))
- self._set_Flags(self.getEncoderId(), self.focusToStudio,
SPLFocusToStudio, "FocusToStudio")
- # Translators: Input help mode message in SAM Encoder window.
- script_toggleFocusToStudio.__doc__=_("Toggles whether NVDA will switch
to Studio when connected to a streaming server.")
-
- def script_togglePlay(self, gesture):
- if not self.playAfterConnecting:
- self.playAfterConnecting = True
- # Translators: Presented when toggling the setting to
play selected song when connected to a streaming server.
- ui.message(_("Play first track after connecting"))
- else:
- self.playAfterConnecting = False
- # Translators: Presented when toggling the setting to
switch to Studio when connected to a streaming server.
- ui.message(_("Do not play first track after
connecting"))
- self._set_Flags(self.getEncoderId(), self.playAfterConnecting,
SPLPlayAfterConnecting, "PlayAfterConnecting")
- # Translators: Input help mode message in SAM Encoder window.
- script_togglePlay.__doc__=_("Toggles whether Studio will play the first
song when connected to a streaming server.")
-
- def script_toggleBackgroundEncoderMonitor(self, gesture):
- if scriptHandler.getLastScriptRepeatCount()==0:
- if not self.backgroundMonitor:
- self.backgroundMonitor = True
- encoderMonCount[self.encoderType] += 1 #
Multiple encoders.
- # Translators: Presented when toggling the
setting to monitor the selected encoder.
- ui.message(_("Monitoring encoder
{encoderNumber}").format(encoderNumber = self.IAccessibleChildID))
- else:
- self.backgroundMonitor = False
- encoderMonCount[self.encoderType] -= 1
- # Translators: Presented when toggling the
setting to monitor the selected encoder.
- ui.message(_("Encoder {encoderNumber} will not
be monitored").format(encoderNumber = self.IAccessibleChildID))
- threadPool = self.setBackgroundMonitor()
- if self.backgroundMonitor:
- try:
- monitoring =
threadPool[self.IAccessibleChildID].isAlive()
- except KeyError:
- monitoring = False
- if not monitoring:
- statusThread =
threading.Thread(target=self.reportConnectionStatus)
- statusThread.name = "Connection Status
Reporter " + str(self.IAccessibleChildID)
- statusThread.start()
- threadPool[self.IAccessibleChildID] =
statusThread
- else:
- for encoderType in encoderMonCount:
- encoderMonCount[encoderType] = 0
- SPLBackgroundMonitor.clear()
- # Translators: Announced when background encoder
monitoring is canceled.
- ui.message(_("Encoder monitoring canceled"))
- # Translators: Input help mode message in SAM Encoder window.
- script_toggleBackgroundEncoderMonitor.__doc__=_("Toggles whether NVDA
will monitor the selected encoder in the background.")
-
- def script_streamLabeler(self, gesture):
- curStreamLabel, title = self.getStreamLabel(getTitle=True)
- if not curStreamLabel: curStreamLabel = ""
- # Translators: The title of the stream labeler dialog (example:
stream labeler for 1).
- streamTitle = _("Stream labeler for
{streamEntry}").format(streamEntry = title)
- # Translators: The text of the stream labeler dialog.
- streamText = _("Enter the label for this stream")
- dlg = wx.TextEntryDialog(gui.mainFrame,
- streamText, streamTitle, defaultValue=curStreamLabel)
- def callback(result):
- if result == wx.ID_OK:
- newStreamLabel = dlg.GetValue()
- if newStreamLabel == curStreamLabel:
- return # No need to write to disk.
- else: self.setStreamLabel(newStreamLabel)
- gui.runScriptModalDialog(dlg, callback)
- # Translators: Input help mode message in SAM Encoder window.
- script_streamLabeler.__doc__=_("Opens a dialog to label the selected
encoder.")
-
- def script_streamLabelEraser(self, gesture):
- # Translators: The title of the stream configuration eraser
dialog.
- streamEraserTitle = _("Stream label and settings eraser")
- # Translators: The text of the stream configuration eraser
dialog.
- streamEraserText = _("Enter the position of the encoder you
wish to delete or will delete")
- dlg = wx.NumberEntryDialog(gui.mainFrame,
- streamEraserText, "", streamEraserTitle,
self.IAccessibleChildID, 1, self.simpleParent.childCount)
- def callback(result):
- if result == wx.ID_OK:
- self.removeStreamConfig(str(dlg.GetValue()))
- gui.runScriptModalDialog(dlg, callback)
- # Translators: Input help mode message in SAM Encoder window.
- script_streamLabelEraser.__doc__=_("Opens a dialog to erase stream
labels and settings from an encoder that was deleted.")
-
- # stream settings.
- def script_encoderSettings(self, gesture):
- try:
- d = EncoderConfigDialog(gui.mainFrame, self)
- gui.mainFrame.prePopup()
- d.Raise()
- d.Show()
- gui.mainFrame.postPopup()
- except RuntimeError:
- wx.CallAfter(ui.message, "A settings dialog is opened")
- # Translators: Input help mode message for a command in Station
Playlist Studio.
- script_encoderSettings.__doc__=_("Shows encoder configuration dialog to
configure various encoder settings such as stream label.")
- script_encoderSettings.category=_("Station Playlist Studio")
-
- # Announce complete time including seconds (slight change from global
commands version).
- def script_encoderDateTime(self, gesture):
- if scriptHandler.getLastScriptRepeatCount()==0:
-
text=winKernel.GetTimeFormat(winKernel.LOCALE_USER_DEFAULT, 0, None, None)
- else:
-
text=winKernel.GetDateFormat(winKernel.LOCALE_USER_DEFAULT,
winKernel.DATE_LONGDATE, None, None)
- ui.message(text)
- # Translators: Input help mode message for report date and time command.
- script_encoderDateTime.__doc__=_("If pressed once, reports the current
time including seconds. If pressed twice, reports the current date")
- script_encoderDateTime.category=_("Station Playlist Studio")
-
- # Various column announcement scripts.
- # This base class implements encoder position and stream labels.
- def script_announceEncoderPosition(self, gesture):
- ui.message(_("Position: {pos}").format(pos =
self.IAccessibleChildID))
-
- def script_announceEncoderLabel(self, gesture):
- try:
- streamLabel = self.getStreamLabel()[0]
- except TypeError:
- streamLabel = None
- if streamLabel:
- ui.message(_("Label: {label}").format(label =
streamLabel))
- else:
- ui.message(_("No stream label"))
-
-
- def initOverlayClass(self):
- global encoderMonCount
- # Load stream labels upon request.
- if streamLabels is None: loadStreamLabels()
- encoderIdentifier = self.getEncoderId()
- # Can I switch to Studio when connected to a streaming server?
- try:
- self.focusToStudio = encoderIdentifier in
SPLFocusToStudio
- except KeyError:
- pass
- # Can I play tracks when connected?
- try:
- self.playAfterConnecting = encoderIdentifier in
SPLPlayAfterConnecting
- except KeyError:
- pass
- # Am I being monitored for connection changes?
- try:
- self.backgroundMonitor = encoderIdentifier in
SPLBackgroundMonitor
- except KeyError:
- pass
- # 6.2: Make sure background monitor threads are started if the
flag is set.
- if self.backgroundMonitor:
- if self.encoderType == "SAM": threadPool =
SAMMonitorThreads
- elif self.encoderType == "SPL": threadPool =
SPLMonitorThreads
- if self.IAccessibleChildID in threadPool:
- if not
threadPool[self.IAccessibleChildID].is_alive():
- del threadPool[self.IAccessibleChildID]
- # If it is indeed alive... Otherwise another
thread will be created to keep an eye on this encoder (undesirable).
- else: return
- statusThread =
threading.Thread(target=self.reportConnectionStatus)
- statusThread.name = "Connection Status Reporter " +
str(self.IAccessibleChildID)
- statusThread.start()
- threadPool[self.IAccessibleChildID] = statusThread
- encoderMonCount[self.encoderType] += 1
- # Can I play connection beeps?
- try:
- self.connectionTone = encoderIdentifier not in
SPLNoConnectionTone
- except KeyError:
- pass
-
- def reportFocus(self):
- try:
- streamLabel = self.getStreamLabel()[0]
- except TypeError:
- streamLabel = None
- # Announce stream label if it exists.
- if streamLabel is not None:
- try:
- self.name = "(" + streamLabel + ") " + self.name
- except TypeError:
- pass
- super(Encoder, self).reportFocus()
-
-
- __gestures={
- "kb:f11":"toggleFocusToStudio",
- "kb:shift+f11":"togglePlay",
- "kb:control+f11":"toggleBackgroundEncoderMonitor",
- "kb:f12":"streamLabeler",
- "kb:control+f12":"streamLabelEraser",
- "kb:NVDA+F12":"encoderDateTime",
- "kb:alt+NVDA+0":"encoderSettings",
- "kb:control+NVDA+1":"announceEncoderPosition",
- "kb:control+NVDA+2":"announceEncoderLabel",
- }
-
-
-class SAMEncoder(Encoder):
- # Support for Sam Encoders.
-
- encoderType = "SAM"
-
- def reportConnectionStatus(self, connecting=False):
- # Keep an eye on the stream's description field for connection
changes.
- # In order to not block NVDA commands, this will be done using
a different thread.
- SPLWin = user32.FindWindowA("SPLStudio", None)
- toneCounter = 0
- messageCache = ""
- # Status message flags.
- idle = False
- error = False
- encoding = False
- alreadyEncoding = False
- while True:
- time.sleep(0.001)
- try:
- if messageCache !=
self.description[self.description.find("Status")+8:]:
- messageCache =
self.description[self.description.find("Status")+8:]
- if not
messageCache.startswith("Encoding"):
-
self.encoderStatusMessage(messageCache, self.IAccessibleChildID)
- except AttributeError:
- return
- if messageCache.startswith("Idle"):
- if alreadyEncoding: alreadyEncoding = False
- if encoding: encoding = False
- if not idle:
- tones.beep(250, 250)
- idle = True
- toneCounter = 0
- elif messageCache.startswith("Error"):
- # Announce the description of the error.
- if connecting: connecting= False
- if not error:
- error = True
- toneCounter = 0
- if alreadyEncoding: alreadyEncoding = False
- elif messageCache.startswith("Encoding"):
- if connecting: connecting = False
- # We're on air, so exit unless told to monitor
for connection changes.
- if not encoding:
- tones.beep(1000, 150)
- self.encoderStatusMessage(messageCache,
self.IAccessibleChildID)
- if self.focusToStudio and not encoding:
- if api.getFocusObject().appModule ==
"splstudio":
- continue
- try:
-
fetchSPLForegroundWindow().setFocus()
- except AttributeError:
- pass
- if self.playAfterConnecting and not encoding:
- # Do not interupt the currently playing
track.
- if winUser.sendMessage(SPLWin, SPLMSG,
0, SPL_TrackPlaybackStatus) == 0:
- winUser.sendMessage(SPLWin,
SPLMSG, 0, SPLPlay)
- if not encoding: encoding = True
- else:
- if alreadyEncoding: alreadyEncoding = False
- if encoding: encoding = False
- elif "Error" not in self.description and error:
error = False
- toneCounter+=1
- if toneCounter%250 == 0 and self.connectionTone:
- tones.beep(500, 50)
- if connecting: continue
- if not " ".join([self.encoderType,
str(self.IAccessibleChildID)]) in SPLBackgroundMonitor: return
-
- def script_connect(self, gesture):
- gesture.send()
- # Translators: Presented when an Encoder is trying to connect
to a streaming server.
- ui.message(_("Connecting..."))
- # Oi, status thread, can you keep an eye on the connection
status for me?
- # To be packaged into a new function in 7.0.
- if not self.backgroundMonitor:
- statusThread =
threading.Thread(target=self.reportConnectionStatus,
kwargs=dict(connecting=True))
- statusThread.name = "Connection Status Reporter " +
str(self.IAccessibleChildID)
- statusThread.start()
- SAMMonitorThreads[self.IAccessibleChildID] =
statusThread
-
- def script_disconnect(self, gesture):
- gesture.send()
- # Translators: Presented when an Encoder is disconnecting from
a streaming server.
- ui.message(_("Disconnecting..."))
-
- # Connecting/disconnecting all encoders at once.
- # Control+F9/Control+F10 hotkeys are broken. Thankfully, context menu
retains these commands.
- # Use object navigation and key press emulation hack.
-
- def _samContextMenu(self, pos):
- def _samContextMenuActivate(pos):
- speech.cancelSpeech()
- focus =api.getFocusObject()
- focus.children[pos].doAction()
- import keyboardHandler
- contextMenu =
keyboardHandler.KeyboardInputGesture.fromName("applications")
- contextMenu.send()
- wx.CallLater(100, _samContextMenuActivate, pos)
- time.sleep(0.2)
-
- def script_connectAll(self, gesture):
- ui.message(_("Connecting..."))
- speechMode = speech.speechMode
- speech.speechMode = 0
- wx.CallAfter(self._samContextMenu, 7)
- # Oi, status thread, can you keep an eye on the connection
status for me?
- if not self.backgroundMonitor:
- statusThread =
threading.Thread(target=self.reportConnectionStatus,
kwargs=dict(connecting=True))
- statusThread.name = "Connection Status Reporter " +
str(self.IAccessibleChildID)
- statusThread.start()
- SAMMonitorThreads[self.IAccessibleChildID] =
statusThread
- speech.speechMode = speechMode
-
- def script_disconnectAll(self, gesture):
- ui.message(_("Disconnecting..."))
- speechMode = speech.speechMode
- speech.speechMode = 0
- wx.CallAfter(self._samContextMenu, 8)
- time.sleep(0.5)
- speech.speechMode = speechMode
- speech.cancelSpeech()
-
-
- # Announce SAM columns: encoder name/type, status and description.
- def script_announceEncoderFormat(self, gesture):
- typeIndex = self.description.find(", Status: ")
- ui.message(self.description[:typeIndex])
-
- def script_announceEncoderStatus(self, gesture):
- typeIndex = self.description.find(", Status: ")
- statusIndex = self.description.find(", Description: ")
- ui.message(self.description[typeIndex+2:statusIndex])
-
- def script_announceEncoderStatusDesc(self, gesture):
- statusIndex = self.description.find(", Description: ")
- ui.message(self.description[statusIndex+2:])
-
-
- def setBackgroundMonitor(self):
- self._set_Flags(self.getEncoderId(), self.backgroundMonitor,
SPLBackgroundMonitor, "BackgroundMonitor")
- return SAMMonitorThreads
-
-
- def getStreamLabel(self, getTitle=False):
- if str(self.IAccessibleChildID) in SAMStreamLabels:
- streamLabel =
SAMStreamLabels[str(self.IAccessibleChildID)]
- return streamLabel, self.IAccessibleChildID if getTitle
else streamLabel
- return None, self.IAccessibleChildID if getTitle else None
-
- def setStreamLabel(self, newStreamLabel):
- if len(newStreamLabel):
- SAMStreamLabels[str(self.IAccessibleChildID)] =
newStreamLabel
- else:
- try:
- del
SAMStreamLabels[str(self.IAccessibleChildID)]
- except KeyError:
- pass
- streamLabels["SAMEncoders"] = SAMStreamLabels
- streamLabels.write()
-
- def removeStreamConfig(self, pos):
- # An application of map successor algorithm.
- global _encoderConfigRemoved
- # Manipulate SAM encoder settings and labels.
- _removeEncoderID("SAM", pos)
- labelLength = len(SAMStreamLabels)
- if not labelLength or pos > max(SAMStreamLabels.keys()):
- if _encoderConfigRemoved is not None:
- streamLabels.write()
- _encoderConfigRemoved = None
- return
- elif labelLength == 1:
- if not pos in SAMStreamLabels:
- pos = SAMStreamLabels.keys()[0]
- oldPosition = int(pos)
- SAMStreamLabels[str(oldPosition-1)] =
SAMStreamLabels[pos]
- del SAMStreamLabels[pos]
- else:
- encoderPositions = sorted(SAMStreamLabels.keys())
- # What if the position happens to be the last stream
label position?
- if pos == max(encoderPositions): del
SAMStreamLabels[pos]
- else:
- # Find the exact or closest successor.
- startPosition = 0
- if pos == min(encoderPositions):
- del SAMStreamLabels[pos]
- startPosition = 1
- elif pos > min(encoderPositions):
- for candidate in encoderPositions:
- if candidate >= pos:
- startPositionCandidate
= encoderPositions.index(candidate)
- startPosition =
startPositionCandidate+1 if candidate == pos else startPositionCandidate
- break
- # Now move them forward.
- for position in
encoderPositions[startPosition:]:
- oldPosition = int(position)
- SAMStreamLabels[str(oldPosition-1)] =
SAMStreamLabels[position]
- del SAMStreamLabels[position]
- streamLabels["SAMEncoders"] = SAMStreamLabels
- streamLabels.write()
-
-
- __gestures={
- "kb:f9":"connect",
- "kb:control+f9":"connectAll",
- "kb:f10":"disconnect",
- "kb:control+f10":"disconnectAll",
- "kb:control+NVDA+3":"announceEncoderFormat",
- "kb:control+NVDA+4":"announceEncoderStatus",
- "kb:control+NVDA+5":"announceEncoderStatusDesc"
- }
-
-
-class SPLEncoder(Encoder):
- # Support for SPL Encoder window.
-
- encoderType = "SPL"
-
- def reportConnectionStatus(self, connecting=False):
- # Same routine as SAM encoder: use a thread to prevent blocking
NVDA commands.
- SPLWin = user32.FindWindowA("SPLStudio", None)
- attempt = 0
- messageCache = ""
- # Status flags.
- connected = False
- while True:
- time.sleep(0.001)
- try:
- # An inner try block is required because
statChild may say the base class is gone.
- try:
- statChild = self.children[1]
- except NotImplementedError:
- return # Only seen when the encoder
dies.
- except IndexError:
- return # Don't leave zombie objects around.
- if messageCache != statChild.name:
- messageCache = statChild.name
- if not messageCache: return
- if "Kbps" not in messageCache:
- self.encoderStatusMessage(messageCache,
self.IAccessibleChildID)
- if messageCache == "Disconnected":
- connected = False
- if connecting: continue
- elif messageCache == "Connected":
- connecting = False
- # We're on air, so exit.
- if not connected: tones.beep(1000, 150)
- if self.focusToStudio and not connected:
- try:
-
fetchSPLForegroundWindow().setFocus()
- except AttributeError:
- pass
- if self.playAfterConnecting and not connected:
- if winUser.sendMessage(SPLWin, SPLMSG,
0, SPL_TrackPlaybackStatus) == 0:
- winUser.sendMessage(SPLWin,
SPLMSG, 0, SPLPlay)
- if not connected: connected = True
- elif "Unable to connect" in messageCache or "Failed" in
messageCache or statChild.name == "AutoConnect stopped.":
- if connected: connected = False
- else:
- if connected: connected = False
- if not "Kbps" in messageCache:
- attempt += 1
- if attempt%250 == 0 and
self.connectionTone:
- tones.beep(500, 50)
- if attempt>= 500 and
statChild.name == "Disconnected":
- tones.beep(250, 250)
- if connecting: continue
- if not " ".join([self.encoderType,
str(self.IAccessibleChildID)]) in SPLBackgroundMonitor: return
-
- def script_connect(self, gesture):
- # Same as SAM's connection routine, but this time, keep an eye
on self.name and a different connection flag.
- connectButton = api.getForegroundObject().children[2]
- if connectButton.name == "Disconnect": return
- ui.message(_("Connecting..."))
- # Juggle the focus around.
- connectButton.doAction()
- self.setFocus()
- # Same as SAM encoders.
- if not self.backgroundMonitor:
- statusThread =
threading.Thread(target=self.reportConnectionStatus,
kwargs=dict(connecting=True))
- statusThread.name = "Connection Status Reporter"
- statusThread.start()
- SPLMonitorThreads[self.IAccessibleChildID] =
statusThread
- script_connect.__doc__=_("Connects to a streaming server.")
-
- # Announce SPL Encoder columns: encoder settings and transfer rate.
- def script_announceEncoderSettings(self, gesture):
- ui.message(_("Encoder Settings: {setting}").format(setting =
self.children[0].name))
-
- def script_announceEncoderTransfer(self, gesture):
- ui.message(_("Transfer Rate:
{transferRate}").format(transferRate = self.children[1].name))
-
- def setBackgroundMonitor(self):
- self._set_Flags(self.getEncoderId(), self.backgroundMonitor,
SPLBackgroundMonitor, "BackgroundMonitor")
- return SPLMonitorThreads
-
- def getStreamLabel(self, getTitle=False):
- if str(self.IAccessibleChildID) in SPLStreamLabels:
- streamLabel =
SPLStreamLabels[str(self.IAccessibleChildID)]
- return streamLabel, self.firstChild.name if getTitle
else streamLabel
- return (None, self.firstChild.name) if getTitle else None
-
- def setStreamLabel(self, newStreamLabel):
- if len(newStreamLabel):
- SPLStreamLabels[str(self.IAccessibleChildID)] =
newStreamLabel
- else:
- try:
- del
SPLStreamLabels[str(self.IAccessibleChildID)]
- except KeyError:
- pass
- streamLabels["SPLEncoders"] = SPLStreamLabels
- streamLabels.write()
-
- def removeStreamConfig(self, pos):
- global _encoderConfigRemoved
- # This time, manipulate SPL ID entries.
- _removeEncoderID("SPL", pos)
- labelLength = len(SPLStreamLabels)
- if not labelLength or pos > max(SPLStreamLabels.keys()):
- if _encoderConfigRemoved is not None:
- streamLabels.write()
- _encoderConfigRemoved = None
- return
- elif labelLength == 1:
- if not pos in SPLStreamLabels:
- pos = SPLStreamLabels.keys()[0]
- oldPosition = int(pos)
- SPLStreamLabels[str(oldPosition-1)] =
SPLStreamLabels[pos]
- del SPLStreamLabels[pos]
- else:
- encoderPositions = sorted(SPLStreamLabels.keys())
- if pos == max(encoderPositions): del
SPLStreamLabels[pos]
- else:
- # Find the exact or closest successor.
- startPosition = 0
- if pos == min(encoderPositions):
- del SPLStreamLabels[pos]
- startPosition = 1
- elif pos > min(encoderPositions):
- for candidate in encoderPositions:
- if candidate >= pos:
- startPositionCandidate
= encoderPositions.index(candidate)
- startPosition =
startPositionCandidate+1 if candidate == pos else startPositionCandidate
- break
- # Now move them forward.
- for position in
encoderPositions[startPosition:]:
- oldPosition = int(position)
- SPLStreamLabels[str(oldPosition-1)] =
SPLStreamLabels[position]
- del SPLStreamLabels[position]
- streamLabels["SPLEncoders"] = SPLStreamLabels
- streamLabels.write()
-
-
- __gestures={
- "kb:f9":"connect",
- "kb:control+NVDA+3":"announceEncoderSettings",
- "kb:control+NVDA+4":"announceEncoderTransfer"
- }
-
-
This diff is so big that we needed to truncate the remainder.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/e187589de342/
Changeset: e187589de342
Branch: None
User: josephsl
Date: 2016-12-30 01:31:30+00:00
Summary: Playlist snapshots (17.1-dev): Document playlist snapshots command
(F8 for now).
Affected #: 1 file
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index f076683..541f269 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -405,6 +405,7 @@ U: Studio up time.
W: Weather and temperature.
Y: Playlist modification.
1 through 0: Announce columns via Columns Explorer (0 is tenth column slot).
+F8: Take playlist snapshots such as track count, longest track and so on.
F9: Mark current track as start of track time analysis.
F10: Perform track time analysis.
F12: Switch to an instant switch profile.
@@ -437,6 +438,7 @@ U: Studio up time.
W: Weather and temperature.
Y: Playlist modification.
1 through 0: Announce columns via Columns Explorer (0 is tenth column slot).
+F8: Take playlist snapshots such as track count, longest track and so on.
F9: Mark current track as start of track time analysis.
F10: Perform track time analysis.
F12: Switch to an instant switch profile.
@@ -471,6 +473,7 @@ U: Studio up time.
W: Weather and temperature.
Y: Playlist modification.
1 through 0: Announce columns via Columns Explorer (0 is tenth column slot).
+F8: Take playlist snapshots such as track count, longest track and so on.
F9: Mark current track as start of track time analysis.
F10: Perform track time analysis.
F12: Switch to an instant switch profile.
@@ -1990,6 +1993,7 @@ class AppModule(appModuleHandler.AppModule):
"kb:shift+s":"sayScheduledToPlay",
"kb:shift+p":"sayTrackPitch",
"kb:shift+r":"libraryScanMonitor",
+ "kb:f8":"takePlaylistSnapshots",
"kb:f9":"markTrackForAnalysis",
"kb:f10":"trackTimeAnalysis",
"kb:f12":"switchProfiles",
@@ -2024,6 +2028,7 @@ class AppModule(appModuleHandler.AppModule):
"kb:shift+s":"sayScheduledToPlay",
"kb:shift+p":"sayTrackPitch",
"kb:shift+r":"libraryScanMonitor",
+ "kb:f8":"takePlaylistSnapshots",
"kb:f9":"markTrackForAnalysis",
"kb:f10":"trackTimeAnalysis",
"kb:f12":"switchProfiles",
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/f39a725ba771/
Changeset: f39a725ba771
Branch: None
User: josephsl
Date: 2017-01-01 17:51:52+00:00
Summary: Merge branch 'master' into plSnaps
Affected #: 3 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 541f269..0ce67b3 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -777,13 +777,14 @@ class AppModule(appModuleHandler.AppModule):
def doExtraAction(self, status):
# Be sure to only deal with cart mode changes if Cart Explorer
is on.
# Optimization: Return early if the below condition is true.
- if self.cartExplorer and status.startswith("Cart"):
+ if self.cartExplorer and status.startswith("Cart") and
status.endswith((" On", " Off")):
# 17.01: The best way to detect Cart Edit off is
consulting file modification time.
# Automatically reload cart information if this is the
case.
- studioTitle = api.getForegroundObject().name
- if splmisc.shouldCartExplorerRefresh(studioTitle):
- self.carts =
splmisc.cartExplorerInit(studioTitle)
- # Translators: Presented when cart edit mode is toggled
on while cart explorer is on.
+ if status in ("Cart Edit Off", "Cart Insert On"):
+ studioTitle = api.getForegroundObject().name
+ if
splmisc.shouldCartExplorerRefresh(studioTitle):
+ self.carts =
splmisc.cartExplorerInit(studioTitle)
+ # Translators: Presented when cart modes are toggled
while cart explorer is on.
ui.message(_("Cart explorer is active"))
return
# Microphone alarm and alarm interval if defined.
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index a5b448e..30072e9 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -872,17 +872,18 @@ class AudioDuckingReminder(wx.Dialog):
mainSizer = wx.BoxSizer(wx.VERTICAL)
# Translators: A message displayed if audio ducking should be
disabled.
- label = wx.StaticText(self, wx.ID_ANY, label=_("NVDA 2016.1 and
later allows NVDA to decrease volume of background audio including that of
Studio. In order to not disrupt the listening experience of your listeners,
please disable audio ducking by opening synthesizer dialog in NVDA and
selecting 'no ducking' from audio ducking mode combo box or press
NVDA+Shift+D."))
+ label = wx.StaticText(self, wx.ID_ANY, label=_("""NVDA 2016.1
and later allows NVDA to decrease volume of background audio including that of
Studio.
+ In order to not disrupt the listening experience of your
listeners, please disable audio ducking either by:
+ * Opening synthesizer dialog in NVDA and selecting 'no ducking'
from audio ducking mode combo box.
+ * Press NVDA+Shift+D to set it to 'no ducking'."""))
mainSizer.Add(label,border=20,flag=wx.LEFT|wx.RIGHT|wx.TOP)
- sizer = wx.BoxSizer(wx.HORIZONTAL)
# Translators: A checkbox to turn off audio ducking reminder
message.
self.audioDuckingReminder=wx.CheckBox(self,wx.NewId(),label=_("Do not show this
message again"))
self.audioDuckingReminder.SetValue(not
SPLConfig["Startup"]["AudioDuckingReminder"])
- sizer.Add(self.audioDuckingReminder, border=10,flag=wx.TOP)
- mainSizer.Add(sizer, border=10, flag=wx.BOTTOM)
+ mainSizer.Add(self.audioDuckingReminder, border=10,flag=wx.TOP)
- mainSizer.Add(self.CreateButtonSizer(wx.OK))
+ mainSizer.Add(self.CreateButtonSizer(wx.OK),
flag=wx.ALIGN_CENTER_HORIZONTAL)
self.Bind(wx.EVT_BUTTON, self.onOk, id=wx.ID_OK)
mainSizer.Fit(self)
self.Sizer = mainSizer
@@ -928,14 +929,12 @@ Thank you.""")
label = wx.StaticText(self, wx.ID_ANY,
label=self.welcomeMessage)
mainSizer.Add(label,border=20,flag=wx.LEFT|wx.RIGHT|wx.TOP)
- sizer = wx.BoxSizer(wx.HORIZONTAL)
# Translators: A checkbox to show welcome dialog.
self.showWelcomeDialog=wx.CheckBox(self,wx.NewId(),label=_("Show welcome dialog
when I start Studio"))
self.showWelcomeDialog.SetValue(SPLConfig["Startup"]["WelcomeDialog"])
- sizer.Add(self.showWelcomeDialog, border=10,flag=wx.TOP)
- mainSizer.Add(sizer, border=10, flag=wx.BOTTOM)
+ mainSizer.Add(self.showWelcomeDialog, border=10,flag=wx.TOP)
- mainSizer.Add(self.CreateButtonSizer(wx.OK))
+ mainSizer.Add(self.CreateButtonSizer(wx.OK),
flag=wx.ALIGN_CENTER_HORIZONTAL)
self.Bind(wx.EVT_BUTTON, self.onOk, id=wx.ID_OK)
mainSizer.Fit(self)
self.Sizer = mainSizer
diff --git a/readme.md b/readme.md
index abc6f63..0f0378f 100755
--- a/readme.md
+++ b/readme.md
@@ -181,6 +181,12 @@ If you are using Studio on a touchscreen computer running
Windows 8 or later and
* Initial support for StationPlaylist Creator.
* Added a new command in SPL Controller layer to announce Studio status such
as track playback and microphone status (Q).
+## Version 17.01/15.5-LTS
+
+* Improved responsiveness and reliability when using the add-on to switch to
Studio, either using focus to Studio command from other programs or when an
encoder is connected and NVDA is told to switch to Studio when this happens. If
Studio is minimized, Studio window will be shown as unavailable. If so, restore
Studio window from system tray.
+* If editing carts while Cart Explorer is active, it is no longer necessary to
reenter Cart Explorer to view updated cart assignments when Cart Edit mode is
turned off. Consequently, Cart Explorer reentry message is no longer announced.
+* In add-on 15.5-LTS, corrected user interface presentation for SPL add-on
settings dialog.
+
## Version 16.12.1
* Corrected user interface presentation for SPL add-on settings dialog.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/893780951c52/
Changeset: 893780951c52
Branch: None
User: josephsl
Date: 2017-01-04 19:30:33+00:00
Summary: Correctly remove deprecated keys.
Logic error found: only the deprecated sections were checked instead of
checking the 'real' thing (the Config Hub), which caused a bug where deprecated
keys reminaed in the config file (now fixed).
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 30072e9..5fc24e8 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -112,7 +112,7 @@ class ConfigHub(ChainMap):
# This action must be performed after caching, otherwise the
newly modified profile will not be saved.
deprecatedKeys = {"General":"TrackDial", "Startup":"Studio500"}
for section, key in deprecatedKeys.iteritems():
- if key in section: del self.maps[0][section][key]
+ if key in self.maps[0][section]: del
self.maps[0][section][key]
# Moving onto broadcast profiles if any.
try:
profiles = filter(lambda fn: os.path.splitext(fn)[-1]
== ".ini", os.listdir(SPLProfiles))
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/9969b2eb416e/
Changeset: 9969b2eb416e
Branch: None
User: josephsl
Date: 2017-01-04 20:05:55+00:00
Summary: SPLDefaults7 is now SPLDefaults
Affected #: 4 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 7de90c2..dbab6bb 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -365,7 +365,7 @@ class SPL510TrackItem(SPLTrackItem):
# Studio 5.10 version of original index finder.
def _origIndexOf(self, columnHeader):
- return
splconfig._SPLDefaults7["ColumnAnnouncement"]["ColumnOrder"].index(columnHeader)+1
+ return
splconfig._SPLDefaults["ColumnAnnouncement"]["ColumnOrder"].index(columnHeader)+1
# Handle track dial for SPL 5.10.
def _leftmostcol(self):
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 5fc24e8..444d4ff 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -180,7 +180,7 @@ class ConfigHub(ChainMap):
# Case 1: restore settings to defaults when 5.x
config validation has failed on all values.
# 6.0: In case this is a user profile, apply
base configuration.
# 8.0: Call copy profile function directly to
reduce overhead.
- copyProfile(_SPLDefaults7, SPLConfigCheckpoint,
complete=SPLConfigCheckpoint.filename == SPLIni)
+ copyProfile(_SPLDefaults, SPLConfigCheckpoint,
complete=SPLConfigCheckpoint.filename == SPLIni)
_configLoadStatus[profileName] = "completeReset"
elif isinstance(configTest, dict):
# Case 2: For 5.x and later, attempt to
reconstruct the failed values.
@@ -190,7 +190,7 @@ class ConfigHub(ChainMap):
if isinstance(configTest[setting],
dict):
for failedKey in
configTest[setting].keys():
# 7.0 optimization:
just reload from defaults dictionary, as broadcast profiles contain
profile-specific settings only.
-
SPLConfigCheckpoint[setting][failedKey] = _SPLDefaults7[setting][failedKey]
+
SPLConfigCheckpoint[setting][failedKey] = _SPLDefaults[setting][failedKey]
# 7.0: Disqualified from being cached this time.
SPLConfigCheckpoint.write()
_configLoadStatus[profileName] = "partialReset"
@@ -301,9 +301,9 @@ class ConfigHub(ChainMap):
profilePath = conf.filename
conf.reset()
conf.filename = profilePath
- resetConfig(_SPLDefaults7, conf)
+ resetConfig(_SPLDefaults, conf)
# Convert certain settings to a different format.
- conf["ColumnAnnouncement"]["IncludedColumns"] =
set(_SPLDefaults7["ColumnAnnouncement"]["IncludedColumns"])
+ conf["ColumnAnnouncement"]["IncludedColumns"] =
set(_SPLDefaults["ColumnAnnouncement"]["IncludedColumns"])
# Switch back to normal profile via a custom variant of swap
routine.
if self.profiles[0].name != _("Normal profile"):
npIndex = self.profileIndexByName(_("Normal profile"))
@@ -364,9 +364,9 @@ class ConfigHub(ChainMap):
# Default config spec container.
# To be moved to a different place in 8.0.
-_SPLDefaults7 = ConfigObj(None, configspec = confspec7, encoding="UTF-8")
+_SPLDefaults = ConfigObj(None, configspec = confspec7, encoding="UTF-8")
_val = Validator()
-_SPLDefaults7.validate(_val, copy=True)
+_SPLDefaults.validate(_val, copy=True)
# Display an error dialog when configuration validation fails.
def runConfigErrorDialog(errorText, errorType):
@@ -458,7 +458,7 @@ def _extraInitSteps(conf, profileName=None):
global _configLoadStatus
columnOrder = conf["ColumnAnnouncement"]["ColumnOrder"]
# Catch suttle errors.
- fields = _SPLDefaults7["ColumnAnnouncement"]["ColumnOrder"]
+ fields = _SPLDefaults["ColumnAnnouncement"]["ColumnOrder"]
invalidFields = 0
for field in fields:
if field not in columnOrder: invalidFields+=1
@@ -706,7 +706,7 @@ def _preSave(conf):
for setting in conf.keys():
for key in conf[setting].keys():
try:
- if conf[setting][key] ==
_SPLDefaults7[setting][key]:
+ if conf[setting][key] ==
_SPLDefaults[setting][key]:
del conf[setting][key]
except KeyError:
pass
@@ -851,7 +851,7 @@ def updateInit():
# To be renamed and used in other places in 7.0.
def _shouldBuildDescriptionPieces():
return (not SPLConfig["ColumnAnnouncement"]["UseScreenColumnOrder"]
- and (SPLConfig["ColumnAnnouncement"]["ColumnOrder"] !=
_SPLDefaults7["ColumnAnnouncement"]["ColumnOrder"]
+ and (SPLConfig["ColumnAnnouncement"]["ColumnOrder"] !=
_SPLDefaults["ColumnAnnouncement"]["ColumnOrder"]
or len(SPLConfig["ColumnAnnouncement"]["IncludedColumns"]) != 17))
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index 743aab1..6bbd046 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -153,7 +153,7 @@ class SPLConfigDialog(gui.SettingsDialog):
# Translators: The label for a setting in SPL add-on dialog to
set vertical column.
verticalColLabel = _("&Vertical column navigation
announcement:")
# Translators: One of the options for vertical column
navigation denoting NVDA will announce current column positoin (e.g. second
column position from the left).
- self.verticalColumnsList =
SPLConfigHelper.addLabeledControl(verticalColLabel, wx.Choice,
choices=[_("whichever column I am reviewing"), "Status"] +
splconfig._SPLDefaults7["ColumnAnnouncement"]["ColumnOrder"])
+ self.verticalColumnsList =
SPLConfigHelper.addLabeledControl(verticalColLabel, wx.Choice,
choices=[_("whichever column I am reviewing"), "Status"] +
splconfig._SPLDefaults["ColumnAnnouncement"]["ColumnOrder"])
verticalColumn =
splconfig.SPLConfig["General"]["VerticalColumnAnnounce"]
selection = self.verticalColumnsList.FindString(verticalColumn)
if verticalColumn is not None else 0
try:
@@ -1087,7 +1087,7 @@ class ColumnsExplorerDialog(wx.Dialog):
if not tt:
# Translators: The title of Columns Explorer
configuration dialog.
actualTitle = _("Columns Explorer")
- cols =
splconfig._SPLDefaults7["ColumnAnnouncement"]["ColumnOrder"]
+ cols =
splconfig._SPLDefaults["ColumnAnnouncement"]["ColumnOrder"]
else:
# Translators: The title of Columns Explorer
configuration dialog.
actualTitle = _("Columns Explorer for Track Tool")
diff --git a/addon/appModules/splstudio/splmisc.py
b/addon/appModules/splstudio/splmisc.py
index e3355bc..b4123b4 100755
--- a/addon/appModules/splstudio/splmisc.py
+++ b/addon/appModules/splstudio/splmisc.py
@@ -96,7 +96,7 @@ class SPLFindDialog(wx.Dialog):
columnSizer = wx.BoxSizer(wx.HORIZONTAL)
# Translators: The label in track finder to search
columns.
label = wx.StaticText(self, wx.ID_ANY, label=_("C&olumn
to search:"))
- self.columnHeaders = wx.Choice(self, wx.ID_ANY,
choices=splconfig._SPLDefaults7["ColumnAnnouncement"]["ColumnOrder"])
+ self.columnHeaders = wx.Choice(self, wx.ID_ANY,
choices=splconfig._SPLDefaults["ColumnAnnouncement"]["ColumnOrder"])
self.columnHeaders.SetSelection(0)
columnSizer.Add(label)
columnSizer.Add(self.columnHeaders)
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/8f7c87a4d3d0/
Changeset: 8f7c87a4d3d0
Branch: None
User: josephsl
Date: 2017-01-04 20:06:51+00:00
Summary: Merge branch 'master' into plSnaps
Affected #: 4 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 0ce67b3..3725fa1 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -365,7 +365,7 @@ class SPL510TrackItem(SPLTrackItem):
# Studio 5.10 version of original index finder.
def _origIndexOf(self, columnHeader):
- return
splconfig._SPLDefaults7["ColumnAnnouncement"]["ColumnOrder"].index(columnHeader)+1
+ return
splconfig._SPLDefaults["ColumnAnnouncement"]["ColumnOrder"].index(columnHeader)+1
# Handle track dial for SPL 5.10.
def _leftmostcol(self):
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 30072e9..444d4ff 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -112,7 +112,7 @@ class ConfigHub(ChainMap):
# This action must be performed after caching, otherwise the
newly modified profile will not be saved.
deprecatedKeys = {"General":"TrackDial", "Startup":"Studio500"}
for section, key in deprecatedKeys.iteritems():
- if key in section: del self.maps[0][section][key]
+ if key in self.maps[0][section]: del
self.maps[0][section][key]
# Moving onto broadcast profiles if any.
try:
profiles = filter(lambda fn: os.path.splitext(fn)[-1]
== ".ini", os.listdir(SPLProfiles))
@@ -180,7 +180,7 @@ class ConfigHub(ChainMap):
# Case 1: restore settings to defaults when 5.x
config validation has failed on all values.
# 6.0: In case this is a user profile, apply
base configuration.
# 8.0: Call copy profile function directly to
reduce overhead.
- copyProfile(_SPLDefaults7, SPLConfigCheckpoint,
complete=SPLConfigCheckpoint.filename == SPLIni)
+ copyProfile(_SPLDefaults, SPLConfigCheckpoint,
complete=SPLConfigCheckpoint.filename == SPLIni)
_configLoadStatus[profileName] = "completeReset"
elif isinstance(configTest, dict):
# Case 2: For 5.x and later, attempt to
reconstruct the failed values.
@@ -190,7 +190,7 @@ class ConfigHub(ChainMap):
if isinstance(configTest[setting],
dict):
for failedKey in
configTest[setting].keys():
# 7.0 optimization:
just reload from defaults dictionary, as broadcast profiles contain
profile-specific settings only.
-
SPLConfigCheckpoint[setting][failedKey] = _SPLDefaults7[setting][failedKey]
+
SPLConfigCheckpoint[setting][failedKey] = _SPLDefaults[setting][failedKey]
# 7.0: Disqualified from being cached this time.
SPLConfigCheckpoint.write()
_configLoadStatus[profileName] = "partialReset"
@@ -301,9 +301,9 @@ class ConfigHub(ChainMap):
profilePath = conf.filename
conf.reset()
conf.filename = profilePath
- resetConfig(_SPLDefaults7, conf)
+ resetConfig(_SPLDefaults, conf)
# Convert certain settings to a different format.
- conf["ColumnAnnouncement"]["IncludedColumns"] =
set(_SPLDefaults7["ColumnAnnouncement"]["IncludedColumns"])
+ conf["ColumnAnnouncement"]["IncludedColumns"] =
set(_SPLDefaults["ColumnAnnouncement"]["IncludedColumns"])
# Switch back to normal profile via a custom variant of swap
routine.
if self.profiles[0].name != _("Normal profile"):
npIndex = self.profileIndexByName(_("Normal profile"))
@@ -364,9 +364,9 @@ class ConfigHub(ChainMap):
# Default config spec container.
# To be moved to a different place in 8.0.
-_SPLDefaults7 = ConfigObj(None, configspec = confspec7, encoding="UTF-8")
+_SPLDefaults = ConfigObj(None, configspec = confspec7, encoding="UTF-8")
_val = Validator()
-_SPLDefaults7.validate(_val, copy=True)
+_SPLDefaults.validate(_val, copy=True)
# Display an error dialog when configuration validation fails.
def runConfigErrorDialog(errorText, errorType):
@@ -458,7 +458,7 @@ def _extraInitSteps(conf, profileName=None):
global _configLoadStatus
columnOrder = conf["ColumnAnnouncement"]["ColumnOrder"]
# Catch suttle errors.
- fields = _SPLDefaults7["ColumnAnnouncement"]["ColumnOrder"]
+ fields = _SPLDefaults["ColumnAnnouncement"]["ColumnOrder"]
invalidFields = 0
for field in fields:
if field not in columnOrder: invalidFields+=1
@@ -706,7 +706,7 @@ def _preSave(conf):
for setting in conf.keys():
for key in conf[setting].keys():
try:
- if conf[setting][key] ==
_SPLDefaults7[setting][key]:
+ if conf[setting][key] ==
_SPLDefaults[setting][key]:
del conf[setting][key]
except KeyError:
pass
@@ -851,7 +851,7 @@ def updateInit():
# To be renamed and used in other places in 7.0.
def _shouldBuildDescriptionPieces():
return (not SPLConfig["ColumnAnnouncement"]["UseScreenColumnOrder"]
- and (SPLConfig["ColumnAnnouncement"]["ColumnOrder"] !=
_SPLDefaults7["ColumnAnnouncement"]["ColumnOrder"]
+ and (SPLConfig["ColumnAnnouncement"]["ColumnOrder"] !=
_SPLDefaults["ColumnAnnouncement"]["ColumnOrder"]
or len(SPLConfig["ColumnAnnouncement"]["IncludedColumns"]) != 17))
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index 743aab1..6bbd046 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -153,7 +153,7 @@ class SPLConfigDialog(gui.SettingsDialog):
# Translators: The label for a setting in SPL add-on dialog to
set vertical column.
verticalColLabel = _("&Vertical column navigation
announcement:")
# Translators: One of the options for vertical column
navigation denoting NVDA will announce current column positoin (e.g. second
column position from the left).
- self.verticalColumnsList =
SPLConfigHelper.addLabeledControl(verticalColLabel, wx.Choice,
choices=[_("whichever column I am reviewing"), "Status"] +
splconfig._SPLDefaults7["ColumnAnnouncement"]["ColumnOrder"])
+ self.verticalColumnsList =
SPLConfigHelper.addLabeledControl(verticalColLabel, wx.Choice,
choices=[_("whichever column I am reviewing"), "Status"] +
splconfig._SPLDefaults["ColumnAnnouncement"]["ColumnOrder"])
verticalColumn =
splconfig.SPLConfig["General"]["VerticalColumnAnnounce"]
selection = self.verticalColumnsList.FindString(verticalColumn)
if verticalColumn is not None else 0
try:
@@ -1087,7 +1087,7 @@ class ColumnsExplorerDialog(wx.Dialog):
if not tt:
# Translators: The title of Columns Explorer
configuration dialog.
actualTitle = _("Columns Explorer")
- cols =
splconfig._SPLDefaults7["ColumnAnnouncement"]["ColumnOrder"]
+ cols =
splconfig._SPLDefaults["ColumnAnnouncement"]["ColumnOrder"]
else:
# Translators: The title of Columns Explorer
configuration dialog.
actualTitle = _("Columns Explorer for Track Tool")
diff --git a/addon/appModules/splstudio/splmisc.py
b/addon/appModules/splstudio/splmisc.py
index e3355bc..b4123b4 100755
--- a/addon/appModules/splstudio/splmisc.py
+++ b/addon/appModules/splstudio/splmisc.py
@@ -96,7 +96,7 @@ class SPLFindDialog(wx.Dialog):
columnSizer = wx.BoxSizer(wx.HORIZONTAL)
# Translators: The label in track finder to search
columns.
label = wx.StaticText(self, wx.ID_ANY, label=_("C&olumn
to search:"))
- self.columnHeaders = wx.Choice(self, wx.ID_ANY,
choices=splconfig._SPLDefaults7["ColumnAnnouncement"]["ColumnOrder"])
+ self.columnHeaders = wx.Choice(self, wx.ID_ANY,
choices=splconfig._SPLDefaults["ColumnAnnouncement"]["ColumnOrder"])
self.columnHeaders.SetSelection(0)
columnSizer.Add(label)
columnSizer.Add(self.columnHeaders)
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/4c92a6ced82d/
Changeset: 4c92a6ced82d
Branch: None
User: josephsl
Date: 2017-01-04 21:33:00+00:00
Summary: Playlist snapshots (17.1-dev): Add playlist snapshots config key
to ConfigHub as a set.
For compact representation, it is best to add playlist snapshots flags as a set
(similar to 'included columns' key), and now this set has been included as part
of COnfigHub. In the app module, this set will be looked up as a last resort of
no flags are specified. The next commit will be the user-visible config dialog.
Affected #: 2 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 3725fa1..3388ca6 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1422,11 +1422,12 @@ class AppModule(appModuleHandler.AppModule):
# Playlist snapshots
# Data to be gathered comes from a set of flags.
+ # By default, playlist duration (including shortest and average),
category summary and other statistics will be gathered.
def playlistSnapshots(self, obj, end, snapshotFlags=None):
# Track count and total duration are always included.
snapshot = {}
if snapshotFlags is None:
- snapshotFlags = ["PlaylistDurationMinMax",
"PlaylistCategoryCount", "PlaylistDurationAverage"]
+ snapshotFlags =
splconfig.SPLConfig["General"]["PlaylistSnapshots"]
duration = obj.indexOf("Duration")
title = obj.indexOf("Title")
min, max = None, None
@@ -1490,7 +1491,7 @@ class AppModule(appModuleHandler.AppModule):
if scriptCount == 0:
ui.message(", ".join(statusInfo))
else:
-
ui.browseableMessage("<p>".join(statusInfo),title="Playlist snapshot",
isHtml=True)
+
ui.browseableMessage("<p>".join(statusInfo),title="Playlist snapshots",
isHtml=True)
# Some handlers for native commands.
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 444d4ff..db90af8 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -46,6 +46,7 @@ TimeHourAnnounce = boolean(default=true)
ExploreColumns =
string_list(default=list("Artist","Title","Duration","Intro","Category","Filename","Year","Album","Genre","Time
Scheduled"))
ExploreColumnsTT =
string_list(default=list("Artist","Title","Duration","Cue","Overlap","Intro","Segue","Filename","Album","CD
Code"))
VerticalColumnAnnounce =
option(None,"Status","Artist","Title","Duration","Intro","Outro","Category","Year","Album","Genre","Mood","Energy","Tempo","BPM","Gender","Rating","Filename","Time
Scheduled",default=None)
+PlaylistSnapshots =
string_list(default=list("PlaylistDurationMinMax","PlaylistDurationAverage","PlaylistCategoryCount"))
[IntroOutroAlarms]
SayEndOfTrack = boolean(default=true)
EndOfTrackTime = integer(min=1, max=59, default=5)
@@ -268,6 +269,8 @@ class ConfigHub(ChainMap):
# 6.1: Transform column inclusion data structure (for
normal profile) now.
# 7.0: This will be repeated for broadcast profiles
later.
# 8.0: Conversion will happen here, as conversion to
list is necessary before writing it to disk (if told to do so).
+ # 17.04: Playlist snapshots is a global key, so only
normal profile will go through list conversion.
+
self.profiles[normalProfile]["General"]["PlaylistSnapshots"] =
list(self.profiles[normalProfile]["General"]["PlaylistSnapshots"])
self.profiles[normalProfile]["ColumnAnnouncement"]["IncludedColumns"] =
list(self.profiles[normalProfile]["ColumnAnnouncement"]["IncludedColumns"])
self.profiles[normalProfile].write()
del self.profiles[normalProfile]
@@ -482,6 +485,8 @@ def _extraInitSteps(conf, profileName=None):
# 17.04: If vertical column announcement value is "None", transform
this to NULL.
if conf["General"]["VerticalColumnAnnounce"] == "None":
conf["General"]["VerticalColumnAnnounce"] = None
+ # 17.04: Convert playlist snapshots list to a proper set.
+ conf["General"]["PlaylistSnapshots"] =
set(conf["General"]["PlaylistSnapshots"])
# Cache a copy of the loaded config.
# This comes in handy when saving configuration to disk. For the most part, no
change occurs to config.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/3dc1c5a9230b/
Changeset: 3dc1c5a9230b
Branch: None
User: josephsl
Date: 2017-01-06 17:55:22+00:00
Summary: SPL Utils: Removed unneeded imports, microphone toggle via SPL
Controller will no longer let sounds to be played.
In the past, when background event monitor wasn't there, microphone toggle
command from SPL Controller will tell NVDA to play a toggle beep sound (same as
name change handler in the app module). This is no longer necessary as long as
Studio is not minimized.
Affected #: 2 files
diff --git a/addon/globalPlugins/splUtils/__init__.py
b/addon/globalPlugins/splUtils/__init__.py
index cb63d5a..12856ab 100755
--- a/addon/globalPlugins/splUtils/__init__.py
+++ b/addon/globalPlugins/splUtils/__init__.py
@@ -5,14 +5,11 @@
# For encoder support, see the encoders package.
from functools import wraps
-import os
import globalPluginHandler
import api
import ui
import globalVars
-from NVDAObjects.IAccessible import getNVDAObjectFromEvent
import winUser
-import nvwave
import addonHandler
addonHandler.initTranslation()
@@ -160,12 +157,10 @@ class GlobalPlugin(globalPluginHandler.GlobalPlugin):
def script_micOn(self, gesture):
winUser.sendMessage(SPLWin,SPLMSG,1,SPLMic)
- nvwave.playWaveFile(os.path.join(os.path.dirname(__file__),
"..", "..", "appModules", "splstudio", "SPL_on.wav"))
self.finish()
def script_micOff(self, gesture):
winUser.sendMessage(SPLWin,SPLMSG,0,SPLMic)
- nvwave.playWaveFile(os.path.join(os.path.dirname(__file__),
"..", "..", "appModules", "splstudio", "SPL_off.wav"))
self.finish()
def script_micNoFade(self, gesture):
diff --git a/addon/globalPlugins/splUtils/encoders.py
b/addon/globalPlugins/splUtils/encoders.py
index b25486a..9e3b876 100755
--- a/addon/globalPlugins/splUtils/encoders.py
+++ b/addon/globalPlugins/splUtils/encoders.py
@@ -9,7 +9,7 @@ import api
import ui
import speech
import scriptHandler
-from NVDAObjects.IAccessible import IAccessible, getNVDAObjectFromEvent
+from NVDAObjects.IAccessible import IAccessible
import winUser
import tones
import gui
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/a5ee2f28701a/
Changeset: a5ee2f28701a
Branch: None
User: josephsl
Date: 2017-01-07 23:52:51+00:00
Summary: Update check: removed unneeded keys.
Really old PSZ and PCH keys are no longer required in update checks, so don't
check for it anymore. Also, why is dev branch set to get updates from 'stable'
builds? (fixed)
Affected #: 2 files
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 444d4ff..fe7db3e 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -817,7 +817,6 @@ def triggerProfileSwitch():
_SPLTriggerEndTimer.Stop()
_SPLTriggerEndTimer = None
-
# Automatic update checker.
# The function below is called as part of the update check timer.
@@ -846,7 +845,6 @@ def updateInit():
splupdate._SPLUpdateT = wx.PyTimer(autoUpdateCheck)
splupdate._SPLUpdateT.Start(interval * 1000, True)
-
# Let SPL track item know if it needs to build description pieces.
# To be renamed and used in other places in 7.0.
def _shouldBuildDescriptionPieces():
@@ -854,7 +852,6 @@ def _shouldBuildDescriptionPieces():
and (SPLConfig["ColumnAnnouncement"]["ColumnOrder"] !=
_SPLDefaults["ColumnAnnouncement"]["ColumnOrder"]
or len(SPLConfig["ColumnAnnouncement"]["IncludedColumns"]) != 17))
-
# Additional configuration and miscellaneous dialogs
# See splconfui module for basic configuration dialogs.
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index 218d98a..3664986 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -46,10 +46,8 @@ def initialize():
try:
SPLAddonState = cPickle.load(file(_updatePickle, "r"))
SPLAddonCheck = SPLAddonState["PDT"]
- if "PSZ" in SPLAddonState: del SPLAddonState["PSZ"]
- if "PCH" in SPLAddonState: del SPLAddonState["PCH"]
_updateNow = "pendingChannelChange" in SPLAddonState
- if "pendingChannelChange" in SPLAddonState: del
SPLAddonState["pendingChannelChange"]
+ if _updateNow: del SPLAddonState["pendingChannelChange"]
if "UpdateChannel" in SPLAddonState:
SPLUpdateChannel = SPLAddonState["UpdateChannel"]
if SPLUpdateChannel in ("beta", "lts"):
@@ -57,7 +55,7 @@ def initialize():
except IOError, KeyError:
SPLAddonState["PDT"] = 0
_updateNow = False
- SPLUpdateChannel = "stable"
+ SPLUpdateChannel = "dev"
def terminate():
global SPLAddonState
@@ -72,7 +70,6 @@ def terminate():
cPickle.dump(SPLAddonState, file(_updatePickle, "wb"))
SPLAddonState = None
-
def _versionFromURL(url):
# 7.3: Be sure to handle both GitHub and old URL format.
filename = url.split("/")[-1]
@@ -155,4 +152,3 @@ def updateCheck(auto=False, continuous=False,
confUpdateInterval=1):
def getUpdateResponse(message, caption, updateURL):
if gui.messageBox(message, caption, wx.YES | wx.NO | wx.CANCEL |
wx.CENTER | wx.ICON_QUESTION) == wx.YES:
os.startfile(updateURL)
-
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/29eea9fb85a5/
Changeset: 29eea9fb85a5
Branch: None
User: josephsl
Date: 2017-01-08 19:22:19+00:00
Summary: Playlist snapshots (17.1-dev): Add a configuration dialog to
configure playlsit snapshots info flags.
A new dialog (partially powered by code from Say Status dialog and Column
Announcements dialog) has been added to configure playlist snapshot flags
(using set operations if necessary). Because the dialog could be confusing at
first, an explanatory text has been added.
This is destined for add-on 17.04.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index 6bbd046..2c1a909 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -185,6 +185,11 @@ class SPLConfigDialog(gui.SettingsDialog):
self.topBottomCheckbox =
SPLConfigHelper.addItem(wx.CheckBox(self, label=_("Notify when located at &top
or bottom of playlist viewer")))
self.topBottomCheckbox.SetValue(splconfig.SPLConfig["General"]["TopBottomAnnounce"])
+ self.playlistSnapshots =
set(splconfig.SPLConfig["General"]["PlaylistSnapshots"])
+ # Translators: The label of a button to manage playlist
snapshot flags.
+ playlistSnapshotFlagsButton =
SPLConfigHelper.addItem(wx.Button(self, label=_("&Playlist snapshots...")))
+ playlistSnapshotFlagsButton.Bind(wx.EVT_BUTTON,
self.onPlaylistSnapshotFlags)
+
sizer = gui.guiHelper.BoxSizerHelper(self,
orientation=wx.HORIZONTAL)
self.metadataValues=[("off",_("Off")),
# Translators: One of the metadata notification settings.
@@ -277,6 +282,7 @@ class SPLConfigDialog(gui.SettingsDialog):
splconfig.SPLConfig["General"]["CategorySounds"] =
self.categorySoundsCheckbox.Value
splconfig.SPLConfig["General"]["TrackCommentAnnounce"] =
self.trackCommentValues[self.trackCommentList.GetSelection()][0]
splconfig.SPLConfig["General"]["TopBottomAnnounce"] =
self.topBottomCheckbox.Value
+ splconfig.SPLConfig["General"]["PlaylistSnapshots"] =
self.playlistSnapshots
splconfig.SPLConfig["General"]["MetadataReminder"] =
self.metadataValues[self.metadataList.GetSelection()][0]
splconfig.SPLConfig["MetadataStreaming"]["MetadataEnabled"] =
self.metadataStreams
splconfig.SPLConfig["ColumnAnnouncement"]["UseScreenColumnOrder"] =
self.columnOrderCheckbox.Value
@@ -510,7 +516,12 @@ class SPLConfigDialog(gui.SettingsDialog):
self.Disable()
AlarmsCenter(self).Show()
- # Manage metadata streaming.
+ # Configure playlist snapshot flags.
+ def onPlaylistSnapshotFlags(self, evt):
+ self.Disable()
+ PlaylistSnapshotsDialog(self).Show()
+
+ # Manage metadata streaming.
def onManageMetadata(self, evt):
self.Disable()
MetadataStreamingDialog(self).Show()
@@ -875,6 +886,57 @@ class AlarmsCenter(wx.Dialog):
global _alarmDialogOpened
_alarmDialogOpened = False
+# Playlist snapshot flags
+# For things such as checkboxes for average duration and top category count.
+class PlaylistSnapshotsDialog(wx.Dialog):
+
+ def __init__(self, parent):
+ # Translators: Title of a dialog to configure playlist snapshot
information.
+ super(PlaylistSnapshotsDialog, self).__init__(parent,
title=_("Playlist snapshots"))
+
+ mainSizer = wx.BoxSizer(wx.VERTICAL)
+ playlistSnapshotsHelper = gui.guiHelper.BoxSizerHelper(self,
orientation=wx.VERTICAL)
+
+ # Translators: Help text for playlist snapshots dialog.
+ labelText = _("""Select information to be included when
obtaining playlist snapshots.
+ Track count and total duration are always included.""")
+ playlistSnapshotsHelper.addItem(wx.StaticText(self,
label=labelText))
+
+ # Translators: the label for a setting in SPL add-on settings
to include shortest and longest track duration in playlist snapshots window.
+
self.playlistDurationMinMaxCheckbox=playlistSnapshotsHelper.addItem(wx.CheckBox(self,
label=_("Shortest and longest tracks")))
+
self.playlistDurationMinMaxCheckbox.SetValue("PlaylistDurationMinMax" in
parent.playlistSnapshots)
+ # Translators: the label for a setting in SPL add-on settings
to include average track duration in playlist snapshots window.
+
self.playlistDurationAverageCheckbox=playlistSnapshotsHelper.addItem(wx.CheckBox(self,
label=_("Average track duration")))
+
self.playlistDurationAverageCheckbox.SetValue("PlaylistDurationAverage" in
parent.playlistSnapshots)
+ # Translators: the label for a setting in SPL add-on settings
to include track category count in playlist snapshots window.
+
self.playlistCategoryCountCheckbox=playlistSnapshotsHelper.addItem(wx.CheckBox(self,
label=_("Category count")))
+
self.playlistCategoryCountCheckbox.SetValue("PlaylistCategoryCount" in
parent.playlistSnapshots)
+
+
playlistSnapshotsHelper.addDialogDismissButtons(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.Add(playlistSnapshotsHelper.sizer,
border=gui.guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL)
+ mainSizer.Fit(self)
+ self.Sizer = mainSizer
+ self.playlistDurationMinMaxCheckbox.SetFocus()
+ self.Center(wx.BOTH | wx.CENTER_ON_SCREEN)
+
+ def onOk(self, evt):
+ playlistSnapshots = set()
+ if self.playlistDurationMinMaxCheckbox.Value:
playlistSnapshots.add("PlaylistDurationMinMax")
+ if self.playlistDurationAverageCheckbox.Value:
playlistSnapshots.add("PlaylistDurationAverage")
+ if self.playlistCategoryCountCheckbox.Value:
playlistSnapshots.add("PlaylistCategoryCount")
+ parent = self.Parent
+ parent.playlistSnapshots = playlistSnapshots
+ parent.profiles.SetFocus()
+ 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
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/125b3d7a372a/
Changeset: 125b3d7a372a
Branch: None
User: josephsl
Date: 2017-01-09 01:34:38+00:00
Summary: Playlist snapshots (17.1-dev): Use a dedicated section for
playlist snapshots variables, now booleans.
A conscious decision due to inconsistency: dialogs that are branches off from
add-on settings sets keys that are in different section, so why was it that
playlist snapshots did not follow this rule? Now corrected.
Also, for ease of readability and maintenance, playlist snapshots keys are now
booleans 9or would be integers later). The caveat is that playlist snapshots
function in the pap module must convert dictionaries to lists (via list
comprehension), a necessary thing but acceptable for now.
Affected #: 3 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 3388ca6..27588e1 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1427,7 +1427,7 @@ class AppModule(appModuleHandler.AppModule):
# Track count and total duration are always included.
snapshot = {}
if snapshotFlags is None:
- snapshotFlags =
splconfig.SPLConfig["General"]["PlaylistSnapshots"]
+ snapshotFlags = [flag for flag in
splconfig.SPLConfig["PlaylistSnapshots"] if
splconfig.SPLConfig["PlaylistSnapshots"][flag]]
duration = obj.indexOf("Duration")
title = obj.indexOf("Title")
min, max = None, None
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index db90af8..e00aef4 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -46,7 +46,10 @@ TimeHourAnnounce = boolean(default=true)
ExploreColumns =
string_list(default=list("Artist","Title","Duration","Intro","Category","Filename","Year","Album","Genre","Time
Scheduled"))
ExploreColumnsTT =
string_list(default=list("Artist","Title","Duration","Cue","Overlap","Intro","Segue","Filename","Album","CD
Code"))
VerticalColumnAnnounce =
option(None,"Status","Artist","Title","Duration","Intro","Outro","Category","Year","Album","Genre","Mood","Energy","Tempo","BPM","Gender","Rating","Filename","Time
Scheduled",default=None)
-PlaylistSnapshots =
string_list(default=list("PlaylistDurationMinMax","PlaylistDurationAverage","PlaylistCategoryCount"))
+[PlaylistSnapshots]
+PlaylistDurationMinMax = boolean(default=true)
+PlaylistDurationAverage = boolean(default=true)
+PlaylistCategoryCount = boolean(default=true)
[IntroOutroAlarms]
SayEndOfTrack = boolean(default=true)
EndOfTrackTime = integer(min=1, max=59, default=5)
@@ -114,6 +117,8 @@ class ConfigHub(ChainMap):
deprecatedKeys = {"General":"TrackDial", "Startup":"Studio500"}
for section, key in deprecatedKeys.iteritems():
if key in self.maps[0][section]: del
self.maps[0][section][key]
+ # January 2017 only: playlist snapshots is now its own
dedicated section.
+ if "PlaylistSnapshots" in self.maps[0]["General"]: del
self.maps[0]["General"]["PlaylistSnapshots"]
# Moving onto broadcast profiles if any.
try:
profiles = filter(lambda fn: os.path.splitext(fn)[-1]
== ".ini", os.listdir(SPLProfiles))
@@ -269,8 +274,6 @@ class ConfigHub(ChainMap):
# 6.1: Transform column inclusion data structure (for
normal profile) now.
# 7.0: This will be repeated for broadcast profiles
later.
# 8.0: Conversion will happen here, as conversion to
list is necessary before writing it to disk (if told to do so).
- # 17.04: Playlist snapshots is a global key, so only
normal profile will go through list conversion.
-
self.profiles[normalProfile]["General"]["PlaylistSnapshots"] =
list(self.profiles[normalProfile]["General"]["PlaylistSnapshots"])
self.profiles[normalProfile]["ColumnAnnouncement"]["IncludedColumns"] =
list(self.profiles[normalProfile]["ColumnAnnouncement"]["IncludedColumns"])
self.profiles[normalProfile].write()
del self.profiles[normalProfile]
@@ -485,8 +488,6 @@ def _extraInitSteps(conf, profileName=None):
# 17.04: If vertical column announcement value is "None", transform
this to NULL.
if conf["General"]["VerticalColumnAnnounce"] == "None":
conf["General"]["VerticalColumnAnnounce"] = None
- # 17.04: Convert playlist snapshots list to a proper set.
- conf["General"]["PlaylistSnapshots"] =
set(conf["General"]["PlaylistSnapshots"])
# Cache a copy of the loaded config.
# This comes in handy when saving configuration to disk. For the most part, no
change occurs to config.
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index 2c1a909..5f7cbf6 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -185,7 +185,9 @@ class SPLConfigDialog(gui.SettingsDialog):
self.topBottomCheckbox =
SPLConfigHelper.addItem(wx.CheckBox(self, label=_("Notify when located at &top
or bottom of playlist viewer")))
self.topBottomCheckbox.SetValue(splconfig.SPLConfig["General"]["TopBottomAnnounce"])
- self.playlistSnapshots =
set(splconfig.SPLConfig["General"]["PlaylistSnapshots"])
+ self.playlistDurationMinMax =
splconfig.SPLConfig["PlaylistSnapshots"]["PlaylistDurationMinMax"]
+ self.playlistDurationAverage =
splconfig.SPLConfig["PlaylistSnapshots"]["PlaylistDurationAverage"]
+ self.playlistCategoryCount =
splconfig.SPLConfig["PlaylistSnapshots"]["PlaylistCategoryCount"]
# Translators: The label of a button to manage playlist
snapshot flags.
playlistSnapshotFlagsButton =
SPLConfigHelper.addItem(wx.Button(self, label=_("&Playlist snapshots...")))
playlistSnapshotFlagsButton.Bind(wx.EVT_BUTTON,
self.onPlaylistSnapshotFlags)
@@ -282,7 +284,9 @@ class SPLConfigDialog(gui.SettingsDialog):
splconfig.SPLConfig["General"]["CategorySounds"] =
self.categorySoundsCheckbox.Value
splconfig.SPLConfig["General"]["TrackCommentAnnounce"] =
self.trackCommentValues[self.trackCommentList.GetSelection()][0]
splconfig.SPLConfig["General"]["TopBottomAnnounce"] =
self.topBottomCheckbox.Value
- splconfig.SPLConfig["General"]["PlaylistSnapshots"] =
self.playlistSnapshots
+
splconfig.SPLConfig["PlaylistSnapshots"]["PlaylistDurationMinMax"] =
self.playlistDurationMinMax
+
splconfig.SPLConfig["PlaylistSnapshots"]["PlaylistDurationAverage"] =
self.playlistDurationAverage
+
splconfig.SPLConfig["PlaylistSnapshots"]["PlaylistCategoryCount"] =
self.playlistCategoryCount
splconfig.SPLConfig["General"]["MetadataReminder"] =
self.metadataValues[self.metadataList.GetSelection()][0]
splconfig.SPLConfig["MetadataStreaming"]["MetadataEnabled"] =
self.metadataStreams
splconfig.SPLConfig["ColumnAnnouncement"]["UseScreenColumnOrder"] =
self.columnOrderCheckbox.Value
@@ -904,13 +908,13 @@ class PlaylistSnapshotsDialog(wx.Dialog):
# Translators: the label for a setting in SPL add-on settings
to include shortest and longest track duration in playlist snapshots window.
self.playlistDurationMinMaxCheckbox=playlistSnapshotsHelper.addItem(wx.CheckBox(self,
label=_("Shortest and longest tracks")))
-
self.playlistDurationMinMaxCheckbox.SetValue("PlaylistDurationMinMax" in
parent.playlistSnapshots)
+
self.playlistDurationMinMaxCheckbox.SetValue(parent.playlistDurationMinMax)
# Translators: the label for a setting in SPL add-on settings
to include average track duration in playlist snapshots window.
self.playlistDurationAverageCheckbox=playlistSnapshotsHelper.addItem(wx.CheckBox(self,
label=_("Average track duration")))
-
self.playlistDurationAverageCheckbox.SetValue("PlaylistDurationAverage" in
parent.playlistSnapshots)
+
self.playlistDurationAverageCheckbox.SetValue(parent.playlistDurationAverage)
# Translators: the label for a setting in SPL add-on settings
to include track category count in playlist snapshots window.
self.playlistCategoryCountCheckbox=playlistSnapshotsHelper.addItem(wx.CheckBox(self,
label=_("Category count")))
-
self.playlistCategoryCountCheckbox.SetValue("PlaylistCategoryCount" in
parent.playlistSnapshots)
+
self.playlistCategoryCountCheckbox.SetValue(parent.playlistCategoryCount)
playlistSnapshotsHelper.addDialogDismissButtons(self.CreateButtonSizer(wx.OK |
wx.CANCEL))
self.Bind(wx.EVT_BUTTON, self.onOk, id=wx.ID_OK)
@@ -922,12 +926,10 @@ class PlaylistSnapshotsDialog(wx.Dialog):
self.Center(wx.BOTH | wx.CENTER_ON_SCREEN)
def onOk(self, evt):
- playlistSnapshots = set()
- if self.playlistDurationMinMaxCheckbox.Value:
playlistSnapshots.add("PlaylistDurationMinMax")
- if self.playlistDurationAverageCheckbox.Value:
playlistSnapshots.add("PlaylistDurationAverage")
- if self.playlistCategoryCountCheckbox.Value:
playlistSnapshots.add("PlaylistCategoryCount")
parent = self.Parent
- parent.playlistSnapshots = playlistSnapshots
+ parent.playlistDurationMinMax =
self.playlistDurationMinMaxCheckbox.Value
+ parent.playlistDurationAverage =
self.playlistDurationAverageCheckbox.Value
+ parent.playlistCategoryCount =
self.playlistCategoryCountCheckbox.Value
parent.profiles.SetFocus()
parent.Enable()
self.Destroy()
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/02f04586e2f2/
Changeset: 02f04586e2f2
Branch: None
User: josephsl
Date: 2017-01-10 17:03:47+00:00
Summary: Update check (17.1-dev): Use a regular expression to locate new
version string.
A better solution than splitting strings: because the file name for the add-on
package will stay the same for a long time, just use regular expressions to
locate the new version string (stationplaylist-[version].nvda-addon). This
improves update version check routine and is resistant to URL scheme changes.
This is destined for add-on 17.04.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index 3664986..3191f1c 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -70,17 +70,14 @@ def terminate():
cPickle.dump(SPLAddonState, file(_updatePickle, "wb"))
SPLAddonState = None
-def _versionFromURL(url):
- # 7.3: Be sure to handle both GitHub and old URL format.
- filename = url.split("/")[-1]
- return filename.split("stationPlaylist-")[1].split(".nvda-addon")[0]
-
def updateQualify(url):
- # The add-on version is of the form "major.minor". The "-dev" suffix
indicates development release.
+ # The add-on version is of the form "x.y.z". The "-dev" suffix
indicates development release.
# Anything after "-dev" indicates a try or a custom build.
# LTS: Support upgrading between LTS releases.
# 7.0: Just worry about version label differences (suggested by Jamie
Teh from NV Access).
- version = _versionFromURL(url.url)
+ # 17.04: Version is of the form year.month.revision, and regular
expression will be employed (looks cleaner).
+ import re
+ version = re.search("stationPlaylist-(?P<version>.*).nvda-addon",
url.url).groupdict()["version"]
return None if version == SPLAddonVersion else version
_progressDialog = None
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/e7d43f26e442/
Changeset: e7d43f26e442
Branch: None
User: josephsl
Date: 2017-01-10 18:32:42+00:00
Summary: Merge branch 'master' into plSnaps
Affected #: 4 files
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index e00aef4..1ff7398 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -823,7 +823,6 @@ def triggerProfileSwitch():
_SPLTriggerEndTimer.Stop()
_SPLTriggerEndTimer = None
-
# Automatic update checker.
# The function below is called as part of the update check timer.
@@ -852,7 +851,6 @@ def updateInit():
splupdate._SPLUpdateT = wx.PyTimer(autoUpdateCheck)
splupdate._SPLUpdateT.Start(interval * 1000, True)
-
# Let SPL track item know if it needs to build description pieces.
# To be renamed and used in other places in 7.0.
def _shouldBuildDescriptionPieces():
@@ -860,7 +858,6 @@ def _shouldBuildDescriptionPieces():
and (SPLConfig["ColumnAnnouncement"]["ColumnOrder"] !=
_SPLDefaults["ColumnAnnouncement"]["ColumnOrder"]
or len(SPLConfig["ColumnAnnouncement"]["IncludedColumns"]) != 17))
-
# Additional configuration and miscellaneous dialogs
# See splconfui module for basic configuration dialogs.
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index 218d98a..3191f1c 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -46,10 +46,8 @@ def initialize():
try:
SPLAddonState = cPickle.load(file(_updatePickle, "r"))
SPLAddonCheck = SPLAddonState["PDT"]
- if "PSZ" in SPLAddonState: del SPLAddonState["PSZ"]
- if "PCH" in SPLAddonState: del SPLAddonState["PCH"]
_updateNow = "pendingChannelChange" in SPLAddonState
- if "pendingChannelChange" in SPLAddonState: del
SPLAddonState["pendingChannelChange"]
+ if _updateNow: del SPLAddonState["pendingChannelChange"]
if "UpdateChannel" in SPLAddonState:
SPLUpdateChannel = SPLAddonState["UpdateChannel"]
if SPLUpdateChannel in ("beta", "lts"):
@@ -57,7 +55,7 @@ def initialize():
except IOError, KeyError:
SPLAddonState["PDT"] = 0
_updateNow = False
- SPLUpdateChannel = "stable"
+ SPLUpdateChannel = "dev"
def terminate():
global SPLAddonState
@@ -72,18 +70,14 @@ def terminate():
cPickle.dump(SPLAddonState, file(_updatePickle, "wb"))
SPLAddonState = None
-
-def _versionFromURL(url):
- # 7.3: Be sure to handle both GitHub and old URL format.
- filename = url.split("/")[-1]
- return filename.split("stationPlaylist-")[1].split(".nvda-addon")[0]
-
def updateQualify(url):
- # The add-on version is of the form "major.minor". The "-dev" suffix
indicates development release.
+ # The add-on version is of the form "x.y.z". The "-dev" suffix
indicates development release.
# Anything after "-dev" indicates a try or a custom build.
# LTS: Support upgrading between LTS releases.
# 7.0: Just worry about version label differences (suggested by Jamie
Teh from NV Access).
- version = _versionFromURL(url.url)
+ # 17.04: Version is of the form year.month.revision, and regular
expression will be employed (looks cleaner).
+ import re
+ version = re.search("stationPlaylist-(?P<version>.*).nvda-addon",
url.url).groupdict()["version"]
return None if version == SPLAddonVersion else version
_progressDialog = None
@@ -155,4 +149,3 @@ def updateCheck(auto=False, continuous=False,
confUpdateInterval=1):
def getUpdateResponse(message, caption, updateURL):
if gui.messageBox(message, caption, wx.YES | wx.NO | wx.CANCEL |
wx.CENTER | wx.ICON_QUESTION) == wx.YES:
os.startfile(updateURL)
-
diff --git a/addon/globalPlugins/splUtils/__init__.py
b/addon/globalPlugins/splUtils/__init__.py
index cb63d5a..12856ab 100755
--- a/addon/globalPlugins/splUtils/__init__.py
+++ b/addon/globalPlugins/splUtils/__init__.py
@@ -5,14 +5,11 @@
# For encoder support, see the encoders package.
from functools import wraps
-import os
import globalPluginHandler
import api
import ui
import globalVars
-from NVDAObjects.IAccessible import getNVDAObjectFromEvent
import winUser
-import nvwave
import addonHandler
addonHandler.initTranslation()
@@ -160,12 +157,10 @@ class GlobalPlugin(globalPluginHandler.GlobalPlugin):
def script_micOn(self, gesture):
winUser.sendMessage(SPLWin,SPLMSG,1,SPLMic)
- nvwave.playWaveFile(os.path.join(os.path.dirname(__file__),
"..", "..", "appModules", "splstudio", "SPL_on.wav"))
self.finish()
def script_micOff(self, gesture):
winUser.sendMessage(SPLWin,SPLMSG,0,SPLMic)
- nvwave.playWaveFile(os.path.join(os.path.dirname(__file__),
"..", "..", "appModules", "splstudio", "SPL_off.wav"))
self.finish()
def script_micNoFade(self, gesture):
diff --git a/addon/globalPlugins/splUtils/encoders.py
b/addon/globalPlugins/splUtils/encoders.py
index b25486a..9e3b876 100755
--- a/addon/globalPlugins/splUtils/encoders.py
+++ b/addon/globalPlugins/splUtils/encoders.py
@@ -9,7 +9,7 @@ import api
import ui
import speech
import scriptHandler
-from NVDAObjects.IAccessible import IAccessible, getNVDAObjectFromEvent
+from NVDAObjects.IAccessible import IAccessible
import winUser
import tones
import gui
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/c665ea96a2eb/
Changeset: c665ea96a2eb
Branch: None
User: josephsl
Date: 2017-01-10 19:36:52+00:00
Summary: Playlist snapshots (17.1-dev): allow artist count to be included.
Artist count is now included (at least top artists will be displayed). The
config option for this flag is next.
Affected #: 2 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 27588e1..7c79544 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1430,6 +1430,8 @@ class AppModule(appModuleHandler.AppModule):
snapshotFlags = [flag for flag in
splconfig.SPLConfig["PlaylistSnapshots"] if
splconfig.SPLConfig["PlaylistSnapshots"][flag]]
duration = obj.indexOf("Duration")
title = obj.indexOf("Title")
+ artist = obj.indexOf("Artist")
+ artists = []
min, max = None, None
minTitle, maxTitle = None, None
category = obj.indexOf("Category")
@@ -1440,6 +1442,7 @@ class AppModule(appModuleHandler.AppModule):
segue = obj._getColumnContent(duration)
trackTitle = obj._getColumnContent(title)
categories.append(obj._getColumnContent(category))
+ if categories[-1] != "Hour Marker":
artists.append(obj._getColumnContent(artist))
# Shortest and longest tracks.
if min is None: min = segue
if segue and segue < min:
@@ -1460,9 +1463,10 @@ class AppModule(appModuleHandler.AppModule):
snapshot["PlaylistDurationMax"] = "%s (%s)"%(maxTitle,
max)
if "PlaylistDurationAverage" in snapshotFlags:
snapshot["PlaylistDurationAverage"] =
self._ms2time(totalDuration/snapshot["PlaylistTrackCount"], ms=False)
- if "PlaylistCategoryCount" in snapshotFlags:
+ if "PlaylistCategoryCount" in snapshotFlags or
"PlaylistArtistCount" in snapshotFlags:
import collections
- snapshot["PlaylistCategoryCount"] =
collections.Counter(categories)
+ if "PlaylistCategoryCount" in snapshotFlags:
snapshot["PlaylistCategoryCount"] = collections.Counter(categories)
+ if "PlaylistArtistCount" in snapshotFlags:
snapshot["PlaylistArtistCount"] = collections.Counter(artists)
return snapshot
# Output formatter for playlist snapshots.
@@ -1476,6 +1480,21 @@ class AppModule(appModuleHandler.AppModule):
statusInfo.append("Longest:
%s"%snapshot["PlaylistDurationMax"])
if "PlaylistDurationAverage" in snapshot:
statusInfo.append("Average:
%s"%snapshot["PlaylistDurationAverage"])
+ if "PlaylistArtistCount" in snapshot:
+ artists = snapshot["PlaylistArtistCount"].most_common()
+ if scriptCount == 0:
+ statusInfo.append("Top artist: %s
(%s)"%(artists[0]))
+ else:
+ artistList = []
+ for item in artists:
+ artist, count = item
+ try:
+ artist = artist.replace("<", "")
+ artist = artist.replace(">", "")
+ artistList.append("<li>%s
(%s)</li>"%(artist, count))
+ except AttributeError:
+ artistList.append("<li> No
artist information (%s)</li>"%(count))
+ statusInfo.append("".join(["Top artists:<ol>",
"\n".join(artistList), "</ol>"]))
if "PlaylistCategoryCount" in snapshot:
categories =
snapshot["PlaylistCategoryCount"].most_common()
if scriptCount == 0:
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 1ff7398..595c032 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -49,7 +49,10 @@ VerticalColumnAnnounce =
option(None,"Status","Artist","Title","Duration","Intro
[PlaylistSnapshots]
PlaylistDurationMinMax = boolean(default=true)
PlaylistDurationAverage = boolean(default=true)
+PlaylistArtistCount = boolean(default=true)
+ArtistCountLimit = integer(min=0, max=10, default=5)
PlaylistCategoryCount = boolean(default=true)
+CategoryCountLimit = integer(min=0, max=10, default=5)
[IntroOutroAlarms]
SayEndOfTrack = boolean(default=true)
EndOfTrackTime = integer(min=1, max=59, default=5)
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/99c3115b5296/
Changeset: 99c3115b5296
Branch: None
User: josephsl
Date: 2017-01-10 20:57:50+00:00
Summary: Playlist snapshots (17.1-dev): Simplfied key names for flags.
Quite verbose to say ['Playlistsnapshtos']['Playlist*'] for key names, thus
only say ['PlaylistSnapshots']['*'] as the section itself deals with playlist
snapshots.
Affected #: 3 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 7c79544..555dbab 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1458,15 +1458,15 @@ class AppModule(appModuleHandler.AppModule):
obj = obj.next
if end is None: snapshot["PlaylistTrackCount"] = statusAPI(0,
124, ret=True)
snapshot["PlaylistDurationTotal"] =
self._ms2time(totalDuration, ms=False)
- if "PlaylistDurationMinMax" in snapshotFlags:
+ if "DurationMinMax" in snapshotFlags:
snapshot["PlaylistDurationMin"] = "%s (%s)"%(minTitle,
min)
snapshot["PlaylistDurationMax"] = "%s (%s)"%(maxTitle,
max)
- if "PlaylistDurationAverage" in snapshotFlags:
+ if "DurationAverage" in snapshotFlags:
snapshot["PlaylistDurationAverage"] =
self._ms2time(totalDuration/snapshot["PlaylistTrackCount"], ms=False)
- if "PlaylistCategoryCount" in snapshotFlags or
"PlaylistArtistCount" in snapshotFlags:
+ if "CategoryCount" in snapshotFlags or "ArtistCount" in
snapshotFlags:
import collections
- if "PlaylistCategoryCount" in snapshotFlags:
snapshot["PlaylistCategoryCount"] = collections.Counter(categories)
- if "PlaylistArtistCount" in snapshotFlags:
snapshot["PlaylistArtistCount"] = collections.Counter(artists)
+ if "CategoryCount" in snapshotFlags:
snapshot["PlaylistCategoryCount"] = collections.Counter(categories)
+ if "ArtistCount" in snapshotFlags:
snapshot["PlaylistArtistCount"] = collections.Counter(artists)
return snapshot
# Output formatter for playlist snapshots.
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 595c032..05c18a1 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -47,11 +47,11 @@ ExploreColumns =
string_list(default=list("Artist","Title","Duration","Intro","C
ExploreColumnsTT =
string_list(default=list("Artist","Title","Duration","Cue","Overlap","Intro","Segue","Filename","Album","CD
Code"))
VerticalColumnAnnounce =
option(None,"Status","Artist","Title","Duration","Intro","Outro","Category","Year","Album","Genre","Mood","Energy","Tempo","BPM","Gender","Rating","Filename","Time
Scheduled",default=None)
[PlaylistSnapshots]
-PlaylistDurationMinMax = boolean(default=true)
-PlaylistDurationAverage = boolean(default=true)
-PlaylistArtistCount = boolean(default=true)
+DurationMinMax = boolean(default=true)
+DurationAverage = boolean(default=true)
+ArtistCount = boolean(default=true)
ArtistCountLimit = integer(min=0, max=10, default=5)
-PlaylistCategoryCount = boolean(default=true)
+CategoryCount = boolean(default=true)
CategoryCountLimit = integer(min=0, max=10, default=5)
[IntroOutroAlarms]
SayEndOfTrack = boolean(default=true)
@@ -122,6 +122,10 @@ class ConfigHub(ChainMap):
if key in self.maps[0][section]: del
self.maps[0][section][key]
# January 2017 only: playlist snapshots is now its own
dedicated section.
if "PlaylistSnapshots" in self.maps[0]["General"]: del
self.maps[0]["General"]["PlaylistSnapshots"]
+ for key in ("PlaylistDurationMinMax",
"PlaylistDurationAverage", "PlaylistCategoryCount"):
+ if key in self.maps[0]["PlaylistSnapshots"]:
+ self.maps[0]["PlaylistSnapshots"][key[8:]] =
self.maps[0]["PlaylistSnapshots"][key]
+ del self.maps[0]["PlaylistSnapshots"][key]
# Moving onto broadcast profiles if any.
try:
profiles = filter(lambda fn: os.path.splitext(fn)[-1]
== ".ini", os.listdir(SPLProfiles))
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index 5f7cbf6..0981885 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -185,9 +185,9 @@ class SPLConfigDialog(gui.SettingsDialog):
self.topBottomCheckbox =
SPLConfigHelper.addItem(wx.CheckBox(self, label=_("Notify when located at &top
or bottom of playlist viewer")))
self.topBottomCheckbox.SetValue(splconfig.SPLConfig["General"]["TopBottomAnnounce"])
- self.playlistDurationMinMax =
splconfig.SPLConfig["PlaylistSnapshots"]["PlaylistDurationMinMax"]
- self.playlistDurationAverage =
splconfig.SPLConfig["PlaylistSnapshots"]["PlaylistDurationAverage"]
- self.playlistCategoryCount =
splconfig.SPLConfig["PlaylistSnapshots"]["PlaylistCategoryCount"]
+ self.playlistDurationMinMax =
splconfig.SPLConfig["PlaylistSnapshots"]["DurationMinMax"]
+ self.playlistDurationAverage =
splconfig.SPLConfig["PlaylistSnapshots"]["DurationAverage"]
+ self.playlistCategoryCount =
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCount"]
# Translators: The label of a button to manage playlist
snapshot flags.
playlistSnapshotFlagsButton =
SPLConfigHelper.addItem(wx.Button(self, label=_("&Playlist snapshots...")))
playlistSnapshotFlagsButton.Bind(wx.EVT_BUTTON,
self.onPlaylistSnapshotFlags)
@@ -284,9 +284,9 @@ class SPLConfigDialog(gui.SettingsDialog):
splconfig.SPLConfig["General"]["CategorySounds"] =
self.categorySoundsCheckbox.Value
splconfig.SPLConfig["General"]["TrackCommentAnnounce"] =
self.trackCommentValues[self.trackCommentList.GetSelection()][0]
splconfig.SPLConfig["General"]["TopBottomAnnounce"] =
self.topBottomCheckbox.Value
-
splconfig.SPLConfig["PlaylistSnapshots"]["PlaylistDurationMinMax"] =
self.playlistDurationMinMax
-
splconfig.SPLConfig["PlaylistSnapshots"]["PlaylistDurationAverage"] =
self.playlistDurationAverage
-
splconfig.SPLConfig["PlaylistSnapshots"]["PlaylistCategoryCount"] =
self.playlistCategoryCount
+ splconfig.SPLConfig["PlaylistSnapshots"]["DurationMinMax"] =
self.playlistDurationMinMax
+ splconfig.SPLConfig["PlaylistSnapshots"]["DurationAverage"] =
self.playlistDurationAverage
+ splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCount"] =
self.playlistCategoryCount
splconfig.SPLConfig["General"]["MetadataReminder"] =
self.metadataValues[self.metadataList.GetSelection()][0]
splconfig.SPLConfig["MetadataStreaming"]["MetadataEnabled"] =
self.metadataStreams
splconfig.SPLConfig["ColumnAnnouncement"]["UseScreenColumnOrder"] =
self.columnOrderCheckbox.Value
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/c3a719c1c375/
Changeset: c3a719c1c375
Branch: None
User: josephsl
Date: 2017-01-10 21:23:29+00:00
Summary: Removed legacy variable names for confspec and friends.
No longer called 'confspec7' or 'mutatablesettings7' - drop '7' from these
names, as the 7.x style config is in effect.
Affected #: 2 files
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index fe7db3e..ec84cdf 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -31,7 +31,7 @@ except ImportError:
SPLIni = os.path.join(globalVars.appArgs.configPath, "splstudio.ini")
SPLProfiles = os.path.join(globalVars.appArgs.configPath, "addons",
"stationPlaylist", "profiles")
# New (7.0) style config.
-confspec7 = ConfigObj(StringIO("""
+confspec = ConfigObj(StringIO("""
[General]
BeepAnnounce = boolean(default=false)
MessageVerbosity = option("beginner", "advanced", default="beginner")
@@ -76,13 +76,13 @@ UpdateInterval = integer(min=1, max=30, default=7)
AudioDuckingReminder = boolean(default=true)
WelcomeDialog = boolean(default=true)
"""), encoding="UTF-8", list_values=False)
-confspec7.newlines = "\r\n"
+confspec.newlines = "\r\n"
SPLConfig = None
# The following settings can be changed in profiles:
-_mutatableSettings7=("IntroOutroAlarms", "MicrophoneAlarm",
"MetadataStreaming", "ColumnAnnouncement")
+_mutatableSettings=("IntroOutroAlarms", "MicrophoneAlarm",
"MetadataStreaming", "ColumnAnnouncement")
# 7.0: Profile-specific confspec (might be removed once a more optimal way to
validate sections is found).
# Dictionary comprehension is better here.
-confspecprofiles = {sect:key for sect, key in confspec7.iteritems() if sect in
_mutatableSettings7}
+confspecprofiles = {sect:key for sect, key in confspec.iteritems() if sect in
_mutatableSettings}
# 8.0: Run-time config storage and management will use ConfigHub data
structure, a subclass of chain map.
# A chain map allows a dictionary to look up predefined mappings to locate a
key.
@@ -152,10 +152,10 @@ class ConfigHub(ChainMap):
# 7.0: What if profiles have parsing errors?
# If so, reset everything back to factory defaults.
try:
- SPLConfigCheckpoint = ConfigObj(path, configspec =
confspec7 if prefill else confspecprofiles, encoding="UTF-8")
+ SPLConfigCheckpoint = ConfigObj(path, configspec =
confspec if prefill else confspecprofiles, encoding="UTF-8")
except:
open(path, "w").close()
- SPLConfigCheckpoint = ConfigObj(path, configspec =
confspec7 if prefill else confspecprofiles, encoding="UTF-8")
+ SPLConfigCheckpoint = ConfigObj(path, configspec =
confspec if prefill else confspecprofiles, encoding="UTF-8")
_configLoadStatus[profileName] = "fileReset"
# 5.2 and later: check to make sure all values are correct.
# 7.0: Make sure errors are displayed as config keys are now
sections and may need to go through subkeys.
@@ -249,7 +249,7 @@ class ConfigHub(ChainMap):
def __delitem__(self, key):
# Consult profile-specific key first before deleting anything.
- pos = 0 if key in _mutatableSettings7 else [profile.name for
profile in self.maps].index(_("Normal Profile"))
+ pos = 0 if key in _mutatableSettings else [profile.name for
profile in self.maps].index(_("Normal Profile"))
try:
del self.maps[pos][key]
except KeyError:
@@ -364,7 +364,7 @@ class ConfigHub(ChainMap):
# Default config spec container.
# To be moved to a different place in 8.0.
-_SPLDefaults = ConfigObj(None, configspec = confspec7, encoding="UTF-8")
+_SPLDefaults = ConfigObj(None, configspec = confspec, encoding="UTF-8")
_val = Validator()
_SPLDefaults.validate(_val, copy=True)
@@ -659,7 +659,7 @@ def getProfileByName(name):
# Setting complete flag controls whether profile-specific settings are applied
(true otherwise, only set when resetting profiles).
# 8.0: Simplified thanks to in-place swapping.
def copyProfile(sourceProfile, targetProfile, complete=False):
- for section in sourceProfile.keys() if complete else
_mutatableSettings7:
+ for section in sourceProfile.keys() if complete else _mutatableSettings:
targetProfile[section] = dict(sourceProfile[section])
# Last but not least...
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index 6bbd046..d513340 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -610,7 +610,7 @@ class NewProfileDialog(wx.Dialog):
# LTS optimization: just build base profile dictionary here if
copying a profile.
if self.copy:
baseConfig =
splconfig.getProfileByName(self.baseProfiles.GetStringSelection())
- baseProfile = {sect:key for sect, key in
baseConfig.iteritems() if sect in splconfig._mutatableSettings7}
+ baseProfile = {sect:key for sect, key in
baseConfig.iteritems() if sect in splconfig._mutatableSettings}
else: baseProfile = None
splconfig.SPLConfig.createProfile(newProfilePath,
profileName=name, parent=baseProfile)
parent.profileNames.append(name)
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/9d3d3ef088bb/
Changeset: 9d3d3ef088bb
Branch: None
User: josephsl
Date: 2017-01-10 21:35:25+00:00
Summary: Readme: mention vertical column navigation commands.
Affected #: 1 file
diff --git a/readme.md b/readme.md
index 0f0378f..29f22f3 100755
--- a/readme.md
+++ b/readme.md
@@ -24,6 +24,7 @@ IMPORTANT: This add-on requires NVDA 2016.4 or later and
StationPlaylist Studio
* Alt+NVDA+R from Studio window: Steps through library scan announcement
settings.
* Control+Shift+X from Studio window: Steps through braille timer settings.
* Control+Alt+right/left arrow (while focused on a track): Announce
next/previous track column.
+* Control+Alt+down/up arrow (while focused on a track): Move to next or
previous track and announce specific columns (unavailable in add-on 15.x).
* Control+NVDA+1 through 0 (6 for Studio 5.0x): Announce column content for a
specified column.
* Alt+NVDA+C while focused on a track: announces track comments if any.
* Alt+NVDA+0 from Studio window: Opens the Studio add-on configuration dialog.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/18b1dcd9d1ce/
Changeset: 18b1dcd9d1ce
Branch: None
User: josephsl
Date: 2017-01-10 21:47:08+00:00
Summary: Status info (17.1-dev): Check if Studio is running before invoking
this command.
SPL Controller, Q: if a custom gesture has been defined for this command, the
script will assume that Studio is running (but it might not be running at all).
In case Studio isn't running, catch this and report it (same routine as other
SP lUtils commands where custom gestures can be defined).
Affected #: 1 file
diff --git a/addon/globalPlugins/splUtils/__init__.py
b/addon/globalPlugins/splUtils/__init__.py
index 12856ab..c527bae 100755
--- a/addon/globalPlugins/splUtils/__init__.py
+++ b/addon/globalPlugins/splUtils/__init__.py
@@ -239,6 +239,11 @@ class GlobalPlugin(globalPluginHandler.GlobalPlugin):
self.finish()
def script_statusInfo(self, gesture):
+ SPLWin = user32.FindWindowA("SPLStudio", None) # Used ANSI
version, as Wide char version always returns 0.
+ if not SPLWin:
+ ui.message(_("SPL Studio is not running."))
+ self.finish()
+ return
# For consistency reasons (because of the Studio status bar),
messages in this method will remain in English.
statusInfo = []
# 17.04: For Studio 5.10 and up, announce playback and
automation status.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/0d3714f516b1/
Changeset: 0d3714f516b1
Branch: None
User: josephsl
Date: 2017-01-12 05:00:38+00:00
Summary: Code cleanup: Renamed 'statusAPI' to 'studioAPI' to better reflect
its purpose, use hardcoded constant for user32.WM_USER.
In previous versions of the add-on, statusAPI did what it is supposed to do -
obtain status information. However, with the advent of metadata streaming and
other features, it became clear that this function needed to be renamed to
something else. Because this function is a thin wrapper around
user32.SendMessage with the handle to Studio and message type filled in, and
because this function is now used to set some options, it is more appropriate
to call it 'studioAPI'.
Also, for global plugin, WM_USER will be hardcoded (1024 or 0x400) for
consistency reasons (other places uses the constant directly, while the global
plugin used variable names, causing consistency headahce).
These changes are destined for add-on 17.04 and later.
Affected #: 3 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index dbab6bb..a7e31a3 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -90,10 +90,10 @@ def micAlarmManager(micAlarmWav, micAlarmMessage):
micAlarmT2 = wx.PyTimer(_micAlarmAnnouncer)
micAlarmT2.Start(splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarmInterval"] *
1000)
-# Call SPL API to obtain needed values.
+# Use SPL Studio API to obtain needed values.
# A thin wrapper around user32.SendMessage and calling a callback if defined.
# Offset is used in some time commands.
-def statusAPI(arg, command, func=None, ret=False, offset=None):
+def studioAPI(arg, command, func=None, ret=False, offset=None):
if _SPLWin is None: return
val = sendMessage(_SPLWin, 1024, arg, command)
if ret:
@@ -103,8 +103,8 @@ def statusAPI(arg, command, func=None, ret=False,
offset=None):
# Select a track upon request.
def selectTrack(trackIndex):
- statusAPI(-1, 121)
- statusAPI(trackIndex, 121)
+ studioAPI(-1, 121)
+ studioAPI(trackIndex, 121)
# Category sounds dictionary (key = category, value = tone pitch).
_SPLCategoryTones = {
@@ -922,12 +922,12 @@ class AppModule(appModuleHandler.AppModule):
# Scripts which rely on API.
def script_sayRemainingTime(self, gesture):
- statusAPI(3, 105, self.announceTime, offset=1)
+ studioAPI(3, 105, self.announceTime, offset=1)
# Translators: Input help mode message for a command in Station
Playlist Studio.
script_sayRemainingTime.__doc__=_("Announces the remaining track time.")
def script_sayElapsedTime(self, gesture):
- statusAPI(0, 105, self.announceTime)
+ studioAPI(0, 105, self.announceTime)
# Translators: Input help mode message for a command in Station
Playlist Studio.
script_sayElapsedTime.__doc__=_("Announces the elapsed time for the
currently playing track.")
@@ -1144,7 +1144,7 @@ class AppModule(appModuleHandler.AppModule):
def script_timeRangeFinder(self, gesture):
if self._trackFinderCheck(2):
try:
- d = splmisc.SPLTimeRangeDialog(gui.mainFrame,
api.getFocusObject(), statusAPI)
+ d = splmisc.SPLTimeRangeDialog(gui.mainFrame,
api.getFocusObject(), studioAPI)
gui.mainFrame.prePopup()
d.Raise()
d.Show()
@@ -1263,7 +1263,7 @@ class AppModule(appModuleHandler.AppModule):
global libScanT
if libScanT and libScanT.isAlive() and
api.getForegroundObject().windowClassName == "TTrackInsertForm":
return
- if statusAPI(1, 32, ret=True) < 0:
+ if studioAPI(1, 32, ret=True) < 0:
self.libraryScanning = False
return
time.sleep(0.1)
@@ -1271,10 +1271,10 @@ class AppModule(appModuleHandler.AppModule):
self.libraryScanning = False
return
# 17.04: Library scan may have finished while this thread was
sleeping.
- if statusAPI(1, 32, ret=True) < 0:
+ if studioAPI(1, 32, ret=True) < 0:
self.libraryScanning = False
# Translators: Presented when library scanning is
finished.
- ui.message(_("{itemCount} items in the
library").format(itemCount = statusAPI(0, 32, ret=True)))
+ ui.message(_("{itemCount} items in the
library").format(itemCount = studioAPI(0, 32, ret=True)))
else:
libScanT =
threading.Thread(target=self.libraryScanReporter)
libScanT.daemon = True
@@ -1283,7 +1283,7 @@ class AppModule(appModuleHandler.AppModule):
def libraryScanReporter(self):
scanIter = 0
# 17.04: Use the constant directly, as 5.10 and later provides
a convenient method to detect completion of library scans.
- scanCount = statusAPI(1, 32, ret=True)
+ scanCount = studioAPI(1, 32, ret=True)
while scanCount >= 0:
if not self.libraryScanning: return
time.sleep(1)
@@ -1291,7 +1291,7 @@ class AppModule(appModuleHandler.AppModule):
if api.getForegroundObject().windowClassName ==
"TTrackInsertForm" or not self.libraryScanning:
return
# Scan count may have changed during sleep.
- scanCount = statusAPI(1, 32, ret=True)
+ scanCount = studioAPI(1, 32, ret=True)
if scanCount < 0:
break
scanIter+=1
@@ -1304,7 +1304,7 @@ class AppModule(appModuleHandler.AppModule):
tones.beep(370, 100)
else:
# Translators: Presented after library scan is
done.
- ui.message(_("Scan complete with {itemCount}
items").format(itemCount = statusAPI(0, 32, ret=True)))
+ ui.message(_("Scan complete with {itemCount}
items").format(itemCount = studioAPI(0, 32, ret=True)))
# Take care of library scanning announcement.
def _libraryScanAnnouncer(self, count, announcementType):
@@ -1358,7 +1358,7 @@ class AppModule(appModuleHandler.AppModule):
return
try:
# Passing in the function object is enough to change
the dialog UI.
- d = splconfui.MetadataStreamingDialog(gui.mainFrame,
func=statusAPI)
+ d = splconfui.MetadataStreamingDialog(gui.mainFrame,
func=studioAPI)
gui.mainFrame.prePopup()
d.Raise()
d.Show()
@@ -1387,17 +1387,17 @@ class AppModule(appModuleHandler.AppModule):
# This is also called from playlist duration scripts.
def totalTime(self, start, end):
# Take care of errors such as the following.
- if start < 0 or end > statusAPI(0, 124, ret=True)-1:
+ if start < 0 or end > studioAPI(0, 124, ret=True)-1:
raise ValueError("Track range start or end position out
of range")
return
totalLength = 0
if start == end:
- filename = statusAPI(start, 211, ret=True)
- totalLength = statusAPI(filename, 30, ret=True)
+ filename = studioAPI(start, 211, ret=True)
+ totalLength = studioAPI(filename, 30, ret=True)
else:
for track in xrange(start, end+1):
- filename = statusAPI(track, 211, ret=True)
- totalLength+=statusAPI(filename, 30, ret=True)
+ filename = studioAPI(track, 211, ret=True)
+ totalLength+=studioAPI(filename, 30, ret=True)
return totalLength
# Some handlers for native commands.
@@ -1547,7 +1547,7 @@ class AppModule(appModuleHandler.AppModule):
if self.SPLCurVersion < "5.20":
status =
self.status(self.SPLPlayStatus).getChild(index).name
else:
- status =
self._statusBarMessages[index][statusAPI(index, 39, ret=True)]
+ status =
self._statusBarMessages[index][studioAPI(index, 39, ret=True)]
ui.message(status if
splconfig.SPLConfig["General"]["MessageVerbosity"] == "beginner" else
status.split()[-1])
# The layer commands themselves.
@@ -1570,8 +1570,8 @@ class AppModule(appModuleHandler.AppModule):
def script_sayCartEditStatus(self, gesture):
# 16.12: Because cart edit status also shows cart insert
status, verbosity control will not apply.
if self.productVersion >= "5.20":
- cartEdit = statusAPI(5, 39, ret=True)
- cartInsert = statusAPI(6, 39, ret=True)
+ cartEdit = studioAPI(5, 39, ret=True)
+ cartInsert = studioAPI(6, 39, ret=True)
if cartEdit: ui.message("Cart Edit On")
elif not cartEdit and cartInsert: ui.message("Cart
Insert On")
else: ui.message("Cart Edit Off")
@@ -1579,11 +1579,11 @@ class AppModule(appModuleHandler.AppModule):
ui.message(self.status(self.SPLPlayStatus).getChild(5).name)
def script_sayHourTrackDuration(self, gesture):
- statusAPI(0, 27, self.announceTime)
+ studioAPI(0, 27, self.announceTime)
def script_sayHourRemaining(self, gesture):
# 7.0: Split from playlist remaining script (formerly the
playlist remainder command).
- statusAPI(1, 27, self.announceTime)
+ studioAPI(1, 27, self.announceTime)
def script_sayPlaylistRemainingDuration(self, gesture):
obj = api.getFocusObject() if
api.getForegroundObject().windowClassName == "TStudioForm" else
self._focusedTrack
@@ -1660,7 +1660,7 @@ class AppModule(appModuleHandler.AppModule):
# 16.12: use Studio API if using 5.20.
if self.productVersion >= "5.20":
# Sometimes, hour markers return seconds.999 due to
rounding error, hence this must be taken care of here.
- trackStarts = divmod(statusAPI(3, 27, ret=True), 1000)
+ trackStarts = divmod(studioAPI(3, 27, ret=True), 1000)
# For this method, all three components of time display
(hour, minute, second) must be present.
# In case it is midnight (0.0 but sometimes shown as
86399.999 due to rounding error), just say "midnight".
if trackStarts in ((86399, 999), (0, 0)):
ui.message("00:00:00")
@@ -1674,7 +1674,7 @@ class AppModule(appModuleHandler.AppModule):
# 16.12: Use Studio 5.20 API (faster and more reliable).
if self.productVersion >= "5.20":
# This is the only time hour announcement should not be
used in order to conform to what's displayed on screen.
- self.announceTime(statusAPI(4, 27, ret=True),
includeHours=False)
+ self.announceTime(studioAPI(4, 27, ret=True),
includeHours=False)
else:
obj = self.status(self.SPLScheduledToPlay).firstChild
ui.message(obj.name)
@@ -1696,8 +1696,8 @@ class AppModule(appModuleHandler.AppModule):
def script_libraryScanMonitor(self, gesture):
if not self.libraryScanning:
- if statusAPI(1, 32, ret=True) < 0:
- ui.message(_("{itemCount} items in the
library").format(itemCount = statusAPI(0, 32, ret=True)))
+ if studioAPI(1, 32, ret=True) < 0:
+ ui.message(_("{itemCount} items in the
library").format(itemCount = studioAPI(0, 32, ret=True)))
return
self.libraryScanning = True
# Translators: Presented when attempting to start
library scan.
@@ -1792,7 +1792,7 @@ class AppModule(appModuleHandler.AppModule):
# Gesture(s) for the following script cannot be changed by users.
def script_metadataEnabled(self, gesture):
url = int(gesture.displayName[-1])
- if statusAPI(url, 36, ret=True):
+ if studioAPI(url, 36, ret=True):
# 0 is DSP encoder status, others are servers.
if url:
# Translators: Status message for metadata
streaming.
diff --git a/addon/globalPlugins/splUtils/__init__.py
b/addon/globalPlugins/splUtils/__init__.py
index c527bae..15564ad 100755
--- a/addon/globalPlugins/splUtils/__init__.py
+++ b/addon/globalPlugins/splUtils/__init__.py
@@ -30,7 +30,6 @@ def finally_(func, final):
# SPL Studio uses WM messages to send and receive data, similar to Winamp (see
NVDA sources/appModules/winamp.py for more information).
user32 = winUser.user32 # user32.dll.
SPLWin = 0 # A handle to studio window.
-SPLMSG = winUser.WM_USER
# Various SPL IPC tags.
SPLVersion = 2
@@ -148,70 +147,70 @@ class GlobalPlugin(globalPluginHandler.GlobalPlugin):
# The layer commands themselves. Calls user32.SendMessage method for
each script.
def script_automateOn(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,1,SPLAutomate)
+ winUser.sendMessage(SPLWin,1024,1,SPLAutomate)
self.finish()
def script_automateOff(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,0,SPLAutomate)
+ winUser.sendMessage(SPLWin,1024,0,SPLAutomate)
self.finish()
def script_micOn(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,1,SPLMic)
+ winUser.sendMessage(SPLWin,1024,1,SPLMic)
self.finish()
def script_micOff(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,0,SPLMic)
+ winUser.sendMessage(SPLWin,1024,0,SPLMic)
self.finish()
def script_micNoFade(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,2,SPLMic)
+ winUser.sendMessage(SPLWin,1024,2,SPLMic)
self.finish()
def script_lineInOn(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,1,SPLLineIn)
+ winUser.sendMessage(SPLWin,1024,1,SPLLineIn)
self.finish()
def script_lineInOff(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,0,SPLLineIn)
+ winUser.sendMessage(SPLWin,1024,0,SPLLineIn)
self.finish()
def script_stopFade(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,0,SPLStop)
+ winUser.sendMessage(SPLWin,1024,0,SPLStop)
self.finish()
def script_stopInstant(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,1,SPLStop)
+ winUser.sendMessage(SPLWin,1024,1,SPLStop)
self.finish()
def script_play(self, gesture):
- winUser.sendMessage(SPLWin, SPLMSG, 0, SPLPlay)
+ winUser.sendMessage(SPLWin, 1024, 0, SPLPlay)
self.finish()
def script_pause(self, gesture):
- playingNow = winUser.sendMessage(SPLWin, SPLMSG, 0,
SPL_TrackPlaybackStatus)
+ playingNow = winUser.sendMessage(SPLWin, 1024, 0,
SPL_TrackPlaybackStatus)
# Translators: Presented when no track is playing in Station
Playlist Studio.
if not playingNow: ui.message(_("There is no track playing. Try
pausing while a track is playing."))
- elif playingNow == 3: winUser.sendMessage(SPLWin, SPLMSG, 0,
SPLPause)
- else: winUser.sendMessage(SPLWin, SPLMSG, 1, SPLPause)
+ elif playingNow == 3: winUser.sendMessage(SPLWin, 1024, 0,
SPLPause)
+ else: winUser.sendMessage(SPLWin, 1024, 1, SPLPause)
self.finish()
def script_libraryScanProgress(self, gesture):
- scanned = winUser.sendMessage(SPLWin, SPLMSG, 1,
SPLLibraryScanCount)
+ scanned = winUser.sendMessage(SPLWin, 1024, 1,
SPLLibraryScanCount)
if scanned >= 0:
# Translators: Announces number of items in the
Studio's track library (example: 1000 items scanned).
ui.message(_("Scan in progress with {itemCount} items
scanned").format(itemCount = scanned))
else:
# Translators: Announces number of items in the
Studio's track library (example: 1000 items scanned).
- ui.message(_("Scan complete with {itemCount} items
scanned").format(itemCount = winUser.sendMessage(SPLWin, SPLMSG, 0,
SPLLibraryScanCount)))
+ ui.message(_("Scan complete with {itemCount} items
scanned").format(itemCount = winUser.sendMessage(SPLWin, 1024, 0,
SPLLibraryScanCount)))
self.finish()
def script_listenerCount(self, gesture):
# Translators: Announces number of stream listeners.
- ui.message(_("Listener count:
{listenerCount}").format(listenerCount = winUser.sendMessage(SPLWin, SPLMSG, 0,
SPLListenerCount)))
+ ui.message(_("Listener count:
{listenerCount}").format(listenerCount = winUser.sendMessage(SPLWin, 1024, 0,
SPLListenerCount)))
self.finish()
def script_remainingTime(self, gesture):
- remainingTime = winUser.sendMessage(SPLWin, SPLMSG, 3,
SPLCurTrackPlaybackTime)
+ remainingTime = winUser.sendMessage(SPLWin, 1024, 3,
SPLCurTrackPlaybackTime)
# Translators: Presented when no track is playing in Station
Playlist Studio.
if remainingTime < 0: ui.message(_("There is no track
playing."))
else:
@@ -247,19 +246,19 @@ class GlobalPlugin(globalPluginHandler.GlobalPlugin):
# For consistency reasons (because of the Studio status bar),
messages in this method will remain in English.
statusInfo = []
# 17.04: For Studio 5.10 and up, announce playback and
automation status.
- playingNow = winUser.sendMessage(SPLWin, SPLMSG, 0,
SPL_TrackPlaybackStatus)
+ playingNow = winUser.sendMessage(SPLWin, 1024, 0,
SPL_TrackPlaybackStatus)
statusInfo.append("Play status: playing" if playingNow else
"Play status: stopped")
# For automation, Studio 5.11 and earlier does not have an easy
way to detect this flag, thus resort to using playback status.
- if winUser.sendMessage(SPLWin, SPLMSG, 0, SPLVersion) < 520:
+ if winUser.sendMessage(SPLWin, 1024, 0, SPLVersion) < 520:
statusInfo.append("Automation on" if playingNow == 2
else "Automation off")
else:
- statusInfo.append("Automation on" if
winUser.sendMessage(SPLWin, SPLMSG, 1, SPLStatusInfo) else "Automation off")
+ statusInfo.append("Automation on" if
winUser.sendMessage(SPLWin, 1024, 1, SPLStatusInfo) else "Automation off")
# 5.20 and later.
- statusInfo.append("Microphone on" if
winUser.sendMessage(SPLWin, SPLMSG, 2, SPLStatusInfo) else "Microphone off")
- statusInfo.append("Line-inon" if
winUser.sendMessage(SPLWin, SPLMSG, 3, SPLStatusInfo) else "Line-in off")
- statusInfo.append("Record to file on" if
winUser.sendMessage(SPLWin, SPLMSG, 4, SPLStatusInfo) else "Record to file off")
- cartEdit = winUser.sendMessage(SPLWin, SPLMSG, 5,
SPLStatusInfo)
- cartInsert = winUser.sendMessage(SPLWin, SPLMSG, 6,
SPLStatusInfo)
+ statusInfo.append("Microphone on" if
winUser.sendMessage(SPLWin, 1024, 2, SPLStatusInfo) else "Microphone off")
+ statusInfo.append("Line-inon" if
winUser.sendMessage(SPLWin, 1024, 3, SPLStatusInfo) else "Line-in off")
+ statusInfo.append("Record to file on" if
winUser.sendMessage(SPLWin, 1024, 4, SPLStatusInfo) else "Record to file off")
+ cartEdit = winUser.sendMessage(SPLWin, 1024, 5,
SPLStatusInfo)
+ cartInsert = winUser.sendMessage(SPLWin, 1024, 6,
SPLStatusInfo)
if cartEdit: statusInfo.append("Cart edit on")
elif not cartEdit and cartInsert:
statusInfo.append("Cart insert on")
else: statusInfo.append("Cart edit off")
diff --git a/addon/globalPlugins/splUtils/encoders.py
b/addon/globalPlugins/splUtils/encoders.py
index 9e3b876..8143753 100755
--- a/addon/globalPlugins/splUtils/encoders.py
+++ b/addon/globalPlugins/splUtils/encoders.py
@@ -19,7 +19,6 @@ import wx
# SPL Studio uses WM messages to send and receive data, similar to Winamp (see
NVDA sources/appModules/winamp.py for more information).
user32 = winUser.user32 # user32.dll.
SPLWin = 0 # A handle to studio window.
-SPLMSG = winUser.WM_USER
# Various SPL IPC tags.
SPLPlay = 12
@@ -526,8 +525,8 @@ class SAMEncoder(Encoder):
user32.SetForegroundWindow(user32.FindWindowA("TStudioForm", None))
if self.playAfterConnecting and not encoding:
# Do not interupt the currently playing
track.
- if winUser.sendMessage(SPLWin, SPLMSG,
0, SPL_TrackPlaybackStatus) == 0:
- winUser.sendMessage(SPLWin,
SPLMSG, 0, SPLPlay)
+ if winUser.sendMessage(SPLWin, 1024, 0,
SPL_TrackPlaybackStatus) == 0:
+ winUser.sendMessage(SPLWin,
1024, 0, SPLPlay)
if not encoding: encoding = True
else:
if alreadyEncoding: alreadyEncoding = False
@@ -721,8 +720,8 @@ class SPLEncoder(Encoder):
if self.focusToStudio and not connected:
user32.SetForegroundWindow(user32.FindWindowA("TStudioForm", None))
if self.playAfterConnecting and not connected:
- if winUser.sendMessage(SPLWin, SPLMSG,
0, SPL_TrackPlaybackStatus) == 0:
- winUser.sendMessage(SPLWin,
SPLMSG, 0, SPLPlay)
+ if winUser.sendMessage(SPLWin, 1024, 0,
SPL_TrackPlaybackStatus) == 0:
+ winUser.sendMessage(SPLWin,
1024, 0, SPLPlay)
if not connected: connected = True
elif "Unable to connect" in messageCache or "Failed" in
messageCache or statChild.name == "AutoConnect stopped.":
if connected: connected = False
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/479206f6b05a/
Changeset: 479206f6b05a
Branch: None
User: josephsl
Date: 2017-01-12 05:02:34+00:00
Summary: Merge branch 'master' into plSnaps
Affected #: 6 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 555dbab..8f3320e 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -90,10 +90,10 @@ def micAlarmManager(micAlarmWav, micAlarmMessage):
micAlarmT2 = wx.PyTimer(_micAlarmAnnouncer)
micAlarmT2.Start(splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarmInterval"] *
1000)
-# Call SPL API to obtain needed values.
+# Use SPL Studio API to obtain needed values.
# A thin wrapper around user32.SendMessage and calling a callback if defined.
# Offset is used in some time commands.
-def statusAPI(arg, command, func=None, ret=False, offset=None):
+def studioAPI(arg, command, func=None, ret=False, offset=None):
if _SPLWin is None: return
val = sendMessage(_SPLWin, 1024, arg, command)
if ret:
@@ -103,8 +103,8 @@ def statusAPI(arg, command, func=None, ret=False,
offset=None):
# Select a track upon request.
def selectTrack(trackIndex):
- statusAPI(-1, 121)
- statusAPI(trackIndex, 121)
+ studioAPI(-1, 121)
+ studioAPI(trackIndex, 121)
# Category sounds dictionary (key = category, value = tone pitch).
_SPLCategoryTones = {
@@ -925,12 +925,12 @@ class AppModule(appModuleHandler.AppModule):
# Scripts which rely on API.
def script_sayRemainingTime(self, gesture):
- statusAPI(3, 105, self.announceTime, offset=1)
+ studioAPI(3, 105, self.announceTime, offset=1)
# Translators: Input help mode message for a command in Station
Playlist Studio.
script_sayRemainingTime.__doc__=_("Announces the remaining track time.")
def script_sayElapsedTime(self, gesture):
- statusAPI(0, 105, self.announceTime)
+ studioAPI(0, 105, self.announceTime)
# Translators: Input help mode message for a command in Station
Playlist Studio.
script_sayElapsedTime.__doc__=_("Announces the elapsed time for the
currently playing track.")
@@ -1147,7 +1147,7 @@ class AppModule(appModuleHandler.AppModule):
def script_timeRangeFinder(self, gesture):
if self._trackFinderCheck(2):
try:
- d = splmisc.SPLTimeRangeDialog(gui.mainFrame,
api.getFocusObject(), statusAPI)
+ d = splmisc.SPLTimeRangeDialog(gui.mainFrame,
api.getFocusObject(), studioAPI)
gui.mainFrame.prePopup()
d.Raise()
d.Show()
@@ -1266,7 +1266,7 @@ class AppModule(appModuleHandler.AppModule):
global libScanT
if libScanT and libScanT.isAlive() and
api.getForegroundObject().windowClassName == "TTrackInsertForm":
return
- if statusAPI(1, 32, ret=True) < 0:
+ if studioAPI(1, 32, ret=True) < 0:
self.libraryScanning = False
return
time.sleep(0.1)
@@ -1274,10 +1274,10 @@ class AppModule(appModuleHandler.AppModule):
self.libraryScanning = False
return
# 17.04: Library scan may have finished while this thread was
sleeping.
- if statusAPI(1, 32, ret=True) < 0:
+ if studioAPI(1, 32, ret=True) < 0:
self.libraryScanning = False
# Translators: Presented when library scanning is
finished.
- ui.message(_("{itemCount} items in the
library").format(itemCount = statusAPI(0, 32, ret=True)))
+ ui.message(_("{itemCount} items in the
library").format(itemCount = studioAPI(0, 32, ret=True)))
else:
libScanT =
threading.Thread(target=self.libraryScanReporter)
libScanT.daemon = True
@@ -1286,7 +1286,7 @@ class AppModule(appModuleHandler.AppModule):
def libraryScanReporter(self):
scanIter = 0
# 17.04: Use the constant directly, as 5.10 and later provides
a convenient method to detect completion of library scans.
- scanCount = statusAPI(1, 32, ret=True)
+ scanCount = studioAPI(1, 32, ret=True)
while scanCount >= 0:
if not self.libraryScanning: return
time.sleep(1)
@@ -1294,7 +1294,7 @@ class AppModule(appModuleHandler.AppModule):
if api.getForegroundObject().windowClassName ==
"TTrackInsertForm" or not self.libraryScanning:
return
# Scan count may have changed during sleep.
- scanCount = statusAPI(1, 32, ret=True)
+ scanCount = studioAPI(1, 32, ret=True)
if scanCount < 0:
break
scanIter+=1
@@ -1307,7 +1307,7 @@ class AppModule(appModuleHandler.AppModule):
tones.beep(370, 100)
else:
# Translators: Presented after library scan is
done.
- ui.message(_("Scan complete with {itemCount}
items").format(itemCount = statusAPI(0, 32, ret=True)))
+ ui.message(_("Scan complete with {itemCount}
items").format(itemCount = studioAPI(0, 32, ret=True)))
# Take care of library scanning announcement.
def _libraryScanAnnouncer(self, count, announcementType):
@@ -1361,7 +1361,7 @@ class AppModule(appModuleHandler.AppModule):
return
try:
# Passing in the function object is enough to change
the dialog UI.
- d = splconfui.MetadataStreamingDialog(gui.mainFrame,
func=statusAPI)
+ d = splconfui.MetadataStreamingDialog(gui.mainFrame,
func=studioAPI)
gui.mainFrame.prePopup()
d.Raise()
d.Show()
@@ -1407,17 +1407,17 @@ class AppModule(appModuleHandler.AppModule):
# Segue version of this will be used in some places (the below is the
raw duration).)
def playlistDurationRaw(self, start, end):
# Take care of errors such as the following.
- if start < 0 or end > statusAPI(0, 124, ret=True)-1:
+ if start < 0 or end > studioAPI(0, 124, ret=True)-1:
raise ValueError("Track range start or end position out
of range")
return
totalLength = 0
if start == end:
- filename = statusAPI(start, 211, ret=True)
- totalLength = statusAPI(filename, 30, ret=True)
+ filename = studioAPI(start, 211, ret=True)
+ totalLength = studioAPI(filename, 30, ret=True)
else:
for track in xrange(start, end+1):
- filename = statusAPI(track, 211, ret=True)
- totalLength+=statusAPI(filename, 30, ret=True)
+ filename = studioAPI(track, 211, ret=True)
+ totalLength+=studioAPI(filename, 30, ret=True)
return totalLength
# Playlist snapshots
@@ -1659,7 +1659,7 @@ class AppModule(appModuleHandler.AppModule):
if self.SPLCurVersion < "5.20":
status =
self.status(self.SPLPlayStatus).getChild(index).name
else:
- status =
self._statusBarMessages[index][statusAPI(index, 39, ret=True)]
+ status =
self._statusBarMessages[index][studioAPI(index, 39, ret=True)]
ui.message(status if
splconfig.SPLConfig["General"]["MessageVerbosity"] == "beginner" else
status.split()[-1])
# The layer commands themselves.
@@ -1682,8 +1682,8 @@ class AppModule(appModuleHandler.AppModule):
def script_sayCartEditStatus(self, gesture):
# 16.12: Because cart edit status also shows cart insert
status, verbosity control will not apply.
if self.productVersion >= "5.20":
- cartEdit = statusAPI(5, 39, ret=True)
- cartInsert = statusAPI(6, 39, ret=True)
+ cartEdit = studioAPI(5, 39, ret=True)
+ cartInsert = studioAPI(6, 39, ret=True)
if cartEdit: ui.message("Cart Edit On")
elif not cartEdit and cartInsert: ui.message("Cart
Insert On")
else: ui.message("Cart Edit Off")
@@ -1691,11 +1691,11 @@ class AppModule(appModuleHandler.AppModule):
ui.message(self.status(self.SPLPlayStatus).getChild(5).name)
def script_sayHourTrackDuration(self, gesture):
- statusAPI(0, 27, self.announceTime)
+ studioAPI(0, 27, self.announceTime)
def script_sayHourRemaining(self, gesture):
# 7.0: Split from playlist remaining script (formerly the
playlist remainder command).
- statusAPI(1, 27, self.announceTime)
+ studioAPI(1, 27, self.announceTime)
def script_sayPlaylistRemainingDuration(self, gesture):
obj = api.getFocusObject() if
api.getForegroundObject().windowClassName == "TStudioForm" else
self._focusedTrack
@@ -1763,7 +1763,7 @@ class AppModule(appModuleHandler.AppModule):
# 16.12: use Studio API if using 5.20.
if self.productVersion >= "5.20":
# Sometimes, hour markers return seconds.999 due to
rounding error, hence this must be taken care of here.
- trackStarts = divmod(statusAPI(3, 27, ret=True), 1000)
+ trackStarts = divmod(studioAPI(3, 27, ret=True), 1000)
# For this method, all three components of time display
(hour, minute, second) must be present.
# In case it is midnight (0.0 but sometimes shown as
86399.999 due to rounding error), just say "midnight".
if trackStarts in ((86399, 999), (0, 0)):
ui.message("00:00:00")
@@ -1777,7 +1777,7 @@ class AppModule(appModuleHandler.AppModule):
# 16.12: Use Studio 5.20 API (faster and more reliable).
if self.productVersion >= "5.20":
# This is the only time hour announcement should not be
used in order to conform to what's displayed on screen.
- self.announceTime(statusAPI(4, 27, ret=True),
includeHours=False)
+ self.announceTime(studioAPI(4, 27, ret=True),
includeHours=False)
else:
obj = self.status(self.SPLScheduledToPlay).firstChild
ui.message(obj.name)
@@ -1799,8 +1799,8 @@ class AppModule(appModuleHandler.AppModule):
def script_libraryScanMonitor(self, gesture):
if not self.libraryScanning:
- if statusAPI(1, 32, ret=True) < 0:
- ui.message(_("{itemCount} items in the
library").format(itemCount = statusAPI(0, 32, ret=True)))
+ if studioAPI(1, 32, ret=True) < 0:
+ ui.message(_("{itemCount} items in the
library").format(itemCount = studioAPI(0, 32, ret=True)))
return
self.libraryScanning = True
# Translators: Presented when attempting to start
library scan.
@@ -1906,7 +1906,7 @@ class AppModule(appModuleHandler.AppModule):
# Gesture(s) for the following script cannot be changed by users.
def script_metadataEnabled(self, gesture):
url = int(gesture.displayName[-1])
- if statusAPI(url, 36, ret=True):
+ if studioAPI(url, 36, ret=True):
# 0 is DSP encoder status, others are servers.
if url:
# Translators: Status message for metadata
streaming.
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 05c18a1..c58c6b2 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -31,7 +31,7 @@ except ImportError:
SPLIni = os.path.join(globalVars.appArgs.configPath, "splstudio.ini")
SPLProfiles = os.path.join(globalVars.appArgs.configPath, "addons",
"stationPlaylist", "profiles")
# New (7.0) style config.
-confspec7 = ConfigObj(StringIO("""
+confspec = ConfigObj(StringIO("""
[General]
BeepAnnounce = boolean(default=false)
MessageVerbosity = option("beginner", "advanced", default="beginner")
@@ -83,13 +83,13 @@ UpdateInterval = integer(min=1, max=30, default=7)
AudioDuckingReminder = boolean(default=true)
WelcomeDialog = boolean(default=true)
"""), encoding="UTF-8", list_values=False)
-confspec7.newlines = "\r\n"
+confspec.newlines = "\r\n"
SPLConfig = None
# The following settings can be changed in profiles:
-_mutatableSettings7=("IntroOutroAlarms", "MicrophoneAlarm",
"MetadataStreaming", "ColumnAnnouncement")
+_mutatableSettings=("IntroOutroAlarms", "MicrophoneAlarm",
"MetadataStreaming", "ColumnAnnouncement")
# 7.0: Profile-specific confspec (might be removed once a more optimal way to
validate sections is found).
# Dictionary comprehension is better here.
-confspecprofiles = {sect:key for sect, key in confspec7.iteritems() if sect in
_mutatableSettings7}
+confspecprofiles = {sect:key for sect, key in confspec.iteritems() if sect in
_mutatableSettings}
# 8.0: Run-time config storage and management will use ConfigHub data
structure, a subclass of chain map.
# A chain map allows a dictionary to look up predefined mappings to locate a
key.
@@ -165,10 +165,10 @@ class ConfigHub(ChainMap):
# 7.0: What if profiles have parsing errors?
# If so, reset everything back to factory defaults.
try:
- SPLConfigCheckpoint = ConfigObj(path, configspec =
confspec7 if prefill else confspecprofiles, encoding="UTF-8")
+ SPLConfigCheckpoint = ConfigObj(path, configspec =
confspec if prefill else confspecprofiles, encoding="UTF-8")
except:
open(path, "w").close()
- SPLConfigCheckpoint = ConfigObj(path, configspec =
confspec7 if prefill else confspecprofiles, encoding="UTF-8")
+ SPLConfigCheckpoint = ConfigObj(path, configspec =
confspec if prefill else confspecprofiles, encoding="UTF-8")
_configLoadStatus[profileName] = "fileReset"
# 5.2 and later: check to make sure all values are correct.
# 7.0: Make sure errors are displayed as config keys are now
sections and may need to go through subkeys.
@@ -262,7 +262,7 @@ class ConfigHub(ChainMap):
def __delitem__(self, key):
# Consult profile-specific key first before deleting anything.
- pos = 0 if key in _mutatableSettings7 else [profile.name for
profile in self.maps].index(_("Normal Profile"))
+ pos = 0 if key in _mutatableSettings else [profile.name for
profile in self.maps].index(_("Normal Profile"))
try:
del self.maps[pos][key]
except KeyError:
@@ -377,7 +377,7 @@ class ConfigHub(ChainMap):
# Default config spec container.
# To be moved to a different place in 8.0.
-_SPLDefaults = ConfigObj(None, configspec = confspec7, encoding="UTF-8")
+_SPLDefaults = ConfigObj(None, configspec = confspec, encoding="UTF-8")
_val = Validator()
_SPLDefaults.validate(_val, copy=True)
@@ -672,7 +672,7 @@ def getProfileByName(name):
# Setting complete flag controls whether profile-specific settings are applied
(true otherwise, only set when resetting profiles).
# 8.0: Simplified thanks to in-place swapping.
def copyProfile(sourceProfile, targetProfile, complete=False):
- for section in sourceProfile.keys() if complete else
_mutatableSettings7:
+ for section in sourceProfile.keys() if complete else _mutatableSettings:
targetProfile[section] = dict(sourceProfile[section])
# Last but not least...
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index 0981885..ba9a496 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -625,7 +625,7 @@ class NewProfileDialog(wx.Dialog):
# LTS optimization: just build base profile dictionary here if
copying a profile.
if self.copy:
baseConfig =
splconfig.getProfileByName(self.baseProfiles.GetStringSelection())
- baseProfile = {sect:key for sect, key in
baseConfig.iteritems() if sect in splconfig._mutatableSettings7}
+ baseProfile = {sect:key for sect, key in
baseConfig.iteritems() if sect in splconfig._mutatableSettings}
else: baseProfile = None
splconfig.SPLConfig.createProfile(newProfilePath,
profileName=name, parent=baseProfile)
parent.profileNames.append(name)
diff --git a/addon/globalPlugins/splUtils/__init__.py
b/addon/globalPlugins/splUtils/__init__.py
index 12856ab..15564ad 100755
--- a/addon/globalPlugins/splUtils/__init__.py
+++ b/addon/globalPlugins/splUtils/__init__.py
@@ -30,7 +30,6 @@ def finally_(func, final):
# SPL Studio uses WM messages to send and receive data, similar to Winamp (see
NVDA sources/appModules/winamp.py for more information).
user32 = winUser.user32 # user32.dll.
SPLWin = 0 # A handle to studio window.
-SPLMSG = winUser.WM_USER
# Various SPL IPC tags.
SPLVersion = 2
@@ -148,70 +147,70 @@ class GlobalPlugin(globalPluginHandler.GlobalPlugin):
# The layer commands themselves. Calls user32.SendMessage method for
each script.
def script_automateOn(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,1,SPLAutomate)
+ winUser.sendMessage(SPLWin,1024,1,SPLAutomate)
self.finish()
def script_automateOff(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,0,SPLAutomate)
+ winUser.sendMessage(SPLWin,1024,0,SPLAutomate)
self.finish()
def script_micOn(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,1,SPLMic)
+ winUser.sendMessage(SPLWin,1024,1,SPLMic)
self.finish()
def script_micOff(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,0,SPLMic)
+ winUser.sendMessage(SPLWin,1024,0,SPLMic)
self.finish()
def script_micNoFade(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,2,SPLMic)
+ winUser.sendMessage(SPLWin,1024,2,SPLMic)
self.finish()
def script_lineInOn(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,1,SPLLineIn)
+ winUser.sendMessage(SPLWin,1024,1,SPLLineIn)
self.finish()
def script_lineInOff(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,0,SPLLineIn)
+ winUser.sendMessage(SPLWin,1024,0,SPLLineIn)
self.finish()
def script_stopFade(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,0,SPLStop)
+ winUser.sendMessage(SPLWin,1024,0,SPLStop)
self.finish()
def script_stopInstant(self, gesture):
- winUser.sendMessage(SPLWin,SPLMSG,1,SPLStop)
+ winUser.sendMessage(SPLWin,1024,1,SPLStop)
self.finish()
def script_play(self, gesture):
- winUser.sendMessage(SPLWin, SPLMSG, 0, SPLPlay)
+ winUser.sendMessage(SPLWin, 1024, 0, SPLPlay)
self.finish()
def script_pause(self, gesture):
- playingNow = winUser.sendMessage(SPLWin, SPLMSG, 0,
SPL_TrackPlaybackStatus)
+ playingNow = winUser.sendMessage(SPLWin, 1024, 0,
SPL_TrackPlaybackStatus)
# Translators: Presented when no track is playing in Station
Playlist Studio.
if not playingNow: ui.message(_("There is no track playing. Try
pausing while a track is playing."))
- elif playingNow == 3: winUser.sendMessage(SPLWin, SPLMSG, 0,
SPLPause)
- else: winUser.sendMessage(SPLWin, SPLMSG, 1, SPLPause)
+ elif playingNow == 3: winUser.sendMessage(SPLWin, 1024, 0,
SPLPause)
+ else: winUser.sendMessage(SPLWin, 1024, 1, SPLPause)
self.finish()
def script_libraryScanProgress(self, gesture):
- scanned = winUser.sendMessage(SPLWin, SPLMSG, 1,
SPLLibraryScanCount)
+ scanned = winUser.sendMessage(SPLWin, 1024, 1,
SPLLibraryScanCount)
if scanned >= 0:
# Translators: Announces number of items in the
Studio's track library (example: 1000 items scanned).
ui.message(_("Scan in progress with {itemCount} items
scanned").format(itemCount = scanned))
else:
# Translators: Announces number of items in the
Studio's track library (example: 1000 items scanned).
- ui.message(_("Scan complete with {itemCount} items
scanned").format(itemCount = winUser.sendMessage(SPLWin, SPLMSG, 0,
SPLLibraryScanCount)))
+ ui.message(_("Scan complete with {itemCount} items
scanned").format(itemCount = winUser.sendMessage(SPLWin, 1024, 0,
SPLLibraryScanCount)))
self.finish()
def script_listenerCount(self, gesture):
# Translators: Announces number of stream listeners.
- ui.message(_("Listener count:
{listenerCount}").format(listenerCount = winUser.sendMessage(SPLWin, SPLMSG, 0,
SPLListenerCount)))
+ ui.message(_("Listener count:
{listenerCount}").format(listenerCount = winUser.sendMessage(SPLWin, 1024, 0,
SPLListenerCount)))
self.finish()
def script_remainingTime(self, gesture):
- remainingTime = winUser.sendMessage(SPLWin, SPLMSG, 3,
SPLCurTrackPlaybackTime)
+ remainingTime = winUser.sendMessage(SPLWin, 1024, 3,
SPLCurTrackPlaybackTime)
# Translators: Presented when no track is playing in Station
Playlist Studio.
if remainingTime < 0: ui.message(_("There is no track
playing."))
else:
@@ -239,22 +238,27 @@ class GlobalPlugin(globalPluginHandler.GlobalPlugin):
self.finish()
def script_statusInfo(self, gesture):
+ SPLWin = user32.FindWindowA("SPLStudio", None) # Used ANSI
version, as Wide char version always returns 0.
+ if not SPLWin:
+ ui.message(_("SPL Studio is not running."))
+ self.finish()
+ return
# For consistency reasons (because of the Studio status bar),
messages in this method will remain in English.
statusInfo = []
# 17.04: For Studio 5.10 and up, announce playback and
automation status.
- playingNow = winUser.sendMessage(SPLWin, SPLMSG, 0,
SPL_TrackPlaybackStatus)
+ playingNow = winUser.sendMessage(SPLWin, 1024, 0,
SPL_TrackPlaybackStatus)
statusInfo.append("Play status: playing" if playingNow else
"Play status: stopped")
# For automation, Studio 5.11 and earlier does not have an easy
way to detect this flag, thus resort to using playback status.
- if winUser.sendMessage(SPLWin, SPLMSG, 0, SPLVersion) < 520:
+ if winUser.sendMessage(SPLWin, 1024, 0, SPLVersion) < 520:
statusInfo.append("Automation on" if playingNow == 2
else "Automation off")
else:
- statusInfo.append("Automation on" if
winUser.sendMessage(SPLWin, SPLMSG, 1, SPLStatusInfo) else "Automation off")
+ statusInfo.append("Automation on" if
winUser.sendMessage(SPLWin, 1024, 1, SPLStatusInfo) else "Automation off")
# 5.20 and later.
- statusInfo.append("Microphone on" if
winUser.sendMessage(SPLWin, SPLMSG, 2, SPLStatusInfo) else "Microphone off")
- statusInfo.append("Line-inon" if
winUser.sendMessage(SPLWin, SPLMSG, 3, SPLStatusInfo) else "Line-in off")
- statusInfo.append("Record to file on" if
winUser.sendMessage(SPLWin, SPLMSG, 4, SPLStatusInfo) else "Record to file off")
- cartEdit = winUser.sendMessage(SPLWin, SPLMSG, 5,
SPLStatusInfo)
- cartInsert = winUser.sendMessage(SPLWin, SPLMSG, 6,
SPLStatusInfo)
+ statusInfo.append("Microphone on" if
winUser.sendMessage(SPLWin, 1024, 2, SPLStatusInfo) else "Microphone off")
+ statusInfo.append("Line-inon" if
winUser.sendMessage(SPLWin, 1024, 3, SPLStatusInfo) else "Line-in off")
+ statusInfo.append("Record to file on" if
winUser.sendMessage(SPLWin, 1024, 4, SPLStatusInfo) else "Record to file off")
+ cartEdit = winUser.sendMessage(SPLWin, 1024, 5,
SPLStatusInfo)
+ cartInsert = winUser.sendMessage(SPLWin, 1024, 6,
SPLStatusInfo)
if cartEdit: statusInfo.append("Cart edit on")
elif not cartEdit and cartInsert:
statusInfo.append("Cart insert on")
else: statusInfo.append("Cart edit off")
diff --git a/addon/globalPlugins/splUtils/encoders.py
b/addon/globalPlugins/splUtils/encoders.py
index 9e3b876..8143753 100755
--- a/addon/globalPlugins/splUtils/encoders.py
+++ b/addon/globalPlugins/splUtils/encoders.py
@@ -19,7 +19,6 @@ import wx
# SPL Studio uses WM messages to send and receive data, similar to Winamp (see
NVDA sources/appModules/winamp.py for more information).
user32 = winUser.user32 # user32.dll.
SPLWin = 0 # A handle to studio window.
-SPLMSG = winUser.WM_USER
# Various SPL IPC tags.
SPLPlay = 12
@@ -526,8 +525,8 @@ class SAMEncoder(Encoder):
user32.SetForegroundWindow(user32.FindWindowA("TStudioForm", None))
if self.playAfterConnecting and not encoding:
# Do not interupt the currently playing
track.
- if winUser.sendMessage(SPLWin, SPLMSG,
0, SPL_TrackPlaybackStatus) == 0:
- winUser.sendMessage(SPLWin,
SPLMSG, 0, SPLPlay)
+ if winUser.sendMessage(SPLWin, 1024, 0,
SPL_TrackPlaybackStatus) == 0:
+ winUser.sendMessage(SPLWin,
1024, 0, SPLPlay)
if not encoding: encoding = True
else:
if alreadyEncoding: alreadyEncoding = False
@@ -721,8 +720,8 @@ class SPLEncoder(Encoder):
if self.focusToStudio and not connected:
user32.SetForegroundWindow(user32.FindWindowA("TStudioForm", None))
if self.playAfterConnecting and not connected:
- if winUser.sendMessage(SPLWin, SPLMSG,
0, SPL_TrackPlaybackStatus) == 0:
- winUser.sendMessage(SPLWin,
SPLMSG, 0, SPLPlay)
+ if winUser.sendMessage(SPLWin, 1024, 0,
SPL_TrackPlaybackStatus) == 0:
+ winUser.sendMessage(SPLWin,
1024, 0, SPLPlay)
if not connected: connected = True
elif "Unable to connect" in messageCache or "Failed" in
messageCache or statChild.name == "AutoConnect stopped.":
if connected: connected = False
diff --git a/readme.md b/readme.md
index 0f0378f..29f22f3 100755
--- a/readme.md
+++ b/readme.md
@@ -24,6 +24,7 @@ IMPORTANT: This add-on requires NVDA 2016.4 or later and
StationPlaylist Studio
* Alt+NVDA+R from Studio window: Steps through library scan announcement
settings.
* Control+Shift+X from Studio window: Steps through braille timer settings.
* Control+Alt+right/left arrow (while focused on a track): Announce
next/previous track column.
+* Control+Alt+down/up arrow (while focused on a track): Move to next or
previous track and announce specific columns (unavailable in add-on 15.x).
* Control+NVDA+1 through 0 (6 for Studio 5.0x): Announce column content for a
specified column.
* Alt+NVDA+C while focused on a track: announces track comments if any.
* Alt+NVDA+0 from Studio window: Opens the Studio add-on configuration dialog.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/545caf68bed4/
Changeset: 545caf68bed4
Branch: None
User: josephsl
Date: 2017-01-12 05:28:19+00:00
Summary: Playlist snapshots (17.1-dev): Allow artist count to be
configurable.
Just like category count, allow users to configure announcement of top
artistsfor a playlist, while also incorporating studioAPI function renaming
logic from master branch.
Affected #: 2 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 8f3320e..eb69d2d 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1456,7 +1456,7 @@ class AppModule(appModuleHandler.AppModule):
totalDuration += (int(hms[-2])*60) +
int(hms[-1])
if len(hms) == 3: totalDuration +=
int(hms[0])*3600
obj = obj.next
- if end is None: snapshot["PlaylistTrackCount"] = statusAPI(0,
124, ret=True)
+ if end is None: snapshot["PlaylistTrackCount"] = studioAPI(0,
124, ret=True)
snapshot["PlaylistDurationTotal"] =
self._ms2time(totalDuration, ms=False)
if "DurationMinMax" in snapshotFlags:
snapshot["PlaylistDurationMin"] = "%s (%s)"%(minTitle,
min)
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index ba9a496..b9d418f 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -185,12 +185,14 @@ class SPLConfigDialog(gui.SettingsDialog):
self.topBottomCheckbox =
SPLConfigHelper.addItem(wx.CheckBox(self, label=_("Notify when located at &top
or bottom of playlist viewer")))
self.topBottomCheckbox.SetValue(splconfig.SPLConfig["General"]["TopBottomAnnounce"])
- self.playlistDurationMinMax =
splconfig.SPLConfig["PlaylistSnapshots"]["DurationMinMax"]
- self.playlistDurationAverage =
splconfig.SPLConfig["PlaylistSnapshots"]["DurationAverage"]
- self.playlistCategoryCount =
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCount"]
# Translators: The label of a button to manage playlist
snapshot flags.
playlistSnapshotFlagsButton =
SPLConfigHelper.addItem(wx.Button(self, label=_("&Playlist snapshots...")))
playlistSnapshotFlagsButton.Bind(wx.EVT_BUTTON,
self.onPlaylistSnapshotFlags)
+ # Playlist snapshot flags to be manipulated by the
configuration dialog.
+ self.playlistDurationMinMax =
splconfig.SPLConfig["PlaylistSnapshots"]["DurationMinMax"]
+ self.playlistDurationAverage =
splconfig.SPLConfig["PlaylistSnapshots"]["DurationAverage"]
+ self.playlistArtistCount =
splconfig.SPLConfig["PlaylistSnapshots"]["ArtistCount"]
+ self.playlistCategoryCount =
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCount"]
sizer = gui.guiHelper.BoxSizerHelper(self,
orientation=wx.HORIZONTAL)
self.metadataValues=[("off",_("Off")),
@@ -286,6 +288,7 @@ class SPLConfigDialog(gui.SettingsDialog):
splconfig.SPLConfig["General"]["TopBottomAnnounce"] =
self.topBottomCheckbox.Value
splconfig.SPLConfig["PlaylistSnapshots"]["DurationMinMax"] =
self.playlistDurationMinMax
splconfig.SPLConfig["PlaylistSnapshots"]["DurationAverage"] =
self.playlistDurationAverage
+ splconfig.SPLConfig["PlaylistSnapshots"]["ArtistCount"] =
self.playlistArtistCount
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCount"] =
self.playlistCategoryCount
splconfig.SPLConfig["General"]["MetadataReminder"] =
self.metadataValues[self.metadataList.GetSelection()][0]
splconfig.SPLConfig["MetadataStreaming"]["MetadataEnabled"] =
self.metadataStreams
@@ -912,6 +915,9 @@ class PlaylistSnapshotsDialog(wx.Dialog):
# Translators: the label for a setting in SPL add-on settings
to include average track duration in playlist snapshots window.
self.playlistDurationAverageCheckbox=playlistSnapshotsHelper.addItem(wx.CheckBox(self,
label=_("Average track duration")))
self.playlistDurationAverageCheckbox.SetValue(parent.playlistDurationAverage)
+ # Translators: the label for a setting in SPL add-on settings
to include track artist count in playlist snapshots window.
+
self.playlistArtistCountCheckbox=playlistSnapshotsHelper.addItem(wx.CheckBox(self,
label=_("Artist count")))
+
self.playlistArtistCountCheckbox.SetValue(parent.playlistArtistCount)
# Translators: the label for a setting in SPL add-on settings
to include track category count in playlist snapshots window.
self.playlistCategoryCountCheckbox=playlistSnapshotsHelper.addItem(wx.CheckBox(self,
label=_("Category count")))
self.playlistCategoryCountCheckbox.SetValue(parent.playlistCategoryCount)
@@ -929,6 +935,7 @@ class PlaylistSnapshotsDialog(wx.Dialog):
parent = self.Parent
parent.playlistDurationMinMax =
self.playlistDurationMinMaxCheckbox.Value
parent.playlistDurationAverage =
self.playlistDurationAverageCheckbox.Value
+ parent.playlistArtistCount =
self.playlistArtistCountCheckbox.Value
parent.playlistCategoryCount =
self.playlistCategoryCountCheckbox.Value
parent.profiles.SetFocus()
parent.Enable()
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/bdbe8e594db6/
Changeset: bdbe8e594db6
Branch: None
User: josephsl
Date: 2017-01-12 18:40:01+00:00
Summary: Playlist snapshots (17.1-dev): Genre statistics will be gathered
if told to do so.
Genre statistics as in top genres represented in the playlist (such as 'Rock'
and so on). Also, a corresponding setting to toggle this off has been added. As
of this commit basics of playlist snapshots is complete.
Affected #: 3 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index eb69d2d..ef7c46d 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1434,15 +1434,18 @@ class AppModule(appModuleHandler.AppModule):
artists = []
min, max = None, None
minTitle, maxTitle = None, None
- category = obj.indexOf("Category")
totalDuration = 0
+ category = obj.indexOf("Category")
categories = []
+ genre = obj.indexOf("Genre")
+ genres = []
# A specific version of the playlist duration loop is needed in
order to gather statistics.
while obj not in (None, end):
segue = obj._getColumnContent(duration)
trackTitle = obj._getColumnContent(title)
categories.append(obj._getColumnContent(category))
if categories[-1] != "Hour Marker":
artists.append(obj._getColumnContent(artist))
+ genres.append(obj._getColumnContent(genre))
# Shortest and longest tracks.
if min is None: min = segue
if segue and segue < min:
@@ -1463,10 +1466,11 @@ class AppModule(appModuleHandler.AppModule):
snapshot["PlaylistDurationMax"] = "%s (%s)"%(maxTitle,
max)
if "DurationAverage" in snapshotFlags:
snapshot["PlaylistDurationAverage"] =
self._ms2time(totalDuration/snapshot["PlaylistTrackCount"], ms=False)
- if "CategoryCount" in snapshotFlags or "ArtistCount" in
snapshotFlags:
+ if "CategoryCount" in snapshotFlags or "ArtistCount" in
snapshotFlags or "GenreCount" in snapshotFlags:
import collections
if "CategoryCount" in snapshotFlags:
snapshot["PlaylistCategoryCount"] = collections.Counter(categories)
if "ArtistCount" in snapshotFlags:
snapshot["PlaylistArtistCount"] = collections.Counter(artists)
+ if "GenreCount" in snapshotFlags:
snapshot["PlaylistGenreCount"] = collections.Counter(genres)
return snapshot
# Output formatter for playlist snapshots.
@@ -1507,6 +1511,21 @@ class AppModule(appModuleHandler.AppModule):
category = category.replace(">", "")
categoryList.append("<li>%s
(%s)</li>"%(category, count))
statusInfo.append("".join(["Categories:<ol>",
"\n".join(categoryList), "</ol>"]))
+ if "PlaylistGenreCount" in snapshot:
+ genres = snapshot["PlaylistGenreCount"].most_common()
+ if scriptCount == 0:
+ statusInfo.append("Top genre: %s
(%s)"%(genres[0]))
+ else:
+ genreList = []
+ for item in genres:
+ genre, count = item
+ try:
+ genre = genre.replace("<", "")
+ genre = genre.replace(">", "")
+ genreList.append("<li>%s
(%s)</li>"%(genre, count))
+ except AttributeError:
+ genreList.append("<li> No genre
information (%s)</li>"%(count))
+ statusInfo.append("".join(["Top genres:<ol>",
"\n".join(genreList), "</ol>"]))
if scriptCount == 0:
ui.message(", ".join(statusInfo))
else:
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index c58c6b2..7c4699f 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -53,6 +53,8 @@ ArtistCount = boolean(default=true)
ArtistCountLimit = integer(min=0, max=10, default=5)
CategoryCount = boolean(default=true)
CategoryCountLimit = integer(min=0, max=10, default=5)
+GenreCount = boolean(default=true)
+GenreCountLimit = integer(min=0, max=10, default=5)
[IntroOutroAlarms]
SayEndOfTrack = boolean(default=true)
EndOfTrackTime = integer(min=1, max=59, default=5)
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index b9d418f..7ee7532 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -193,6 +193,7 @@ class SPLConfigDialog(gui.SettingsDialog):
self.playlistDurationAverage =
splconfig.SPLConfig["PlaylistSnapshots"]["DurationAverage"]
self.playlistArtistCount =
splconfig.SPLConfig["PlaylistSnapshots"]["ArtistCount"]
self.playlistCategoryCount =
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCount"]
+ self.playlistGenreCount =
splconfig.SPLConfig["PlaylistSnapshots"]["GenreCount"]
sizer = gui.guiHelper.BoxSizerHelper(self,
orientation=wx.HORIZONTAL)
self.metadataValues=[("off",_("Off")),
@@ -290,6 +291,7 @@ class SPLConfigDialog(gui.SettingsDialog):
splconfig.SPLConfig["PlaylistSnapshots"]["DurationAverage"] =
self.playlistDurationAverage
splconfig.SPLConfig["PlaylistSnapshots"]["ArtistCount"] =
self.playlistArtistCount
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCount"] =
self.playlistCategoryCount
+ splconfig.SPLConfig["PlaylistSnapshots"]["GenreCount"] =
self.playlistGenreCount
splconfig.SPLConfig["General"]["MetadataReminder"] =
self.metadataValues[self.metadataList.GetSelection()][0]
splconfig.SPLConfig["MetadataStreaming"]["MetadataEnabled"] =
self.metadataStreams
splconfig.SPLConfig["ColumnAnnouncement"]["UseScreenColumnOrder"] =
self.columnOrderCheckbox.Value
@@ -921,6 +923,9 @@ class PlaylistSnapshotsDialog(wx.Dialog):
# Translators: the label for a setting in SPL add-on settings
to include track category count in playlist snapshots window.
self.playlistCategoryCountCheckbox=playlistSnapshotsHelper.addItem(wx.CheckBox(self,
label=_("Category count")))
self.playlistCategoryCountCheckbox.SetValue(parent.playlistCategoryCount)
+ # Translators: the label for a setting in SPL add-on settings
to include track genre count in playlist snapshots window.
+
self.playlistGenreCountCheckbox=playlistSnapshotsHelper.addItem(wx.CheckBox(self,
label=_("Genre count")))
+
self.playlistGenreCountCheckbox.SetValue(parent.playlistGenreCount)
playlistSnapshotsHelper.addDialogDismissButtons(self.CreateButtonSizer(wx.OK |
wx.CANCEL))
self.Bind(wx.EVT_BUTTON, self.onOk, id=wx.ID_OK)
@@ -937,6 +942,7 @@ class PlaylistSnapshotsDialog(wx.Dialog):
parent.playlistDurationAverage =
self.playlistDurationAverageCheckbox.Value
parent.playlistArtistCount =
self.playlistArtistCountCheckbox.Value
parent.playlistCategoryCount =
self.playlistCategoryCountCheckbox.Value
+ parent.playlistGenreCount =
self.playlistGenreCountCheckbox.Value
parent.profiles.SetFocus()
parent.Enable()
self.Destroy()
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/a8d5492f7a3a/
Changeset: a8d5492f7a3a
Branch: None
User: josephsl
Date: 2017-01-12 22:22:21+00:00
Summary: Playlist snapshots (17.1-dev): No longer allow old config format
(playlist snapshots key in general settings) to be ported, playlist snapshots
is ready.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 7c4699f..313e7f8 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -122,12 +122,6 @@ class ConfigHub(ChainMap):
deprecatedKeys = {"General":"TrackDial", "Startup":"Studio500"}
for section, key in deprecatedKeys.iteritems():
if key in self.maps[0][section]: del
self.maps[0][section][key]
- # January 2017 only: playlist snapshots is now its own
dedicated section.
- if "PlaylistSnapshots" in self.maps[0]["General"]: del
self.maps[0]["General"]["PlaylistSnapshots"]
- for key in ("PlaylistDurationMinMax",
"PlaylistDurationAverage", "PlaylistCategoryCount"):
- if key in self.maps[0]["PlaylistSnapshots"]:
- self.maps[0]["PlaylistSnapshots"][key[8:]] =
self.maps[0]["PlaylistSnapshots"][key]
- del self.maps[0]["PlaylistSnapshots"][key]
# Moving onto broadcast profiles if any.
try:
profiles = filter(lambda fn: os.path.splitext(fn)[-1]
== ".ini", os.listdir(SPLProfiles))
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/73b61c93f408/
Changeset: 73b61c93f408
Branch: None
User: josephsl
Date: 2017-01-13 19:31:59+00:00
Summary: Playlist snapshots (17.04-dev): apply limits on artist, category
and genre count when announcing results.
The limit values for artist, category and genre counts are now used to limit
top results to certain number of items, with values being 0 to 10 (0 being
report all results). The configuration UI is next.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index ef7c46d..6320c26 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1485,25 +1485,25 @@ class AppModule(appModuleHandler.AppModule):
if "PlaylistDurationAverage" in snapshot:
statusInfo.append("Average:
%s"%snapshot["PlaylistDurationAverage"])
if "PlaylistArtistCount" in snapshot:
- artists = snapshot["PlaylistArtistCount"].most_common()
if scriptCount == 0:
- statusInfo.append("Top artist: %s
(%s)"%(artists[0]))
+ statusInfo.append("Top artist: %s
(%s)"%(snapshot["PlaylistArtistCount"][0]))
else:
+ artistCount =
splconfig.SPLConfig["PlaylistSnapshots"]["ArtistCountLimit"]
+ artists =
snapshot["PlaylistArtistCount"].most_common(None if not artistCount else
artistCount)
artistList = []
for item in artists:
artist, count = item
- try:
- artist = artist.replace("<", "")
- artist = artist.replace(">", "")
+ if artist is None:
+ artistList.append("<li>No
artist information (%s)</li>"%(count))
+ else:
artistList.append("<li>%s
(%s)</li>"%(artist, count))
- except AttributeError:
- artistList.append("<li> No
artist information (%s)</li>"%(count))
statusInfo.append("".join(["Top artists:<ol>",
"\n".join(artistList), "</ol>"]))
if "PlaylistCategoryCount" in snapshot:
- categories =
snapshot["PlaylistCategoryCount"].most_common()
if scriptCount == 0:
- statusInfo.append("Top category: %s
(%s)"%(categories[0]))
+ statusInfo.append("Top category: %s
(%s)"%(snapshot["PlaylistCategoryCount"][0]))
else:
+ categoryCount =
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCountLimit"]
+ categories =
snapshot["PlaylistCategoryCount"].most_common(None if not categoryCount else
categoryCount)
categoryList = []
for item in categories:
category, count = item
@@ -1512,19 +1512,18 @@ class AppModule(appModuleHandler.AppModule):
categoryList.append("<li>%s
(%s)</li>"%(category, count))
statusInfo.append("".join(["Categories:<ol>",
"\n".join(categoryList), "</ol>"]))
if "PlaylistGenreCount" in snapshot:
- genres = snapshot["PlaylistGenreCount"].most_common()
if scriptCount == 0:
- statusInfo.append("Top genre: %s
(%s)"%(genres[0]))
+ statusInfo.append("Top genre: %s
(%s)"%(snapshot["PlaylistGenreCount"][0]))
else:
+ genreCount =
splconfig.SPLConfig["PlaylistSnapshots"]["GenreCountLimit"]
+ genres =
snapshot["PlaylistGenreCount"].most_common(None if not genreCount else
genreCount)
genreList = []
for item in genres:
genre, count = item
- try:
- genre = genre.replace("<", "")
- genre = genre.replace(">", "")
+ if genre is None:
+ genreList.append("<li>No genre
information (%s)</li>"%(count))
+ else:
genreList.append("<li>%s
(%s)</li>"%(genre, count))
- except AttributeError:
- genreList.append("<li> No genre
information (%s)</li>"%(count))
statusInfo.append("".join(["Top genres:<ol>",
"\n".join(genreList), "</ol>"]))
if scriptCount == 0:
ui.message(", ".join(statusInfo))
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/5f16cdaffbd7/
Changeset: 5f16cdaffbd7
Branch: None
User: josephsl
Date: 2017-01-14 03:50:13+00:00
Summary: Playlist snapshots (17.04-dev): allow artist, category and genre
count limit to be configurable.
Users can now configure how many artists, categories or genres would be counted
(0 means count all) via three new number edit fields.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index 7ee7532..454dbb1 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -192,8 +192,11 @@ class SPLConfigDialog(gui.SettingsDialog):
self.playlistDurationMinMax =
splconfig.SPLConfig["PlaylistSnapshots"]["DurationMinMax"]
self.playlistDurationAverage =
splconfig.SPLConfig["PlaylistSnapshots"]["DurationAverage"]
self.playlistArtistCount =
splconfig.SPLConfig["PlaylistSnapshots"]["ArtistCount"]
+ self.playlistArtistCountLimit =
splconfig.SPLConfig["PlaylistSnapshots"]["ArtistCountLimit"]
self.playlistCategoryCount =
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCount"]
+ self.playlistCategoryCountLimit =
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCountLimit"]
self.playlistGenreCount =
splconfig.SPLConfig["PlaylistSnapshots"]["GenreCount"]
+ self.playlistGenreCountLimit =
splconfig.SPLConfig["PlaylistSnapshots"]["GenreCountLimit"]
sizer = gui.guiHelper.BoxSizerHelper(self,
orientation=wx.HORIZONTAL)
self.metadataValues=[("off",_("Off")),
@@ -290,8 +293,11 @@ class SPLConfigDialog(gui.SettingsDialog):
splconfig.SPLConfig["PlaylistSnapshots"]["DurationMinMax"] =
self.playlistDurationMinMax
splconfig.SPLConfig["PlaylistSnapshots"]["DurationAverage"] =
self.playlistDurationAverage
splconfig.SPLConfig["PlaylistSnapshots"]["ArtistCount"] =
self.playlistArtistCount
+ splconfig.SPLConfig["PlaylistSnapshots"]["ArtistCountLimit"] =
self.playlistArtistCountLimit
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCount"] =
self.playlistCategoryCount
+ splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCountLimit"]
= self.playlistCategoryCountLimit
splconfig.SPLConfig["PlaylistSnapshots"]["GenreCount"] =
self.playlistGenreCount
+ splconfig.SPLConfig["PlaylistSnapshots"]["GenreCountLimit"] =
self.playlistGenreCountLimit
splconfig.SPLConfig["General"]["MetadataReminder"] =
self.metadataValues[self.metadataList.GetSelection()][0]
splconfig.SPLConfig["MetadataStreaming"]["MetadataEnabled"] =
self.metadataStreams
splconfig.SPLConfig["ColumnAnnouncement"]["UseScreenColumnOrder"] =
self.columnOrderCheckbox.Value
@@ -920,12 +926,15 @@ class PlaylistSnapshotsDialog(wx.Dialog):
# Translators: the label for a setting in SPL add-on settings
to include track artist count in playlist snapshots window.
self.playlistArtistCountCheckbox=playlistSnapshotsHelper.addItem(wx.CheckBox(self,
label=_("Artist count")))
self.playlistArtistCountCheckbox.SetValue(parent.playlistArtistCount)
+
self.playlistArtistCountLimit=playlistSnapshotsHelper.addLabeledControl(_("Top
artist count (0 displays all artists)"),
gui.nvdaControls.SelectOnFocusSpinCtrl, min=0, max=10,
initial=parent.playlistArtistCountLimit)
# Translators: the label for a setting in SPL add-on settings
to include track category count in playlist snapshots window.
self.playlistCategoryCountCheckbox=playlistSnapshotsHelper.addItem(wx.CheckBox(self,
label=_("Category count")))
self.playlistCategoryCountCheckbox.SetValue(parent.playlistCategoryCount)
+
self.playlistCategoryCountLimit=playlistSnapshotsHelper.addLabeledControl(_("Top
category count (0 displays all categories)"),
gui.nvdaControls.SelectOnFocusSpinCtrl, min=0, max=10,
initial=parent.playlistCategoryCountLimit)
# Translators: the label for a setting in SPL add-on settings
to include track genre count in playlist snapshots window.
self.playlistGenreCountCheckbox=playlistSnapshotsHelper.addItem(wx.CheckBox(self,
label=_("Genre count")))
self.playlistGenreCountCheckbox.SetValue(parent.playlistGenreCount)
+
self.playlistGenreCountLimit=playlistSnapshotsHelper.addLabeledControl(_("Top
genre count (0 displays all genres)"), gui.nvdaControls.SelectOnFocusSpinCtrl,
min=0, max=10, initial=parent.playlistGenreCountLimit)
playlistSnapshotsHelper.addDialogDismissButtons(self.CreateButtonSizer(wx.OK |
wx.CANCEL))
self.Bind(wx.EVT_BUTTON, self.onOk, id=wx.ID_OK)
@@ -941,8 +950,11 @@ class PlaylistSnapshotsDialog(wx.Dialog):
parent.playlistDurationMinMax =
self.playlistDurationMinMaxCheckbox.Value
parent.playlistDurationAverage =
self.playlistDurationAverageCheckbox.Value
parent.playlistArtistCount =
self.playlistArtistCountCheckbox.Value
+ parent.playlistArtistCountLimit =
self.playlistArtistCountLimit.GetValue()
parent.playlistCategoryCount =
self.playlistCategoryCountCheckbox.Value
+ parent.playlistCategoryCountLimit =
self.playlistCategoryCountLimit.GetValue()
parent.playlistGenreCount =
self.playlistGenreCountCheckbox.Value
+ parent.playlistGenreCountLimit =
self.playlistGenreCountLimit.GetValue()
parent.profiles.SetFocus()
parent.Enable()
self.Destroy()
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/e8cf7a633b24/
Changeset: e8cf7a633b24
Branch: None
User: josephsl
Date: 2017-01-14 06:34:34+00:00
Summary: Playlist snapshots (17.04-dev): Allow custom commands to be
defined for playlist snapshtos script, some presentation tweaks.
It is now possible for broadcasters to assign a hotkey to invoke playlist
snapshots. Pressing once will speak and braille brief snapshot info, while
pressing it twice will present the full snapshots display. To prevent mishaps
with multiple HTML windows appearing and slowing down performance, the command
will not work if pressed more than twice.
Also tweaked how snapshot info is presented. Specificlaly, because the top
artist and others will be announced when the script is invoked once, counters
will be fetched early.
All this work is destined for add-on 17.04.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 6320c26..30c2c44 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1476,7 +1476,6 @@ class AppModule(appModuleHandler.AppModule):
# Output formatter for playlist snapshots.
# Pressed once will speak and/or braille it, pressing twice or more will
output this info to an HTML file.
def playlistSnapshotOutput(self, snapshot, scriptCount):
- scriptCount = 1
statusInfo = ["Tracks: %s"%snapshot["PlaylistTrackCount"]]
statusInfo.append("Duration:
%s"%snapshot["PlaylistDurationTotal"])
if "PlaylistDurationMin" in snapshot:
@@ -1485,11 +1484,11 @@ class AppModule(appModuleHandler.AppModule):
if "PlaylistDurationAverage" in snapshot:
statusInfo.append("Average:
%s"%snapshot["PlaylistDurationAverage"])
if "PlaylistArtistCount" in snapshot:
+ artistCount =
splconfig.SPLConfig["PlaylistSnapshots"]["ArtistCountLimit"]
+ artists =
snapshot["PlaylistArtistCount"].most_common(None if not artistCount else
artistCount)
if scriptCount == 0:
- statusInfo.append("Top artist: %s
(%s)"%(snapshot["PlaylistArtistCount"][0]))
- else:
- artistCount =
splconfig.SPLConfig["PlaylistSnapshots"]["ArtistCountLimit"]
- artists =
snapshot["PlaylistArtistCount"].most_common(None if not artistCount else
artistCount)
+ statusInfo.append("Top artist: %s
(%s)"%(artists[0][:]))
+ elif scriptCount == 1:
artistList = []
for item in artists:
artist, count = item
@@ -1499,11 +1498,11 @@ class AppModule(appModuleHandler.AppModule):
artistList.append("<li>%s
(%s)</li>"%(artist, count))
statusInfo.append("".join(["Top artists:<ol>",
"\n".join(artistList), "</ol>"]))
if "PlaylistCategoryCount" in snapshot:
+ categoryCount =
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCountLimit"]
+ categories =
snapshot["PlaylistCategoryCount"].most_common(None if not categoryCount else
categoryCount)
if scriptCount == 0:
- statusInfo.append("Top category: %s
(%s)"%(snapshot["PlaylistCategoryCount"][0]))
- else:
- categoryCount =
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCountLimit"]
- categories =
snapshot["PlaylistCategoryCount"].most_common(None if not categoryCount else
categoryCount)
+ statusInfo.append("Top category: %s
(%s)"%(categories[0][:]))
+ elif scriptCount == 1:
categoryList = []
for item in categories:
category, count = item
@@ -1512,11 +1511,11 @@ class AppModule(appModuleHandler.AppModule):
categoryList.append("<li>%s
(%s)</li>"%(category, count))
statusInfo.append("".join(["Categories:<ol>",
"\n".join(categoryList), "</ol>"]))
if "PlaylistGenreCount" in snapshot:
+ genreCount =
splconfig.SPLConfig["PlaylistSnapshots"]["GenreCountLimit"]
+ genres =
snapshot["PlaylistGenreCount"].most_common(None if not genreCount else
genreCount)
if scriptCount == 0:
- statusInfo.append("Top genre: %s
(%s)"%(snapshot["PlaylistGenreCount"][0]))
- else:
- genreCount =
splconfig.SPLConfig["PlaylistSnapshots"]["GenreCountLimit"]
- genres =
snapshot["PlaylistGenreCount"].most_common(None if not genreCount else
genreCount)
+ statusInfo.append("Top genre: %s
(%s)"%(genres[0][:]))
+ elif scriptCount == 1:
genreList = []
for item in genres:
genre, count = item
@@ -1875,12 +1874,22 @@ class AppModule(appModuleHandler.AppModule):
obj = api.getFocusObject() if
api.getForegroundObject().windowClassName == "TStudioForm" else
self._focusedTrack
if obj is None:
ui.message("Please return to playlist viewer before
invoking this command.")
+ self.finish()
return
if obj.role == controlTypes.ROLE_LIST:
ui.message(_("You need to add tracks before invoking
this command"))
+ self.finish()
return
- # Speak and braille on the first press, display a decorated
HTML message for subsequent presses.
-
self.playlistSnapshotOutput(self.playlistSnapshots(obj.parent.firstChild,
None), scriptHandler.getLastScriptRepeatCount())
+ scriptCount = scriptHandler.getLastScriptRepeatCount()
+ # Never allow this to be invoked more than twice, as it causes
performance degredation and multiple HTML windows are opened.
+ if scriptCount >= 2:
+ self.finish()
+ return
+ # Speak and braille on the first press, display a
decorated HTML message for subsequent presses.
+
self.playlistSnapshotOutput(self.playlistSnapshots(obj.parent.firstChild,
None), scriptCount)
+ self.finish()
+ # Translators: Input help mode message for a command in Station
Playlist Studio.
+ script_takePlaylistSnapshots.__doc__=_("Presents playlist snapshot
information such as number of tracks and top artists")
def script_switchProfiles(self, gesture):
splconfig.triggerProfileSwitch() if
splconfig._triggerProfileActive else splconfig.instantProfileSwitch()
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/9fe9e5c484f9/
Changeset: 9fe9e5c484f9
Branch: None
User: josephsl
Date: 2017-01-14 06:49:39+00:00
Summary: Readme entry for playlist snapshots.
Affected #: 1 file
diff --git a/readme.md b/readme.md
index 29f22f3..748477a 100755
--- a/readme.md
+++ b/readme.md
@@ -45,6 +45,7 @@ The following commands are not assigned by default; if you
wish to assign them,
* Announcing title of the currently playing track.
* Marking current track for start of track time analysis.
* Performing track time analysis.
+* Take playlist snapshots.
* Find text in specific columns.
* Find tracks with duration that falls within a given range via time range
finder.
* Quickly enable or disable metadata streaming.
@@ -107,6 +108,7 @@ The available commands are:
* W: Weather and temperature if configured.
* Y: Playlist modified status.
* 1 through 0 (6 for Studio 5.0x): Announce column content for a specified
column.
+* F8: Take playlist snapshots (number of tracks, longest track, etc.).
* F9: Mark current track for track time analysis (playlist viewer only).
* F10: Perform track time analysis (playlist viewer only).
* F12: Switch between current and a predefined profile.
@@ -160,6 +162,10 @@ To obtain length to play selected tracks, mark current
track for start of track
By pressing Control+NVDA+1 through 0 (6 for Studio 5.0x) or SPL Assistant, 1
through 0 (6 for Studio 5.01 and earlier), you can obtain contents of specific
columns. By default, these are artist, title, duration, intro, category and
filename (Studio 5.10 adds year, album, genre and time scheduled). You can
configure which columns will be explored via columns explorer dialog found in
add-on settings dialog.
+## Playlist snapshots
+
+You can press SPL Assistant, F8 while focused on a playlist in Studio to
obtain various statistics about a playlist, including number of tracks in the
playlist, longest track, top artists and so on. After assigning a custom
command for this feature, pressing the custom command twice will cause NVDA to
present playlist snapshot information as a webpage so you can use browse mode
to navigate (press escape to close).
+
## Configuration dialog
From studio window, you can press Alt+NVDA+0 to open the add-on configuration
dialog. Alternatively, go to NVDA's preferences menu and select SPL Studio
Settings item. This dialog is also used to manage broadcast profiles.
@@ -175,6 +181,8 @@ If you are using Studio on a touchscreen computer running
Windows 8 or later and
* Added a combo box in add-on settings dialog to set which column should be
announced when moving through columns vertically.
* Moved end of track , intro and microphone alarm controls from add-on
settings to the new Alarms Center.
* In Alarms Center, end of track and track intro edit fields are always shown
regardless of state of alarm notification checkboxes.
+* Added a command in SPL Assistant to obtain playlist snapshots such as number
of tracks, longest track, top artists and so on (F8). You can also add a custom
command for this feature.
+* Pressing the custom gesture for playlist snapshots command once will let
NVDA speak and braile a short snapshot information. Pressing the command twice
will cause NVDA to open a webpage containing a fuller playlist snapshot
information. Press escape to close this webpage.
* Removed Track Dial (NVDA's version of enhanced arrow keys), replaced by
Columns explorer and Column Navigator/table navigation commands). This affects
Studio and Track Tool.
* After closing Insert Tracks dialog while a library scan is in progress, it
is no longer required to press SPL Assistant, Shift+R to monitor scan progress.
* Improved accuracy of detecting and reporting completion of library scans in
Studio 5.10 and later. This fixes a problem where library scan monitor will end
prematurely when there are more tracks to be scanned, necessitating restarting
library scan monitor.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/2c667a1eaa0e/
Changeset: 2c667a1eaa0e
Branch: None
User: josephsl
Date: 2017-01-14 20:07:00+00:00
Summary: SPL gestures: do not allow built-in and custom SPL add-on commands
to proceed if handle to studio is not present.
In studio Demo, before the main window appears, registration screen is shown.
At this time, main window handle to Studio isn't present, which causes commands
such as remainig time of the track, temperature, playlist snapshots and others
to fail (not announcing anything, playing error tones, or giving wrong info).
This has been mitigated via a new function that'll announce an error message if
this is such a case.
This fix is destined for add-on 17.04.
Affected #: 2 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 30c2c44..4069f79 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -101,6 +101,15 @@ def studioAPI(arg, command, func=None, ret=False,
offset=None):
if func:
func(val) if not offset else func(val, offset)
+# Check if Studio itself is running.
+# This is to make sure custom commands for SPL Assistant comamnds and other
app module gestures display appropriate error messages.
+def studioIsRunning():
+ if _SPLWin is None:
+ # Translators: A message informing users that Studio is not
running so certain commands will not work.
+ ui.message(_("Studio main window not found"))
+ return False
+ return True
+
# Select a track upon request.
def selectTrack(trackIndex):
studioAPI(-1, 121)
@@ -925,16 +934,17 @@ class AppModule(appModuleHandler.AppModule):
# Scripts which rely on API.
def script_sayRemainingTime(self, gesture):
- studioAPI(3, 105, self.announceTime, offset=1)
+ if studioIsRunning(): studioAPI(3, 105, self.announceTime,
offset=1)
# Translators: Input help mode message for a command in Station
Playlist Studio.
script_sayRemainingTime.__doc__=_("Announces the remaining track time.")
def script_sayElapsedTime(self, gesture):
- studioAPI(0, 105, self.announceTime)
+ if studioIsRunning(): studioAPI(0, 105, self.announceTime)
# Translators: Input help mode message for a command in Station
Playlist Studio.
script_sayElapsedTime.__doc__=_("Announces the elapsed time for the
currently playing track.")
def script_sayBroadcasterTime(self, gesture):
+ if not studioIsRunning(): return
# Says things such as "25 minutes to 2" and "5 past 11".
# Parse the local time and say it similar to how Studio
presents broadcaster time.
h, m = time.localtime()[3], time.localtime()[4]
@@ -956,6 +966,7 @@ class AppModule(appModuleHandler.AppModule):
script_sayBroadcasterTime.__doc__=_("Announces broadcaster time.")
def script_sayCompleteTime(self, gesture):
+ if not studioIsRunning(): return
import winKernel
# Says complete time in hours, minutes and seconds via
kernel32's routines.
ui.message(winKernel.GetTimeFormat(winKernel.LOCALE_USER_DEFAULT, 0, None,
None))
@@ -1083,6 +1094,7 @@ class AppModule(appModuleHandler.AppModule):
# But first, check if track finder can be invoked.
# Attempt level specifies which track finder to open (0 = Track Finder,
1 = Column Search, 2 = Time range).
def _trackFinderCheck(self, attemptLevel):
+ if not studioIsRunning(): return False
if api.getForegroundObject().windowClassName != "TStudioForm":
if attemptLevel == 0:
# Translators: Presented when a user attempts
to find tracks but is not at the track list.
@@ -1194,6 +1206,7 @@ class AppModule(appModuleHandler.AppModule):
self.bindGestures(self.__gestures)
def script_toggleCartExplorer(self, gesture):
+ if not studioIsRunning(): return
if not self.cartExplorer:
# Prevent cart explorer from being engaged outside of
playlist viewer.
# Todo for 6.0: Let users set cart banks.
@@ -1352,7 +1365,7 @@ class AppModule(appModuleHandler.AppModule):
def script_manageMetadataStreams(self, gesture):
# Do not even think about opening this dialog if handle to
Studio isn't found.
if _SPLWin is None:
- # Translators: Presented when stremaing dialog cannot
be shown.
+ # Translators: Presented when streaming dialog cannot
be shown.
ui.message(_("Cannot open metadata streaming dialog"))
return
if splconfui._configDialogOpened or
splconfui._metadataDialogOpened:
@@ -1380,6 +1393,8 @@ class AppModule(appModuleHandler.AppModule):
# Trakc time analysis and playlist snapshots require main playlist
viewer to be the foreground window.
def _trackAnalysisAllowed(self):
+ if not studioIsRunning():
+ return False
if api.getForegroundObject().windowClassName != "TStudioForm":
# Translators: Presented when track time anlaysis
cannot be performed because user is not focused on playlist viewer.
ui.message(_("Not in playlist viewer, cannot perform
track time analysis or gather playlist snapshot statistics"))
@@ -1551,7 +1566,6 @@ class AppModule(appModuleHandler.AppModule):
os.startfile("mailto:joseph.lee22590@xxxxxxxxx";)
script_sendFeedbackEmail.__doc__="Opens the default email client to
send an email to the add-on developer"
-
# SPL Assistant: reports status on playback, operation, etc.
# Used layer command approach to save gesture assignments.
# Most were borrowed from JFW and Window-Eyes layer scripts.
@@ -1733,6 +1747,9 @@ class AppModule(appModuleHandler.AppModule):
ui.message(_("Playlist modification not available"))
def script_sayNextTrackTitle(self, gesture):
+ if not studioIsRunning():
+ self.finish()
+ return
try:
obj = self.status(self.SPLNextTrackTitle).firstChild
# Translators: Presented when there is no information
for the next track.
@@ -1746,6 +1763,9 @@ class AppModule(appModuleHandler.AppModule):
script_sayNextTrackTitle.__doc__=_("Announces title of the next track
if any")
def script_sayCurrentTrackTitle(self, gesture):
+ if not studioIsRunning():
+ self.finish()
+ return
try:
obj = self.status(self.SPLCurrentTrackTitle).firstChild
# Translators: Presented when there is no information
for the current track.
@@ -1759,6 +1779,9 @@ class AppModule(appModuleHandler.AppModule):
script_sayCurrentTrackTitle.__doc__=_("Announces title of the currently
playing track")
def script_sayTemperature(self, gesture):
+ if not studioIsRunning():
+ self.finish()
+ return
try:
obj = self.status(self.SPLTemperature).firstChild
# Translators: Presented when there is no weather or
temperature information.
@@ -1871,6 +1894,9 @@ class AppModule(appModuleHandler.AppModule):
script_trackTimeAnalysis.__doc__=_("Announces total length of tracks
between analysis start marker and the current track")
def script_takePlaylistSnapshots(self, gesture):
+ if not studioIsRunning():
+ self.finish()
+ return
obj = api.getFocusObject() if
api.getForegroundObject().windowClassName == "TStudioForm" else
self._focusedTrack
if obj is None:
ui.message("Please return to playlist viewer before
invoking this command.")
diff --git a/readme.md b/readme.md
index 748477a..efa7b0b 100755
--- a/readme.md
+++ b/readme.md
@@ -187,6 +187,7 @@ If you are using Studio on a touchscreen computer running
Windows 8 or later and
* After closing Insert Tracks dialog while a library scan is in progress, it
is no longer required to press SPL Assistant, Shift+R to monitor scan progress.
* Improved accuracy of detecting and reporting completion of library scans in
Studio 5.10 and later. This fixes a problem where library scan monitor will end
prematurely when there are more tracks to be scanned, necessitating restarting
library scan monitor.
* Improved library scan status reporting via SPL Controller (Shift+R) by
announcing scan count if scan is indeed happening.
+* In studio Demo, when registration screen appears when starting Studio,
commands such as remaining time for a track will no longer cause NVDA to do
nothing, play error tones, or give wrong information. An error message will be
announced instead. Commands such as these will require Studio's main window
handle to be present.
* Initial support for StationPlaylist Creator.
* Added a new command in SPL Controller layer to announce Studio status such
as track playback and microphone status (Q).
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/3feff4e9ec7d/
Changeset: 3feff4e9ec7d
Branch: None
User: josephsl
Date: 2017-01-15 05:03:41+00:00
Summary: Merged stable
Affected #: 2 files
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index 3191f1c..3d1827f 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -37,6 +37,7 @@ _updatePickle = os.path.join(globalVars.appArgs.configPath,
"splupdate.pickle")
# Not all update channels are listed. The one not listed here is the default
("stable" for this branch).
channels={
"stable":"http://addons.nvda-project.org/files/get.php?file=spl";,
+ "lts":"http://josephsl.net/files/nvdaaddons/get.php?file=spl-lts16";,
#"beta":"http://spl.nvda-kr.org/files/get.php?file=spl-beta";,
}
diff --git a/readme.md b/readme.md
index efa7b0b..d7786b1 100755
--- a/readme.md
+++ b/readme.md
@@ -193,6 +193,9 @@ If you are using Studio on a touchscreen computer running
Windows 8 or later and
## Version 17.01/15.5-LTS
+Note: 17.01.1/15.5A-LTS replaces 17.01 due to changes to location of new
add-on files.
+
+* 17.01.1/15.5A-LTS: Changed where updates are downloaded from for long-term
support releases. Installing this version is mandatory.
* Improved responsiveness and reliability when using the add-on to switch to
Studio, either using focus to Studio command from other programs or when an
encoder is connected and NVDA is told to switch to Studio when this happens. If
Studio is minimized, Studio window will be shown as unavailable. If so, restore
Studio window from system tray.
* If editing carts while Cart Explorer is active, it is no longer necessary to
reenter Cart Explorer to view updated cart assignments when Cart Edit mode is
turned off. Consequently, Cart Explorer reentry message is no longer announced.
* In add-on 15.5-LTS, corrected user interface presentation for SPL add-on
settings dialog.
@@ -527,6 +530,6 @@ Version 4.0 supports SPL Studio 5.00 and later, with 3.x
designed to provide som
[2]: http://addons.nvda-project.org/files/get.php?file=spl-dev
-[3]: http://spl.nvda-kr.org/files/get.php?file=spl-lts7
+[3]: http://josephsl.net/files/nvdaaddons/get.php?file=spl-lts7
[4]: https://github.com/josephsl/stationplaylist/wiki/SPLAddonGuide
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/e2a9ace7a735/
Changeset: e2a9ace7a735
Branch: None
User: josephsl
Date: 2017-01-15 16:56:41+00:00
Summary: Merged stable
Affected #: 2 files
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index 3d1827f..2acd22a 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -37,7 +37,7 @@ _updatePickle = os.path.join(globalVars.appArgs.configPath,
"splupdate.pickle")
# Not all update channels are listed. The one not listed here is the default
("stable" for this branch).
channels={
"stable":"http://addons.nvda-project.org/files/get.php?file=spl";,
- "lts":"http://josephsl.net/files/nvdaaddons/get.php?file=spl-lts16";,
+ "lts":"http://www.josephsl.net/files/nvdaaddons/get.php?file=spl-lts16";,
#"beta":"http://spl.nvda-kr.org/files/get.php?file=spl-beta";,
}
diff --git a/readme.md b/readme.md
index d7786b1..3019fe4 100755
--- a/readme.md
+++ b/readme.md
@@ -530,6 +530,6 @@ Version 4.0 supports SPL Studio 5.00 and later, with 3.x
designed to provide som
[2]: http://addons.nvda-project.org/files/get.php?file=spl-dev
-[3]: http://josephsl.net/files/nvdaaddons/get.php?file=spl-lts7
+[3]: http://www.josephsl.net/files/nvdaaddons/get.php?file=spl-lts7
[4]: https://github.com/josephsl/stationplaylist/wiki/SPLAddonGuide
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/fc6344772dbc/
Changeset: fc6344772dbc
Branch: None
User: josephsl
Date: 2017-01-15 16:58:20+00:00
Summary: Removed LTS branch from dev channels list
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index 2acd22a..3191f1c 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -37,7 +37,6 @@ _updatePickle = os.path.join(globalVars.appArgs.configPath,
"splupdate.pickle")
# Not all update channels are listed. The one not listed here is the default
("stable" for this branch).
channels={
"stable":"http://addons.nvda-project.org/files/get.php?file=spl";,
- "lts":"http://www.josephsl.net/files/nvdaaddons/get.php?file=spl-lts16";,
#"beta":"http://spl.nvda-kr.org/files/get.php?file=spl-beta";,
}
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/7e80df8e8e38/
Changeset: 7e80df8e8e38
Branch: None
User: josephsl
Date: 2017-01-16 22:01:51+00:00
Summary: Merge branch 'stable'
Affected #: 6 files
diff --git a/addon/doc/ar/readme.md b/addon/doc/ar/readme.md
index 83680a9..8358bef 100644
--- a/addon/doc/ar/readme.md
+++ b/addon/doc/ar/readme.md
@@ -1,4 +1,5 @@
-# StationPlaylist Studio #
+
+[[!meta title="StationPlaylist Studio"]]
* مطورو الإضافة: Geoff Shang, Joseph Lee وآخرون
* تحميل [الإصدار النهائي][1]
@@ -249,6 +250,20 @@ broadcast profiles.
استخدم لمسة ب3 أصابع للانتقال لنمط اللمس, ثم استخدم أوامر اللمس المسرودة
أعلاه لأداء المهام.
+## Version 17.01/15.5-LTS
+
+* Improved responsiveness and reliability when using the add-on to switch to
+ Studio, either using focus to Studio command from other programs or when
+ an encoder is connected and NVDA is told to switch to Studio when this
+ happens. If Studio is minimized, Studio window will be shown as
+ unavailable. If so, restore Studio window from system tray.
+* If editing carts while Cart Explorer is active, it is no longer necessary
+ to reenter Cart Explorer to view updated cart assignments when Cart Edit
+ mode is turned off. Consequently, Cart Explorer reentry message is no
+ longer announced.
+* In add-on 15.5-LTS, corrected user interface presentation for SPL add-on
+ settings dialog.
+
## Version 16.12.1
* Corrected user interface presentation for SPL add-on settings dialog.
diff --git a/addon/doc/es/readme.md b/addon/doc/es/readme.md
index 72e3e18..ebfa84a 100644
--- a/addon/doc/es/readme.md
+++ b/addon/doc/es/readme.md
@@ -1,4 +1,5 @@
-# StationPlaylist Studio #
+
+[[!meta title="StationPlaylist Studio"]]
* Autores: Geoff Shang, Joseph Lee y otros colaboradores
* Descargar [Versión estable][1]
@@ -287,9 +288,26 @@ realizar algunas órdenes de Studio desde la pantalla
táctil. Primero utiliza
un toque con tres dedos para cambiar a modo SPL, entonces utiliza las
órdenes táctiles listadas arriba para llevar a cabo tareas.
-## Version 16.12.1
-
-* Corrected user interface presentation for SPL add-on settings dialog.
+## Versión 17.01/15.5-LTS
+
+* Mejorada la respuesta y la fiabilidad al utilizar el complemento para
+ cambiar a Studio, o utilizando el foco para órdenes de Studio desde otros
+ programas o cuando un codificador está conectado y se le pide a NVDA que
+ cambie a Studio cuando esto ocurra. Si Studio se minimiza, la ventana de
+ Studio se mostrará como no disponible. Si es así, restaura la ventana de
+ Studio desde la bandeja del sistema.
+* Si se editan carts mientras el explorador de Cart está activado, ya no es
+ necesario reintroducir el explorador de Cart para ver las asignaciones de
+ cart actualizadas cuando el modo Edición de Cart se
+ desactive. Consecuentemente, el mensaje reintroducir explorador de Cart ya
+ no se anuncia.
+* En el complemento 15.5-LTS, se corrigió la presentación de la interfaz de
+ usuario para el diálogo Opciones del complemento SPL.
+
+## Versión 16.12.1
+
+* Corregida la presentación de la interfaz de usuario para el diálogo
+ Opciones del complemento SPL.
* Traducciones actualizadas.
## Versión 16.12/15.4-LTS
diff --git a/addon/doc/fr/readme.md b/addon/doc/fr/readme.md
index c496723..99d5c8d 100644
--- a/addon/doc/fr/readme.md
+++ b/addon/doc/fr/readme.md
@@ -1,4 +1,5 @@
-# StationPlaylist Studio #
+
+[[!meta title="StationPlaylist Studio"]]
* Auteurs: Geoff Shang, Joseph Lee et d'autres contributeurs.
* Télécharger [version stable][1]
@@ -15,11 +16,12 @@ module complémentaire][4]. Pour les développeurs cherchant
à savoir comment
construire le module complémentaire, voir buildInstructions.txt situé à la
racine du code source du module complémentaire du référentiel.
-IMPORTANT: This add-on requires NVDA 2015.3 or later and StationPlaylist
-Studio 5.00 or later. If you have installed NVDA 2016.1 or later on Windows
-8 and later, disable audio ducking mode. Also, add-on 8.0/16.10 requires
-Studio 5.10 and later, and for broadcasters using Studio 5.0x, a long-term
-support version (15.x) is available.
+IMPORTANT : Ce module complémentaire nécessite NVDA 2015.3 ou plus récent et
+StationPlaylist Studio 5.00 ou version ultérieure. Si vous avez installé
+NVDA 2016.1 ou version ultérieure sur Windows 8 et supérieur désactiver le
+Mode d'atténuation audio. En outre,le module complémentaire 8.0/16.10
+nécessite Studio 5.10 et ultérieure, et pour les diffusions utilisant Studio
+5.0x, une version prise en charge à long terme (15.x) est disponible.
## Raccourcis clavier
@@ -164,9 +166,10 @@ Les commandes disponibles sont :
* R (Maj+E dans la disposition de JAWS et Window-Eyes) : Enregistrer dans
un fichier activé/désactivé.
* Maj+R : Contrôle du balayage de la bibliothèque en cours.
-* S: Track starts (scheduled).
-* Shift+S: Time until selected track will play (track starts in).
-* T: Cart edit/insert mode on/off.
+* S : Piste débute (planifié).
+* Maj+S : Durée jusqu'à la piste sélectionnée qui va être jouer (piste
+ débute dans).
+* T : Mode édition/insertion chariot activé/désactivé.
* U: temps de fonctionnement Studio.
* Contrôle+Maj+U : Rechercher les mises à jour du module complémentaire.
* W: Météo et température si configurée.
@@ -297,22 +300,44 @@ un écran tactile. Tout d'abord utiliser une tape à trois
doigts pour
basculer en mode SPL, puis utilisez les commandes tactile énumérées
ci-dessus pour exécuter des commandes.
+## Version 17.01/15.5-LTS
+
+* Amélioration de la réactivité et de la fiabilité lors de l'utilisation du
+ module complémentaire pour basculer à Studio, en utilisant le focus sur la
+ commande Studio à partir d'autres programmes ou lorsqu'un encodeur est
+ connecté et NVDA est invité à basculer vers Studio lorsque cela se
+ produit. Si Studio est minimisé, la fenêtre Studio s'affichera comme
+ indisponible. Dans ce cas, restaurez la fenêtre Studio depuis la barre
+ d'état système.
+* Si vous modifier des chariots pendant que l'Explorateur de Chariot est
+ actif, il n'est plus nécessaire d'entrer à nouveau dans l'Explorateur de
+ Chariot pour afficher la mise à jour des assignations de chariot lorsque
+ le mode édition chariot est désactivé. Par conséquent, le message pour
+ entrer à nouveau dans l'Explorateur de Chariot n'est plus annoncé.
+* Dans le module complémentaire 15.5-LTS, correction de la présentation de
+ l'interface utilisateur pour le dialogue Paramètres module complémentaire
+ SPL.
+
## Version 16.12.1
-* Corrected user interface presentation for SPL add-on settings dialog.
+* Correction de la présentation de l'interface utilisateur pour le dialogue
+ Paramètres module complémentaire SPL.
* Mises à jour des traductions.
## Version 16.12/15.4-LTS
-* More work on supporting Studio 5.20, including announcing cart insert mode
- status (if turned on) from SPL Assistant layer (T).
-* Cart edit/insert mode toggle is no longer affected by message verbosity
- nor status announcement type settings (this status will always be
- announced via speech and/or braille).
-* It is no longer possible to add comments to timed break notes.
-* Support for Track Tool 5.20, including fixed an issue where wrong
- information is announced when using Columns Explorer commands to announce
- column information.
+* Plus de travail sur le support Studio 5.20, incluant l'annonce du statut
+ en mode insertion chariot (si celui-ci est activé) depuis la couche
+ Assistant SPL (T).
+* Le basculement du Mode édition/insertion chariot n'est plus affecté par
+ les paramètres de type verbosité du message ni par le statut (ce statut
+ sera toujours annoncé par la parole et / ou le braille).
+* Il n'est plus possible d'ajouter des commentaires aux notes de pause
+ temporisées.
+* Support pour l'Outil de Piste 5.20, incluant la résolution d'un problème
+ où des informations erronées sont annoncées lors de l'utilisation des
+ commandes dans l'Explorateur de Colonnes pour annoncer les informations
+ sur les colonnes.
## Version 16.11/15.3-LTS
@@ -337,12 +362,14 @@ ci-dessus pour exécuter des commandes.
## Version 8.0/16.10/15.0-LTS
-Version 8.0 (also known as 16.10) supports SPL Studio 5.10 and later, with
-15.0-LTS (formerly 7.x) designed to provide some new features from 8.0 for
-users using earlier versions of Studio. Unless otherwise noted, entries
-below apply to both 8.0 and 7.x. A warning dialog will be shown the first
-time you use add-on 8.0 with Studio 5.0x installed, asking you to use 15.x
-LTS version.
+La version 8.0 (également connu sous le nom de 16.10) prend en charge la
+version SPL Studio 5.10 et ultérieure, avec la 15.0-LTS (anciennement la
+7.x) conçu pour fournir de nouvelles fonctionnalités depuis la 8.0 pour les
+utilisateurs des versions antérieures de Studio. À moins que dans le cas
+contraire les rubriques ci-dessous s’appliquent à les deux, 8.0 et 7.x. Un
+dialogue d'avertissement apparaît la première fois que vous utilisez le
+module complémentaire 8.0 avec Studio 5.0x installé, vous demandant
+d’utiliser la version 15.x LTS.
* Le Schéma de la version a changé pour refléter la version year.month au
lieu de major.minor. Au cours de la période de transition (jusqu'au
diff --git a/addon/doc/gl/readme.md b/addon/doc/gl/readme.md
index 8ebec55..6d22cba 100644
--- a/addon/doc/gl/readme.md
+++ b/addon/doc/gl/readme.md
@@ -1,4 +1,5 @@
-# StationPlaylist Studio #
+
+[[!meta title="StationPlaylist Studio"]]
* Autores: Geoff Shang, Joseph Lee e outros colaboradores
* Descargar [versión estable][1]
@@ -279,9 +280,26 @@ realizar algunhas ordes do Studio dende a pantalla tactil.
Primeiro usa un
toque con tgres dedos para cambiar a modo SPL, logo usa as ordes tactiles
listadas arriba para realizar ordes.
-## Version 16.12.1
-
-* Corrected user interface presentation for SPL add-on settings dialog.
+## Versión 17.01/15.5-LTS
+
+* Mellorada a resposta e a fiabilidade ao se usar o complemento para cambiar
+ ao Studio, ou usando o foco para ordes do Studio dende outros programas ou
+ cando un codificador está conectado e se lle pide ao NVDA que cambie ao
+ Studio cando esto ocurra. Se o Studio se minimiza, a ventá do Studio
+ amosarase como non dispoñible. Se é así, restaura a ventá do Studio dende
+ a bandexa do sistema.
+* Se se editan carts mentres o explorador de Cart está activado, xa non é
+ necesario reintroducir o explorador de Cart para ver as asignacións de
+ cart actualizadas cando o modo Edición de Cart se
+ desactive. Consecuentemente, a mensaxe reintroducir explorador de Cart xa
+ non se anuncia.
+* No complemento 15.5-LTS, correxiuse a presentación da interfaz do usuario
+ para o diálogo de opcións do complemento SPL.
+
+## Versión 16.12.1
+
+* Correxida a presentación da interfaz do usuario para o diálogo de opcións
+ do complemento SPL.
* Traducións actualizadas.
## Versión 16.12/15.4-LTS
diff --git a/addon/doc/hu/readme.md b/addon/doc/hu/readme.md
index 4e65bd7..16ba864 100644
--- a/addon/doc/hu/readme.md
+++ b/addon/doc/hu/readme.md
@@ -1,4 +1,5 @@
-# StationPlaylist Studio #
+
+[[!meta title="StationPlaylist Studio"]]
* Készítők: Geoff Shang, Joseph Lee, és további közreműködők
* Letöltés [Stabil verzió][1]
@@ -260,6 +261,20 @@ Amennyiben érintőképernyős számítógépen használja a
Studiot Windows 8,
parancsokat végrehajthat az érintőképernyőn is. Először 3 ujjas koppintással
váltson SPL módra, és utána már használhatók az alább felsorolt parancsok.
+## Version 17.01/15.5-LTS
+
+* Improved responsiveness and reliability when using the add-on to switch to
+ Studio, either using focus to Studio command from other programs or when
+ an encoder is connected and NVDA is told to switch to Studio when this
+ happens. If Studio is minimized, Studio window will be shown as
+ unavailable. If so, restore Studio window from system tray.
+* If editing carts while Cart Explorer is active, it is no longer necessary
+ to reenter Cart Explorer to view updated cart assignments when Cart Edit
+ mode is turned off. Consequently, Cart Explorer reentry message is no
+ longer announced.
+* In add-on 15.5-LTS, corrected user interface presentation for SPL add-on
+ settings dialog.
+
## Version 16.12.1
* Corrected user interface presentation for SPL add-on settings dialog.
diff --git a/addon/locale/fr/LC_MESSAGES/nvda.po
b/addon/locale/fr/LC_MESSAGES/nvda.po
index 723e04d..2773fb9 100755
--- a/addon/locale/fr/LC_MESSAGES/nvda.po
+++ b/addon/locale/fr/LC_MESSAGES/nvda.po
@@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: StationPlaylist 4.1\n"
"Report-Msgid-Bugs-To: nvda-translations@xxxxxxxxxxxxx\n"
"POT-Creation-Date: \n"
-"PO-Revision-Date: 2016-11-21 07:53-0800\n"
+"PO-Revision-Date: 2016-12-20 13:07+0100\n"
"Last-Translator: Rémy Ruiz <remyruiz@xxxxxxxxx>\n"
"Language-Team: Rémy Ruiz <remyruiz@xxxxxxxxx>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Poedit 1.5.7\n"
+"X-Generator: Poedit 1.8.9\n"
"X-Poedit-SourceCharset: UTF-8\n"
#. Translators: Presented when only Track Tool is running (Track Dial requires
Studio to be running as well).
@@ -144,7 +144,6 @@ msgid "Status: {name}"
msgstr "Statut : {name}"
#. Translators: The text of the help command in SPL Assistant layer.
-#, fuzzy
msgid ""
"After entering SPL Assistant, press:\n"
"A: Automation.\n"
@@ -199,7 +198,7 @@ msgstr ""
"Maj+R : Contrôle du balayage de la bibliothèque.\n"
"S : Heure prévue pour la piste.\n"
"Maj+S : Durée jusqu'à la piste sélectionnée qui va être jouer.\n"
-"T : Mode édition chariot.\n"
+"T : Mode édition/insertion chariot.\n"
"U : Temps de fonctionnement Studio.\n"
"W : Météo et température.\n"
"Y : Modification de la playlist.\n"
@@ -212,7 +211,6 @@ msgstr ""
"Maj+F1 : Ouvre le guide de l'utilisateur en ligne."
#. Translators: The text of the help command in SPL Assistant layer when JFW
layer is active.
-#, fuzzy
msgid ""
"After entering SPL Assistant, press:\n"
"A: Automation.\n"
@@ -271,7 +269,7 @@ msgstr ""
"Maj+R : Contrôle du balayage de la bibliothèque.\n"
"S : Heure prévue pour la piste.\n"
"Maj+S : Durée jusqu'à la piste sélectionnée qui va être jouer.\n"
-"T : Mode édition chariot.\n"
+"T : Mode édition/insertion chariot.\n"
"U : Temps de fonctionnement Studio.\n"
"W : Météo et température.\n"
"Y : Modification de la playlist.\n"
@@ -284,7 +282,6 @@ msgstr ""
"Maj+F1 : Ouvre le guide de l'utilisateur en ligne."
#. Translators: The text of the help command in SPL Assistant layer when
Window-Eyes layer is active.
-#, fuzzy
msgid ""
"After entering SPL Assistant, press:\n"
"A: Automation.\n"
@@ -347,7 +344,7 @@ msgstr ""
"Maj+R : Contrôle du balayage de la bibliothèque.\n"
"S : Heure prévue pour la piste.\n"
"Maj+S : Durée jusqu'à la piste sélectionnée qui va être jouer.\n"
-"T : Mode édition chariot.\n"
+"T : Mode édition/insertion chariot.\n"
"U : Temps de fonctionnement Studio.\n"
"W : Météo et température.\n"
"Y : Modification de la playlist.\n"
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/fd7ebf4ae3bd/
Changeset: fd7ebf4ae3bd
Branch: None
User: josephsl
Date: 2017-01-17 00:42:30+00:00
Summary: Vertical column nav optimization: let column tracker be managed by
the overlay class instead of exposing this in the app module.
in the app module, column nav tracker (the variable used to track which column
a broadcaster is reviewing) should be handled by the overlay class. This
prevents someone from changing column nav behavior by modifying the app module
and allows the object (track item) itself to record which column one is
consulting.
This is destined for add-on 17.04. A similar change will be made to track Tool
app module.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 4069f79..d85628a 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -126,6 +126,9 @@ _SPLCategoryTones = {
class SPLTrackItem(IAccessible):
"""A base class for providing utility scripts when track entries are
focused, such as track dial."""
+ # Keep a record of which column is being looked at.
+ _curColumnNumber = None
+
def initOverlayClass(self):
# LTS: Take a greater role in assigning enhanced Columns
Explorer command at the expense of limiting where this can be invoked.
# 8.0: Just assign number row.
@@ -148,6 +151,8 @@ class SPLTrackItem(IAccessible):
return None
def reportFocus(self):
+ # initialize column navigation tracker.
+ if self.__class__._curColumnNumber is None:
self.__class__._curColumnNumber = 0
# 7.0: Cache column header data structures if meeting track
items for the first time.
# It is better to do it while reporting focus, otherwise Python
throws recursion limit exceeded error when initOverlayClass does this.
if self.appModule._columnHeaders is None:
@@ -180,10 +185,10 @@ class SPLTrackItem(IAccessible):
else:
self.appModule._announceColumnOnly = None
verticalColumnAnnounce =
splconfig.SPLConfig["General"]["VerticalColumnAnnounce"]
- if verticalColumnAnnounce == "Status" or
(verticalColumnAnnounce is None and self.appModule.SPLColNumber == 0):
+ if verticalColumnAnnounce == "Status" or
(verticalColumnAnnounce is None and self._curColumnNumber == 0):
self._leftmostcol()
else:
-
self.announceColumnContent(self.appModule.SPLColNumber if
verticalColumnAnnounce is None else self.indexOf(verticalColumnAnnounce),
header=verticalColumnAnnounce, reportStatus=self.name is not None)
+
self.announceColumnContent(self._curColumnNumber if verticalColumnAnnounce is
None else self.indexOf(verticalColumnAnnounce), header=verticalColumnAnnounce,
reportStatus=self.name is not None)
# 7.0: Let the app module keep a reference to this track.
self.appModule._focusedTrack = self
@@ -225,29 +230,29 @@ class SPLTrackItem(IAccessible):
# Now the scripts.
def script_nextColumn(self, gesture):
- if (self.appModule.SPLColNumber+1) ==
self.appModule._columnHeaders.childCount:
+ if (self._curColumnNumber+1) ==
self.appModule._columnHeaders.childCount:
tones.beep(2000, 100)
else:
- self.appModule.SPLColNumber +=1
- self.announceColumnContent(self.appModule.SPLColNumber)
+ self.__class__._curColumnNumber +=1
+ self.announceColumnContent(self._curColumnNumber)
def script_prevColumn(self, gesture):
- if self.appModule.SPLColNumber <= 0:
+ if self._curColumnNumber <= 0:
tones.beep(2000, 100)
else:
- self.appModule.SPLColNumber -=1
- if self.appModule.SPLColNumber == 0:
+ self.__class__._curColumnNumber -=1
+ if self._curColumnNumber == 0:
self._leftmostcol()
else:
- self.announceColumnContent(self.appModule.SPLColNumber)
+ self.announceColumnContent(self._curColumnNumber)
def script_firstColumn(self, gesture):
- self.appModule.SPLColNumber = 0
+ self.__class__._curColumnNumber = 0
self._leftmostcol()
def script_lastColumn(self, gesture):
- self.appModule.SPLColNumber =
self.appModule._columnHeaders.childCount-1
- self.announceColumnContent(self.appModule.SPLColNumber)
+ self.__class__._curColumnNumber =
self.appModule._columnHeaders.childCount-1
+ self.announceColumnContent(self._curColumnNumber)
# Track movement scripts.
# Detects top/bottom of a playlist if told to do so.
@@ -270,6 +275,7 @@ class SPLTrackItem(IAccessible):
tones.beep(2000, 100)
else:
self.appModule._announceColumnOnly = True
+ newTrack._curColumnNumber = self._curColumnNumber
newTrack.setFocus(), newTrack.setFocus()
selectTrack(newTrack.IAccessibleChildID-1)
@@ -279,6 +285,7 @@ class SPLTrackItem(IAccessible):
tones.beep(2000, 100)
else:
self.appModule._announceColumnOnly = True
+ newTrack._curColumnNumber = self._curColumnNumber
newTrack.setFocus(), newTrack.setFocus()
selectTrack(newTrack.IAccessibleChildID-1)
@@ -665,8 +672,6 @@ class AppModule(appModuleHandler.AppModule):
scanCount = 0
# Prevent NVDA from announcing scheduled time multiple times.
scheduledTimeCache = ""
- # Track Dial (A.K.A. enhanced arrow keys)
- SPLColNumber = 0
# Automatically announce mic, line in, etc changes
# These items are static text items whose name changes.
@@ -874,6 +879,8 @@ class AppModule(appModuleHandler.AppModule):
import globalPlugins.splUtils.encoders
globalPlugins.splUtils.encoders.cleanup()
splconfig.saveConfig()
+ # reset column number for column navigation commands.
+ if self._focusedTrack:
self._focusedTrack.__class__._curColumnNumber = None
# Delete focused track reference.
self._focusedTrack = None
try:
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/71475c3dec87/
Changeset: 71475c3dec87
Branch: None
User: josephsl
Date: 2017-01-20 10:48:19+00:00
Summary: 17.02: Do not allow update download nor profile deletion to
proceed if NVDA unexpectedly quits while the dialogs are opened.
Based on an issue with another add-on: if a dialog opens (such as new updates)
and NVDA unexpectedly quits, the default choice will be invoked (update
downloads, profile being deleted, etc.). Thus prevent this by setting 'no'
button to be the default.
Affected #: 2 files
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index a80a8b2..c3c2ed3 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -579,7 +579,7 @@ class SPLConfigDialog(gui.SettingsDialog):
_("Are you sure you want to delete this profile? This
cannot be undone."),
# Translators: The title of the confirmation dialog for
deletion of a profile.
_("Confirm Deletion"),
- wx.YES | wx.NO | wx.ICON_QUESTION, self
+ wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION, self
) == wx.NO:
return
splconfig.SPLConfig.deleteProfile(name)
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index 81f4001..af41dc9 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -153,6 +153,6 @@ def updateCheck(auto=False, continuous=False,
confUpdateInterval=1):
else: wx.CallAfter(getUpdateResponse, checkMessage, _("Studio add-on
update"), updateURL)
def getUpdateResponse(message, caption, updateURL):
- if gui.messageBox(message, caption, wx.YES | wx.NO | wx.CANCEL |
wx.CENTER | wx.ICON_QUESTION) == wx.YES:
+ if gui.messageBox(message, caption, wx.YES_NO | wx.NO_DEFAULT |
wx.CANCEL | wx.CENTER | wx.ICON_QUESTION) == wx.YES:
os.startfile(updateURL)
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/82f2ab7cfe98/
Changeset: 82f2ab7cfe98
Branch: None
User: josephsl
Date: 2017-01-22 20:38:37+00:00
Summary: Merge branch '16.10.x'
Affected #: 7 files
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index 454dbb1..1e7167b 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -484,7 +484,7 @@ class SPLConfigDialog(gui.SettingsDialog):
_("Are you sure you want to delete this profile? This
cannot be undone."),
# Translators: The title of the confirmation dialog for
deletion of a profile.
_("Confirm Deletion"),
- wx.YES | wx.NO | wx.ICON_QUESTION, self
+ wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION, self
) == wx.NO:
return
splconfig.SPLConfig.deleteProfile(name)
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index 3191f1c..ff7b209 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -147,5 +147,5 @@ def updateCheck(auto=False, continuous=False,
confUpdateInterval=1):
else: wx.CallAfter(getUpdateResponse, checkMessage, _("Studio add-on
update"), updateURL)
def getUpdateResponse(message, caption, updateURL):
- if gui.messageBox(message, caption, wx.YES | wx.NO | wx.CANCEL |
wx.CENTER | wx.ICON_QUESTION) == wx.YES:
+ if gui.messageBox(message, caption, wx.YES_NO | wx.NO_DEFAULT |
wx.CANCEL | wx.CENTER | wx.ICON_QUESTION) == wx.YES:
os.startfile(updateURL)
diff --git a/addon/doc/ar/readme.md b/addon/doc/ar/readme.md
index 8358bef..8c41a3c 100644
--- a/addon/doc/ar/readme.md
+++ b/addon/doc/ar/readme.md
@@ -1,5 +1,4 @@
-
-[[!meta title="StationPlaylist Studio"]]
+# StationPlaylist Studio #
* مطورو الإضافة: Geoff Shang, Joseph Lee وآخرون
* تحميل [الإصدار النهائي][1]
@@ -252,6 +251,11 @@ broadcast profiles.
## Version 17.01/15.5-LTS
+Note: 17.01.1/15.5A-LTS replaces 17.01 due to changes to location of new
+add-on files.
+
+* 17.01.1/15.5A-LTS: Changed where updates are downloaded from for long-term
+ support releases. Installing this version is mandatory.
* Improved responsiveness and reliability when using the add-on to switch to
Studio, either using focus to Studio command from other programs or when
an encoder is connected and NVDA is told to switch to Studio when this
@@ -857,6 +861,6 @@ for stable releases.
[2]: http://addons.nvda-project.org/files/get.php?file=spl-dev [2]: ;
-[3]: http://spl.nvda-kr.org/files/get.php?file=spl-lts16
+[3]: http://josephsl.net/files/nvdaaddons/get.php?file=spl-lts16
[4]: https://github.com/josephsl/stationplaylist/wiki/SPLAddonGuide
diff --git a/addon/doc/es/readme.md b/addon/doc/es/readme.md
index ebfa84a..1d53d76 100644
--- a/addon/doc/es/readme.md
+++ b/addon/doc/es/readme.md
@@ -1,5 +1,4 @@
-
-[[!meta title="StationPlaylist Studio"]]
+# StationPlaylist Studio #
* Autores: Geoff Shang, Joseph Lee y otros colaboradores
* Descargar [Versión estable][1]
@@ -290,6 +289,12 @@ un toque con tres dedos para cambiar a modo SPL, entonces
utiliza las
## Versión 17.01/15.5-LTS
+Nota: 17.01.1/15.5A-LTS reemplaza a 17.01 debido a cambios de la
+localización de los ficheros nuevos del complemento.
+
+* 17.01.1/15.5A-LTS: se cambió de dónde se descargan las actualizaciones
+ para las versiones de soporte a largo plazo. Es obligatoria la instalación
+ de esta versión.
* Mejorada la respuesta y la fiabilidad al utilizar el complemento para
cambiar a Studio, o utilizando el foco para órdenes de Studio desde otros
programas o cuando un codificador está conectado y se le pide a NVDA que
@@ -1014,6 +1019,6 @@ utilizando versiones anteriores de Studio.
[2]: http://addons.nvda-project.org/files/get.php?file=spl-dev
-[3]: http://spl.nvda-kr.org/files/get.php?file=spl-lts16
+[3]: http://josephsl.net/files/nvdaaddons/get.php?file=spl-lts16
[4]: https://github.com/josephsl/stationplaylist/wiki/SPLAddonGuide
diff --git a/addon/doc/fr/readme.md b/addon/doc/fr/readme.md
index 99d5c8d..27dbba1 100644
--- a/addon/doc/fr/readme.md
+++ b/addon/doc/fr/readme.md
@@ -1,5 +1,4 @@
-
-[[!meta title="StationPlaylist Studio"]]
+# StationPlaylist Studio #
* Auteurs: Geoff Shang, Joseph Lee et d'autres contributeurs.
* Télécharger [version stable][1]
@@ -302,6 +301,12 @@ ci-dessus pour exécuter des commandes.
## Version 17.01/15.5-LTS
+Remarque: 17.01.1/15.5A-LTS remplace la 17.01 en raison des changements
+apportés à l'emplacement des nouveaux fichiers du module complémentaire.
+
+* 17.01.1/15.5A-LTS: Modifié à partir duquel les mises à jour sont
+ téléchargées pour les versions prises en charges à long
+ terme. L'installation de cette version est obligatoire.
* Amélioration de la réactivité et de la fiabilité lors de l'utilisation du
module complémentaire pour basculer à Studio, en utilisant le focus sur la
commande Studio à partir d'autres programmes ou lorsqu'un encodeur est
@@ -1080,6 +1085,6 @@ utilisateurs des versions antérieures de Studio.
[2]: http://addons.nvda-project.org/files/get.php?file=spl-dev
-[3]: http://spl.nvda-kr.org/files/get.php?file=spl-lts16
+[3]: http://josephsl.net/files/nvdaaddons/get.php?file=spl-lts16
[4]: https://github.com/josephsl/stationplaylist/wiki/SPLAddonGuide
diff --git a/addon/doc/gl/readme.md b/addon/doc/gl/readme.md
index 6d22cba..e918f45 100644
--- a/addon/doc/gl/readme.md
+++ b/addon/doc/gl/readme.md
@@ -1,5 +1,4 @@
-
-[[!meta title="StationPlaylist Studio"]]
+# StationPlaylist Studio #
* Autores: Geoff Shang, Joseph Lee e outros colaboradores
* Descargar [versión estable][1]
@@ -282,6 +281,12 @@ listadas arriba para realizar ordes.
## Versión 17.01/15.5-LTS
+Nota: 17.01.1/15.5A-LTS reemplaza a 17.01 debido aos cambios da localización
+dos ficheiros novos do complemento.
+
+* 17.01.1/15.5A-LTS: cambiouse de onde se descargan as actualizacións para
+ as versións de soporte a longo prazo. É obrigatoria a instalación desta
+ versión.
* Mellorada a resposta e a fiabilidade ao se usar o complemento para cambiar
ao Studio, ou usando o foco para ordes do Studio dende outros programas ou
cando un codificador está conectado e se lle pide ao NVDA que cambie ao
@@ -988,6 +993,6 @@ utilicen versións anteriores de Studio.
[2]: http://addons.nvda-project.org/files/get.php?file=spl-dev
-[3]: http://spl.nvda-kr.org/files/get.php?file=spl-lts16
+[3]: http://josephsl.net/files/nvdaaddons/get.php?file=spl-lts16
[4]: https://github.com/josephsl/stationplaylist/wiki/SPLAddonGuide
diff --git a/addon/doc/hu/readme.md b/addon/doc/hu/readme.md
index 16ba864..f3aafb1 100644
--- a/addon/doc/hu/readme.md
+++ b/addon/doc/hu/readme.md
@@ -1,5 +1,4 @@
-
-[[!meta title="StationPlaylist Studio"]]
+# StationPlaylist Studio #
* Készítők: Geoff Shang, Joseph Lee, és további közreműködők
* Letöltés [Stabil verzió][1]
@@ -263,6 +262,11 @@ váltson SPL módra, és utána már használhatók az alább
felsorolt parancso
## Version 17.01/15.5-LTS
+Note: 17.01.1/15.5A-LTS replaces 17.01 due to changes to location of new
+add-on files.
+
+* 17.01.1/15.5A-LTS: Changed where updates are downloaded from for long-term
+ support releases. Installing this version is mandatory.
* Improved responsiveness and reliability when using the add-on to switch to
Studio, either using focus to Studio command from other programs or when
an encoder is connected and NVDA is told to switch to Studio when this
@@ -923,6 +927,6 @@ A kiegészítő 4.0 verziója a Studio 5.00 és későbbi
kiadásait támogatja.
[2]: http://addons.nvda-project.org/files/get.php?file=spl-dev
-[3]: http://spl.nvda-kr.org/files/get.php?file=spl-lts16
+[3]: http://josephsl.net/files/nvdaaddons/get.php?file=spl-lts16
[4]: https://github.com/josephsl/stationplaylist/wiki/SPLAddonGuide
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/57a837a14289/
Changeset: 57a837a14289
Branch: None
User: josephsl
Date: 2017-01-23 06:07:55+00:00
Summary: Code deduplication/optimization: just use one copy of layer helper.
Code duplication: layer helper is defined in both the app module and the global
pulgin, and they are identical. Thus it is better to let the app module use the
global plugin (not the other way around as it causes import error, and if
fooled to ignore nonexist SPL window, causes bytecode to be generated when the
app isn't running) in order to let just one version deal with both Controller
and Assistant layers.
Affected #: 2 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index d85628a..24656e2 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -10,7 +10,6 @@
# Minimum version: SPL 5.10, NvDA 2016.4.
-from functools import wraps
import os
import time
import threading
@@ -38,20 +37,6 @@ import splupdate
import addonHandler
addonHandler.initTranslation()
-
-# The finally function for status announcement scripts in this module (source:
Tyler Spivey's code).
-def finally_(func, final):
- """Calls final after func, even if it fails."""
- def wrap(f):
- @wraps(f)
- def new(*args, **kwargs):
- try:
- func(*args, **kwargs)
- finally:
- final()
- return new
- return wrap(final)
-
# Make sure the broadcaster is running a compatible version.
SPLMinVersion = "5.10"
@@ -1583,8 +1568,10 @@ class AppModule(appModuleHandler.AppModule):
return appModuleHandler.AppModule.getScript(self,
gesture)
script = appModuleHandler.AppModule.getScript(self, gesture)
if not script:
- script = finally_(self.script_error, self.finish)
- return finally_(script, self.finish)
+ script = self.script_error
+ # Just use finally function from the global plugin to reduce
code duplication.
+ import globalPlugins.splUtils
+ return globalPlugins.splUtils.finally_(script, self.finish)
def finish(self):
self.SPLAssistant = False
diff --git a/addon/globalPlugins/splUtils/__init__.py
b/addon/globalPlugins/splUtils/__init__.py
index 15564ad..6a3db64 100755
--- a/addon/globalPlugins/splUtils/__init__.py
+++ b/addon/globalPlugins/splUtils/__init__.py
@@ -13,8 +13,7 @@ import winUser
import addonHandler
addonHandler.initTranslation()
-# Layer environment: same as the app module counterpart.
-
+# The finally function for status announcement scripts in this module (source:
Tyler Spivey's code).
def finally_(func, final):
"""Calls final after func, even if it fails."""
def wrap(f):
@@ -80,7 +79,7 @@ class GlobalPlugin(globalPluginHandler.GlobalPlugin):
return globalPluginHandler.GlobalPlugin.getScript(self,
gesture)
script = globalPluginHandler.GlobalPlugin.getScript(self,
gesture)
if not script:
- script = finally_(self.script_error, self.finish)
+ script = self.script_error
return finally_(script, self.finish)
def finish(self):
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/dae960baff1c/
Changeset: dae960baff1c
Branch: None
User: josephsl
Date: 2017-01-26 19:40:22+00:00
Summary: Update downloader (17.1-dev only): allow installed copy of nVDA to
download updates on its own. re #20.
Using NVDA Core's update downloader facility, SPL add-on can check for and
download updates on its won. However, for security reasons and for consistent
user experience with that of NVDA Core, only installed copies will do this.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index ff7b209..45fcf35 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -6,10 +6,20 @@
import os # Essentially, update download is no different than file downloads.
import cPickle
+import threading
+import tempfile
+import hashlib
+import ctypes.wintypes
+import ssl
+import wx
+import shellapi
import gui
import wx
import addonHandler
import globalVars
+import updateCheck as coreUpdateCheck
+import config
+import winUser
# Add-on manifest routine (credit: various add-on authors including Noelia
Martinez).
# Do not rely on using absolute path to open to manifest, as installation
directory may change in a future NVDA Core version (highly unlikely, but...).
@@ -148,4 +158,158 @@ def updateCheck(auto=False, continuous=False,
confUpdateInterval=1):
def getUpdateResponse(message, caption, updateURL):
if gui.messageBox(message, caption, wx.YES_NO | wx.NO_DEFAULT |
wx.CANCEL | wx.CENTER | wx.ICON_QUESTION) == wx.YES:
- os.startfile(updateURL)
+ SPLUpdateDownloader([updateURL]).start() if
config.isInstalledCopy() else os.startfile(updateURL)
+
+# Update downloader (credit: NV Access)
+# Customized for SPL add-on.
+
+#: The download block size in bytes.
+DOWNLOAD_BLOCK_SIZE = 8192 # 8 kb
+
+def checkForUpdate(auto=False):
+ """Check for an updated version of NVDA.
+ This will block, so it generally shouldn't be called from the main
thread.
+ @param auto: Whether this is an automatic check for updates.
+ @type auto: bool
+ @return: Information about the update or C{None} if there is no update.
+ @rtype: dict
+ @raise RuntimeError: If there is an error checking for an update.
+ """
+ params = {
+ "autoCheck": auto,
+ "version": versionInfo.version,
+ "versionType": versionInfo.updateVersionType,
+ "osVersion": winVersion.winVersionText,
+ "x64": os.environ.get("PROCESSOR_ARCHITEW6432") == "AMD64",
+ "language": languageHandler.getLanguage(),
+ "installed": config.isInstalledCopy(),
+ }
+ url = "%s?%s" % (CHECK_URL, urllib.urlencode(params))
+ try:
+ res = urllib.urlopen(url)
+ except IOError as e:
+ if isinstance(e.strerror, ssl.SSLError) and e.strerror.reason
== "CERTIFICATE_VERIFY_FAILED":
+ # #4803: Windows fetches trusted root certificates on
demand.
+ # Python doesn't trigger this fetch
(PythonIssue:20916), so try it ourselves
+ _updateWindowsRootCertificates()
+ # and then retry the update check.
+ res = urllib.urlopen(url)
+ else:
+ raise
+ if res.code != 200:
+ raise RuntimeError("Checking for update failed with code %d" %
res.code)
+ info = {}
+ for line in res:
+ line = line.rstrip()
+ try:
+ key, val = line.split(": ", 1)
+ except ValueError:
+ raise RuntimeError("Error in update check output")
+ info[key] = val
+ if not info:
+ return None
+ return info
+
+
+class SPLUpdateDownloader(coreUpdateCheck.UpdateDownloader):
+ """Overrides NVDA Core's downloader.)
+ No hash checking for now, and URL's and temp file paths are different.
+ """
+
+ def __init__(self, urls, fileHash=None):
+ """Constructor.
+ @param urls: URLs to try for the update file.
+ @type urls: list of str
+ @param fileHash: The SHA-1 hash of the file as a hex string.
+ @type fileHash: basestring
+ """
+ super(SPLUpdateDownloader, self).__init__(urls, fileHash)
+ self.urls = urls
+ self.destPath =
tempfile.mktemp(prefix="stationPlaylist_update-", suffix=".nvda-addon")
+ self.fileHash = fileHash
+
+ def start(self):
+ """Start the download.
+ """
+ self._shouldCancel = False
+ # Use a timer because timers aren't re-entrant.
+ self._guiExecTimer = wx.PyTimer(self._guiExecNotify)
+ gui.mainFrame.prePopup()
+ # Translators: The title of the dialog displayed while
downloading add-on update.
+ self._progressDialog = wx.ProgressDialog(_("Downloading Add-on
Update"),
+ # Translators: The progress message indicating that a
connection is being established.
+ _("Connecting"),
+ # PD_AUTO_HIDE is required because
ProgressDialog.Update blocks at 100%
+ # and waits for the user to press the Close button.
+ style=wx.PD_CAN_ABORT | wx.PD_ELAPSED_TIME |
wx.PD_REMAINING_TIME | wx.PD_AUTO_HIDE,
+ parent=gui.mainFrame)
+ self._progressDialog.Raise()
+ t = threading.Thread(target=self._bg)
+ t.daemon = True
+ t.start()
+
+ def _error(self):
+ self._stopped()
+ gui.messageBox(
+ # Translators: A message indicating that an error
occurred while downloading an update to NVDA.
+ _("Error downloading add-on update."),
+ _("Error"),
+ wx.OK | wx.ICON_ERROR)
+
+ def _downloadSuccess(self):
+ self._stopped()
+ # Translators: The message presented when the update has been
successfully downloaded
+ # and is about to be installed.
+ gui.messageBox(_("Add-on update downloaded. It will now be
installed."),
+ # Translators: The title of the dialog displayed when
the update is about to be installed.
+ _("Install Add-on Update"))
+ # #4475: ensure that the new process shows its first window, by
providing SW_SHOWNORMAL
+ shellapi.ShellExecute(None, None,
+ self.destPath.decode("mbcs"),
+ None, None, winUser.SW_SHOWNORMAL)
+
+
+# These structs are only complete enough to achieve what we need.
+class CERT_USAGE_MATCH(ctypes.Structure):
+ _fields_ = (
+ ("dwType", ctypes.wintypes.DWORD),
+ # CERT_ENHKEY_USAGE struct
+ ("cUsageIdentifier", ctypes.wintypes.DWORD),
+ ("rgpszUsageIdentifier", ctypes.c_void_p), # LPSTR *
+ )
+
+class CERT_CHAIN_PARA(ctypes.Structure):
+ _fields_ = (
+ ("cbSize", ctypes.wintypes.DWORD),
+ ("RequestedUsage", CERT_USAGE_MATCH),
+ ("RequestedIssuancePolicy", CERT_USAGE_MATCH),
+ ("dwUrlRetrievalTimeout", ctypes.wintypes.DWORD),
+ ("fCheckRevocationFreshnessTime", ctypes.wintypes.BOOL),
+ ("dwRevocationFreshnessTime", ctypes.wintypes.DWORD),
+ ("pftCacheResync", ctypes.c_void_p), # LPFILETIME
+ ("pStrongSignPara", ctypes.c_void_p), # PCCERT_STRONG_SIGN_PARA
+ ("dwStrongSignFlags", ctypes.wintypes.DWORD),
+ )
+
+# Borrowed from NVDA Core (the only difference is the URL).
+def _updateWindowsRootCertificates():
+ crypt = ctypes.windll.crypt32
+ # Get the server certificate.
+ sslCont = ssl._create_unverified_context()
+ u = urllib.urlopen("https://www.nvaccess.org/nvdaUpdateCheck", ;
context=sslCont)
+ cert = u.fp._sock.getpeercert(True)
+ u.close()
+ # Convert to a form usable by Windows.
+ certCont = crypt.CertCreateCertificateContext(
+ 0x00000001, # X509_ASN_ENCODING
+ cert,
+ len(cert))
+ # Ask Windows to build a certificate chain, thus triggering a root
certificate update.
+ chainCont = ctypes.c_void_p()
+ crypt.CertGetCertificateChain(None, certCont, None, None,
+
ctypes.byref(CERT_CHAIN_PARA(cbSize=ctypes.sizeof(CERT_CHAIN_PARA),
+ RequestedUsage=CERT_USAGE_MATCH())),
+ 0, None,
+ ctypes.byref(chainCont))
+ crypt.CertFreeCertificateChain(chainCont)
+ crypt.CertFreeCertificateContext(certCont)
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/27d64877c636/
Changeset: 27d64877c636
Branch: None
User: josephsl
Date: 2017-01-27 02:48:28+00:00
Summary: Update downloader (17.1-dev): Change splupdate.updateCheck to
updateChecker to avoid name clashes with nVDA Core's own update check module.
Affected #: 3 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 24656e2..2f2a239 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -2004,7 +2004,7 @@ class AppModule(appModuleHandler.AppModule):
_("Add-on update check"),
# Translators: The message displayed while checking for
newer version of Studio add-on.
_("Checking for new version of Studio add-on..."))
- threading.Thread(target=splupdate.updateCheck,
kwargs={"continuous":splconfig.SPLConfig["Update"]["AutoUpdateCheck"],
"confUpdateInterval":splconfig.SPLConfig["Update"]["UpdateInterval"]}).start()
+ threading.Thread(target=splupdate.updateChecker,
kwargs={"continuous":splconfig.SPLConfig["Update"]["AutoUpdateCheck"],
"confUpdateInterval":splconfig.SPLConfig["Update"]["UpdateInterval"]}).start()
__SPLAssistantGestures={
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 313e7f8..77f25ca 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -832,14 +832,14 @@ def triggerProfileSwitch():
# Its only job is to call the update check function (splupdate) with the auto
check enabled.
# The update checker will not be engaged if an instant switch profile is
active or it is not time to check for it yet (check will be done every 24
hours).
def autoUpdateCheck():
- splupdate.updateCheck(auto=True,
continuous=SPLConfig["Update"]["AutoUpdateCheck"],
confUpdateInterval=SPLConfig["Update"]["UpdateInterval"])
+ splupdate.updateChecker(auto=True,
continuous=SPLConfig["Update"]["AutoUpdateCheck"],
confUpdateInterval=SPLConfig["Update"]["UpdateInterval"])
# The timer itself.
# A bit simpler than NVDA Core's auto update checker.
def updateInit():
# LTS: Launch updater if channel change is detected.
if splupdate._updateNow:
- splupdate.updateCheck(auto=True) # No repeat here.
+ splupdate.updateChecker(auto=True) # No repeat here.
splupdate._SPLUpdateT = wx.PyTimer(autoUpdateCheck)
splupdate._updateNow = False
return
@@ -850,7 +850,7 @@ def updateInit():
elif splupdate.SPLAddonCheck < nextCheck < currentTime:
interval = SPLConfig["Update"]["UpdateInterval"]* 86400
# Call the update check now.
- splupdate.updateCheck(auto=True) # No repeat here.
+ splupdate.updateChecker(auto=True) # No repeat here.
splupdate._SPLUpdateT = wx.PyTimer(autoUpdateCheck)
splupdate._SPLUpdateT.Start(interval * 1000, True)
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index 45fcf35..369553d 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -17,7 +17,7 @@ import gui
import wx
import addonHandler
import globalVars
-import updateCheck as coreUpdateCheck
+import updateCheck
import config
import winUser
@@ -95,7 +95,7 @@ _progressDialog = None
# The update check routine.
# Auto is whether to respond with UI (manual check only), continuous takes in
auto update check variable for restarting the timer.
# ConfUpdateInterval comes from add-on config dictionary.
-def updateCheck(auto=False, continuous=False, confUpdateInterval=1):
+def updateChecker(auto=False, continuous=False, confUpdateInterval=1):
if _pendingChannelChange:
wx.CallAfter(gui.messageBox, _("Did you recently tell SPL
add-on to use a different update channel? If so, please restart NVDA before
checking for add-on updates."), _("Update channel changed"), wx.ICON_ERROR)
return
@@ -211,7 +211,7 @@ def checkForUpdate(auto=False):
return info
-class SPLUpdateDownloader(coreUpdateCheck.UpdateDownloader):
+class SPLUpdateDownloader(updateCheck.UpdateDownloader):
"""Overrides NVDA Core's downloader.)
No hash checking for now, and URL's and temp file paths are different.
"""
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/169ae5a8c61b/
Changeset: 169ae5a8c61b
Branch: None
User: josephsl
Date: 2017-01-28 17:59:50+00:00
Summary: Merged stable
Affected #: 0 files
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/80f5e7abd3eb/
Changeset: 80f5e7abd3eb
Branch: None
User: josephsl
Date: 2017-01-28 18:08:41+00:00
Summary: Update channel selection: add channels attribute to advanced
options dialog, useful for specifying what channels are available from which
release.
In case try build will gain ability to switch to development channel: use
update channels tuple from advanced options dialog to select release channels.
This will not be the case if a release supports one channel.
This is destined for add-on 17.04 and later.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index 1e7167b..1b9434e 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -1299,6 +1299,9 @@ class SayStatusDialog(wx.Dialog):
# 7.0: Auto update check will be configurable from this dialog.
class AdvancedOptionsDialog(wx.Dialog):
+ # Available channels (if there's only one, channel selection list will
not be shown).
+ _updateChannels = ("dev", "stable")
+
def __init__(self, parent):
# Translators: The title of a dialog to configure advanced SPL
add-on options such as update checking.
super(AdvancedOptionsDialog, self).__init__(parent,
title=_("Advanced options"))
@@ -1311,12 +1314,12 @@ class AdvancedOptionsDialog(wx.Dialog):
self.autoUpdateCheckbox.SetValue(self.Parent.autoUpdateCheck)
# Translators: The label for a setting in SPL add-on
settings/advanced options to select automatic update interval in days.
self.updateInterval=advOptionsHelper.addLabeledControl(_("Update &interval in
days"), gui.nvdaControls.SelectOnFocusSpinCtrl, min=1, max=30,
initial=parent.updateInterval)
- # LTS and 8.x only.
- # Translators: The label for a combo box to select update
channel.
- labelText = _("&Add-on update channel:")
- self.channels=advOptionsHelper.addLabeledControl(labelText,
wx.Choice, choices=["development", "stable"])
- self.updateChannels = ("dev", "stable")
-
self.channels.SetSelection(self.updateChannels.index(self.Parent.updateChannel))
+ # For releases that support channel switching.
+ if len(self._updateChannels) > 1:
+ # Translators: The label for a combo box to select
update channel.
+ labelText = _("&Add-on update channel:")
+
self.channels=advOptionsHelper.addLabeledControl(labelText, wx.Choice,
choices=["development", "stable"])
+
self.channels.SetSelection(self._updateChannels.index(self.Parent.updateChannel))
# Translators: A checkbox to toggle if SPL Controller command
can be used to invoke Assistant layer.
self.splConPassthroughCheckbox=advOptionsHelper.addItem(wx.CheckBox(self,
label=_("Allow SPL C&ontroller command to invoke SPL Assistant layer")))
self.splConPassthroughCheckbox.SetValue(self.Parent.splConPassthrough)
@@ -1347,7 +1350,7 @@ class AdvancedOptionsDialog(wx.Dialog):
parent.compLayer =
self.compatibilityLayouts[self.compatibilityList.GetSelection()][0]
parent.autoUpdateCheck = self.autoUpdateCheckbox.Value
parent.updateInterval = self.updateInterval.Value
- parent.updateChannel = ("dev",
"stable")[self.channels.GetSelection()]
+ if len(self._updateChannels) > 1: parent.updateChannel =
self.updateChannels[self.channels.GetSelection()]
parent.profiles.SetFocus()
parent.Enable()
self.Destroy()
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/25c6ad9dee7e/
Changeset: 25c6ad9dee7e
Branch: None
User: josephsl
Date: 2017-01-28 18:09:27+00:00
Summary: Merge branch 'master' into updateDownloader
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index 1e7167b..1b9434e 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -1299,6 +1299,9 @@ class SayStatusDialog(wx.Dialog):
# 7.0: Auto update check will be configurable from this dialog.
class AdvancedOptionsDialog(wx.Dialog):
+ # Available channels (if there's only one, channel selection list will
not be shown).
+ _updateChannels = ("dev", "stable")
+
def __init__(self, parent):
# Translators: The title of a dialog to configure advanced SPL
add-on options such as update checking.
super(AdvancedOptionsDialog, self).__init__(parent,
title=_("Advanced options"))
@@ -1311,12 +1314,12 @@ class AdvancedOptionsDialog(wx.Dialog):
self.autoUpdateCheckbox.SetValue(self.Parent.autoUpdateCheck)
# Translators: The label for a setting in SPL add-on
settings/advanced options to select automatic update interval in days.
self.updateInterval=advOptionsHelper.addLabeledControl(_("Update &interval in
days"), gui.nvdaControls.SelectOnFocusSpinCtrl, min=1, max=30,
initial=parent.updateInterval)
- # LTS and 8.x only.
- # Translators: The label for a combo box to select update
channel.
- labelText = _("&Add-on update channel:")
- self.channels=advOptionsHelper.addLabeledControl(labelText,
wx.Choice, choices=["development", "stable"])
- self.updateChannels = ("dev", "stable")
-
self.channels.SetSelection(self.updateChannels.index(self.Parent.updateChannel))
+ # For releases that support channel switching.
+ if len(self._updateChannels) > 1:
+ # Translators: The label for a combo box to select
update channel.
+ labelText = _("&Add-on update channel:")
+
self.channels=advOptionsHelper.addLabeledControl(labelText, wx.Choice,
choices=["development", "stable"])
+
self.channels.SetSelection(self._updateChannels.index(self.Parent.updateChannel))
# Translators: A checkbox to toggle if SPL Controller command
can be used to invoke Assistant layer.
self.splConPassthroughCheckbox=advOptionsHelper.addItem(wx.CheckBox(self,
label=_("Allow SPL C&ontroller command to invoke SPL Assistant layer")))
self.splConPassthroughCheckbox.SetValue(self.Parent.splConPassthrough)
@@ -1347,7 +1350,7 @@ class AdvancedOptionsDialog(wx.Dialog):
parent.compLayer =
self.compatibilityLayouts[self.compatibilityList.GetSelection()][0]
parent.autoUpdateCheck = self.autoUpdateCheckbox.Value
parent.updateInterval = self.updateInterval.Value
- parent.updateChannel = ("dev",
"stable")[self.channels.GetSelection()]
+ if len(self._updateChannels) > 1: parent.updateChannel =
self.updateChannels[self.channels.GetSelection()]
parent.profiles.SetFocus()
parent.Enable()
self.Destroy()
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/c88ba82d4f53/
Changeset: c88ba82d4f53
Branch: None
User: josephsl
Date: 2017-01-28 18:15:50+00:00
Summary: Update downloader: allow portable copy to download add-ons in the
background, add-on download success message is no longer displayed.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index 369553d..403c212 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -158,7 +158,7 @@ def updateChecker(auto=False, continuous=False,
confUpdateInterval=1):
def getUpdateResponse(message, caption, updateURL):
if gui.messageBox(message, caption, wx.YES_NO | wx.NO_DEFAULT |
wx.CANCEL | wx.CENTER | wx.ICON_QUESTION) == wx.YES:
- SPLUpdateDownloader([updateURL]).start() if
config.isInstalledCopy() else os.startfile(updateURL)
+ SPLUpdateDownloader([updateURL]).start()
# Update downloader (credit: NV Access)
# Customized for SPL add-on.
@@ -258,15 +258,8 @@ class SPLUpdateDownloader(updateCheck.UpdateDownloader):
def _downloadSuccess(self):
self._stopped()
- # Translators: The message presented when the update has been
successfully downloaded
- # and is about to be installed.
- gui.messageBox(_("Add-on update downloaded. It will now be
installed."),
- # Translators: The title of the dialog displayed when
the update is about to be installed.
- _("Install Add-on Update"))
- # #4475: ensure that the new process shows its first window, by
providing SW_SHOWNORMAL
- shellapi.ShellExecute(None, None,
- self.destPath.decode("mbcs"),
- None, None, winUser.SW_SHOWNORMAL)
+ from gui import addonGui
+ wx.CallAfter(addonGui.AddonsDialog.handleRemoteAddonInstall,
self.destPath.decode("mbcs"))
# These structs are only complete enough to achieve what we need.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/b660537a6856/
Changeset: b660537a6856
Branch: None
User: josephsl
Date: 2017-01-31 05:49:52+00:00
Summary: Readme entry for add-on updates. fixes #20
Affected #: 1 file
diff --git a/readme.md b/readme.md
index 3019fe4..ea471b7 100755
--- a/readme.md
+++ b/readme.md
@@ -177,6 +177,7 @@ If you are using Studio on a touchscreen computer running
Windows 8 or later and
## Version 17.04-dev
* Improvements to presentation of various add-on dialogs thanks to NVDA 2016.4
features.
+* NVDA will download add-on updates in the background if you say "yes" when
asked to update the add-on. Consequently, file download notifications from web
browsers will no longer be shown.
* Added ability to press Control+Alt+up or down arrow keys to move between
tracks (specifically, track columns) vertically just as one is moving to next
or previous row in a table.
* Added a combo box in add-on settings dialog to set which column should be
announced when moving through columns vertically.
* Moved end of track , intro and microphone alarm controls from add-on
settings to the new Alarms Center.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/ed8eaf7a87d7/
Changeset: ed8eaf7a87d7
Branch: None
User: josephsl
Date: 2017-01-31 15:42:23+00:00
Summary: Oops, nonexistent variable should not block channel changes from
being saved.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index 1b9434e..95b7250 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -1350,7 +1350,7 @@ class AdvancedOptionsDialog(wx.Dialog):
parent.compLayer =
self.compatibilityLayouts[self.compatibilityList.GetSelection()][0]
parent.autoUpdateCheck = self.autoUpdateCheckbox.Value
parent.updateInterval = self.updateInterval.Value
- if len(self._updateChannels) > 1: parent.updateChannel =
self.updateChannels[self.channels.GetSelection()]
+ if len(self._updateChannels) > 1: parent.updateChannel =
self._updateChannels[self.channels.GetSelection()]
parent.profiles.SetFocus()
parent.Enable()
self.Destroy()
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/6ce76569e1fb/
Changeset: 6ce76569e1fb
Branch: None
User: josephsl
Date: 2017-02-01 02:01:02+00:00
Summary: Update check addresses are now HTTPS.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index 403c212..83601ca 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -31,7 +31,7 @@ SPLAddonCheck = 0
# Update metadata storage.
SPLAddonState = {}
# Update URL (the only way to change it is installing a different version from
a different branch).
-SPLUpdateURL = "http://addons.nvda-project.org/files/get.php?file=spl-dev";
+SPLUpdateURL = "https://addons.nvda-project.org/files/get.php?file=spl-dev";
_pendingChannelChange = False
_updateNow = False
SPLUpdateChannel = "dev"
@@ -46,7 +46,7 @@ _updatePickle = os.path.join(globalVars.appArgs.configPath,
"splupdate.pickle")
# Not all update channels are listed. The one not listed here is the default
("stable" for this branch).
channels={
- "stable":"http://addons.nvda-project.org/files/get.php?file=spl";,
+ "stable":"https://addons.nvda-project.org/files/get.php?file=spl";,
#"beta":"http://spl.nvda-kr.org/files/get.php?file=spl-beta";,
}
@@ -289,7 +289,7 @@ def _updateWindowsRootCertificates():
crypt = ctypes.windll.crypt32
# Get the server certificate.
sslCont = ssl._create_unverified_context()
- u = urllib.urlopen("https://www.nvaccess.org/nvdaUpdateCheck", ;
context=sslCont)
+ u = urllib.urlopen("https://addons.nvda-project.org", context=sslCont)
cert = u.fp._sock.getpeercert(True)
u.close()
# Convert to a form usable by Windows.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/7e6eaf4d60f4/
Changeset: 7e6eaf4d60f4
Branch: None
User: josephsl
Date: 2017-02-01 03:22:57+00:00
Summary: Update check dictionary (17.04/17.2-dev): fetch add-on update info
via a dictionary, similar to NVDA Core does it. re #21.
NVDA Core uses a dictionary to pass update info between connection routine and
update GUI. Thus emulate this in SPL add-on via a new experimental function.
Due to potential for regression, only parts of it will make it to 17.04, the
rest will be incorporated into 17.2-dev later in 2017.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index 83601ca..f8df6c5 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -80,25 +80,92 @@ def terminate():
cPickle.dump(SPLAddonState, file(_updatePickle, "wb"))
SPLAddonState = None
-def updateQualify(url):
+def checkForAddonUpdate():
+ import urllib
+ updateURL = SPLUpdateURL if SPLUpdateChannel not in channels else
channels[SPLUpdateChannel]
+ try:
+ # Look up the channel if different from the default.
+ res = urllib.urlopen(updateURL)
+ res.close()
+ except IOError as e:
+ # NVDA Core 2015.1 and later.
+ if isinstance(e.strerror, ssl.SSLError) and e.strerror.reason
== "CERTIFICATE_VERIFY_FAILED":
+ _updateWindowsRootCertificates()
+ res = urllib.urlopen(updateURL)
+ else:
+ raise
+ if res.code != 200:
+ raise RuntimeError("Checking for update failed with code %d" %
res.code)
+ # Build emulated add-on update dictionary if there is indeed a new
verison.
# The add-on version is of the form "x.y.z". The "-dev" suffix
indicates development release.
# Anything after "-dev" indicates a try or a custom build.
# LTS: Support upgrading between LTS releases.
# 7.0: Just worry about version label differences (suggested by Jamie
Teh from NV Access).
# 17.04: Version is of the form year.month.revision, and regular
expression will be employed (looks cleaner).
import re
- version = re.search("stationPlaylist-(?P<version>.*).nvda-addon",
url.url).groupdict()["version"]
- return None if version == SPLAddonVersion else version
+ version = re.search("stationPlaylist-(?P<version>.*).nvda-addon",
res.url).groupdict()["version"]
+ if version != SPLAddonVersion:
+ return {"curVersion": SPLAddonVersion, "newVersion": version,
"path": res.url}
+ return None
_progressDialog = None
+updateDictionary = True
# The update check routine.
# Auto is whether to respond with UI (manual check only), continuous takes in
auto update check variable for restarting the timer.
# ConfUpdateInterval comes from add-on config dictionary.
+def updateCheckerEx(auto=False, continuous=False, confUpdateInterval=1):
+ global _SPLUpdateT, SPLAddonCheck, _retryAfterFailure, _progressDialog,
_updateNow
+ if _updateNow: _updateNow = False
+ import time
+ from logHandler import log
+ # Regardless of whether it is an auto check, update the check time.
+ # However, this shouldnt' be done if this is a retry after a failed
attempt.
+ if not _retryAfterFailure: SPLAddonCheck = time.time()
+ updateInterval = confUpdateInterval*_updateInterval*1000
+ # Should the timer be set again?
+ if continuous and not _retryAfterFailure:
_SPLUpdateT.Start(updateInterval, True)
+ # Auto disables UI portion of this function if no updates are pending.
+ try:
+ info = checkForAddonUpdate()
+ except:
+ log.debugWarning("Error checking for update", exc_info=True)
+ _retryAfterFailure = True
+ if not auto:
+ wx.CallAfter(_progressDialog.done)
+ _progressDialog = None
+ # Translators: Error text shown when add-on update
check fails.
+ wx.CallAfter(gui.messageBox, _("Error checking for
update."), _("Studio add-on update"), wx.ICON_ERROR)
+ if continuous: _SPLUpdateT.Start(600000, True)
+ return
+ if _retryAfterFailure:
+ _retryAfterFailure = False
+ # Now is the time to update the check time if this is a retry.
+ SPLAddonCheck = time.time()
+ if info is None:
+ if auto:
+ if continuous: _SPLUpdateT.Start(updateInterval, True)
+ return # No need to interact with the user.
+ # Translators: Presented when no add-on update is available.
+ checkMessage = _("No add-on update available.")
+ else:
+ # Translators: Text shown if an add-on update is available.
+ checkMessage = _("Studio add-on {newVersion} is available.
Would you like to update?").format(newVersion = info["newVersion"])
+ updateCandidate = True
+ if not auto:
+ wx.CallAfter(_progressDialog.done)
+ _progressDialog = None
+ # Translators: Title of the add-on update check dialog.
+ if not updateCandidate: wx.CallAfter(gui.messageBox, checkMessage,
_("Studio add-on update"))
+ else: wx.CallAfter(getUpdateResponse, checkMessage, _("Studio add-on
update"), info["path"])
+
def updateChecker(auto=False, continuous=False, confUpdateInterval=1):
if _pendingChannelChange:
wx.CallAfter(gui.messageBox, _("Did you recently tell SPL
add-on to use a different update channel? If so, please restart NVDA before
checking for add-on updates."), _("Update channel changed"), wx.ICON_ERROR)
return
+ if updateDictionary:
+ updateCheckerEx(auto=auto, continuous=continuous,
confUpdateInterval=confUpdateInterval)
+ return
global _SPLUpdateT, SPLAddonCheck, _retryAfterFailure, _progressDialog,
_updateNow
if _updateNow: _updateNow = False
import time
@@ -138,8 +205,9 @@ def updateChecker(auto=False, continuous=False,
confUpdateInterval=1):
checkMessage = _("Add-on update check failed.")
else:
# Am I qualified to update?
- qualified = updateQualify(url)
- if qualified is None:
+ import re
+ version =
re.search("stationPlaylist-(?P<version>.*).nvda-addon",
res.url).groupdict()["version"]
+ if version == SPLAddonVersion:
if auto:
if continuous:
_SPLUpdateT.Start(updateInterval, True)
return
@@ -147,7 +215,7 @@ def updateChecker(auto=False, continuous=False,
confUpdateInterval=1):
checkMessage = _("No add-on update available.")
else:
# Translators: Text shown if an add-on update is
available.
- checkMessage = _("Studio add-on {newVersion} is
available. Would you like to update?").format(newVersion = qualified)
+ checkMessage = _("Studio add-on {newVersion} is
available. Would you like to update?").format(newVersion = version)
updateCandidate = True
if not auto:
wx.CallAfter(_progressDialog.done)
@@ -166,51 +234,6 @@ def getUpdateResponse(message, caption, updateURL):
#: The download block size in bytes.
DOWNLOAD_BLOCK_SIZE = 8192 # 8 kb
-def checkForUpdate(auto=False):
- """Check for an updated version of NVDA.
- This will block, so it generally shouldn't be called from the main
thread.
- @param auto: Whether this is an automatic check for updates.
- @type auto: bool
- @return: Information about the update or C{None} if there is no update.
- @rtype: dict
- @raise RuntimeError: If there is an error checking for an update.
- """
- params = {
- "autoCheck": auto,
- "version": versionInfo.version,
- "versionType": versionInfo.updateVersionType,
- "osVersion": winVersion.winVersionText,
- "x64": os.environ.get("PROCESSOR_ARCHITEW6432") == "AMD64",
- "language": languageHandler.getLanguage(),
- "installed": config.isInstalledCopy(),
- }
- url = "%s?%s" % (CHECK_URL, urllib.urlencode(params))
- try:
- res = urllib.urlopen(url)
- except IOError as e:
- if isinstance(e.strerror, ssl.SSLError) and e.strerror.reason
== "CERTIFICATE_VERIFY_FAILED":
- # #4803: Windows fetches trusted root certificates on
demand.
- # Python doesn't trigger this fetch
(PythonIssue:20916), so try it ourselves
- _updateWindowsRootCertificates()
- # and then retry the update check.
- res = urllib.urlopen(url)
- else:
- raise
- if res.code != 200:
- raise RuntimeError("Checking for update failed with code %d" %
res.code)
- info = {}
- for line in res:
- line = line.rstrip()
- try:
- key, val = line.split(": ", 1)
- except ValueError:
- raise RuntimeError("Error in update check output")
- info[key] = val
- if not info:
- return None
- return info
-
-
class SPLUpdateDownloader(updateCheck.UpdateDownloader):
"""Overrides NVDA Core's downloader.)
No hash checking for now, and URL's and temp file paths are different.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/7bce7303b8af/
Changeset: 7bce7303b8af
Branch: None
User: josephsl
Date: 2017-02-01 18:03:43+00:00
Summary: Playlist snapshots: do not record artist and genre information for
an hur marker, add item count to snapshots output.
reported by a broadcaster: hour marker should not be listed as a track, leads
to confusion. Therefore artist and genre information will not be recorded for
hour markers, and to minimize confusion, item count will be added (item count
includes hour markers). The track count will now be used to count actual track
count.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 2f2a239..5c197bd 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1451,8 +1451,10 @@ class AppModule(appModuleHandler.AppModule):
segue = obj._getColumnContent(duration)
trackTitle = obj._getColumnContent(title)
categories.append(obj._getColumnContent(category))
- if categories[-1] != "Hour Marker":
artists.append(obj._getColumnContent(artist))
- genres.append(obj._getColumnContent(genre))
+ # Don't record artist and genre information for an hour
marker (reported by a broadcaster).
+ if categories[-1] != "Hour Marker":
+ artists.append(obj._getColumnContent(artist))
+ genres.append(obj._getColumnContent(genre))
# Shortest and longest tracks.
if min is None: min = segue
if segue and segue < min:
@@ -1466,7 +1468,8 @@ class AppModule(appModuleHandler.AppModule):
totalDuration += (int(hms[-2])*60) +
int(hms[-1])
if len(hms) == 3: totalDuration +=
int(hms[0])*3600
obj = obj.next
- if end is None: snapshot["PlaylistTrackCount"] = studioAPI(0,
124, ret=True)
+ if end is None: snapshot["PlaylistItemCount"] = studioAPI(0,
124, ret=True)
+ snapshot["PlaylistTrackCount"] = len(artists)
snapshot["PlaylistDurationTotal"] =
self._ms2time(totalDuration, ms=False)
if "DurationMinMax" in snapshotFlags:
snapshot["PlaylistDurationMin"] = "%s (%s)"%(minTitle,
min)
@@ -1483,7 +1486,8 @@ class AppModule(appModuleHandler.AppModule):
# Output formatter for playlist snapshots.
# Pressed once will speak and/or braille it, pressing twice or more will
output this info to an HTML file.
def playlistSnapshotOutput(self, snapshot, scriptCount):
- statusInfo = ["Tracks: %s"%snapshot["PlaylistTrackCount"]]
+ statusInfo = ["Items: %s"%snapshot["PlaylistItemCount"]]
+ statusInfo.append("Tracks: %s"%snapshot["PlaylistTrackCount"])
statusInfo.append("Duration:
%s"%snapshot["PlaylistDurationTotal"])
if "PlaylistDurationMin" in snapshot:
statusInfo.append("Shortest:
%s"%snapshot["PlaylistDurationMin"])
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/a29500eb384e/
Changeset: a29500eb384e
Branch: None
User: josephsl
Date: 2017-02-01 23:17:12+00:00
Summary: Playlist snapshots: shortest track is recognized correctly if the
first track of a plyalist is the shortest track of them all. re #22.
Reported by a broadcaster and noted as a logic error: just because shortest
track segue is None doesn't mean it should be assigned to the first segue, thus
causing inequality to fail (the inequality checks for current min, which could
very well be the very first segue). Also this takes care of a problem where all
tracks in a playlistlist have the same length.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 5c197bd..14ba829 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1456,8 +1456,8 @@ class AppModule(appModuleHandler.AppModule):
artists.append(obj._getColumnContent(artist))
genres.append(obj._getColumnContent(genre))
# Shortest and longest tracks.
- if min is None: min = segue
- if segue and segue < min:
+ # #22: assign min to the first segue in order to not
forget title of the shortest track.
+ if segue and (min is None or segue < min):
min = segue
minTitle = trackTitle
if segue and segue > max:
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/d15e85dbfca8/
Changeset: d15e85dbfca8
Branch: None
User: josephsl
Date: 2017-02-01 23:20:16+00:00
Summary: Merge branch 'master' into updateInfoDictionary
Affected #: 1 file
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 2f2a239..14ba829 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1451,11 +1451,13 @@ class AppModule(appModuleHandler.AppModule):
segue = obj._getColumnContent(duration)
trackTitle = obj._getColumnContent(title)
categories.append(obj._getColumnContent(category))
- if categories[-1] != "Hour Marker":
artists.append(obj._getColumnContent(artist))
- genres.append(obj._getColumnContent(genre))
+ # Don't record artist and genre information for an hour
marker (reported by a broadcaster).
+ if categories[-1] != "Hour Marker":
+ artists.append(obj._getColumnContent(artist))
+ genres.append(obj._getColumnContent(genre))
# Shortest and longest tracks.
- if min is None: min = segue
- if segue and segue < min:
+ # #22: assign min to the first segue in order to not
forget title of the shortest track.
+ if segue and (min is None or segue < min):
min = segue
minTitle = trackTitle
if segue and segue > max:
@@ -1466,7 +1468,8 @@ class AppModule(appModuleHandler.AppModule):
totalDuration += (int(hms[-2])*60) +
int(hms[-1])
if len(hms) == 3: totalDuration +=
int(hms[0])*3600
obj = obj.next
- if end is None: snapshot["PlaylistTrackCount"] = studioAPI(0,
124, ret=True)
+ if end is None: snapshot["PlaylistItemCount"] = studioAPI(0,
124, ret=True)
+ snapshot["PlaylistTrackCount"] = len(artists)
snapshot["PlaylistDurationTotal"] =
self._ms2time(totalDuration, ms=False)
if "DurationMinMax" in snapshotFlags:
snapshot["PlaylistDurationMin"] = "%s (%s)"%(minTitle,
min)
@@ -1483,7 +1486,8 @@ class AppModule(appModuleHandler.AppModule):
# Output formatter for playlist snapshots.
# Pressed once will speak and/or braille it, pressing twice or more will
output this info to an HTML file.
def playlistSnapshotOutput(self, snapshot, scriptCount):
- statusInfo = ["Tracks: %s"%snapshot["PlaylistTrackCount"]]
+ statusInfo = ["Items: %s"%snapshot["PlaylistItemCount"]]
+ statusInfo.append("Tracks: %s"%snapshot["PlaylistTrackCount"])
statusInfo.append("Duration:
%s"%snapshot["PlaylistDurationTotal"])
if "PlaylistDurationMin" in snapshot:
statusInfo.append("Shortest:
%s"%snapshot["PlaylistDurationMin"])
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/3101d8e1c489/
Changeset: 3101d8e1c489
Branch: None
User: josephsl
Date: 2017-02-01 23:27:14+00:00
Summary: Merge branch 'stable' into 16.10.x
Affected #: 1 file
diff --git a/buildVars.py b/buildVars.py
index f2c81b4..8256280 100755
--- a/buildVars.py
+++ b/buildVars.py
@@ -20,7 +20,7 @@ addon_info = {
"addon_description" : _("""Enhances support for StationPlaylist Studio.
In addition, adds global commands for the studio from everywhere."""),
# version
- "addon_version" : "17.01",
+ "addon_version" : "17.02",
# Author(s)
"addon_author" : u"Geoff Shang, Joseph Lee and other contributors",
# URL for the add-on documentation support
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/9418e531ae16/
Changeset: 9418e531ae16
Branch: None
User: josephsl
Date: 2017-02-01 23:55:17+00:00
Summary: Add-on update check: use a background thread to check for updates
at startup in order to avoid a freeze. re #23.
When a user changes channels or when the update interval elapses at startup,
NVDA will be told to check for add-on updates. If this happens, NVDA will
appear to freeze because urllib blocks. Thus use a background thread to check
for updates.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 8a10737..e9a7fe9 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -822,9 +822,13 @@ def autoUpdateCheck():
# A bit simpler than NVDA Core's auto update checker.
def updateInit():
# LTS: Launch updater if channel change is detected.
+ # Use a background thread for this as urllib blocks.
+ import threading
if splupdate._updateNow:
- splupdate.updateCheck(auto=True) # No repeat here.
+ t = threading.Thread(target=splupdate.updateCheck,
kwargs={"auto": True}) # No repeat here.
+ t.daemon = True
splupdate._SPLUpdateT = wx.PyTimer(autoUpdateCheck)
+ t.start()
splupdate._updateNow = False
return
currentTime = time.time()
@@ -834,7 +838,9 @@ def updateInit():
elif splupdate.SPLAddonCheck < nextCheck < currentTime:
interval = SPLConfig["Update"]["UpdateInterval"]* 86400
# Call the update check now.
- splupdate.updateCheck(auto=True) # No repeat here.
+ t = threading.Thread(target=splupdate.updateCheck,
kwargs={"auto": True}) # No repeat here.
+ t.daemon = True
+ t.start()
splupdate._SPLUpdateT = wx.PyTimer(autoUpdateCheck)
splupdate._SPLUpdateT.Start(interval * 1000, True)
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/407df812941b/
Changeset: 407df812941b
Branch: None
User: josephsl
Date: 2017-02-01 23:58:04+00:00
Summary: Merged 16.10.x
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 77f25ca..a3d9b38 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -838,9 +838,13 @@ def autoUpdateCheck():
# A bit simpler than NVDA Core's auto update checker.
def updateInit():
# LTS: Launch updater if channel change is detected.
+ # Use a background thread for this as urllib blocks.
+ import threading
if splupdate._updateNow:
- splupdate.updateChecker(auto=True) # No repeat here.
+ t = threading.Thread(target=splupdate.updateChecker,
kwargs={"auto": True}) # No repeat here.
+ t.daemon = True
splupdate._SPLUpdateT = wx.PyTimer(autoUpdateCheck)
+ t.start()
splupdate._updateNow = False
return
currentTime = time.time()
@@ -850,7 +854,9 @@ def updateInit():
elif splupdate.SPLAddonCheck < nextCheck < currentTime:
interval = SPLConfig["Update"]["UpdateInterval"]* 86400
# Call the update check now.
- splupdate.updateChecker(auto=True) # No repeat here.
+ t = threading.Thread(target=splupdate.updateChecker,
kwargs={"auto": True}) # No repeat here.
+ t.daemon = True
+ t.start()
splupdate._SPLUpdateT = wx.PyTimer(autoUpdateCheck)
splupdate._SPLUpdateT.Start(interval * 1000, True)
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/e4bb7df4a67e/
Changeset: e4bb7df4a67e
Branch: None
User: josephsl
Date: 2017-02-02 11:20:55+00:00
Summary: Debugging framework (17.04/17.2-dev): initial foundation for
debugging framework with a debug logging printer. re #24.
In NVDA 2017.1, it is possible to allow NVDA to provide debug logging for a
single session. Thus take advantage of this by introducing a basic debugging
framework that'll let the add-on output important debugging information (such
as command execution steps) to the log for analysis later.
An initial solution will be in place in 17.04, with functionality coming in via
subsequent stable releases, full picture to be ready by 17.2-dev (summer/fall).
Affected #: 2 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 14ba829..3f0a7d4 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -36,6 +36,7 @@ import splmisc
import splupdate
import addonHandler
addonHandler.initTranslation()
+from spldebugging import debugOutput
# Make sure the broadcaster is running a compatible version.
SPLMinVersion = "5.10"
@@ -78,9 +79,14 @@ def micAlarmManager(micAlarmWav, micAlarmMessage):
# Use SPL Studio API to obtain needed values.
# A thin wrapper around user32.SendMessage and calling a callback if defined.
# Offset is used in some time commands.
+# If debugging framework is on, print arg, command and other values.
def studioAPI(arg, command, func=None, ret=False, offset=None):
- if _SPLWin is None: return
+ if _SPLWin is None:
+ debugOutput("SPL: Studio handle not found")
+ return
+ debugOutput("SPL: Studio API wParem is %s, lParem is %s"%(arg, command))
val = sendMessage(_SPLWin, 1024, arg, command)
+ debugOutput("SPL: Studio API result is %s"%val)
if ret:
return val
if func:
@@ -90,6 +96,7 @@ def studioAPI(arg, command, func=None, ret=False,
offset=None):
# This is to make sure custom commands for SPL Assistant comamnds and other
app module gestures display appropriate error messages.
def studioIsRunning():
if _SPLWin is None:
+ debugOutput("SPL: Studio handle not found")
# Translators: A message informing users that Studio is not
running so certain commands will not work.
ui.message(_("Studio main window not found"))
return False
@@ -571,6 +578,7 @@ class AppModule(appModuleHandler.AppModule):
ui.message(_("Using SPL Studio version
{SPLVersion}").format(SPLVersion = self.SPLCurVersion))
except IOError, AttributeError:
pass
+ debugOutput("SPL: loading add-on settings")
splconfig.initConfig()
# Announce status changes while using other programs.
# This requires NVDA core support and will be available in 6.0
and later (cannot be ported to earlier versions).
@@ -592,6 +600,7 @@ class AppModule(appModuleHandler.AppModule):
# Check for add-on update if told to do so.
# LTS: Only do this if channel hasn't changed.
if splconfig.SPLConfig["Update"]["AutoUpdateCheck"] or
splupdate._updateNow:
+ debugOutput("SPL: checking for add-on updates from %s
channel"%splupdate.updateChannel)
# 7.0: Have a timer call the update function indirectly.
import queueHandler
queueHandler.queueFunction(queueHandler.eventQueue,
splconfig.updateInit)
@@ -853,16 +862,17 @@ class AppModule(appModuleHandler.AppModule):
except KeyError:
pass
-
# Save configuration when terminating.
def terminate(self):
super(AppModule, self).terminate()
+ debugOutput("SPL: terminating app module")
# 6.3: Memory leak results if encoder flag sets and other
encoder support maps aren't cleaned up.
# This also could have allowed a hacker to modify the flags set
(highly unlikely) so NvDA could get confused next time Studio loads.
import sys
if "globalPlugins.splUtils.encoders" in sys.modules:
import globalPlugins.splUtils.encoders
globalPlugins.splUtils.encoders.cleanup()
+ debugOutput("SPL: saving add-on settings")
splconfig.saveConfig()
# reset column number for column navigation commands.
if self._focusedTrack:
self._focusedTrack.__class__._curColumnNumber = None
@@ -883,7 +893,6 @@ class AppModule(appModuleHandler.AppModule):
global _SPLWin
if _SPLWin: _SPLWin = None
-
# Script sections (for ease of maintenance):
# Time-related: elapsed time, end of track alarm, etc.
# Misc scripts: track finder and others.
diff --git a/addon/appModules/splstudio/spldebugging.py
b/addon/appModules/splstudio/spldebugging.py
new file mode 100755
index 0000000..b9f3498
--- /dev/null
+++ b/addon/appModules/splstudio/spldebugging.py
@@ -0,0 +1,17 @@
+# SPL Studio add-on debugging framework
+# An app module and global plugin package for NVDA
+# Copyright 2017 Joseph Lee and others, released under GPL.
+# Provides debug output and other diagnostics probes.
+
+from logHandler import log
+
+try:
+ import globalVars
+ SPLDebuggingFramework = globalVars.appArgs.debugLogging
+except AttributeError:
+ SPLDebuggingFramework = None
+
+def debugOutput(message):
+ if SPLDebuggingFramework:
+ log.debug(message)
+
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/a1d1bfa93af1/
Changeset: a1d1bfa93af1
Branch: None
User: josephsl
Date: 2017-02-02 18:51:04+00:00
Summary: Merge branch 'master' into updateInfoDictionary
Affected #: 3 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 14ba829..3f0a7d4 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -36,6 +36,7 @@ import splmisc
import splupdate
import addonHandler
addonHandler.initTranslation()
+from spldebugging import debugOutput
# Make sure the broadcaster is running a compatible version.
SPLMinVersion = "5.10"
@@ -78,9 +79,14 @@ def micAlarmManager(micAlarmWav, micAlarmMessage):
# Use SPL Studio API to obtain needed values.
# A thin wrapper around user32.SendMessage and calling a callback if defined.
# Offset is used in some time commands.
+# If debugging framework is on, print arg, command and other values.
def studioAPI(arg, command, func=None, ret=False, offset=None):
- if _SPLWin is None: return
+ if _SPLWin is None:
+ debugOutput("SPL: Studio handle not found")
+ return
+ debugOutput("SPL: Studio API wParem is %s, lParem is %s"%(arg, command))
val = sendMessage(_SPLWin, 1024, arg, command)
+ debugOutput("SPL: Studio API result is %s"%val)
if ret:
return val
if func:
@@ -90,6 +96,7 @@ def studioAPI(arg, command, func=None, ret=False,
offset=None):
# This is to make sure custom commands for SPL Assistant comamnds and other
app module gestures display appropriate error messages.
def studioIsRunning():
if _SPLWin is None:
+ debugOutput("SPL: Studio handle not found")
# Translators: A message informing users that Studio is not
running so certain commands will not work.
ui.message(_("Studio main window not found"))
return False
@@ -571,6 +578,7 @@ class AppModule(appModuleHandler.AppModule):
ui.message(_("Using SPL Studio version
{SPLVersion}").format(SPLVersion = self.SPLCurVersion))
except IOError, AttributeError:
pass
+ debugOutput("SPL: loading add-on settings")
splconfig.initConfig()
# Announce status changes while using other programs.
# This requires NVDA core support and will be available in 6.0
and later (cannot be ported to earlier versions).
@@ -592,6 +600,7 @@ class AppModule(appModuleHandler.AppModule):
# Check for add-on update if told to do so.
# LTS: Only do this if channel hasn't changed.
if splconfig.SPLConfig["Update"]["AutoUpdateCheck"] or
splupdate._updateNow:
+ debugOutput("SPL: checking for add-on updates from %s
channel"%splupdate.updateChannel)
# 7.0: Have a timer call the update function indirectly.
import queueHandler
queueHandler.queueFunction(queueHandler.eventQueue,
splconfig.updateInit)
@@ -853,16 +862,17 @@ class AppModule(appModuleHandler.AppModule):
except KeyError:
pass
-
# Save configuration when terminating.
def terminate(self):
super(AppModule, self).terminate()
+ debugOutput("SPL: terminating app module")
# 6.3: Memory leak results if encoder flag sets and other
encoder support maps aren't cleaned up.
# This also could have allowed a hacker to modify the flags set
(highly unlikely) so NvDA could get confused next time Studio loads.
import sys
if "globalPlugins.splUtils.encoders" in sys.modules:
import globalPlugins.splUtils.encoders
globalPlugins.splUtils.encoders.cleanup()
+ debugOutput("SPL: saving add-on settings")
splconfig.saveConfig()
# reset column number for column navigation commands.
if self._focusedTrack:
self._focusedTrack.__class__._curColumnNumber = None
@@ -883,7 +893,6 @@ class AppModule(appModuleHandler.AppModule):
global _SPLWin
if _SPLWin: _SPLWin = None
-
# Script sections (for ease of maintenance):
# Time-related: elapsed time, end of track alarm, etc.
# Misc scripts: track finder and others.
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 77f25ca..a3d9b38 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -838,9 +838,13 @@ def autoUpdateCheck():
# A bit simpler than NVDA Core's auto update checker.
def updateInit():
# LTS: Launch updater if channel change is detected.
+ # Use a background thread for this as urllib blocks.
+ import threading
if splupdate._updateNow:
- splupdate.updateChecker(auto=True) # No repeat here.
+ t = threading.Thread(target=splupdate.updateChecker,
kwargs={"auto": True}) # No repeat here.
+ t.daemon = True
splupdate._SPLUpdateT = wx.PyTimer(autoUpdateCheck)
+ t.start()
splupdate._updateNow = False
return
currentTime = time.time()
@@ -850,7 +854,9 @@ def updateInit():
elif splupdate.SPLAddonCheck < nextCheck < currentTime:
interval = SPLConfig["Update"]["UpdateInterval"]* 86400
# Call the update check now.
- splupdate.updateChecker(auto=True) # No repeat here.
+ t = threading.Thread(target=splupdate.updateChecker,
kwargs={"auto": True}) # No repeat here.
+ t.daemon = True
+ t.start()
splupdate._SPLUpdateT = wx.PyTimer(autoUpdateCheck)
splupdate._SPLUpdateT.Start(interval * 1000, True)
diff --git a/addon/appModules/splstudio/spldebugging.py
b/addon/appModules/splstudio/spldebugging.py
new file mode 100755
index 0000000..b9f3498
--- /dev/null
+++ b/addon/appModules/splstudio/spldebugging.py
@@ -0,0 +1,17 @@
+# SPL Studio add-on debugging framework
+# An app module and global plugin package for NVDA
+# Copyright 2017 Joseph Lee and others, released under GPL.
+# Provides debug output and other diagnostics probes.
+
+from logHandler import log
+
+try:
+ import globalVars
+ SPLDebuggingFramework = globalVars.appArgs.debugLogging
+except AttributeError:
+ SPLDebuggingFramework = None
+
+def debugOutput(message):
+ if SPLDebuggingFramework:
+ log.debug(message)
+
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/047473c1acf8/
Changeset: 047473c1acf8
Branch: None
User: josephsl
Date: 2017-02-02 21:19:35+00:00
Summary: Cart Explorer 3 (17.04): refresh carts via the init function in
order to reduce loop traversals.
Optimization: In add-on 17.01, when refreshing cart banks, cart files were read
up to two times (two for loops). The best case was only one loop (no refresh
needed). To reduce loop traversal, combine aspects of cart explorer init and
refresh functions into cart explorer init that'll also be able to refresh carts
if the timestamps for carts exists (error conditions to be taken care of
later). This optimization results in code reuse and allows just one function to
provide debug output, and in some cases only one loop will be run (check the
timestamp, and if they are equal for all, don't update carts by processing cart
files), as well as saving the add-on from processing all carts unnecessarily.
This is destined for add-on 17.04.
Affected #: 2 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 3f0a7d4..e43a279 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -789,9 +789,7 @@ class AppModule(appModuleHandler.AppModule):
# 17.01: The best way to detect Cart Edit off is
consulting file modification time.
# Automatically reload cart information if this is the
case.
if status in ("Cart Edit Off", "Cart Insert On"):
- studioTitle = api.getForegroundObject().name
- if
splmisc.shouldCartExplorerRefresh(studioTitle):
- self.carts =
splmisc.cartExplorerInit(studioTitle)
+ self.carts =
splmisc.cartExplorerInit(api.getForegroundObject().name, refresh=True)
# Translators: Presented when cart modes are toggled
while cart explorer is on.
ui.message(_("Cart explorer is active"))
return
@@ -1230,6 +1228,7 @@ class AppModule(appModuleHandler.AppModule):
self.cartExplorer = False
self.cartsBuilder(build=False)
self.carts.clear()
+ splmisc._cartEditTimestamps = None
# Translators: Presented when cart explorer is off.
ui.message(_("Exiting cart explorer"))
# Translators: Input help mode message for a command in Station
Playlist Studio.
diff --git a/addon/appModules/splstudio/splmisc.py
b/addon/appModules/splstudio/splmisc.py
index b4123b4..9b4cadd 100755
--- a/addon/appModules/splstudio/splmisc.py
+++ b/addon/appModules/splstudio/splmisc.py
@@ -14,6 +14,7 @@ import gui
import wx
import ui
from winUser import user32, sendMessage
+from spldebugging import debugOutput
# Locate column content.
# Given an object and the column number, locate text in the given column.
@@ -263,11 +264,13 @@ def _populateCarts(carts, cartlst, modifier,
standardEdition=False):
carts[cart] = cartName
# Cart file timestamps.
-_cartEditTimestamps = [0, 0, 0, 0]
+_cartEditTimestamps = None
# Initialize Cart Explorer i.e. fetch carts.
# Cart files list is for future use when custom cart names are used.
-def cartExplorerInit(StudioTitle, cartFiles=None):
+# if told to refresh, timestamps will be checked and updated banks will be
reassigned.
+def cartExplorerInit(StudioTitle, cartFiles=None, refresh=False):
global _cartEditTimestamps
+ debugOutput("SPL: refreshing Cart Explorer" if refresh else "SPL:
preparing cart Explorer")
# Use cart files in SPL's data folder to build carts dictionary.
# use a combination of SPL user name and static cart location to locate
cart bank files.
# Once the cart banks are located, use the routines in the populate
method above to assign carts.
@@ -284,7 +287,10 @@ def cartExplorerInit(StudioTitle, cartFiles=None):
if userNameIndex >= 0:
cartFiles = [StudioTitle[userNameIndex+2:]+" "+cartFile
for cartFile in cartFiles]
faultyCarts = False
+ if not refresh:
+ _cartEditTimestamps = []
for f in cartFiles:
+ # Only do this if told to build cart banks from scratch, as
refresh flag is set if cart explorer is active in the first place.
try:
mod = f.split()[-2] # Checking for modifier string such
as ctrl.
# Todo: Check just in case some SPL flavors doesn't
ship with a particular cart file.
@@ -292,36 +298,24 @@ def cartExplorerInit(StudioTitle, cartFiles=None):
faultyCarts = True # In a rare event that the
broadcaster has saved the cart bank with the name like "carts.cart".
continue
cartFile = os.path.join(cartsDataPath,f)
- if not os.path.isfile(cartFile): # Cart explorer will fail if
whitespaces are in the beginning or at the end of a user name.
+ # Cart explorer can safely assume that the cart bank exists if
refresh flag is set.
+ if not refresh and not os.path.isfile(cartFile): # Cart
explorer will fail if whitespaces are in the beginning or at the end of a user
name.
faultyCarts = True
continue
+ debugOutput("SPL: examining carts from file %s"%cartFile)
+ cartTimestamp = os.path.getmtime(cartFile)
+ if refresh and _cartEditTimestamps[cartFiles.index(f)] ==
cartTimestamp:
+ debugOutput("SPL: no changes to cart bank, skipping")
+ continue
+ _cartEditTimestamps.append(cartTimestamp)
with open(cartFile) as cartInfo:
cl = [row for row in reader(cartInfo)]
- # 17.01: Look up file modification date to signal the
app module that Cart Explorer reentry should occur.
- _cartEditTimestamps[cartFiles.index(f)] =
os.path.getmtime(cartFile)
_populateCarts(carts, cl[1], mod,
standardEdition=carts["standardLicense"]) # See the comment for _populate
method above.
+ debugOutput("SPL: carts processed so far: %s"%(len(carts)-1))
carts["faultyCarts"] = faultyCarts
+ debugOutput("SPL: total carts processed: %s"%(len(carts)-2))
return carts
-# See if cart files were modified.
-# This is needed in order to announce Cart Explorer reentry command.
-def shouldCartExplorerRefresh(StudioTitle):
- global _cartEditTimestamps
- cartsDataPath =
os.path.join(os.environ["PROGRAMFILES"],"StationPlaylist","Data") # Provided
that Studio was installed using default path.
- userNameIndex = StudioTitle.find("-")
- # Until NVDA core moves to Python 3, assume that file names aren't
unicode.
- cartFiles = [u"main carts.cart", u"shift carts.cart", u"ctrl
carts.cart", u"alt carts.cart"]
- if userNameIndex >= 0:
- cartFiles = [StudioTitle[userNameIndex+2:]+" "+cartFile for
cartFile in cartFiles]
- for f in cartFiles:
- # No need to check for faulty carts here, as Cart Explorer
activation checked it already.
- timestamp = os.path.getmtime(os.path.join(cartsDataPath,f))
- # 17.01: Look up file modification date to signal the app
module that Cart Explorer reentry should occur.
- # Optimization: Short-circuit if even one cart file has been
modified.
- if _cartEditTimestamps[cartFiles.index(f)] != timestamp:
- return True
- return False
-
# Countdown timer.
# This is utilized by many services, chiefly profile triggers routine.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/0d1ed6879047/
Changeset: 0d1ed6879047
Branch: None
User: josephsl
Date: 2017-02-03 01:22:11+00:00
Summary: Cart Explorer 3 (17.04): cart assignment optimization, fixed an
issue where unmodified cart banks could be lost if toggling cart edit mode
while cart explorer is active.
Consider the following scenario: a user starts Studio, enters Cart Explorer,
turns on cart edit, adds a cart assignment to a brand new bank, then turns off
cart edit. If this happens, unmodified cart banks will be lost, caused by the
fact that previous optimization did not take current carts state into account.
To mitigate this, a new function (cartExplorerRefresh) is now used to change
modified assignments (additions, changes, deletions). This also makes the app
module routine for Cart Explorer refresh (do extra function) reader friendly.
The populate carts function has been modified to treat the incoming cart as the
active Cart Explorer carts dictionary when refresh flag is active. This allows
entry type to be saved and consulted when checking for cart edit changes.
Affected #: 2 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index e43a279..6d1c201 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -789,7 +789,7 @@ class AppModule(appModuleHandler.AppModule):
# 17.01: The best way to detect Cart Edit off is
consulting file modification time.
# Automatically reload cart information if this is the
case.
if status in ("Cart Edit Off", "Cart Insert On"):
- self.carts =
splmisc.cartExplorerInit(api.getForegroundObject().name, refresh=True)
+ self.carts =
splmisc.cartExplorerRefresh(api.getForegroundObject().name, self.carts)
# Translators: Presented when cart modes are toggled
while cart explorer is on.
ui.message(_("Cart explorer is active"))
return
diff --git a/addon/appModules/splstudio/splmisc.py
b/addon/appModules/splstudio/splmisc.py
index 9b4cadd..119fe4a 100755
--- a/addon/appModules/splstudio/splmisc.py
+++ b/addon/appModules/splstudio/splmisc.py
@@ -242,13 +242,15 @@ class SPLTimeRangeDialog(wx.Dialog):
# Cart Explorer helper.
-def _populateCarts(carts, cartlst, modifier, standardEdition=False):
+def _populateCarts(carts, cartlst, modifier, standardEdition=False,
refresh=False):
# The real cart string parser, a helper for cart explorer for building
cart entries.
# 5.2: Discard number row if SPL Standard is in use.
if standardEdition: cartlst = cartlst[:12]
for entry in cartlst:
# An unassigned cart is stored with three consecutive commas,
so skip it.
- if ",,," in entry: continue
+ # 17.04: If refresh is on, the cart we're dealing with is the
actual carts dictionary that was built previously.
+ noEntry = ",,," in entry
+ if noEntry and not refresh: continue
# Pos between 1 and 12 = function carts, 13 through 24 = number
row carts, modifiers are checked.
pos = cartlst.index(entry)+1
# If a cart name has commas or other characters, SPL surrounds
the cart name with quotes (""), so parse it as well.
@@ -259,23 +261,27 @@ def _populateCarts(carts, cartlst, modifier,
standardEdition=False):
elif pos == 22: identifier = "0"
elif pos == 23: identifier = "-"
else: identifier = "="
- if modifier == "main": cart = identifier
- else: cart = "%s+%s"%(modifier, identifier)
- carts[cart] = cartName
+ cart = identifier if not modifier else "+".join([modifier,
identifier])
+ if noEntry and refresh:
+ if cart in carts: del carts[cart]
+ else:
+ carts[cart] = cartName
# Cart file timestamps.
_cartEditTimestamps = None
# Initialize Cart Explorer i.e. fetch carts.
# Cart files list is for future use when custom cart names are used.
# if told to refresh, timestamps will be checked and updated banks will be
reassigned.
-def cartExplorerInit(StudioTitle, cartFiles=None, refresh=False):
+# Carts dictionary is used if and only if refresh is on, as it'll modify live
cats.
+def cartExplorerInit(StudioTitle, cartFiles=None, refresh=False, carts=None):
global _cartEditTimestamps
debugOutput("SPL: refreshing Cart Explorer" if refresh else "SPL:
preparing cart Explorer")
# Use cart files in SPL's data folder to build carts dictionary.
# use a combination of SPL user name and static cart location to locate
cart bank files.
# Once the cart banks are located, use the routines in the populate
method above to assign carts.
# Since sstandard edition does not support number row carts, skip them
if told to do so.
- carts = {"standardLicense":StudioTitle.startswith("StationPlaylist
Studio Standard")}
+ if carts is None: carts =
{"standardLicense":StudioTitle.startswith("StationPlaylist Studio Standard")}
+ if refresh: carts["modifiedBanks"] = []
# Obtain the "real" path for SPL via environment variables and open the
cart data folder.
cartsDataPath =
os.path.join(os.environ["PROGRAMFILES"],"StationPlaylist","Data") # Provided
that Studio was installed using default path.
if cartFiles is None:
@@ -310,12 +316,19 @@ def cartExplorerInit(StudioTitle, cartFiles=None,
refresh=False):
_cartEditTimestamps.append(cartTimestamp)
with open(cartFile) as cartInfo:
cl = [row for row in reader(cartInfo)]
- _populateCarts(carts, cl[1], mod,
standardEdition=carts["standardLicense"]) # See the comment for _populate
method above.
- debugOutput("SPL: carts processed so far: %s"%(len(carts)-1))
+ # 17.04 (optimization): let empty string represent main cart
bank to avoid this being partially consulted up to 24 times.
+ # The below method will just check for string length, which is
faster than looking for specific substring.
+ _populateCarts(carts, cl[1], mod if mod != "main" else "",
standardEdition=carts["standardLicense"], refresh=refresh) # See the comment
for _populate method above.
+ if not refresh:
+ debugOutput("SPL: carts processed so far:
%s"%(len(carts)-1))
carts["faultyCarts"] = faultyCarts
debugOutput("SPL: total carts processed: %s"%(len(carts)-2))
return carts
+# Refresh carts upon request.
+# calls cart explorer init with special (internal) flags.
+def cartExplorerRefresh(studioTitle, currentCarts):
+ return cartExplorerInit(studioTitle, refresh=True, carts=currentCarts)
# Countdown timer.
# This is utilized by many services, chiefly profile triggers routine.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/f9962fe49034/
Changeset: f9962fe49034
Branch: None
User: josephsl
Date: 2017-02-07 02:09:27+00:00
Summary: Merge branch 'master' into updateInfoDictionary
Affected #: 2 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 3f0a7d4..6d1c201 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -789,9 +789,7 @@ class AppModule(appModuleHandler.AppModule):
# 17.01: The best way to detect Cart Edit off is
consulting file modification time.
# Automatically reload cart information if this is the
case.
if status in ("Cart Edit Off", "Cart Insert On"):
- studioTitle = api.getForegroundObject().name
- if
splmisc.shouldCartExplorerRefresh(studioTitle):
- self.carts =
splmisc.cartExplorerInit(studioTitle)
+ self.carts =
splmisc.cartExplorerRefresh(api.getForegroundObject().name, self.carts)
# Translators: Presented when cart modes are toggled
while cart explorer is on.
ui.message(_("Cart explorer is active"))
return
@@ -1230,6 +1228,7 @@ class AppModule(appModuleHandler.AppModule):
self.cartExplorer = False
self.cartsBuilder(build=False)
self.carts.clear()
+ splmisc._cartEditTimestamps = None
# Translators: Presented when cart explorer is off.
ui.message(_("Exiting cart explorer"))
# Translators: Input help mode message for a command in Station
Playlist Studio.
diff --git a/addon/appModules/splstudio/splmisc.py
b/addon/appModules/splstudio/splmisc.py
index b4123b4..119fe4a 100755
--- a/addon/appModules/splstudio/splmisc.py
+++ b/addon/appModules/splstudio/splmisc.py
@@ -14,6 +14,7 @@ import gui
import wx
import ui
from winUser import user32, sendMessage
+from spldebugging import debugOutput
# Locate column content.
# Given an object and the column number, locate text in the given column.
@@ -241,13 +242,15 @@ class SPLTimeRangeDialog(wx.Dialog):
# Cart Explorer helper.
-def _populateCarts(carts, cartlst, modifier, standardEdition=False):
+def _populateCarts(carts, cartlst, modifier, standardEdition=False,
refresh=False):
# The real cart string parser, a helper for cart explorer for building
cart entries.
# 5.2: Discard number row if SPL Standard is in use.
if standardEdition: cartlst = cartlst[:12]
for entry in cartlst:
# An unassigned cart is stored with three consecutive commas,
so skip it.
- if ",,," in entry: continue
+ # 17.04: If refresh is on, the cart we're dealing with is the
actual carts dictionary that was built previously.
+ noEntry = ",,," in entry
+ if noEntry and not refresh: continue
# Pos between 1 and 12 = function carts, 13 through 24 = number
row carts, modifiers are checked.
pos = cartlst.index(entry)+1
# If a cart name has commas or other characters, SPL surrounds
the cart name with quotes (""), so parse it as well.
@@ -258,21 +261,27 @@ def _populateCarts(carts, cartlst, modifier,
standardEdition=False):
elif pos == 22: identifier = "0"
elif pos == 23: identifier = "-"
else: identifier = "="
- if modifier == "main": cart = identifier
- else: cart = "%s+%s"%(modifier, identifier)
- carts[cart] = cartName
+ cart = identifier if not modifier else "+".join([modifier,
identifier])
+ if noEntry and refresh:
+ if cart in carts: del carts[cart]
+ else:
+ carts[cart] = cartName
# Cart file timestamps.
-_cartEditTimestamps = [0, 0, 0, 0]
+_cartEditTimestamps = None
# Initialize Cart Explorer i.e. fetch carts.
# Cart files list is for future use when custom cart names are used.
-def cartExplorerInit(StudioTitle, cartFiles=None):
+# if told to refresh, timestamps will be checked and updated banks will be
reassigned.
+# Carts dictionary is used if and only if refresh is on, as it'll modify live
cats.
+def cartExplorerInit(StudioTitle, cartFiles=None, refresh=False, carts=None):
global _cartEditTimestamps
+ debugOutput("SPL: refreshing Cart Explorer" if refresh else "SPL:
preparing cart Explorer")
# Use cart files in SPL's data folder to build carts dictionary.
# use a combination of SPL user name and static cart location to locate
cart bank files.
# Once the cart banks are located, use the routines in the populate
method above to assign carts.
# Since sstandard edition does not support number row carts, skip them
if told to do so.
- carts = {"standardLicense":StudioTitle.startswith("StationPlaylist
Studio Standard")}
+ if carts is None: carts =
{"standardLicense":StudioTitle.startswith("StationPlaylist Studio Standard")}
+ if refresh: carts["modifiedBanks"] = []
# Obtain the "real" path for SPL via environment variables and open the
cart data folder.
cartsDataPath =
os.path.join(os.environ["PROGRAMFILES"],"StationPlaylist","Data") # Provided
that Studio was installed using default path.
if cartFiles is None:
@@ -284,7 +293,10 @@ def cartExplorerInit(StudioTitle, cartFiles=None):
if userNameIndex >= 0:
cartFiles = [StudioTitle[userNameIndex+2:]+" "+cartFile
for cartFile in cartFiles]
faultyCarts = False
+ if not refresh:
+ _cartEditTimestamps = []
for f in cartFiles:
+ # Only do this if told to build cart banks from scratch, as
refresh flag is set if cart explorer is active in the first place.
try:
mod = f.split()[-2] # Checking for modifier string such
as ctrl.
# Todo: Check just in case some SPL flavors doesn't
ship with a particular cart file.
@@ -292,36 +304,31 @@ def cartExplorerInit(StudioTitle, cartFiles=None):
faultyCarts = True # In a rare event that the
broadcaster has saved the cart bank with the name like "carts.cart".
continue
cartFile = os.path.join(cartsDataPath,f)
- if not os.path.isfile(cartFile): # Cart explorer will fail if
whitespaces are in the beginning or at the end of a user name.
+ # Cart explorer can safely assume that the cart bank exists if
refresh flag is set.
+ if not refresh and not os.path.isfile(cartFile): # Cart
explorer will fail if whitespaces are in the beginning or at the end of a user
name.
faultyCarts = True
continue
+ debugOutput("SPL: examining carts from file %s"%cartFile)
+ cartTimestamp = os.path.getmtime(cartFile)
+ if refresh and _cartEditTimestamps[cartFiles.index(f)] ==
cartTimestamp:
+ debugOutput("SPL: no changes to cart bank, skipping")
+ continue
+ _cartEditTimestamps.append(cartTimestamp)
with open(cartFile) as cartInfo:
cl = [row for row in reader(cartInfo)]
- # 17.01: Look up file modification date to signal the
app module that Cart Explorer reentry should occur.
- _cartEditTimestamps[cartFiles.index(f)] =
os.path.getmtime(cartFile)
- _populateCarts(carts, cl[1], mod,
standardEdition=carts["standardLicense"]) # See the comment for _populate
method above.
+ # 17.04 (optimization): let empty string represent main cart
bank to avoid this being partially consulted up to 24 times.
+ # The below method will just check for string length, which is
faster than looking for specific substring.
+ _populateCarts(carts, cl[1], mod if mod != "main" else "",
standardEdition=carts["standardLicense"], refresh=refresh) # See the comment
for _populate method above.
+ if not refresh:
+ debugOutput("SPL: carts processed so far:
%s"%(len(carts)-1))
carts["faultyCarts"] = faultyCarts
+ debugOutput("SPL: total carts processed: %s"%(len(carts)-2))
return carts
-# See if cart files were modified.
-# This is needed in order to announce Cart Explorer reentry command.
-def shouldCartExplorerRefresh(StudioTitle):
- global _cartEditTimestamps
- cartsDataPath =
os.path.join(os.environ["PROGRAMFILES"],"StationPlaylist","Data") # Provided
that Studio was installed using default path.
- userNameIndex = StudioTitle.find("-")
- # Until NVDA core moves to Python 3, assume that file names aren't
unicode.
- cartFiles = [u"main carts.cart", u"shift carts.cart", u"ctrl
carts.cart", u"alt carts.cart"]
- if userNameIndex >= 0:
- cartFiles = [StudioTitle[userNameIndex+2:]+" "+cartFile for
cartFile in cartFiles]
- for f in cartFiles:
- # No need to check for faulty carts here, as Cart Explorer
activation checked it already.
- timestamp = os.path.getmtime(os.path.join(cartsDataPath,f))
- # 17.01: Look up file modification date to signal the app
module that Cart Explorer reentry should occur.
- # Optimization: Short-circuit if even one cart file has been
modified.
- if _cartEditTimestamps[cartFiles.index(f)] != timestamp:
- return True
- return False
-
+# Refresh carts upon request.
+# calls cart explorer init with special (internal) flags.
+def cartExplorerRefresh(studioTitle, currentCarts):
+ return cartExplorerInit(studioTitle, refresh=True, carts=currentCarts)
# Countdown timer.
# This is utilized by many services, chiefly profile triggers routine.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/d45d6079eb60/
Changeset: d45d6079eb60
Branch: None
User: josephsl
Date: 2017-02-07 17:15:43+00:00
Summary: Update check (17.04): no more install prompt during add-on updates.
During add-on updates, just update the add-on and never prompt to install it.
However, the reboot requirement prompt wil be displayed at the end.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index f8df6c5..d833e0c 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -281,8 +281,50 @@ class SPLUpdateDownloader(updateCheck.UpdateDownloader):
def _downloadSuccess(self):
self._stopped()
+ # Emulate add-on update (don't prompt to install).
from gui import addonGui
- wx.CallAfter(addonGui.AddonsDialog.handleRemoteAddonInstall,
self.destPath.decode("mbcs"))
+ closeAfter = addonGui.AddonsDialog._instance is None
+ try:
+ try:
+
bundle=addonHandler.AddonBundle(self.destPath.decode("mbcs"))
+ except:
+ log.error("Error opening addon bundle from
%s"%addonPath,exc_info=True)
+ # Translators: The message displayed when an
error occurs when opening an add-on package for adding.
+ gui.messageBox(_("Failed to open add-on package
file at %s - missing file or invalid file format")%addonPath,
+ # Translators: The title of a dialog
presented when an error occurs.
+ _("Error"),
+ wx.OK | wx.ICON_ERROR)
+ return
+ bundleName=bundle.manifest['name']
+ for addon in addonHandler.getAvailableAddons():
+ if not addon.isPendingRemove and
bundleName==addon.manifest['name']:
+ addon.requestRemove()
+ break
+ progressDialog =
gui.IndeterminateProgressDialog(gui.mainFrame,
+ # Translators: The title of the dialog presented while
an Addon is being updated.
+ _("Updating Add-on"),
+ # Translators: The message displayed while an addon is
being updated.
+ _("Please wait while the add-on is being updated."))
+ try:
+
gui.ExecAndPump(addonHandler.installAddonBundle,bundle)
+ except:
+ log.error("Error installing addon bundle from
%s"%addonPath,exc_info=True)
+ if not closeAfter:
addonGui.AddonsDialog(gui.mainFrame).refreshAddonsList()
+ progressDialog.done()
+ del progressDialog
+ # Translators: The message displayed when an
error occurs when installing an add-on package.
+ gui.messageBox(_("Failed to update add-on from
%s")%addonPath,
+ # Translators: The title of a dialog
presented when an error occurs.
+ _("Error"),
+ wx.OK | wx.ICON_ERROR)
+ return
+ else:
+ if not closeAfter:
addonGui.AddonsDialog(gui.mainFrame).refreshAddonsList(activeIndex=-1)
+ progressDialog.done()
+ del progressDialog
+ finally:
+ if closeAfter:
+ wx.CallLater(1,
addonGui.AddonsDialog(gui.mainFrame).Close)
# These structs are only complete enough to achieve what we need.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/c0ae59d6141b/
Changeset: c0ae59d6141b
Branch: None
User: josephsl
Date: 2017-02-07 18:19:53+00:00
Summary: Update check (17.04): use update info dictionary for update
results. fixes #21.
Emulating NVDA Core: an update info dictionary wil be reutrned that'll include
current version and new version, simulating how NVDA Core fetches update
inormation.
This is destined for add-on 17.04.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index d833e0c..71aa4e0 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -109,12 +109,14 @@ def checkForAddonUpdate():
return None
_progressDialog = None
-updateDictionary = True
# The update check routine.
# Auto is whether to respond with UI (manual check only), continuous takes in
auto update check variable for restarting the timer.
# ConfUpdateInterval comes from add-on config dictionary.
-def updateCheckerEx(auto=False, continuous=False, confUpdateInterval=1):
+def updateChecker(auto=False, continuous=False, confUpdateInterval=1):
+ if _pendingChannelChange:
+ wx.CallAfter(gui.messageBox, _("Did you recently tell SPL
add-on to use a different update channel? If so, please restart NVDA before
checking for add-on updates."), _("Update channel changed"), wx.ICON_ERROR)
+ return
global _SPLUpdateT, SPLAddonCheck, _retryAfterFailure, _progressDialog,
_updateNow
if _updateNow: _updateNow = False
import time
@@ -159,71 +161,6 @@ def updateCheckerEx(auto=False, continuous=False,
confUpdateInterval=1):
if not updateCandidate: wx.CallAfter(gui.messageBox, checkMessage,
_("Studio add-on update"))
else: wx.CallAfter(getUpdateResponse, checkMessage, _("Studio add-on
update"), info["path"])
-def updateChecker(auto=False, continuous=False, confUpdateInterval=1):
- if _pendingChannelChange:
- wx.CallAfter(gui.messageBox, _("Did you recently tell SPL
add-on to use a different update channel? If so, please restart NVDA before
checking for add-on updates."), _("Update channel changed"), wx.ICON_ERROR)
- return
- if updateDictionary:
- updateCheckerEx(auto=auto, continuous=continuous,
confUpdateInterval=confUpdateInterval)
- return
- global _SPLUpdateT, SPLAddonCheck, _retryAfterFailure, _progressDialog,
_updateNow
- if _updateNow: _updateNow = False
- import time
- # Regardless of whether it is an auto check, update the check time.
- # However, this shouldnt' be done if this is a retry after a failed
attempt.
- if not _retryAfterFailure: SPLAddonCheck = time.time()
- updateInterval = confUpdateInterval*_updateInterval*1000
- # Should the timer be set again?
- if continuous and not _retryAfterFailure:
_SPLUpdateT.Start(updateInterval, True)
- # Auto disables UI portion of this function if no updates are pending.
- # All the information will be stored in the URL object, so just close
it once the headers are downloaded.
- updateCandidate = False
- updateURL = SPLUpdateURL if SPLUpdateChannel not in channels else
channels[SPLUpdateChannel]
- try:
- import urllib
- # Look up the channel if different from the default.
- url = urllib.urlopen(updateURL)
- url.close()
- except IOError:
- _retryAfterFailure = True
- if not auto:
- wx.CallAfter(_progressDialog.done)
- _progressDialog = None
- # Translators: Error text shown when add-on update
check fails.
- wx.CallAfter(gui.messageBox, _("Error checking for
update."), _("Studio add-on update"), wx.ICON_ERROR)
- if continuous: _SPLUpdateT.Start(600000, True)
- return
- if _retryAfterFailure:
- _retryAfterFailure = False
- # Now is the time to update the check time if this is a retry.
- SPLAddonCheck = time.time()
- if url.code != 200:
- if auto:
- if continuous: _SPLUpdateT.Start(updateInterval, True)
- return # No need to interact with the user.
- # Translators: Text shown when update check fails for some odd
reason.
- checkMessage = _("Add-on update check failed.")
- else:
- # Am I qualified to update?
- import re
- version =
re.search("stationPlaylist-(?P<version>.*).nvda-addon",
res.url).groupdict()["version"]
- if version == SPLAddonVersion:
- if auto:
- if continuous:
_SPLUpdateT.Start(updateInterval, True)
- return
- # Translators: Presented when no add-on update is
available.
- checkMessage = _("No add-on update available.")
- else:
- # Translators: Text shown if an add-on update is
available.
- checkMessage = _("Studio add-on {newVersion} is
available. Would you like to update?").format(newVersion = version)
- updateCandidate = True
- if not auto:
- wx.CallAfter(_progressDialog.done)
- _progressDialog = None
- # Translators: Title of the add-on update check dialog.
- if not updateCandidate: wx.CallAfter(gui.messageBox, checkMessage,
_("Studio add-on update"))
- else: wx.CallAfter(getUpdateResponse, checkMessage, _("Studio add-on
update"), updateURL)
-
def getUpdateResponse(message, caption, updateURL):
if gui.messageBox(message, caption, wx.YES_NO | wx.NO_DEFAULT |
wx.CANCEL | wx.CENTER | wx.ICON_QUESTION) == wx.YES:
SPLUpdateDownloader([updateURL]).start()
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/6df1d2312713/
Changeset: 6df1d2312713
Branch: None
User: josephsl
Date: 2017-02-07 18:35:06+00:00
Summary: Readme entries for update check fix and debugging framework. fixes
#34, #24
Affected #: 1 file
diff --git a/readme.md b/readme.md
index ea471b7..bdad363 100755
--- a/readme.md
+++ b/readme.md
@@ -176,8 +176,10 @@ If you are using Studio on a touchscreen computer running
Windows 8 or later and
## Version 17.04-dev
+* Added a basic add-on debugging support by logging various information while
the add-on is active with NVDA set to debug logging (requires NVDA 2017.1 and
later). To use this, after installing NVDA 2017.1, from Exit NVDA dialog,
choose "restart with debug logging enabled" option.
* Improvements to presentation of various add-on dialogs thanks to NVDA 2016.4
features.
* NVDA will download add-on updates in the background if you say "yes" when
asked to update the add-on. Consequently, file download notifications from web
browsers will no longer be shown.
+* NVDA will no longer appear to freeze when checking for update at startup due
to add-on update channel change.
* Added ability to press Control+Alt+up or down arrow keys to move between
tracks (specifically, track columns) vertically just as one is moving to next
or previous row in a table.
* Added a combo box in add-on settings dialog to set which column should be
announced when moving through columns vertically.
* Moved end of track , intro and microphone alarm controls from add-on
settings to the new Alarms Center.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/1dc065d224a8/
Changeset: 1dc065d224a8
Branch: None
User: josephsl
Date: 2017-02-08 22:59:18+00:00
Summary: SPL Status global command (17.04): Cart edit/insert mode output
now follows that of SPL's own output.
Affected #: 1 file
diff --git a/addon/globalPlugins/splUtils/__init__.py
b/addon/globalPlugins/splUtils/__init__.py
index 6a3db64..68e5268 100755
--- a/addon/globalPlugins/splUtils/__init__.py
+++ b/addon/globalPlugins/splUtils/__init__.py
@@ -258,9 +258,9 @@ class GlobalPlugin(globalPluginHandler.GlobalPlugin):
statusInfo.append("Record to file on" if
winUser.sendMessage(SPLWin, 1024, 4, SPLStatusInfo) else "Record to file off")
cartEdit = winUser.sendMessage(SPLWin, 1024, 5,
SPLStatusInfo)
cartInsert = winUser.sendMessage(SPLWin, 1024, 6,
SPLStatusInfo)
- if cartEdit: statusInfo.append("Cart edit on")
- elif not cartEdit and cartInsert:
statusInfo.append("Cart insert on")
- else: statusInfo.append("Cart edit off")
+ if cartEdit: statusInfo.append("Cart Edit on")
+ elif not cartEdit and cartInsert:
statusInfo.append("Cart Insert on")
+ else: statusInfo.append("Cart Edit off")
ui.message("; ".join(statusInfo))
self.finish()
# Translators: Input help message for a SPL Controller command.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/d24a17c677b6/
Changeset: d24a17c677b6
Branch: None
User: josephsl
Date: 2017-02-09 02:39:53+00:00
Summary: Playlist snapshots (17.04): add an option to display the results
window when snapshots command was first pressed.
Requested by a broadcaster: add an option to allow the snapshots results window
to come up when snapshots command (SPL Assistant, F8 or a custom gesture) is
pressed once. This is achieved by adding a 1 to script count if this flag is in
effect (by default, it'll be off).
Affected #: 3 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 6d1c201..67667be 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1913,11 +1913,14 @@ class AppModule(appModuleHandler.AppModule):
self.finish()
return
scriptCount = scriptHandler.getLastScriptRepeatCount()
+ # Display the decorated HTML window on the first press if told
to do so.
+ if
splconfig.SPLConfig["PlaylistSnapshots"]["ShowResultsWindowOnFirstPress"]:
+ scriptCount += 1
# Never allow this to be invoked more than twice, as it causes
performance degredation and multiple HTML windows are opened.
if scriptCount >= 2:
self.finish()
return
- # Speak and braille on the first press, display a
decorated HTML message for subsequent presses.
+ # Speak and braille on the first press, display a decorated
HTML message for subsequent presses.
self.playlistSnapshotOutput(self.playlistSnapshots(obj.parent.firstChild,
None), scriptCount)
self.finish()
# Translators: Input help mode message for a command in Station
Playlist Studio.
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index a3d9b38..fa531a2 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -55,6 +55,7 @@ CategoryCount = boolean(default=true)
CategoryCountLimit = integer(min=0, max=10, default=5)
GenreCount = boolean(default=true)
GenreCountLimit = integer(min=0, max=10, default=5)
+ShowResultsWindowOnFirstPress = boolean(default=false)
[IntroOutroAlarms]
SayEndOfTrack = boolean(default=true)
EndOfTrackTime = integer(min=1, max=59, default=5)
diff --git a/addon/appModules/splstudio/splconfui.py
b/addon/appModules/splstudio/splconfui.py
index 95b7250..a66f6ba 100755
--- a/addon/appModules/splstudio/splconfui.py
+++ b/addon/appModules/splstudio/splconfui.py
@@ -197,6 +197,7 @@ class SPLConfigDialog(gui.SettingsDialog):
self.playlistCategoryCountLimit =
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCountLimit"]
self.playlistGenreCount =
splconfig.SPLConfig["PlaylistSnapshots"]["GenreCount"]
self.playlistGenreCountLimit =
splconfig.SPLConfig["PlaylistSnapshots"]["GenreCountLimit"]
+ self.resultsWindowOnFirstPress =
splconfig.SPLConfig["PlaylistSnapshots"]["ShowResultsWindowOnFirstPress"]
sizer = gui.guiHelper.BoxSizerHelper(self,
orientation=wx.HORIZONTAL)
self.metadataValues=[("off",_("Off")),
@@ -298,6 +299,7 @@ class SPLConfigDialog(gui.SettingsDialog):
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCountLimit"]
= self.playlistCategoryCountLimit
splconfig.SPLConfig["PlaylistSnapshots"]["GenreCount"] =
self.playlistGenreCount
splconfig.SPLConfig["PlaylistSnapshots"]["GenreCountLimit"] =
self.playlistGenreCountLimit
+
splconfig.SPLConfig["PlaylistSnapshots"]["ShowResultsWindowOnFirstPress"] =
self.resultsWindowOnFirstPress
splconfig.SPLConfig["General"]["MetadataReminder"] =
self.metadataValues[self.metadataList.GetSelection()][0]
splconfig.SPLConfig["MetadataStreaming"]["MetadataEnabled"] =
self.metadataStreams
splconfig.SPLConfig["ColumnAnnouncement"]["UseScreenColumnOrder"] =
self.columnOrderCheckbox.Value
@@ -935,6 +937,9 @@ class PlaylistSnapshotsDialog(wx.Dialog):
self.playlistGenreCountCheckbox=playlistSnapshotsHelper.addItem(wx.CheckBox(self,
label=_("Genre count")))
self.playlistGenreCountCheckbox.SetValue(parent.playlistGenreCount)
self.playlistGenreCountLimit=playlistSnapshotsHelper.addLabeledControl(_("Top
genre count (0 displays all genres)"), gui.nvdaControls.SelectOnFocusSpinCtrl,
min=0, max=10, initial=parent.playlistGenreCountLimit)
+ # Translators: the label for a setting in SPL add-on settings
to show playlist snaphsots window when the snapshots command is pressed once.
+
self.resultsWindowOnFirstPressCheckbox=playlistSnapshotsHelper.addItem(wx.CheckBox(self,
label=_("&Show results window when playlist snapshots command is performed
once")))
+
self.resultsWindowOnFirstPressCheckbox.SetValue(parent.resultsWindowOnFirstPress)
playlistSnapshotsHelper.addDialogDismissButtons(self.CreateButtonSizer(wx.OK |
wx.CANCEL))
self.Bind(wx.EVT_BUTTON, self.onOk, id=wx.ID_OK)
@@ -955,6 +960,7 @@ class PlaylistSnapshotsDialog(wx.Dialog):
parent.playlistCategoryCountLimit =
self.playlistCategoryCountLimit.GetValue()
parent.playlistGenreCount =
self.playlistGenreCountCheckbox.Value
parent.playlistGenreCountLimit =
self.playlistGenreCountLimit.GetValue()
+ parent.resultsWindowOnFirstPress =
self.resultsWindowOnFirstPressCheckbox.Value
parent.profiles.SetFocus()
parent.Enable()
self.Destroy()
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/079843bcd4f0/
Changeset: 079843bcd4f0
Branch: None
User: josephsl
Date: 2017-02-13 03:36:54+00:00
Summary: Merge branch 'stable'
Affected #: 1 file
diff --git a/addon/locale/he/LC_MESSAGES/nvda.po
b/addon/locale/he/LC_MESSAGES/nvda.po
index 70f5a7d..80f95ab 100644
--- a/addon/locale/he/LC_MESSAGES/nvda.po
+++ b/addon/locale/he/LC_MESSAGES/nvda.po
@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: stationPlaylist 5.5\n"
"Report-Msgid-Bugs-To: nvda-translations@xxxxxxxxxxxxx\n"
"POT-Creation-Date: 2015-09-25 19:00+1000\n"
-"PO-Revision-Date: 2016-11-21 21:09+0200\n"
+"PO-Revision-Date: 2017-02-05 01:29+0200\n"
"Last-Translator: <shmuel_naaman@xxxxxxxxx>\n"
"Language-Team: \n"
"Language: he\n"
@@ -141,7 +141,6 @@ msgid "Status: {name}"
msgstr "מצב: {name}"
#. Translators: The text of the help command in SPL Assistant layer.
-#, fuzzy
msgid ""
"After entering SPL Assistant, press:\n"
"A: Automation.\n"
@@ -176,7 +175,7 @@ msgid ""
"Shift+F1: Open online user guide."
msgstr ""
"לאחר הכניסה למסייע של SPL נא להקיש :\n"
-"A: אוטומציה\n"
+" A: אוטומציה\n"
"C : מיתוג סייר\n"
"Shift+C: הקראת שם הרצועה המנגנת כעת\n"
"D: הזמן הנותר ברשימת הנגינה\n"
@@ -208,7 +207,6 @@ msgstr ""
"Shift+F1: פתיחת המדריך המקוון"
#. Translators: The text of the help command in SPL Assistant layer when JFW
layer is active.
-#, fuzzy
msgid ""
"After entering SPL Assistant, press:\n"
"A: Automation.\n"
@@ -246,7 +244,7 @@ msgid ""
msgstr ""
"לאחר הכניסה למסייע של SPL נא להקיש :\n"
"A: אוטומציה\n"
-"C : מיתוג סייר\n"
+" C : מיתוג סייר\n"
"Shift+C: הקראת שם הרצועה המנגנת כעת\n"
"D: הזמן הנותר ברשימת הנגינה\n"
"E: קבלת מידע כולל על זרימת המטאדאטה\n"
@@ -277,7 +275,6 @@ msgstr ""
"Shift+F1: פתיחת המדריך המקוון"
#. Translators: The text of the help command in SPL Assistant layer when
Window-Eyes layer is active.
-#, fuzzy
msgid ""
"After entering SPL Assistant, press:\n"
"A: Automation.\n"
@@ -317,7 +314,7 @@ msgid ""
msgstr ""
"לאחר הכניסה למסייע של SPL נא להקיש :\n"
"A: אוטומציה\n"
-"C : מיתוג סייר\n"
+" C : מיתוג סייר\n"
"Shift+C: הקראת שם הרצועה המנגנת כעת\n"
"D: הזמן הנותר ברשימת הנגינה\n"
"E: קבלת מידע כולל על זרימת המטאדאטה\n"
@@ -738,9 +735,8 @@ msgid "Normal profile"
msgstr "פרופיל בסיסי"
#. Consult profile-specific key first before deleting anything.
-#, fuzzy
msgid "Normal Profile"
-msgstr "פרופיל בסיסי"
+msgstr "פרופיל בסיסי "
#. Translators: Presented when trying to switch to an instant switch profile
when add-on settings dialog is active.
msgid "Add-on settings dialog is open, cannot switch profiles"
@@ -864,9 +860,8 @@ msgstr ""
"נא להקיש אורך זמן התראת ההקלטה בשניות (כרגע לא פעיל. ) ערך 0, פירושו ללא "
"ההתראה."
-#, fuzzy
msgid "Microphone alarm interval"
-msgstr "התראה על ההקלטה ב&פרקי זמן קצובים"
+msgstr "התראה על ההקלטה ב&פרקי זמן קצובים "
#. Translators: Title of a dialog displayed when the add-on starts reminding
broadcasters to disable audio ducking.
msgid "SPL Studio and audio ducking"
@@ -919,9 +914,8 @@ msgid ""
msgstr "ברוכים הבאים לתוסף NVDA לStation Playlist."
#. Translators: Title of a dialog displayed when the add-on starts presenting
basic information, similar to NVDA's own welcome dialog.
-#, fuzzy
msgid "Welcome to StationPlaylist Studio add-on"
-msgstr "StationPlaylist Studio"
+msgstr "ברוך הבא להרחבת StationPlaylist "
#. Translators: A checkbox to show welcome dialog.
msgid "Show welcome dialog when I start Studio"
@@ -1127,19 +1121,16 @@ msgid "&Beep for different track categories"
msgstr "צל&יל שונה עבור סוגים של רצועות"
#. Translators: the label for a setting in SPL add-on settings to set how
track comments are announced.
-#, fuzzy
msgid "&Track comment announcement:"
-msgstr "הקראת שם הר&צועות"
+msgstr "הקראת שם הר&צועות "
#. Translators: One of the track comment notification settings.
-#, fuzzy
msgid "Message"
-msgstr "במלל"
+msgstr "הודעה"
#. Translators: One of the track comment notification settings.
-#, fuzzy
msgid "Beep"
-msgstr "בצלילים"
+msgstr "צלילים"
#. Translators: the label for a setting in SPL add-on settings to toggle top
and bottom notification.
msgid "Notify when located at &top or bottom of playlist viewer"
@@ -1174,9 +1165,8 @@ msgid "Columns E&xplorer..."
msgstr "סייר &הפקודות..."
#. Translators: The label of a button to configure columns explorer slots for
Track Tool (SPL Assistant, number row keys to announce specific columns).
-#, fuzzy
msgid "Columns Explorer for &Track Tool..."
-msgstr "סייר &הפקודות..."
+msgstr "סייר &הפקודות... "
#. Translators: The label of a button to open advanced options such as using
SPL Controller command to invoke Assistant layer.
msgid "&Status announcements..."
@@ -1187,9 +1177,8 @@ msgid "&Advanced options..."
msgstr "אפשרויות מתק&דמות..."
#. Translators: The label for a button in SPL add-on configuration dialog to
reset settings to defaults.
-#, fuzzy
msgid "Reset settings..."
-msgstr "שחזור הגדרות"
+msgstr "שחזור הגדרות ברירת מחדל"
#. Translators: A dialog message shown when add-on update channel has changed.
msgid ""
@@ -1347,9 +1336,8 @@ msgid "Columns Explorer"
msgstr "סייר העמודות"
#. Translators: The title of Columns Explorer configuration dialog.
-#, fuzzy
msgid "Columns Explorer for Track Tool"
-msgstr "סייר העמודות"
+msgstr "סייר העמודות "
#. Translators: The label for a setting in SPL add-on dialog to select column
for this column slot.
#, python-brace-format
@@ -1417,24 +1405,20 @@ msgid "Reset settings"
msgstr "שחזור הגדרות"
#. Translators: the label for resetting profile triggers.
-#, fuzzy
msgid "Reset instant switch profile"
-msgstr "זהו פרופיל ל&החלפה מיידית"
+msgstr "זהו פרופיל ל&החלפה מיידית "
#. Translators: the label for resetting profile triggers.
-#, fuzzy
msgid "Delete time-based profile database"
-msgstr "פרופילים מבוססי זמן חסרים."
+msgstr "פרופילים מבוססי זמן חסרים. "
#. Translators: the label for resetting encoder settings.
-#, fuzzy
msgid "Remove encoder settings"
-msgstr "שגיות בהגדרות מנגנון הקידוד."
+msgstr "הסר הגדרות קידוד"
#. Translators: the label for resetting track comments.
-#, fuzzy
msgid "Erase track comments"
-msgstr "סיום הרצועות בברייל"
+msgstr "מחר רצועת הערה"
#. Translators: A message to warn about resetting SPL config settings to
factory defaults.
msgid "Are you sure you wish to reset SPL add-on settings to defaults?"
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/570769537b08/
Changeset: 570769537b08
Branch: None
User: josephsl
Date: 2017-02-13 04:47:13+00:00
Summary: Update check: unused imports removed, certain data structures
removed.
Some data structures, especially used for SSL, are now pulled in from update
check module (NVDA Core). Also, unused imports were removed, and logging has
been improved (no longer referencing a nonexistent addonPath variable).
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index 71aa4e0..c00942d 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -8,18 +8,13 @@ import os # Essentially, update download is no different than
file downloads.
import cPickle
import threading
import tempfile
-import hashlib
-import ctypes.wintypes
+import ctypes
import ssl
-import wx
-import shellapi
import gui
import wx
import addonHandler
import globalVars
import updateCheck
-import config
-import winUser
# Add-on manifest routine (credit: various add-on authors including Noelia
Martinez).
# Do not rely on using absolute path to open to manifest, as installation
directory may change in a future NVDA Core version (highly unlikely, but...).
@@ -96,7 +91,7 @@ def checkForAddonUpdate():
raise
if res.code != 200:
raise RuntimeError("Checking for update failed with code %d" %
res.code)
- # Build emulated add-on update dictionary if there is indeed a new
verison.
+ # Build emulated add-on update dictionary if there is indeed a new
version.
# The add-on version is of the form "x.y.z". The "-dev" suffix
indicates development release.
# Anything after "-dev" indicates a try or a custom build.
# LTS: Support upgrading between LTS releases.
@@ -167,10 +162,6 @@ def getUpdateResponse(message, caption, updateURL):
# Update downloader (credit: NV Access)
# Customized for SPL add-on.
-
-#: The download block size in bytes.
-DOWNLOAD_BLOCK_SIZE = 8192 # 8 kb
-
class SPLUpdateDownloader(updateCheck.UpdateDownloader):
"""Overrides NVDA Core's downloader.)
No hash checking for now, and URL's and temp file paths are different.
@@ -225,9 +216,9 @@ class SPLUpdateDownloader(updateCheck.UpdateDownloader):
try:
bundle=addonHandler.AddonBundle(self.destPath.decode("mbcs"))
except:
- log.error("Error opening addon bundle from
%s"%addonPath,exc_info=True)
+ log.error("Error opening addon bundle from
%s"%self.destPath,exc_info=True)
# Translators: The message displayed when an
error occurs when opening an add-on package for adding.
- gui.messageBox(_("Failed to open add-on package
file at %s - missing file or invalid file format")%addonPath,
+ gui.messageBox(_("Failed to open add-on package
file at %s - missing file or invalid file format")%self.destPath,
# Translators: The title of a dialog
presented when an error occurs.
_("Error"),
wx.OK | wx.ICON_ERROR)
@@ -245,12 +236,12 @@ class SPLUpdateDownloader(updateCheck.UpdateDownloader):
try:
gui.ExecAndPump(addonHandler.installAddonBundle,bundle)
except:
- log.error("Error installing addon bundle from
%s"%addonPath,exc_info=True)
+ log.error("Error installing addon bundle from
%s"%self.destPath,exc_info=True)
if not closeAfter:
addonGui.AddonsDialog(gui.mainFrame).refreshAddonsList()
progressDialog.done()
del progressDialog
# Translators: The message displayed when an
error occurs when installing an add-on package.
- gui.messageBox(_("Failed to update add-on from
%s")%addonPath,
+ gui.messageBox(_("Failed to update add-on from
%s")%self.destPath,
# Translators: The title of a dialog
presented when an error occurs.
_("Error"),
wx.OK | wx.ICON_ERROR)
@@ -260,33 +251,15 @@ class SPLUpdateDownloader(updateCheck.UpdateDownloader):
progressDialog.done()
del progressDialog
finally:
+ try:
+ os.remove(self.destPath)
+ except OSError:
+ pass
if closeAfter:
wx.CallLater(1,
addonGui.AddonsDialog(gui.mainFrame).Close)
-# These structs are only complete enough to achieve what we need.
-class CERT_USAGE_MATCH(ctypes.Structure):
- _fields_ = (
- ("dwType", ctypes.wintypes.DWORD),
- # CERT_ENHKEY_USAGE struct
- ("cUsageIdentifier", ctypes.wintypes.DWORD),
- ("rgpszUsageIdentifier", ctypes.c_void_p), # LPSTR *
- )
-
-class CERT_CHAIN_PARA(ctypes.Structure):
- _fields_ = (
- ("cbSize", ctypes.wintypes.DWORD),
- ("RequestedUsage", CERT_USAGE_MATCH),
- ("RequestedIssuancePolicy", CERT_USAGE_MATCH),
- ("dwUrlRetrievalTimeout", ctypes.wintypes.DWORD),
- ("fCheckRevocationFreshnessTime", ctypes.wintypes.BOOL),
- ("dwRevocationFreshnessTime", ctypes.wintypes.DWORD),
- ("pftCacheResync", ctypes.c_void_p), # LPFILETIME
- ("pStrongSignPara", ctypes.c_void_p), # PCCERT_STRONG_SIGN_PARA
- ("dwStrongSignFlags", ctypes.wintypes.DWORD),
- )
-
-# Borrowed from NVDA Core (the only difference is the URL).
+# Borrowed from NVDA Core (the only difference is the URL and where structures
are coming from).
def _updateWindowsRootCertificates():
crypt = ctypes.windll.crypt32
# Get the server certificate.
@@ -302,8 +275,8 @@ def _updateWindowsRootCertificates():
# Ask Windows to build a certificate chain, thus triggering a root
certificate update.
chainCont = ctypes.c_void_p()
crypt.CertGetCertificateChain(None, certCont, None, None,
-
ctypes.byref(CERT_CHAIN_PARA(cbSize=ctypes.sizeof(CERT_CHAIN_PARA),
- RequestedUsage=CERT_USAGE_MATCH())),
+
ctypes.byref(updateCheck.CERT_CHAIN_PARA(cbSize=ctypes.sizeof(updateCheck.CERT_CHAIN_PARA),
+ RequestedUsage=updateCheck.CERT_USAGE_MATCH())),
0, None,
ctypes.byref(chainCont))
crypt.CertFreeCertificateChain(chainCont)
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/4f2b0487f7c4/
Changeset: 4f2b0487f7c4
Branch: None
User: josephsl
Date: 2017-02-15 17:11:09+00:00
Summary: Debug logging (17.04): oops, splupdate.updateChannel does not
exist (typo).
Affected #: 1 file
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 67667be..dea0c15 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -600,7 +600,7 @@ class AppModule(appModuleHandler.AppModule):
# Check for add-on update if told to do so.
# LTS: Only do this if channel hasn't changed.
if splconfig.SPLConfig["Update"]["AutoUpdateCheck"] or
splupdate._updateNow:
- debugOutput("SPL: checking for add-on updates from %s
channel"%splupdate.updateChannel)
+ debugOutput("SPL: checking for add-on updates from %s
channel"%splupdate.SPLUpdateChannel)
# 7.0: Have a timer call the update function indirectly.
import queueHandler
queueHandler.queueFunction(queueHandler.eventQueue,
splconfig.updateInit)
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/148d4c230b81/
Changeset: 148d4c230b81
Branch: None
User: josephsl
Date: 2017-02-15 19:47:03+00:00
Summary: Update check: assign the update candidate variable until update
dictionary is fully used.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index c00942d..6663174 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -123,6 +123,7 @@ def updateChecker(auto=False, continuous=False,
confUpdateInterval=1):
# Should the timer be set again?
if continuous and not _retryAfterFailure:
_SPLUpdateT.Start(updateInterval, True)
# Auto disables UI portion of this function if no updates are pending.
+ updateCandidate = False
try:
info = checkForAddonUpdate()
except:
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/3116816d0b97/
Changeset: 3116816d0b97
Branch: None
User: josephsl
Date: 2017-02-17 18:21:50+00:00
Summary: Update check: simplified UI code.
Because an update dictionary will be returned, there's really no need to say
update candidate flag. Also simplified the UI code for displaying update result
dialog.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index 6663174..1b26bbd 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -123,7 +123,6 @@ def updateChecker(auto=False, continuous=False,
confUpdateInterval=1):
# Should the timer be set again?
if continuous and not _retryAfterFailure:
_SPLUpdateT.Start(updateInterval, True)
# Auto disables UI portion of this function if no updates are pending.
- updateCandidate = False
try:
info = checkForAddonUpdate()
except:
@@ -140,22 +139,21 @@ def updateChecker(auto=False, continuous=False,
confUpdateInterval=1):
_retryAfterFailure = False
# Now is the time to update the check time if this is a retry.
SPLAddonCheck = time.time()
+ if not auto:
+ wx.CallAfter(_progressDialog.done)
+ _progressDialog = None
+ # Translators: Title of the add-on update check dialog.
+ dialogTitle = _("Studio add-on update")
if info is None:
if auto:
if continuous: _SPLUpdateT.Start(updateInterval, True)
return # No need to interact with the user.
# Translators: Presented when no add-on update is available.
- checkMessage = _("No add-on update available.")
+ wx.CallAfter(gui.messageBox, _("No add-on update available."),
dialogTitle)
else:
# Translators: Text shown if an add-on update is available.
checkMessage = _("Studio add-on {newVersion} is available.
Would you like to update?").format(newVersion = info["newVersion"])
- updateCandidate = True
- if not auto:
- wx.CallAfter(_progressDialog.done)
- _progressDialog = None
- # Translators: Title of the add-on update check dialog.
- if not updateCandidate: wx.CallAfter(gui.messageBox, checkMessage,
_("Studio add-on update"))
- else: wx.CallAfter(getUpdateResponse, checkMessage, _("Studio add-on
update"), info["path"])
+ wx.CallAfter(getUpdateResponse, checkMessage, dialogTitle,
info["path"])
def getUpdateResponse(message, caption, updateURL):
if gui.messageBox(message, caption, wx.YES_NO | wx.NO_DEFAULT |
wx.CANCEL | wx.CENTER | wx.ICON_QUESTION) == wx.YES:
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/82ec06f140ad/
Changeset: 82ec06f140ad
Branch: None
User: josephsl
Date: 2017-02-19 21:11:06+00:00
Summary: Translatable strings: Normal Profile -> Normal profile, update
add-on component files to point to SPL Utils global plugin.
Affected #: 2 files
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index fa531a2..6ef7fca 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -93,6 +93,8 @@ _mutatableSettings=("IntroOutroAlarms", "MicrophoneAlarm",
"MetadataStreaming",
# 7.0: Profile-specific confspec (might be removed once a more optimal way to
validate sections is found).
# Dictionary comprehension is better here.
confspecprofiles = {sect:key for sect, key in confspec.iteritems() if sect in
_mutatableSettings}
+# Translators: The name of the default (normal) profile.
+defaultProfileName = _("Normal profile")
# 8.0: Run-time config storage and management will use ConfigHub data
structure, a subclass of chain map.
# A chain map allows a dictionary to look up predefined mappings to locate a
key.
@@ -113,8 +115,7 @@ class ConfigHub(ChainMap):
super(ConfigHub, self).__init__()
# For presentational purposes.
self.profileNames = []
- # Translators: The name of the default (normal) profile.
- self.maps[0] = self._unlockConfig(SPLIni, profileName=_("Normal
profile"), prefill=True, validateNow=True)
+ self.maps[0] = self._unlockConfig(SPLIni,
profileName=defaultProfileName, prefill=True, validateNow=True)
self.profileNames.append(None) # Signifying normal profile.
# Always cache normal profile upon startup.
self._cacheConfig(self.maps[0])
@@ -237,7 +238,7 @@ class ConfigHub(ChainMap):
def deleteProfile(self, name):
# Bring normal profile to the front if it isn't.
# Optimization: Tell the swapper that we need index to the
normal profile for this case.
- configPos = self.swapProfiles(name, _("Normal profile"),
showSwitchIndex=True) if self.profiles[0].name == name else
self.profileIndexByName(name)
+ configPos = self.swapProfiles(name, defaultProfileName,
showSwitchIndex=True) if self.profiles[0].name == name else
self.profileIndexByName(name)
profilePos = self.profileNames.index(name)
try:
os.remove(self.profiles[configPos].filename)
@@ -259,7 +260,7 @@ class ConfigHub(ChainMap):
def __delitem__(self, key):
# Consult profile-specific key first before deleting anything.
- pos = 0 if key in _mutatableSettings else [profile.name for
profile in self.maps].index(_("Normal Profile"))
+ pos = 0 if key in _mutatableSettings else [profile.name for
profile in self.maps].index(defaultProfileName)
try:
del self.maps[pos][key]
except KeyError:
@@ -270,7 +271,7 @@ class ConfigHub(ChainMap):
# 7.0: Save normal profile first.
# Temporarily merge normal profile.
# 8.0: Locate the index instead.
- normalProfile = self.profileIndexByName(_("Normal profile"))
+ normalProfile = self.profileIndexByName(defaultProfileName)
_preSave(self.profiles[normalProfile])
# Disk write optimization check please.
# 8.0: Bypass this if profiles were reset.
@@ -315,10 +316,10 @@ class ConfigHub(ChainMap):
# Convert certain settings to a different format.
conf["ColumnAnnouncement"]["IncludedColumns"] =
set(_SPLDefaults["ColumnAnnouncement"]["IncludedColumns"])
# Switch back to normal profile via a custom variant of swap
routine.
- if self.profiles[0].name != _("Normal profile"):
- npIndex = self.profileIndexByName(_("Normal profile"))
+ if self.profiles[0].name != defaultProfileName:
+ npIndex = self.profileIndexByName(defaultProfileName)
self.profiles[0], self.profiles[npIndex] =
self.profiles[npIndex], self.profiles[0]
- self.activeProfile = _("Normal profile")
+ self.activeProfile = defaultProfileName
# 8.0 optimization: Tell other modules that reset was done in
order to postpone disk writes until the end.
self.resetHappened = True
@@ -770,7 +771,7 @@ def switchProfile(prevProfile, newProfile):
SPLConfig.switchProfile(prevProfile, newProfile)
SPLPrevProfile = prevProfile
# 8.0: Cache other profiles this time.
- if newProfile != _("Normal profile") and newProfile not in _SPLCache:
+ if newProfile != defaultProfileName and newProfile not in _SPLCache:
_cacheConfig(getProfileByName(selectedProfile))
# Called from within the app module.
diff --git a/buildVars.py b/buildVars.py
index 26e6739..5b65263 100755
--- a/buildVars.py
+++ b/buildVars.py
@@ -37,7 +37,7 @@ import os.path
pythonSources = [os.path.join("addon", "*.py"),
os.path.join("addon", "appModules", "*.py"),
os.path.join("addon", "appModules", "splstudio", "*.py"),
-os.path.join("addon", "globalPlugins", "SPLStudioUtils", "*.py")]
+os.path.join("addon", "globalPlugins", "splUtils", "*.py")]
# Files that contain strings for translation. Usually your python sources
i18nSources = pythonSources + ["buildVars.py"]
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/b4bac115c783/
Changeset: b4bac115c783
Branch: None
User: josephsl
Date: 2017-02-24 17:18:41+00:00
Summary: Playlist snapshots (17.04): translatable strings.
For some, the percent character has been employed in order to avoid index error
exception. Some uses numeric interpolation substitutions, while others have
friendly variable names defined (will be explained in translator comments).
Affected #: 1 file
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index dea0c15..1a64e89 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1494,59 +1494,65 @@ class AppModule(appModuleHandler.AppModule):
# Output formatter for playlist snapshots.
# Pressed once will speak and/or braille it, pressing twice or more will
output this info to an HTML file.
def playlistSnapshotOutput(self, snapshot, scriptCount):
- statusInfo = ["Items: %s"%snapshot["PlaylistItemCount"]]
- statusInfo.append("Tracks: %s"%snapshot["PlaylistTrackCount"])
- statusInfo.append("Duration:
%s"%snapshot["PlaylistDurationTotal"])
+ statusInfo = [_("Items:
{playlistItemCount}").format(playlistItemCount = snapshot["PlaylistItemCount"])]
+ statusInfo.append(_("Tracks:
{playlistTrackCount}").format(playlistTrackCount =
snapshot["PlaylistTrackCount"]))
+ statusInfo.append(_("Duration:
{playlistTotalDuration}").format(playlistTotalDuration =
snapshot["PlaylistDurationTotal"]))
if "PlaylistDurationMin" in snapshot:
- statusInfo.append("Shortest:
%s"%snapshot["PlaylistDurationMin"])
- statusInfo.append("Longest:
%s"%snapshot["PlaylistDurationMax"])
+ statusInfo.append(_("Shortest:
{playlistShortestTrack}").format(playlistShortestTrack =
snapshot["PlaylistDurationMin"]))
+ statusInfo.append(_("Longest:
{playlistLongestTrack}").format(playlistLongestTrack =
snapshot["PlaylistDurationMax"]))
if "PlaylistDurationAverage" in snapshot:
- statusInfo.append("Average:
%s"%snapshot["PlaylistDurationAverage"])
+ statusInfo.append(_("Average:
{playlistAverageDuration}").format(playlistAverageDuration =
snapshot["PlaylistDurationAverage"]))
if "PlaylistArtistCount" in snapshot:
artistCount =
splconfig.SPLConfig["PlaylistSnapshots"]["ArtistCountLimit"]
artists =
snapshot["PlaylistArtistCount"].most_common(None if not artistCount else
artistCount)
if scriptCount == 0:
- statusInfo.append("Top artist: %s
(%s)"%(artists[0][:]))
+ statusInfo.append(_("Top artist: %s
(%s)")%(artists[0][:]))
elif scriptCount == 1:
artistList = []
+ header = _("Top artists:")
for item in artists:
artist, count = item
if artist is None:
- artistList.append("<li>No
artist information (%s)</li>"%(count))
+ info = _("No artist information
({artistCount})").format(artistCount = count)
else:
- artistList.append("<li>%s
(%s)</li>"%(artist, count))
- statusInfo.append("".join(["Top artists:<ol>",
"\n".join(artistList), "</ol>"]))
+ info = _("{artistName}
({artistCount})").format(artistName = artist, artistCount = count)
+ artistList.append("<li>%s</li>"%info)
+ statusInfo.append("".join([header, "<ol>",
"\n".join(artistList), "</ol>"]))
if "PlaylistCategoryCount" in snapshot:
categoryCount =
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCountLimit"]
categories =
snapshot["PlaylistCategoryCount"].most_common(None if not categoryCount else
categoryCount)
if scriptCount == 0:
- statusInfo.append("Top category: %s
(%s)"%(categories[0][:]))
+ statusInfo.append(_("Top category: %s
(%s)")%(categories[0][:]))
elif scriptCount == 1:
categoryList = []
+ header = _("Categories:")
for item in categories:
category, count = item
category = category.replace("<", "")
category = category.replace(">", "")
- categoryList.append("<li>%s
(%s)</li>"%(category, count))
- statusInfo.append("".join(["Categories:<ol>",
"\n".join(categoryList), "</ol>"]))
+ info = _("{categoryName}
({categoryCount})").format(categoryName = category, categoryCount = count)
+ categoryList.append("<li>%s</li>"%info)
+ statusInfo.append("".join([header, "<ol>",
"\n".join(categoryList), "</ol>"]))
if "PlaylistGenreCount" in snapshot:
genreCount =
splconfig.SPLConfig["PlaylistSnapshots"]["GenreCountLimit"]
genres =
snapshot["PlaylistGenreCount"].most_common(None if not genreCount else
genreCount)
if scriptCount == 0:
- statusInfo.append("Top genre: %s
(%s)"%(genres[0][:]))
+ statusInfo.append(_("Top genre: %s
(%s)")%(genres[0][:]))
elif scriptCount == 1:
genreList = []
+ header = _("Top genres:")
for item in genres:
genre, count = item
if genre is None:
- genreList.append("<li>No genre
information (%s)</li>"%(count))
+ info = _("No genre information
({genreCount})").format(genreCount = count)
else:
- genreList.append("<li>%s
(%s)</li>"%(genre, count))
- statusInfo.append("".join(["Top genres:<ol>",
"\n".join(genreList), "</ol>"]))
+ info = _("{genreName}
({genreCount})").format(genreName = genre, genreCount = count)
+ genreList.append("<li>%s</li>"%info)
+ statusInfo.append("".join([header, "<ol>",
"\n".join(genreList), "</ol>"]))
if scriptCount == 0:
ui.message(", ".join(statusInfo))
else:
-
ui.browseableMessage("<p>".join(statusInfo),title="Playlist snapshots",
isHtml=True)
+
ui.browseableMessage("<p>".join(statusInfo),title=_("Playlist snapshots"),
isHtml=True)
# Some handlers for native commands.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/e25a3f2e72d1/
Changeset: e25a3f2e72d1
Branch: None
User: josephsl
Date: 2017-03-02 16:00:25+00:00
Summary: Track item (17.04): announce item position when NVDA+Delete
(Numpad delete) is pressed.
Instead of a generic object rectangle, announce current position within a
playlist when location command is invoked, similar to PowerPoint and Excel
support in NVDA Core and other places.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 1a64e89..118a876 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -184,6 +184,11 @@ class SPLTrackItem(IAccessible):
# 7.0: Let the app module keep a reference to this track.
self.appModule._focusedTrack = self
+ # A friendly way to report track position via location text.
+ def _get_locationText(self):
+ # Translaotrs: location text for a playlist item (example: item
1 of 10).
+ return _("Item {current} of {total}").format(current =
self.IAccessibleChildID, total = studioAPI(0, 124, ret=True))
+
# Track Dial: using arrow keys to move through columns.
# This is similar to enhanced arrow keys in other screen readers.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/1f8299ab798c/
Changeset: 1f8299ab798c
Branch: None
User: josephsl
Date: 2017-03-02 16:02:11+00:00
Summary: Track Dial (17.04): second to last step - pass gestures along.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 118a876..5cdccac 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -193,7 +193,7 @@ class SPLTrackItem(IAccessible):
# This is similar to enhanced arrow keys in other screen readers.
def script_toggleTrackDial(self, gesture):
- ui.message("Track Dial is deprecated in 2017, please unassign
Track Dial toggle command via Input Gestures dialog")
+ gesture.send()
# Translators: Input help mode message for SPL track item.
script_toggleTrackDial.__doc__=_("Toggles track dial on and off.")
script_toggleTrackDial.category = _("StationPlaylist Studio")
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/636b879d3f81/
Changeset: 636b879d3f81
Branch: None
User: josephsl
Date: 2017-03-02 20:46:43+00:00
Summary: Merged stable
Affected #: 2 files
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 6ef7fca..0e41fef 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -772,7 +772,7 @@ def switchProfile(prevProfile, newProfile):
SPLPrevProfile = prevProfile
# 8.0: Cache other profiles this time.
if newProfile != defaultProfileName and newProfile not in _SPLCache:
- _cacheConfig(getProfileByName(selectedProfile))
+ _cacheConfig(getProfileByName(newProfile))
# Called from within the app module.
def instantProfileSwitch():
diff --git a/readme.md b/readme.md
index bdad363..320f7ab 100755
--- a/readme.md
+++ b/readme.md
@@ -194,6 +194,11 @@ If you are using Studio on a touchscreen computer running
Windows 8 or later and
* Initial support for StationPlaylist Creator.
* Added a new command in SPL Controller layer to announce Studio status such
as track playback and microphone status (Q).
+## Version 17.03
+
+* NVDA will no longer appear to do anything or play an error tone when
switching to a time-based broadcast profile.
+* Updated translations.
+
## Version 17.01/15.5-LTS
Note: 17.01.1/15.5A-LTS replaces 17.01 due to changes to location of new
add-on files.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/3cce1ccb0c7c/
Changeset: 3cce1ccb0c7c
Branch: None
User: josephsl
Date: 2017-03-13 23:27:03+00:00
Summary: Track Dial and location text (17.04): delay location text
announcement to 17.05, Track Dial is now part of history.
As 17.04 release is close at hand, it would be advisable to freeze features,
thus location text announcement will be delayed to 17.05.
Affected #: 1 file
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 5cdccac..9c671c3 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -116,7 +116,7 @@ _SPLCategoryTones = {
# Routines for track items themselves (prepare for future work).
class SPLTrackItem(IAccessible):
- """A base class for providing utility scripts when track entries are
focused, such as track dial."""
+ """A base class for providing utility scripts when track entries are
focused, such as location text and column navigation."""
# Keep a record of which column is being looked at.
_curColumnNumber = None
@@ -185,18 +185,9 @@ class SPLTrackItem(IAccessible):
self.appModule._focusedTrack = self
# A friendly way to report track position via location text.
- def _get_locationText(self):
- # Translaotrs: location text for a playlist item (example: item
1 of 10).
- return _("Item {current} of {total}").format(current =
self.IAccessibleChildID, total = studioAPI(0, 124, ret=True))
-
- # Track Dial: using arrow keys to move through columns.
- # This is similar to enhanced arrow keys in other screen readers.
-
- def script_toggleTrackDial(self, gesture):
- gesture.send()
- # Translators: Input help mode message for SPL track item.
- script_toggleTrackDial.__doc__=_("Toggles track dial on and off.")
- script_toggleTrackDial.category = _("StationPlaylist Studio")
+ """def _get_locationText(self):
+ # Translators: location text for a playlist item (example: item
1 of 10).
+ return _("Item {current} of {total}").format(current =
self.IAccessibleChildID, total = studioAPI(0, 124, ret=True))"""
# Some helper functions to handle corner cases.
# Each track item provides its own version.
@@ -380,7 +371,7 @@ class SPL510TrackItem(SPLTrackItem):
def _origIndexOf(self, columnHeader):
return
splconfig._SPLDefaults["ColumnAnnouncement"]["ColumnOrder"].index(columnHeader)+1
- # Handle track dial for SPL 5.10.
+ # Handle column announcement for SPL 5.10.
def _leftmostcol(self):
if not self.name:
# Translators: Presented when no track status is found
in Studio 5.10.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/b6b9852e2575/
Changeset: b6b9852e2575
Branch: None
User: josephsl
Date: 2017-03-15 18:33:50+00:00
Summary: Docstrings: corrected case for end of track and song intro scripts
Affected #: 1 file
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 9c671c3..2ede801 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -992,14 +992,14 @@ class AppModule(appModuleHandler.AppModule):
def script_setEndOfTrackTime(self, gesture):
self.alarmDialog(1)
# Translators: Input help mode message for a command in Station
Playlist Studio.
- script_setEndOfTrackTime.__doc__=_("sets end of track alarm (default is
5 seconds).")
+ script_setEndOfTrackTime.__doc__=_("Sets end of track alarm (default is
5 seconds).")
# Set song ramp (introduction) time between 1 and 9 seconds.
def script_setSongRampTime(self, gesture):
self.alarmDialog(2)
# Translators: Input help mode message for a command in Station
Playlist Studio.
- script_setSongRampTime.__doc__=_("sets song intro alarm (default is 5
seconds).")
+ script_setSongRampTime.__doc__=_("Sets song intro alarm (default is 5
seconds).")
# Tell NVDA to play a sound when mic was active for a long time, as
well as contorl the alarm interval.
# 8.0: This dialog will let users configure mic alarm interval as well.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/abcc41644ecd/
Changeset: abcc41644ecd
Branch: None
User: josephsl
Date: 2017-03-19 20:09:08+00:00
Summary: Translatable strings, translator comments, readme updates, release
candidate is up
Affected #: 2 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 2ede801..634a8f5 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -1490,21 +1490,29 @@ class AppModule(appModuleHandler.AppModule):
# Output formatter for playlist snapshots.
# Pressed once will speak and/or braille it, pressing twice or more will
output this info to an HTML file.
def playlistSnapshotOutput(self, snapshot, scriptCount):
+ # Translators: one of the results for playlist snapshots
feature for announcing total number of items in a playlist.
statusInfo = [_("Items:
{playlistItemCount}").format(playlistItemCount = snapshot["PlaylistItemCount"])]
+ # Translators: one of the results for playlist snapshots
feature for announcing total number of tracks in a playlist.
statusInfo.append(_("Tracks:
{playlistTrackCount}").format(playlistTrackCount =
snapshot["PlaylistTrackCount"]))
+ # Translators: one of the results for playlist snapshots
feature for announcing total duration of a playlist.
statusInfo.append(_("Duration:
{playlistTotalDuration}").format(playlistTotalDuration =
snapshot["PlaylistDurationTotal"]))
if "PlaylistDurationMin" in snapshot:
+ # Translators: one of the results for playlist
snapshots feature for announcing shortest track name and duration of a playlist.
statusInfo.append(_("Shortest:
{playlistShortestTrack}").format(playlistShortestTrack =
snapshot["PlaylistDurationMin"]))
+ # Translators: one of the results for playlist
snapshots feature for announcing longest track name and duration of a playlist.
statusInfo.append(_("Longest:
{playlistLongestTrack}").format(playlistLongestTrack =
snapshot["PlaylistDurationMax"]))
if "PlaylistDurationAverage" in snapshot:
+ # Translators: one of the results for playlist
snapshots feature for announcing average duration for tracks in a playlist.
statusInfo.append(_("Average:
{playlistAverageDuration}").format(playlistAverageDuration =
snapshot["PlaylistDurationAverage"]))
if "PlaylistArtistCount" in snapshot:
artistCount =
splconfig.SPLConfig["PlaylistSnapshots"]["ArtistCountLimit"]
artists =
snapshot["PlaylistArtistCount"].most_common(None if not artistCount else
artistCount)
if scriptCount == 0:
+ # Translators: one of the results for playlist
snapshots feature for announcing top artist in a playlist.
statusInfo.append(_("Top artist: %s
(%s)")%(artists[0][:]))
elif scriptCount == 1:
artistList = []
+ # Translators: one of the results for playlist
snapshots feature, a heading for a group of items.
header = _("Top artists:")
for item in artists:
artist, count = item
@@ -1518,9 +1526,11 @@ class AppModule(appModuleHandler.AppModule):
categoryCount =
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCountLimit"]
categories =
snapshot["PlaylistCategoryCount"].most_common(None if not categoryCount else
categoryCount)
if scriptCount == 0:
+ # Translators: one of the results for playlist
snapshots feature for announcing top track category in a playlist.
statusInfo.append(_("Top category: %s
(%s)")%(categories[0][:]))
elif scriptCount == 1:
categoryList = []
+ # Translators: one of the results for playlist
snapshots feature, a heading for a group of items.
header = _("Categories:")
for item in categories:
category, count = item
@@ -1533,9 +1543,11 @@ class AppModule(appModuleHandler.AppModule):
genreCount =
splconfig.SPLConfig["PlaylistSnapshots"]["GenreCountLimit"]
genres =
snapshot["PlaylistGenreCount"].most_common(None if not genreCount else
genreCount)
if scriptCount == 0:
+ # Translators: one of the results for playlist
snapshots feature for announcing top genre in a playlist.
statusInfo.append(_("Top genre: %s
(%s)")%(genres[0][:]))
elif scriptCount == 1:
genreList = []
+ # Translators: one of the results for playlist
snapshots feature, a heading for a group of items.
header = _("Top genres:")
for item in genres:
genre, count = item
@@ -1548,6 +1560,7 @@ class AppModule(appModuleHandler.AppModule):
if scriptCount == 0:
ui.message(", ".join(statusInfo))
else:
+ # Translators: The title of a window for displaying
playlist snapshots information.
ui.browseableMessage("<p>".join(statusInfo),title=_("Playlist snapshots"),
isHtml=True)
# Some handlers for native commands.
diff --git a/readme.md b/readme.md
index 320f7ab..be808b2 100755
--- a/readme.md
+++ b/readme.md
@@ -174,7 +174,7 @@ From studio window, you can press Alt+NVDA+0 to open the
add-on configuration di
If you are using Studio on a touchscreen computer running Windows 8 or later
and have NVDA 2012.3 or later installed, you can perform some Studio commands
from the touchscreen. First use three finger tap to switch to SPL mode, then
use the touch commands listed above to perform commands.
-## Version 17.04-dev
+## Version 17.04
* Added a basic add-on debugging support by logging various information while
the add-on is active with NVDA set to debug logging (requires NVDA 2017.1 and
later). To use this, after installing NVDA 2017.1, from Exit NVDA dialog,
choose "restart with debug logging enabled" option.
* Improvements to presentation of various add-on dialogs thanks to NVDA 2016.4
features.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/bf43ec356e3a/
Changeset: bf43ec356e3a
Branch: None
User: josephsl
Date: 2017-03-20 06:28:55+00:00
Summary: Merge branch 'master' into stable
Affected #: 15 files
diff --git a/addon/appModules/splcreator.py b/addon/appModules/splcreator.py
new file mode 100755
index 0000000..31b99da
--- /dev/null
+++ b/addon/appModules/splcreator.py
@@ -0,0 +1,15 @@
+# StationPlaylist Creator
+# An app module and global plugin package for NVDA
+# Copyright 2016-2017 Joseph Lee and others, released under GPL.
+
+# Basic support for StationPlaylist Creator.
+
+import appModuleHandler
+
+
+class AppModule(appModuleHandler.AppModule):
+
+ def chooseNVDAObjectOverlayClasses(self, obj, clsList):
+ if obj.windowClassName in ("TDemoRegForm", "TAboutForm"):
+ from NVDAObjects.behaviors import Dialog
+ clsList.insert(0, Dialog)
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index d15c3d0..634a8f5 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -8,19 +8,15 @@
# Additional work done by Joseph Lee and other contributors.
# For SPL Studio Controller, focus movement, SAM Encoder support and other
utilities, see the global plugin version of this app module.
-# Minimum version: SPL 5.00, NvDA 2015.3.
+# Minimum version: SPL 5.10, NvDA 2016.4.
-from functools import wraps
import os
import time
import threading
import controlTypes
import appModuleHandler
import api
-import review
-import eventHandler
import scriptHandler
-import queueHandler
import ui
import nvwave
import speech
@@ -40,23 +36,10 @@ import splmisc
import splupdate
import addonHandler
addonHandler.initTranslation()
-
-
-# The finally function for status announcement scripts in this module (source:
Tyler Spivey's code).
-def finally_(func, final):
- """Calls final after func, even if it fails."""
- def wrap(f):
- @wraps(f)
- def new(*args, **kwargs):
- try:
- func(*args, **kwargs)
- finally:
- final()
- return new
- return wrap(final)
+from spldebugging import debugOutput
# Make sure the broadcaster is running a compatible version.
-SPLMinVersion = "5.00"
+SPLMinVersion = "5.10"
# Cache the handle to main Studio window.
_SPLWin = None
@@ -93,21 +76,36 @@ def micAlarmManager(micAlarmWav, micAlarmMessage):
micAlarmT2 = wx.PyTimer(_micAlarmAnnouncer)
micAlarmT2.Start(splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarmInterval"] *
1000)
-# Call SPL API to obtain needed values.
+# Use SPL Studio API to obtain needed values.
# A thin wrapper around user32.SendMessage and calling a callback if defined.
# Offset is used in some time commands.
-def statusAPI(arg, command, func=None, ret=False, offset=None):
- if _SPLWin is None: return
+# If debugging framework is on, print arg, command and other values.
+def studioAPI(arg, command, func=None, ret=False, offset=None):
+ if _SPLWin is None:
+ debugOutput("SPL: Studio handle not found")
+ return
+ debugOutput("SPL: Studio API wParem is %s, lParem is %s"%(arg, command))
val = sendMessage(_SPLWin, 1024, arg, command)
+ debugOutput("SPL: Studio API result is %s"%val)
if ret:
return val
if func:
func(val) if not offset else func(val, offset)
+# Check if Studio itself is running.
+# This is to make sure custom commands for SPL Assistant comamnds and other
app module gestures display appropriate error messages.
+def studioIsRunning():
+ if _SPLWin is None:
+ debugOutput("SPL: Studio handle not found")
+ # Translators: A message informing users that Studio is not
running so certain commands will not work.
+ ui.message(_("Studio main window not found"))
+ return False
+ return True
+
# Select a track upon request.
def selectTrack(trackIndex):
- statusAPI(-1, 121)
- statusAPI(trackIndex, 121)
+ studioAPI(-1, 121)
+ studioAPI(trackIndex, 121)
# Category sounds dictionary (key = category, value = tone pitch).
_SPLCategoryTones = {
@@ -118,13 +116,12 @@ _SPLCategoryTones = {
# Routines for track items themselves (prepare for future work).
class SPLTrackItem(IAccessible):
- """Track item for earlier versions of Studio such as 5.00.
- A base class for providing utility scripts when track entries are
focused, such as track dial."""
+ """A base class for providing utility scripts when track entries are
focused, such as location text and column navigation."""
+
+ # Keep a record of which column is being looked at.
+ _curColumnNumber = None
def initOverlayClass(self):
- if splconfig.SPLConfig["General"]["TrackDial"]:
- self.bindGesture("kb:rightArrow", "nextColumn")
- self.bindGesture("kb:leftArrow", "prevColumn")
# LTS: Take a greater role in assigning enhanced Columns
Explorer command at the expense of limiting where this can be invoked.
# 8.0: Just assign number row.
for i in xrange(10):
@@ -134,19 +131,20 @@ class SPLTrackItem(IAccessible):
# This is response to a situation where columns were rearranged yet
testing shows in-memory arrangement remains the same.
# Subclasses must provide this function.
def _origIndexOf(self, columnHeader):
- return
splconfig._SPLDefaults7["General"]["ExploreColumns"].index(columnHeader)
+ return None
# Read selected columns.
# But first, find where the requested column lives.
# 8.0: Make this a public function.
def indexOf(self, columnHeader):
- # Handle both 5.0x and 5.10 column headers.
try:
return self._origIndexOf(columnHeader)
except ValueError:
return None
def reportFocus(self):
+ # initialize column navigation tracker.
+ if self.__class__._curColumnNumber is None:
self.__class__._curColumnNumber = 0
# 7.0: Cache column header data structures if meeting track
items for the first time.
# It is better to do it while reporting focus, otherwise Python
throws recursion limit exceeded error when initOverlayClass does this.
if self.appModule._columnHeaders is None:
@@ -162,71 +160,39 @@ class SPLTrackItem(IAccessible):
if splconfig.SPLConfig["General"]["TrackCommentAnnounce"] !=
"off":
self.announceTrackComment(0)
# 6.3: Catch an unusual case where screen order is off yet
column order is same as screen order and NvDA is told to announce all columns.
+ # 17.04: Even if vertical column commands are performed, build
description pieces for consistency.
if splconfig._shouldBuildDescriptionPieces():
descriptionPieces = []
+ columnsToInclude =
splconfig.SPLConfig["ColumnAnnouncement"]["IncludedColumns"]
for header in
splconfig.SPLConfig["ColumnAnnouncement"]["ColumnOrder"]:
- # Artist field should not be included in Studio
5.0x, as the checkbox serves this role.
- if header == "Artist" and
self.appModule.productVersion.startswith("5.0"):
- continue
- if header in
splconfig.SPLConfig["ColumnAnnouncement"]["IncludedColumns"]:
+ if header in columnsToInclude:
index = self.indexOf(header)
if index is None: continue # Header not
found, mostly encountered in Studio 5.0x.
content = self._getColumnContent(index)
if content:
descriptionPieces.append("%s:
%s"%(header, content))
self.description = ", ".join(descriptionPieces)
- super(IAccessible, self).reportFocus()
+ if self.appModule._announceColumnOnly is None:
+ super(IAccessible, self).reportFocus()
+ else:
+ self.appModule._announceColumnOnly = None
+ verticalColumnAnnounce =
splconfig.SPLConfig["General"]["VerticalColumnAnnounce"]
+ if verticalColumnAnnounce == "Status" or
(verticalColumnAnnounce is None and self._curColumnNumber == 0):
+ self._leftmostcol()
+ else:
+
self.announceColumnContent(self._curColumnNumber if verticalColumnAnnounce is
None else self.indexOf(verticalColumnAnnounce), header=verticalColumnAnnounce,
reportStatus=self.name is not None)
# 7.0: Let the app module keep a reference to this track.
self.appModule._focusedTrack = self
- # Track Dial: using arrow keys to move through columns.
- # This is similar to enhanced arrow keys in other screen readers.
-
- def script_toggleTrackDial(self, gesture):
- if not splconfig.SPLConfig["General"]["TrackDial"]:
- splconfig.SPLConfig["General"]["TrackDial"] = True
- self.bindGesture("kb:rightArrow", "nextColumn")
- self.bindGesture("kb:leftArrow", "prevColumn")
- # Translators: Reported when track dial is on.
- dialText = _("Track Dial on")
- if self.appModule.SPLColNumber > 0:
- # Translators: Announced when located on a
column other than the leftmost column while using track dial.
- dialText+= _(", located at column
{columnHeader}").format(columnHeader = self.appModule.SPLColNumber+1)
- dialTone = 780
- else:
- splconfig.SPLConfig["General"]["TrackDial"] = False
- try:
- self.removeGestureBinding("kb:rightArrow")
- self.removeGestureBinding("kb:leftArrow")
- except KeyError:
- pass
- # Translators: Reported when track dial is off.
- dialText = _("Track Dial off")
- dialTone = 390
- if not splconfig.SPLConfig["General"]["BeepAnnounce"]:
- ui.message(dialText)
- else:
- tones.beep(dialTone, 100)
- braille.handler.message(dialText)
- if splconfig.SPLConfig["General"]["TrackDial"] and
self.appModule.SPLColNumber > 0:
- # Translators: Spoken when enabling track dial
while status message is set to beeps.
- speech.speakMessage(_("Column
{columnNumber}").format(columnNumber = self.appModule.SPLColNumber+1))
- # Translators: Input help mode message for SPL track item.
- script_toggleTrackDial.__doc__=_("Toggles track dial on and off.")
- script_toggleTrackDial.category = _("StationPlaylist Studio")
+ # A friendly way to report track position via location text.
+ """def _get_locationText(self):
+ # Translators: location text for a playlist item (example: item
1 of 10).
+ return _("Item {current} of {total}").format(current =
self.IAccessibleChildID, total = studioAPI(0, 124, ret=True))"""
# Some helper functions to handle corner cases.
# Each track item provides its own version.
def _leftmostcol(self):
- if self.appModule._columnHeaders is None:
- self.appModule._columnHeaders = self.parent.children[-1]
- leftmost = self.appModule._columnHeaders.firstChild.name
- if not self.name or self.name == "":
- # Translators: Announced when leftmost column has no
text while track dial is active.
- ui.message(_("{leftmostColumn} not
found").format(leftmostColumn = leftmost))
- else:
- # Translators: Standard message for announcing column
content.
- ui.message(_("{leftmostColumn}:
{leftmostContent}").format(leftmostColumn = leftmost, leftmostContent =
self.name))
+ pass
# Locate column content.
# This is merely the proxy of the module level function defined in the
misc module.
@@ -235,44 +201,46 @@ class SPLTrackItem(IAccessible):
# Announce column content if any.
# 7.0: Add an optional header in order to announce correct header
information in columns explorer.
- def announceColumnContent(self, colNumber, header=None):
+ # 17.04: Allow checked status in 5.1x and later to be announced if this
is such a case (vertical column navigation).)
+ def announceColumnContent(self, colNumber, header=None,
reportStatus=False):
columnHeader = header if header is not None else
self.appModule._columnHeaderNames[colNumber]
columnContent =
self._getColumnContent(self.indexOf(columnHeader))
+ status = self.name + " " if reportStatus else ""
if columnContent:
# Translators: Standard message for announcing column
content.
- ui.message(unicode(_("{header}:
{content}")).format(header = columnHeader, content = columnContent))
+ ui.message(unicode(_("{checkStatus}{header}:
{content}")).format(checkStatus = status, header = columnHeader, content =
columnContent))
else:
# Translators: Spoken when column content is blank.
- speech.speakMessage(_("{header}: blank").format(header
= columnHeader))
+ speech.speakMessage(_("{checkStatus}{header}:
blank").format(checkStatus = status, header = columnHeader))
# Translators: Brailled to indicate empty column
content.
- braille.handler.message(_("{header}: ()").format(header
= columnHeader))
+ braille.handler.message(_("{checkStatus}{header}:
()").format(checkStatus = status, header = columnHeader))
# Now the scripts.
def script_nextColumn(self, gesture):
- if (self.appModule.SPLColNumber+1) ==
self.appModule._columnHeaders.childCount:
+ if (self._curColumnNumber+1) ==
self.appModule._columnHeaders.childCount:
tones.beep(2000, 100)
else:
- self.appModule.SPLColNumber +=1
- self.announceColumnContent(self.appModule.SPLColNumber)
+ self.__class__._curColumnNumber +=1
+ self.announceColumnContent(self._curColumnNumber)
def script_prevColumn(self, gesture):
- if self.appModule.SPLColNumber <= 0:
+ if self._curColumnNumber <= 0:
tones.beep(2000, 100)
else:
- self.appModule.SPLColNumber -=1
- if self.appModule.SPLColNumber == 0:
+ self.__class__._curColumnNumber -=1
+ if self._curColumnNumber == 0:
self._leftmostcol()
else:
- self.announceColumnContent(self.appModule.SPLColNumber)
+ self.announceColumnContent(self._curColumnNumber)
def script_firstColumn(self, gesture):
- self.appModule.SPLColNumber = 0
+ self.__class__._curColumnNumber = 0
self._leftmostcol()
def script_lastColumn(self, gesture):
- self.appModule.SPLColNumber =
self.appModule._columnHeaders.childCount-1
- self.announceColumnContent(self.appModule.SPLColNumber)
+ self.__class__._curColumnNumber =
self.appModule._columnHeaders.childCount-1
+ self.announceColumnContent(self._curColumnNumber)
# Track movement scripts.
# Detects top/bottom of a playlist if told to do so.
@@ -287,7 +255,29 @@ class SPLTrackItem(IAccessible):
if self.IAccessibleChildID == 1 and
splconfig.SPLConfig["General"]["TopBottomAnnounce"]:
tones.beep(2000, 100)
- # Overlay class version of Columns Explorer.
+ # Vertical column navigation.
+
+ def script_nextRowColumn(self, gesture):
+ newTrack = self.next
+ if newTrack is None and
splconfig.SPLConfig["General"]["TopBottomAnnounce"]:
+ tones.beep(2000, 100)
+ else:
+ self.appModule._announceColumnOnly = True
+ newTrack._curColumnNumber = self._curColumnNumber
+ newTrack.setFocus(), newTrack.setFocus()
+ selectTrack(newTrack.IAccessibleChildID-1)
+
+ def script_prevRowColumn(self, gesture):
+ newTrack = self.previous
+ if newTrack is None and
splconfig.SPLConfig["General"]["TopBottomAnnounce"]:
+ tones.beep(2000, 100)
+ else:
+ self.appModule._announceColumnOnly = True
+ newTrack._curColumnNumber = self._curColumnNumber
+ newTrack.setFocus(), newTrack.setFocus()
+ selectTrack(newTrack.IAccessibleChildID-1)
+
+ # Overlay class version of Columns Explorer.
def script_columnExplorer(self, gesture):
# LTS: Just in case Control+NVDA+number row command is
pressed...
@@ -355,9 +345,10 @@ class SPLTrackItem(IAccessible):
__gestures={
"kb:control+alt+rightArrow":"nextColumn",
"kb:control+alt+leftArrow":"prevColumn",
+ "kb:control+alt+downArrow":"nextRowColumn",
+ "kb:control+alt+upArrow":"prevRowColumn",
"kb:control+alt+home":"firstColumn",
"kb:control+alt+end":"lastColumn",
- #"kb:control+`":"toggleTrackDial",
"kb:downArrow":"nextTrack",
"kb:upArrow":"prevTrack",
"kb:Alt+NVDA+C":"announceTrackComment"
@@ -378,9 +369,9 @@ class SPL510TrackItem(SPLTrackItem):
# Studio 5.10 version of original index finder.
def _origIndexOf(self, columnHeader):
- return
splconfig._SPLDefaults7["ColumnAnnouncement"]["ColumnOrder"].index(columnHeader)+1
+ return
splconfig._SPLDefaults["ColumnAnnouncement"]["ColumnOrder"].index(columnHeader)+1
- # Handle track dial for SPL 5.10.
+ # Handle column announcement for SPL 5.10.
def _leftmostcol(self):
if not self.name:
# Translators: Presented when no track status is found
in Studio 5.10.
@@ -417,7 +408,8 @@ T: Cart edit/insert mode.
U: Studio up time.
W: Weather and temperature.
Y: Playlist modification.
-1 through 0 (6 for Studio 5.01 and earlier): Announce columns via Columns
Explorer (0 is tenth column slot).
+1 through 0: Announce columns via Columns Explorer (0 is tenth column slot).
+F8: Take playlist snapshots such as track count, longest track and so on.
F9: Mark current track as start of track time analysis.
F10: Perform track time analysis.
F12: Switch to an instant switch profile.
@@ -449,7 +441,8 @@ T: Cart edit/insert mode.
U: Studio up time.
W: Weather and temperature.
Y: Playlist modification.
-1 through 0 (6 for Studio 5.01 and earlier): Announce columns via Columns
Explorer (0 is tenth column slot).
+1 through 0: Announce columns via Columns Explorer (0 is tenth column slot).
+F8: Take playlist snapshots such as track count, longest track and so on.
F9: Mark current track as start of track time analysis.
F10: Perform track time analysis.
F12: Switch to an instant switch profile.
@@ -483,7 +476,8 @@ T: Cart edit/insert mode.
U: Studio up time.
W: Weather and temperature.
Y: Playlist modification.
-1 through 0 (6 for Studio 5.01 and earlier): Announce columns via Columns
Explorer (0 is tenth column slot).
+1 through 0: Announce columns via Columns Explorer (0 is tenth column slot).
+F8: Take playlist snapshots such as track count, longest track and so on.
F9: Mark current track as start of track time analysis.
F10: Perform track time analysis.
F12: Switch to an instant switch profile.
@@ -568,6 +562,7 @@ class AppModule(appModuleHandler.AppModule):
_columnHeaders = None
_columnHeaderNames = None
_focusedTrack = None
+ _announceColumnOnly = None # Used only if vertical column navigation
commands are used.
# Prepare the settings dialog among other things.
def __init__(self, *args, **kwargs):
@@ -579,10 +574,12 @@ class AppModule(appModuleHandler.AppModule):
ui.message(_("Using SPL Studio version
{SPLVersion}").format(SPLVersion = self.SPLCurVersion))
except IOError, AttributeError:
pass
+ debugOutput("SPL: loading add-on settings")
splconfig.initConfig()
# Announce status changes while using other programs.
# This requires NVDA core support and will be available in 6.0
and later (cannot be ported to earlier versions).
# For now, handle all background events, but in the end, make
this configurable.
+ import eventHandler
if hasattr(eventHandler, "requestEvents"):
eventHandler.requestEvents(eventName="nameChange",
processId=self.processID, windowClassName="TStatusBar")
eventHandler.requestEvents(eventName="nameChange",
processId=self.processID, windowClassName="TStaticText")
@@ -599,10 +596,12 @@ class AppModule(appModuleHandler.AppModule):
# Check for add-on update if told to do so.
# LTS: Only do this if channel hasn't changed.
if splconfig.SPLConfig["Update"]["AutoUpdateCheck"] or
splupdate._updateNow:
+ debugOutput("SPL: checking for add-on updates from %s
channel"%splupdate.SPLUpdateChannel)
# 7.0: Have a timer call the update function indirectly.
+ import queueHandler
queueHandler.queueFunction(queueHandler.eventQueue,
splconfig.updateInit)
# Display startup dialogs if any.
- wx.CallAfter(splconfig.showStartupDialogs,
oldVer=self.SPLCurVersion < "5.10")
+ wx.CallAfter(splconfig.showStartupDialogs)
# Locate the handle for main window for caching purposes.
def _locateSPLHwnd(self):
@@ -637,6 +636,7 @@ class AppModule(appModuleHandler.AppModule):
obj.role = controlTypes.ROLE_GROUPING
# In certain edit fields and combo boxes, the field name is
written to the screen, and there's no way to fetch the object for this text.
Thus use review position text.
elif obj.windowClassName in ("TEdit", "TComboBox") and not
obj.name:
+ import review
fieldName, fieldObj = review.getScreenPosition(obj)
fieldName.expand(textInfos.UNIT_LINE)
if obj.windowClassName == "TComboBox":
@@ -650,8 +650,6 @@ class AppModule(appModuleHandler.AppModule):
windowStyle = obj.windowStyle
if obj.windowClassName == "TTntListView.UnicodeClass" and role
== controlTypes.ROLE_LISTITEM and abs(windowStyle - 1443991625)%0x100000 == 0:
clsList.insert(0, SPL510TrackItem)
- elif obj.windowClassName == "TListView" and role ==
controlTypes.ROLE_CHECKBOX and abs(windowStyle - 1442938953)%0x100000 == 0:
- clsList.insert(0, SPLTrackItem)
# 7.2: Recognize known dialogs.
elif obj.windowClassName in ("TDemoRegForm", "TOpenPlaylist"):
clsList.insert(0, Dialog)
@@ -662,10 +660,8 @@ class AppModule(appModuleHandler.AppModule):
# Keep an eye on library scans in insert tracks window.
libraryScanning = False
scanCount = 0
- # For 5.0X and earlier: prevent NVDA from announcing scheduled time
multiple times.
+ # Prevent NVDA from announcing scheduled time multiple times.
scheduledTimeCache = ""
- # Track Dial (A.K.A. enhanced arrow keys)
- SPLColNumber = 0
# Automatically announce mic, line in, etc changes
# These items are static text items whose name changes.
@@ -705,8 +701,7 @@ class AppModule(appModuleHandler.AppModule):
if self.scanCount%100 == 0:
self._libraryScanAnnouncer(obj.name[1:obj.name.find("]")],
splconfig.SPLConfig["General"]["LibraryScanAnnounce"])
if not self.libraryScanning:
- if self.productVersion not in
noLibScanMonitor:
- if not
self.backgroundStatusMonitor: self.libraryScanning = True
+ if self.productVersion not in
noLibScanMonitor: self.libraryScanning = True
elif "match" in obj.name:
if
splconfig.SPLConfig["General"]["LibraryScanAnnounce"] != "off" and
self.libraryScanning:
if
splconfig.SPLConfig["General"]["BeepAnnounce"]: tones.beep(370, 100)
@@ -721,7 +716,7 @@ class AppModule(appModuleHandler.AppModule):
self._toggleMessage(obj.name)
else:
ui.message(obj.name)
- if self.cartExplorer or
int(splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarm"]):
+ if self.cartExplorer or
splconfig.SPLConfig["MicrophoneAlarm"]["MicAlarm"]:
# Activate mic alarm or announce when
cart explorer is active.
self.doExtraAction(obj.name)
# Monitor the end of track and song intro time and announce it.
@@ -790,9 +785,7 @@ class AppModule(appModuleHandler.AppModule):
# 17.01: The best way to detect Cart Edit off is
consulting file modification time.
# Automatically reload cart information if this is the
case.
if status in ("Cart Edit Off", "Cart Insert On"):
- studioTitle = api.getForegroundObject().name
- if
splmisc.shouldCartExplorerRefresh(studioTitle):
- self.carts =
splmisc.cartExplorerInit(studioTitle)
+ self.carts =
splmisc.cartExplorerRefresh(api.getForegroundObject().name, self.carts)
# Translators: Presented when cart modes are toggled
while cart explorer is on.
ui.message(_("Cart explorer is active"))
return
@@ -863,17 +856,20 @@ class AppModule(appModuleHandler.AppModule):
except KeyError:
pass
-
# Save configuration when terminating.
def terminate(self):
super(AppModule, self).terminate()
+ debugOutput("SPL: terminating app module")
# 6.3: Memory leak results if encoder flag sets and other
encoder support maps aren't cleaned up.
# This also could have allowed a hacker to modify the flags set
(highly unlikely) so NvDA could get confused next time Studio loads.
import sys
- if "globalPlugins.SPLStudioUtils.encoders" in sys.modules:
- import globalPlugins.SPLStudioUtils.encoders
- globalPlugins.SPLStudioUtils.encoders.cleanup()
+ if "globalPlugins.splUtils.encoders" in sys.modules:
+ import globalPlugins.splUtils.encoders
+ globalPlugins.splUtils.encoders.cleanup()
+ debugOutput("SPL: saving add-on settings")
splconfig.saveConfig()
+ # reset column number for column navigation commands.
+ if self._focusedTrack:
self._focusedTrack.__class__._curColumnNumber = None
# Delete focused track reference.
self._focusedTrack = None
try:
@@ -891,7 +887,6 @@ class AppModule(appModuleHandler.AppModule):
global _SPLWin
if _SPLWin: _SPLWin = None
-
# Script sections (for ease of maintenance):
# Time-related: elapsed time, end of track alarm, etc.
# Misc scripts: track finder and others.
@@ -934,16 +929,17 @@ class AppModule(appModuleHandler.AppModule):
# Scripts which rely on API.
def script_sayRemainingTime(self, gesture):
- statusAPI(3, 105, self.announceTime, offset=1)
+ if studioIsRunning(): studioAPI(3, 105, self.announceTime,
offset=1)
# Translators: Input help mode message for a command in Station
Playlist Studio.
script_sayRemainingTime.__doc__=_("Announces the remaining track time.")
def script_sayElapsedTime(self, gesture):
- statusAPI(0, 105, self.announceTime)
+ if studioIsRunning(): studioAPI(0, 105, self.announceTime)
# Translators: Input help mode message for a command in Station
Playlist Studio.
script_sayElapsedTime.__doc__=_("Announces the elapsed time for the
currently playing track.")
def script_sayBroadcasterTime(self, gesture):
+ if not studioIsRunning(): return
# Says things such as "25 minutes to 2" and "5 past 11".
# Parse the local time and say it similar to how Studio
presents broadcaster time.
h, m = time.localtime()[3], time.localtime()[4]
@@ -965,6 +961,7 @@ class AppModule(appModuleHandler.AppModule):
script_sayBroadcasterTime.__doc__=_("Announces broadcaster time.")
def script_sayCompleteTime(self, gesture):
+ if not studioIsRunning(): return
import winKernel
# Says complete time in hours, minutes and seconds via
kernel32's routines.
ui.message(winKernel.GetTimeFormat(winKernel.LOCALE_USER_DEFAULT, 0, None,
None))
@@ -981,12 +978,12 @@ 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:
- d = splconfig.SPLAlarmDialog(gui.mainFrame, level)
+ d = splconfui.AlarmsCenter(gui.mainFrame, level)
gui.mainFrame.prePopup()
d.Raise()
d.Show()
gui.mainFrame.postPopup()
- splconfig._alarmDialogOpened = True
+ splconfui._alarmDialogOpened = True
except RuntimeError:
wx.CallAfter(splconfig._alarmError)
@@ -995,14 +992,14 @@ class AppModule(appModuleHandler.AppModule):
def script_setEndOfTrackTime(self, gesture):
self.alarmDialog(1)
# Translators: Input help mode message for a command in Station
Playlist Studio.
- script_setEndOfTrackTime.__doc__=_("sets end of track alarm (default is
5 seconds).")
+ script_setEndOfTrackTime.__doc__=_("Sets end of track alarm (default is
5 seconds).")
# Set song ramp (introduction) time between 1 and 9 seconds.
def script_setSongRampTime(self, gesture):
self.alarmDialog(2)
# Translators: Input help mode message for a command in Station
Playlist Studio.
- script_setSongRampTime.__doc__=_("sets song intro alarm (default is 5
seconds).")
+ script_setSongRampTime.__doc__=_("Sets song intro alarm (default is 5
seconds).")
# Tell NVDA to play a sound when mic was active for a long time, as
well as contorl the alarm interval.
# 8.0: This dialog will let users configure mic alarm interval as well.
@@ -1028,14 +1025,6 @@ class AppModule(appModuleHandler.AppModule):
# Other commands (track finder and others)
- # Toggle whether beeps should be heard instead of toggle announcements.
- # Deprecated in 8.0, may come back later.
-
- #def script_toggleBeepAnnounce(self, gesture):
- #splconfig.SPLConfig["General"]["BeepAnnounce"] = not
splconfig.SPLConfig["General"]["BeepAnnounce"]
- #splconfig.message("BeepAnnounce",
splconfig.SPLConfig["General"]["BeepAnnounce"])
- #script_toggleBeepAnnounce.__doc__=_("Toggles status announcements
between words and beeps.")
-
# Braille timer.
# Announce end of track and other info via braille.
@@ -1072,8 +1061,7 @@ class AppModule(appModuleHandler.AppModule):
# 16.10.1/15.2 LTS: Just select this track in order to
prevent a dispute between NVDA and SPL in regards to focused track.
# 16.11: Call setFocus if it is post-5.01, as SPL API
can be used to select the desired track.
selectTrack(track.IAccessibleChildID-1)
- if self.productVersion >= "5.10":
- track.setFocus(), track.setFocus()
+ track.setFocus(), track.setFocus()
else:
wx.CallAfter(gui.messageBox,
# Translators: Standard dialog message when an item one
wishes to search is not found (copy this from main nvda.po).
@@ -1101,6 +1089,7 @@ class AppModule(appModuleHandler.AppModule):
# But first, check if track finder can be invoked.
# Attempt level specifies which track finder to open (0 = Track Finder,
1 = Column Search, 2 = Time range).
def _trackFinderCheck(self, attemptLevel):
+ if not studioIsRunning(): return False
if api.getForegroundObject().windowClassName != "TStudioForm":
if attemptLevel == 0:
# Translators: Presented when a user attempts
to find tracks but is not at the track list.
@@ -1165,7 +1154,7 @@ class AppModule(appModuleHandler.AppModule):
def script_timeRangeFinder(self, gesture):
if self._trackFinderCheck(2):
try:
- d = splmisc.SPLTimeRangeDialog(gui.mainFrame,
api.getFocusObject(), statusAPI)
+ d = splmisc.SPLTimeRangeDialog(gui.mainFrame,
api.getFocusObject(), studioAPI)
gui.mainFrame.prePopup()
d.Raise()
d.Show()
@@ -1212,6 +1201,7 @@ class AppModule(appModuleHandler.AppModule):
self.bindGestures(self.__gestures)
def script_toggleCartExplorer(self, gesture):
+ if not studioIsRunning(): return
if not self.cartExplorer:
# Prevent cart explorer from being engaged outside of
playlist viewer.
# Todo for 6.0: Let users set cart banks.
@@ -1234,6 +1224,7 @@ class AppModule(appModuleHandler.AppModule):
self.cartExplorer = False
self.cartsBuilder(build=False)
self.carts.clear()
+ splmisc._cartEditTimestamps = None
# Translators: Presented when cart explorer is off.
ui.message(_("Exiting cart explorer"))
# Translators: Input help mode message for a command in Station
Playlist Studio.
@@ -1284,41 +1275,40 @@ class AppModule(appModuleHandler.AppModule):
global libScanT
if libScanT and libScanT.isAlive() and
api.getForegroundObject().windowClassName == "TTrackInsertForm":
return
- parem = 0 if self.SPLCurVersion < "5.10" else 1
- countA = statusAPI(parem, 32, ret=True)
- if countA == 0:
+ if studioAPI(1, 32, ret=True) < 0:
self.libraryScanning = False
return
time.sleep(0.1)
if api.getForegroundObject().windowClassName ==
"TTrackInsertForm" and self.productVersion in noLibScanMonitor:
self.libraryScanning = False
return
- countB = statusAPI(parem, 32, ret=True)
- if countA == countB:
+ # 17.04: Library scan may have finished while this thread was
sleeping.
+ if studioAPI(1, 32, ret=True) < 0:
self.libraryScanning = False
- if self.SPLCurVersion >= "5.10":
- countB = statusAPI(0, 32, ret=True)
# Translators: Presented when library scanning is
finished.
- ui.message(_("{itemCount} items in the
library").format(itemCount = countB))
+ ui.message(_("{itemCount} items in the
library").format(itemCount = studioAPI(0, 32, ret=True)))
else:
- libScanT =
threading.Thread(target=self.libraryScanReporter, args=(_SPLWin, countA,
countB, parem))
+ libScanT =
threading.Thread(target=self.libraryScanReporter)
libScanT.daemon = True
libScanT.start()
- def libraryScanReporter(self, _SPLWin, countA, countB, parem):
+ def libraryScanReporter(self):
scanIter = 0
- while countA != countB:
+ # 17.04: Use the constant directly, as 5.10 and later provides
a convenient method to detect completion of library scans.
+ scanCount = studioAPI(1, 32, ret=True)
+ while scanCount >= 0:
if not self.libraryScanning: return
- countA = countB
time.sleep(1)
# Do not continue if we're back on insert tracks form
or library scan is finished.
if api.getForegroundObject().windowClassName ==
"TTrackInsertForm" or not self.libraryScanning:
return
- countB, scanIter = statusAPI(parem, 32, ret=True),
scanIter+1
- if countB < 0:
+ # Scan count may have changed during sleep.
+ scanCount = studioAPI(1, 32, ret=True)
+ if scanCount < 0:
break
+ scanIter+=1
if scanIter%5 == 0 and
splconfig.SPLConfig["General"]["LibraryScanAnnounce"] not in ("off", "ending"):
- self._libraryScanAnnouncer(countB,
splconfig.SPLConfig["General"]["LibraryScanAnnounce"])
+ self._libraryScanAnnouncer(scanCount,
splconfig.SPLConfig["General"]["LibraryScanAnnounce"])
self.libraryScanning = False
if self.backgroundStatusMonitor: return
if splconfig.SPLConfig["General"]["LibraryScanAnnounce"] !=
"off":
@@ -1326,7 +1316,7 @@ class AppModule(appModuleHandler.AppModule):
tones.beep(370, 100)
else:
# Translators: Presented after library scan is
done.
- ui.message(_("Scan complete with {itemCount}
items").format(itemCount = countB))
+ ui.message(_("Scan complete with {itemCount}
items").format(itemCount = studioAPI(0, 32, ret=True)))
# Take care of library scanning announcement.
def _libraryScanAnnouncer(self, count, announcementType):
@@ -1371,7 +1361,7 @@ class AppModule(appModuleHandler.AppModule):
def script_manageMetadataStreams(self, gesture):
# Do not even think about opening this dialog if handle to
Studio isn't found.
if _SPLWin is None:
- # Translators: Presented when stremaing dialog cannot
be shown.
+ # Translators: Presented when streaming dialog cannot
be shown.
ui.message(_("Cannot open metadata streaming dialog"))
return
if splconfui._configDialogOpened or
splconfui._metadataDialogOpened:
@@ -1380,7 +1370,7 @@ class AppModule(appModuleHandler.AppModule):
return
try:
# Passing in the function object is enough to change
the dialog UI.
- d = splconfui.MetadataStreamingDialog(gui.mainFrame,
func=statusAPI)
+ d = splconfui.MetadataStreamingDialog(gui.mainFrame,
func=studioAPI)
gui.mainFrame.prePopup()
d.Raise()
d.Show()
@@ -1391,54 +1381,197 @@ class AppModule(appModuleHandler.AppModule):
# Translators: Input help mode message for a command in Station
Playlist Studio.
script_manageMetadataStreams.__doc__=_("Opens a dialog to quickly
enable or disable metadata streaming.")
- # Track time analysis
+ # Track time analysis/Playlist snapshots
# Return total length of the selected tracks upon request.
# Analysis command (SPL Assistant) will be assignable.
+ # Also gather various data about the playlist.
_analysisMarker = None
- # Trakc time analysis requires main playlist viewer to be the
foreground window.
+ # Trakc time analysis and playlist snapshots require main playlist
viewer to be the foreground window.
def _trackAnalysisAllowed(self):
+ if not studioIsRunning():
+ return False
if api.getForegroundObject().windowClassName != "TStudioForm":
# Translators: Presented when track time anlaysis
cannot be performed because user is not focused on playlist viewer.
- ui.message(_("Not in playlist viewer, cannot perform
track time analysis"))
+ ui.message(_("Not in playlist viewer, cannot perform
track time analysis or gather playlist snapshot statistics"))
return False
return True
# Return total duration of a range of tracks.
# This is used in track time analysis when multiple tracks are selected.
# This is also called from playlist duration scripts.
- def totalTime(self, start, end):
+ def playlistDuration(self, start=None, end=None):
+ if start is None: start = api.getFocusObject()
+ duration = start.indexOf("Duration")
+ totalDuration = 0
+ obj = start
+ while obj not in (None, end):
+ # Technically segue.
+ segue = obj._getColumnContent(duration)
+ if segue not in (None, "00:00"):
+ hms = segue.split(":")
+ totalDuration += (int(hms[-2])*60) +
int(hms[-1])
+ if len(hms) == 3: totalDuration +=
int(hms[0])*3600
+ obj = obj.next
+ return totalDuration
+
+ # Segue version of this will be used in some places (the below is the
raw duration).)
+ def playlistDurationRaw(self, start, end):
# Take care of errors such as the following.
- if start < 0 or end > statusAPI(0, 124, ret=True)-1:
+ if start < 0 or end > studioAPI(0, 124, ret=True)-1:
raise ValueError("Track range start or end position out
of range")
return
totalLength = 0
if start == end:
- filename = statusAPI(start, 211, ret=True)
- totalLength = statusAPI(filename, 30, ret=True)
+ filename = studioAPI(start, 211, ret=True)
+ totalLength = studioAPI(filename, 30, ret=True)
else:
for track in xrange(start, end+1):
- filename = statusAPI(track, 211, ret=True)
- totalLength+=statusAPI(filename, 30, ret=True)
+ filename = studioAPI(track, 211, ret=True)
+ totalLength+=studioAPI(filename, 30, ret=True)
return totalLength
+ # Playlist snapshots
+ # Data to be gathered comes from a set of flags.
+ # By default, playlist duration (including shortest and average),
category summary and other statistics will be gathered.
+ def playlistSnapshots(self, obj, end, snapshotFlags=None):
+ # Track count and total duration are always included.
+ snapshot = {}
+ if snapshotFlags is None:
+ snapshotFlags = [flag for flag in
splconfig.SPLConfig["PlaylistSnapshots"] if
splconfig.SPLConfig["PlaylistSnapshots"][flag]]
+ duration = obj.indexOf("Duration")
+ title = obj.indexOf("Title")
+ artist = obj.indexOf("Artist")
+ artists = []
+ min, max = None, None
+ minTitle, maxTitle = None, None
+ totalDuration = 0
+ category = obj.indexOf("Category")
+ categories = []
+ genre = obj.indexOf("Genre")
+ genres = []
+ # A specific version of the playlist duration loop is needed in
order to gather statistics.
+ while obj not in (None, end):
+ segue = obj._getColumnContent(duration)
+ trackTitle = obj._getColumnContent(title)
+ categories.append(obj._getColumnContent(category))
+ # Don't record artist and genre information for an hour
marker (reported by a broadcaster).
+ if categories[-1] != "Hour Marker":
+ artists.append(obj._getColumnContent(artist))
+ genres.append(obj._getColumnContent(genre))
+ # Shortest and longest tracks.
+ # #22: assign min to the first segue in order to not
forget title of the shortest track.
+ if segue and (min is None or segue < min):
+ min = segue
+ minTitle = trackTitle
+ if segue and segue > max:
+ max = segue
+ maxTitle = trackTitle
+ if segue not in (None, "00:00"):
+ hms = segue.split(":")
+ totalDuration += (int(hms[-2])*60) +
int(hms[-1])
+ if len(hms) == 3: totalDuration +=
int(hms[0])*3600
+ obj = obj.next
+ if end is None: snapshot["PlaylistItemCount"] = studioAPI(0,
124, ret=True)
+ snapshot["PlaylistTrackCount"] = len(artists)
+ snapshot["PlaylistDurationTotal"] =
self._ms2time(totalDuration, ms=False)
+ if "DurationMinMax" in snapshotFlags:
+ snapshot["PlaylistDurationMin"] = "%s (%s)"%(minTitle,
min)
+ snapshot["PlaylistDurationMax"] = "%s (%s)"%(maxTitle,
max)
+ if "DurationAverage" in snapshotFlags:
+ snapshot["PlaylistDurationAverage"] =
self._ms2time(totalDuration/snapshot["PlaylistTrackCount"], ms=False)
+ if "CategoryCount" in snapshotFlags or "ArtistCount" in
snapshotFlags or "GenreCount" in snapshotFlags:
+ import collections
+ if "CategoryCount" in snapshotFlags:
snapshot["PlaylistCategoryCount"] = collections.Counter(categories)
+ if "ArtistCount" in snapshotFlags:
snapshot["PlaylistArtistCount"] = collections.Counter(artists)
+ if "GenreCount" in snapshotFlags:
snapshot["PlaylistGenreCount"] = collections.Counter(genres)
+ return snapshot
+
+# Output formatter for playlist snapshots.
+# Pressed once will speak and/or braille it, pressing twice or more will
output this info to an HTML file.
+ def playlistSnapshotOutput(self, snapshot, scriptCount):
+ # Translators: one of the results for playlist snapshots
feature for announcing total number of items in a playlist.
+ statusInfo = [_("Items:
{playlistItemCount}").format(playlistItemCount = snapshot["PlaylistItemCount"])]
+ # Translators: one of the results for playlist snapshots
feature for announcing total number of tracks in a playlist.
+ statusInfo.append(_("Tracks:
{playlistTrackCount}").format(playlistTrackCount =
snapshot["PlaylistTrackCount"]))
+ # Translators: one of the results for playlist snapshots
feature for announcing total duration of a playlist.
+ statusInfo.append(_("Duration:
{playlistTotalDuration}").format(playlistTotalDuration =
snapshot["PlaylistDurationTotal"]))
+ if "PlaylistDurationMin" in snapshot:
+ # Translators: one of the results for playlist
snapshots feature for announcing shortest track name and duration of a playlist.
+ statusInfo.append(_("Shortest:
{playlistShortestTrack}").format(playlistShortestTrack =
snapshot["PlaylistDurationMin"]))
+ # Translators: one of the results for playlist
snapshots feature for announcing longest track name and duration of a playlist.
+ statusInfo.append(_("Longest:
{playlistLongestTrack}").format(playlistLongestTrack =
snapshot["PlaylistDurationMax"]))
+ if "PlaylistDurationAverage" in snapshot:
+ # Translators: one of the results for playlist
snapshots feature for announcing average duration for tracks in a playlist.
+ statusInfo.append(_("Average:
{playlistAverageDuration}").format(playlistAverageDuration =
snapshot["PlaylistDurationAverage"]))
+ if "PlaylistArtistCount" in snapshot:
+ artistCount =
splconfig.SPLConfig["PlaylistSnapshots"]["ArtistCountLimit"]
+ artists =
snapshot["PlaylistArtistCount"].most_common(None if not artistCount else
artistCount)
+ if scriptCount == 0:
+ # Translators: one of the results for playlist
snapshots feature for announcing top artist in a playlist.
+ statusInfo.append(_("Top artist: %s
(%s)")%(artists[0][:]))
+ elif scriptCount == 1:
+ artistList = []
+ # Translators: one of the results for playlist
snapshots feature, a heading for a group of items.
+ header = _("Top artists:")
+ for item in artists:
+ artist, count = item
+ if artist is None:
+ info = _("No artist information
({artistCount})").format(artistCount = count)
+ else:
+ info = _("{artistName}
({artistCount})").format(artistName = artist, artistCount = count)
+ artistList.append("<li>%s</li>"%info)
+ statusInfo.append("".join([header, "<ol>",
"\n".join(artistList), "</ol>"]))
+ if "PlaylistCategoryCount" in snapshot:
+ categoryCount =
splconfig.SPLConfig["PlaylistSnapshots"]["CategoryCountLimit"]
+ categories =
snapshot["PlaylistCategoryCount"].most_common(None if not categoryCount else
categoryCount)
+ if scriptCount == 0:
+ # Translators: one of the results for playlist
snapshots feature for announcing top track category in a playlist.
+ statusInfo.append(_("Top category: %s
(%s)")%(categories[0][:]))
+ elif scriptCount == 1:
+ categoryList = []
+ # Translators: one of the results for playlist
snapshots feature, a heading for a group of items.
+ header = _("Categories:")
+ for item in categories:
+ category, count = item
+ category = category.replace("<", "")
+ category = category.replace(">", "")
+ info = _("{categoryName}
({categoryCount})").format(categoryName = category, categoryCount = count)
+ categoryList.append("<li>%s</li>"%info)
+ statusInfo.append("".join([header, "<ol>",
"\n".join(categoryList), "</ol>"]))
+ if "PlaylistGenreCount" in snapshot:
+ genreCount =
splconfig.SPLConfig["PlaylistSnapshots"]["GenreCountLimit"]
+ genres =
snapshot["PlaylistGenreCount"].most_common(None if not genreCount else
genreCount)
+ if scriptCount == 0:
+ # Translators: one of the results for playlist
snapshots feature for announcing top genre in a playlist.
+ statusInfo.append(_("Top genre: %s
(%s)")%(genres[0][:]))
+ elif scriptCount == 1:
+ genreList = []
+ # Translators: one of the results for playlist
snapshots feature, a heading for a group of items.
+ header = _("Top genres:")
+ for item in genres:
+ genre, count = item
+ if genre is None:
+ info = _("No genre information
({genreCount})").format(genreCount = count)
+ else:
+ info = _("{genreName}
({genreCount})").format(genreName = genre, genreCount = count)
+ genreList.append("<li>%s</li>"%info)
+ statusInfo.append("".join([header, "<ol>",
"\n".join(genreList), "</ol>"]))
+ if scriptCount == 0:
+ ui.message(", ".join(statusInfo))
+ else:
+ # Translators: The title of a window for displaying
playlist snapshots information.
+
ui.browseableMessage("<p>".join(statusInfo),title=_("Playlist snapshots"),
isHtml=True)
+
# Some handlers for native commands.
- # In Studio 5.0x, when deleting a track, NVDA announces wrong track
item due to focus bouncing.
+ # In Studio 5.0x, when deleting a track, NVDA announces wrong track
item due to focus bouncing (not the case in 5.10 and later).
# The below hack is sensitive to changes in NVDA core.
deletedFocusObj = False
def script_deleteTrack(self, gesture):
self.preTrackRemoval()
gesture.send()
- if self.productVersion.startswith("5.0"):
- if api.getForegroundObject().windowClassName ==
"TStudioForm":
- focus = api.getFocusObject()
- if focus.IAccessibleChildID <
focus.parent.childCount:
- self.deletedFocusObj = True
- focus.setFocus()
- self.deletedFocusObj = False
- focus.setFocus()
# When Escape is pressed, activate background library scan if
conditions are right.
def script_escape(self, gesture):
@@ -1452,7 +1585,6 @@ class AppModule(appModuleHandler.AppModule):
os.startfile("mailto:joseph.lee22590@xxxxxxxxx";)
script_sendFeedbackEmail.__doc__="Opens the default email client to
send an email to the add-on developer"
-
# SPL Assistant: reports status on playback, operation, etc.
# Used layer command approach to save gesture assignments.
# Most were borrowed from JFW and Window-Eyes layer scripts.
@@ -1463,8 +1595,10 @@ class AppModule(appModuleHandler.AppModule):
return appModuleHandler.AppModule.getScript(self,
gesture)
script = appModuleHandler.AppModule.getScript(self, gesture)
if not script:
- script = finally_(self.script_error, self.finish)
- return finally_(script, self.finish)
+ script = self.script_error
+ # Just use finally function from the global plugin to reduce
code duplication.
+ import globalPlugins.splUtils
+ return globalPlugins.splUtils.finally_(script, self.finish)
def finish(self):
self.SPLAssistant = False
@@ -1536,13 +1670,13 @@ class AppModule(appModuleHandler.AppModule):
# These are scattered throughout the screen, so one can use
foreground.getChild(index) to fetch them (getChild tip from Jamie Teh (NV
Access)).
# Because 5.x (an perhaps future releases) uses different screen
layout, look up the needed constant from the table below (row = info needed,
column = version).
statusObjs={
- SPLPlayStatus:[5, 6], # Play status, mic, etc.
- SPLSystemStatus:[-3, -2], # The second status bar containing
system status such as up time.
- SPLScheduledToPlay:[18, 19], # In case the user selects one or
more tracks in a given hour.
- SPLScheduled:[19, 20], # Time when the selected track will
begin.
- SPLNextTrackTitle:[7, 8], # Name and duration of the next track
if any.
- SPLCurrentTrackTitle:[8, 9], # Name of the currently playing
track.
- SPLTemperature:[6, 7], # Temperature for the current city.
+ SPLPlayStatus: 6, # Play status, mic, etc.
+ SPLSystemStatus: -2, # The second status bar containing system
status such as up time.
+ SPLScheduledToPlay: 19, # In case the user selects one or more
tracks in a given hour.
+ SPLScheduled: 20, # Time when the selected track will begin.
+ SPLNextTrackTitle: 8, # Name and duration of the next track if
any.
+ SPLCurrentTrackTitle: 9, # Name of the currently playing track.
+ SPLTemperature: 7, # Temperature for the current city.
}
_cachedStatusObjs = {}
@@ -1556,8 +1690,7 @@ class AppModule(appModuleHandler.AppModule):
if fg is not None and fg.windowClassName !=
"TStudioForm":
# 6.1: Allow gesture-based functions to look up
status information even if Studio window isn't focused.
fg =
getNVDAObjectFromEvent(user32.FindWindowA("TStudioForm", None), OBJID_CLIENT, 0)
- if not self.productVersion >= "5.10": statusObj =
self.statusObjs[infoIndex][0]
- else: statusObj = self.statusObjs[infoIndex][1]
+ statusObj = self.statusObjs[infoIndex]
# 7.0: sometimes (especially when first loaded),
OBJID_CLIENT fails, so resort to retrieving focused object instead.
if fg is not None and fg.childCount > 1:
self._cachedStatusObjs[infoIndex] =
fg.getChild(statusObj)
@@ -1578,7 +1711,7 @@ class AppModule(appModuleHandler.AppModule):
if self.SPLCurVersion < "5.20":
status =
self.status(self.SPLPlayStatus).getChild(index).name
else:
- status =
self._statusBarMessages[index][statusAPI(index, 39, ret=True)]
+ status =
self._statusBarMessages[index][studioAPI(index, 39, ret=True)]
ui.message(status if
splconfig.SPLConfig["General"]["MessageVerbosity"] == "beginner" else
status.split()[-1])
# The layer commands themselves.
@@ -1601,8 +1734,8 @@ class AppModule(appModuleHandler.AppModule):
def script_sayCartEditStatus(self, gesture):
# 16.12: Because cart edit status also shows cart insert
status, verbosity control will not apply.
if self.productVersion >= "5.20":
- cartEdit = statusAPI(5, 39, ret=True)
- cartInsert = statusAPI(6, 39, ret=True)
+ cartEdit = studioAPI(5, 39, ret=True)
+ cartInsert = studioAPI(6, 39, ret=True)
if cartEdit: ui.message("Cart Edit On")
elif not cartEdit and cartInsert: ui.message("Cart
Insert On")
else: ui.message("Cart Edit Off")
@@ -1610,11 +1743,11 @@ class AppModule(appModuleHandler.AppModule):
ui.message(self.status(self.SPLPlayStatus).getChild(5).name)
def script_sayHourTrackDuration(self, gesture):
- statusAPI(0, 27, self.announceTime)
+ studioAPI(0, 27, self.announceTime)
def script_sayHourRemaining(self, gesture):
# 7.0: Split from playlist remaining script (formerly the
playlist remainder command).
- statusAPI(1, 27, self.announceTime)
+ studioAPI(1, 27, self.announceTime)
def script_sayPlaylistRemainingDuration(self, gesture):
obj = api.getFocusObject() if
api.getForegroundObject().windowClassName == "TStudioForm" else
self._focusedTrack
@@ -1624,15 +1757,7 @@ class AppModule(appModuleHandler.AppModule):
if obj.role == controlTypes.ROLE_LIST:
ui.message("00:00")
return
- col = obj.indexOf("Duration")
- totalDuration = 0
- while obj is not None:
- segue = obj._getColumnContent(col)
- if segue is not None:
- hms = segue.split(":")
- totalDuration += (int(hms[0])*3600) +
(int(hms[1])*60) + int(hms[2]) if len(hms) == 3 else (int(hms[0])*60) +
int(hms[1])
- obj = obj.next
- self.announceTime(totalDuration, ms=False)
+ self.announceTime(self.playlistDuration(start=obj), ms=False)
def script_sayPlaylistModified(self, gesture):
try:
@@ -1643,6 +1768,9 @@ class AppModule(appModuleHandler.AppModule):
ui.message(_("Playlist modification not available"))
def script_sayNextTrackTitle(self, gesture):
+ if not studioIsRunning():
+ self.finish()
+ return
try:
obj = self.status(self.SPLNextTrackTitle).firstChild
# Translators: Presented when there is no information
for the next track.
@@ -1656,6 +1784,9 @@ class AppModule(appModuleHandler.AppModule):
script_sayNextTrackTitle.__doc__=_("Announces title of the next track
if any")
def script_sayCurrentTrackTitle(self, gesture):
+ if not studioIsRunning():
+ self.finish()
+ return
try:
obj = self.status(self.SPLCurrentTrackTitle).firstChild
# Translators: Presented when there is no information
for the current track.
@@ -1669,6 +1800,9 @@ class AppModule(appModuleHandler.AppModule):
script_sayCurrentTrackTitle.__doc__=_("Announces title of the currently
playing track")
def script_sayTemperature(self, gesture):
+ if not studioIsRunning():
+ self.finish()
+ return
try:
obj = self.status(self.SPLTemperature).firstChild
# Translators: Presented when there is no weather or
temperature information.
@@ -1690,7 +1824,7 @@ class AppModule(appModuleHandler.AppModule):
# 16.12: use Studio API if using 5.20.
if self.productVersion >= "5.20":
# Sometimes, hour markers return seconds.999 due to
rounding error, hence this must be taken care of here.
- trackStarts = divmod(statusAPI(3, 27, ret=True), 1000)
+ trackStarts = divmod(studioAPI(3, 27, ret=True), 1000)
# For this method, all three components of time display
(hour, minute, second) must be present.
# In case it is midnight (0.0 but sometimes shown as
86399.999 due to rounding error), just say "midnight".
if trackStarts in ((86399, 999), (0, 0)):
ui.message("00:00:00")
@@ -1704,7 +1838,7 @@ class AppModule(appModuleHandler.AppModule):
# 16.12: Use Studio 5.20 API (faster and more reliable).
if self.productVersion >= "5.20":
# This is the only time hour announcement should not be
used in order to conform to what's displayed on screen.
- self.announceTime(statusAPI(4, 27, ret=True),
includeHours=False)
+ self.announceTime(studioAPI(4, 27, ret=True),
includeHours=False)
else:
obj = self.status(self.SPLScheduledToPlay).firstChild
ui.message(obj.name)
@@ -1726,12 +1860,9 @@ class AppModule(appModuleHandler.AppModule):
def script_libraryScanMonitor(self, gesture):
if not self.libraryScanning:
- if self.productVersion >= "5.10":
- scanning = statusAPI(1, 32, ret=True)
- if scanning < 0:
- items = statusAPI(0, 32, ret=True)
- ui.message(_("{itemCount} items in the
library").format(itemCount = items))
- return
+ if studioAPI(1, 32, ret=True) < 0:
+ ui.message(_("{itemCount} items in the
library").format(itemCount = studioAPI(0, 32, ret=True)))
+ return
self.libraryScanning = True
# Translators: Presented when attempting to start
library scan.
ui.message(_("Monitoring library scan"))
@@ -1774,7 +1905,7 @@ class AppModule(appModuleHandler.AppModule):
analysisBegin = min(self._analysisMarker, trackPos)
analysisEnd = max(self._analysisMarker, trackPos)
analysisRange = analysisEnd-analysisBegin+1
- totalLength = self.totalTime(analysisBegin, analysisEnd)
+ totalLength = self.playlistDurationRaw(analysisBegin,
analysisEnd)
if analysisRange == 1:
self.announceTime(totalLength)
else:
@@ -1783,6 +1914,33 @@ class AppModule(appModuleHandler.AppModule):
# Translators: Input help mode message for a command in Station
Playlist Studio.
script_trackTimeAnalysis.__doc__=_("Announces total length of tracks
between analysis start marker and the current track")
+ def script_takePlaylistSnapshots(self, gesture):
+ if not studioIsRunning():
+ self.finish()
+ return
+ obj = api.getFocusObject() if
api.getForegroundObject().windowClassName == "TStudioForm" else
self._focusedTrack
+ if obj is None:
+ ui.message("Please return to playlist viewer before
invoking this command.")
+ self.finish()
+ return
+ if obj.role == controlTypes.ROLE_LIST:
+ ui.message(_("You need to add tracks before invoking
this command"))
+ self.finish()
+ return
+ scriptCount = scriptHandler.getLastScriptRepeatCount()
+ # Display the decorated HTML window on the first press if told
to do so.
+ if
splconfig.SPLConfig["PlaylistSnapshots"]["ShowResultsWindowOnFirstPress"]:
+ scriptCount += 1
+ # Never allow this to be invoked more than twice, as it causes
performance degredation and multiple HTML windows are opened.
+ if scriptCount >= 2:
+ self.finish()
+ return
+ # Speak and braille on the first press, display a decorated
HTML message for subsequent presses.
+
self.playlistSnapshotOutput(self.playlistSnapshots(obj.parent.firstChild,
None), scriptCount)
+ self.finish()
+ # Translators: Input help mode message for a command in Station
Playlist Studio.
+ script_takePlaylistSnapshots.__doc__=_("Presents playlist snapshot
information such as number of tracks and top artists")
+
def script_switchProfiles(self, gesture):
splconfig.triggerProfileSwitch() if
splconfig._triggerProfileActive else splconfig.instantProfileSwitch()
@@ -1816,8 +1974,7 @@ class AppModule(appModuleHandler.AppModule):
track = self._trackLocator(self.placeMarker[1],
obj=api.getFocusObject().parent.firstChild, columns=[self.placeMarker[0]])
# 16.11: Just like Track Finder, use select track
function to select the place marker track.
selectTrack(track.IAccessibleChildID-1)
- if self.productVersion >= "5.10":
- track.setFocus(), track.setFocus()
+ track.setFocus(), track.setFocus()
def script_metadataStreamingAnnouncer(self, gesture):
# 8.0: Call the module-level function directly.
@@ -1826,8 +1983,7 @@ class AppModule(appModuleHandler.AppModule):
# Gesture(s) for the following script cannot be changed by users.
def script_metadataEnabled(self, gesture):
url = int(gesture.displayName[-1])
- checked = statusAPI(url, 36, ret=True)
- if checked == 1:
+ if studioAPI(url, 36, ret=True):
# 0 is DSP encoder status, others are servers.
if url:
# Translators: Status message for metadata
streaming.
@@ -1865,7 +2021,7 @@ class AppModule(appModuleHandler.AppModule):
wx.CallAfter(gui.messageBox, SPLAssistantHelp[compatibility],
title)
def script_openOnlineDoc(self, gesture):
-
os.startfile("https://github.com/josephsl/stationplaylist/wiki/SPLAddonGuide";)
+
os.startfile("https://github.com/josephsl/stationplaylist/wiki/SPLDevAddonGuide";)
def script_updateCheck(self, gesture):
self.finish()
@@ -1878,7 +2034,7 @@ class AppModule(appModuleHandler.AppModule):
_("Add-on update check"),
# Translators: The message displayed while checking for
newer version of Studio add-on.
_("Checking for new version of Studio add-on..."))
- threading.Thread(target=splupdate.updateCheck,
kwargs={"continuous":splconfig.SPLConfig["Update"]["AutoUpdateCheck"],
"confUpdateInterval":splconfig.SPLConfig["Update"]["UpdateInterval"]}).start()
+ threading.Thread(target=splupdate.updateChecker,
kwargs={"continuous":splconfig.SPLConfig["Update"]["AutoUpdateCheck"],
"confUpdateInterval":splconfig.SPLConfig["Update"]["UpdateInterval"]}).start()
__SPLAssistantGestures={
@@ -1901,6 +2057,7 @@ class AppModule(appModuleHandler.AppModule):
"kb:shift+s":"sayScheduledToPlay",
"kb:shift+p":"sayTrackPitch",
"kb:shift+r":"libraryScanMonitor",
+ "kb:f8":"takePlaylistSnapshots",
"kb:f9":"markTrackForAnalysis",
"kb:f10":"trackTimeAnalysis",
"kb:f12":"switchProfiles",
@@ -1934,6 +2091,7 @@ class AppModule(appModuleHandler.AppModule):
"kb:shift+s":"sayScheduledToPlay",
"kb:shift+p":"sayTrackPitch",
"kb:shift+r":"libraryScanMonitor",
+ "kb:f8":"takePlaylistSnapshots",
"kb:f9":"markTrackForAnalysis",
"kb:f10":"trackTimeAnalysis",
"kb:f12":"switchProfiles",
@@ -1968,6 +2126,7 @@ class AppModule(appModuleHandler.AppModule):
"kb:shift+s":"sayScheduledToPlay",
"kb:shift+p":"sayTrackPitch",
"kb:shift+r":"libraryScanMonitor",
+ "kb:f8":"takePlaylistSnapshots",
"kb:f9":"markTrackForAnalysis",
"kb:f10":"trackTimeAnalysis",
"kb:f12":"switchProfiles",
diff --git a/addon/appModules/splstudio/splconfig.py
b/addon/appModules/splstudio/splconfig.py
index 7ae5c1f..0e41fef 100755
--- a/addon/appModules/splstudio/splconfig.py
+++ b/addon/appModules/splstudio/splconfig.py
@@ -12,7 +12,6 @@ from validate import Validator
import time
import datetime
import cPickle
-import copy
import globalVars
import ui
import gui
@@ -32,7 +31,7 @@ except ImportError:
SPLIni = os.path.join(globalVars.appArgs.configPath, "splstudio.ini")
SPLProfiles = os.path.join(globalVars.appArgs.configPath, "addons",
"stationPlaylist", "profiles")
# New (7.0) style config.
-confspec7 = ConfigObj(StringIO("""
+confspec = ConfigObj(StringIO("""
[General]
BeepAnnounce = boolean(default=false)
MessageVerbosity = option("beginner", "advanced", default="beginner")
@@ -40,13 +39,23 @@ BrailleTimer = option("off", "intro", "outro", "both",
default="off")
AlarmAnnounce = option("beep", "message", "both", default="beep")
TrackCommentAnnounce = option("off", "beep", "message", "both", default="off")
LibraryScanAnnounce = option("off", "ending", "progress", "numbers",
default="off")
-TrackDial = boolean(default=false)
CategorySounds = boolean(default=false)
TopBottomAnnounce = boolean(default=true)
MetadataReminder = option("off", "startup", "instant", default="off")
TimeHourAnnounce = boolean(default=true)
ExploreColumns =
string_list(default=list("Artist","Title","Duration","Intro","Category","Filename","Year","Album","Genre","Time
Scheduled"))
ExploreColumnsTT =
string_list(default=list("Artist","Title","Duration","Cue","Overlap","Intro","Segue","Filename","Album","CD
Code"))
+VerticalColumnAnnounce =
option(None,"Status","Artist","Title","Duration","Intro","Outro","Category","Year","Album","Genre","Mood","Energy","Tempo","BPM","Gender","Rating","Filename","Time
Scheduled",default=None)
+[PlaylistSnapshots]
+DurationMinMax = boolean(default=true)
+DurationAverage = boolean(default=true)
+ArtistCount = boolean(default=true)
+ArtistCountLimit = integer(min=0, max=10, default=5)
+CategoryCount = boolean(default=true)
+CategoryCountLimit = integer(min=0, max=10, default=5)
+GenreCount = boolean(default=true)
+GenreCountLimit = integer(min=0, max=10, default=5)
+ShowResultsWindowOnFirstPress = boolean(default=false)
[IntroOutroAlarms]
SayEndOfTrack = boolean(default=true)
EndOfTrackTime = integer(min=1, max=59, default=5)
@@ -76,15 +85,16 @@ UpdateInterval = integer(min=1, max=30, default=7)
[Startup]
AudioDuckingReminder = boolean(default=true)
WelcomeDialog = boolean(default=true)
-Studio500 = boolean(default=true)
"""), encoding="UTF-8", list_values=False)
-confspec7.newlines = "\r\n"
+confspec.newlines = "\r\n"
SPLConfig = None
# The following settings can be changed in profiles:
-_mutatableSettings7=("IntroOutroAlarms", "MicrophoneAlarm",
"MetadataStreaming", "ColumnAnnouncement")
+_mutatableSettings=("IntroOutroAlarms", "MicrophoneAlarm",
"MetadataStreaming", "ColumnAnnouncement")
# 7.0: Profile-specific confspec (might be removed once a more optimal way to
validate sections is found).
# Dictionary comprehension is better here.
-confspecprofiles = {sect:key for sect, key in confspec7.iteritems() if sect in
_mutatableSettings7}
+confspecprofiles = {sect:key for sect, key in confspec.iteritems() if sect in
_mutatableSettings}
+# Translators: The name of the default (normal) profile.
+defaultProfileName = _("Normal profile")
# 8.0: Run-time config storage and management will use ConfigHub data
structure, a subclass of chain map.
# A chain map allows a dictionary to look up predefined mappings to locate a
key.
@@ -105,11 +115,16 @@ class ConfigHub(ChainMap):
super(ConfigHub, self).__init__()
# For presentational purposes.
self.profileNames = []
- # Translators: The name of the default (normal) profile.
- self.maps[0] = self._unlockConfig(SPLIni, profileName=_("Normal
profile"), prefill=True, validateNow=True)
+ self.maps[0] = self._unlockConfig(SPLIni,
profileName=defaultProfileName, prefill=True, validateNow=True)
self.profileNames.append(None) # Signifying normal profile.
# Always cache normal profile upon startup.
self._cacheConfig(self.maps[0])
+ # Remove deprecated keys.
+ # This action must be performed after caching, otherwise the
newly modified profile will not be saved.
+ deprecatedKeys = {"General":"TrackDial", "Startup":"Studio500"}
+ for section, key in deprecatedKeys.iteritems():
+ if key in self.maps[0][section]: del
self.maps[0][section][key]
+ # Moving onto broadcast profiles if any.
try:
profiles = filter(lambda fn: os.path.splitext(fn)[-1]
== ".ini", os.listdir(SPLProfiles))
for profile in profiles:
@@ -148,10 +163,10 @@ class ConfigHub(ChainMap):
# 7.0: What if profiles have parsing errors?
# If so, reset everything back to factory defaults.
try:
- SPLConfigCheckpoint = ConfigObj(path, configspec =
confspec7 if prefill else confspecprofiles, encoding="UTF-8")
+ SPLConfigCheckpoint = ConfigObj(path, configspec =
confspec if prefill else confspecprofiles, encoding="UTF-8")
except:
open(path, "w").close()
- SPLConfigCheckpoint = ConfigObj(path, configspec =
confspec7 if prefill else confspecprofiles, encoding="UTF-8")
+ SPLConfigCheckpoint = ConfigObj(path, configspec =
confspec if prefill else confspecprofiles, encoding="UTF-8")
_configLoadStatus[profileName] = "fileReset"
# 5.2 and later: check to make sure all values are correct.
# 7.0: Make sure errors are displayed as config keys are now
sections and may need to go through subkeys.
@@ -176,7 +191,7 @@ class ConfigHub(ChainMap):
# Case 1: restore settings to defaults when 5.x
config validation has failed on all values.
# 6.0: In case this is a user profile, apply
base configuration.
# 8.0: Call copy profile function directly to
reduce overhead.
- copyProfile(_SPLDefaults7, SPLConfigCheckpoint,
complete=SPLConfigCheckpoint.filename == SPLIni)
+ copyProfile(_SPLDefaults, SPLConfigCheckpoint,
complete=SPLConfigCheckpoint.filename == SPLIni)
_configLoadStatus[profileName] = "completeReset"
elif isinstance(configTest, dict):
# Case 2: For 5.x and later, attempt to
reconstruct the failed values.
@@ -186,7 +201,7 @@ class ConfigHub(ChainMap):
if isinstance(configTest[setting],
dict):
for failedKey in
configTest[setting].keys():
# 7.0 optimization:
just reload from defaults dictionary, as broadcast profiles contain
profile-specific settings only.
-
SPLConfigCheckpoint[setting][failedKey] = _SPLDefaults7[setting][failedKey]
+
SPLConfigCheckpoint[setting][failedKey] = _SPLDefaults[setting][failedKey]
# 7.0: Disqualified from being cached this time.
SPLConfigCheckpoint.write()
_configLoadStatus[profileName] = "partialReset"
@@ -223,7 +238,7 @@ class ConfigHub(ChainMap):
def deleteProfile(self, name):
# Bring normal profile to the front if it isn't.
# Optimization: Tell the swapper that we need index to the
normal profile for this case.
- configPos = self.swapProfiles(name, _("Normal profile"),
showSwitchIndex=True) if self.profiles[0].name == name else
self.profileIndexByName(name)
+ configPos = self.swapProfiles(name, defaultProfileName,
showSwitchIndex=True) if self.profiles[0].name == name else
self.profileIndexByName(name)
profilePos = self.profileNames.index(name)
try:
os.remove(self.profiles[configPos].filename)
@@ -235,6 +250,7 @@ class ConfigHub(ChainMap):
def _cacheConfig(self, conf):
global _SPLCache
+ import copy
if _SPLCache is None: _SPLCache = {}
key = None if conf.filename == SPLIni else conf.name
_SPLCache[key] = {}
@@ -244,7 +260,7 @@ class ConfigHub(ChainMap):
def __delitem__(self, key):
# Consult profile-specific key first before deleting anything.
- pos = 0 if key in _mutatableSettings7 else [profile.name for
profile in self.maps].index(_("Normal Profile"))
+ pos = 0 if key in _mutatableSettings else [profile.name for
profile in self.maps].index(defaultProfileName)
try:
del self.maps[pos][key]
except KeyError:
@@ -255,7 +271,7 @@ class ConfigHub(ChainMap):
# 7.0: Save normal profile first.
# Temporarily merge normal profile.
# 8.0: Locate the index instead.
- normalProfile = self.profileIndexByName(_("Normal profile"))
+ normalProfile = self.profileIndexByName(defaultProfileName)
_preSave(self.profiles[normalProfile])
# Disk write optimization check please.
# 8.0: Bypass this if profiles were reset.
@@ -296,14 +312,14 @@ class ConfigHub(ChainMap):
profilePath = conf.filename
conf.reset()
conf.filename = profilePath
- resetConfig(_SPLDefaults7, conf)
+ resetConfig(_SPLDefaults, conf)
# Convert certain settings to a different format.
- conf["ColumnAnnouncement"]["IncludedColumns"] =
set(_SPLDefaults7["ColumnAnnouncement"]["IncludedColumns"])
+ conf["ColumnAnnouncement"]["IncludedColumns"] =
set(_SPLDefaults["ColumnAnnouncement"]["IncludedColumns"])
# Switch back to normal profile via a custom variant of swap
routine.
- if self.profiles[0].name != _("Normal profile"):
- npIndex = self.profileIndexByName(_("Normal profile"))
+ if self.profiles[0].name != defaultProfileName:
+ npIndex = self.profileIndexByName(defaultProfileName)
self.profiles[0], self.profiles[npIndex] =
self.profiles[npIndex], self.profiles[0]
- self.activeProfile = _("Normal profile")
+ self.activeProfile = defaultProfileName
# 8.0 optimization: Tell other modules that reset was done in
order to postpone disk writes until the end.
self.resetHappened = True
@@ -359,9 +375,9 @@ class ConfigHub(ChainMap):
# Default config spec container.
# To be moved to a different place in 8.0.
-_SPLDefaults7 = ConfigObj(None, configspec = confspec7, encoding="UTF-8")
+_SPLDefaults = ConfigObj(None, configspec = confspec, encoding="UTF-8")
_val = Validator()
-_SPLDefaults7.validate(_val, copy=True)
+_SPLDefaults.validate(_val, copy=True)
# Display an error dialog when configuration validation fails.
def runConfigErrorDialog(errorText, errorType):
@@ -453,7 +469,7 @@ def _extraInitSteps(conf, profileName=None):
global _configLoadStatus
columnOrder = conf["ColumnAnnouncement"]["ColumnOrder"]
# Catch suttle errors.
- fields = _SPLDefaults7["ColumnAnnouncement"]["ColumnOrder"]
+ fields = _SPLDefaults["ColumnAnnouncement"]["ColumnOrder"]
invalidFields = 0
for field in fields:
if field not in columnOrder: invalidFields+=1
@@ -474,6 +490,9 @@ def _extraInitSteps(conf, profileName=None):
else:
_configLoadStatus[profileName] = "metadataReset"
conf["MetadataStreaming"]["MetadataEnabled"] = [False, False,
False, False, False]
+ # 17.04: If vertical column announcement value is "None", transform
this to NULL.
+ if conf["General"]["VerticalColumnAnnounce"] == "None":
+ conf["General"]["VerticalColumnAnnounce"] = None
# Cache a copy of the loaded config.
# This comes in handy when saving configuration to disk. For the most part, no
change occurs to config.
@@ -651,7 +670,7 @@ def getProfileByName(name):
# Setting complete flag controls whether profile-specific settings are applied
(true otherwise, only set when resetting profiles).
# 8.0: Simplified thanks to in-place swapping.
def copyProfile(sourceProfile, targetProfile, complete=False):
- for section in sourceProfile.keys() if complete else
_mutatableSettings7:
+ for section in sourceProfile.keys() if complete else _mutatableSettings:
targetProfile[section] = dict(sourceProfile[section])
# Last but not least...
@@ -698,7 +717,7 @@ def _preSave(conf):
for setting in conf.keys():
for key in conf[setting].keys():
try:
- if conf[setting][key] ==
_SPLDefaults7[setting][key]:
+ if conf[setting][key] ==
_SPLDefaults[setting][key]:
del conf[setting][key]
except KeyError:
pass
@@ -752,7 +771,7 @@ def switchProfile(prevProfile, newProfile):
SPLConfig.switchProfile(prevProfile, newProfile)
SPLPrevProfile = prevProfile
# 8.0: Cache other profiles this time.
- if newProfile != _("Normal profile") and newProfile not in _SPLCache:
+ if newProfile != defaultProfileName and newProfile not in _SPLCache:
_cacheConfig(getProfileByName(newProfile))
# Called from within the app module.
@@ -809,22 +828,25 @@ def triggerProfileSwitch():
_SPLTriggerEndTimer.Stop()
_SPLTriggerEndTimer = None
-
# Automatic update checker.
# The function below is called as part of the update check timer.
# Its only job is to call the update check function (splupdate) with the auto
check enabled.
# The update checker will not be engaged if an instant switch profile is
active or it is not time to check for it yet (check will be done every 24
hours).
def autoUpdateCheck():
- splupdate.updateCheck(auto=True,
continuous=SPLConfig["Update"]["AutoUpdateCheck"],
confUpdateInterval=SPLConfig["Update"]["UpdateInterval"])
+ splupdate.updateChecker(auto=True,
continuous=SPLConfig["Update"]["AutoUpdateCheck"],
confUpdateInterval=SPLConfig["Update"]["UpdateInterval"])
# The timer itself.
# A bit simpler than NVDA Core's auto update checker.
def updateInit():
# LTS: Launch updater if channel change is detected.
+ # Use a background thread for this as urllib blocks.
+ import threading
if splupdate._updateNow:
- splupdate.updateCheck(auto=True) # No repeat here.
+ t = threading.Thread(target=splupdate.updateChecker,
kwargs={"auto": True}) # No repeat here.
+ t.daemon = True
splupdate._SPLUpdateT = wx.PyTimer(autoUpdateCheck)
+ t.start()
splupdate._updateNow = False
return
currentTime = time.time()
@@ -834,153 +856,22 @@ def updateInit():
elif splupdate.SPLAddonCheck < nextCheck < currentTime:
interval = SPLConfig["Update"]["UpdateInterval"]* 86400
# Call the update check now.
- splupdate.updateCheck(auto=True) # No repeat here.
+ t = threading.Thread(target=splupdate.updateChecker,
kwargs={"auto": True}) # No repeat here.
+ t.daemon = True
+ t.start()
splupdate._SPLUpdateT = wx.PyTimer(autoUpdateCheck)
splupdate._SPLUpdateT.Start(interval * 1000, True)
-
# Let SPL track item know if it needs to build description pieces.
# To be renamed and used in other places in 7.0.
def _shouldBuildDescriptionPieces():
return (not SPLConfig["ColumnAnnouncement"]["UseScreenColumnOrder"]
- and (SPLConfig["ColumnAnnouncement"]["ColumnOrder"] !=
_SPLDefaults7["ColumnAnnouncement"]["ColumnOrder"]
+ and (SPLConfig["ColumnAnnouncement"]["ColumnOrder"] !=
_SPLDefaults["ColumnAnnouncement"]["ColumnOrder"]
or len(SPLConfig["ColumnAnnouncement"]["IncludedColumns"]) != 17))
-
-# Additional configuration dialogs
+# Additional configuration and miscellaneous dialogs
# See splconfui module for basic configuration dialogs.
-# A common alarm dialog
-# Based on NVDA core's find dialog code (implemented by the author of this
add-on).
-# Extended in 2016 to handle microphone alarms.
-# Only one instance can be active at a given moment (code borrowed from GUI's
exit dialog routine).
-_alarmDialogOpened = False
-
-# A common alarm error dialog.
-def _alarmError():
- # Translators: Text of the dialog when another alarm dialog is open.
- gui.messageBox(_("Another alarm dialog is
open."),_("Error"),style=wx.OK | wx.ICON_ERROR)
-
-class SPLAlarmDialog(wx.Dialog):
- """A dialog providing common alarm settings.
- This dialog contains a number entry field for alarm duration and a
check box to enable or disable the alarm.
- For one particular case, it consists of two number entry fields.
- """
-
- # The following comes from exit dialog class from GUI package (credit:
NV Access and Zahari from Bulgaria).
- _instance = None
-
- def __new__(cls, parent, *args, **kwargs):
- # Make this a singleton and prompt an error dialog if it isn't.
- if _alarmDialogOpened:
- raise RuntimeError("An instance of alarm dialog is
opened")
- inst = cls._instance() if cls._instance else None
- if not inst:
- return super(cls, cls).__new__(cls, parent, *args,
**kwargs)
- return inst
-
- def __init__(self, parent, level=0):
- inst = SPLAlarmDialog._instance() if SPLAlarmDialog._instance
else None
- if inst:
- return
- # Use a weakref so the instance can die.
- import weakref
- SPLAlarmDialog._instance = weakref.ref(self)
-
- # Now the actual alarm dialog code.
- # 8.0: Apart from level 0 (all settings shown), levels change
title.
- titles = (_("Alarms Center"), _("End of track alarm"), _("Song
intro alarm"), _("Microphone alarm"))
- super(SPLAlarmDialog, self).__init__(parent, wx.ID_ANY,
titles[level])
- self.level = level
- mainSizer = wx.BoxSizer(wx.VERTICAL)
-
- if level in (0, 1):
- timeVal =
SPLConfig["IntroOutroAlarms"]["EndOfTrackTime"]
- alarmSizer = wx.BoxSizer(wx.HORIZONTAL)
- alarmMessage = wx.StaticText(self, wx.ID_ANY,
label=_("Enter &end of track alarm time in seconds (currently
{curAlarmSec})").format(curAlarmSec = timeVal))
- alarmSizer.Add(alarmMessage)
- self.outroAlarmEntry = wx.SpinCtrl(self, wx.ID_ANY,
min=1, max=59)
- self.outroAlarmEntry.SetValue(timeVal)
- self.outroAlarmEntry.SetSelection(-1, -1)
- alarmSizer.Add(self.outroAlarmEntry)
-
mainSizer.Add(alarmSizer,border=20,flag=wx.LEFT|wx.RIGHT|wx.TOP)
-
self.outroToggleCheckBox=wx.CheckBox(self,wx.NewId(),label=_("&Notify when end
of track is approaching"))
-
self.outroToggleCheckBox.SetValue(SPLConfig["IntroOutroAlarms"]["SayEndOfTrack"])
-
mainSizer.Add(self.outroToggleCheckBox,border=10,flag=wx.BOTTOM)
-
- if level in (0, 2):
- rampVal = SPLConfig["IntroOutroAlarms"]["SongRampTime"]
- alarmSizer = wx.BoxSizer(wx.HORIZONTAL)
- alarmMessage = wx.StaticText(self, wx.ID_ANY,
label=_("Enter song &intro alarm time in seconds (currently
{curRampSec})").format(curRampSec = rampVal))
- alarmSizer.Add(alarmMessage)
- self.introAlarmEntry = wx.SpinCtrl(self, wx.ID_ANY,
min=1, max=9)
- self.introAlarmEntry.SetValue(rampVal)
- self.introAlarmEntry.SetSelection(-1, -1)
- alarmSizer.Add(self.introAlarmEntry)
-
mainSizer.Add(alarmSizer,border=20,flag=wx.LEFT|wx.RIGHT|wx.TOP)
-
self.introToggleCheckBox=wx.CheckBox(self,wx.NewId(),label=_("&Notify when end
of introduction is approaching"))
-
self.introToggleCheckBox.SetValue(SPLConfig["IntroOutroAlarms"]["SaySongRamp"])
-
mainSizer.Add(self.introToggleCheckBox,border=10,flag=wx.BOTTOM)
-
- if level in (0, 3):
- micAlarm = SPLConfig["MicrophoneAlarm"]["MicAlarm"]
- micAlarmInterval =
SPLConfig["MicrophoneAlarm"]["MicAlarmInterval"]
- if micAlarm:
- # Translators: A dialog message to set
microphone active alarm (curAlarmSec is the current mic monitoring alarm in
seconds).
- timeMSG = _("Enter microphone alarm time in
seconds (currently {curAlarmSec}, 0 disables the alarm)").format(curAlarmSec =
micAlarm)
- else:
- # Translators: A dialog message when microphone
alarm is disabled (set to 0).
- timeMSG = _("Enter microphone alarm time in
seconds (currently disabled, 0 disables the alarm)")
- alarmSizer = wx.BoxSizer(wx.VERTICAL)
- alarmMessage = wx.StaticText(self, wx.ID_ANY,
label=timeMSG)
- alarmSizer.Add(alarmMessage)
- self.micAlarmEntry = wx.SpinCtrl(self, wx.ID_ANY,
min=0, max=7200)
- self.micAlarmEntry.SetValue(micAlarm)
- self.micAlarmEntry.SetSelection(-1, -1)
- alarmSizer.Add(self.micAlarmEntry)
- alarmMessage = wx.StaticText(self, wx.ID_ANY,
label=_("Microphone alarm interval"))
- alarmSizer.Add(alarmMessage)
- self.micIntervalEntry = wx.SpinCtrl(self, wx.ID_ANY,
min=0, max=60)
- self.micIntervalEntry.SetValue(micAlarmInterval)
- self.micIntervalEntry.SetSelection(-1, -1)
- alarmSizer.Add(self.micIntervalEntry)
-
mainSizer.Add(alarmSizer,border=20,flag=wx.LEFT|wx.RIGHT|wx.TOP)
-
- mainSizer.AddSizer(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)
- if level in (0, 1): self.outroAlarmEntry.SetFocus()
- elif level == 2: self.introAlarmEntry.SetFocus()
- elif level == 3: self.micAlarmEntry.SetFocus()
-
- def onOk(self, evt):
- global SPLConfig, _alarmDialogOpened
- # Optimization: don't bother if Studio is dead and if the same
value has been entered.
- import winUser
- if winUser.user32.FindWindowA("SPLStudio", None):
- # Gather settings to be applied in section/key format.
- settings = []
- if self.level in (0, 1):
- SPLConfig["IntroOutroAlarms"]["EndOfTrackTime"]
= self.outroAlarmEntry.GetValue()
- SPLConfig["IntroOutroAlarms"]["SayEndOfTrack"]
= self.outroToggleCheckBox.GetValue()
- elif self.level in (0, 2):
- SPLConfig["IntroOutroAlarms"]["SongRampTime"] =
self.introAlarmEntry.GetValue()
- SPLConfig["IntroOutroAlarms"]["SaySongRamp"] =
self.introToggleCheckBox.GetValue()
- elif self.level in (0, 3):
- SPLConfig["MicrophoneAlarm"]["MicAlarm"] =
self.micAlarmEntry.GetValue()
-
SPLConfig["MicrophoneAlarm"]["MicAlarmInterval"] =
self.micIntervalEntry.GetValue()
- self.Destroy()
- _alarmDialogOpened = False
-
- def onCancel(self, evt):
- self.Destroy()
- global _alarmDialogOpened
- _alarmDialogOpened = False
-
-
# Startup dialogs.
# Audio ducking reminder (NVDA 2016.1 and later).
@@ -995,17 +886,18 @@ class AudioDuckingReminder(wx.Dialog):
mainSizer = wx.BoxSizer(wx.VERTICAL)
# Translators: A message displayed if audio ducking should be
disabled.
- label = wx.StaticText(self, wx.ID_ANY, label=_("NVDA 2016.1 and
later allows NVDA to decrease volume of background audio including that of
Studio. In order to not disrupt the listening experience of your listeners,
please disable audio ducking by opening synthesizer dialog in NVDA and
selecting 'no ducking' from audio ducking mode combo box or press
NVDA+Shift+D."))
+ label = wx.StaticText(self, wx.ID_ANY, label=_("""NVDA 2016.1
and later allows NVDA to decrease volume of background audio including that of
Studio.
+ In order to not disrupt the listening experience of your
listeners, please disable audio ducking either by:
+ * Opening synthesizer dialog in NVDA and selecting 'no ducking'
from audio ducking mode combo box.
+ * Press NVDA+Shift+D to set it to 'no ducking'."""))
mainSizer.Add(label,border=20,flag=wx.LEFT|wx.RIGHT|wx.TOP)
- sizer = wx.BoxSizer(wx.HORIZONTAL)
# Translators: A checkbox to turn off audio ducking reminder
message.
self.audioDuckingReminder=wx.CheckBox(self,wx.NewId(),label=_("Do not show this
message again"))
self.audioDuckingReminder.SetValue(not
SPLConfig["Startup"]["AudioDuckingReminder"])
- sizer.Add(self.audioDuckingReminder, border=10,flag=wx.TOP)
- mainSizer.Add(sizer, border=10, flag=wx.BOTTOM)
+ mainSizer.Add(self.audioDuckingReminder, border=10,flag=wx.TOP)
- mainSizer.Add(self.CreateButtonSizer(wx.OK))
+ mainSizer.Add(self.CreateButtonSizer(wx.OK),
flag=wx.ALIGN_CENTER_HORIZONTAL)
self.Bind(wx.EVT_BUTTON, self.onOk, id=wx.ID_OK)
mainSizer.Fit(self)
self.Sizer = mainSizer
@@ -1051,14 +943,12 @@ Thank you.""")
label = wx.StaticText(self, wx.ID_ANY,
label=self.welcomeMessage)
mainSizer.Add(label,border=20,flag=wx.LEFT|wx.RIGHT|wx.TOP)
- sizer = wx.BoxSizer(wx.HORIZONTAL)
# Translators: A checkbox to show welcome dialog.
self.showWelcomeDialog=wx.CheckBox(self,wx.NewId(),label=_("Show welcome dialog
when I start Studio"))
self.showWelcomeDialog.SetValue(SPLConfig["Startup"]["WelcomeDialog"])
- sizer.Add(self.showWelcomeDialog, border=10,flag=wx.TOP)
- mainSizer.Add(sizer, border=10, flag=wx.BOTTOM)
+ mainSizer.Add(self.showWelcomeDialog, border=10,flag=wx.TOP)
- mainSizer.Add(self.CreateButtonSizer(wx.OK))
+ mainSizer.Add(self.CreateButtonSizer(wx.OK),
flag=wx.ALIGN_CENTER_HORIZONTAL)
self.Bind(wx.EVT_BUTTON, self.onOk, id=wx.ID_OK)
mainSizer.Fit(self)
self.Sizer = mainSizer
@@ -1071,9 +961,10 @@ Thank you.""")
self.Destroy()
# Old version reminder.
-class OldVersionReminder(wx.Dialog):
- """A dialog shown when using add-on 8.x under Studio 5.0x.
- """
+# Only used when there is a LTS version.
+"""class OldVersionReminder(wx.Dialog):
+ #A dialog shown when using add-on 8.x under Studio 5.0x.
+ #
def __init__(self, parent):
# Translators: Title of a dialog displayed when the add-on
starts reminding broadcasters about old Studio releases.
@@ -1088,7 +979,7 @@ class OldVersionReminder(wx.Dialog):
sizer = wx.BoxSizer(wx.HORIZONTAL)
# Translators: A checkbox to turn off old version reminder
message.
self.oldVersionReminder=wx.CheckBox(self,wx.NewId(),label=_("Do
not show this message again"))
- self.oldVersionReminder.SetValue(not
SPLConfig["Startup"]["Studio500"])
+ self.oldVersionReminder.SetValue(not
SPLConfig["Startup"]["OldSPLVersionReminder"])
sizer.Add(self.oldVersionReminder, border=10,flag=wx.TOP)
mainSizer.Add(sizer, border=10, flag=wx.BOTTOM)
@@ -1102,15 +993,16 @@ class OldVersionReminder(wx.Dialog):
def onOk(self, evt):
global SPLConfig
if self.oldVersionReminder.Value:
- SPLConfig["Startup"]["Studio500"] = not
self.oldVersionReminder.Value
- self.Destroy()
+ SPLConfig["Startup"]["OldSPLVersionReminder"] = not
self.oldVersionReminder.Value
+ self.Destroy()"""
# And to open the above dialog and any other dialogs.
def showStartupDialogs(oldVer=False):
- if oldVer and SPLConfig["Startup"]["Studio500"]:
- gui.mainFrame.prePopup()
- OldVersionReminder(gui.mainFrame).Show()
- gui.mainFrame.postPopup()
+ # Old version reminder if this is such a case.
+ #if oldVer and SPLConfig["Startup"]["OldSPLVersionReminder"]:
+ #gui.mainFrame.prePopup()
+ #OldVersionReminder(gui.mainFrame).Show()
+ #gui.mainFrame.postPopup()
if SPLConfig["Startup"]["WelcomeDialog"]:
gui.mainFrame.prePopup()
WelcomeDialog(gui.mainFrame).Show()
@@ -1120,14 +1012,11 @@ def showStartupDialogs(oldVer=False):
#if gui.messageBox("The next major version of the add-on (15.x)
will be the last version to support Studio versions earlier than 5.10, with
add-on 15.x being designated as a long-term support version. Would you like to
switch to long-term support release?", "Long-Term Support version", wx.YES |
wx.NO | wx.CANCEL | wx.CENTER | wx.ICON_QUESTION) == wx.YES:
#splupdate.SPLUpdateChannel = "lts"
#os.remove(os.path.join(globalVars.appArgs.configPath,
"addons", "stationPlaylist", "ltsprep"))
- try:
- import audioDucking
- if SPLConfig["Startup"]["AudioDuckingReminder"] and
audioDucking.isAudioDuckingSupported():
- gui.mainFrame.prePopup()
- AudioDuckingReminder(gui.mainFrame).Show()
- gui.mainFrame.postPopup()
- except ImportError:
- pass
+ import audioDucking
+ if SPLConfig["Startup"]["AudioDuckingReminder"] and
audioDucking.isAudioDuckingSupported():
+ gui.mainFrame.prePopup()
+ AudioDuckingReminder(gui.mainFrame).Show()
+ gui.mainFrame.postPopup()
# Message verbosity pool.
This diff is so big that we needed to truncate the remainder.
https://bitbucket.org/nvdaaddonteam/stationplaylist/commits/87b528628407/
Changeset: 87b528628407
Branch: stable
User: josephsl
Date: 2017-03-24 23:48:50+00:00
Summary: SPL Studio add-on 17.04 official
Signed-off-by: Joseph Lee <joseph.lee22590@xxxxxxxxx>
Affected #: 3 files
diff --git a/addon/appModules/splstudio/__init__.py
b/addon/appModules/splstudio/__init__.py
index 634a8f5..297d0e8 100755
--- a/addon/appModules/splstudio/__init__.py
+++ b/addon/appModules/splstudio/__init__.py
@@ -2021,7 +2021,7 @@ class AppModule(appModuleHandler.AppModule):
wx.CallAfter(gui.messageBox, SPLAssistantHelp[compatibility],
title)
def script_openOnlineDoc(self, gesture):
-
os.startfile("https://github.com/josephsl/stationplaylist/wiki/SPLDevAddonGuide";)
+
os.startfile("https://github.com/josephsl/stationplaylist/wiki/SPLAddonGuide";)
def script_updateCheck(self, gesture):
self.finish()
diff --git a/addon/appModules/splstudio/splupdate.py
b/addon/appModules/splstudio/splupdate.py
index 1b26bbd..a00e9d9 100755
--- a/addon/appModules/splstudio/splupdate.py
+++ b/addon/appModules/splstudio/splupdate.py
@@ -26,10 +26,10 @@ SPLAddonCheck = 0
# Update metadata storage.
SPLAddonState = {}
# Update URL (the only way to change it is installing a different version from
a different branch).
-SPLUpdateURL = "https://addons.nvda-project.org/files/get.php?file=spl-dev";
+SPLUpdateURL = "https://addons.nvda-project.org/files/get.php?file=spl";
_pendingChannelChange = False
_updateNow = False
-SPLUpdateChannel = "dev"
+SPLUpdateChannel = "stable"
# Update check timer.
_SPLUpdateT = None
# How long it should wait between automatic checks.
@@ -41,7 +41,7 @@ _updatePickle = os.path.join(globalVars.appArgs.configPath,
"splupdate.pickle")
# Not all update channels are listed. The one not listed here is the default
("stable" for this branch).
channels={
- "stable":"https://addons.nvda-project.org/files/get.php?file=spl";,
+ "dev":"https://addons.nvda-project.org/files/get.php?file=spl-dev";,
#"beta":"http://spl.nvda-kr.org/files/get.php?file=spl-beta";,
}
@@ -55,12 +55,12 @@ def initialize():
if _updateNow: del SPLAddonState["pendingChannelChange"]
if "UpdateChannel" in SPLAddonState:
SPLUpdateChannel = SPLAddonState["UpdateChannel"]
- if SPLUpdateChannel in ("beta", "lts"):
- SPLUpdateChannel = "dev"
+ if SPLUpdateChannel in ("beta", "preview", "lts"):
+ SPLUpdateChannel = "stable"
except IOError, KeyError:
SPLAddonState["PDT"] = 0
_updateNow = False
- SPLUpdateChannel = "dev"
+ SPLUpdateChannel = "stable"
def terminate():
global SPLAddonState
diff --git a/buildVars.py b/buildVars.py
index 5b65263..c12b7d6 100755
--- a/buildVars.py
+++ b/buildVars.py
@@ -20,7 +20,7 @@ addon_info = {
"addon_description" : _("""Enhances support for StationPlaylist Studio.
In addition, adds global commands for the studio from everywhere."""),
# version
- "addon_version" : "17.04-dev",
+ "addon_version" : "17.04",
# Author(s)
"addon_author" : u"Geoff Shang, Joseph Lee and other contributors",
# URL for the add-on documentation support
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.