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

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.