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          #-----------------------------------------------------
    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