Package Gnumed :: Package wxpython :: Module gmPlugin
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmPlugin

  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  #============================================================================== 
36 -class cLoadProgressBar (wx.ProgressDialog):
37 - def __init__(self, nr_plugins):
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 # This is for NOTEBOOK plugins. Please write other base 71 # classes for other types of plugins. 72 #==================================================================
73 -class cNotebookPlugin:
74 """Base class for plugins which provide a full notebook page. 75 """
76 - def __init__(self):
77 self.gb = gmGuiBroker.GuiBroker() 78 self._set = 'gui' 79 self._widget = None 80 self.__register_events()
81 82 #----------------------------------------------------- 83 # plugin load API 84 #-----------------------------------------------------
85 - def register(self):
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 # create widget 91 nb = self.gb['horstspace.notebook'] 92 widget = self.GetWidget(nb) 93 94 # add ourselves to the main notebook 95 nb.AddPage(widget, self.name()) 96 97 # so notebook can find this widget 98 self.gb['horstspace.notebook.%s' % self._set][self.__class__.__name__] = self 99 self.gb['horstspace.notebook.pages'].append(self) 100 101 # and put ourselves into the menu structure 102 menu_info = self.MenuInfo() 103 if menu_info is None: 104 # register with direct access menu only 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 # FIXME: this shouldn't be self.name() but rather self.menu_help_string() 115 menu_help_string = self.name() 116 ) 117 118 return True
119 120 #-----------------------------------------------------
121 - def unregister(self):
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 # delete menu item 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 # correct the notebook page list 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 # delete notebook page 138 nb = self.gb['horstspace.notebook'] 139 nb.DeletePage(nb_page_num)
140 141 #-----------------------------------------------------
142 - def name(self):
143 return 'plugin <%s>' % self.__class__.__name__
144 145 #-----------------------------------------------------
146 - def MenuInfo(self):
147 """Return tuple of (menuname, menuitem). 148 149 None: no menu entry wanted 150 """ 151 return None
152 153 #----------------------------------------------------- 154 # activation API 155 #-----------------------------------------------------
156 - def can_receive_focus(self):
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 # FIXME: fail if locked 163 return True
164 165 #-----------------------------------------------------
166 - def receive_focus(self):
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 # else apparently it doesn't need it 174 return True
175 176 #-----------------------------------------------------
177 - def _verify_patient_avail(self):
178 """Check for patient availability. 179 180 - convenience method for your can_receive_focus() handlers 181 """ 182 # fail if no patient selected 183 pat = gmPerson.gmCurrentPatient() 184 if not pat.connected: 185 # FIXME: people want an optional red backgound here 186 gmDispatcher.send('statustext', msg = _('Cannot switch to [%s]: no patient selected') % self.name()) 187 return None 188 return 1
189 190 #-----------------------------------------------------
191 - def Raise(self):
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 #-----------------------------------------------------
200 - def _on_raise_by_menu(self, event):
201 if not self.can_receive_focus(): 202 return False 203 self.Raise() 204 return True
205 206 #-----------------------------------------------------
207 - def _on_raise_by_signal(self, **kwds):
208 # does this signal concern us ? 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 # event handlers for the popup window
215 - def on_load(self, evt):
216 # FIXME: talk to the configurator so we're loaded next time 217 self.register()
218 # FIXME: raise ? 219 220 # -----------------------------------------------------
221 - def OnShow(self, evt):
222 self.register() # register without changing configuration
223 224 # -----------------------------------------------------
225 - def __register_events(self):
226 gmDispatcher.connect(signal = 'display_widget', receiver = self._on_raise_by_signal)
227 228 #==================================================================
229 -class cPatientChange_PluginMixin:
230 """This mixin adds listening to patient change signals."""
231 - def __init__(self):
232 gmDispatcher.connect(self._pre_patient_unselection, 'pre_patient_unselection') 233 gmDispatcher.connect(self._on_current_patient_unset, 'current_patient_unset') 234 gmDispatcher.connect(self._post_patient_selection, 'post_patient_selection')
235 236 # -----------------------------------------------------
237 - def _pre_patient_unselection(self, **kwds):
238 # print "%s._pre_patient_unselection() not implemented" % self.__class__.__name__ 239 # print "should usually be used to commit unsaved data" 240 pass
241 242 # -----------------------------------------------------
243 - def _on_current_patient_unset(self, **kwds):
244 # print "%s._on_current_patient_unset() not implemented" % self.__class__.__name__ 245 # print "should usually be used to empty the UI" 246 pass
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 # some convenience functions 255 #------------------------------------------------------------------
256 -def __gm_import(module_name):
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 #------------------------------------------------------------------
273 -def instantiate_plugin(aPackage='xxxDEFAULTxxx', plugin_name='xxxDEFAULTxxx'):
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 # we do need brokers, else we are useless 290 gb = gmGuiBroker.GuiBroker() 291 292 # bean counting ! -> loaded plugins 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 # find name of class of plugin (must be the same as the plugin module filename) 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 Exception: 310 _log.exception('Cannot open module "%s.%s".' % (aPackage, plugin_name)) 311 return None 312 313 return plugin
314 315 #------------------------------------------------------------------
316 -def get_installed_plugins(plugin_dir=''):
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 # read from config file 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 #------------------------------------------------------------------
370 -def GetPluginLoadList(option, plugin_dir = '', defaults = None, workplace=None):
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 # store for current user/current workplace 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 #------------------------------------------------------------------
418 -def UnloadPlugin (set, name):
419 """ 420 Unloads the named plugin 421 """ 422 gb = gmGuiBroker.GuiBroker() 423 plugin = gb['horstspace.notebook.%s' % set][name] 424 plugin.unregister()
425 426 #================================================================== 427 # Main 428 #------------------------------------------------------------------ 429 if __name__ == '__main__': 430 431 if len(sys.argv) > 1 and sys.argv[1] == 'test': 432 print(get_installed_plugins('gui')) 433