Skip to content

Commit

Permalink
Add ability to sort add-ons by columns (#17135)
Browse files Browse the repository at this point in the history
Fixes #15277

Summary of the issue:
Currently, add-ons in the store can't be sortered by column, which is desirable to search for add-ons in a confortable way.

Description of user facing changes
A combo box with available columns has been added to the store dialog, to sort by columns in ascending or descending order.
Description of development approach
A combo box has been added before the combo box channel.
_columnSortChoices property, and an optional reverse boolean parameter to setSortField function (to set a self._reverseSort_variable) have been added to the AddonListVMclass to build the combo box with ascending and descending options.
  • Loading branch information
nvdaes authored Sep 17, 2024
1 parent 8827e60 commit 4cfe718
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 2 deletions.
26 changes: 26 additions & 0 deletions source/gui/addonStoreGui/controls/storeDialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,18 @@ def _createFilterControls(self, filterCtrlHelper: guiHelper.BoxSizerHelper) -> N
filterCtrlsLine1.sizer.AddSpacer(FILTER_MARGIN_PADDING)
filterCtrlHelper.addItem(filterCtrlsLine1.sizer, flag=wx.EXPAND, proportion=1)

self.columnFilterCtrl = cast(
wx.Choice,
filterCtrlsLine0.addLabeledControl(
# Translators: The label of a selection field to sort the list of add-ons in the add-on store dialog.
labelText=pgettext("addonStore", "Sort by colu&mn:"),
wxCtrlClass=wx.Choice,
choices=self._storeVM.listVM._columnSortChoices,
),
)
self.columnFilterCtrl.Bind(wx.EVT_CHOICE, self.onColumnFilterChange, self.columnFilterCtrl)
self.bindHelpEvent("AddonStoreSortByColumn", self.columnFilterCtrl)

self.channelFilterCtrl = cast(
wx.Choice,
filterCtrlsLine0.addLabeledControl(
Expand Down Expand Up @@ -323,6 +335,9 @@ def _setListLabels(self):
self.SetTitle(self._titleText)

def _toggleFilterControls(self):
self.columnFilterCtrl.Clear()
for c in self._storeVM.listVM._columnSortChoices:
self.columnFilterCtrl.Append(c)
self.channelFilterCtrl.Clear()
for c in _channelFilters:
if c != Channel.EXTERNAL:
Expand Down Expand Up @@ -358,6 +373,8 @@ def onListTabPageChange(self, evt: wx.EVT_CHOICE):
self._storeVM._filteredStatusKey = self._statusFilterKey
self.addonListView._refreshColumns()
self._toggleFilterControls()
self.columnFilterCtrl.SetSelection(0)
self._storeVM.listVM.setSortField(self._storeVM.listVM.presentedFields[0])

channelFilterIndex = list(_channelFilters.keys()).index(self._storeVM._filterChannelKey)
self.channelFilterCtrl.SetSelection(channelFilterIndex)
Expand All @@ -370,6 +387,15 @@ def onListTabPageChange(self, evt: wx.EVT_CHOICE):
if not self.addonListTabs.HasFocus():
self.addonListTabs.SetFocus()

def onColumnFilterChange(self, evt: wx.EVT_CHOICE):
# Each col index will correspond to 2 choices in the combo box (ascending and descending)
colIndex = evt.GetSelection() // 2
# Descending sort should be applied for odd choices of the combo box
reverse = evt.GetSelection() % 2
self._storeVM.listVM.setSortField(self._storeVM.listVM.presentedFields[colIndex], reverse)
log.debug(f"sortered by: {colIndex}; reversed: {reverse}")
self._storeVM.refresh()

def onChannelFilterChange(self, evt: wx.EVT_CHOICE):
self._storeVM._filterChannelKey = self._channelFilterKey
self._storeVM.listVM.setSelection(None)
Expand Down
34 changes: 32 additions & 2 deletions source/gui/addonStoreGui/viewModels/addonList.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ def __init__(
self.lastSelectedAddonId = self.selectedAddonId
self._sortByModelField: AddonListField = AddonListField.displayName
self._filterString: Optional[str] = None
self._reverseSort: bool = False

self._setSelectionPending = False
self._addonsFilteredOrdered: List[str] = self._getFilteredSortedIds()
Expand Down Expand Up @@ -340,15 +341,42 @@ def _validate(
if selectionId is not None:
assert selectionId in self._addons.keys()

def setSortField(self, modelField: AddonListField):
def setSortField(self, modelField: AddonListField, reverse: bool = False):
oldOrder = self._addonsFilteredOrdered
self._validate(sortField=modelField)
self._sortByModelField = modelField
self._reverseSort = reverse
self._updateAddonListing()
if oldOrder != self._addonsFilteredOrdered:
# ensure calling on the main thread.
core.callLater(delay=0, callable=self.updated.notify)

@property
def _columnSortChoices(self) -> list[str]:
columnChoices = []
for c in self.presentedFields:
columnChoices.append(
pgettext(
"addonStore",
# Translators: An option of a combo box to sort columns in the add-on store, in ascending order.
# {column} will be replaced with the column display string.
"{column} (ascending)",
).format(
column=c.displayString,
),
)
columnChoices.append(
pgettext(
"addonStore",
# Translators: An option of a combo box to sort columns in the add-on store, in descending order.
# {column} will be replaced with the column display string.
"{column} (descending)",
).format(
column=c.displayString,
),
)
return columnChoices

def _getFilteredSortedIds(self) -> List[str]:
def _getSortFieldData(listItemVM: AddonListItemVM) -> "SupportsLessThan":
return strxfrm(self._getAddonFieldText(listItemVM, self._sortByModelField))
Expand All @@ -371,7 +399,9 @@ def _containsTerm(detailsVM: AddonListItemVM, term: str) -> bool:
for vm in self._addons.values()
if self._filterString is None or _containsTerm(vm, self._filterString)
)
filteredSorted = list([vm.Id for vm in sorted(filtered, key=_getSortFieldData)])
filteredSorted = list(
[vm.Id for vm in sorted(filtered, key=_getSortFieldData, reverse=self._reverseSort)],
)
return filteredSorted

def _tryPersistSelection(
Expand Down
10 changes: 10 additions & 0 deletions tests/manual/addonStore.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ Add-ons can be filtered by display name, publisher and description.
1. Enable the "Include incompatible add-ons" filter
1. Ensure add-ons with status "incompatible" are included in the list with the available add-ons.

### Sorting the add-ons list by column
1. Open the Add-on Store
1. Sort by column:
1. Using the combo-box:
1. Jump to the sort by column field (`alt+m`)
1. Select different columns (ascending or descending order)
1. Alternatively, perform a left mouse click on different columns
1. Ensure that the add-ons list is sorted accordingly
1. Change to different tabs, and repeat the previous steps.

### Failure to fetch add-ons available for download
1. Disable your internet connection
1. Go to your [NVDA user configuration folder](#editing-user-configuration)
Expand Down
1 change: 1 addition & 0 deletions user_docs/en/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ In order to use this feature, the application volume adjuster needs to be enable
* Added an action in the Add-on Store to cancel the install of add-ons. (#15578, @hwf1324)
* Added an action in the Add-on Store to retry the installation if the download/installation of an add-on fails. (#17090, @hwf1324)
* It is now possible to specify a mirror URL to use for the Add-on Store. (#14974)
* The add-ons lists in the Add-on Store can be sorted by columns. (#15277, @nvdaes)
* When decreasing or increasing the font size in LibreOffice Writer using the corresponding keyboard shortcuts, NVDA announces the new font size. (#6915, @michaelweghorn)
* When applying the "Body Text" or a heading paragraph style using the corresponding keyboard shortcut in LibreOffice Writer 25.2 or newer, NVDA announces the new paragraph style. (#6915, @michaelweghorn)
* Automatic language switching is now supported when using Microsoft Speech API version 5 (SAPI5) and Microsoft Speech Platform voices. (#17146, @gexgd0419)
Expand Down
7 changes: 7 additions & 0 deletions user_docs/en/userGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -3637,6 +3637,13 @@ You can reach it by pressing `shift+tab` from the list of add-ons.
Type a keyword or two for the kind of add-on you're looking for, then `tab` to the list of add-ons.
Add-ons will be listed if the search text can be found in the add-on ID, display name, publisher, author or description.

#### Sorting the add-ons list by column {#AddonStoreSortByColumn}

By default, the add-ons list is sorted by the add-ons' display name.
The "Sort by column" combo box can be used to sort the list by the available columns for each tab.
For example, you may wish to sort add-ons by publisher, available version, etc.
Add-ons can be sortered in ascending or descending order.

### Add-on actions {#AddonStoreActions}

Add-ons have associated actions, such as install, help, disable, and remove.
Expand Down

0 comments on commit 4cfe718

Please sign in to comment.