addonGui.py
13.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
#gui/addonGui.py
#A part of NonVisual Desktop Access (NVDA)
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
#Copyright (C) 2012-2016 NV Access Limited, Beqa Gozalishvili, Joseph Lee
import os
import wx
import core
import languageHandler
import gui
from logHandler import log
import addonHandler
import globalVars
class AddonsDialog(wx.Dialog):
_instance = None
def __new__(cls, *args, **kwargs):
if AddonsDialog._instance is None:
return super(AddonsDialog, cls).__new__(cls, *args, **kwargs)
return AddonsDialog._instance
def __init__(self,parent):
if AddonsDialog._instance is not None:
return
AddonsDialog._instance = self
# Translators: The title of the Addons Dialog
super(AddonsDialog,self).__init__(parent,title=_("Add-ons Manager"))
mainSizer=wx.BoxSizer(wx.VERTICAL)
settingsSizer=wx.BoxSizer(wx.VERTICAL)
entriesSizer=wx.BoxSizer(wx.VERTICAL)
if globalVars.appArgs.disableAddons:
# Translators: A message in the add-ons manager shown when all add-ons are disabled.
addonsDisabledLabel=wx.StaticText(self,-1,label=_("All add-ons are currently disabled. To enable add-ons you must restart NVDA."))
mainSizer.Add(addonsDisabledLabel)
# Translators: the label for the installed addons list in the addons manager.
entriesLabel=wx.StaticText(self,-1,label=_("Installed Add-ons"))
entriesSizer.Add(entriesLabel)
self.addonsList=wx.ListCtrl(self,-1,style=wx.LC_REPORT|wx.LC_SINGLE_SEL,size=(550,350))
# Translators: The label for a column in add-ons list used to identify add-on package name (example: package is OCR).
self.addonsList.InsertColumn(0,_("Package"),width=150)
# Translators: The label for a column in add-ons list used to identify add-on's running status (example: status is running).
self.addonsList.InsertColumn(1,_("Status"),width=50)
# Translators: The label for a column in add-ons list used to identify add-on's version (example: version is 0.3).
self.addonsList.InsertColumn(2,_("Version"),width=50)
# Translators: The label for a column in add-ons list used to identify add-on's author (example: author is NV Access).
self.addonsList.InsertColumn(3,_("Author"),width=300)
self.addonsList.Bind(wx.EVT_LIST_ITEM_FOCUSED, self.onListItemSelected)
entriesSizer.Add(self.addonsList,proportion=8)
settingsSizer.Add(entriesSizer)
entryButtonsSizer=wx.BoxSizer(wx.HORIZONTAL)
# Translators: The label for a button in Add-ons Manager dialog to show information about the selected add-on.
self.aboutButton=wx.Button(self,label=_("&About add-on..."))
self.aboutButton.Disable()
self.aboutButton.Bind(wx.EVT_BUTTON,self.onAbout)
entryButtonsSizer.Add(self.aboutButton)
# Translators: The label for a button in Add-ons Manager dialog to show the help for the selected add-on.
self.helpButton=wx.Button(self,label=_("Add-on &help"))
self.helpButton.Disable()
self.helpButton.Bind(wx.EVT_BUTTON,self.onHelp)
entryButtonsSizer.Add(self.helpButton)
# Translators: The label for a button in Add-ons Manager dialog to enable or disable the selected add-on.
self.enableDisableButton=wx.Button(self,label=_("Disable add-on"))
self.enableDisableButton.Disable()
self.enableDisableButton.Bind(wx.EVT_BUTTON,self.onEnableDisable)
entryButtonsSizer.Add(self.enableDisableButton)
# Translators: The label for a button in Add-ons Manager dialog to install an add-on.
self.addButton=wx.Button(self,label=_("&Install..."))
self.addButton.Bind(wx.EVT_BUTTON,self.onAddClick)
entryButtonsSizer.Add(self.addButton)
# Translators: The label for a button to remove either:
# Remove the selected add-on in Add-ons Manager dialog.
# Remove a speech dictionary entry.
self.removeButton=wx.Button(self,label=_("&Remove"))
self.removeButton.Disable()
self.removeButton.Bind(wx.EVT_BUTTON,self.onRemoveClick)
entryButtonsSizer.Add(self.removeButton)
# Translators: The label of a button in Add-ons Manager to open the Add-ons website and get more add-ons.
self.getAddonsButton=wx.Button(self,label=_("&Get add-ons..."))
self.getAddonsButton.Bind(wx.EVT_BUTTON,self.onGetAddonsClick)
entryButtonsSizer.Add(self.getAddonsButton)
settingsSizer.Add(entryButtonsSizer)
mainSizer.Add(settingsSizer,border=20,flag=wx.LEFT|wx.RIGHT|wx.TOP)
# Translators: The label of a button to close the Addons dialog.
closeButton = wx.Button(self, label=_("&Close"), id=wx.ID_CLOSE)
closeButton.Bind(wx.EVT_BUTTON, lambda evt: self.Close())
mainSizer.Add(closeButton,border=20,flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.CENTER)
self.Bind(wx.EVT_CLOSE, self.onClose)
self.EscapeId = wx.ID_CLOSE
mainSizer.Fit(self)
self.SetSizer(mainSizer)
self.refreshAddonsList()
self.addonsList.SetFocus()
self.Center(wx.BOTH | wx.CENTER_ON_SCREEN)
def onAddClick(self,evt):
# Translators: The message displayed in the dialog that allows you to choose an add-on package for installation.
fd=wx.FileDialog(self,message=_("Choose Add-on Package File"),
# Translators: the label for the NVDA add-on package file type in the Choose add-on dialog.
wildcard=(_("NVDA Add-on Package (*.{ext})")+"|*.{ext}").format(ext=addonHandler.BUNDLE_EXTENSION),
defaultDir="c:",style=wx.FD_OPEN)
if fd.ShowModal()!=wx.ID_OK:
return
addonPath=fd.GetPath()
self.installAddon(addonPath)
def installAddon(self, addonPath, closeAfter=False):
try:
try:
bundle=addonHandler.AddonBundle(addonPath)
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
# Translators: A message asking the user if they really wish to install an addon.
if gui.messageBox(_("Are you sure you want to install this add-on? Only install add-ons from trusted sources.\nAddon: {summary} {version}\nAuthor: {author}").format(**bundle.manifest),
# Translators: Title for message asking if the user really wishes to install an Addon.
_("Add-on Installation"),
wx.YES|wx.NO|wx.ICON_WARNING)!=wx.YES:
return
bundleName=bundle.manifest['name']
prevAddon=None
for addon in self.curAddons:
if not addon.isPendingRemove and bundleName==addon.manifest['name']:
prevAddon=addon
break
if prevAddon:
# Translators: A message asking if the user wishes to update a previously installed add-on with this one.
if gui.messageBox(_("A version of this add-on is already installed. Would you like to update it?"),
# Translators: A title for the dialog asking if the user wishes to update a previously installed add-on with this one.
_("Add-on Installation"),
wx.YES|wx.NO|wx.ICON_WARNING)!=wx.YES:
return
prevAddon.requestRemove()
progressDialog = gui.IndeterminateProgressDialog(gui.mainFrame,
# Translators: The title of the dialog presented while an Addon is being installed.
_("Installing Add-on"),
# Translators: The message displayed while an addon is being installed.
_("Please wait while the add-on is being installed."))
try:
gui.ExecAndPump(addonHandler.installAddonBundle,bundle)
except:
log.error("Error installing addon bundle from %s"%addonPath,exc_info=True)
self.refreshAddonsList()
progressDialog.done()
del progressDialog
# Translators: The message displayed when an error occurs when installing an add-on package.
gui.messageBox(_("Failed to install add-on from %s")%addonPath,
# Translators: The title of a dialog presented when an error occurs.
_("Error"),
wx.OK | wx.ICON_ERROR)
return
else:
self.refreshAddonsList(activeIndex=-1)
progressDialog.done()
del progressDialog
finally:
if closeAfter:
# #4460: If we do this immediately, wx seems to drop the WM_QUIT sent if the user chooses to restart.
# This seems to have something to do with the wx.ProgressDialog.
# The CallLater seems to work around this.
wx.CallLater(1, self.Close)
def onRemoveClick(self,evt):
index=self.addonsList.GetFirstSelected()
if index<0: return
# Translators: Presented when attempting to remove the selected add-on.
if gui.messageBox(_("Are you sure you wish to remove the selected add-on from NVDA?"),
# Translators: Title for message asking if the user really wishes to remove the selected Addon.
_("Remove Add-on"), wx.YES_NO|wx.ICON_WARNING) != wx.YES: return
addon=self.curAddons[index]
addon.requestRemove()
self.refreshAddonsList(activeIndex=index)
self.addonsList.SetFocus()
def getAddonStatus(self,addon):
if addon.isPendingInstall:
# Translators: The status shown for a newly installed addon before NVDA is restarted.
return _("install")
elif addon.isPendingRemove:
# Translators: The status shown for an addon that has been marked as removed, before NVDA has been restarted.
return _("remove")
# Need to do this here, as 'isDisabled' overrides other flags.
elif addon.isPendingDisable:
# Translators: The status shown for an addon when its disabled.
return _("disable")
elif addon.isPendingEnable:
# Translators: The status shown for an addon when its enabled.
return _("enable")
elif globalVars.appArgs.disableAddons or addon.isDisabled:
# Translators: The status shown for an addon when its currently suspended do to addons being disabled.
return _("suspended")
else:
# Translators: The status shown for an addon when its currently running in NVDA.
return _("running")
def refreshAddonsList(self,activeIndex=0):
self.addonsList.DeleteAllItems()
self.curAddons=[]
for addon in addonHandler.getAvailableAddons():
self.addonsList.Append((addon.manifest['summary'], self.getAddonStatus(addon), addon.manifest['version'], addon.manifest['author']))
self.curAddons.append(addon)
# select the given active addon or the first addon if not given
curAddonsLen=len(self.curAddons)
if curAddonsLen>0:
if activeIndex==-1:
activeIndex=curAddonsLen-1
elif activeIndex<0 or activeIndex>=curAddonsLen:
activeIndex=0
self.addonsList.Select(activeIndex,on=1)
self.addonsList.SetItemState(activeIndex,wx.LIST_STATE_FOCUSED,wx.LIST_STATE_FOCUSED)
else:
self.aboutButton.Disable()
self.helpButton.Disable()
self.removeButton.Disable()
def _shouldDisable(self, addon):
return not (addon.isPendingDisable or (addon.isDisabled and not addon.isPendingEnable))
def onListItemSelected(self, evt):
index=evt.GetIndex()
addon=self.curAddons[index] if index>=0 else None
# #3090: Change toggle button label to indicate action to be taken if clicked.
if addon is not None:
# Translators: The label for a button in Add-ons Manager dialog to enable or disable the selected add-on.
self.enableDisableButton.SetLabel(_("Enable add-on") if not self._shouldDisable(addon) else _("Disable add-on"))
self.aboutButton.Enable(addon is not None and not addon.isPendingRemove)
self.helpButton.Enable(bool(addon is not None and not addon.isPendingRemove and addon.getDocFilePath()))
self.enableDisableButton.Enable(addon is not None and not addon.isPendingRemove)
self.removeButton.Enable(addon is not None and not addon.isPendingRemove)
def onClose(self,evt):
self.Destroy()
needsRestart = False
for addon in self.curAddons:
if (addon.isPendingInstall or addon.isPendingRemove
or addon.isDisabled and addon.isPendingEnable
or addon.isRunning and addon.isPendingDisable):
needsRestart = True
break
if needsRestart:
# Translators: A message asking the user if they wish to restart NVDA as addons have been added, enabled/disabled or removed.
if gui.messageBox(_("Changes were made to add-ons. You must restart NVDA for these changes to take effect. Would you like to restart now?"),
# Translators: Title for message asking if the user wishes to restart NVDA as addons have been added or removed.
_("Restart NVDA"),
wx.YES|wx.NO|wx.ICON_WARNING)==wx.YES:
core.restart()
def onAbout(self,evt):
index=self.addonsList.GetFirstSelected()
if index<0: return
manifest=self.curAddons[index].manifest
# Translators: message shown in the Addon Information dialog.
message=_("""{summary} ({name})
Version: {version}
Author: {author}
Description: {description}
""").format(**manifest)
url=manifest.get('url')
if url:
# Translators: the url part of the About Add-on information
message+=_("URL: {url}").format(url=url)
# Translators: title for the Addon Information dialog
title=_("Add-on Information")
gui.messageBox(message, title, wx.OK)
def onHelp(self, evt):
index = self.addonsList.GetFirstSelected()
if index < 0:
return
path = self.curAddons[index].getDocFilePath()
os.startfile(path)
def onEnableDisable(self, evt):
index=self.addonsList.GetFirstSelected()
if index<0: return
addon=self.curAddons[index]
shouldDisable = self._shouldDisable(addon)
# Counterintuitive, but makes sense when context is taken into account.
addon.enable(not shouldDisable)
self.enableDisableButton.SetLabel(_("Enable add-on") if shouldDisable else _("Disable add-on"))
self.refreshAddonsList(activeIndex=index)
def onGetAddonsClick(self,evt):
ADDONS_URL = "http://addons.nvda-project.org"
os.startfile(ADDONS_URL)
def __del__(self):
AddonsDialog._instance = None
@classmethod
def handleRemoteAddonInstall(cls, addonPath):
closeAfter = AddonsDialog._instance is None
dialog = AddonsDialog(gui.mainFrame)
dialog.installAddon(addonPath, closeAfter=closeAfter)
del dialog