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 _log.debug('searching installed plugins') 306 search_path = None 307 candidates = sys.path[:] 308 candidates.append(gmTools.gmPaths().local_base_dir) 309 for candidate in candidates: 310 candidate = os.path.join(candidate, 'Gnumed', 'wxpython', plugin_dir) 311 _log.debug(candidate) 312 if os.path.exists(candidate): 313 search_path = candidate 314 break 315 _log.debug('not found') 316 if search_path is None: 317 _log.error('unable to find any directory matching [%s]', os.path.join('${CANDIDATE}', 'Gnumed', 'wxpython', plugin_dir)) 318 _log.error('candidates: %s', str(candidates)) 319 return [] 320 321 _log.info("scanning plugin directory [%s]" % search_path) 322 323 files = glob.glob(os.path.join(search_path, 'gm*.py')) 324 plugins = [] 325 for f in files: 326 path, fname = os.path.split(f) 327 mod_name, ext = os.path.splitext(fname) 328 plugins.append(mod_name) 329 330 _log.debug("plugins found: %s" % str(plugins)) 331 332 return plugins
333 #------------------------------------------------------------------
334 -def GetPluginLoadList(option, plugin_dir = '', defaults = None, workplace=None):
335 """Get a list of plugins to load. 336 337 1) from database if option is not None 338 2) from list of defaults 339 3) if 2 is None, from source directory (then stored in database) 340 341 FIXME: NOT from files in directories (important for py2exe) 342 """ 343 if workplace == u'System Fallback': 344 return [u'gmProviderInboxPlugin', u'gmDataMiningPlugin'] 345 346 if workplace is None: 347 workplace = gmSurgery.gmCurrentPractice().active_workplace 348 349 p_list = None 350 351 if option is not None: 352 dbcfg = gmCfg.cCfgSQL() 353 p_list = dbcfg.get2 ( 354 option = option, 355 workplace = workplace, 356 bias = 'workplace', 357 default = defaults 358 ) 359 360 if p_list is not None: 361 return p_list 362 363 if defaults is None: 364 p_list = get_installed_plugins(plugin_dir = plugin_dir) 365 if (len(p_list) == 0): 366 _log.error('cannot find plugins by scanning plugin directory ?!?') 367 return defaults 368 else: 369 p_list = defaults 370 371 # store for current user/current workplace 372 dbcfg.set ( 373 option = option, 374 value = p_list, 375 workplace = workplace 376 ) 377 378 _log.debug("plugin load list stored: %s" % str(p_list)) 379 return p_list
380 #------------------------------------------------------------------
381 -def UnloadPlugin (set, name):
382 """ 383 Unloads the named plugin 384 """ 385 gb = gmGuiBroker.GuiBroker() 386 plugin = gb['horstspace.notebook.%s' % set][name] 387 plugin.unregister()
388 #================================================================== 389 # Main 390 #------------------------------------------------------------------ 391 if __name__ == '__main__': 392 393 if len(sys.argv) > 1 and sys.argv[1] == 'test': 394 print get_installed_plugins('gui') 395 396 #================================================================== 397