Skip to content

Commit 8249300

Browse files
authored
Merge 48458ab into dd1df90
2 parents dd1df90 + 48458ab commit 8249300

5 files changed

Lines changed: 117 additions & 79 deletions

File tree

source/addonStore/models/status.py

Lines changed: 102 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def _displayStringLabels(self) -> Dict["EnabledStatus", str]:
4545

4646

4747
@enum.unique
48+
# TODO refactor rename from AvailableAddonStatus to AddonStatus
4849
class AvailableAddonStatus(DisplayStringEnum):
4950
""" Values to represent the status of add-ons within the NVDA add-on store.
5051
Although related, these are independent of the states in L{addonHandler}
@@ -53,6 +54,7 @@ class AvailableAddonStatus(DisplayStringEnum):
5354
PENDING_REMOVE = enum.auto()
5455
AVAILABLE = enum.auto()
5556
UPDATE = enum.auto()
57+
UPDATE_INCOMPATIBLE = enum.auto()
5658
REPLACE_SIDE_LOAD = enum.auto()
5759
"""
5860
Used when an addon in the store matches an installed add-on ID.
@@ -86,6 +88,8 @@ def _displayStringLabels(self) -> Dict["AvailableAddonStatus", str]:
8688
# Translators: Status for addons shown in the add-on store dialog
8789
self.UPDATE: pgettext("addonStore", "Update Available"),
8890
# Translators: Status for addons shown in the add-on store dialog
91+
self.UPDATE_INCOMPATIBLE: pgettext("addonStore", "Update Available (incompatible)"),
92+
# Translators: Status for addons shown in the add-on store dialog
8993
self.REPLACE_SIDE_LOAD: pgettext("addonStore", "Migrate to add-on store"),
9094
# Translators: Status for addons shown in the add-on store dialog
9195
self.INCOMPATIBLE: pgettext("addonStore", "Incompatible"),
@@ -147,6 +151,63 @@ class AddonStateCategory(str, enum.Enum):
147151
"""Add-ons in this state are incompatible but their compatibility would be overridden on the next restart."""
148152

149153

154+
class _StatusFilterKey(DisplayStringEnum):
155+
"""Keys for filtering by status in the NVDA add-on store."""
156+
INSTALLED = enum.auto()
157+
UPDATE = enum.auto()
158+
AVAILABLE = enum.auto()
159+
INCOMPATIBLE = enum.auto()
160+
161+
@property
162+
def _displayStringLabels(self) -> Dict["_StatusFilterKey", str]:
163+
return {
164+
# Translators: The label of a tab to display installed add-ons in the add-on store.
165+
# Ensure the translation matches the label for the add-on list which includes an accelerator key.
166+
self.INSTALLED: pgettext("addonStore", "Installed add-ons"),
167+
# Translators: The label of a tab to display updatable add-ons in the add-on store.
168+
# Ensure the translation matches the label for the add-on list which includes an accelerator key.
169+
self.UPDATE: pgettext("addonStore", "Updatable add-ons"),
170+
# Translators: The label of a tab to display available add-ons in the add-on store.
171+
# Ensure the translation matches the label for the add-on list which includes an accelerator key.
172+
self.AVAILABLE: pgettext("addonStore", "Available add-ons"),
173+
# Translators: The label of a tab to display incompatible add-ons in the add-on store.
174+
# Ensure the translation matches the label for the add-on list which includes an accelerator key.
175+
self.INCOMPATIBLE: pgettext("addonStore", "Installed incompatible add-ons"),
176+
}
177+
178+
@property
179+
def _displayStringLabelsWithAccelerators(self) -> Dict["_StatusFilterKey", str]:
180+
return {
181+
# Translators: The label of the add-ons list in the corresponding panel.
182+
# Preferably use the same accelerator key for the four labels.
183+
# Ensure the translation matches the label for the add-on tab which has the accelerator key removed.
184+
self.INSTALLED: pgettext("addonStore", "Installed &add-ons"),
185+
# Translators: The label of the add-ons list in the corresponding panel.
186+
# Preferably use the same accelerator key for the four labels.
187+
# Ensure the translation matches the label for the add-on tab which has the accelerator key removed.
188+
self.UPDATE: pgettext("addonStore", "Updatable &add-ons"),
189+
# Translators: The label of the add-ons list in the corresponding panel.
190+
# Preferably use the same accelerator key for the four labels.
191+
# Ensure the translation matches the label for the add-on tab which has the accelerator key removed.
192+
self.AVAILABLE: pgettext("addonStore", "Available &add-ons"),
193+
# Translators: The label of the add-ons list in the corresponding panel.
194+
# Preferably use the same accelerator key for the four labels.
195+
# Ensure the translation matches the label for the add-on tab which has the accelerator key removed.
196+
self.INCOMPATIBLE: pgettext("addonStore", "Installed incompatible &add-ons"),
197+
}
198+
199+
@property
200+
def displayStringWithAccelerator(self) -> str:
201+
"""
202+
@return: The translated UI display string with accelerator that should be used for this value of the enum.
203+
"""
204+
try:
205+
return self._displayStringLabelsWithAccelerators[self]
206+
except KeyError as e:
207+
log.error(f"No translation mapping for: {self}")
208+
raise e
209+
210+
150211
def _getDownloadableStatus(model: "_AddonGUIModel") -> Optional[AvailableAddonStatus]:
151212
from ..dataManager import addonDataManager
152213
assert addonDataManager is not None
@@ -186,6 +247,8 @@ def _getUpdateStatus(model: "_AddonGUIModel") -> Optional[AvailableAddonStatus]:
186247
addonStoreInstalledData = addonDataManager._getCachedInstalledAddonData(model.addonId)
187248
if addonStoreInstalledData is not None:
188249
if model.addonVersionNumber > addonStoreInstalledData.addonVersionNumber:
250+
if not model.isCompatible:
251+
return AvailableAddonStatus.UPDATE_INCOMPATIBLE
189252
return AvailableAddonStatus.UPDATE
190253
else:
191254
# Parsing from a side-loaded add-on
@@ -200,22 +263,18 @@ def _getUpdateStatus(model: "_AddonGUIModel") -> Optional[AvailableAddonStatus]:
200263
return AvailableAddonStatus.REPLACE_SIDE_LOAD
201264

202265
if model.addonVersionNumber > manifestAddonVersion:
266+
if not model.isCompatible:
267+
return AvailableAddonStatus.UPDATE_INCOMPATIBLE
203268
return AvailableAddonStatus.UPDATE
204269

205270
return None
206271

207272

208-
def getStatus(model: "_AddonGUIModel") -> AvailableAddonStatus:
273+
def _getInstalledStatus(model: "_AddonGUIModel") -> Optional[AvailableAddonStatus]:
209274
from addonHandler import state as addonHandlerState
210-
211-
downloadableStatus = _getDownloadableStatus(model)
212-
if downloadableStatus:
213-
# Is this available in the add-on store and not installed?
214-
return downloadableStatus
215-
else:
216-
# Add-on is currently installed or pending restart
217-
addonHandlerModel = model._addonHandlerModel
218-
assert addonHandlerModel
275+
from ..dataManager import addonDataManager
276+
assert addonDataManager is not None
277+
assert model._addonHandlerModel is not None
219278

220279
for storeState, handlerStateCategories in _addonStoreStateToAddonHandlerState.items():
221280
# Match special addonHandler states early for installed add-ons.
@@ -233,18 +292,41 @@ def getStatus(model: "_AddonGUIModel") -> AvailableAddonStatus:
233292
# and another for enabled/disabled.
234293
return storeState
235294

236-
updateStatus = _getUpdateStatus(model)
237-
if updateStatus:
238-
# Can add-on be updated?
239-
return updateStatus
240-
241-
if addonHandlerModel.isRunning:
295+
if model._addonHandlerModel.isRunning:
242296
return AvailableAddonStatus.RUNNING
243297

244-
if addonHandlerModel.isEnabled:
298+
if model._addonHandlerModel.isEnabled:
245299
return AvailableAddonStatus.ENABLED
246300

247-
log.error(f"Add-on in unknown state: {model.addonId}")
301+
return None
302+
303+
304+
def getStatus(model: "_AddonGUIModel", context: _StatusFilterKey) -> AvailableAddonStatus:
305+
"""Get status for an add-on in the context of the current tab in the add-on store.
306+
e.g. "update available" from the update tab, and "installed (incompatible)" from the installed tab.
307+
308+
:param model: Add-on to determine the status of.
309+
:param context: Add-on Store tab context we are checking the status for.
310+
:return: Status of add-on for the context of the current tab.
311+
"""
312+
313+
if context in (_StatusFilterKey.AVAILABLE, _StatusFilterKey.UPDATE):
314+
downloadableStatus = _getDownloadableStatus(model)
315+
if downloadableStatus:
316+
# Is this available in the add-on store and not installed?
317+
return downloadableStatus
318+
319+
updateStatus = _getUpdateStatus(model)
320+
if updateStatus:
321+
# Can add-on be updated?
322+
return updateStatus
323+
324+
# This add-on should be installed if we aren't fetching for the available add-ons tab
325+
installedStatus = _getInstalledStatus(model)
326+
if installedStatus:
327+
return installedStatus
328+
329+
log.error(f"Add-on in unknown state: {model.addonId} {context}")
248330
return AvailableAddonStatus.UNKNOWN
249331

250332

@@ -277,63 +359,6 @@ def getStatus(model: "_AddonGUIModel") -> AvailableAddonStatus:
277359
})
278360

279361

280-
class _StatusFilterKey(DisplayStringEnum):
281-
"""Keys for filtering by status in the NVDA add-on store."""
282-
INSTALLED = enum.auto()
283-
UPDATE = enum.auto()
284-
AVAILABLE = enum.auto()
285-
INCOMPATIBLE = enum.auto()
286-
287-
@property
288-
def _displayStringLabels(self) -> Dict["_StatusFilterKey", str]:
289-
return {
290-
# Translators: The label of a tab to display installed add-ons in the add-on store.
291-
# Ensure the translation matches the label for the add-on list which includes an accelerator key.
292-
self.INSTALLED: pgettext("addonStore", "Installed add-ons"),
293-
# Translators: The label of a tab to display updatable add-ons in the add-on store.
294-
# Ensure the translation matches the label for the add-on list which includes an accelerator key.
295-
self.UPDATE: pgettext("addonStore", "Updatable add-ons"),
296-
# Translators: The label of a tab to display available add-ons in the add-on store.
297-
# Ensure the translation matches the label for the add-on list which includes an accelerator key.
298-
self.AVAILABLE: pgettext("addonStore", "Available add-ons"),
299-
# Translators: The label of a tab to display incompatible add-ons in the add-on store.
300-
# Ensure the translation matches the label for the add-on list which includes an accelerator key.
301-
self.INCOMPATIBLE: pgettext("addonStore", "Installed incompatible add-ons"),
302-
}
303-
304-
@property
305-
def _displayStringLabelsWithAccelerators(self) -> Dict["_StatusFilterKey", str]:
306-
return {
307-
# Translators: The label of the add-ons list in the corresponding panel.
308-
# Preferably use the same accelerator key for the four labels.
309-
# Ensure the translation matches the label for the add-on tab which has the accelerator key removed.
310-
self.INSTALLED: pgettext("addonStore", "Installed &add-ons"),
311-
# Translators: The label of the add-ons list in the corresponding panel.
312-
# Preferably use the same accelerator key for the four labels.
313-
# Ensure the translation matches the label for the add-on tab which has the accelerator key removed.
314-
self.UPDATE: pgettext("addonStore", "Updatable &add-ons"),
315-
# Translators: The label of the add-ons list in the corresponding panel.
316-
# Preferably use the same accelerator key for the four labels.
317-
# Ensure the translation matches the label for the add-on tab which has the accelerator key removed.
318-
self.AVAILABLE: pgettext("addonStore", "Available &add-ons"),
319-
# Translators: The label of the add-ons list in the corresponding panel.
320-
# Preferably use the same accelerator key for the four labels.
321-
# Ensure the translation matches the label for the add-on tab which has the accelerator key removed.
322-
self.INCOMPATIBLE: pgettext("addonStore", "Installed incompatible &add-ons"),
323-
}
324-
325-
@property
326-
def displayStringWithAccelerator(self) -> str:
327-
"""
328-
@return: The translated UI display string with accelerator that should be used for this value of the enum.
329-
"""
330-
try:
331-
return self._displayStringLabelsWithAccelerators[self]
332-
except KeyError as e:
333-
log.error(f"No translation mapping for: {self}")
334-
raise e
335-
336-
337362
_installedAddonStatuses: Set[AvailableAddonStatus] = {
338363
AvailableAddonStatus.UPDATE,
339364
AvailableAddonStatus.REPLACE_SIDE_LOAD,
@@ -355,12 +380,14 @@ def displayStringWithAccelerator(self) -> str:
355380
_StatusFilterKey.INSTALLED: _installedAddonStatuses,
356381
_StatusFilterKey.UPDATE: {
357382
AvailableAddonStatus.UPDATE,
383+
AvailableAddonStatus.UPDATE_INCOMPATIBLE,
358384
AvailableAddonStatus.REPLACE_SIDE_LOAD,
359385
},
360386
_StatusFilterKey.AVAILABLE: _installedAddonStatuses.union({
361387
AvailableAddonStatus.INCOMPATIBLE,
362388
AvailableAddonStatus.AVAILABLE,
363389
AvailableAddonStatus.UPDATE,
390+
AvailableAddonStatus.UPDATE_INCOMPATIBLE,
364391
AvailableAddonStatus.REPLACE_SIDE_LOAD,
365392
AvailableAddonStatus.DOWNLOAD_FAILED,
366393
AvailableAddonStatus.DOWNLOAD_SUCCESS,

source/gui/addonStoreGui/controls/actions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ def canUseUpdateAction(self) -> bool:
199199
hasUpdatable = False
200200
hasInstallable = False
201201
for aVM in self.addonsList:
202-
if aVM.canUseUpdateAction() or aVM.canUseReplaceAction():
202+
if aVM.canUseUpdateAction() or aVM.canUseReplaceAction() or aVM.canUseUpdateOverrideIncompatibilityAction():
203203
hasUpdatable = True
204204
if aVM.canUseInstallAction() or aVM.canUseInstallOverrideIncompatibilityAction():
205205
hasInstallable = True

source/gui/addonStoreGui/viewModels/addonList.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ def canUseInstallOverrideIncompatibilityAction(self) -> bool:
133133
def canUseUpdateAction(self) -> bool:
134134
return self.status == AvailableAddonStatus.UPDATE
135135

136+
def canUseUpdateOverrideIncompatibilityAction(self) -> bool:
137+
return self.status == AvailableAddonStatus.UPDATE_INCOMPATIBLE and self.model.canOverrideCompatibility
138+
136139
def canUseReplaceAction(self) -> bool:
137140
return self.status == AvailableAddonStatus.REPLACE_SIDE_LOAD
138141

source/gui/addonStoreGui/viewModels/store.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@ def _makeActionsList(self):
132132
validCheck=lambda aVM: aVM.canUseUpdateAction(),
133133
actionTarget=selectedListItem
134134
),
135+
AddonActionVM(
136+
# Translators: Label for an action that installs the selected addon
137+
displayName=pgettext("addonStore", "&Update (override incompatibility)"),
138+
actionHandler=self.installOverrideIncompatibilityForAddon,
139+
validCheck=lambda aVM: aVM.canUseUpdateOverrideIncompatibilityAction(),
140+
actionTarget=selectedListItem
141+
),
135142
AddonActionVM(
136143
# Translators: Label for an action that replaces the selected addon with
137144
# an add-on store version.
@@ -268,7 +275,7 @@ def removeAddon(
268275
assert listItemVM.model._addonHandlerModel is not None
269276
listItemVM.model._addonHandlerModel.requestRemove()
270277
self.refresh()
271-
listItemVM.status = getStatus(listItemVM.model)
278+
listItemVM.status = getStatus(listItemVM.model, self._filteredStatusKey)
272279
return shouldRemove, shouldRememberChoice
273280

274281
def removeAddons(self, listItemVMs: Iterable[AddonListItemVM[_AddonStoreModel]]) -> None:
@@ -344,7 +351,7 @@ def _handleEnableDisable(self, listItemVM: AddonListItemVM[_AddonManifestModel],
344351
# ensure calling on the main thread.
345352
core.callLater(delay=0, callable=self.onDisplayableError.notify, displayableError=displayableError)
346353

347-
listItemVM.status = getStatus(listItemVM.model)
354+
listItemVM.status = getStatus(listItemVM.model, self._filteredStatusKey)
348355
self.refresh()
349356

350357
def enableOverrideIncompatibilityForAddon(
@@ -635,7 +642,7 @@ def _createListItemVMs(self) -> List[AddonListItemVM]:
635642
raise NotImplementedError(f"Unhandled status filter key {self._filteredStatusKey}")
636643

637644
addonsWithStatus = (
638-
(model, getStatus(model))
645+
(model, getStatus(model, self._filteredStatusKey))
639646
for channel in addons
640647
for model in addons[channel].values()
641648
)

user_docs/en/changes.t2t

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Users of Poedit 1 are encouraged to update to Poedit 3 if they want to rely on e
6969
- When the status of an add-on is changed while it has focus, e.g. a change from "downloading" to "downloaded", the updated item is now announced correctly. (#15859, @LeonarddeR)
7070
- When installing add-ons install prompts are no longer overlapped by the restart dialog. (#15613, @lukaszgo1)
7171
- When reinstalling an incompatible add-on it is no longer forcefully disabled. (#15584, @lukaszgo1)
72+
- Disabled and incompatible add-ons can now be updated. (#15568, #15029)
7273
-
7374
- Audio:
7475
- NVDA no longer freezes briefly when multiple sounds are played in rapid succession. (#15311, #15757, @jcsteh)

0 commit comments

Comments
 (0)