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

Source Code for Module Gnumed.wxpython.gmTopPanel

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