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