Gnumed.wxpython.gmHorstSpace

Package Gnumed :: Package wxpython :: Module gmHorstSpace |
[frames] | no frames]

# Source Code for Module Gnumed.wxpython.gmHorstSpace

      1  """GNUmed Horst-space inner-frame layout manager.
      2
      3  This implements the simple wx.Notebook based layout as
      4  originally suggested by Horst Herb.
      5
      6  copyright: authors
      7  """
      8  #==============================================================================
      9  __author__  = "H. Herb <hherb@gnumed.net>,\
     10                             K. Hilbert <Karsten.Hilbert@gmx.net>,\
     11                             I. Haywood <i.haywood@ugrad.unimelb.edu.au>"
     12  __license__ = 'GPL v2 or later (details at http://www.gnu.org)'
     13
     14  import os.path, os, sys, logging
     15
     16
     17  import wx
     18
     19
     20  from Gnumed.pycommon import gmGuiBroker, gmI18N, gmDispatcher, gmCfg, gmLog2
     21  from Gnumed.wxpython import gmPlugin, gmTopPanel, gmGuiHelpers
     22  from Gnumed.business import gmPerson, gmPraxis
     23
     24
     25  _log = logging.getLogger('gm.ui')
     26
     27  #==============================================================================
     28 -class cHorstSpaceNotebook(wx.Notebook):                 # wx.BestBook ?
     29
     30 -        def __init__(self, *args, **kwargs):
     31
     32                  kwargs['style'] = wx.NB_BOTTOM
     33                  kwargs['id'] = -1
     34                  wx.Notebook.__init__(self, *args, **kwargs)
     35
     36                  _log.debug('created wx.Notebook: %s with ID %s', self.__class__.__name__, self.Id)
     37
     38                  #self.__register_events()
     39
     40          #----------------------------------------------
     41          # internal API
     42          #----------------------------------------------
     43 -        def __register_events(self):
     44                  # because of
     45                  #       https://www.wiki.wxpython.org/self.Bind%20vs.%20self.button.Bind
     46                  # do self.Bind() rather than self.nb.Bind()
     47                  # - notebook page is about to change
     48                  #self.nb.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self._on_notebook_page_changing)
     49                  #self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self._on_notebook_page_changing, self)
     50                  self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self._on_notebook_page_changing)
     51                  # - notebook page has been changed
     52                  #self.nb.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self._on_notebook_page_changed)
     53                  #self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self._on_notebook_page_changed, self)
     54                  self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self._on_notebook_page_changed)
     55                  # - popup menu on right click in notebook
     56                  #wx.EVT_RIGHT_UP(self.nb, self._on_right_click)
     57
     58          #----------------------------------------------
     59          # event handlers
     60          #----------------------------------------------
     61 -        def _on_notebook_page_changing(self, event):
     62                  """Called before notebook page change is processed."""
     63
     64                  _log.debug('just before switching notebook tabs')
     65
     66                  _log.debug('id: %s', event.Id)
     67                  _log.debug('event object (= source notebook): %s = %s', event.EventObject.Id, event.EventObject)
     68                  _log.debug('this notebook (= event receiver): %s = %s', self.Id, self)
     69                  if event.EventObject.Id != self.Id:
     70                          _log.error('this event came from another notebook')
     71
     72                  self.__target_page_already_checked = False
     73
     74                  self.__id_nb_page_before_switch = self.GetSelection()
     75                  self.__id_evt_page_before_switch = event.GetOldSelection()
     76                  __id_evt_page_after_switch = event.GetSelection()
     77
     78                  _log.debug('source/target page state in EVT_NOTEBOOK_PAGE_CHANGING:')
     79                  _log.debug(' #1 - notebook current page: %s (= notebook.GetSelection())', self.__id_nb_page_before_switch)
     80                  _log.debug(' #2 - event source page: %s (= page event says it is coming from, event.GetOldSelection())', self.__id_evt_page_before_switch)
     81                  _log.debug(' #3 - event target page: %s (= page event wants to go to, event.GetSelection())', __id_evt_page_after_switch)
     82                  if self.__id_evt_page_before_switch != self.__id_nb_page_before_switch:
     83                          _log.warning(' problem: #1 and #2 really should match but do not')
     84
     85                  # can we check the target page ?
     86                  if __id_evt_page_after_switch == self.__id_evt_page_before_switch:
     87                          # no, so complain
     88                          # (the docs say that on Windows GetSelection() returns the
     89                          #  old page ID, eg. the same value GetOldSelection() returns)
     90                          _log.debug('this system is: sys: [%s] wx: [%s]', sys.platform, wx.Platform)
     91                          _log.debug('it seems to be one of those platforms that have no clue which notebook page they are switching to')
     92                          _log.debug('(Windows is documented to return the old page from both evt.GetOldSelection() and evt.GetSelection())')
     93                          _log.debug('current notebook page : %s', self.__id_nb_page_before_switch)
     94                          _log.debug('source page from event: %s', self.__id_evt_page_before_switch)
     95                          _log.debug('target page from event: %s', __id_evt_page_after_switch)
     96                          _log.warning('cannot check whether notebook page change needs to be vetoed')
     97                          # but let's do a basic check anyways
     98                          pat = gmPerson.gmCurrentPatient()
     99                          if not pat.connected:
    100                                  gmDispatcher.send(signal = 'statustext', msg =_('Cannot change notebook tabs. No active patient.'))
    101                                  event.Veto()
    102                                  return
    103                          # that test passed, so let's hope things are fine
    104                          event.Allow()           # redundant ?
    105                          event.Skip()
    106                          return
    107
    108                  # check target page
    109                  target_page = self.__gb['horstspace.notebook.pages'][__id_evt_page_after_switch]
    110                  _log.debug('checking event target page for focussability: %s', target_page)
    111                  if not target_page.can_receive_focus():
    112                          _log.warning('veto()ing page change')
    113                          event.Veto()
    114                          return
    115
    116                  # everything seems fine so switch
    117                  _log.debug('event target page seems focussable')
    118                  self.__target_page_already_checked = True
    119                  event.Allow()           # redundant ?
    120                  event.Skip()
    121                  return
    122
    123          #----------------------------------------------
    124 -        def _on_notebook_page_changed(self, event):
    125                  """Called when notebook page changes."""
    126
    127                  _log.debug('just after switching notebook tabs')
    128
    129                  _log.debug('id: %s', event.Id)
    130                  _log.debug('event object (= source notebook): %s = %s', event.EventObject.Id, event.EventObject)
    131                  _log.debug('this notebook (= event receiver): %s = %s', self.Id, self)
    132                  if event.EventObject.Id != self.Id:
    133                          _log.error('this event came from another notebook')
    134
    135                  event.Skip()
    136
    137                  id_nb_page_after_switch = self.GetSelection()
    138                  id_evt_page_before_switch = event.GetOldSelection()
    139                  id_evt_page_after_switch = event.GetSelection()
    140
    141                  _log.debug('source/target page state in EVT_NOTEBOOK_PAGE_CHANGED:')
    142                  _log.debug(' #1 - current notebook page: %s (notebook.GetSelection())', id_nb_page_after_switch)
    143                  _log.debug(' #2 - event source page: %s (= page event says it is coming from, event.GetOldSelection())', id_evt_page_before_switch)
    144                  _log.debug(' #3 - event target page: %s (= page event wants to go to, event.GetSelection())', id_evt_page_after_switch)
    145
    146                  if self.__id_nb_page_before_switch != id_evt_page_before_switch:
    147                          _log.warning('those two really *should* match:')
    148                          _log.warning(' wx.Notebook.GetSelection(): %s (notebook current page before switch) ', self.__id_nb_page_before_switch)
    149                          _log.warning(' EVT_NOTEBOOK_PAGE_CHANGED.GetOldSelection(): %s (event source page)' % id_evt_page_before_switch)
    150
    151                  target_page = self.__gb['horstspace.notebook.pages'][id_evt_page_after_switch]
    152
    153                  # well-behaving wxPython port ?
    154                  if self.__target_page_already_checked:
    155                          _log.debug('target page (evt=%s, nb=%s) claims to have been checked for focussability already: %s', id_evt_page_after_switch, id_nb_page_after_switch, target_page)
    156                          target_page.receive_focus()
    157                          self.__target_page_already_checked = False
    158                          return
    159
    160                  # no, complain
    161                  _log.debug('target page not checked for focussability yet: %s', target_page)
    162                  _log.debug('EVT_NOTEBOOK_PAGE_CHANGED.GetOldSelection(): %s' % id_evt_page_before_switch)
    163                  _log.debug('EVT_NOTEBOOK_PAGE_CHANGED.GetSelection()   : %s' % id_evt_page_after_switch)
    164                  _log.debug('wx.Notebook.GetSelection() (after switch)  : %s' % id_nb_page_after_switch)
    165
    166                  # check the new page just for good measure
    167                  if target_page.can_receive_focus():
    168                          _log.debug('we are lucky: target page *can* receive focus anyway')
    169                          target_page.receive_focus()
    170                          return
    171
    172                  _log.error('target page cannot receive focus but too late for veto')
    173                  return
    174
    175  #==============================================================================
    176  # finding the visible page from a notebook page: self.GetParent.GetCurrentPage == self
    177 -class cHorstSpaceLayoutMgr(wx.Panel):
    178          """GNUmed inner-frame layout manager.
    179
    180          This implements a Horst-space notebook-only
    181          "inner-frame" layout manager.
    182          """
    183 -        def __init__(self, parent, id):
    184                  # main panel
    185                  wx.Panel.__init__(
    186                          self,
    187                          parent = parent,
    188                          id = id,
    189                          pos = wx.DefaultPosition,
    190                          size = wx.DefaultSize,
    191                          style = wx.NO_BORDER,
    192                          name = 'HorstSpace.LayoutMgrPnl'
    193                  )
    194
    195                  # top "row": important patient data is displayed there continually
    196                  self.top_panel = gmTopPanel.cTopPnl(self, -1)
    197
    198                  # notebook
    199                  self.nb = cHorstSpaceNotebook(parent = self)
    200
    201                  # plugins
    202                  self.__gb = gmGuiBroker.GuiBroker()
    203                  self.__gb['horstspace.top_panel'] = self.top_panel
    204                  self.__gb['horstspace.notebook'] = self.nb # FIXME: remove per Ian's API suggestion
    205                  self.__load_plugins()
    206
    207                  # layout handling
    208                  self.main_szr = wx.BoxSizer(wx.VERTICAL)
    209                  self.main_szr.Add(self.top_panel, 0, wx.EXPAND)
    210                  self.main_szr.Add(self.nb, 1, wx.EXPAND)
    211                  self.SetSizer(self.main_szr)
    212  #               self.SetSizerAndFit(self.main_szr)
    213  #               self.Layout()
    214  #               self.Show(True)
    215
    216                  self.__register_events()
    217
    218          #----------------------------------------------
    219          # internal API
    220          #----------------------------------------------
    221 -        def __register_events(self):
    222  #               # because of
    223  #               #       https://www.wiki.wxpython.org/self.Bind%20vs.%20self.button.Bind
    224  #               # do self.Bind() rather than self.nb.Bind()
    225  #               # - notebook page is about to change
    226  #               #self.nb.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self._on_notebook_page_changing)
    227                  self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self._on_notebook_page_changing, self.nb)
    228  #               # - notebook page has been changed
    229  #               #self.nb.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self._on_notebook_page_changed)
    230                  self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self._on_notebook_page_changed, self.nb)
    231  #               # - popup menu on right click in notebook
    232  #               #wx.EVT_RIGHT_UP(self.nb, self._on_right_click)
    233
    234                  gmDispatcher.connect(self._on_post_patient_selection, 'post_patient_selection')
    235
    236          #----------------------------------------------
    237 -        def __load_plugins(self):
    238
    239                  wx.BeginBusyCursor()
    240
    241                  # get plugin list
    242                  plugins2load_by_name = gmPlugin.GetPluginLoadList (
    243                          option = 'horstspace.notebook.plugin_load_order',
    244                          plugin_dir = 'gui',
    245                          defaults = ['gmProviderInboxPlugin']
    246                  )
    247
    248                  _log.debug('plugin load order: %s', plugins2load_by_name)
    249
    250                  nr_plugins = len(plugins2load_by_name)
    251                  failed_plugins = []
    252
    253                  #  set up a progress bar
    254                  progress_bar = gmPlugin.cLoadProgressBar(nr_plugins)
    255
    256                  #  and load them
    257                  prev_plugin = ""
    258                  first_plugin = None
    259                  plugin = None
    260                  result = -1
    261                  for idx in range(nr_plugins):
    262                          curr_plugin = plugins2load_by_name[idx]
    263                          progress_bar.Update(result, curr_plugin)
    264                          try:
    265                                  plugin = gmPlugin.instantiate_plugin('gui', curr_plugin)
    266                                  if plugin:
    267                                          plugin.register()
    268                                          result = 1
    269                                  else:
    270                                          _log.error("plugin [%s] not loaded, see errors above", curr_plugin)
    271                                          failed_plugins.append(curr_plugin)
    272                                          result = 1
    273                          except:
    274                                  _log.exception('failed to load plugin %s', curr_plugin)
    275                                  failed_plugins.append(curr_plugin)
    276                                  result = 0
    277
    278                          if first_plugin is None:
    279                                  first_plugin = plugin
    280                          prev_plugin = curr_plugin
    281
    282                  _log.debug('failed plugins: %s', failed_plugins)
    283                  progress_bar.Destroy()
    284                  wx.EndBusyCursor()
    285
    286                  # force-refresh first notebook page
    287                  page = self.nb.GetPage(0)
    288                  page.Refresh()
    289
    290                  return True
    291
    292          #----------------------------------------------
    293          # external callbacks
    294          #----------------------------------------------
    295 -        def _on_post_patient_selection(self, **kwargs):
    296                  db_cfg = gmCfg.cCfgSQL()
    297                  default_plugin = db_cfg.get2 (
    298                          option = 'patient_search.plugin_to_raise_after_search',
    299                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    300                          bias = 'user',
    301                          default = 'gmPatientOverviewPlugin'
    302                  )
    303                  gmDispatcher.send(signal = 'display_widget', name = default_plugin)
    304
    305          #----------------------------------------------
    306 -        def _on_notebook_page_changing(self, event):
    307                  """Called before notebook page change is processed."""
    308
    309                  _log.debug('just before switching notebook tabs')
    310
    311                  _log.debug('id: %s', event.Id)
    312                  _log.debug('event object (= source notebook): %s = %s', event.EventObject.Id, event.EventObject)
    313                  _log.debug('this notebook (= event receiver): %s = %s', self.nb.Id, self.nb)
    314                  if event.EventObject.Id != self.nb.Id:
    315                          _log.error('this event came from another notebook')
    316
    317                  self.__target_page_already_checked = False
    318
    319                  self.__id_nb_page_before_switch = self.nb.GetSelection()
    320                  self.__id_evt_page_before_switch = event.GetOldSelection()
    321                  __id_evt_page_after_switch = event.GetSelection()
    322
    323                  _log.debug('source/target page state in EVT_NOTEBOOK_PAGE_CHANGING:')
    324                  _log.debug(' #1 - notebook current page: %s (= notebook.GetSelection())', self.__id_nb_page_before_switch)
    325                  _log.debug(' #2 - event source page: %s (= page event says it is coming from, event.GetOldSelection())', self.__id_evt_page_before_switch)
    326                  _log.debug(' #3 - event target page: %s (= page event wants to go to, event.GetSelection())', __id_evt_page_after_switch)
    327                  if self.__id_evt_page_before_switch != self.__id_nb_page_before_switch:
    328                          _log.warning(' problem: #1 and #2 really should match but do not')
    329
    330                  # can we check the target page ?
    331                  if __id_evt_page_after_switch == self.__id_evt_page_before_switch:
    332                          # no, so complain
    333                          # (the docs say that on Windows GetSelection() returns the
    334                          #  old page ID, eg. the same value GetOldSelection() returns)
    335                          _log.debug('this system is: sys: [%s] wx: [%s]', sys.platform, wx.Platform)
    336                          _log.debug('it seems to be one of those platforms that have no clue which notebook page they are switching to')
    337                          _log.debug('(Windows is documented to return the old page from both evt.GetOldSelection() and evt.GetSelection())')
    338                          _log.debug('current notebook page : %s', self.__id_nb_page_before_switch)
    339                          _log.debug('source page from event: %s', self.__id_evt_page_before_switch)
    340                          _log.debug('target page from event: %s', __id_evt_page_after_switch)
    341                          _log.warning('cannot check whether notebook page change needs to be vetoed')
    342                          # but let's do a basic check anyways
    343                          pat = gmPerson.gmCurrentPatient()
    344                          if not pat.connected:
    345                                  gmDispatcher.send(signal = 'statustext', msg =_('Cannot change notebook tabs. No active patient.'))
    346                                  event.Veto()
    347                                  return
    348                          # that test passed, so let's hope things are fine
    349                          event.Allow()           # redundant ?
    350                          event.Skip()
    351                          return
    352
    353                  # check target page
    354                  target_page = self.__gb['horstspace.notebook.pages'][__id_evt_page_after_switch]
    355                  _log.debug('checking event target page for focussability: %s', target_page)
    356                  if not target_page.can_receive_focus():
    357                          _log.warning('veto()ing page change')
    358                          event.Veto()
    359                          return
    360
    361                  # everything seems fine so switch
    362                  _log.debug('event target page seems focussable')
    363                  self.__target_page_already_checked = True
    364                  event.Allow()           # redundant ?
    365                  event.Skip()
    366                  return
    367
    368          #----------------------------------------------
    369 -        def _on_notebook_page_changed(self, event):
    370                  """Called when notebook page changes."""
    371
    372                  _log.debug('just after switching notebook tabs')
    373
    374                  _log.debug('id: %s', event.Id)
    375                  _log.debug('event object (= source notebook): %s = %s', event.EventObject.Id, event.EventObject)
    376                  _log.debug('this notebook (= event receiver): %s = %s', self.nb.Id, self.nb)
    377                  if event.EventObject.Id != self.nb.Id:
    378                          _log.error('this event came from another notebook')
    379
    380                  event.Skip()
    381
    382                  id_nb_page_after_switch = self.nb.GetSelection()
    383                  id_evt_page_before_switch = event.GetOldSelection()
    384                  id_evt_page_after_switch = event.GetSelection()
    385
    386                  _log.debug('source/target page state in EVT_NOTEBOOK_PAGE_CHANGED:')
    387                  _log.debug(' #1 - current notebook page: %s (notebook.GetSelection())', id_nb_page_after_switch)
    388                  _log.debug(' #2 - event source page: %s (= page event says it is coming from, event.GetOldSelection())', id_evt_page_before_switch)
    389                  _log.debug(' #3 - event target page: %s (= page event wants to go to, event.GetSelection())', id_evt_page_after_switch)
    390
    391                  if self.__id_nb_page_before_switch != id_evt_page_before_switch:
    392                          _log.warning('those two really *should* match:')
    393                          _log.warning(' wx.Notebook.GetSelection(): %s (notebook current page before switch) ', self.__id_nb_page_before_switch)
    394                          _log.warning(' EVT_NOTEBOOK_PAGE_CHANGED.GetOldSelection(): %s (event source page)' % id_evt_page_before_switch)
    395
    396                  target_page = self.__gb['horstspace.notebook.pages'][id_evt_page_after_switch]
    397
    398                  # well-behaving wxPython port ?
    399                  if self.__target_page_already_checked:
    400                          _log.debug('target page (evt=%s, nb=%s) claims to have been checked for focussability already: %s', id_evt_page_after_switch, id_nb_page_after_switch, target_page)
    401                          target_page.receive_focus()
    402                          self.__target_page_already_checked = False
    403                          return
    404
    405                  # no, complain
    406                  _log.debug('target page not checked for focussability yet: %s', target_page)
    407                  _log.debug('EVT_NOTEBOOK_PAGE_CHANGED.GetOldSelection(): %s' % id_evt_page_before_switch)
    408                  _log.debug('EVT_NOTEBOOK_PAGE_CHANGED.GetSelection()   : %s' % id_evt_page_after_switch)
    409                  _log.debug('wx.Notebook.GetSelection() (after switch)  : %s' % id_nb_page_after_switch)
    410
    411                  # check the new page just for good measure
    412                  if target_page.can_receive_focus():
    413                          _log.debug('we are lucky: target page *can* receive focus anyway')
    414                          target_page.receive_focus()
    415                          return
    416
    417                  _log.error('target page cannot receive focus but too late for veto')
    418                  return
    419
    420  #       #----------------------------------------------
    421  #       def _on_right_click(self, evt):
    422  #               evt.Skip()
    423  #               return
    424  #
    425  #               load_menu = wx.Menu()
    426  #               any_loadable = 0
    427  #               plugins2load_by_name = gmPlugin.GetPluginLoadList('gui')
    428  #               plugin = None
    429  #               for plugin_name in plugins2load_by_name:
    430  #                       try:
    431  #                               plugin = gmPlugin.instantiate_plugin('gui', plugin_name)
    432  #                       except Exception:
    433  #                               continue
    434  #                       # not a plugin
    435  #                       if not isinstance(plugin, gmPlugin.cNotebookPlugin):
    436  #                               plugin = None
    437  #                               continue
    438  #                       # already loaded ?
    439  #                       if plugin.__class__.__name__ in self.guibroker['horstspace.notebook.gui'].keys():
    440  #                               plugin = None
    441  #                               continue
    442  #                       # add to load menu
    443  #                       item = load_menu.AppendItem(wx.MenuItem(load_menu, plugin.name()))
    444  #                       self.Bind(wx.EVT_MENU, plugin.on_load, load_menu_OR_item)
    445  #                       any_loadable = 1
    446  #               # make menus
    447  #               menu = wx.Menu()
    448  #               ID_LOAD = wx.NewId()
    449  #               ID_DROP = wx.NewId()
    450  #               if any_loadable:
    451  #                       menu.Append(ID_LOAD, _('add plugin ...'), load_menu)
    452  #               plugins = self.guibroker['horstspace.notebook.gui']
    453  #               raised_plugin = plugins[self.nb.GetSelection()].name()
    454  #               item = menu.AppendItem(wx.MenuItem(-1, "drop [%s]" % raised_plugin))
    455  #               self.Bind(wx.EVT_MENU, self._on_drop_plugin, item)
    456  #               self.PopupMenu(menu, evt.GetPosition())
    457  #               menu.Destroy()
    458  #               evt.Skip()
    459
    460          #----------------------------------------------
    461 -        def _on_drop_plugin(self, evt):
    462                  """Unload plugin and drop from load list."""
    463                  pages = self.guibroker['horstspace.notebook.pages']
    464                  page = pages[self.nb.GetSelection()]
    465                  page.unregister()
    466                  self.nb.AdvanceSelection()
    467                  # FIXME:"dropping" means talking to configurator so not reloaded
    468
    469          #----------------------------------------------
    470 -        def _on_hide_plugin (self, evt):
    471                  """Unload plugin but don't touch configuration."""
    472                  # this dictionary links notebook page numbers to plugin objects
    473                  pages = self.guibroker['horstspace.notebook.pages']
    474                  page = pages[self.nb.GetSelection()]
    475                  page.unregister()
    476
    477  #==============================================================================
    478  if __name__ == '__main__':
    479          wx.InitAllImageHandlers()
    480          pgbar = gmPluginLoadProgressBar(3)
    481