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

# Source Code for Module Gnumed.wxpython.gmTopPanel

      1  # GNUmed
      2
      3  #===========================================================
      4  __author__  = "R.Terry <rterry@gnumed.net>, I.Haywood <i.haywood@ugrad.unimelb.edu.au>, K.Hilbert <Karsten.Hilbert@gmx.net>"
      5  __license__ = "GPL v2 or later"
      6
      7
      8  import sys
      9  import os.path
     10  import datetime as pyDT
     11  import logging
     12  import decimal
     13
     14
     15  import wx
     16
     17
     18  from Gnumed.pycommon import gmGuiBroker
     19  from Gnumed.pycommon import gmDispatcher
     20  from Gnumed.pycommon import gmTools
     21  from Gnumed.pycommon import gmCfg
     22  from Gnumed.pycommon import gmCfg2
     23  from Gnumed.pycommon import gmDateTime
     24  from Gnumed.pycommon import gmI18N
     25
     26  from Gnumed.business import gmPerson
     27  from Gnumed.business import gmEMRStructItems
     28  from Gnumed.business import gmAllergy
     29  from Gnumed.business import gmLOINC
     30  from Gnumed.business import gmClinicalCalculator
     31  from Gnumed.business import gmPathLab
     32  from Gnumed.business import gmPraxis
     33
     34  from Gnumed.wxpython import gmGuiHelpers
     35  from Gnumed.wxpython import gmDemographicsWidgets
     36  from Gnumed.wxpython import gmAllergyWidgets
     37  from Gnumed.wxpython import gmPatSearchWidgets
     38  from Gnumed.wxpython import gmEMRStructWidgets
     39  from Gnumed.wxpython import gmPatPicWidgets
     40
     41
     42  _log = logging.getLogger('gm.ui')
     43
     44  #===========================================================
     45  from Gnumed.wxGladeWidgets import wxgTopPnl
     46
     47 -class cTopPnl(wxgTopPnl.wxgTopPnl):
     48
     49 -        def __init__(self, *args, **kwargs):
     50
     51                  wxgTopPnl.wxgTopPnl.__init__(self, *args, **kwargs)
     52
     53                  self.__gb = gmGuiBroker.GuiBroker()
     54
     55                  self.curr_pat = gmPerson.gmCurrentPatient()
     56
     57                  self.__init_ui()
     58                  self.__register_interests()
     59
     60          #-------------------------------------------------------
     61 -        def __init_ui(self):
     62                  cfg = gmCfg2.gmCfgData()
     63                  if cfg.get(option = 'slave'):
     64                          self._TCTRL_patient_selector.SetEditable(0)
     65                          self._TCTRL_patient_selector.SetToolTip(None)
     66
     67                  if sys.platform == 'darwin':
     68                          _log.debug('adjusting font size on Mac for top panel parts')
     69                          for ctrl in [self._TCTRL_patient_selector, self._LBL_age, self._LBL_allergies, self._TCTRL_allergies]:
     70                                  curr_font = ctrl.GetFont()
     71                                  mac_font = wx.Font(curr_font.GetNativeFontInfo())
     72                                  mac_font.SetPointSize(pointSize = int(curr_font.GetPointSize() / 0.8))
     73                                  ctrl.SetFont(mac_font)
     74
     75                  # get panel to use
     76                  dbcfg = gmCfg.cCfgSQL()
     77                  pk_panel = dbcfg.get2 (
     78                          option = 'horstspace.top_panel.lab_panel',
     79                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     80                          bias = 'user'
     81                  )
     82                  if pk_panel is None:
     83                          self.__lab_panel = None
     84                  else:
     85                          self.__lab_panel = gmPathLab.cTestPanel(aPK_obj = pk_panel)
     86
     87          #-------------------------------------------------------
     88 -        def __register_interests(self):
     89                  # events
     90                  self._TCTRL_allergies.Bind(wx.EVT_LEFT_DCLICK, self._on_allergies_dclicked)
     91
     92                  # client internal signals
     93                  gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
     94                  gmDispatcher.connect(signal = 'focus_patient_search', receiver = self._on_focus_patient_search)
     95
     96                  gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
     97
     98          #----------------------------------------------
     99          # event handling
    100          #----------------------------------------------
    101 -        def _on_allergies_dclicked(self, evt):
    102                  if not self.curr_pat.connected:
    103                          gmDispatcher.send('statustext', msg = _('Cannot activate Allergy Manager. No active patient.'))
    104                          return
    105                  dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1)
    106                  dlg.ShowModal()
    107                  return
    108
    109          #----------------------------------------------
    110 -        def _on_database_signal(self, **kwds):
    111
    112                  if kwds['table'] not in ['dem.identity', 'dem.names', 'dem.identity_tag', 'clin.allergy', 'clin.allergy_state', 'clin.test_result', 'clin.patient']:
    113                          return True
    114
    115                  if self.curr_pat.connected:
    116                          # signal is not about our patient: ignore signal
    117                          if kwds['pk_identity'] != self.curr_pat.ID:
    118                                  return True
    119
    120                  if kwds['table'] == 'dem.identity':
    121                          # we don't care about newly INSERTed or DELETEd patients
    122                          if kwds['operation'] != 'UPDATE':
    123                                  return True
    124                          self.__update_age_label()
    125                          return True
    126
    127                  if kwds['table'] == 'dem.names':
    128                          self.__update_age_label()
    129                          return True
    130
    131                  if kwds['table'] == 'dem.identity_tag':
    132                          self.__update_tags()
    133                          return True
    134
    135                  if kwds['table'] in ['clin.allergy', 'clin.allergy_state']:
    136                          self.__update_allergies()
    137                          return True
    138
    139                  if kwds['table'] in ['clin.test_result', 'clin.patient']:
    140                          self.__update_lab()
    141                          return True
    142
    143                  return True
    144
    145          #----------------------------------------------
    146 -        def _on_post_patient_selection(self, **kwargs):
    147                  wx.CallAfter(self.__on_post_patient_selection)
    148
    149          #-------------------------------------------------------
    150 -        def __on_post_patient_selection(self):
    151                  self.__update_age_label()
    152                  self.__update_allergies()
    153                  self.__update_tags()
    154                  self.__update_lab()
    155                  self.Layout()
    156
    157          #-------------------------------------------------------
    158 -        def _on_focus_patient_search(self, **kwargs):
    159                  wx.CallAfter(self._TCTRL_patient_selector.SetFocus)
    160
    161          #-------------------------------------------------------
    162          # internal API
    163          #-------------------------------------------------------
    164 -        def __update_tags(self):
    165                  self._PNL_tags.refresh(patient = self.curr_pat)
    166
    167          #-------------------------------------------------------
    168 -        def __update_lab(self):
    169
    170                  if not self.curr_pat.connected:
    171                          self._LBL_lab.SetLabel('')
    172                          return
    173
    174                  tests2show = []
    175
    176                  rr = self.curr_pat.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_rr_quantity, no_of_results = 1)
    177                  if rr is None:
    178                          tests2show.append(_('RR ?'))
    179                  else:
    180                          #tests2show.append(_(u'%s%s') % (rr['unified_val'], rr['val_unit']))
    181                          tests2show.append(rr['unified_val'])
    182
    183                  hr = self.curr_pat.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_heart_rate_quantity, no_of_results = 1)
    184                  if hr is not None:
    185                          tests2show.append('%s %s' % (hr['abbrev_tt'], hr['unified_val']))
    186
    187                  bmi = self.curr_pat.emr.bmi
    188                  if bmi.numeric_value is not None:
    189                          tests2show.append(_('BMI %s') % bmi.numeric_value.quantize(decimal.Decimal('1.')))
    190                  else:
    191                          weight = self.curr_pat.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_weight, no_of_results = 1)
    192                          if weight is None:
    193                                  tests2show.append(_('BMI ?'))
    194                          else:
    195                                  tests2show.append('%s%s' % (weight['unified_val'], weight['val_unit']))
    196
    197                  gfr_or_crea = self.curr_pat.emr.best_gfr_or_crea
    198                  if gfr_or_crea is None:
    199                          tests2show.append(_('GFR ?'))
    200                  else:
    201                          try:
    202                                  tests2show.append(_('GFR %s') % gfr_or_crea.numeric_value.quantize(decimal.Decimal('1.')))
    203                          except AttributeError:
    204                                  tests2show.append('%s %s' % (gfr_or_crea['abbrev_tt'], gfr_or_crea['unified_val']))
    205
    206                  edc = self.curr_pat.emr.EDC
    207                  if edc is not None:
    208                          if self.curr_pat.emr.EDC_is_fishy:
    209                                  tests2show.append(_('?EDC %s') % gmDateTime.pydt_strftime(edc, '%Y-%b-%d', accuracy = gmDateTime.acc_days))
    210                          else:
    211                                  tests2show.append(_('EDC %s') % gmDateTime.pydt_strftime(edc, '%Y-%b-%d', accuracy = gmDateTime.acc_days))
    212
    213                  inr = self.curr_pat.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_inr_quantity, no_of_results = 1)
    214                  if inr is not None:
    215                          tests2show.append('%s %s' % (inr['abbrev_tt'], inr['unified_val']))
    216
    217                  # include panel if configured, only show if exist
    218                  if self.__lab_panel is not None:
    219                          for result in self.__lab_panel.get_most_recent_results(pk_patient = self.curr_pat.ID, order_by = 'unified_abbrev', group_by_meta_type = True):
    220                                  tests2show.append('%s %s' % (result['abbrev_tt'], result['unified_val']))
    221
    222                  self._LBL_lab.SetLabel('; '.join(tests2show))
    223
    224          #-------------------------------------------------------
    225 -        def __update_age_label(self):
    226
    227                  # no patient
    228                  if not self.curr_pat.connected:
    229                          self._LBL_age.SetLabel(_('<Age>'))
    230                          self._LBL_age.SetToolTip(_('no patient selected'))
    231                          return
    232
    233                  # gender is always known
    234                  tt = _('Gender: %s (%s) - %s\n') % (
    235                          self.curr_pat.gender_symbol,
    236                          gmTools.coalesce(self.curr_pat['gender'], '?'),
    237                          self.curr_pat.gender_string
    238                  )
    239
    240                  # dob is not known
    241                  if self.curr_pat['dob'] is None:
    242                          age = '%s  %s' % (
    243                                  self.curr_pat.gender_symbol,
    244                                  self.curr_pat.get_formatted_dob()
    245                          )
    246                          self._LBL_age.SetLabel(age)
    247                          self._LBL_age.SetToolTip(tt)
    248                          return
    249
    250                  tt += _('Born: %s\n') % self.curr_pat.get_formatted_dob(format = '%d %b %Y')
    251
    252                  # patient is dead
    253                  if self.curr_pat['deceased'] is not None:
    254                          tt += _('Died: %s\n') % gmDateTime.pydt_strftime(self.curr_pat['deceased'], '%d %b %Y')
    255                          tt += _('At age: %s\n') % self.curr_pat['medical_age']
    256                          age = '%s  %s - %s (%s)' % (
    257                                  self.curr_pat.gender_symbol,
    258                                  self.curr_pat.get_formatted_dob(format = '%d %b %Y'),
    259                                  gmDateTime.pydt_strftime(self.curr_pat['deceased'], '%d %b %Y'),
    260                                  self.curr_pat['medical_age']
    261                          )
    262                          if self.curr_pat['dob_is_estimated']:
    263                                  tt += _(' (date of birth and age are estimated)\n')
    264                          self._LBL_age.SetLabel(age)
    265                          self._LBL_age.SetToolTip(tt)
    266                          return
    267
    268                  # patient alive
    269                  now = gmDateTime.pydt_now_here()
    270
    271                  # patient birthday ?
    272                  if self.curr_pat.get_formatted_dob(format = '%m-%d', honor_estimation = False) == now.strftime('%m-%d'):
    273                          template = _('%(sex)s  %(dob)s (%(age)s today !)')
    274                          tt += _("\nToday is the patient's birthday !\n\n")
    275                          tt += _('Age: %s\n') % self.curr_pat['medical_age']
    276                  else:
    277                          tt += _('Age: %s, birthday:\n') % self.curr_pat['medical_age']
    278                          if self.curr_pat.current_birthday_passed is True:
    279                                  template = '%(sex)s  %(dob)s%(l_arr)s (%(age)s)'
    280                                  tt += ' ' + _('last: %s ago (this year)') % gmDateTime.format_apparent_age_medically (
    281                                          age = gmDateTime.calculate_apparent_age(start = self.curr_pat.birthday_this_year, end = now)
    282                                  ) + '\n'
    283                                  tt += ' ' + _('next: in %s (next year)') % gmDateTime.format_apparent_age_medically (
    284                                          age = gmDateTime.calculate_apparent_age(start = now, end = self.curr_pat.birthday_next_year)
    285                                  ) + '\n'
    286                          elif self.curr_pat.current_birthday_passed is False:
    287                                  template = '%(sex)s  %(r_arr)s%(dob)s (%(age)s)'
    288                                  tt += ' ' + _('next: in %s (this year)') % gmDateTime.format_apparent_age_medically (
    289                                          age = gmDateTime.calculate_apparent_age(start = now, end = self.curr_pat.birthday_this_year)
    290                                  ) + '\n'
    291                                  tt += ' ' + _('last: %s ago (last year)') % gmDateTime.format_apparent_age_medically (
    292                                          age = gmDateTime.calculate_apparent_age(start = self.curr_pat.birthday_last_year, end = now)
    293                                  ) + '\n'
    294                          else:   # None, unknown
    295                                  template = '%(sex)s  %(dob)s (%(age)s)'
    296
    297                  # FIXME: if the age is below, say, 2 hours we should fire
    298                  # a timer here that updates the age in increments of 1 minute ... :-)
    299                  age = template % {
    300                          'sex': self.curr_pat.gender_symbol,
    301                          'dob': self.curr_pat.get_formatted_dob(format = '%d %b %Y'),
    302                          'age': self.curr_pat['medical_age'],
    303                          'r_arr': gmTools.u_arrow2right,
    304                          'l_arr': gmTools.u_left_arrow
    305                  }
    306
    307                  # Easter Egg ;-)
    308                  if self.curr_pat['lastnames'] == 'Leibner':
    309                          if self.curr_pat['firstnames'] == 'Steffi':
    310                                  if self.curr_pat['preferred'] == 'Wildfang':
    311                                          age = '%s %s' % (gmTools.u_black_heart, age)
    312
    313                  if self.curr_pat['dob_is_estimated']:
    314                          tt += _(' (date of birth and age are estimated)\n')
    315
    316                  self._LBL_age.SetLabel(age)
    317                  self._LBL_age.SetToolTip(tt)
    318
    319          #-------------------------------------------------------
    320 -        def __update_allergies(self, **kwargs):
    321
    322                  if not self.curr_pat.connected:
    323                          self._LBL_allergies.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
    324                          self._TCTRL_allergies.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
    325                          self._TCTRL_allergies.SetValue('')
    326                          self._TCTRL_allergies.SetToolTip('')
    327                          return
    328
    329                  show_red = True
    330
    331                  emr = self.curr_pat.emr
    332                  state = emr.allergy_state
    333
    334                  # state in tooltip
    335                  if state['last_confirmed'] is None:
    336                          confirmed = _('never')
    337                  else:
    338                          confirmed = gmDateTime.pydt_strftime(state['last_confirmed'], '%Y %b %d')
    339                  tt = (state.state_string + (90 * ' '))[:90] + '\n'
    340                  tt += _('last confirmed %s\n') % confirmed
    341                  tt += gmTools.coalesce(state['comment'], '', _('Comment (%s): %%s') % state['modified_by'])
    342                  tt += '\n'
    343
    344                  # allergies
    345                  display = []
    346                  for allergy in emr.get_allergies():
    347                          # in field: "true" allergies only, not intolerances
    348                          if allergy['type'] == 'allergy':
    349                                  display.append(allergy['descriptor'][:10].strip() + gmTools.u_ellipsis)
    350                          # in tooltip
    351                          if allergy['definite']:
    352                                  certainty = _('definite')
    353                          else:
    354                                  certainty = _('suspected')
    355                          reaction = gmTools.coalesce(allergy['reaction'], _('reaction not recorded'))
    356                          if len(reaction) > 50:
    357                                  reaction = reaction[:50] + gmTools.u_ellipsis
    358                          tt += '%s (%s, %s): %s\n' % (
    359                                  allergy['descriptor'],
    360                                  allergy['l10n_type'],
    361                                  certainty,
    362                                  reaction
    363                          )
    364
    365                  if len(display) == 0:
    366                          display = state.state_symbol
    367                          if display == gmTools.u_diameter:
    368                                  show_red = False
    369                  else:
    370                          display = ','.join(display)
    371
    372                  if state['last_confirmed'] is not None:
    373                          display += gmDateTime.pydt_strftime(state['last_confirmed'], ' (%Y %b)')
    374
    375                  if show_red:
    376                          self._LBL_allergies.SetForegroundColour('red')
    377                          self._TCTRL_allergies.SetForegroundColour('red')
    378                  else:
    379                          self._LBL_allergies.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
    380                          self._TCTRL_allergies.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
    381
    382                  self._TCTRL_allergies.SetValue(display)
    383                  self._TCTRL_allergies.SetToolTip(tt)
    384
    385  #===========================================================
    386  if __name__ == "__main__":
    387          app = wxPyWidgetTester(size = (400, 200))
    388          app.SetWidget(cMainTopPanel, -1)
    389          app.SetWidget(cTopPanel, -1)
    390          app.MainLoop()
    391  #===========================================================
    392