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