File: gmPlugin.py

package info (click to toggle)
gnumed-client 1.4.12%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 164,192 kB
  • ctags: 168,531
  • sloc: python: 88,281; sh: 765; makefile: 37
file content (426 lines) | stat: -rw-r--r-- 14,174 bytes parent folder | download
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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
"""gmPlugin - base classes for GNUmed Horst space notebook plugins.

@copyright: author
"""
#==================================================================
__author__ = "H.Herb, I.Haywood, K.Hilbert"
__license__ = 'GPL v2 or later (details at http://www.gnu.org)'

import os
import sys
import glob
import logging


import wx


if __name__ == '__main__':
	sys.path.insert(0, '../../')
from Gnumed.pycommon import gmExceptions
from Gnumed.pycommon import gmGuiBroker
from Gnumed.pycommon import gmCfg
from Gnumed.pycommon import gmCfg2
from Gnumed.pycommon import gmDispatcher
from Gnumed.pycommon import gmTools

from Gnumed.business import gmPerson
from Gnumed.business import gmPraxis


_cfg = gmCfg2.gmCfgData()

_log = logging.getLogger('gm.ui')

#==============================================================================
class cLoadProgressBar (wx.ProgressDialog):
	def __init__(self, nr_plugins):
		wx.ProgressDialog.__init__(
			self,
			title = _("GNUmed: configuring [%s] (%s plugins)") % (gmPraxis.gmCurrentPraxisBranch().active_workplace, nr_plugins),
			message = _("loading list of plugins                               "),
			maximum = nr_plugins,
			parent = None,
			style = wx.PD_ELAPSED_TIME
			)
		self.SetIcon(gmTools.get_icon(wx = wx))
		self.idx = 0
		self.nr_plugins = nr_plugins
		self.prev_plugin = ""
	#----------------------------------------------------------
	def Update (self, result, plugin):
		if result == -1:
			result = ""
		elif result == 0:
			result = _("failed")
		else:
			result = _("success")
		wx.ProgressDialog.Update (self, 
				self.idx,
				_("previous: %s (%s)\ncurrent (%s/%s): %s") % (
					self.prev_plugin,
					result,
					(self.idx+1),
					self.nr_plugins,
					plugin))
		self.prev_plugin = plugin
		self.idx += 1
#==================================================================
# This is for NOTEBOOK plugins. Please write other base
# classes for other types of plugins.
#==================================================================
class cNotebookPlugin:
	"""Base class for plugins which provide a full notebook page.
	"""
	def __init__(self):
		self.gb = gmGuiBroker.GuiBroker()
		self._set = 'gui'
		self._widget = None
		self.__register_events()
	#-----------------------------------------------------
	# plugin load API
	#-----------------------------------------------------
	def register(self):
		"""Register ourselves with the main notebook widget."""

		_log.info("set: [%s] class: [%s] name: [%s]" % (self._set, self.__class__.__name__, self.name()))

		# create widget
		nb = self.gb['horstspace.notebook']
		widget = self.GetWidget(nb)

		# create toolbar
		#top_panel = self.gb['horstspace.top_panel']
		#tb = top_panel.CreateBar()
		#self.populate_toolbar(tb, widget)
		#tb.Realize()
		# place bar in top panel
		# (pages that don't want a toolbar must install a blank one
		#  otherwise the previous page's toolbar would be visible)
		#top_panel.AddBar(key=self.__class__.__name__, bar=tb)
		#self.gb['toolbar.%s' % self.__class__.__name__] = tb

		# add ourselves to the main notebook
		nb.AddPage(widget, self.name())

		# so notebook can find this widget
		self.gb['horstspace.notebook.%s' % self._set][self.__class__.__name__] = self
		self.gb['horstspace.notebook.pages'].append(self)

		# and put ourselves into the menu structure
		menu_info = self.MenuInfo()
		if menu_info is None:
			# register with direct access menu only
			gmDispatcher.send(signal = u'plugin_loaded', plugin_name = self.name(), class_name = self.__class__.__name__)
		else:
			name_of_menu, menu_item_name = menu_info
			gmDispatcher.send (
				signal = u'plugin_loaded',
				plugin_name = menu_item_name,
				class_name = self.__class__.__name__,
				menu_name = name_of_menu,
				menu_item_name = menu_item_name,
				# FIXME: this shouldn't be self.name() but rather self.menu_help_string()
				menu_help_string = self.name()
			)

		return True
	#-----------------------------------------------------
	def unregister(self):
		"""Remove ourselves."""
		del self.gb['horstspace.notebook.%s' % self._set][self.__class__.__name__]
		_log.info("plugin: [%s] (class: [%s]) set: [%s]" % (self.name(), self.__class__.__name__, self._set))

		# delete menu item
		menu_info = self.MenuInfo()
		if menu_info is not None:
			menu = self.gb['main.%smenu' % menu_info[0]]
			menu.Delete(self.menu_id)

		# delete toolbar
		#top_panel = self.gb['main.top_panel']
		#top_panel.DeleteBar(self.__class__.__name__)

		# correct the notebook page list
		nb_pages = self.gb['horstspace.notebook.pages']
		nb_page_num = nb_pages.index(self)
		del nb_pages[nb_page_num]

		# delete notebook page
		nb = self.gb['horstspace.notebook']
		nb.DeletePage(nb_page_num)
	#-----------------------------------------------------
	def name(self):
		return 'plugin <%s>' % self.__class__.__name__
	#-----------------------------------------------------
	def MenuInfo(self):
		"""Return tuple of (menuname, menuitem).

		None: no menu entry wanted
		"""
		return None
	#-----------------------------------------------------
#	def populate_toolbar (self, tb, widget):
#		"""Populates the toolbar for this widget.
#
#		- tb is the toolbar to populate
#		- widget is the widget returned by GetWidget()		# FIXME: is this really needed ?
#		"""
#		pass
	#-----------------------------------------------------
	# activation API
	#-----------------------------------------------------
	def can_receive_focus(self):
		"""Called when this plugin is *about to* receive focus.

		If None returned from here (or from overriders) the
		plugin activation will be veto()ed (if it can be).
		"""
		# FIXME: fail if locked
		return True
	#-----------------------------------------------------
	def receive_focus(self):
		"""We *are* receiving focus via wx.EVT_NotebookPageChanged.

		This can be used to populate the plugin widget on receiving focus.
		"""
		if hasattr(self._widget, 'repopulate_ui'):
			self._widget.repopulate_ui()
		# else apparently it doesn't need it
		return True
	#-----------------------------------------------------
	def _verify_patient_avail(self):
		"""Check for patient availability.

		- convenience method for your can_receive_focus() handlers
		"""
		# fail if no patient selected
		pat = gmPerson.gmCurrentPatient()
		if not pat.connected:
			# FIXME: people want an optional red backgound here
			gmDispatcher.send('statustext', msg = _('Cannot switch to [%s]: no patient selected') % self.name())
			return None
		return 1
	#-----------------------------------------------------
	def Raise(self):
		"""Raise ourselves."""
		nb_pages = self.gb['horstspace.notebook.pages']
		plugin_page = nb_pages.index(self)
		nb = self.gb['horstspace.notebook']
		nb.SetSelection(plugin_page)
		return True
	#-----------------------------------------------------
	def _on_raise_by_menu(self, event):
		if not self.can_receive_focus():
			return False
		self.Raise()
		return True
	#-----------------------------------------------------
	def _on_raise_by_signal(self, **kwds):
		# does this signal concern us ?
		if kwds['name'] not in [self.__class__.__name__, self.name()]:
			return False
		return self._on_raise_by_menu(None)
	# -----------------------------------------------------
	# event handlers for the popup window
	def on_load(self, evt):
		# FIXME: talk to the configurator so we're loaded next time
		self.register()
		# FIXME: raise ?
	# -----------------------------------------------------
	def OnShow(self, evt):
		self.register() # register without changing configuration
	# -----------------------------------------------------
	def __register_events(self):
		gmDispatcher.connect(signal = 'display_widget', receiver = self._on_raise_by_signal)
#==================================================================
class cPatientChange_PluginMixin:
	"""This mixin adds listening to patient change signals."""
	def __init__(self):
		gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection')
		gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
	# -----------------------------------------------------
	def _pre_patient_selection(self, **kwds):
		print "%s._pre_patient_selection() not implemented" % self.__class__.__name__
		print "should usually be used to commit unsaved data"
	# -----------------------------------------------------
	def _post_patient_selection(self, **kwds):
		print "%s._post_patient_selection() not implemented" % self.__class__.__name__
		print "should usually be used to initialize state"
#==================================================================
# some convenience functions
#------------------------------------------------------------------
def __gm_import(module_name):
	"""Import a module.

	I am not sure *why* we need this. But the docs
	and Google say so. It's got something to do with
	package imports returning the toplevel package name."""
	try:
		mod = __import__(module_name)
	except ImportError:
		_log.exception ('Cannot __import__() module [%s].' % module_name)
		return None
	components = module_name.split('.')
	for component in components[1:]:
		mod = getattr(mod, component)
	return mod
#------------------------------------------------------------------
def instantiate_plugin(aPackage='xxxDEFAULTxxx', plugin_name='xxxDEFAULTxxx'):
	"""Instantiates a plugin object from a package directory, returning the object.

	NOTE: it does NOT call register() for you !!!!

	- "set" specifies the subdirectory in which to find the plugin
	- this knows nothing of databases, all it does is instantiate a named plugin

	There will be a general 'gui' directory for large GUI
	components: prescritions, etc., then several others for more
	specific types: export/import filters, crypto algorithms
	guibroker, dbbroker are broker objects provided
	defaults are the default set of plugins to be loaded

	FIXME: we should inform the user about failing plugins
	"""
	# we do need brokers, else we are useless
	gb = gmGuiBroker.GuiBroker()

	# bean counting ! -> loaded plugins
	if not ('horstspace.notebook.%s' % aPackage) in gb.keylist():
		gb['horstspace.notebook.%s' % aPackage] = {}
	if not 'horstspace.notebook.pages' in gb.keylist():
		gb['horstspace.notebook.pages'] = []

	module_from_package = __gm_import('Gnumed.wxpython.%s.%s' % (aPackage, plugin_name))
	# find name of class of plugin (must be the same as the plugin module filename)
	plugin_class = module_from_package.__dict__[plugin_name]

	if not issubclass(plugin_class, cNotebookPlugin):
		_log.error("[%s] not a subclass of cNotebookPlugin" % plugin_name)
		return None

	_log.info(plugin_name)
	try:
		plugin = plugin_class()
	except:
		_log.exception('Cannot open module "%s.%s".' % (aPackage, plugin_name))
		return None

	return plugin
#------------------------------------------------------------------
def get_installed_plugins(plugin_dir=''):
	"""Looks for installed plugins in the filesystem.

	The first directory in sys.path which contains a wxpython/gui/
	is considered the one -- because that's where the import will
	get it from.
	"""
	_log.debug('searching installed plugins')
	search_path = None
	candidates = sys.path[:]
	candidates.append(gmTools.gmPaths().local_base_dir)
	for candidate in candidates:
		candidate = os.path.join(candidate, 'Gnumed', 'wxpython', plugin_dir)
		_log.debug(candidate)
		if os.path.exists(candidate):
			search_path = candidate
			break
		_log.debug('not found')

	if search_path is None:
		_log.error('unable to find any directory matching [%s]', os.path.join('${CANDIDATE}', 'Gnumed', 'wxpython', plugin_dir))
		_log.error('candidates: %s', str(candidates))
		# read from config file
		_log.info('trying to read list of installed plugins from config files')
		plugins = _cfg.get (
			group = u'client',
			option = u'installed plugins',
			source_order = [
				('system', 'extend'),
				('user', 'extend'),
				('workbase', 'extend'),
				('explicit', 'extend')
			]
		)
		if plugins is None:
			_log.debug('no plugins found in config files')
			return []
		_log.debug("plugins found: %s" % str(plugins))
		return plugins

	_log.info("scanning plugin directory [%s]" % search_path)

	files = glob.glob(os.path.join(search_path, 'gm*.py'))
	plugins = []
	for f in files:
		path, fname = os.path.split(f)
		mod_name, ext = os.path.splitext(fname)
		plugins.append(mod_name)

	_log.debug("plugins found: %s" % str(plugins))

	return plugins
#------------------------------------------------------------------
def GetPluginLoadList(option, plugin_dir = '', defaults = None, workplace=None):
	"""Get a list of plugins to load.

	1) from database if option is not None
	2) from list of defaults
	3) if 2 is None, from source directory (then stored in database)

	FIXME: NOT from files in directories (important for py2exe)
	"""
	if workplace == u'System Fallback':
		return [u'gmProviderInboxPlugin', u'gmDataMiningPlugin']

	if workplace is None:
		workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace

	p_list = None

	if option is not None:
		dbcfg = gmCfg.cCfgSQL()
		p_list = dbcfg.get2 (
			option = option,
			workplace = workplace,
			bias = 'workplace',
			default = defaults
		)

	if p_list is not None:
		return p_list

	if defaults is None:
		p_list = get_installed_plugins(plugin_dir = plugin_dir)
		if (len(p_list) == 0):
			_log.error('cannot find plugins by scanning plugin directory ?!?')
			return defaults
	else:
		p_list = defaults

	# store for current user/current workplace
	dbcfg.set (
		option = option,
		value = p_list,
		workplace = workplace
	)

	_log.debug("plugin load list stored: %s" % str(p_list))
	return p_list
#------------------------------------------------------------------
def UnloadPlugin (set, name):
	"""
	Unloads the named plugin
	"""
	gb = gmGuiBroker.GuiBroker()
	plugin = gb['horstspace.notebook.%s' % set][name]
	plugin.unregister()
#==================================================================
# Main
#------------------------------------------------------------------
if __name__ == '__main__':

	if len(sys.argv) > 1 and sys.argv[1] == 'test':
		print get_installed_plugins('gui')

#------------------------------------------------------------------