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 # This is for NOTEBOOK plugins. Please write other base 70 # classes for other types of plugins. 71 #==================================================================
72 -class cNotebookPlugin:
73 """Base class for plugins which provide a full notebook page. 74 """
75 - def __init__(self):
76 self.gb = gmGuiBroker.GuiBroker() 77 self._set = 'gui' 78 self._widget = None 79 self.__register_events()
80 #----------------------------------------------------- 81 # plugin load API 82 #-----------------------------------------------------
83 - def register(self):
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 # create widget 89 nb = self.gb['horstspace.notebook'] 90 widget = self.GetWidget(nb) 91 92 # create toolbar 93 #top_panel = self.gb['horstspace.top_panel'] 94 #tb = top_panel.CreateBar() 95 #self.populate_toolbar(tb, widget) 96 #tb.Realize() 97 # place bar in top panel 98 # (pages that don't want a toolbar must install a blank one 99 # otherwise the previous page's toolbar would be visible) 100 #top_panel.AddBar(key=self.__class__.__name__, bar=tb) 101 #self.gb['toolbar.%s' % self.__class__.__name__] = tb 102 103 # add ourselves to the main notebook 104 nb.AddPage(widget, self.name()) 105 106 # so notebook can find this widget 107 self.gb['horstspace.notebook.%s' % self._set][self.__class__.__name__] = self 108 self.gb['horstspace.notebook.pages'].append(self) 109 110 # and put ourselves into the menu structure 111 menu_info = self.MenuInfo() 112 if menu_info is None: 113 # register with direct access menu only 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 # FIXME: this shouldn't be self.name() but rather self.menu_help_string() 124 menu_help_string = self.name() 125 ) 126 127 return True
128 #-----------------------------------------------------
129 - def unregister(self):
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 # delete menu item 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 # delete toolbar 141 #top_panel = self.gb['main.top_panel'] 142 #top_panel.DeleteBar(self.__class__.__name__) 143 144 # correct the notebook page list 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 # delete notebook page 150 nb = self.gb['horstspace.notebook'] 151 nb.DeletePage(nb_page_num)
152 #-----------------------------------------------------
153 - def name(self):
154 return 'plugin <%s>' % self.__class__.__name__
155 #-----------------------------------------------------
156 - def MenuInfo(self):
157 """Return tuple of (menuname, menuitem). 158 159 None: no menu entry wanted 160 """ 161 return None
162 #----------------------------------------------------- 163 # def populate_toolbar (self, tb, widget): 164 # """Populates the toolbar for this widget. 165 # 166 # - tb is the toolbar to populate 167 # - widget is the widget returned by GetWidget() # FIXME: is this really needed ? 168 # """ 169 # pass 170 #----------------------------------------------------- 171 # activation API 172 #-----------------------------------------------------
173 - def can_receive_focus(self):
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 # FIXME: fail if locked 180 return True
181 #-----------------------------------------------------
182 - def receive_focus(self):
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 # else apparently it doesn't need it 190 return True
191 #-----------------------------------------------------
192 - def _verify_patient_avail(self):
193 """Check for patient availability. 194 195 - convenience method for your can_receive_focus() handlers 196 """ 197 # fail if no patient selected 198 pat = gmPerson.gmCurrentPatient() 199 if not pat.connected: 200 # FIXME: people want an optional red backgound here 201 gmDispatcher.send('statustext', msg = _('Cannot switch to [%s]: no patient selected') % self.name()) 202 return None 203 return 1
204 #-----------------------------------------------------
205 - def Raise(self):
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 #-----------------------------------------------------
213 - def _on_raise_by_menu(self, event):
214 if not self.can_receive_focus(): 215 return False 216 self.Raise() 217 return True
218 #-----------------------------------------------------
219 - def _on_raise_by_signal(self, **kwds):
220 # does this signal concern us ? 221 if kwds['name'] not in [self.__class__.__name__, self.name()]: 222 return False 223 return self._on_raise_by_menu(None)
224 # ----------------------------------------------------- 225 # event handlers for the popup window
226 - def on_load(self, evt):
227 # FIXME: talk to the configurator so we're loaded next time 228 self.register()
229 # FIXME: raise ? 230 # -----------------------------------------------------
231 - def OnShow(self, evt):
232 self.register() # register without changing configuration
233 # -----------------------------------------------------
234 - def __register_events(self):
235 gmDispatcher.connect(signal = 'display_widget', receiver = self._on_raise_by_signal)
236 #==================================================================
237 -class cPatientChange_PluginMixin:
238 """This mixin adds listening to patient change signals."""
239 - def __init__(self):
240 gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection') 241 gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
242 # -----------------------------------------------------
243 - def _pre_patient_selection(self, **kwds):
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 # some convenience functions 252 #------------------------------------------------------------------
253 -def __gm_import(module_name):
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 #------------------------------------------------------------------
269 -def instantiate_plugin(aPackage='xxxDEFAULTxxx', plugin_name='xxxDEFAULTxxx'):
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 # we do need brokers, else we are useless 286 gb = gmGuiBroker.GuiBroker() 287 288 # bean counting ! -> loaded plugins 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 # find name of class of plugin (must be the same as the plugin module filename) 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 #------------------------------------------------------------------
311 -def get_installed_plugins(plugin_dir=''):
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 # read from config file 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 #------------------------------------------------------------------
364 -def GetPluginLoadList(option, plugin_dir = '', defaults = None, workplace=None):
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 # store for current user/current workplace 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 #------------------------------------------------------------------
411 -def UnloadPlugin (set, name):
412 """ 413 Unloads the named plugin 414 """ 415 gb = gmGuiBroker.GuiBroker() 416 plugin = gb['horstspace.notebook.%s' % set][name] 417 plugin.unregister()
418 #================================================================== 419 # Main 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