|
15 | 15 | from locale import strxfrm |
16 | 16 | import re |
17 | 17 | import typing |
| 18 | +import requests |
18 | 19 | import wx |
19 | 20 | from NVDAState import WritePaths |
20 | 21 |
|
|
62 | 63 | Set, |
63 | 64 | cast, |
64 | 65 | ) |
65 | | -from url_normalize import url_normalize |
66 | 66 | import core |
67 | 67 | import keyboardHandler |
68 | 68 | import characterProcessing |
@@ -996,7 +996,7 @@ def onChangeMirrorURL(self, evt: wx.CommandEvent | wx.KeyEvent): |
996 | 996 | # Translators: Title of the dialog used to change NVDA's update server mirror URL. |
997 | 997 | title=_("Set NVDA Update Mirror"), |
998 | 998 | configPath=("update", "serverURL"), |
999 | | - helpId="SetUpdateMirror", |
| 999 | + helpId="SetURLDialog", |
1000 | 1000 | urlTransformer=lambda url: f"{url}?versionType=stable", |
1001 | 1001 | ) |
1002 | 1002 | ret = changeMirror.ShowModal() |
@@ -3274,27 +3274,83 @@ def makeSettings(self, settingsSizer: wx.BoxSizer) -> None: |
3274 | 3274 | index = [x.value for x in AddonsAutomaticUpdate].index(config.conf["addonStore"]["automaticUpdates"]) |
3275 | 3275 | self.automaticUpdatesComboBox.SetSelection(index) |
3276 | 3276 |
|
3277 | | - # Translators: This is the label for a text box in the add-on store settings dialog. |
3278 | | - self.addonMetadataMirrorLabelText = _("Server &mirror URL") |
3279 | | - self.addonMetadataMirrorTextbox = sHelper.addLabeledControl( |
3280 | | - self.addonMetadataMirrorLabelText, |
3281 | | - wx.TextCtrl, |
| 3277 | + # Translators: The label for the server mirror on the Add-on store Settings panel. |
| 3278 | + mirrorBoxSizer = wx.StaticBoxSizer(wx.HORIZONTAL, self, label=_("Server mirror")) |
| 3279 | + mirrorBox = mirrorBoxSizer.GetStaticBox() |
| 3280 | + mirrorBoxSizerHelper = guiHelper.BoxSizerHelper(self, sizer=mirrorBoxSizer) |
| 3281 | + sHelper.addItem(mirrorBoxSizerHelper) |
| 3282 | + |
| 3283 | + # Use an ExpandoTextCtrl because even when read-only it accepts focus from keyboard, which |
| 3284 | + # standard read-only TextCtrl does not. ExpandoTextCtrl is a TE_MULTILINE control, however |
| 3285 | + # by default it renders as a single line. Standard TextCtrl with TE_MULTILINE has two lines, |
| 3286 | + # and a vertical scroll bar. This is not neccessary for the single line of text we wish to |
| 3287 | + # display here. |
| 3288 | + # Note: To avoid code duplication, the value of this text box will be set in `onPanelActivated`. |
| 3289 | + self.mirrorURLTextBox = ExpandoTextCtrl( |
| 3290 | + mirrorBox, |
| 3291 | + size=(self.scaleSize(250), -1), |
| 3292 | + style=wx.TE_READONLY, |
3282 | 3293 | ) |
3283 | | - self.addonMetadataMirrorTextbox.SetValue(config.conf["addonStore"]["baseServerURL"]) |
3284 | | - self.bindHelpEvent("AddonStoreMetadataMirror", self.addonMetadataMirrorTextbox) |
| 3294 | + # Translators: This is the label for the button used to change the Add-on Store mirror URL, |
| 3295 | + # it appears in the context of the Server mirror group on the Add-on Store page of NVDA's settings. |
| 3296 | + changeMirrorBtn = wx.Button(mirrorBox, label=_("Change...")) |
| 3297 | + mirrorBoxSizerHelper.addItem( |
| 3298 | + guiHelper.associateElements( |
| 3299 | + self.mirrorURLTextBox, |
| 3300 | + changeMirrorBtn, |
| 3301 | + ), |
| 3302 | + ) |
| 3303 | + self.bindHelpEvent("AddonStoreMetadataMirror", mirrorBox) |
| 3304 | + self.mirrorURLTextBox.Bind(wx.EVT_CHAR_HOOK, self._enterTriggersOnChangeMirrorURL) |
| 3305 | + changeMirrorBtn.Bind(wx.EVT_BUTTON, self.onChangeMirrorURL) |
3285 | 3306 |
|
3286 | | - def isValid(self) -> bool: |
3287 | | - self.addonMetadataMirrorTextbox.SetValue( |
3288 | | - url_normalize(self.addonMetadataMirrorTextbox.GetValue().strip()).rstrip("/"), |
| 3307 | + def onChangeMirrorURL(self, evt: wx.CommandEvent | wx.KeyEvent): |
| 3308 | + """Show the dialog to change the Add-on Store mirror URL, and refresh the dialog in response to the URL being changed.""" |
| 3309 | + # Import late to avoid circular dependency. |
| 3310 | + from gui._SetURLDialog import _SetURLDialog |
| 3311 | + |
| 3312 | + changeMirror = _SetURLDialog( |
| 3313 | + self, |
| 3314 | + # Translators: Title of the dialog used to change the Add-on Store server mirror URL. |
| 3315 | + title=_("Set Add-on Store Server Mirror"), |
| 3316 | + configPath=("addonStore", "baseServerURL"), |
| 3317 | + helpId="SetURLDialog", |
| 3318 | + urlTransformer=lambda url: f"{url}/cacheHash.json", |
| 3319 | + responseValidator=_isResponseAddonStoreCacheHash, |
3289 | 3320 | ) |
3290 | | - return True |
| 3321 | + ret = changeMirror.ShowModal() |
| 3322 | + if ret == wx.ID_OK: |
| 3323 | + self.Freeze() |
| 3324 | + # trigger a refresh of the settings |
| 3325 | + self.onPanelActivated() |
| 3326 | + self._sendLayoutUpdatedEvent() |
| 3327 | + self.Thaw() |
| 3328 | + |
| 3329 | + def _enterTriggersOnChangeMirrorURL(self, evt: wx.KeyEvent): |
| 3330 | + """Open the change update mirror URL dialog in response to the enter key in the mirror URL read-only text box.""" |
| 3331 | + if evt.KeyCode == wx.WXK_RETURN: |
| 3332 | + self.onChangeMirrorURL(evt) |
| 3333 | + else: |
| 3334 | + evt.Skip() |
| 3335 | + |
| 3336 | + def _updateCurrentMirrorURL(self): |
| 3337 | + self.mirrorURLTextBox.SetValue( |
| 3338 | + ( |
| 3339 | + url |
| 3340 | + if (url := config.conf["addonStore"]["baseServerURL"]) |
| 3341 | + # Translators: A value that appears in NVDA's Settings to indicate that no mirror is in use. |
| 3342 | + else _("No mirror") |
| 3343 | + ), |
| 3344 | + ) |
| 3345 | + |
| 3346 | + def onPanelActivated(self): |
| 3347 | + self._updateCurrentMirrorURL() |
| 3348 | + super().onPanelActivated() |
3291 | 3349 |
|
3292 | 3350 | def onSave(self): |
3293 | 3351 | index = self.automaticUpdatesComboBox.GetSelection() |
3294 | 3352 | config.conf["addonStore"]["automaticUpdates"] = [x.value for x in AddonsAutomaticUpdate][index] |
3295 | 3353 |
|
3296 | | - config.conf["addonStore"]["baseServerURL"] = self.addonMetadataMirrorTextbox.Value.strip().rstrip("/") |
3297 | | - |
3298 | 3354 |
|
3299 | 3355 | class TouchInteractionPanel(SettingsPanel): |
3300 | 3356 | # Translators: This is the label for the touch interaction settings panel. |
@@ -5520,3 +5576,14 @@ def onFilterEditTextChange(self, evt): |
5520 | 5576 | self.filter(self.filterEdit.Value) |
5521 | 5577 | self._refreshVisibleItems() |
5522 | 5578 | evt.Skip() |
| 5579 | + |
| 5580 | + |
| 5581 | +def _isResponseAddonStoreCacheHash(response: requests.Response) -> bool: |
| 5582 | + try: |
| 5583 | + # Attempt to parse the response as JSON |
| 5584 | + data = response.json() |
| 5585 | + except ValueError: |
| 5586 | + # Add-on Store cache hash is JSON, so this can't be it. |
| 5587 | + return False |
| 5588 | + # Add-on Store cache hash is a git commit hash as a string. |
| 5589 | + return isinstance(data, str) and bool(re.fullmatch("[0-9a-fA-F]{7,40}", data)) |
0 commit comments