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