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

Source Code for Module Gnumed.wxpython.gmTopPanel

  1  # GnuMed 
  2   
  3  #=========================================================== 
  4  # $Source: /home/ncq/Projekte/cvs2git/vcs-mirror/gnumed/gnumed/client/wxpython/gmTopPanel.py,v $ 
  5  # $Id: gmTopPanel.py,v 1.106 2009-07-17 09:26:53 ncq Exp $ 
  6  __version__ = "$Revision: 1.106 $" 
  7  __author__  = "R.Terry <rterry@gnumed.net>, I.Haywood <i.haywood@ugrad.unimelb.edu.au>, K.Hilbert <Karsten.Hilbert@gmx.net>" 
  8  __license__ = "GPL" 
  9   
 10   
 11  import sys, os.path, datetime as pyDT, logging 
 12   
 13   
 14  import wx 
 15   
 16   
 17  from Gnumed.pycommon import gmGuiBroker, gmPG2, gmDispatcher, gmTools, gmCfg2, gmDateTime, gmI18N 
 18  from Gnumed.business import gmPerson, gmEMRStructItems, gmAllergy 
 19   
 20  from Gnumed.wxpython import gmGuiHelpers 
 21  from Gnumed.wxpython import gmDemographicsWidgets 
 22  from Gnumed.wxpython import gmAllergyWidgets 
 23  from Gnumed.wxpython import gmPatSearchWidgets 
 24  from Gnumed.wxpython import gmPatPicWidgets 
 25   
 26   
 27  _log = logging.getLogger('gm.ui') 
 28  _log.info(__version__) 
 29   
 30  [       ID_BTN_pat_demographics, 
 31  #       ID_CBOX_consult_type, 
 32          ID_BMITOOL, 
 33          ID_BMIMENU, 
 34          ID_PREGTOOL, 
 35          ID_PREGMENU, 
 36          ID_LOCKBUTTON, 
 37          ID_LOCKMENU, 
 38  ] = map(lambda _init_ctrls: wx.NewId(), range(7)) 
 39   
 40  # FIXME: need a better name here ! 
 41  bg_col = wx.Colour(214,214,214) 
 42  fg_col = wx.Colour(0,0,131) 
 43  col_brightred = wx.Colour(255,0,0) 
 44  #=========================================================== 
45 -class cMainTopPanel(wx.Panel):
46
47 - def __init__(self, parent, id):
48 49 wx.Panel.__init__(self, parent, id, wx.DefaultPosition, wx.DefaultSize, wx.RAISED_BORDER) 50 51 self.__gb = gmGuiBroker.GuiBroker() 52 53 self.__do_layout() 54 self.__register_interests() 55 56 # init plugin toolbars dict 57 #self.subbars = {} 58 self.curr_pat = gmPerson.gmCurrentPatient() 59 60 # and actually display ourselves 61 self.SetAutoLayout(True) 62 self.Show(True)
63 #-------------------------------------------------------
64 - def __do_layout(self):
65 """Create the layout. 66 67 .--------------------------------. 68 | patient | top row | 69 | picture |----------------------| 70 | | bottom row | 71 `--------------------------------' 72 """ 73 self.SetBackgroundColour(bg_col) 74 75 # create rows 76 # - top row 77 # .--------------------------------------. 78 # | details | patient | age | allergies | 79 # | button | selector | | | 80 # `--------------------------------------' 81 self.szr_top_row = wx.BoxSizer(wx.HORIZONTAL) 82 83 # - details button 84 # fname = os.path.join(self.__gb['gnumed_dir'], 'bitmaps', 'binoculars_form.png') 85 # img = wxImage(fname, wx.BITMAP_TYPE_ANY) 86 # bmp = wx.BitmapFromImage(img) 87 # self.btn_pat_demographics = wx.BitmapButton ( 88 # parent = self, 89 # id = ID_BTN_pat_demographics, 90 # bitmap = bmp, 91 # style = wx.BU_EXACTFIT | wxNO_BORDER 92 # ) 93 # self.btn_pat_demographics.SetToolTip(wxToolTip(_("display patient demographics"))) 94 # self.szr_top_row.Add (self.btn_pat_demographics, 0, wxEXPAND | wx.BOTTOM, 3) 95 96 # padlock button - Dare I say HIPAA ? 97 # fname = os.path.join(self.__gb['gnumed_dir'], 'bitmaps', 'padlock_closed.png') 98 # img = wxImage(fname, wx.BITMAP_TYPE_ANY) 99 # bmp = wx.BitmapFromImage(img) 100 # self.btn_lock = wx.BitmapButton ( 101 # parent = self, 102 # id = ID_LOCKBUTTON, 103 # bitmap = bmp, 104 # style = wx.BU_EXACTFIT | wxNO_BORDER 105 # ) 106 # self.btn_lock.SetToolTip(wxToolTip(_('lock client'))) 107 # self.szr_top_row.Add(self.btn_lock, 0, wxALL, 3) 108 109 # - patient selector 110 self.patient_selector = gmPatSearchWidgets.cActivePatientSelector(self, -1) 111 cfg = gmCfg2.gmCfgData() 112 if cfg.get(option = 'slave'): 113 self.patient_selector.SetEditable(0) 114 self.patient_selector.SetToolTip(None) 115 self.patient_selector.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD, False, '')) 116 117 # - age 118 self.lbl_age = wx.StaticText(self, -1, u'', style = wx.ALIGN_CENTER_VERTICAL) 119 self.lbl_age.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD, False, '')) 120 121 # - allergies (substances only, like "makrolides, penicillins, eggs") 122 self.lbl_allergies = wx.StaticText (self, -1, _('Caveat'), style = wx.ALIGN_CENTER_VERTICAL) 123 self.lbl_allergies.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD, False, '')) 124 self.lbl_allergies.SetBackgroundColour(bg_col) 125 self.lbl_allergies.SetForegroundColour(col_brightred) 126 self.txt_allergies = wx.TextCtrl (self, -1, "", style = wx.TE_READONLY) 127 self.txt_allergies.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD, False, '')) 128 self.txt_allergies.SetForegroundColour (col_brightred) 129 130 self.szr_top_row.Add(self.patient_selector, 6, wx.LEFT | wx.BOTTOM, 3) 131 self.szr_top_row.Add(self.lbl_age, 0, wx.ALL, 3) 132 self.szr_top_row.Add(self.lbl_allergies, 0, wx.ALL, 3) 133 self.szr_top_row.Add(self.txt_allergies, 8, wx.BOTTOM, 3) 134 135 # - bottom row 136 # .----------------------------------------------------------. 137 # | plugin toolbar | bmi | edc | | encounter | lock | 138 # | | | | | type sel | | 139 # `----------------------------------------------------------' 140 #self.tb_lock.AddControl(wx.StaticBitmap(self.tb_lock, -1, getvertical_separator_thinBitmap(), wx.DefaultPosition, wx.DefaultSize)) 141 142 # (holds most of the buttons) 143 self.szr_bottom_row = wx.BoxSizer(wx.HORIZONTAL) 144 self._PNL_tags = gmDemographicsWidgets.cImageTagPresenterPnl(self, -1) 145 self.szr_bottom_row.Add(self._PNL_tags, 0, wx.GROW, 0) 146 147 # self.pnl_bottom_row = wx.Panel(self, -1) 148 # self.szr_bottom_row.Add(self.pnl_bottom_row, 6, wx.GROW, 0) 149 150 # BMI calculator button 151 # fname = os.path.join(self.__gb['gnumed_dir'], 'bitmaps', 'bmi_calculator.png') 152 # img = wx.Image(fname, wx.BITMAP_TYPE_ANY) 153 # bmp = wx.BitmapFromImage(img) 154 # self.btn_bmi = wx.BitmapButton ( 155 # parent = self, 156 # id = ID_BMITOOL, 157 # bitmap = bmp, 158 # style = wx.BU_EXACTFIT | wx.NO_BORDER 159 # ) 160 # self.btn_bmi.SetToolTip(wx.ToolTip(_("BMI Calculator"))) 161 # self.szr_bottom_row.Add(self.btn_bmi, 0) 162 163 # tb = wxToolBar(self, -1, style=wx.TB_HORIZONTAL | wxNO_BORDER | wx.TB_FLAT) 164 # tb.AddTool ( 165 # ID_BMITOOL, 166 # gmImgTools.xpm2bmp(bmicalculator.get_xpm()), 167 # shortHelpString = _("BMI Calculator") 168 # ) 169 # self.szr_bottom_row.Add(tb, 0, wxRIGHT, 0) 170 171 # pregnancy calculator button 172 # fname = os.path.join(self.__gb['gnumed_dir'], 'bitmaps', 'preg_calculator.png') 173 # img = wxImage(fname, wx.BITMAP_TYPE_ANY) 174 # bmp = wx.BitmapFromImage(img) 175 # self.btn_preg = wx.BitmapButton ( 176 # parent = self, 177 # id = ID_PREGTOOL, 178 # bitmap = bmp, 179 # style = wx.BU_EXACTFIT | wxNO_BORDER 180 # ) 181 # self.btn_preg.SetToolTip(wxToolTip(_("Pregnancy Calculator"))) 182 # self.szr_bottom_row.Add(self.btn_preg, 0) 183 184 # - stack them atop each other 185 self.szr_stacked_rows = wx.BoxSizer(wx.VERTICAL) 186 # ??? (IMHO: space is at too much of a premium for such padding) 187 # FIXME: deuglify 188 try: 189 self.szr_stacked_rows.Add(1, 1, 0) 190 except: 191 self.szr_stacked_rows.Add((1, 1), 0) 192 193 # 0 here indicates the sizer cannot change its heights - which is intended 194 self.szr_stacked_rows.Add(self.szr_top_row, 0, wx.EXPAND) 195 self.szr_stacked_rows.Add(self.szr_bottom_row, 1, wx.EXPAND|wx.TOP, 5) 196 197 # create patient picture 198 self.patient_picture = gmPatPicWidgets.cPatientPicture(self, -1) 199 # tt = wx.ToolTip(_('Patient picture.\nRight-click for context menu.')) 200 # self.patient_picture.SetToolTip(tt) 201 202 # create main sizer 203 self.szr_main = wx.BoxSizer(wx.HORIZONTAL) 204 # - insert patient picture 205 self.szr_main.Add(self.patient_picture, 0, wx.LEFT | wx.TOP | wx.Right, 5) 206 # - insert stacked rows 207 self.szr_main.Add(self.szr_stacked_rows, 1) 208 209 # associate ourselves with our main sizer 210 self.SetSizer(self.szr_main) 211 # and auto-size to minimum calculated size 212 self.szr_main.Fit(self)
213 #------------------------------------------------------- 214 # internal helpers 215 #------------------------------------------------------- 216 #------------------------------------------------------- 217 # event handling 218 #-------------------------------------------------------
219 - def __register_interests(self):
220 # events 221 wx.EVT_BUTTON(self, ID_BTN_pat_demographics, self.__on_display_demographics) 222 223 # tools_menu = self.__gb['main.toolsmenu'] 224 225 # - BMI calculator 226 # wx.EVT_BUTTON(self, ID_BMITOOL, self._on_show_BMI) 227 # tools_menu.Append(ID_BMIMENU, _("BMI"), _("Body Mass Index Calculator")) 228 # wx.EVT_MENU(main_frame, ID_BMIMENU, self._on_show_BMI) 229 230 # - pregnancy calculator 231 # wx.EVT_BUTTON(self, ID_PREGTOOL, self._on_show_Preg_Calc) 232 # tools_menu.Append(ID_PREGMENU, _("EDC"), _("Pregnancy Calculator")) 233 # wx.EVT_MENU(main_frame, ID_PREGMENU, self._on_show_Preg_Calc) 234 235 # - lock button 236 # wx.EVT_BUTTON(self, ID_LOCKBUTTON, self._on_lock) 237 # tools_menu.Append(ID_LOCKMENU, _("lock client"), _("locks client and hides data")) 238 # wx.EVT_MENU(main_frame, ID_LOCKMENU, self._on_lock) 239 240 wx.EVT_LEFT_DCLICK(self.txt_allergies, self._on_allergies_dclicked) 241 242 # client internal signals 243 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 244 gmDispatcher.connect(signal = u'allg_mod_db', receiver = self._update_allergies) 245 gmDispatcher.connect(signal = u'allg_state_mod_db', receiver = self._update_allergies) 246 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_name_identity_change) 247 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_name_identity_change) 248 gmDispatcher.connect(signal = u'identity_tag_mod_db', receiver = self._on_tag_change)
249 #---------------------------------------------- 250 # def _on_lock(self, evt): 251 # print "should be locking client now by obscuring data" 252 # print "and popping up a modal dialog box asking for a" 253 # print "password to reactivate" 254 #----------------------------------------------
255 - def _on_allergies_dclicked(self, evt):
256 pat = gmPerson.gmCurrentPatient() 257 if not pat.connected: 258 gmDispatcher.send('statustext', msg = _('Cannot activate Allergy Manager. No active patient.')) 259 return 260 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1) 261 dlg.ShowModal() 262 return
263 #---------------------------------------------- 264 # def _on_show_BMI(self, evt): 265 # FIXME: update patient ID ? 266 # bmi = gmBMIWidgets.BMI_Frame(self) 267 # bmi.Centre(wx.BOTH) 268 # bmi.Show(1) 269 #---------------------------------------------- 270 # def _on_show_Preg_Calc(self, evt): 271 # FIXME: update patient ID ? 272 # pc = gmPregWidgets.cPregCalcFrame(self) 273 # pc.Centre(wx.BOTH) 274 # pc.Show(1) 275 #----------------------------------------------
276 - def _on_tag_change(self):
277 wx.CallAfter(self.__update_tags)
278 #----------------------------------------------
279 - def _on_name_identity_change(self):
280 wx.CallAfter(self.__on_name_identity_change)
281 #----------------------------------------------
283 self.__update_age_label() 284 self.Layout()
285 #----------------------------------------------
286 - def _on_post_patient_selection(self, **kwargs):
287 # needed because GUI stuff can't be called from a thread (and that's 288 # where we are coming from via backend listener -> dispatcher) 289 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
290 #----------------------------------------------
291 - def __on_post_patient_selection(self, **kwargs):
292 self.__update_age_label() 293 self.__update_allergies() 294 self.__update_tags() 295 self.Layout()
296 #-------------------------------------------------------
297 - def __on_display_demographics(self, evt):
298 print "display patient demographic window now"
299 #-------------------------------------------------------
300 - def _update_allergies(self, **kwargs):
301 wx.CallAfter(self.__update_allergies)
302 #------------------------------------------------------- 303 # internal API 304 #-------------------------------------------------------
305 - def __update_tags(self):
306 self._PNL_tags.refresh(patient = self.curr_pat)
307 #-------------------------------------------------------
308 - def __update_age_label(self):
309 310 if self.curr_pat['deceased'] is None: 311 312 if self.curr_pat.get_formatted_dob(format = '%m-%d') == pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone).strftime('%m-%d'): 313 template = _('%s %s (%s today !)') 314 else: 315 template = u'%s %s (%s)' 316 317 # FIXME: if the age is below, say, 2 hours we should fire 318 # a timer here that updates the age in increments of 1 minute ... :-) 319 age = template % ( 320 gmPerson.map_gender2symbol[self.curr_pat['gender']], 321 self.curr_pat.get_formatted_dob(format = '%d %b %Y', encoding = gmI18N.get_encoding()), 322 self.curr_pat['medical_age'] 323 ) 324 325 # Easter Egg ;-) 326 if self.curr_pat['lastnames'] == u'Leibner': 327 if self.curr_pat['firstnames'] == u'Steffi': 328 if self.curr_pat['preferred'] == u'Wildfang': 329 age = u'%s %s' % (gmTools.u_black_heart, age) 330 331 else: 332 333 template = u'%s %s - %s (%s)' 334 age = template % ( 335 gmPerson.map_gender2symbol[self.curr_pat['gender']], 336 self.curr_pat.get_formatted_dob(format = '%d.%b %Y', encoding = gmI18N.get_encoding()), 337 self.curr_pat['deceased'].strftime('%d.%b %Y').decode(gmI18N.get_encoding()), 338 self.curr_pat['medical_age'] 339 ) 340 341 self.lbl_age.SetLabel(age)
342 #-------------------------------------------------------
343 - def __update_allergies(self, **kwargs):
344 345 emr = self.curr_pat.get_emr() 346 state = emr.allergy_state 347 348 # state in tooltip 349 if state['last_confirmed'] is None: 350 confirmed = _('never') 351 else: 352 confirmed = state['last_confirmed'].strftime('%Y %B %d').decode(gmI18N.get_encoding()) 353 tt = (state.state_string + (90 * u' '))[:90] + u'\n' 354 tt += _('last confirmed %s\n') % confirmed 355 tt += gmTools.coalesce(state['comment'], u'', _('Comment (%s): %%s') % state['modified_by']) 356 tt += u'\n' 357 358 # allergies 359 tmp = [] 360 for allergy in emr.get_allergies(): 361 # in field: "true" allergies only, not intolerances 362 if allergy['type'] == 'allergy': 363 tmp.append(allergy['descriptor'][:10].strip() + gmTools.u_ellipsis) 364 # in tooltip 365 if allergy['definite']: 366 certainty = _('definite') 367 else: 368 certainty = _('suspected') 369 reaction = gmTools.coalesce(allergy['reaction'], _('reaction not recorded')) 370 if len(reaction) > 50: 371 reaction = reaction[:50] + gmTools.u_ellipsis 372 tt += u'%s (%s, %s): %s\n' % ( 373 allergy['descriptor'], 374 allergy['l10n_type'], 375 certainty, 376 reaction 377 ) 378 379 if len(tmp) == 0: 380 tmp = state.state_symbol 381 else: 382 tmp = ','.join(tmp) 383 384 if state['last_confirmed'] is not None: 385 tmp += state['last_confirmed'].strftime(' (%x)') 386 387 self.txt_allergies.SetValue(tmp) 388 self.txt_allergies.SetToolTipString(tt)
389 #------------------------------------------------------- 390 # remote layout handling 391 #-------------------------------------------------------
392 - def AddWidgetRightBottom (self, widget):
393 """Insert a widget on the right-hand side of the bottom toolbar. 394 """ 395 self.szr_bottom_row.Add(widget, 0, wx.RIGHT, 0)
396 #-------------------------------------------------------
397 - def AddWidgetLeftBottom (self, widget):
398 """Insert a widget on the left-hand side of the bottom toolbar. 399 """ 400 self.szr_bottom_row.Prepend(widget, 0, wx.ALL, 0)
401 #------------------------------------------------------- 402 # def CreateBar(self): 403 # """Creates empty toolbar suited for adding to top panel.""" 404 # bar = wx.ToolBar ( 405 # self.pnl_bottom_row, 406 # -1, 407 # size = self.pnl_bottom_row.GetClientSize(), 408 # style = wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT 409 # ) 410 # return bar 411 #------------------------------------------------------- 412 # def AddBar(self, key=None, bar=None): 413 # """Creates and returns a new empty toolbar, referenced by key. 414 # 415 # Key should correspond to the notebook page number as defined 416 # by the notebook (see gmPlugin.py), so that gmGuiMain can 417 # display the toolbar with the notebook 418 # """ 419 # bar.SetToolBitmapSize((16,16)) 420 # self.subbars[key] = bar 421 # if len(self.subbars) == 1: 422 # bar.Show(1) 423 # self.__current = key 424 # else: 425 # bar.Hide() 426 # return True 427 #------------------------------------------------------- 428 # def ReFit (self): 429 # """Refits the toolbar after its been changed 430 # """ 431 # tw = 0 432 # th = 0 433 # # get maximum size for the toolbar 434 # for i in self.subbars.values (): 435 # ntw, nth = i.GetSizeTuple () 436 # if ntw > tw: 437 # tw = ntw 438 # if nth > th: 439 # th = nth 440 # #import pdb 441 # #pdb.set_trace () 442 # sz = wx.Size (tw, th) 443 # self.pnl_bottom_row.SetSize(sz) 444 # for i in self.subbars.values(): 445 # i.SetSize (sz) 446 # self.szr_main.Layout() 447 # self.szr_main.Fit(self) 448 #------------------------------------------------------- 449 # def ShowBar (self, key): 450 # """Displays the named toolbar. 451 # """ 452 # self.subbars[self.__current].Hide() 453 # try: 454 # self.subbars[key].Show(1) 455 # self.__current = key 456 # except KeyError: 457 # _log.exception("cannot show undefined toolbar [%s]" % key) 458 #------------------------------------------------------- 459 # def DeleteBar (self, key): 460 # """Removes a toolbar. 461 # """ 462 # try: 463 # self.subbars[key].Destroy() 464 # del self.subbars[key] 465 # # FIXME: ?? 466 # if self.__current == key and len(self.subbars): 467 # self.__current = self.subbars.keys()[0] 468 # self.subbars[self.__current].Show(1) 469 # except KeyError: 470 # _log.exception("cannot delete undefined toolbar [%s]" % key) 471 472 #=========================================================== 473 if __name__ == "__main__": 474 wx.InitAllImageHandlers() 475 app = wxPyWidgetTester(size = (400, 200)) 476 app.SetWidget(cMainTopPanel, -1) 477 app.MainLoop() 478 #=========================================================== 479