Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to view new add-ons #16728

Closed
wants to merge 48 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
5b16029
Add support to show new add-ons in the store
nvdaes Jun 21, 2024
a2db450
Add schedule job to show new add-ons at startup
nvdaes Jun 21, 2024
e73b778
Check for new add-ons before running scheduled job to show them
nvdaes Jun 21, 2024
9354028
Merge branch 'master' into newAddons
nvdaes Jun 21, 2024
e6ab46a
Fix lint
nvdaes Jun 21, 2024
bc66d8e
Update source/gui/settingsDialogs.py
nvdaes Jun 21, 2024
5409e3e
Use ShowNewAddons class for store settings
nvdaes Jun 21, 2024
3fa7ecd
Update bindHelpEvent for show new add-ons
nvdaes Jun 21, 2024
e3707ba
Update user documentation
nvdaes Jun 21, 2024
6cacda9
Update tests/manual documentation
nvdaes Jun 21, 2024
2c1ccb8
Improvements according to available code in dataManager
nvdaes Jun 22, 2024
d158951
Don't check for new add-ons at startup if previous old add-ons haven'…
nvdaes Jun 22, 2024
6e79517
Improve documentation for manual tests
nvdaes Jun 22, 2024
39d4546
Notify about new add-ons if new versions are published, in addition t…
nvdaes Jun 23, 2024
b4a42b3
Fix typo in user guide
nvdaes Jun 23, 2024
cd2737b
Update manual tests documentation for new add-ons
nvdaes Jun 23, 2024
640e9ae
Fix lint
nvdaes Jun 23, 2024
5ba758b
Fix function to check for new add-ons in dataManager. considering new…
nvdaes Jun 23, 2024
5f1b64f
Improve function to check for new add-ons to avoid returning True for…
nvdaes Jun 24, 2024
3eb80bf
Merge commit '9ea256659' into newAddons
nvdaes Jun 27, 2024
e085bdf
renormalize files
nvdaes Jun 27, 2024
ff97ce7
Merge commit '8fb8ffcaba' into newAddons
nvdaes Jun 27, 2024
d427f2d
Merge remote-tracking branch 'origin/master' into newAddons
nvdaes Jun 27, 2024
32b8a9f
Merge branch 'master' into newAddons
nvdaes Jun 28, 2024
4431cf4
Add options to set the period of time to reset new add-ons
nvdaes Jun 30, 2024
35a6228
Fix checkbox for compatible add-ons
nvdaes Jul 1, 2024
6d8f8c4
Don't show all add-ons as new if old add-ons aren't cached yet
nvdaes Jul 1, 2024
a0a323a
Merge master
nvdaes Jul 1, 2024
75ead41
Restore locale to master
nvdaes Jul 1, 2024
9e53cd0
Show dates for add-ons considered as new
nvdaes Jul 1, 2024
013e732
Add translators comment
nvdaes Jul 1, 2024
bc52842
Update manual tests to check that the period of recent add-ons is pro…
nvdaes Jul 1, 2024
f51e88a
Merge origin/master
nvdaes Jul 4, 2024
14fa539
Fix translators comments
nvdaes Jul 4, 2024
8faf944
Use combo box to show new add-ons, instead of a dedicated tab
nvdaes Jul 4, 2024
7fd286e
Update source/gui/addonStoreGui/controls/storeDialog.py
nvdaes Jul 5, 2024
fcdf66a
Address review
nvdaes Jul 5, 2024
a55a8fc
Update source/addonStore/dataManager.py
nvdaes Jul 5, 2024
05791d7
Update source/addonStore/dataManager.py
nvdaes Jul 5, 2024
cd2a606
Update source/gui/addonStoreGui/viewModels/store.py
nvdaes Jul 5, 2024
fa4d01b
Use %x for new add-ons date
nvdaes Jul 5, 2024
be0bccc
Remove option to notify about new add-ons
nvdaes Jul 6, 2024
3e46d83
Update documentation
nvdaes Jul 6, 2024
d6c93f8
Fix number of days for the monthly option
nvdaes Jul 6, 2024
d45bf26
Update manual tests
nvdaes Jul 6, 2024
cde140d
Update user_docs/en/userGuide.md
nvdaes Jul 6, 2024
d9e4528
Fix bug introduced retrieving all available add-ons
nvdaes Jul 7, 2024
2a1f067
Merge branch 'newAddons' of https://github.com/nvdaes/nvda into newAd…
nvdaes Jul 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 75 additions & 2 deletions source/addonStore/dataManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import os
import pathlib
import threading
from datetime import datetime, timedelta
from typing import (
TYPE_CHECKING,
Optional,
Expand Down Expand Up @@ -80,6 +81,7 @@ def terminate():
class _DataManager:
_cacheLatestFilename: str = "_cachedLatestAddons.json"
_cacheCompatibleFilename: str = "_cachedCompatibleAddons.json"
_cacheCompatibleOldFilename: str = "_cachedCompatibleAddons-old.json"
_downloadsPendingInstall: Set[Tuple["AddonListItemVM[_AddonStoreModel]", os.PathLike]] = set()
_downloadsPendingCompletion: Set["AddonListItemVM[_AddonStoreModel]"] = set()

Expand All @@ -88,8 +90,10 @@ def __init__(self):
self._preferredChannel = Channel.ALL
self._cacheLatestFile = os.path.join(WritePaths.addonStoreDir, _DataManager._cacheLatestFilename)
self._cacheCompatibleFile = os.path.join(
WritePaths.addonStoreDir,
_DataManager._cacheCompatibleFilename,
WritePaths.addonStoreDir, _DataManager._cacheCompatibleFilename
)
self._cacheCompatibleOldFile = os.path.join(
WritePaths.addonStoreDir, _DataManager._cacheCompatibleOldFilename
)
self._installedAddonDataCacheDir = WritePaths.addonsDir

Expand All @@ -100,6 +104,7 @@ def __init__(self):

self._latestAddonCache = self._getCachedAddonData(self._cacheLatestFile)
self._compatibleAddonCache = self._getCachedAddonData(self._cacheCompatibleFile)
self._oldAddonCache = self._getCachedAddonData(self._cacheCompatibleOldFile)
self._installedAddonsCache = _InstalledAddonsCache()
# Fetch available add-ons cache early
self._initialiseAvailableAddonsThread = threading.Thread(
Expand All @@ -114,6 +119,8 @@ def terminate(self):
self._initialiseAvailableAddonsThread.join(timeout=1)
if self._initialiseAvailableAddonsThread.is_alive():
log.debugWarning("initialiseAvailableAddons thread did not terminate immediately")
if self._shouldCacheCompatibleAddonsBackup():
self._cacheCompatibleAddonsBackup()

def _getLatestAddonsDataForVersion(self, apiVersion: str) -> Optional[bytes]:
url = _getAddonStoreURL(self._preferredChannel, self._lang, apiVersion)
Expand Down Expand Up @@ -162,6 +169,52 @@ def _cacheCompatibleAddons(self, addonData: str, cacheHash: Optional[str]):
with open(self._cacheCompatibleFile, "w", encoding="utf-8") as cacheFile:
json.dump(cacheData, cacheFile, ensure_ascii=False)

def _shouldCacheCompatibleAddonsBackup(self) -> bool:
if not os.path.exists(self._cacheCompatibleOldFile):
return True
resetNewAddons = config.conf["addonStore"]["resetNewAddons"]
if resetNewAddons == "startup":
return True
lastBackupTime = os.path.getmtime(self._cacheCompatibleOldFile)
lastBackupDate = datetime.fromtimestamp(lastBackupTime)
nowDate = datetime.now()
diffDate = nowDate - lastBackupDate
if resetNewAddons == "weekly" and diffDate.days >= 7:
return True
if resetNewAddons == "monthly" and diffDate.days >= 30:
return True
return False

def _getResetNewAddonsDate(self) -> str:
resetNewAddons = config.conf["addonStore"]["resetNewAddons"]
if resetNewAddons == "startup":
# Translators: Message presented in the new add-ons combo box, informing that new add-ons will be reset at startup
return _("Will be reset at startup")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return _("Will be reset at startup")
return _("Will be reset at NVDA startup")

Copy link
Collaborator

Choose a reason for hiding this comment

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

@nvdaes you may have missed this one, or at least it's not showing as resolved at my end.

if not os.path.exists(self._cacheCompatibleOldFile):
# Translators: Message presented in the new add-ons combo box, informing that new add-ons will be retrieved when NVDA is restarted
return _("Empty list: NVDA needs to be restarted to retrieve new add-ons")
lastBackupTime = os.path.getmtime(self._cacheCompatibleOldFile)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I haven't read the code, but you should be catching a file not found error here I believe.
On a new portable copy of this PR's artifact, I'm getting:

Traceback (most recent call last):
  File "gui\blockAction.pyc", line 79, in funcWrapper
  File "gui\__init__.pyc", line 434, in onAddonStoreCommand
  File "gui\blockAction.pyc", line 79, in funcWrapper
  File "gui\__init__.pyc", line 237, in popupSettingsDialog
  File "gui\addonStoreGui\controls\storeDialog.pyc", line 56, in __init__
  File "gui\settingsDialogs.pyc", line 225, in __init__
  File "gui\addonStoreGui\controls\storeDialog.pyc", line 94, in makeSettings
  File "gui\addonStoreGui\controls\storeDialog.pyc", line 205, in _createFilterControls
  File "gui\addonStoreGui\controls\storeDialog.pyc", line 205, in <genexpr>
  File "utils\displayString.pyc", line 62, in displayString
  File "addonStore\models\status.pyc", line 53, in _displayStringLabels
  File "addonStore\models\status.pyc", line 457, in getResetNewAddonsDate
  File "addonStore\dataManager.pyc", line 193, in _getResetNewAddonsDate
  File "<frozen genericpath>", line 55, in getmtime
FileNotFoundError: [WinError 2] The system cannot find the file specified: 'C:\\Users\\luke\\Desktop\\NVDA versions\\pr4\\userConfig\\addonStore\\_cachedCompatibleAddons-old.json'

lastBackupDate = datetime.fromtimestamp(lastBackupTime)
formattedLastBackupDate = lastBackupDate.strftime("%x")
if resetNewAddons == "monthly":
timedeltaDays = 31
else: # weekly
timedeltaDays = 7
nextResetDate = lastBackupDate + timedelta(days=timedeltaDays)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The way this is presenting in the UI, makes it look like it's doing the math inversely. That is, on my system, if I don't choose all, the filtering date appears to be trying to show add-ons that are new between July 4th and August 3rd. I think it should be instead between June 3rd to July 4th.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @XLTechie , thanks for your review. No, add-ons will be considered new from 4 July, and will be reset on August. Add-on metadata doesn't have a timestamp, so NVDA will start saving all compatible add-ons cnsidering them as old add-ons, and add-ons published from 4 Jul will be considered as new add-ons. Hope this helps.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@seanbudd how hard would it be to start embedding a generation timestamp in the JSON? I should think, not very.

formattedNextResetDate = nextResetDate.strftime("%x")
return f"{formattedLastBackupDate}-{formattedNextResetDate}"

def _cacheCompatibleAddonsBackup(self):
if not NVDAState.shouldWriteToDisk():
return
try:
with open(self._cacheCompatibleFile, "r", encoding="utf-8") as cacheFile:
cacheData = json.load(cacheFile)
except Exception:
log.exception("Invalid add-on store cache")
with open(self._cacheCompatibleOldFile, "w", encoding="utf-8") as cacheFile:
json.dump(cacheData, cacheFile, ensure_ascii=False)
nvdaes marked this conversation as resolved.
Show resolved Hide resolved

def _cacheLatestAddons(self, addonData: str, cacheHash: Optional[str]):
if not NVDAState.shouldWriteToDisk():
return
Expand Down Expand Up @@ -287,6 +340,26 @@ def getLatestAddons(
return _createAddonGUICollection()
return deepcopy(self._latestAddonCache.cachedAddonData)

def _checkForNewAddons(self) -> bool:
oldAddons = self._getOldAddons()
compatibleAddons = self.getLatestCompatibleAddons()
installedAddons = self._installedAddonsCache._get_installedAddons()
for channel in compatibleAddons:
for addonId in compatibleAddons[channel]:
compatibleAddon = compatibleAddons[channel][addonId]
if (
addonId not in oldAddons[channel]
or compatibleAddon.addonVersionNumber != oldAddons[channel][addonId].addonVersionNumber
) and addonId not in installedAddons:
return True
return False
nvdaes marked this conversation as resolved.
Show resolved Hide resolved
nvdaes marked this conversation as resolved.
Show resolved Hide resolved

def _getOldAddons(self) -> "AddonGUICollectionT":
if self._oldAddonCache is None:
return _createAddonGUICollection()
oldAddons = deepcopy(self._oldAddonCache.cachedAddonData)
return oldAddons

def _deleteCacheInstalledAddon(self, addonId: str):
addonCachePath = os.path.join(self._installedAddonDataCacheDir, f"{addonId}.json")
if pathlib.Path(addonCachePath).exists():
Expand Down
44 changes: 28 additions & 16 deletions source/addonStore/models/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ def _displayStringLabels(self) -> Dict["EnabledStatus", str]:
}


class NewStatus(DisplayStringEnum):
ALL = enum.auto()
NEW = enum.auto()

@property
def _displayStringLabels(self) -> Dict["EnabledStatus", str]:
resetNewAddonsDate = getResetNewAddonsDate()
return {
# Translators: The label of an option to filter the list of add-ons in the add-on store dialog.
self.ALL: pgettext("addonStore", "All"),
# Translators: The label of an option to filter the list of add-ons in the add-on store dialog.
self.NEW: pgettext("addonStore", "New add-ons (%s)" % resetNewAddonsDate),
}


@enum.unique
# TODO refactor rename from AvailableAddonStatus to AddonStatus
class AvailableAddonStatus(DisplayStringEnum):
Expand Down Expand Up @@ -268,7 +283,7 @@ def _getUpdateStatus(model: "_AddonGUIModel") -> Optional[AvailableAddonStatus]:
# Parsing from a side-loaded add-on
try:
manifestAddonVersion = MajorMinorPatch._parseVersionFromVersionStr(
model._addonHandlerModel.version,
model._addonHandlerModel.version
)
except ValueError:
# Parsing failed to get a numeric version.
Expand Down Expand Up @@ -344,10 +359,7 @@ def getStatus(model: "_AddonGUIModel", context: _StatusFilterKey) -> AvailableAd
return AvailableAddonStatus.UNKNOWN


_addonStoreStateToAddonHandlerState: OrderedDict[
AvailableAddonStatus,
Set[AddonStateCategory],
] = OrderedDict(
_addonStoreStateToAddonHandlerState: OrderedDict[AvailableAddonStatus, Set[AddonStateCategory]] = OrderedDict(
{
# Pending states must be first as the pending state may be altering another state.
AvailableAddonStatus.PENDING_INCOMPATIBLE_DISABLED: {
Expand All @@ -371,7 +383,7 @@ def getStatus(model: "_AddonGUIModel", context: _StatusFilterKey) -> AvailableAd
AvailableAddonStatus.INCOMPATIBLE_ENABLED: {AddonStateCategory.OVERRIDE_COMPATIBILITY},
AvailableAddonStatus.DISABLED: {AddonStateCategory.DISABLED},
AvailableAddonStatus.INSTALLED: {AddonStateCategory.PENDING_INSTALL},
},
}
)


Expand Down Expand Up @@ -423,7 +435,7 @@ def getStatus(model: "_AddonGUIModel", context: _StatusFilterKey) -> AvailableAd
{
AvailableAddonStatus.INCOMPATIBLE,
AvailableAddonStatus.AVAILABLE,
},
}
),
_StatusFilterKey.INCOMPATIBLE: {
AvailableAddonStatus.PENDING_INCOMPATIBLE_DISABLED,
Expand All @@ -432,13 +444,19 @@ def getStatus(model: "_AddonGUIModel", context: _StatusFilterKey) -> AvailableAd
AvailableAddonStatus.INCOMPATIBLE_ENABLED,
AvailableAddonStatus.UNKNOWN,
},
},
}
)
"""A dictionary where the keys are a status to filter by,
and the values are which statuses should be shown for a given filter.
"""


def getResetNewAddonsDate() -> str:
from ..dataManager import addonDataManager

return addonDataManager._getResetNewAddonsDate()


class SupportsAddonState(SupportsVersionCheck, Protocol):
@property
def _stateHandler(self) -> "AddonsState":
Expand All @@ -458,17 +476,11 @@ def isRunning(self) -> bool:
def pendingInstallPath(self) -> str:
from addonHandler import ADDON_PENDINGINSTALL_SUFFIX

return os.path.join(
WritePaths.addonsDir,
self.name + ADDON_PENDINGINSTALL_SUFFIX,
)
return os.path.join(WritePaths.addonsDir, self.name + ADDON_PENDINGINSTALL_SUFFIX)

@property
def installPath(self) -> str:
return os.path.join(
WritePaths.addonsDir,
self.name,
)
return os.path.join(WritePaths.addonsDir, self.name)

@property
def isPendingInstall(self) -> bool:
Expand Down
20 changes: 20 additions & 0 deletions source/config/configFlags.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,23 @@ def _displayStringLabels(self):
# Translators: This is a label for the automatic update behaviour for add-ons.
self.DISABLED: _("Disabled"),
}


class ResetNewAddons(DisplayStringStrEnum):
MONTHLY = "monthly"
WEEKLY = "weekly"
STARTUP = "startup"

@property
def _displayStringLabels(self):
return {
# Translators: This is a label for the reset new add-ons behavior.
# It will determine the period for an add-on to be considered as new.
self.MONTHLY: _("Monthly"),
# Translators: This is a label for the reset new add-ons behavior.
# It will determine the period for an add-on to be considered as new.
self.WEEKLY: _("Weekly"),
# Translators: This is a label for the reset new add-ons behavior.
# It will determine the period for an add-on to be considered as new.
self.STARTUP: _("At startup"),
}
1 change: 1 addition & 0 deletions source/config/configSpec.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@
[addonStore]
showWarning = boolean(default=true)
automaticUpdates = option("notify", "disabled", default="notify")
resetNewAddons = option("monthly", "weekly", "startup", default="monthly")
"""

#: The configuration specification
Expand Down
31 changes: 31 additions & 0 deletions source/gui/addonStoreGui/controls/storeDialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from addonStore.models.channel import Channel, _channelFilters
from addonStore.models.status import (
EnabledStatus,
NewStatus,
_statusFilters,
_StatusFilterKey,
)
Expand Down Expand Up @@ -195,6 +196,19 @@ def _createFilterControls(self, filterCtrlHelper: guiHelper.BoxSizerHelper) -> N
self.enabledFilterCtrl.Bind(wx.EVT_CHOICE, self.onEnabledFilterChange, self.enabledFilterCtrl)
self.bindHelpEvent("AddonStoreFilterEnabled", self.enabledFilterCtrl)

self.newFilterCtrl = cast(
wx.Choice,
filterCtrlsLine0.addLabeledControl(
# Translators: The label of a selection field to filter the list of add-ons in the add-on store dialog.
labelText=pgettext("addonStore", "Release date filter:"),
wxCtrlClass=wx.Choice,
choices=list(c.displayString for c in NewStatus),
),
)
self.newFilterCtrl.SetSelection(0)
self.newFilterCtrl.Bind(wx.EVT_CHOICE, self.onNewFilterChange, self.newFilterCtrl)
self.bindHelpEvent("AddonStoreFilterNew", self.newFilterCtrl)

# Translators: The label of a text field to filter the list of add-ons in the add-on store dialog.
searchFilterLabel = wx.StaticText(self, label=pgettext("addonStore", "&Search:"))
# noinspection PyAttributeOutsideInit
Expand Down Expand Up @@ -327,6 +341,12 @@ def _toggleFilterControls(self):
for c in _channelFilters:
if c != Channel.EXTERNAL:
self.channelFilterCtrl.Append(c.displayString)
if self._storeVM._filteredStatusKey == _StatusFilterKey.AVAILABLE:
self.newFilterCtrl.Enable()
self.newFilterCtrl.Show()
else:
self.newFilterCtrl.Hide()
self.newFilterCtrl.Disable()
if self._storeVM._filteredStatusKey in {
_StatusFilterKey.AVAILABLE,
_StatusFilterKey.UPDATE,
Expand Down Expand Up @@ -387,6 +407,17 @@ def onEnabledFilterChange(self, evt: wx.EVT_CHOICE):

def onIncompatibleFilterChange(self, evt: wx.EVT_CHECKBOX):
self._storeVM._filterIncludeIncompatible = self.includeIncompatibleCtrl.GetValue()
if self._storeVM._filterIncludeIncompatible:
self.newFilterCtrl.Hide()
self.newFilterCtrl.Disable()
else:
self.newFilterCtrl.Enable()
self.newFilterCtrl.Show()
self._storeVM.refresh()

def onNewFilterChange(self, evt: wx.EVT_CHOICE):
index = self.newFilterCtrl.GetCurrentSelection()
self._storeVM._filterNew = list(NewStatus)[index]
self._storeVM.refresh()

def filter(self, filterText: str):
Expand Down
20 changes: 20 additions & 0 deletions source/gui/addonStoreGui/viewModels/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
)
from addonStore.models.status import (
EnabledStatus,
NewStatus,
getStatus,
_statusFilters,
_StatusFilterKey,
Expand Down Expand Up @@ -91,6 +92,10 @@ def __init__(self):
Filters the add-on list view model by enabled or disabled.
"""
self._filterIncludeIncompatible: bool = False
self._filterNew: NewStatus = NewStatus.ALL
"""
Filters the add-on list view model by all or recently published.
"""

self.listVM: AddonListVM = AddonListVM(
addons=self._createListItemVMs(),
Expand Down Expand Up @@ -618,6 +623,21 @@ def _getAvailableAddonsInBG(self):
and incompatibleAddons[channel][addonId].canOverrideCompatibility
):
availableAddons[channel][addonId] = incompatibleAddons[channel][addonId]
elif self._filterNew != NewStatus.ALL:
if addonDataManager._oldAddonCache:
oldAddons = addonDataManager._getOldAddons()
newAddons = {
channel: {
addonId: addon
for addonId, addon in addons.items()
if addonId not in oldAddons[channel]
or addon.addonVersionNumber != oldAddons[channel][addonId].addonVersionNumber
}
for channel, addons in availableAddons.items()
}
availableAddons = newAddons
else:
availableAddons = _createAddonGUICollection()
log.debug("completed getting addons in the background")
self._availableAddons = availableAddons
self.listVM.resetListItems(self._createListItemVMs())
Expand Down
11 changes: 11 additions & 0 deletions source/gui/settingsDialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import config
from config.configFlags import (
AddonsAutomaticUpdate,
ResetNewAddons,
NVDAKey,
ShowMessages,
TetherTo,
Expand Down Expand Up @@ -3102,10 +3103,20 @@ def makeSettings(self, settingsSizer: wx.BoxSizer) -> None:
self.bindHelpEvent("AutomaticAddonUpdates", self.automaticUpdatesComboBox)
index = [x.value for x in AddonsAutomaticUpdate].index(config.conf["addonStore"]["automaticUpdates"])
self.automaticUpdatesComboBox.SetSelection(index)
# Translators: This is a label for the reset new add-ons combo box in the Add-on Store Settings dialog.
resetNewAddonsLabelText = _("&Reset new add-ons:")
self.resetNewAddonsComboBox = sHelper.addLabeledControl(
resetNewAddonsLabelText, wx.Choice, choices=[mode.displayString for mode in ResetNewAddons]
)
self.bindHelpEvent("ResetNewAddons", self.resetNewAddonsComboBox)
index = [x.value for x in ResetNewAddons].index(config.conf["addonStore"]["resetNewAddons"])
self.resetNewAddonsComboBox.SetSelection(index)

def onSave(self):
index = self.automaticUpdatesComboBox.GetSelection()
config.conf["addonStore"]["automaticUpdates"] = [x.value for x in AddonsAutomaticUpdate][index]
index = self.resetNewAddonsComboBox.GetSelection()
config.conf["addonStore"]["resetNewAddons"] = [x.value for x in ResetNewAddons][index]


class TouchInteractionPanel(SettingsPanel):
Expand Down
Loading