1
2
3 import ast
4 import os
5 import sys
6 import traceback
7 import types
8
9 import empro
10 from empro.toolkit import _printexception
14 - def __init__(self, menuItem=None, onContextMenu=None):
17
20 '''
21 Helper function to wrap a Python function into a gui.Action.
22 - title: string (which can contain ampersands for shortcuts)
23 - onTriggered: Python function that does not require arguments
24 - icon: optional, either an gui.Icon, or a file path to an icon image.
25 '''
26 from empro import gui
27 def func(checked):
28 try:
29 try:
30 onTriggered()
31 except:
32 gui.MessageBox.critical( "Add-on Error", u"A fatal error occurred while executing add-on call:\n\n%s" % traceback.format_exc(), gui.Ok, gui.Ok )
33 except:
34 traceback.print_exc()
35 action = gui.Action(title)
36 action.onTriggered = func
37 if isinstance(icon, gui.Icon):
38 action.icon = icon
39 elif icon:
40 action.icon = gui.Icon(icon)
41 return action
42
43
44 -def makeContextAction(title, onTriggered, icon=None):
45 '''
46 Helper function to wrap a Python function into a gui.Action for a context menu
47 - title: string (which can contain ampersands for shortcuts)
48 - onTriggered: Python function that does require one argument: the context selection.
49 - icon: optional, either an gui.Icon, or a file path to an icon image.
50 '''
51 from empro import gui
52 def func(checked):
53 try:
54 selection = gui.SelectionList.globalSelectionList().selection()
55 try:
56 onTriggered(selection)
57 except:
58 gui.MessageBox.critical( "Add-on Error", u"A fatal error occurred while executing add-on call:\n\n%s" % traceback.format_exc(), gui.Ok, gui.Ok )
59 except:
60 traceback.print_exc()
61 action = gui.Action(title)
62 action.onTriggered = func
63 if isinstance(icon, gui.Icon):
64 action.icon = icon
65 elif icon:
66 action.icon = gui.Icon(icon)
67 return action
68
71 '''
72 Search additional add-ons in searchDirs, and load them.
73 searchDirs can be a string or list of directories.
74 When forceEnabled is True, the new-found add-ons will be enabled regardless
75 of the user settings.
76
77 Add-ons loaded before calling loadAdditionalAddons can influence the
78 behaviour of this call. If a add-on by the same name but different
79 location is already loaded, the new one will be ignored.
80 '''
81 addons = _searchAddons(searchDirs, forceEnabled=forceEnabled)
82 _loadAddons(addons)
83 return addons
84
89 addons = _searchAddons()
90 _loadAddons(addons)
91
94 if not searchDirs:
95 searchDirs = (_additionalSearchPath().split(os.pathsep) +
96 _platformSearchDirs() +
97 _extraPlatformSearchDirs())
98 if isinstance(searchDirs, basestring):
99 searchDirs = searchDirs.split(os.pathsep)
100
101 try:
102 empro.internal._addons
103 except AttributeError:
104 empro.internal._addons = {}
105
106 addons = {}
107 for location in map(_normPath, searchDirs):
108 if not location or not os.path.isdir(location):
109 continue
110
111 for fname in os.listdir(location):
112 if fname.startswith('_'):
113 continue
114 name, ext = os.path.splitext(fname)
115 fpath = path = os.path.normpath(os.path.join(location, fname))
116 if os.path.isdir(path):
117
118
119 for ext in _SUFFIXES:
120 fpath = os.path.join(path, '__init__' + ext)
121 if os.path.isfile(fpath):
122 break
123 else:
124 continue
125 assert os.path.isfile(fpath), fpath
126 if not ext in _SUFFIXES:
127 continue
128
129 try:
130 found = empro.internal._addons[name]
131 except KeyError:
132 pass
133 else:
134 if found.module or found.error:
135 continue
136 if os.path.dirname(path) != os.path.dirname(found.path):
137 continue
138 try:
139 if _SUFFIXES.index(ext) >= _SUFFIXES.index(os.path.splitext(found.path)[1]):
140 continue
141 except ValueError:
142
143 continue
144
145 addon = _loadAddonMetaData(path, fpath)
146 if not addon:
147 continue
148 addon.forceEnabled = addon.forceEnabled or forceEnabled
149 addons[name] = empro.internal._addons[name] = addon
150
151 return addons
152
190
193 if "--no-load-addons" in sys.argv:
194 return
195
196 personality = empro.core.ApplicationInfo.personality()
197
198 namespace = 'empro.addons'
199 try:
200 empro.addons = sys.modules[namespace]
201 except KeyError:
202 empro.addons = sys.modules[namespace] = types.ModuleType(namespace)
203
204 enabledAddons = _enabledAddons()
205 for (name, addon) in sorted(addons.items()):
206 defaultEnabled = os.path.dirname(addon.path) in _platformSearchDirs()
207 addon.enabled = addon.forceEnabled or enabledAddons.get(name, defaultEnabled)
208
209 if hasattr(addon,"personalities"):
210
211 if addon.personalities=="all" or "all" in addon.personalities:
212 pass
213 else:
214 if personality not in addon.personalities:
215 continue
216
217
218 addon.available = True
219 if not addon.enabled:
220 continue
221
222 if addon.module:
223 continue
224 try:
225 fullName = "{}.{}".format(namespace, name)
226 addon.module = _importModule(fullName, addon.path)
227 setattr(empro.addons, name, addon.module)
228 addon.definition = addon.module._defineAddon()
229 except:
230 addon.error = ''.join(traceback.format_exception_only(*sys.exc_info()[:2])).strip()
231 addon.traceback = traceback.format_exc().strip()
232 continue
233
234 _makeAddonMenu(addons)
235 _prepareOnContextMenus(addons)
236
239 if not name in sys.modules:
240 sys.modules[name] = _loadModule(name, path)
241 return sys.modules[name]
242
243
244 try:
245 import importlib.machinery
246 import importlib.util
247 except ImportError:
248
249 import imp
250 _SUFFIXES = [
251 suffix for (suffix, m, t) in imp.get_suffixes() if t == imp.PY_SOURCE
252 ]
255 imp.acquire_lock()
256 try:
257 basename = name.rsplit('.', 1)[-1]
258 f, modpath, desc = imp.find_module(basename, [os.path.dirname(path)])
259 return imp.load_module(basename, f, modpath, desc)
260 finally:
261 imp.release_lock()
262 else:
263 _SUFFIXES = importlib.machinery.SOURCE_SUFFIXES
266
267 spec = importlib.util.spec_from_file_location(name, path)
268 module = importlib.util.module_from_spec(spec)
269 spec.loader.exec_module(module)
270 return module
271
274 toolsMenu = empro.gui.activeProjectView().menu("tools")
275
276 personality = empro.core.ApplicationInfo.personality()
277
278 try:
279 menuItems, insertPoint = empro.internal._addonMenuRange
280 except AttributeError:
281
282 oldTop = toolsMenu.actions()[0]
283 toolsMenu.insertSeparator(oldTop)
284 insertPoint = toolsMenu.insertSeparator(oldTop)
285 empro.internal._addonManager = toolsMenu.insertAction(oldTop,
286 makeAction('&Add-on Manager...', _showAddonManager, icon=':/workspace/ScriptWorkspaceWindow.ico'))
287 else:
288
289 for item in menuItems:
290 toolsMenu.removeAction(item)
291 _unregisterActions()
292
293 menuItems = []
294 try:
295 for name, addon in sorted(addons.items()):
296 if not addon.enabled or not addon.definition:
297 continue
298 menuItem = addon.definition.menuItem
299
300 if isinstance(menuItem, empro.gui.Action):
301 menuItems.append(toolsMenu.insertAction(insertPoint, menuItem))
302 _registerAction(menuItem)
303 elif isinstance(menuItem, empro.gui.Menu):
304 menuItems.append(toolsMenu.insertMenu(insertPoint, menuItem))
305 _registerAction(menuItem)
306 finally:
307 empro.internal._addonMenuRange = menuItems, insertPoint
308
311 onContextMenus = []
312 for (name, addon) in sorted(addons.items()):
313 if not addon.enabled or not addon.definition:
314 continue
315 if addon.definition.onContextMenu:
316 onContextMenus.append(addon.definition.onContextMenu)
317 empro.internal._addonOnContextMenus = onContextMenus
318
335
336
337 _ADDON_DOWNLOAD_URL = "http://www.keysight.com/find/eesof-empro-addons"
340 from empro import gui
341
342
343 _searchAddons()
344
345 dialog = gui.SimpleDialog(gui.Ok | gui.Cancel)
346 appName = empro.core.ApplicationInfo.applicationName()
347 appName = appName.replace("Setup","")
348 dialog.title = "Keysight %s - Add-on Manager" % (appName)
349 dialog.windowFlags &= ~gui.WF_WindowStaysOnTopHint
350 dialog.resize(400, 500)
351 layout = dialog.layout
352
353 checkboxes = {}
354 enableds = _enabledAddons()
355 scrollArea = gui.ScrollArea()
356 scrollArea.widget = scrollWidget = gui.Frame()
357 scrollArea.widgetResizable = True
358 scrollArea.horizontalScrollBarPolicy = 1
359 layout.add(scrollArea)
360 scrollLayout = gui.VBoxLayout(scrollWidget)
361
362 toolButtons = []
363 for (name, addon) in sorted(empro.internal._addons.items()):
364 if not addon.available:
365 continue
366
367 addonBox = gui.Frame()
368 addonBox.frameStyle = gui.Frame.StyledPanel
369 addonLayout = gui.GridLayout(addonBox)
370
371 if addon.error:
372 icon = gui.ValidityLabel(False, addon.traceback or addon.error)
373 elif addon.module:
374 icon = gui.ValidityLabel(True, "Loaded")
375 else:
376 icon = None
377 if icon:
378 addonLayout.addWidget( icon, 0, 0, 1, 1 )
379 addonLayout.setAlignment( icon, gui.AlignHCenter )
380
381 checkboxes[name] = checkbox = gui.CheckBox("%s" % os.path.basename(addon.path))
382 checkbox.checked = enableds.get(name, addon.enabled)
383 checkbox.styleSheet = "QCheckBox { font: bold; }"
384 checkbox.toolTip = addon.path
385 checkbox.enabled = not addon.forceEnabled
386 addonLayout.addWidget( checkbox, 0, 1, 1, 2 )
387
388 if addon.docstring:
389 description = gui.Label( addon.docstring.splitlines()[0] )
390 description.wordWrap = True
391 addonLayout.addWidget( description, 1, 1, 1, 2 )
392 fullDescription = gui.ToolButton()
393 fullDescription.icon = gui.Icon(":/application/Help.ico")
394 fullDescription.onClicked = lambda _, n=name : _showAddonInfo(n, empro.internal._addons[n])
395 addonLayout.addWidget( fullDescription, 1, 0, 1, 1 )
396 toolButtons.append(fullDescription)
397 if addon.author:
398 addonLayout.addWidget( gui.Label( "Author:" ), 2, 0, 1, 2 )
399 addonLayout.addWidget( gui.Label( addon.author ), 2, 2, 1, 1 )
400 if addon.version:
401 addonLayout.addWidget( gui.Label( "Version:" ), 3, 0, 1, 2 )
402 addonLayout.addWidget( gui.Label( addon.version ), 3, 2, 1, 1 )
403 addonLayout.setColumnStretch(2, 2)
404
405 scrollLayout.addWidget(addonBox)
406
407 scrollLayout.addStretch(1)
408
409 download = gui.Label('Additional add-ons can be downloaded from the <a href="%s">Knowledge Center</a> and saved on the search path:' % _ADDON_DOWNLOAD_URL)
410 download.openExternalLinks = True
411 download.wordWrap = True
412 layout.add(download)
413
414 additionalSearchPathEdit = gui.LineEdit(_additionalSearchPath())
415 layout.add(additionalSearchPathEdit)
416
417 @_printexception
418 def onFinished(code):
419 if code != dialog.Accepted:
420 return
421 _additionalSearchPath(additionalSearchPathEdit.text)
422 _enabledAddons( { name: checkbox.checked for (name, checkbox) in checkboxes.items() } )
423 _loadAddons(empro.internal._addons)
424 dialog.onFinished = onFinished
425
426 dialog.show(True)
427
430 if not addon.docstring:
431 return
432 from empro import gui
433 dialog = gui.SimpleDialog(gui.Close)
434 dialog.resize(600, 300)
435 dialog.windowFlags &= ~gui.WF_WindowStaysOnTopHint
436 dialog.title = name or "Add-on"
437 doc = gui.TextEdit(addon.docstring)
438 doc.readOnly = True
439 doc.styleSheet = "QTextEdit { border: 0; }"
440 dialog.layout.add(doc)
441 dialog.show(True)
442
445 from empro import gui
446 if type( item ) is dict:
447 try:
448 menuItem = item["menuItem"]
449 insertionPoint = item["insertionPoint"]
450 except:
451 return
452 if type( insertionPoint ) is int:
453 if isinstance( menuItem, gui.Action ):
454 menu.insertAction( menu.actions()[insertionPoint], menuItem )
455 elif isinstance( menuItem, gui.Menu ):
456 menu.insertMenu( menu.actions()[insertionPoint], menuItem )
457 else:
458 return
459 menuItems.append( menuItem )
460 else:
461 return
462 else:
463 if isinstance( item, gui.Action ):
464 menu.addAction( item )
465 elif isinstance( item, gui.Menu ):
466 menu.addMenu( item )
467 else:
468 return
469 menuItems.append( item )
470
473 try:
474 onContextMenus = empro.internal._addonOnContextMenus
475 except AttributeError:
476 return
477 typeSet = frozenset(map(type, selection))
478 menuItems = []
479 menu.addSeparator()
480 for onContextMenu in onContextMenus:
481 try:
482 items = onContextMenu(selection, typeSet)
483 except:
484 continue
485 if type( items) is list:
486 for item in items:
487 _addContextMenuItem( menu, menuItems, item )
488 else:
489 _addContextMenuItem( menu, menuItems, items )
490 empro.internal._addonContextMenuItems = menuItems
491
505
512
519
520
521 _PREFERENCE_GROUP = "Addons"
532
538
541 '''
542 joins lines in a docstring where a newline appears to be in the middle
543 of a sentence.
544
545 It's not perfect, and the following rules apply for two lines to be joined:
546 - neither should be empty or all whitespace
547 - there should be no indentation.
548 - the second line should start with a letter (capital or small)
549 '''
550
551
552 if not docstring:
553 return docstring
554 lines = docstring.splitlines(True)
555 indents = [len(line) - len(line.lstrip()) for line in lines]
556 for k in range(len(lines) - 1):
557 if indents[k] > 0 or indents[k + 1]:
558 continue
559 a, b = lines[k].strip(), lines[k + 1].strip()
560 if not (a and b):
561 continue
562 if not b[0].isalpha():
563 continue
564
565 lines[k] = lines[k].rstrip() + ' '
566 lines[k + 1] = lines[k + 1].lstrip()
567 return ''.join(lines)
568
569
570 _ACTION_NAME_PREFIX = "Add-ons\\"
578
593
596 from empro import gui
597 preference = "%s/Notified" % _PREFERENCE_GROUP
598 name = os.path.splitext(os.path.basename(path))[0]
599 url = _ADDON_DOWNLOAD_URL
600 try:
601 notifieds = list(empro.core.ApplicationPreferences.getPreference(preference, None) or [])
602 except TypeError:
603 print "oops"
604 traceback.print_exc()
605 notifieds = []
606 if name in notifieds:
607 return
608 gui.MessageBox.information("Example Add-on", '<p>%(name)s.py is an example add-on and provided as a demonstration only.</p>'
609 '<p>More add-ons and updates can be found on the <a href="%(url)s">Knowledge Center</a>.</p>'
610 '<p>See the <a href="http://edadocs.software.keysight.com/display/empro2012/EMPro+Add-ons">EMPro Documentation</a> for more info.</p>' % vars(), gui.Ok, gui.Ok)
611 notifieds.append(name)
612 empro.core.ApplicationPreferences.setPreference(preference, notifieds)
613