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
38 wx.ProgressDialog.__init__(
39 self,
40 title = _("GNUmed: configuring [%s] (%s plugins)") % (gmPraxis.gmCurrentPraxisBranch().active_workplace, nr_plugins),
41 message = _("loading list of plugins "),
42 maximum = nr_plugins,
43 parent = None,
44 style = wx.PD_ELAPSED_TIME
45 )
46 self.SetIcon(gmTools.get_icon(wx = wx))
47 self.idx = 0
48 self.nr_plugins = nr_plugins
49 self.prev_plugin = ""
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
73 """Base class for plugins which provide a full notebook page.
74 """
76 self.gb = gmGuiBroker.GuiBroker()
77 self._set = 'gui'
78 self._widget = None
79 self.__register_events()
80
81
82
84 """Register ourselves with the main notebook widget."""
85
86 _log.info("set: [%s] class: [%s] name: [%s]" % (self._set, self.__class__.__name__, self.name()))
87
88
89 nb = self.gb['horstspace.notebook']
90 widget = self.GetWidget(nb)
91
92
93
94
95
96
97
98
99
100
101
102
103
104 nb.AddPage(widget, self.name())
105
106
107 self.gb['horstspace.notebook.%s' % self._set][self.__class__.__name__] = self
108 self.gb['horstspace.notebook.pages'].append(self)
109
110
111 menu_info = self.MenuInfo()
112 if menu_info is None:
113
114 gmDispatcher.send(signal = u'plugin_loaded', plugin_name = self.name(), class_name = self.__class__.__name__)
115 else:
116 name_of_menu, menu_item_name = menu_info
117 gmDispatcher.send (
118 signal = u'plugin_loaded',
119 plugin_name = menu_item_name,
120 class_name = self.__class__.__name__,
121 menu_name = name_of_menu,
122 menu_item_name = menu_item_name,
123
124 menu_help_string = self.name()
125 )
126
127 return True
128
130 """Remove ourselves."""
131 del self.gb['horstspace.notebook.%s' % self._set][self.__class__.__name__]
132 _log.info("plugin: [%s] (class: [%s]) set: [%s]" % (self.name(), self.__class__.__name__, self._set))
133
134
135 menu_info = self.MenuInfo()
136 if menu_info is not None:
137 menu = self.gb['main.%smenu' % menu_info[0]]
138 menu.Delete(self.menu_id)
139
140
141
142
143
144
145 nb_pages = self.gb['horstspace.notebook.pages']
146 nb_page_num = nb_pages.index(self)
147 del nb_pages[nb_page_num]
148
149
150 nb = self.gb['horstspace.notebook']
151 nb.DeletePage(nb_page_num)
152
154 return 'plugin <%s>' % self.__class__.__name__
155
157 """Return tuple of (menuname, menuitem).
158
159 None: no menu entry wanted
160 """
161 return None
162
163
164
165
166
167
168
169
170
171
172
174 """Called when this plugin is *about to* receive focus.
175
176 If None returned from here (or from overriders) the
177 plugin activation will be veto()ed (if it can be).
178 """
179
180 return True
181
183 """We *are* receiving focus via wx.EVT_NotebookPageChanged.
184
185 This can be used to populate the plugin widget on receiving focus.
186 """
187 if hasattr(self._widget, 'repopulate_ui'):
188 self._widget.repopulate_ui()
189
190 return True
191
193 """Check for patient availability.
194
195 - convenience method for your can_receive_focus() handlers
196 """
197
198 pat = gmPerson.gmCurrentPatient()
199 if not pat.connected:
200
201 gmDispatcher.send('statustext', msg = _('Cannot switch to [%s]: no patient selected') % self.name())
202 return None
203 return 1
204
206 """Raise ourselves."""
207 nb_pages = self.gb['horstspace.notebook.pages']
208 plugin_page = nb_pages.index(self)
209 nb = self.gb['horstspace.notebook']
210 nb.SetSelection(plugin_page)
211 return True
212
218
220
221 if kwds['name'] not in [self.__class__.__name__, self.name()]:
222 return False
223 return self._on_raise_by_menu(None)
224
225
229
230
233
236
238 """This mixin adds listening to patient change signals."""
242
244 print "%s._pre_patient_selection() not implemented" % self.__class__.__name__
245 print "should usually be used to commit unsaved data"
246
247 - def _post_patient_selection(self, **kwds):
248 print "%s._post_patient_selection() not implemented" % self.__class__.__name__
249 print "should usually be used to initialize state"
250
251
252
254 """Import a module.
255
256 I am not sure *why* we need this. But the docs
257 and Google say so. It's got something to do with
258 package imports returning the toplevel package name."""
259 try:
260 mod = __import__(module_name)
261 except ImportError:
262 _log.exception ('Cannot __import__() module [%s].' % module_name)
263 return None
264 components = module_name.split('.')
265 for component in components[1:]:
266 mod = getattr(mod, component)
267 return mod
268
270 """Instantiates a plugin object from a package directory, returning the object.
271
272 NOTE: it does NOT call register() for you !!!!
273
274 - "set" specifies the subdirectory in which to find the plugin
275 - this knows nothing of databases, all it does is instantiate a named plugin
276
277 There will be a general 'gui' directory for large GUI
278 components: prescritions, etc., then several others for more
279 specific types: export/import filters, crypto algorithms
280 guibroker, dbbroker are broker objects provided
281 defaults are the default set of plugins to be loaded
282
283 FIXME: we should inform the user about failing plugins
284 """
285
286 gb = gmGuiBroker.GuiBroker()
287
288
289 if not ('horstspace.notebook.%s' % aPackage) in gb.keylist():
290 gb['horstspace.notebook.%s' % aPackage] = {}
291 if not 'horstspace.notebook.pages' in gb.keylist():
292 gb['horstspace.notebook.pages'] = []
293
294 module_from_package = __gm_import('Gnumed.wxpython.%s.%s' % (aPackage, plugin_name))
295
296 plugin_class = module_from_package.__dict__[plugin_name]
297
298 if not issubclass(plugin_class, cNotebookPlugin):
299 _log.error("[%s] not a subclass of cNotebookPlugin" % plugin_name)
300 return None
301
302 _log.info(plugin_name)
303 try:
304 plugin = plugin_class()
305 except:
306 _log.exception('Cannot open module "%s.%s".' % (aPackage, plugin_name))
307 return None
308
309 return plugin
310
312 """Looks for installed plugins in the filesystem.
313
314 The first directory in sys.path which contains a wxpython/gui/
315 is considered the one -- because that's where the import will
316 get it from.
317 """
318 _log.debug('searching installed plugins')
319 search_path = None
320 candidates = sys.path[:]
321 candidates.append(gmTools.gmPaths().local_base_dir)
322 for candidate in candidates:
323 candidate = os.path.join(candidate, 'Gnumed', 'wxpython', plugin_dir)
324 _log.debug(candidate)
325 if os.path.exists(candidate):
326 search_path = candidate
327 break
328 _log.debug('not found')
329
330 if search_path is None:
331 _log.error('unable to find any directory matching [%s]', os.path.join('${CANDIDATE}', 'Gnumed', 'wxpython', plugin_dir))
332 _log.error('candidates: %s', str(candidates))
333
334 _log.info('trying to read list of installed plugins from config files')
335 plugins = _cfg.get (
336 group = u'client',
337 option = u'installed plugins',
338 source_order = [
339 ('system', 'extend'),
340 ('user', 'extend'),
341 ('workbase', 'extend'),
342 ('explicit', 'extend')
343 ]
344 )
345 if plugins is None:
346 _log.debug('no plugins found in config files')
347 return []
348 _log.debug("plugins found: %s" % str(plugins))
349 return plugins
350
351 _log.info("scanning plugin directory [%s]" % search_path)
352
353 files = glob.glob(os.path.join(search_path, 'gm*.py'))
354 plugins = []
355 for f in files:
356 path, fname = os.path.split(f)
357 mod_name, ext = os.path.splitext(fname)
358 plugins.append(mod_name)
359
360 _log.debug("plugins found: %s" % str(plugins))
361
362 return plugins
363
365 """Get a list of plugins to load.
366
367 1) from database if option is not None
368 2) from list of defaults
369 3) if 2 is None, from source directory (then stored in database)
370
371 FIXME: NOT from files in directories (important for py2exe)
372 """
373 if workplace == u'System Fallback':
374 return [u'gmProviderInboxPlugin', u'gmDataMiningPlugin']
375
376 if workplace is None:
377 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
378
379 p_list = None
380
381 if option is not None:
382 dbcfg = gmCfg.cCfgSQL()
383 p_list = dbcfg.get2 (
384 option = option,
385 workplace = workplace,
386 bias = 'workplace',
387 default = defaults
388 )
389
390 if p_list is not None:
391 return p_list
392
393 if defaults is None:
394 p_list = get_installed_plugins(plugin_dir = plugin_dir)
395 if (len(p_list) == 0):
396 _log.error('cannot find plugins by scanning plugin directory ?!?')
397 return defaults
398 else:
399 p_list = defaults
400
401
402 dbcfg.set (
403 option = option,
404 value = p_list,
405 workplace = workplace
406 )
407
408 _log.debug("plugin load list stored: %s" % str(p_list))
409 return p_list
410
418
419
420
421 if __name__ == '__main__':
422
423 if len(sys.argv) > 1 and sys.argv[1] == 'test':
424 print get_installed_plugins('gui')
425
426
427