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 Date: Wed, 15 May 2019 09:03:49 -0700 (PDT) 128 From: Robin Dunn <robin@alldunn.com> 129 To: wxPython-users <wxpython-users@googlegroups.com> 130 Subject: [wxPython-users] Re: wx.Notebook events work differently on Mac vs Linux and Windows 131 132 While in the _CHANGED event the notebook's current page (what you get from 133 GetCurrentPage) still hasn't been updated. I agree that this seems 134 unintuitive but it's always been that way. There may be a good reason for 135 it, but I don't know what it is. 136 137 Instead, you can use the event's GetSelection to get the index of the page 138 being changed to, and then use the notebook's GetPage to get the page object. 139 140 Also, you should call event.Skip() in your _CHANGED handler. I think there 141 is some layout things happening in the default handler on one or more of 142 the platforms, so you want to ensure that can still happen. 143 -- 144 Robin 145 """ 146 _log.debug('just after switching notebook tabs') 147 148 event.Skip() 149 150 _log.debug('id: %s', event.Id) 151 _log.debug('event object (= source notebook): %s = %s', event.EventObject.Id, event.EventObject) 152 _log.debug('this notebook (= event receiver): %s = %s', self.Id, self) 153 if event.EventObject.Id != self.Id: 154 _log.error('this event came from another notebook') 155 156 id_nb_page_after_switch = self.GetSelection() 157 id_evt_page_before_switch = event.GetOldSelection() 158 id_evt_page_after_switch = event.GetSelection() 159 160 _log.debug('source/target page state in EVT_NOTEBOOK_PAGE_CHANGED:') 161 _log.debug(' #1 - current notebook page: %s (notebook.GetSelection())', id_nb_page_after_switch) 162 _log.debug(' #2 - event source page: %s (= page event says it is coming from, event.GetOldSelection())', id_evt_page_before_switch) 163 _log.debug(' #3 - event target page: %s (= page event wants to go to, event.GetSelection())', id_evt_page_after_switch) 164 165 if self.__id_nb_page_before_switch != id_evt_page_before_switch: 166 _log.warning('those two really *should* match:') 167 _log.warning(' wx.Notebook.GetSelection(): %s (notebook current page before switch) ', self.__id_nb_page_before_switch) 168 _log.warning(' EVT_NOTEBOOK_PAGE_CHANGED.GetOldSelection(): %s (event source page)' % id_evt_page_before_switch) 169 170 target_page = self.__gb['horstspace.notebook.pages'][id_evt_page_after_switch] 171 172 # well-behaving wxPython port ? 173 if self.__target_page_already_checked: 174 _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) 175 target_page.receive_focus() 176 self.__target_page_already_checked = False 177 return 178 179 # no, complain 180 _log.debug('target page not checked for focussability yet: %s', target_page) 181 _log.debug('EVT_NOTEBOOK_PAGE_CHANGED.GetOldSelection(): %s' % id_evt_page_before_switch) 182 _log.debug('EVT_NOTEBOOK_PAGE_CHANGED.GetSelection() : %s' % id_evt_page_after_switch) 183 _log.debug('wx.Notebook.GetSelection() (after switch) : %s' % id_nb_page_after_switch) 184 185 # check the new page just for good measure 186 if target_page.can_receive_focus(): 187 _log.debug('we are lucky: target page *can* receive focus anyway') 188 target_page.receive_focus() 189 return 190 191 _log.error('target page cannot receive focus but too late for veto') 192 return
193 194 #============================================================================== 195 # finding the visible page from a notebook page: self.GetParent.GetCurrentPage == self
196 -class cHorstSpaceLayoutMgr(wx.Panel):
197 """GNUmed inner-frame layout manager. 198 199 This implements a Horst-space notebook-only 200 "inner-frame" layout manager. 201 """
202 - def __init__(self, parent, id):
203 # main panel 204 wx.Panel.__init__( 205 self, 206 parent = parent, 207 id = id, 208 pos = wx.DefaultPosition, 209 size = wx.DefaultSize, 210 style = wx.NO_BORDER, 211 name = 'HorstSpace.LayoutMgrPnl' 212 ) 213 214 # top "row": important patient data is displayed there continually 215 self.top_panel = gmTopPanel.cTopPnl(self, -1) 216 217 # notebook 218 self.nb = cHorstSpaceNotebook(parent = self) 219 220 # plugins 221 self.__gb = gmGuiBroker.GuiBroker() 222 self.__gb['horstspace.top_panel'] = self.top_panel 223 self.__gb['horstspace.notebook'] = self.nb # FIXME: remove per Ian's API suggestion 224 self.__load_plugins() 225 226 # layout handling 227 self.main_szr = wx.BoxSizer(wx.VERTICAL) 228 self.main_szr.Add(self.top_panel, 0, wx.EXPAND) 229 self.main_szr.Add(self.nb, 1, wx.EXPAND) 230 self.SetSizer(self.main_szr) 231 # self.SetSizerAndFit(self.main_szr) 232 # self.Layout() 233 # self.Show(True) 234 235 self.__register_events()
236 237 #---------------------------------------------- 238 # internal API 239 #----------------------------------------------
240 - def __register_events(self):
241 # # because of 242 # # https://www.wiki.wxpython.org/self.Bind%20vs.%20self.button.Bind 243 # # do self.Bind() rather than self.nb.Bind() 244 # # - notebook page is about to change 245 # #self.nb.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self._on_notebook_page_changing) 246 self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self._on_notebook_page_changing, self.nb) 247 # # - notebook page has been changed 248 # #self.nb.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self._on_notebook_page_changed) 249 self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self._on_notebook_page_changed, self.nb) 250 # # - popup menu on right click in notebook 251 # #wx.EVT_RIGHT_UP(self.nb, self._on_right_click) 252 253 gmDispatcher.connect(self._on_post_patient_selection, 'post_patient_selection')
254 255 #----------------------------------------------
256 - def __load_plugins(self):
257 258 wx.BeginBusyCursor() 259 260 # get plugin list 261 plugins2load_by_name = gmPlugin.GetPluginLoadList ( 262 option = 'horstspace.notebook.plugin_load_order', 263 plugin_dir = 'gui', 264 defaults = ['gmProviderInboxPlugin'] 265 ) 266 267 _log.debug('plugin load order: %s', plugins2load_by_name) 268 269 nr_plugins = len(plugins2load_by_name) 270 failed_plugins = [] 271 272 # set up a progress bar 273 progress_bar = gmPlugin.cLoadProgressBar(nr_plugins) 274 275 # and load them 276 prev_plugin = "" 277 first_plugin = None 278 plugin = None 279 result = -1 280 for idx in range(nr_plugins): 281 curr_plugin = plugins2load_by_name[idx] 282 progress_bar.Update(result, curr_plugin) 283 try: 284 plugin = gmPlugin.instantiate_plugin('gui', curr_plugin) 285 if plugin: 286 plugin.register() 287 result = 1 288 else: 289 _log.error("plugin [%s] not loaded, see errors above", curr_plugin) 290 failed_plugins.append(curr_plugin) 291 result = 1 292 except: 293 _log.exception('failed to load plugin %s', curr_plugin) 294 failed_plugins.append(curr_plugin) 295 result = 0 296 297 if first_plugin is None: 298 first_plugin = plugin 299 prev_plugin = curr_plugin 300 301 _log.debug('failed plugins: %s', failed_plugins) 302 progress_bar.DestroyLater() 303 wx.EndBusyCursor() 304 305 # force-refresh first notebook page 306 page = self.nb.GetPage(0) 307 page.Refresh() 308 309 return True
310 311 #---------------------------------------------- 312 # external callbacks 313 #----------------------------------------------
314 - def _on_post_patient_selection(self, **kwargs):
315 db_cfg = gmCfg.cCfgSQL() 316 default_plugin = db_cfg.get2 ( 317 option = 'patient_search.plugin_to_raise_after_search', 318 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 319 bias = 'user', 320 default = 'gmPatientOverviewPlugin' 321 ) 322 gmDispatcher.send(signal = 'display_widget', name = default_plugin)
323 324 #----------------------------------------------
325 - def _on_notebook_page_changing(self, event):
326 """Called before notebook page change is processed.""" 327 328 _log.debug('just before switching notebook tabs') 329 330 _log.debug('id: %s', event.Id) 331 _log.debug('event object (= source notebook): %s = %s', event.EventObject.Id, event.EventObject) 332 _log.debug('this notebook (= event receiver): %s = %s', self.nb.Id, self.nb) 333 if event.EventObject.Id != self.nb.Id: 334 _log.error('this event came from another notebook') 335 336 self.__target_page_already_checked = False 337 338 self.__id_nb_page_before_switch = self.nb.GetSelection() 339 self.__id_evt_page_before_switch = event.GetOldSelection() 340 __id_evt_page_after_switch = event.GetSelection() 341 342 _log.debug('source/target page state in EVT_NOTEBOOK_PAGE_CHANGING:') 343 _log.debug(' #1 - notebook current page: %s (= notebook.GetSelection())', self.__id_nb_page_before_switch) 344 _log.debug(' #2 - event source page: %s (= page event says it is coming from, event.GetOldSelection())', self.__id_evt_page_before_switch) 345 _log.debug(' #3 - event target page: %s (= page event wants to go to, event.GetSelection())', __id_evt_page_after_switch) 346 if self.__id_evt_page_before_switch != self.__id_nb_page_before_switch: 347 _log.warning(' problem: #1 and #2 really should match but do not') 348 349 # can we check the target page ? 350 if __id_evt_page_after_switch == self.__id_evt_page_before_switch: 351 # no, so complain 352 # (the docs say that on Windows GetSelection() returns the 353 # old page ID, eg. the same value GetOldSelection() returns) 354 _log.debug('this system is: sys: [%s] wx: [%s]', sys.platform, wx.Platform) 355 _log.debug('it seems to be one of those platforms that have no clue which notebook page they are switching to') 356 _log.debug('(Windows is documented to return the old page from both evt.GetOldSelection() and evt.GetSelection())') 357 _log.debug('current notebook page : %s', self.__id_nb_page_before_switch) 358 _log.debug('source page from event: %s', self.__id_evt_page_before_switch) 359 _log.debug('target page from event: %s', __id_evt_page_after_switch) 360 _log.warning('cannot check whether notebook page change needs to be vetoed') 361 # but let's do a basic check anyways 362 pat = gmPerson.gmCurrentPatient() 363 if not pat.connected: 364 gmDispatcher.send(signal = 'statustext', msg =_('Cannot change notebook tabs. No active patient.')) 365 event.Veto() 366 return 367 # that test passed, so let's hope things are fine 368 event.Allow() # redundant ? 369 event.Skip() 370 return 371 372 # check target page 373 target_page = self.__gb['horstspace.notebook.pages'][__id_evt_page_after_switch] 374 _log.debug('checking event target page for focussability: %s', target_page) 375 if not target_page.can_receive_focus(): 376 _log.warning('veto()ing page change') 377 event.Veto() 378 return 379 380 # everything seems fine so switch 381 _log.debug('event target page seems focussable') 382 self.__target_page_already_checked = True 383 event.Allow() # redundant ? 384 event.Skip() 385 return
386 387 #----------------------------------------------
388 - def _on_notebook_page_changed(self, event):
389 """Called when notebook page changes.""" 390 391 _log.debug('just after switching notebook tabs') 392 393 _log.debug('id: %s', event.Id) 394 _log.debug('event object (= source notebook): %s = %s', event.EventObject.Id, event.EventObject) 395 _log.debug('this notebook (= event receiver): %s = %s', self.nb.Id, self.nb) 396 if event.EventObject.Id != self.nb.Id: 397 _log.error('this event came from another notebook') 398 399 event.Skip() 400 401 id_nb_page_after_switch = self.nb.GetSelection() 402 id_evt_page_before_switch = event.GetOldSelection() 403 id_evt_page_after_switch = event.GetSelection() 404 405 _log.debug('source/target page state in EVT_NOTEBOOK_PAGE_CHANGED:') 406 _log.debug(' #1 - current notebook page: %s (notebook.GetSelection())', id_nb_page_after_switch) 407 _log.debug(' #2 - event source page: %s (= page event says it is coming from, event.GetOldSelection())', id_evt_page_before_switch) 408 _log.debug(' #3 - event target page: %s (= page event wants to go to, event.GetSelection())', id_evt_page_after_switch) 409 410 if self.__id_nb_page_before_switch != id_evt_page_before_switch: 411 _log.warning('those two really *should* match:') 412 _log.warning(' wx.Notebook.GetSelection(): %s (notebook current page before switch) ', self.__id_nb_page_before_switch) 413 _log.warning(' EVT_NOTEBOOK_PAGE_CHANGED.GetOldSelection(): %s (event source page)' % id_evt_page_before_switch) 414 415 target_page = self.__gb['horstspace.notebook.pages'][id_evt_page_after_switch] 416 417 # well-behaving wxPython port ? 418 if self.__target_page_already_checked: 419 _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) 420 target_page.receive_focus() 421 self.__target_page_already_checked = False 422 return 423 424 # no, complain 425 _log.debug('target page not checked for focussability yet: %s', target_page) 426 _log.debug('EVT_NOTEBOOK_PAGE_CHANGED.GetOldSelection(): %s' % id_evt_page_before_switch) 427 _log.debug('EVT_NOTEBOOK_PAGE_CHANGED.GetSelection() : %s' % id_evt_page_after_switch) 428 _log.debug('wx.Notebook.GetSelection() (after switch) : %s' % id_nb_page_after_switch) 429 430 # check the new page just for good measure 431 if target_page.can_receive_focus(): 432 _log.debug('we are lucky: target page *can* receive focus anyway') 433 target_page.receive_focus() 434 return 435 436 _log.error('target page cannot receive focus but too late for veto') 437 return
438 439 # #---------------------------------------------- 440 # def _on_right_click(self, evt): 441 # evt.Skip() 442 # return 443 # 444 # load_menu = wx.Menu() 445 # any_loadable = 0 446 # plugins2load_by_name = gmPlugin.GetPluginLoadList('gui') 447 # plugin = None 448 # for plugin_name in plugins2load_by_name: 449 # try: 450 # plugin = gmPlugin.instantiate_plugin('gui', plugin_name) 451 # except Exception: 452 # continue 453 # # not a plugin 454 # if not isinstance(plugin, gmPlugin.cNotebookPlugin): 455 # plugin = None 456 # continue 457 # # already loaded ? 458 # if plugin.__class__.__name__ in self.guibroker['horstspace.notebook.gui'].keys(): 459 # plugin = None 460 # continue 461 # # add to load menu 462 # item = load_menu.AppendItem(wx.MenuItem(load_menu, plugin.name())) 463 # self.Bind(wx.EVT_MENU, plugin.on_load, load_menu_OR_item) 464 # any_loadable = 1 465 # # make menus 466 # menu = wx.Menu() 467 # ID_LOAD = wx.NewId() 468 # ID_DROP = wx.NewId() 469 # if any_loadable: 470 # menu.Append(ID_LOAD, _('add plugin ...'), load_menu) 471 # plugins = self.guibroker['horstspace.notebook.gui'] 472 # raised_plugin = plugins[self.nb.GetSelection()].name() 473 # item = menu.AppendItem(wx.MenuItem(-1, "drop [%s]" % raised_plugin)) 474 # self.Bind(wx.EVT_MENU, self._on_drop_plugin, item) 475 # self.PopupMenu(menu, evt.GetPosition()) 476 # menu.DestroyLater() 477 # evt.Skip() 478 479 #----------------------------------------------
480 - def _on_drop_plugin(self, evt):
481 """Unload plugin and drop from load list.""" 482 pages = self.guibroker['horstspace.notebook.pages'] 483 page = pages[self.nb.GetSelection()] 484 page.unregister() 485 self.nb.AdvanceSelection()
486 # FIXME:"dropping" means talking to configurator so not reloaded 487 488 #----------------------------------------------
489 - def _on_hide_plugin (self, evt):
490 """Unload plugin but don't touch configuration.""" 491 # this dictionary links notebook page numbers to plugin objects 492 pages = self.guibroker['horstspace.notebook.pages'] 493 page = pages[self.nb.GetSelection()] 494 page.unregister()
495 496 #============================================================================== 497 if __name__ == '__main__': 498 wx.InitAllImageHandlers() 499 pgbar = gmPluginLoadProgressBar(3) 500