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  #==================================================================
     70  # This is for NOTEBOOK plugins. Please write other base
     71  # classes for other types of plugins.
     72  #==================================================================
     73 -class cNotebookPlugin:
     74          """Base class for plugins which provide a full notebook page.
     75          """
     76 -        def __init__(self):
     77                  self.gb = gmGuiBroker.GuiBroker()
     78                  self._set = 'gui'
     79                  self._widget = None
     80                  self.__register_events()
     81
     82          #-----------------------------------------------------
     83          # plugin load API
     84          #-----------------------------------------------------
     85 -        def register(self):
     86                  """Register ourselves with the main notebook widget."""
     87
     88                  _log.info("set: [%s] class: [%s] name: [%s]" % (self._set, self.__class__.__name__, self.name()))
     89
     90                  # create widget
     91                  nb = self.gb['horstspace.notebook']
     92                  widget = self.GetWidget(nb)
     93
     94                  # add ourselves to the main notebook
     95                  nb.AddPage(widget, self.name())
     96
     97                  # so notebook can find this widget
     98                  self.gb['horstspace.notebook.%s' % self._set][self.__class__.__name__] = self
     99                  self.gb['horstspace.notebook.pages'].append(self)
    100
    101                  # and put ourselves into the menu structure
    102                  menu_info = self.MenuInfo()
    103                  if menu_info is None:
    104                          # register with direct access menu only
    105                          gmDispatcher.send(signal = 'plugin_loaded', plugin_name = self.name(), class_name = self.__class__.__name__)
    106                  else:
    107                          name_of_menu, menu_item_name = menu_info
    108                          gmDispatcher.send (
    109                                  signal = 'plugin_loaded',
    110                                  plugin_name = menu_item_name,
    111                                  class_name = self.__class__.__name__,
    112                                  menu_name = name_of_menu,
    113                                  menu_item_name = menu_item_name,
    114                                  # FIXME: this shouldn't be self.name() but rather self.menu_help_string()
    115                                  menu_help_string = self.name()
    116                          )
    117
    118                  return True
    119
    120          #-----------------------------------------------------
    121 -        def unregister(self):
    122                  """Remove ourselves."""
    123                  del self.gb['horstspace.notebook.%s' % self._set][self.__class__.__name__]
    124                  _log.info("plugin: [%s] (class: [%s]) set: [%s]" % (self.name(), self.__class__.__name__, self._set))
    125
    126                  # delete menu item
    127                  menu_info = self.MenuInfo()
    128                  if menu_info is not None:
    129                          menu = self.gb['main.%smenu' % menu_info[0]]
    130                          menu.Delete(self.menu_id)
    131
    132                  # correct the notebook page list
    133                  nb_pages = self.gb['horstspace.notebook.pages']
    134                  nb_page_num = nb_pages.index(self)
    135                  del nb_pages[nb_page_num]
    136
    137                  # delete notebook page
    138                  nb = self.gb['horstspace.notebook']
    139                  nb.DeletePage(nb_page_num)
    140
    141          #-----------------------------------------------------
    142 -        def name(self):
    143                  return 'plugin <%s>' % self.__class__.__name__
    144
    145          #-----------------------------------------------------
    146 -        def MenuInfo(self):
    147                  """Return tuple of (menuname, menuitem).
    148
    149                  None: no menu entry wanted
    150                  """
    151                  return None
    152
    153          #-----------------------------------------------------
    154          # activation API
    155          #-----------------------------------------------------
    156 -        def can_receive_focus(self):
    157                  """Called when this plugin is *about to* receive focus.
    158
    159                  If None returned from here (or from overriders) the
    160                  plugin activation will be veto()ed (if it can be).
    161                  """
    162                  # FIXME: fail if locked
    163                  return True
    164
    165          #-----------------------------------------------------
    166 -        def receive_focus(self):
    167                  """We *are* receiving focus via wx.EVT_NotebookPageChanged.
    168
    169                  This can be used to populate the plugin widget on receiving focus.
    170                  """
    171                  if hasattr(self._widget, 'repopulate_ui'):
    172                          self._widget.repopulate_ui()
    173                  # else apparently it doesn't need it
    174                  return True
    175
    176          #-----------------------------------------------------
    177 -        def _verify_patient_avail(self):
    178                  """Check for patient availability.
    179
    180                  - convenience method for your can_receive_focus() handlers
    181                  """
    182                  # fail if no patient selected
    183                  pat = gmPerson.gmCurrentPatient()
    184                  if not pat.connected:
    185                          # FIXME: people want an optional red backgound here
    186                          gmDispatcher.send('statustext', msg = _('Cannot switch to [%s]: no patient selected') % self.name())
    187                          return None
    188                  return 1
    189
    190          #-----------------------------------------------------
    191 -        def Raise(self):
    192                  """Raise ourselves."""
    193                  nb_pages = self.gb['horstspace.notebook.pages']
    194                  plugin_page = nb_pages.index(self)
    195                  nb = self.gb['horstspace.notebook']
    196                  nb.SetSelection(plugin_page)
    197                  return True
    198
    199          #-----------------------------------------------------
    205
    206          #-----------------------------------------------------
    207 -        def _on_raise_by_signal(self, **kwds):
    208                  # does this signal concern us ?
    209                  if kwds['name'] not in [self.__class__.__name__, self.name()]:
    210                          return False
    211                  return self._on_raise_by_menu(None)
    212
    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          # -----------------------------------------------------
    221 -        def OnShow(self, evt):
    222                  self.register() # register without changing configuration
    223
    224          # -----------------------------------------------------
    225 -        def __register_events(self):
    226                  gmDispatcher.connect(signal = 'display_widget', receiver = self._on_raise_by_signal)
    227
    228  #==================================================================
    229 -class cPatientChange_PluginMixin:
    230          """This mixin adds listening to patient change signals."""
    231 -        def __init__(self):
    232                  gmDispatcher.connect(self._pre_patient_unselection, 'pre_patient_unselection')
    233                  gmDispatcher.connect(self._on_current_patient_unset, 'current_patient_unset')
    234                  gmDispatcher.connect(self._post_patient_selection, 'post_patient_selection')
    235
    236          # -----------------------------------------------------
    237 -        def _pre_patient_unselection(self, **kwds):
    238  #               print "%s._pre_patient_unselection() not implemented" % self.__class__.__name__
    239  #               print "should usually be used to commit unsaved data"
    240                  pass
    241
    242          # -----------------------------------------------------
    243 -        def _on_current_patient_unset(self, **kwds):
    244  #               print "%s._on_current_patient_unset() not implemented" % self.__class__.__name__
    245  #               print "should usually be used to empty the UI"
    246                  pass
    247
    248          # -----------------------------------------------------
    249 -        def _post_patient_selection(self, **kwds):
    250                  print("%s._post_patient_selection() not implemented" % self.__class__.__name__)
    251                  print("should usually be used to schedule reloading the UI")
    252
    253  #==================================================================
    254  # some convenience functions
    255  #------------------------------------------------------------------
    256 -def __gm_import(module_name):
    257          """Import a module.
    258
    259          I am not sure *why* we need this. But the docs
    260          and Google say so. It's got something to do with
    261          package imports returning the toplevel package name."""
    262          try:
    263                  mod = __import__(module_name)
    264          except ImportError:
    265                  _log.exception ('Cannot __import__() module [%s].' % module_name)
    266                  return None
    267          components = module_name.split('.')
    268          for component in components[1:]:
    269                  mod = getattr(mod, component)
    270          return mod
    271
    272  #------------------------------------------------------------------
    273 -def instantiate_plugin(aPackage='xxxDEFAULTxxx', plugin_name='xxxDEFAULTxxx'):
    274          """Instantiates a plugin object from a package directory, returning the object.
    275
    276          NOTE: it does NOT call register() for you !!!!
    277
    278          - "set" specifies the subdirectory in which to find the plugin
    279          - this knows nothing of databases, all it does is instantiate a named plugin
    280
    281          There will be a general 'gui' directory for large GUI
    282          components: prescritions, etc., then several others for more
    283          specific types: export/import filters, crypto algorithms
    284          guibroker, dbbroker are broker objects provided
    285          defaults are the default set of plugins to be loaded
    286
    287          FIXME: we should inform the user about failing plugins
    288          """
    289          # we do need brokers, else we are useless
    290          gb = gmGuiBroker.GuiBroker()
    291
    292          # bean counting ! -> loaded plugins
    293          if not ('horstspace.notebook.%s' % aPackage) in gb.keylist():
    294                  gb['horstspace.notebook.%s' % aPackage] = {}
    295          if not 'horstspace.notebook.pages' in gb.keylist():
    296                  gb['horstspace.notebook.pages'] = []
    297
    298          module_from_package = __gm_import('Gnumed.wxpython.%s.%s' % (aPackage, plugin_name))
    299          # find name of class of plugin (must be the same as the plugin module filename)
    300          plugin_class = module_from_package.__dict__[plugin_name]
    301
    302          if not issubclass(plugin_class, cNotebookPlugin):
    303                  _log.error("[%s] not a subclass of cNotebookPlugin" % plugin_name)
    304                  return None
    305
    306          _log.info(plugin_name)
    307          try:
    308                  plugin = plugin_class()
    309          except:
    310                  _log.exception('Cannot open module "%s.%s".' % (aPackage, plugin_name))
    311                  return None
    312
    313          return plugin
    314
    315  #------------------------------------------------------------------
    316 -def get_installed_plugins(plugin_dir=''):
    317          """Looks for installed plugins in the filesystem.
    318
    319          The first directory in sys.path which contains a wxpython/gui/
    320          is considered the one -- because that's where the import will
    321          get it from.
    322          """
    323          _log.debug('searching installed plugins')
    324          search_path = None
    325          candidates = sys.path[:]
    326          candidates.append(gmTools.gmPaths().local_base_dir)
    327          for candidate in candidates:
    328                  candidate = os.path.join(candidate, 'Gnumed', 'wxpython', plugin_dir)
    329                  _log.debug(candidate)
    330                  if os.path.exists(candidate):
    331                          search_path = candidate
    332                          break
    333                  _log.debug('not found')
    334
    335          if search_path is None:
    336                  _log.error('unable to find any directory matching [%s]', os.path.join('${CANDIDATE}', 'Gnumed', 'wxpython', plugin_dir))
    337                  _log.error('candidates: %s', str(candidates))
    338                  # read from config file
    339                  _log.info('trying to read list of installed plugins from config files')
    340                  plugins = _cfg.get (
    341                          group = 'client',
    342                          option = 'installed plugins',
    343                          source_order = [
    344                                  ('system', 'extend'),
    345                                  ('user', 'extend'),
    346                                  ('workbase', 'extend'),
    347                                  ('explicit', 'extend')
    348                          ]
    349                  )
    350                  if plugins is None:
    351                          _log.debug('no plugins found in config files')
    352                          return []
    353                  _log.debug("plugins found: %s" % str(plugins))
    354                  return plugins
    355
    356          _log.info("scanning plugin directory [%s]" % search_path)
    357
    358          files = glob.glob(os.path.join(search_path, 'gm*.py'))
    359          plugins = []
    360          for f in files:
    361                  path, fname = os.path.split(f)
    362                  mod_name, ext = os.path.splitext(fname)
    363                  plugins.append(mod_name)
    364
    365          _log.debug("plugins found: %s" % str(plugins))
    366
    367          return plugins
    368
    369  #------------------------------------------------------------------
    370 -def GetPluginLoadList(option, plugin_dir = '', defaults = None, workplace=None):
    371          """Get a list of plugins to load.
    372
    373          1) from database if option is not None
    374          2) from list of defaults
    375          3) if 2 is None, from source directory (then stored in database)
    376
    377          FIXME: NOT from files in directories (important for py2exe)
    378          """
    379          if workplace == 'System Fallback':
    380                  return ['gmProviderInboxPlugin', 'gmDataMiningPlugin']
    381
    382          if workplace is None:
    383                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
    384
    385          p_list = None
    386
    387          if option is not None:
    388                  dbcfg = gmCfg.cCfgSQL()
    389                  p_list = dbcfg.get2 (
    390                          option = option,
    391                          workplace = workplace,
    392                          bias = 'workplace',
    393                          default = defaults
    394                  )
    395
    396          if p_list is not None:
    397                  return p_list
    398
    399          if defaults is None:
    400                  p_list = get_installed_plugins(plugin_dir = plugin_dir)
    401                  if (len(p_list) == 0):
    402                          _log.error('cannot find plugins by scanning plugin directory ?!?')
    403                          return defaults
    404          else:
    405                  p_list = defaults
    406
    407          # store for current user/current workplace
    408          dbcfg.set (
    409                  option = option,
    410                  value = p_list,
    411                  workplace = workplace
    412          )
    413
    414          _log.debug("plugin load list stored: %s" % str(p_list))
    415          return p_list
    416
    417  #------------------------------------------------------------------
    418 -def UnloadPlugin (set, name):
    419          """
    420          Unloads the named plugin
    421          """
    422          gb = gmGuiBroker.GuiBroker()
    423          plugin = gb['horstspace.notebook.%s' % set][name]
    424          plugin.unregister()
    425
    426  #==================================================================
    427  # Main
    428  #------------------------------------------------------------------
    429  if __name__ == '__main__':
    430
    431          if len(sys.argv) > 1 and sys.argv[1] == 'test':
    432                  print(get_installed_plugins('gui'))
    433