# Module `Gnumed.wxpython.gmPlugin`

gmPlugin - base classes for GNUmed Horst space notebook plugins.

@copyright: author

## Functions

`def GetPluginLoadList(option, plugin_dir='', defaults=None, workplace=None)`
  Expand source code

      def GetPluginLoadList(option, plugin_dir = '', defaults = None, workplace=None):
              """Get a list of plugins to load.

              1) from database if option is not None
              2) from list of defaults
              3) if 2 is None, from source directory (then stored in database)

              FIXME: NOT from files in directories (important for py2exe)
              """
              if workplace == 'System Fallback':
                      return ['gmProviderInboxPlugin', 'gmDataMiningPlugin']

              if workplace is None:
                      workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace

              p_list = None

              if option is not None:
                      p_list = gmCfgDB.get4workplace (
                              option = option,
                              workplace = workplace,
                              default = defaults
                      )

              if p_list is not None:
                      return p_list

              if defaults is None:
                      p_list = get_installed_plugins(plugin_dir = plugin_dir)
                      if (len(p_list) == 0):
                              _log.error('cannot find plugins by scanning plugin directory ?!?')
                              return defaults
              else:
                      p_list = defaults
              # store for current user/current workplace
              gmCfgDB.set(option = option, value = p_list, workplace = workplace)
              _log.debug("plugin load list stored: %s" % str(p_list))
              return p_list

  Get a list of plugins to load.

  1) from database if option is not None 2) from list of defaults 3) if 2 is None, from source directory (then stored in database)

  FIXME: NOT from files in directories (important for py2exe)

`def UnloadPlugin(set, name)`
  Expand source code

      def UnloadPlugin (set, name):
              """
              Unloads the named plugin
              """
              gb = gmGuiBroker.GuiBroker()
              plugin = gb['horstspace.notebook.%s' % set][name]
              plugin.unregister()

  Unloads the named plugin

`def get_installed_plugins(plugin_dir='')`
  Expand source code

      def get_installed_plugins(plugin_dir=''):
              """Looks for installed plugins in the filesystem.

              The first directory in sys.path which contains a wxpython/gui/
              is considered the one -- because that's where the import will
              get it from.
              """
              _log.debug('searching installed plugins')
              search_path = None
              candidates = sys.path[:]
              candidates.append(gmTools.gmPaths().local_base_dir)
              for candidate in candidates:
                      candidate = os.path.join(candidate, 'Gnumed', 'wxpython', plugin_dir)
                      _log.debug(candidate)
                      if os.path.exists(candidate):
                              search_path = candidate
                              break
                      _log.debug('not found')

              if search_path is None:
                      _log.error('unable to find any directory matching [%s]', os.path.join('${CANDIDATE}', 'Gnumed', 'wxpython', plugin_dir))
                      _log.error('candidates: %s', str(candidates))
                      # read from config file
                      _log.info('trying to read list of installed plugins from config files')
                      plugins = _cfg.get (
                              group = 'client',
                              option = 'installed plugins',
                              source_order = [
                                      ('system', 'extend'),
                                      ('user', 'extend'),
                                      ('workbase', 'extend'),
                                      ('explicit', 'extend')
                              ]
                      )
                      if plugins is None:
                              _log.debug('no plugins found in config files')
                              return []
                      _log.debug("plugins found: %s" % str(plugins))
                      return plugins

              _log.info("scanning plugin directory [%s]" % search_path)

              files = glob.glob(os.path.join(search_path, 'gm*.py'))
              plugins = []
              for f in files:
                      path, fname = os.path.split(f)
                      mod_name, ext = os.path.splitext(fname)
                      plugins.append(mod_name)

              _log.debug("plugins found: %s" % str(plugins))

              return plugins

  Looks for installed plugins in the filesystem.

  The first directory in sys.path which contains a wxpython/gui/ is considered the one – because that's where the import will get it from.

`def instantiate_plugin(aPackage='xxxDEFAULTxxx', plugin_name='xxxDEFAULTxxx')`
  Expand source code

      def instantiate_plugin(aPackage='xxxDEFAULTxxx', plugin_name='xxxDEFAULTxxx'):
              """Instantiates a plugin object from a package directory, returning the object.

              NOTE: it does NOT call register() for you !!!!

              - "set" specifies the subdirectory in which to find the plugin
              - this knows nothing of databases, all it does is instantiate a named plugin

              There will be a general 'gui' directory for large GUI
              components: prescriptions, etc., then several others for more
              specific types: export/import filters, crypto algorithms
              guibroker, dbbroker are broker objects provided
              defaults are the default set of plugins to be loaded

              FIXME: we should inform the user about failing plugins
              """
              # we do need brokers, else we are useless
              gb = gmGuiBroker.GuiBroker()

              # bean counting ! -> loaded plugins
              if not ('horstspace.notebook.%s' % aPackage) in gb.keylist():
                      gb['horstspace.notebook.%s' % aPackage] = {}
              if not 'horstspace.notebook.pages' in gb.keylist():
                      gb['horstspace.notebook.pages'] = []

              module_from_package = __gm_import('Gnumed.wxpython.%s.%s' % (aPackage, plugin_name))
              # find name of class of plugin (must be the same as the plugin module filename)
              plugin_class = module_from_package.__dict__[plugin_name]

              if not issubclass(plugin_class, cNotebookPlugin):
                      _log.error("[%s] not a subclass of cNotebookPlugin" % plugin_name)
                      return None

              _log.info(plugin_name)
              try:
                      plugin = plugin_class()
              except Exception:
                      _log.exception('Cannot open module "%s.%s".' % (aPackage, plugin_name))
                      return None

              return plugin

  Instantiates a plugin object from a package directory, returning the object.

  NOTE: it does NOT call register() for you !!!!

  - "set" specifies the subdirectory in which to find the plugin
  - this knows nothing of databases, all it does is instantiate a named plugin

  There will be a general 'gui' directory for large GUI components: prescriptions, etc., then several others for more specific types: export/import filters, crypto algorithms guibroker, dbbroker are broker objects provided defaults are the default set of plugins to be loaded

  FIXME: we should inform the user about failing plugins

## Classes

`class cLoadProgressBar (nr_plugins)`
  Expand source code

      class cLoadProgressBar (wx.ProgressDialog):
              def __init__(self, nr_plugins):
                      wx.ProgressDialog.__init__(
                              self,
                              title = _("GNUmed: configuring [%s] (%s plugins)") % (gmPraxis.gmCurrentPraxisBranch().active_workplace, nr_plugins),
                              message = _("loading list of plugins                               "),
                              maximum = nr_plugins,
                              parent = None,
                              style = wx.PD_ELAPSED_TIME
                              )
                      self.SetIcon(gmTools.get_icon(wx = wx))
                      self.idx = 0
                      self.nr_plugins = nr_plugins
                      self.prev_plugin = ""
              #----------------------------------------------------------
              def Update (self, result, plugin):
                      if result == -1:
                              result = ""
                      elif result == 0:
                              result = _("failed")
                      else:
                              result = _("success")
                      wx.ProgressDialog.Update (self,
                                      self.idx,
                                      _("previous: %s (%s)\ncurrent (%s/%s): %s") % (
                                              self.prev_plugin,
                                              result,
                                              (self.idx+1),
                                              self.nr_plugins,
                                              plugin))
                      self.prev_plugin = plugin
                      self.idx += 1

  ProgressDialog(title, message, maximum=100, parent=None, style=PD_APP_MODAL|PD_AUTO_HIDE)

  If supported by the platform this class will provide the platform's native progress dialog, else it will simply be the wxGenericProgressDialog.

  ### Ancestors

  - wx._core.ProgressDialog
  - wx._core.GenericProgressDialog
  - wx._core.Dialog
  - wx._core.TopLevelWindow
  - wx._core.NonOwnedWindow
  - wx._core.Window
  - wx._core.WindowBase
  - wx._core.EvtHandler
  - wx._core.Object
  - wx._core.Trackable
  - sip.wrapper
  - sip.simplewrapper

  ### Methods

  `def Update(self, result, plugin)`
    Expand source code

        def Update (self, result, plugin):
                if result == -1:
                        result = ""
                elif result == 0:
                        result = _("failed")
                else:
                        result = _("success")
                wx.ProgressDialog.Update (self,
                                self.idx,
                                _("previous: %s (%s)\ncurrent (%s/%s): %s") % (
                                        self.prev_plugin,
                                        result,
                                        (self.idx+1),
                                        self.nr_plugins,
                                        plugin))
                self.prev_plugin = plugin
                self.idx += 1

    Update(value, newmsg=EmptyString) -> (bool, skip)

    Updates the dialog, setting the progress bar to the new value and updating the message if new one is specified.

`class cNotebookPlugin`
  Expand source code

      class cNotebookPlugin:
              """Base class for plugins which provide a full notebook page.
              """
              def __init__(self):
                      self.gb = gmGuiBroker.GuiBroker()
                      self._set = 'gui'
                      self._widget = None
                      self.__register_events()

              #-----------------------------------------------------
              # plugin load API
              #-----------------------------------------------------
              def register(self):
                      """Register ourselves with the main notebook widget."""

                      _log.info("set: [%s] class: [%s] name: [%s]" % (self._set, self.__class__.__name__, self.name()))

                      # create widget
                      nb = self.gb['horstspace.notebook']
                      widget = self.GetWidget(nb)

                      # add ourselves to the main notebook
                      nb.AddPage(widget, self.name())

                      # so notebook can find this widget
                      self.gb['horstspace.notebook.%s' % self._set][self.__class__.__name__] = self
                      self.gb['horstspace.notebook.pages'].append(self)

                      # and put ourselves into the menu structure
                      menu_info = self.MenuInfo()
                      if menu_info is None:
                              # register with direct access menu only
                              gmDispatcher.send(signal = 'plugin_loaded', plugin_name = self.name(), class_name = self.__class__.__name__)
                      else:
                              name_of_menu, menu_item_name = menu_info
                              gmDispatcher.send (
                                      signal = 'plugin_loaded',
                                      plugin_name = menu_item_name,
                                      class_name = self.__class__.__name__,
                                      menu_name = name_of_menu,
                                      menu_item_name = menu_item_name,
                                      # FIXME: this shouldn't be self.name() but rather self.menu_help_string()
                                      menu_help_string = self.name()
                              )

                      return True

              #-----------------------------------------------------
              def unregister(self):
                      """Remove ourselves."""
                      del self.gb['horstspace.notebook.%s' % self._set][self.__class__.__name__]
                      _log.info("plugin: [%s] (class: [%s]) set: [%s]" % (self.name(), self.__class__.__name__, self._set))

                      # delete menu item
                      menu_info = self.MenuInfo()
                      if menu_info is not None:
                              menu = self.gb['main.%smenu' % menu_info[0]]
                              menu.Delete(self.menu_id)

                      # correct the notebook page list
                      nb_pages = self.gb['horstspace.notebook.pages']
                      nb_page_num = nb_pages.index(self)
                      del nb_pages[nb_page_num]

                      # delete notebook page
                      nb = self.gb['horstspace.notebook']
                      nb.DeletePage(nb_page_num)

              #-----------------------------------------------------
              def name(self):
                      return 'plugin <%s>' % self.__class__.__name__

              #-----------------------------------------------------
              def MenuInfo(self):
                      """Return tuple of (menuname, menuitem).

                      None: no menu entry wanted
                      """
                      return None

              #-----------------------------------------------------
              # activation API
              #-----------------------------------------------------
              def can_receive_focus(self):
                      """Called when this plugin is *about to* receive focus.

                      If None returned from here (or from overriders) the
                      plugin activation will be veto()ed (if it can be).
                      """
                      # FIXME: fail if locked
                      return True

              #-----------------------------------------------------
              def receive_focus(self):
                      """We *are* receiving focus via wx.EVT_NotebookPageChanged.

                      This can be used to populate the plugin widget on receiving focus.
                      """
                      if hasattr(self._widget, 'repopulate_ui'):
                              self._widget.repopulate_ui()
                      # else apparently it doesn't need it
                      return True

              #-----------------------------------------------------
              def _verify_patient_avail(self):
                      """Check for patient availability.

                      - convenience method for your can_receive_focus() handlers
                      """
                      # fail if no patient selected
                      pat = gmPerson.gmCurrentPatient()
                      if not pat.connected:
                              # FIXME: people want an optional red backgound here
                              gmDispatcher.send('statustext', msg = _('Cannot switch to [%s]: no patient selected') % self.name())
                              return None
                      return 1

              #-----------------------------------------------------
              def Raise(self):
                      """Raise ourselves."""
                      nb_pages = self.gb['horstspace.notebook.pages']
                      try:
                              plugin_page = nb_pages.index(self)
                      except ValueError:
                              # we may not have been loaded properly, so are not in the list ...
                              _log.error('plugin not loaded: %s', self)
                              return False

                      nb = self.gb['horstspace.notebook']
                      nb.SetSelection(plugin_page)
                      return True

              #-----------------------------------------------------
              def _on_raise_by_menu(self, event):
                      if not self.can_receive_focus():
                              return False
                      self.Raise()
                      return True

              #-----------------------------------------------------
              def _on_raise_by_signal(self, **kwds):
                      # does this signal concern us ?
                      if kwds['name'] not in [self.__class__.__name__, self.name()]:
                              return False
                      return self._on_raise_by_menu(None)

              # -----------------------------------------------------
              # event handlers for the popup window
              def on_load(self, evt):
                      # FIXME: talk to the configurator so we're loaded next time
                      self.register()
                      # FIXME: raise ?

              # -----------------------------------------------------
              def OnShow(self, evt):
                      self.register() # register without changing configuration

              # -----------------------------------------------------
              def __register_events(self):
                      gmDispatcher.connect(signal = 'display_widget', receiver = self._on_raise_by_signal)

  Base class for plugins which provide a full notebook page.

  ### Subclasses

  - gmAllergiesPlugin
  - gmManual

  ### Methods

  `def MenuInfo(self)`
    Expand source code

        def MenuInfo(self):
                """Return tuple of (menuname, menuitem).

                None: no menu entry wanted
                """
                return None

    Return tuple of (menuname, menuitem).

    None: no menu entry wanted

  `def OnShow(self, evt)`
    Expand source code

        def OnShow(self, evt):
                self.register() # register without changing configuration

  `def Raise(self)`
    Expand source code

        def Raise(self):
                """Raise ourselves."""
                nb_pages = self.gb['horstspace.notebook.pages']
                try:
                        plugin_page = nb_pages.index(self)
                except ValueError:
                        # we may not have been loaded properly, so are not in the list ...
                        _log.error('plugin not loaded: %s', self)
                        return False

                nb = self.gb['horstspace.notebook']
                nb.SetSelection(plugin_page)
                return True

    Raise ourselves.

  `def can_receive_focus(self)`
    Expand source code

        def can_receive_focus(self):
                """Called when this plugin is *about to* receive focus.

                If None returned from here (or from overriders) the
                plugin activation will be veto()ed (if it can be).
                """
                # FIXME: fail if locked
                return True

    Called when this plugin is about to receive focus.

    If None returned from here (or from overriders) the plugin activation will be veto()ed (if it can be).

  `def name(self)`
    Expand source code

        def name(self):
                return 'plugin <%s>' % self.__class__.__name__

  `def on_load(self, evt)`
    Expand source code

        def on_load(self, evt):
                # FIXME: talk to the configurator so we're loaded next time
                self.register()
                # FIXME: raise ?

  `def receive_focus(self)`
    Expand source code

        def receive_focus(self):
                """We *are* receiving focus via wx.EVT_NotebookPageChanged.

                This can be used to populate the plugin widget on receiving focus.
                """
                if hasattr(self._widget, 'repopulate_ui'):
                        self._widget.repopulate_ui()
                # else apparently it doesn't need it
                return True

    We are receiving focus via wx.EVT_NotebookPageChanged.

    This can be used to populate the plugin widget on receiving focus.

  `def register(self)`
    Expand source code

        def register(self):
                """Register ourselves with the main notebook widget."""

                _log.info("set: [%s] class: [%s] name: [%s]" % (self._set, self.__class__.__name__, self.name()))

                # create widget
                nb = self.gb['horstspace.notebook']
                widget = self.GetWidget(nb)

                # add ourselves to the main notebook
                nb.AddPage(widget, self.name())

                # so notebook can find this widget
                self.gb['horstspace.notebook.%s' % self._set][self.__class__.__name__] = self
                self.gb['horstspace.notebook.pages'].append(self)

                # and put ourselves into the menu structure
                menu_info = self.MenuInfo()
                if menu_info is None:
                        # register with direct access menu only
                        gmDispatcher.send(signal = 'plugin_loaded', plugin_name = self.name(), class_name = self.__class__.__name__)
                else:
                        name_of_menu, menu_item_name = menu_info
                        gmDispatcher.send (
                                signal = 'plugin_loaded',
                                plugin_name = menu_item_name,
                                class_name = self.__class__.__name__,
                                menu_name = name_of_menu,
                                menu_item_name = menu_item_name,
                                # FIXME: this shouldn't be self.name() but rather self.menu_help_string()
                                menu_help_string = self.name()
                        )

                return True

    Register ourselves with the main notebook widget.

  `def unregister(self)`
    Expand source code

        def unregister(self):
                """Remove ourselves."""
                del self.gb['horstspace.notebook.%s' % self._set][self.__class__.__name__]
                _log.info("plugin: [%s] (class: [%s]) set: [%s]" % (self.name(), self.__class__.__name__, self._set))

                # delete menu item
                menu_info = self.MenuInfo()
                if menu_info is not None:
                        menu = self.gb['main.%smenu' % menu_info[0]]
                        menu.Delete(self.menu_id)

                # correct the notebook page list
                nb_pages = self.gb['horstspace.notebook.pages']
                nb_page_num = nb_pages.index(self)
                del nb_pages[nb_page_num]

                # delete notebook page
                nb = self.gb['horstspace.notebook']
                nb.DeletePage(nb_page_num)

    Remove ourselves.

`class cPatientChange_PluginMixin`
  Expand source code

      class cPatientChange_PluginMixin:
              """This mixin adds listening to patient change signals."""
              def __init__(self):
                      gmDispatcher.connect(self._pre_patient_unselection, 'pre_patient_unselection')
                      gmDispatcher.connect(self._on_current_patient_unset, 'current_patient_unset')
                      gmDispatcher.connect(self._post_patient_selection, 'post_patient_selection')

              # -----------------------------------------------------
              def _pre_patient_unselection(self, **kwds):
      #               print "%s._pre_patient_unselection() not implemented" % self.__class__.__name__
      #               print "should usually be used to commit unsaved data"
                      pass

              # -----------------------------------------------------
              def _on_current_patient_unset(self, **kwds):
      #               print "%s._on_current_patient_unset() not implemented" % self.__class__.__name__
      #               print "should usually be used to empty the UI"
                      pass

              # -----------------------------------------------------
              def _post_patient_selection(self, **kwds):
                      print("%s._post_patient_selection() not implemented" % self.__class__.__name__)
                      print("should usually be used to schedule reloading the UI")

  This mixin adds listening to patient change signals.