1 """gmPlugin - base classes for GNUmed Horst space notebook plugins.
2
3 @copyright: author
4 """
5
6 __author__ = "H.Herb, I.Haywood, K.Hilbert"
7 __license__ = 'GPL v2 or later (details at http://www.gnu.org)'
8
9 import os
10 import sys
11 import glob
12 import logging
13
14
15 import wx
16
17
18 if __name__ == '__main__':
19 sys.path.insert(0, '../../')
20 from Gnumed.pycommon import gmExceptions
21 from Gnumed.pycommon import gmGuiBroker
22 from Gnumed.pycommon import gmCfg
23 from Gnumed.pycommon import gmCfg2
24 from Gnumed.pycommon import gmDispatcher
25 from Gnumed.pycommon import gmTools
26
27 from Gnumed.business import gmPerson
28 from Gnumed.business import gmPraxis
29
30
31 _cfg = gmCfg2.gmCfgData()
32
33 _log = logging.getLogger('gm.ui')
34
35
50
51 - def Update (self, result, plugin):
52 if result == -1:
53 result = ""
54 elif result == 0:
55 result = _("failed")
56 else:
57 result = _("success")
58 wx.ProgressDialog.Update (self,
59 self.idx,
60 _("previous: %s (%s)\ncurrent (%s/%s): %s") % (
61 self.prev_plugin,
62 result,
63 (self.idx+1),
64 self.nr_plugins,
65 plugin))
66 self.prev_plugin = plugin
67 self.idx += 1
68
69
70
71
72
74 """Base class for plugins which provide a full notebook page.
75 """
77 self.gb = gmGuiBroker.GuiBroker()
78 self._set = 'gui'
79 self._widget = None
80 self.__register_events()
81
82
83
84
86 """Register ourselves with the main notebook widget."""
87
88 _log.info("set: [%s] class: [%s] name: [%s]" % (self._set, self.__class__.__name__, self.name()))
89
90
91 nb = self.gb['horstspace.notebook']
92 widget = self.GetWidget(nb)
93
94
95 nb.AddPage(widget, self.name())
96
97
98 self.gb['horstspace.notebook.%s' % self._set][self.__class__.__name__] = self
99 self.gb['horstspace.notebook.pages'].append(self)
100
101
102 menu_info = self.MenuInfo()
103 if menu_info is None:
104
105 gmDispatcher.send(signal = 'plugin_loaded', plugin_name = self.name(), class_name = self.__class__.__name__)
106 else:
107 name_of_menu, menu_item_name = menu_info
108 gmDispatcher.send (
109 signal = 'plugin_loaded',
110 plugin_name = menu_item_name,
111 class_name = self.__class__.__name__,
112 menu_name = name_of_menu,
113 menu_item_name = menu_item_name,
114
115 menu_help_string = self.name()
116 )
117
118 return True
119
120
122 """Remove ourselves."""
123 del self.gb['horstspace.notebook.%s' % self._set][self.__class__.__name__]
124 _log.info("plugin: [%s] (class: [%s]) set: [%s]" % (self.name(), self.__class__.__name__, self._set))
125
126
127 menu_info = self.MenuInfo()
128 if menu_info is not None:
129 menu = self.gb['main.%smenu' % menu_info[0]]
130 menu.Delete(self.menu_id)
131
132
133 nb_pages = self.gb['horstspace.notebook.pages']
134 nb_page_num = nb_pages.index(self)
135 del nb_pages[nb_page_num]
136
137
138 nb = self.gb['horstspace.notebook']
139 nb.DeletePage(nb_page_num)
140
141
143 return 'plugin <%s>' % self.__class__.__name__
144
145
147 """Return tuple of (menuname, menuitem).
148
149 None: no menu entry wanted
150 """
151 return None
152
153
154
155
157 """Called when this plugin is *about to* receive focus.
158
159 If None returned from here (or from overriders) the
160 plugin activation will be veto()ed (if it can be).
161 """
162
163 return True
164
165
167 """We *are* receiving focus via wx.EVT_NotebookPageChanged.
168
169 This can be used to populate the plugin widget on receiving focus.
170 """
171 if hasattr(self._widget, 'repopulate_ui'):
172 self._widget.repopulate_ui()
173
174 return True
175
176
178 """Check for patient availability.
179
180 - convenience method for your can_receive_focus() handlers
181 """
182
183 pat = gmPerson.gmCurrentPatient()
184 if not pat.connected:
185
186 gmDispatcher.send('statustext', msg = _('Cannot switch to [%s]: no patient selected') % self.name())
187 return None
188 return 1
189
190
192 """Raise ourselves."""
193 nb_pages = self.gb['horstspace.notebook.pages']
194 plugin_page = nb_pages.index(self)
195 nb = self.gb['horstspace.notebook']
196 nb.SetSelection(plugin_page)
197 return True
198
199
205
206
208
209 if kwds['name'] not in [self.__class__.__name__, self.name()]:
210 return False
211 return self._on_raise_by_menu(None)
212
213
214
218
219
220
223
224
227
228
230 """This mixin adds listening to patient change signals."""
235
236
241
242
247
248
249 - def _post_patient_selection(self, **kwds):
250 print("%s._post_patient_selection() not implemented" % self.__class__.__name__)
251 print("should usually be used to schedule reloading the UI")
252
253
254
255
257 """Import a module.
258
259 I am not sure *why* we need this. But the docs
260 and Google say so. It's got something to do with
261 package imports returning the toplevel package name."""
262 try:
263 mod = __import__(module_name)
264 except ImportError:
265 _log.exception ('Cannot __import__() module [%s].' % module_name)
266 return None
267 components = module_name.split('.')
268 for component in components[1:]:
269 mod = getattr(mod, component)
270 return mod
271
272
274 """Instantiates a plugin object from a package directory, returning the object.
275
276 NOTE: it does NOT call register() for you !!!!
277
278 - "set" specifies the subdirectory in which to find the plugin
279 - this knows nothing of databases, all it does is instantiate a named plugin
280
281 There will be a general 'gui' directory for large GUI
282 components: prescritions, etc., then several others for more
283 specific types: export/import filters, crypto algorithms
284 guibroker, dbbroker are broker objects provided
285 defaults are the default set of plugins to be loaded
286
287 FIXME: we should inform the user about failing plugins
288 """
289
290 gb = gmGuiBroker.GuiBroker()
291
292
293 if not ('horstspace.notebook.%s' % aPackage) in gb.keylist():
294 gb['horstspace.notebook.%s' % aPackage] = {}
295 if not 'horstspace.notebook.pages' in gb.keylist():
296 gb['horstspace.notebook.pages'] = []
297
298 module_from_package = __gm_import('Gnumed.wxpython.%s.%s' % (aPackage, plugin_name))
299
300 plugin_class = module_from_package.__dict__[plugin_name]
301
302 if not issubclass(plugin_class, cNotebookPlugin):
303 _log.error("[%s] not a subclass of cNotebookPlugin" % plugin_name)
304 return None
305
306 _log.info(plugin_name)
307 try:
308 plugin = plugin_class()
309 except:
310 _log.exception('Cannot open module "%s.%s".' % (aPackage, plugin_name))
311 return None
312
313 return plugin
314
315
317 """Looks for installed plugins in the filesystem.
318
319 The first directory in sys.path which contains a wxpython/gui/
320 is considered the one -- because that's where the import will
321 get it from.
322 """
323 _log.debug('searching installed plugins')
324 search_path = None
325 candidates = sys.path[:]
326 candidates.append(gmTools.gmPaths().local_base_dir)
327 for candidate in candidates:
328 candidate = os.path.join(candidate, 'Gnumed', 'wxpython', plugin_dir)
329 _log.debug(candidate)
330 if os.path.exists(candidate):
331 search_path = candidate
332 break
333 _log.debug('not found')
334
335 if search_path is None:
336 _log.error('unable to find any directory matching [%s]', os.path.join('${CANDIDATE}', 'Gnumed', 'wxpython', plugin_dir))
337 _log.error('candidates: %s', str(candidates))
338
339 _log.info('trying to read list of installed plugins from config files')
340 plugins = _cfg.get (
341 group = 'client',
342 option = 'installed plugins',
343 source_order = [
344 ('system', 'extend'),
345 ('user', 'extend'),
346 ('workbase', 'extend'),
347 ('explicit', 'extend')
348 ]
349 )
350 if plugins is None:
351 _log.debug('no plugins found in config files')
352 return []
353 _log.debug("plugins found: %s" % str(plugins))
354 return plugins
355
356 _log.info("scanning plugin directory [%s]" % search_path)
357
358 files = glob.glob(os.path.join(search_path, 'gm*.py'))
359 plugins = []
360 for f in files:
361 path, fname = os.path.split(f)
362 mod_name, ext = os.path.splitext(fname)
363 plugins.append(mod_name)
364
365 _log.debug("plugins found: %s" % str(plugins))
366
367 return plugins
368
369
371 """Get a list of plugins to load.
372
373 1) from database if option is not None
374 2) from list of defaults
375 3) if 2 is None, from source directory (then stored in database)
376
377 FIXME: NOT from files in directories (important for py2exe)
378 """
379 if workplace == 'System Fallback':
380 return ['gmProviderInboxPlugin', 'gmDataMiningPlugin']
381
382 if workplace is None:
383 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
384
385 p_list = None
386
387 if option is not None:
388 dbcfg = gmCfg.cCfgSQL()
389 p_list = dbcfg.get2 (
390 option = option,
391 workplace = workplace,
392 bias = 'workplace',
393 default = defaults
394 )
395
396 if p_list is not None:
397 return p_list
398
399 if defaults is None:
400 p_list = get_installed_plugins(plugin_dir = plugin_dir)
401 if (len(p_list) == 0):
402 _log.error('cannot find plugins by scanning plugin directory ?!?')
403 return defaults
404 else:
405 p_list = defaults
406
407
408 dbcfg.set (
409 option = option,
410 value = p_list,
411 workplace = workplace
412 )
413
414 _log.debug("plugin load list stored: %s" % str(p_list))
415 return p_list
416
417
425
426
427
428
429 if __name__ == '__main__':
430
431 if len(sys.argv) > 1 and sys.argv[1] == 'test':
432 print(get_installed_plugins('gui'))
433