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, 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  #============================================================================== 
23 -class cLoadProgressBar (wx.ProgressDialog):
24 - def __init__(self, nr_plugins):
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 # This is for NOTEBOOK plugins. Please write other base 57 # classes for other types of plugins. 58 #==================================================================
59 -class cNotebookPlugin:
60 """Base class for plugins which provide a full notebook page. 61 """
62 - def __init__(self):
63 self.gb = gmGuiBroker.GuiBroker() 64 self._set = 'gui' 65 self._widget = None 66 self.__register_events()
67 #----------------------------------------------------- 68 # plugin load API 69 #-----------------------------------------------------
70 - def register(self):
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 # create widget 76 nb = self.gb['horstspace.notebook'] 77 widget = self.GetWidget(nb) 78 79 # create toolbar 80 #top_panel = self.gb['horstspace.top_panel'] 81 #tb = top_panel.CreateBar() 82 #self.populate_toolbar(tb, widget) 83 #tb.Realize() 84 # place bar in top panel 85 # (pages that don't want a toolbar must install a blank one 86 # otherwise the previous page's toolbar would be visible) 87 #top_panel.AddBar(key=self.__class__.__name__, bar=tb) 88 #self.gb['toolbar.%s' % self.__class__.__name__] = tb 89 90 # add ourselves to the main notebook 91 nb.AddPage(widget, self.name()) 92 93 # so notebook can find this widget 94 self.gb['horstspace.notebook.%s' % self._set][self.__class__.__name__] = self 95 self.gb['horstspace.notebook.pages'].append(self) 96 97 # and put ourselves into the menu structure 98 menu_info = self.MenuInfo() 99 if menu_info is None: 100 # register with direct access menu only 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 # FIXME: this shouldn't be self.name() but rather self.menu_help_string() 111 menu_help_string = self.name() 112 ) 113 114 return True
115 #-----------------------------------------------------
116 - def unregister(self):
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 # delete menu item 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 # delete toolbar 128 #top_panel = self.gb['main.top_panel'] 129 #top_panel.DeleteBar(self.__class__.__name__) 130 131 # correct the notebook page list 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 # delete notebook page 137 nb = self.gb['horstspace.notebook'] 138 nb.DeletePage(nb_page_num)
139 #-----------------------------------------------------
140 - def name(self):
141 return 'plugin <%s>' % self.__class__.__name__
142 #-----------------------------------------------------
143 - def MenuInfo(self):
144 """Return tuple of (menuname, menuitem). 145 146 None: no menu entry wanted 147 """ 148 return None
149 #----------------------------------------------------- 150 # def populate_toolbar (self, tb, widget): 151 # """Populates the toolbar for this widget. 152 # 153 # - tb is the toolbar to populate 154 # - widget is the widget returned by GetWidget() # FIXME: is this really needed ? 155 # """ 156 # pass 157 #----------------------------------------------------- 158 # activation API 159 #-----------------------------------------------------
160 - def can_receive_focus(self):
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 # FIXME: fail if locked 167 return True
168 #-----------------------------------------------------
169 - def receive_focus(self):
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 # else apparently it doesn't need it 177 return True
178 #-----------------------------------------------------
179 - def _verify_patient_avail(self):
180 """Check for patient availability. 181 182 - convenience method for your can_receive_focus() handlers 183 """ 184 # fail if no patient selected 185 pat = gmPerson.gmCurrentPatient() 186 if not pat.connected: 187 # FIXME: people want an optional red backgound here 188 gmDispatcher.send('statustext', msg = _('Cannot switch to [%s]: no patient selected') % self.name()) 189 return None 190 return 1
191 #-----------------------------------------------------
192 - def Raise(self):
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 #-----------------------------------------------------
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 - def _on_raise_by_signal(self, **kwds):
207 # does this signal concern us ? 208 if kwds['name'] not in [self.__class__.__name__, self.name()]: 209 return False 210 return self._on_raise_by_menu(None)
211 # ----------------------------------------------------- 212 # event handlers for the popup window
213 - def on_load(self, evt):
214 # FIXME: talk to the configurator so we're loaded next time 215 self.register()
216 # FIXME: raise ? 217 # -----------------------------------------------------
218 - def OnShow(self, evt):
219 self.register() # register without changing configuration
220 # -----------------------------------------------------
221 - def __register_events(self):
222 gmDispatcher.connect(signal = 'display_widget', receiver = self._on_raise_by_signal)
223 #==================================================================
224 -class cPatientChange_PluginMixin:
225 """This mixin adds listening to patient change signals."""
226 - def __init__(self):
227 gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection') 228 gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
229 # -----------------------------------------------------
230 - def _pre_patient_selection(self, **kwds):
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 # some convenience functions 239 #------------------------------------------------------------------
240 -def __gm_import(module_name):
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 #------------------------------------------------------------------
256 -def instantiate_plugin(aPackage='xxxDEFAULTxxx', plugin_name='xxxDEFAULTxxx'):
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 # we do need brokers, else we are useless 273 gb = gmGuiBroker.GuiBroker() 274 275 # bean counting ! -> loaded plugins 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 # find name of class of plugin (must be the same as the plugin module filename) 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 #------------------------------------------------------------------
298 -def get_installed_plugins(plugin_dir=''):
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 search_path = None 306 for path in sys.path: 307 tmp = os.path.join(path, 'Gnumed', 'wxpython', plugin_dir) 308 if os.path.exists(tmp): 309 search_path = tmp 310 break 311 if search_path is None: 312 _log.error('unable to find any candidate directory matching [$candidate/Gnumed/wxpython/%s/]' % plugin_dir) 313 _log.error('candidates: %s' % str(sys.path)) 314 return [] 315 316 _log.info("scanning plugin directory [%s]" % search_path) 317 318 files = glob.glob(os.path.join(search_path, 'gm*.py')) 319 plugins = [] 320 for f in files: 321 path, fname = os.path.split(f) 322 mod_name, ext = os.path.splitext(fname) 323 plugins.append(mod_name) 324 325 _log.debug("plugins found: %s" % str(plugins)) 326 327 return plugins
328 #------------------------------------------------------------------
329 -def GetPluginLoadList(option, plugin_dir = '', defaults = None, workplace=None):
330 """Get a list of plugins to load. 331 332 1) from database if option is not None 333 2) from list of defaults 334 3) if 2 is None, from source directory (then stored in database) 335 336 FIXME: NOT from files in directories (important for py2exe) 337 """ 338 if workplace == u'System Fallback': 339 return [u'gmProviderInboxPlugin', u'gmDataMiningPlugin'] 340 341 if workplace is None: 342 workplace = gmSurgery.gmCurrentPractice().active_workplace 343 344 p_list = None 345 346 if option is not None: 347 dbcfg = gmCfg.cCfgSQL() 348 p_list = dbcfg.get2 ( 349 option = option, 350 workplace = workplace, 351 bias = 'workplace', 352 default = defaults 353 ) 354 355 if p_list is not None: 356 return p_list 357 358 if defaults is None: 359 p_list = get_installed_plugins(plugin_dir = plugin_dir) 360 if (len(p_list) == 0): 361 _log.error('cannot find plugins by scanning plugin directory ?!?') 362 return defaults 363 else: 364 p_list = defaults 365 366 # store for current user/current workplace 367 dbcfg.set ( 368 option = option, 369 value = p_list, 370 workplace = workplace 371 ) 372 373 _log.debug("plugin load list stored: %s" % str(p_list)) 374 return p_list
375 #------------------------------------------------------------------
376 -def UnloadPlugin (set, name):
377 """ 378 Unloads the named plugin 379 """ 380 gb = gmGuiBroker.GuiBroker() 381 plugin = gb['horstspace.notebook.%s' % set][name] 382 plugin.unregister()
383 #================================================================== 384 # Main 385 #------------------------------------------------------------------ 386 if __name__ == '__main__': 387 388 if len(sys.argv) > 1 and sys.argv[1] == 'test': 389 print get_installed_plugins('gui') 390 391 #================================================================== 392