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

Source Code for Module Gnumed.wxpython.gmDemographicsWidgets

   1  """Widgets dealing with patient demographics.""" 
   2  #============================================================ 
   3  __version__ = "$Revision: 1.175 $" 
   4  __author__ = "R.Terry, SJ Tan, I Haywood, Carlos Moro <cfmoro1976@yahoo.es>" 
   5  __license__ = 'GPL v2 or later (details at http://www.gnu.org)' 
   6   
   7  # standard library 
   8  import sys 
   9  import sys 
  10  import codecs 
  11  import re as regex 
  12  import logging 
  13  import webbrowser 
  14  import os 
  15   
  16   
  17  import wx 
  18  import wx.wizard 
  19  import wx.lib.imagebrowser as wx_imagebrowser 
  20  import wx.lib.statbmp as wx_genstatbmp 
  21   
  22   
  23  # GNUmed specific 
  24  if __name__ == '__main__': 
  25          sys.path.insert(0, '../../') 
  26  from Gnumed.pycommon import gmDispatcher 
  27  from Gnumed.pycommon import gmI18N 
  28  from Gnumed.pycommon import gmMatchProvider 
  29  from Gnumed.pycommon import gmPG2 
  30  from Gnumed.pycommon import gmTools 
  31  from Gnumed.pycommon import gmCfg 
  32  from Gnumed.pycommon import gmDateTime 
  33  from Gnumed.pycommon import gmShellAPI 
  34   
  35  from Gnumed.business import gmDemographicRecord 
  36  from Gnumed.business import gmPersonSearch 
  37  from Gnumed.business import gmSurgery 
  38  from Gnumed.business import gmPerson 
  39   
  40  from Gnumed.wxpython import gmPhraseWheel 
  41  from Gnumed.wxpython import gmRegetMixin 
  42  from Gnumed.wxpython import gmAuthWidgets 
  43  from Gnumed.wxpython import gmPersonContactWidgets 
  44  from Gnumed.wxpython import gmEditArea 
  45  from Gnumed.wxpython import gmListWidgets 
  46  from Gnumed.wxpython import gmDateTimeInput 
  47  from Gnumed.wxpython import gmDataMiningWidgets 
  48  from Gnumed.wxpython import gmGuiHelpers 
  49   
  50   
  51  # constant defs 
  52  _log = logging.getLogger('gm.ui') 
  53   
  54   
  55  try: 
  56          _('dummy-no-need-to-translate-but-make-epydoc-happy') 
  57  except NameError: 
  58          _ = lambda x:x 
  59   
  60  #============================================================ 
  61  # image tags related widgets 
  62  #------------------------------------------------------------ 
63 -def edit_tag_image(parent=None, tag_image=None, single_entry=False):
64 if tag_image is not None: 65 if tag_image['is_in_use']: 66 gmGuiHelpers.gm_show_info ( 67 aTitle = _('Editing tag'), 68 aMessage = _( 69 'Cannot edit the image tag\n' 70 '\n' 71 ' "%s"\n' 72 '\n' 73 'because it is currently in use.\n' 74 ) % tag_image['l10n_description'] 75 ) 76 return False 77 78 ea = cTagImageEAPnl(parent = parent, id = -1) 79 ea.data = tag_image 80 ea.mode = gmTools.coalesce(tag_image, 'new', 'edit') 81 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry) 82 dlg.SetTitle(gmTools.coalesce(tag_image, _('Adding new tag'), _('Editing tag'))) 83 if dlg.ShowModal() == wx.ID_OK: 84 dlg.Destroy() 85 return True 86 dlg.Destroy() 87 return False
88 #------------------------------------------------------------
89 -def manage_tag_images(parent=None):
90 91 if parent is None: 92 parent = wx.GetApp().GetTopWindow() 93 #------------------------------------------------------------ 94 def go_to_openclipart_org(tag_image): 95 webbrowser.open ( 96 url = u'http://www.openclipart.org', 97 new = False, 98 autoraise = True 99 ) 100 webbrowser.open ( 101 url = u'http://www.google.com', 102 new = False, 103 autoraise = True 104 ) 105 return True
106 #------------------------------------------------------------ 107 def edit(tag_image=None): 108 return edit_tag_image(parent = parent, tag_image = tag_image, single_entry = (tag_image is not None)) 109 #------------------------------------------------------------ 110 def delete(tag): 111 if tag['is_in_use']: 112 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this tag. It is in use.'), beep = True) 113 return False 114 115 return gmDemographicRecord.delete_tag_image(tag_image = tag['pk_tag_image']) 116 #------------------------------------------------------------ 117 def refresh(lctrl): 118 tags = gmDemographicRecord.get_tag_images(order_by = u'l10n_description') 119 items = [ [ 120 t['l10n_description'], 121 gmTools.bool2subst(t['is_in_use'], u'X', u''), 122 u'%s' % t['size'], 123 t['pk_tag_image'] 124 ] for t in tags ] 125 lctrl.set_string_items(items) 126 lctrl.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE]) 127 lctrl.set_data(tags) 128 #------------------------------------------------------------ 129 msg = _('\nTags with images registered with GNUmed.\n') 130 131 tag = gmListWidgets.get_choices_from_list ( 132 parent = parent, 133 msg = msg, 134 caption = _('Showing tags with images.'), 135 columns = [_('Tag name'), _('In use'), _('Image size'), u'#'], 136 single_selection = True, 137 new_callback = edit, 138 edit_callback = edit, 139 delete_callback = delete, 140 refresh_callback = refresh, 141 left_extra_button = (_('WWW'), _('Go to www.openclipart.org for images.'), go_to_openclipart_org) 142 ) 143 144 return tag 145 #------------------------------------------------------------ 146 from Gnumed.wxGladeWidgets import wxgTagImageEAPnl 147
148 -class cTagImageEAPnl(wxgTagImageEAPnl.wxgTagImageEAPnl, gmEditArea.cGenericEditAreaMixin):
149
150 - def __init__(self, *args, **kwargs):
151 152 try: 153 data = kwargs['tag_image'] 154 del kwargs['tag_image'] 155 except KeyError: 156 data = None 157 158 wxgTagImageEAPnl.wxgTagImageEAPnl.__init__(self, *args, **kwargs) 159 gmEditArea.cGenericEditAreaMixin.__init__(self) 160 161 self.mode = 'new' 162 self.data = data 163 if data is not None: 164 self.mode = 'edit' 165 166 self.__selected_image_file = None
167 #---------------------------------------------------------------- 168 # generic Edit Area mixin API 169 #----------------------------------------------------------------
170 - def _valid_for_save(self):
171 172 valid = True 173 174 if self.mode == u'new': 175 if self.__selected_image_file is None: 176 valid = False 177 gmDispatcher.send(signal = 'statustext', msg = _('Must pick an image file for a new tag.'), beep = True) 178 self._BTN_pick_image.SetFocus() 179 180 if self.__selected_image_file is not None: 181 try: 182 open(self.__selected_image_file).close() 183 except StandardError: 184 valid = False 185 self.__selected_image_file = None 186 gmDispatcher.send(signal = 'statustext', msg = _('Cannot open the image file [%s].') % self.__selected_image_file, beep = True) 187 self._BTN_pick_image.SetFocus() 188 189 if self._TCTRL_description.GetValue().strip() == u'': 190 valid = False 191 self.display_tctrl_as_valid(self._TCTRL_description, False) 192 self._TCTRL_description.SetFocus() 193 else: 194 self.display_tctrl_as_valid(self._TCTRL_description, True) 195 196 return (valid is True)
197 #----------------------------------------------------------------
198 - def _save_as_new(self):
199 200 dbo_conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Creating tag with image')) 201 if dbo_conn is None: 202 return False 203 204 data = gmDemographicRecord.create_tag_image(description = self._TCTRL_description.GetValue().strip(), link_obj = dbo_conn) 205 dbo_conn.close() 206 207 data['filename'] = self._TCTRL_filename.GetValue().strip() 208 data.save() 209 data.update_image_from_file(filename = self.__selected_image_file) 210 211 # must be done very late or else the property access 212 # will refresh the display such that later field 213 # access will return empty values 214 self.data = data 215 return True
216 #----------------------------------------------------------------
217 - def _save_as_update(self):
218 219 # this is somewhat fake as it never actually uses the gm-dbo conn 220 # (although it does verify it) 221 dbo_conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Updating tag with image')) 222 if dbo_conn is None: 223 return False 224 dbo_conn.close() 225 226 self.data['description'] = self._TCTRL_description.GetValue().strip() 227 self.data['filename'] = self._TCTRL_filename.GetValue().strip() 228 self.data.save() 229 230 if self.__selected_image_file is not None: 231 open(self.__selected_image_file).close() 232 self.data.update_image_from_file(filename = self.__selected_image_file) 233 self.__selected_image_file = None 234 235 return True
236 #----------------------------------------------------------------
237 - def _refresh_as_new(self):
238 self._TCTRL_description.SetValue(u'') 239 self._TCTRL_filename.SetValue(u'') 240 self._BMP_image.SetBitmap(bitmap = wx.EmptyBitmap(100, 100)) 241 242 self.__selected_image_file = None 243 244 self._TCTRL_description.SetFocus()
245 #----------------------------------------------------------------
247 self._refresh_as_new()
248 #----------------------------------------------------------------
249 - def _refresh_from_existing(self):
250 self._TCTRL_description.SetValue(self.data['l10n_description']) 251 self._TCTRL_filename.SetValue(gmTools.coalesce(self.data['filename'], u'')) 252 fname = self.data.export_image2file() 253 if fname is None: 254 self._BMP_image.SetBitmap(bitmap = wx.EmptyBitmap(100, 100)) 255 else: 256 self._BMP_image.SetBitmap(bitmap = gmGuiHelpers.file2scaled_image(filename = fname, height = 100)) 257 258 self.__selected_image_file = None 259 260 self._TCTRL_description.SetFocus()
261 #---------------------------------------------------------------- 262 # event handlers 263 #----------------------------------------------------------------
264 - def _on_pick_image_button_pressed(self, event):
265 paths = gmTools.gmPaths() 266 img_dlg = wx_imagebrowser.ImageDialog(parent = self, set_dir = paths.home_dir) 267 img_dlg.Centre() 268 if img_dlg.ShowModal() != wx.ID_OK: 269 return 270 271 self.__selected_image_file = img_dlg.GetFile() 272 self._BMP_image.SetBitmap(bitmap = gmGuiHelpers.file2scaled_image(filename = self.__selected_image_file, height = 100)) 273 fdir, fname = os.path.split(self.__selected_image_file) 274 self._TCTRL_filename.SetValue(fname)
275 276 #============================================================ 277 from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl 278
279 -class cImageTagPresenterPnl(wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl):
280
281 - def __init__(self, *args, **kwargs):
282 wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl.__init__(self, *args, **kwargs) 283 self._SZR_bitmaps = self.GetSizer() 284 self.__bitmaps = [] 285 286 self.__context_popup = wx.Menu() 287 288 item = self.__context_popup.Append(-1, _('&Edit comment')) 289 self.Bind(wx.EVT_MENU, self.__edit_tag, item) 290 291 item = self.__context_popup.Append(-1, _('&Remove tag')) 292 self.Bind(wx.EVT_MENU, self.__remove_tag, item)
293 #-------------------------------------------------------- 294 # external API 295 #--------------------------------------------------------
296 - def refresh(self, patient):
297 298 self.clear() 299 300 for tag in patient.get_tags(order_by = u'l10n_description'): 301 fname = tag.export_image2file() 302 if fname is None: 303 _log.warning('cannot export image data of tag [%s]', tag['l10n_description']) 304 continue 305 img = gmGuiHelpers.file2scaled_image(filename = fname, height = 20) 306 bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER) 307 bmp.SetToolTipString(u'%s%s' % ( 308 tag['l10n_description'], 309 gmTools.coalesce(tag['comment'], u'', u'\n\n%s') 310 )) 311 bmp.tag = tag 312 bmp.Bind(wx.EVT_RIGHT_UP, self._on_bitmap_rightclicked) 313 # FIXME: add context menu for Delete/Clone/Add/Configure 314 self._SZR_bitmaps.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, 1) # | wx.EXPAND 315 self.__bitmaps.append(bmp) 316 317 self.GetParent().Layout()
318 #--------------------------------------------------------
319 - def clear(self):
320 for child_idx in range(len(self._SZR_bitmaps.GetChildren())): 321 self._SZR_bitmaps.Detach(child_idx) 322 for bmp in self.__bitmaps: 323 bmp.Destroy() 324 self.__bitmaps = []
325 #-------------------------------------------------------- 326 # internal helpers 327 #--------------------------------------------------------
328 - def __remove_tag(self, evt):
329 if self.__current_tag is None: 330 return 331 pat = gmPerson.gmCurrentPatient() 332 if not pat.connected: 333 return 334 pat.remove_tag(tag = self.__current_tag['pk_identity_tag'])
335 #--------------------------------------------------------
336 - def __edit_tag(self, evt):
337 if self.__current_tag is None: 338 return 339 340 msg = _('Edit the comment on tag [%s]') % self.__current_tag['l10n_description'] 341 comment = wx.GetTextFromUser ( 342 message = msg, 343 caption = _('Editing tag comment'), 344 default_value = gmTools.coalesce(self.__current_tag['comment'], u''), 345 parent = self 346 ) 347 348 if comment == u'': 349 return 350 351 if comment.strip() == self.__current_tag['comment']: 352 return 353 354 if comment == u' ': 355 self.__current_tag['comment'] = None 356 else: 357 self.__current_tag['comment'] = comment.strip() 358 359 self.__current_tag.save()
360 #-------------------------------------------------------- 361 # event handlers 362 #--------------------------------------------------------
363 - def _on_bitmap_rightclicked(self, evt):
364 self.__current_tag = evt.GetEventObject().tag 365 self.PopupMenu(self.__context_popup, pos = wx.DefaultPosition) 366 self.__current_tag = None
367 #============================================================ 368 #============================================================
369 -class cKOrganizerSchedulePnl(gmDataMiningWidgets.cPatientListingPnl):
370
371 - def __init__(self, *args, **kwargs):
372 373 kwargs['message'] = _("Today's KOrganizer appointments ...") 374 kwargs['button_defs'] = [ 375 {'label': _('Reload'), 'tooltip': _('Reload appointments from KOrganizer')}, 376 {'label': u''}, 377 {'label': u''}, 378 {'label': u''}, 379 {'label': u'KOrganizer', 'tooltip': _('Launch KOrganizer')} 380 ] 381 gmDataMiningWidgets.cPatientListingPnl.__init__(self, *args, **kwargs) 382 383 self.fname = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp', 'korganizer2gnumed.csv')) 384 self.reload_cmd = 'konsolekalendar --view --export-type csv --export-file %s' % self.fname
385 386 #--------------------------------------------------------
387 - def _on_BTN_1_pressed(self, event):
388 """Reload appointments from KOrganizer.""" 389 self.reload_appointments()
390 #--------------------------------------------------------
391 - def _on_BTN_5_pressed(self, event):
392 """Reload appointments from KOrganizer.""" 393 found, cmd = gmShellAPI.detect_external_binary(binary = 'korganizer') 394 395 if not found: 396 gmDispatcher.send(signal = 'statustext', msg = _('KOrganizer is not installed.'), beep = True) 397 return 398 399 gmShellAPI.run_command_in_shell(command = cmd, blocking = False)
400 #--------------------------------------------------------
401 - def reload_appointments(self):
402 try: os.remove(self.fname) 403 except OSError: pass 404 gmShellAPI.run_command_in_shell(command=self.reload_cmd, blocking=True) 405 try: 406 csv_file = codecs.open(self.fname , mode = 'rU', encoding = 'utf8', errors = 'replace') 407 except IOError: 408 gmDispatcher.send(signal = u'statustext', msg = _('Cannot access KOrganizer transfer file [%s]') % self.fname, beep = True) 409 return 410 411 csv_lines = gmTools.unicode_csv_reader ( 412 csv_file, 413 delimiter = ',' 414 ) 415 # start_date, start_time, end_date, end_time, title (patient), ort, comment, UID 416 self._LCTRL_items.set_columns ([ 417 _('Place'), 418 _('Start'), 419 u'', 420 u'', 421 _('Patient'), 422 _('Comment') 423 ]) 424 items = [] 425 data = [] 426 for line in csv_lines: 427 items.append([line[5], line[0], line[1], line[3], line[4], line[6]]) 428 data.append([line[4], line[7]]) 429 430 self._LCTRL_items.set_string_items(items = items) 431 self._LCTRL_items.set_column_widths() 432 self._LCTRL_items.set_data(data = data) 433 self._LCTRL_items.patient_key = 0
434 #-------------------------------------------------------- 435 # notebook plugins API 436 #--------------------------------------------------------
437 - def repopulate_ui(self):
438 self.reload_appointments()
439 #============================================================ 440 # occupation related widgets / functions 441 #============================================================
442 -def edit_occupation():
443 444 pat = gmPerson.gmCurrentPatient() 445 curr_jobs = pat.get_occupations() 446 if len(curr_jobs) > 0: 447 old_job = curr_jobs[0]['l10n_occupation'] 448 update = curr_jobs[0]['modified_when'].strftime('%m/%Y') 449 else: 450 old_job = u'' 451 update = u'' 452 453 msg = _( 454 'Please enter the primary occupation of the patient.\n' 455 '\n' 456 'Currently recorded:\n' 457 '\n' 458 ' %s (last updated %s)' 459 ) % (old_job, update) 460 461 new_job = wx.GetTextFromUser ( 462 message = msg, 463 caption = _('Editing primary occupation'), 464 default_value = old_job, 465 parent = None 466 ) 467 if new_job.strip() == u'': 468 return 469 470 for job in curr_jobs: 471 # unlink all but the new job 472 if job['l10n_occupation'] != new_job: 473 pat.unlink_occupation(occupation = job['l10n_occupation']) 474 # and link the new one 475 pat.link_occupation(occupation = new_job)
476 477 #------------------------------------------------------------
478 -class cOccupationPhraseWheel(gmPhraseWheel.cPhraseWheel):
479
480 - def __init__(self, *args, **kwargs):
481 query = u"SELECT distinct name, _(name) from dem.occupation where _(name) %(fragment_condition)s" 482 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 483 mp.setThresholds(1, 3, 5) 484 gmPhraseWheel.cPhraseWheel.__init__ ( 485 self, 486 *args, 487 **kwargs 488 ) 489 self.SetToolTipString(_("Type or select an occupation.")) 490 self.capitalisation_mode = gmTools.CAPS_FIRST 491 self.matcher = mp
492 493 #============================================================ 494 # identity widgets / functions 495 #============================================================
496 -def disable_identity(identity=None):
497 # ask user for assurance 498 go_ahead = gmGuiHelpers.gm_show_question ( 499 _('Are you sure you really, positively want\n' 500 'to disable the following person ?\n' 501 '\n' 502 ' %s %s %s\n' 503 ' born %s\n' 504 '\n' 505 '%s\n' 506 ) % ( 507 identity['firstnames'], 508 identity['lastnames'], 509 identity['gender'], 510 identity['dob'], 511 gmTools.bool2subst ( 512 identity.is_patient, 513 _('This patient DID receive care.'), 514 _('This person did NOT receive care.') 515 ) 516 ), 517 _('Disabling person') 518 ) 519 if not go_ahead: 520 return True 521 522 # get admin connection 523 conn = gmAuthWidgets.get_dbowner_connection ( 524 procedure = _('Disabling patient') 525 ) 526 # - user cancelled 527 if conn is False: 528 return True 529 # - error 530 if conn is None: 531 return False 532 533 # now disable patient 534 gmPG2.run_rw_queries(queries = [{'cmd': u"update dem.identity set deleted=True where pk=%s", 'args': [identity['pk_identity']]}]) 535 536 return True
537 538 #------------------------------------------------------------ 539 # phrasewheels 540 #------------------------------------------------------------
541 -class cLastnamePhraseWheel(gmPhraseWheel.cPhraseWheel):
542
543 - def __init__(self, *args, **kwargs):
544 query = u"SELECT distinct lastnames, lastnames from dem.names where lastnames %(fragment_condition)s order by lastnames limit 25" 545 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 546 mp.setThresholds(3, 5, 9) 547 gmPhraseWheel.cPhraseWheel.__init__ ( 548 self, 549 *args, 550 **kwargs 551 ) 552 self.SetToolTipString(_("Type or select a last name (family name/surname).")) 553 self.capitalisation_mode = gmTools.CAPS_NAMES 554 self.matcher = mp
555 #------------------------------------------------------------
556 -class cFirstnamePhraseWheel(gmPhraseWheel.cPhraseWheel):
557
558 - def __init__(self, *args, **kwargs):
559 query = u""" 560 (SELECT distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20) 561 union 562 (SELECT distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)""" 563 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 564 mp.setThresholds(3, 5, 9) 565 gmPhraseWheel.cPhraseWheel.__init__ ( 566 self, 567 *args, 568 **kwargs 569 ) 570 self.SetToolTipString(_("Type or select a first name (forename/Christian name/given name).")) 571 self.capitalisation_mode = gmTools.CAPS_NAMES 572 self.matcher = mp
573 #------------------------------------------------------------
574 -class cNicknamePhraseWheel(gmPhraseWheel.cPhraseWheel):
575
576 - def __init__(self, *args, **kwargs):
577 query = u""" 578 (SELECT distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20) 579 union 580 (SELECT distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20) 581 union 582 (SELECT distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)""" 583 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 584 mp.setThresholds(3, 5, 9) 585 gmPhraseWheel.cPhraseWheel.__init__ ( 586 self, 587 *args, 588 **kwargs 589 ) 590 self.SetToolTipString(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name).")) 591 # nicknames CAN start with lower case ! 592 #self.capitalisation_mode = gmTools.CAPS_NAMES 593 self.matcher = mp
594 #------------------------------------------------------------
595 -class cTitlePhraseWheel(gmPhraseWheel.cPhraseWheel):
596
597 - def __init__(self, *args, **kwargs):
598 query = u"SELECT distinct title, title from dem.identity where title %(fragment_condition)s" 599 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 600 mp.setThresholds(1, 3, 9) 601 gmPhraseWheel.cPhraseWheel.__init__ ( 602 self, 603 *args, 604 **kwargs 605 ) 606 self.SetToolTipString(_("Type or select a title. Note that the title applies to the person, not to a particular name !")) 607 self.matcher = mp
608 #------------------------------------------------------------
609 -class cGenderSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
610 """Let user select a gender.""" 611 612 _gender_map = None 613
614 - def __init__(self, *args, **kwargs):
615 616 if cGenderSelectionPhraseWheel._gender_map is None: 617 cmd = u""" 618 SELECT tag, l10n_label, sort_weight 619 from dem.v_gender_labels 620 order by sort_weight desc""" 621 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True) 622 cGenderSelectionPhraseWheel._gender_map = {} 623 for gender in rows: 624 cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = { 625 'data': gender[idx['tag']], 626 'field_label': gender[idx['l10n_label']], 627 'list_label': gender[idx['l10n_label']], 628 'weight': gender[idx['sort_weight']] 629 } 630 631 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = cGenderSelectionPhraseWheel._gender_map.values()) 632 mp.setThresholds(1, 1, 3) 633 634 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 635 self.selection_only = True 636 self.matcher = mp 637 self.picklist_delay = 50
638 #------------------------------------------------------------
639 -class cExternalIDTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
640
641 - def __init__(self, *args, **kwargs):
642 query = u""" 643 SELECT DISTINCT ON (list_label) 644 pk AS data, 645 name AS field_label, 646 name || coalesce(' (' || issuer || ')', '') as list_label 647 FROM dem.enum_ext_id_types 648 WHERE name %(fragment_condition)s 649 ORDER BY list_label 650 LIMIT 25 651 """ 652 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 653 mp.setThresholds(1, 3, 5) 654 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 655 self.SetToolTipString(_("Enter or select a type for the external ID.")) 656 self.matcher = mp
657 #--------------------------------------------------------
658 - def _get_data_tooltip(self):
659 if self.GetData() is None: 660 return None 661 return self._data.values()[0]['list_label']
662 #------------------------------------------------------------
663 -class cExternalIDIssuerPhraseWheel(gmPhraseWheel.cPhraseWheel):
664
665 - def __init__(self, *args, **kwargs):
666 query = u""" 667 SELECT distinct issuer, issuer 668 from dem.enum_ext_id_types 669 where issuer %(fragment_condition)s 670 order by issuer limit 25""" 671 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 672 mp.setThresholds(1, 3, 5) 673 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 674 self.SetToolTipString(_("Type or select an ID issuer.")) 675 self.capitalisation_mode = gmTools.CAPS_FIRST 676 self.matcher = mp
677 #------------------------------------------------------------ 678 # edit areas 679 #------------------------------------------------------------ 680 from Gnumed.wxGladeWidgets import wxgExternalIDEditAreaPnl 681
682 -class cExternalIDEditAreaPnl(wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
683 """An edit area for editing/creating external IDs. 684 685 Does NOT act on/listen to the current patient. 686 """
687 - def __init__(self, *args, **kwargs):
688 689 try: 690 data = kwargs['external_id'] 691 del kwargs['external_id'] 692 except: 693 data = None 694 695 wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl.__init__(self, *args, **kwargs) 696 gmEditArea.cGenericEditAreaMixin.__init__(self) 697 698 self.identity = None 699 700 self.mode = 'new' 701 self.data = data 702 if data is not None: 703 self.mode = 'edit' 704 705 self.__init_ui()
706 #--------------------------------------------------------
707 - def __init_ui(self):
708 self._PRW_type.add_callback_on_lose_focus(self._on_type_set)
709 #---------------------------------------------------------------- 710 # generic Edit Area mixin API 711 #----------------------------------------------------------------
712 - def _valid_for_save(self):
713 validity = True 714 715 # do not test .GetData() because adding external 716 # IDs will create types as necessary 717 #if self._PRW_type.GetData() is None: 718 if self._PRW_type.GetValue().strip() == u'': 719 validity = False 720 self._PRW_type.display_as_valid(False) 721 self._PRW_type.SetFocus() 722 else: 723 self._PRW_type.display_as_valid(True) 724 725 if self._TCTRL_value.GetValue().strip() == u'': 726 validity = False 727 self.display_tctrl_as_valid(tctrl = self._TCTRL_value, valid = False) 728 else: 729 self.display_tctrl_as_valid(tctrl = self._TCTRL_value, valid = True) 730 731 return validity
732 #----------------------------------------------------------------
733 - def _save_as_new(self):
734 data = {} 735 data['pk_type'] = None 736 data['name'] = self._PRW_type.GetValue().strip() 737 data['value'] = self._TCTRL_value.GetValue().strip() 738 data['issuer'] = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u'') 739 data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 740 741 self.identity.add_external_id ( 742 type_name = data['name'], 743 value = data['value'], 744 issuer = data['issuer'], 745 comment = data['comment'] 746 ) 747 748 self.data = data 749 return True
750 #----------------------------------------------------------------
751 - def _save_as_update(self):
752 self.data['name'] = self._PRW_type.GetValue().strip() 753 self.data['value'] = self._TCTRL_value.GetValue().strip() 754 self.data['issuer'] = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u'') 755 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 756 757 self.identity.update_external_id ( 758 pk_id = self.data['pk_id'], 759 type = self.data['name'], 760 value = self.data['value'], 761 issuer = self.data['issuer'], 762 comment = self.data['comment'] 763 ) 764 765 return True
766 #----------------------------------------------------------------
767 - def _refresh_as_new(self):
768 self._PRW_type.SetText(value = u'', data = None) 769 self._TCTRL_value.SetValue(u'') 770 self._PRW_issuer.SetText(value = u'', data = None) 771 self._TCTRL_comment.SetValue(u'')
772 #----------------------------------------------------------------
774 self._refresh_as_new() 775 self._PRW_issuer.SetText(self.data['issuer'])
776 #----------------------------------------------------------------
777 - def _refresh_from_existing(self):
778 self._PRW_type.SetText(value = self.data['name'], data = self.data['pk_type']) 779 self._TCTRL_value.SetValue(self.data['value']) 780 self._PRW_issuer.SetText(self.data['issuer']) 781 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
782 #---------------------------------------------------------------- 783 # internal helpers 784 #----------------------------------------------------------------
785 - def _on_type_set(self):
786 """Set the issuer according to the selected type. 787 788 Matches are fetched from existing records in backend. 789 """ 790 pk_curr_type = self._PRW_type.GetData() 791 if pk_curr_type is None: 792 return True 793 rows, idx = gmPG2.run_ro_queries(queries = [{ 794 'cmd': u"SELECT issuer from dem.enum_ext_id_types where pk = %s", 795 'args': [pk_curr_type] 796 }]) 797 if len(rows) == 0: 798 return True 799 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0]) 800 return True
801 802 #============================================================ 803 # identity widgets 804 #------------------------------------------------------------
805 -def _empty_dob_allowed():
806 allow_empty_dob = gmGuiHelpers.gm_show_question ( 807 _( 808 'Are you sure you want to leave this person\n' 809 'without a valid date of birth ?\n' 810 '\n' 811 'This can be useful for temporary staff members\n' 812 'but will provoke nag screens if this person\n' 813 'becomes a patient.\n' 814 ), 815 _('Validating date of birth') 816 ) 817 return allow_empty_dob
818 #------------------------------------------------------------
819 -def _validate_dob_field(dob_prw):
820 821 # valid timestamp ? 822 if dob_prw.is_valid_timestamp(allow_empty = False): # properly colors the field 823 dob = dob_prw.date 824 # but year also usable ? 825 if (dob.year > 1899) and (dob < gmDateTime.pydt_now_here()): 826 return True 827 828 if dob.year < 1900: 829 msg = _( 830 'DOB: %s\n' 831 '\n' 832 'While this is a valid point in time Python does\n' 833 'not know how to deal with it.\n' 834 '\n' 835 'We suggest using January 1st 1901 instead and adding\n' 836 'the true date of birth to the patient comment.\n' 837 '\n' 838 'Sorry for the inconvenience %s' 839 ) % (dob, gmTools.u_frowning_face) 840 else: 841 msg = _( 842 'DOB: %s\n' 843 '\n' 844 'Date of birth in the future !' 845 ) % dob 846 gmGuiHelpers.gm_show_error ( 847 msg, 848 _('Validating date of birth') 849 ) 850 dob_prw.display_as_valid(False) 851 dob_prw.SetFocus() 852 return False 853 854 # invalid timestamp but not empty 855 if dob_prw.GetValue().strip() != u'': 856 dob_prw.display_as_valid(False) 857 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of birth.')) 858 dob_prw.SetFocus() 859 return False 860 861 # empty DOB field 862 dob_prw.display_as_valid(False) 863 return True
864 #------------------------------------------------------------ 865 from Gnumed.wxGladeWidgets import wxgIdentityEAPnl 866
867 -class cIdentityEAPnl(wxgIdentityEAPnl.wxgIdentityEAPnl, gmEditArea.cGenericEditAreaMixin):
868 """An edit area for editing/creating title/gender/dob/dod etc.""" 869
870 - def __init__(self, *args, **kwargs):
871 872 try: 873 data = kwargs['identity'] 874 del kwargs['identity'] 875 except KeyError: 876 data = None 877 878 wxgIdentityEAPnl.wxgIdentityEAPnl.__init__(self, *args, **kwargs) 879 gmEditArea.cGenericEditAreaMixin.__init__(self) 880 881 self.mode = 'new' 882 self.data = data 883 if data is not None: 884 self.mode = 'edit'
885 886 # self.__init_ui() 887 #---------------------------------------------------------------- 888 # def __init_ui(self): 889 # # adjust phrasewheels etc 890 #---------------------------------------------------------------- 891 # generic Edit Area mixin API 892 #----------------------------------------------------------------
893 - def _valid_for_save(self):
894 895 has_error = False 896 897 if self._PRW_gender.GetData() is None: 898 self._PRW_gender.SetFocus() 899 has_error = True 900 901 if self.data is not None: 902 if not _validate_dob_field(self._PRW_dob): 903 has_error = True 904 905 if not self._PRW_dod.is_valid_timestamp(allow_empty = True): 906 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.')) 907 self._PRW_dod.SetFocus() 908 has_error = True 909 910 return (has_error is False)
911 #----------------------------------------------------------------
912 - def _save_as_new(self):
913 # not used yet 914 return False
915 #----------------------------------------------------------------
916 - def _save_as_update(self):
917 918 if self._PRW_dob.GetValue().strip() == u'': 919 if not _empty_dob_allowed(): 920 return False 921 self.data['dob'] = None 922 else: 923 self.data['dob'] = self._PRW_dob.GetData() 924 925 self.data['gender'] = self._PRW_gender.GetData() 926 self.data['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'') 927 self.data['deceased'] = self._PRW_dod.GetData() 928 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 929 930 self.data.save() 931 return True
932 #----------------------------------------------------------------
933 - def _refresh_as_new(self):
934 pass
935 #----------------------------------------------------------------
936 - def _refresh_from_existing(self):
937 938 self._LBL_info.SetLabel(u'ID: #%s' % ( 939 self.data.ID 940 # FIXME: add 'deleted' status 941 )) 942 if self.data['dob'] is None: 943 val = u'' 944 else: 945 val = gmDateTime.pydt_strftime ( 946 self.data['dob'], 947 format = '%Y-%m-%d %H:%M', 948 accuracy = gmDateTime.acc_minutes 949 ) 950 self._PRW_dob.SetText(value = val, data = self.data['dob']) 951 if self.data['deceased'] is None: 952 val = u'' 953 else: 954 val = gmDateTime.pydt_strftime ( 955 self.data['deceased'], 956 format = '%Y-%m-%d %H:%M', 957 accuracy = gmDateTime.acc_minutes 958 ) 959 self._PRW_dod.SetText(value = val, data = self.data['deceased']) 960 self._PRW_gender.SetData(self.data['gender']) 961 #self._PRW_ethnicity.SetValue() 962 self._PRW_title.SetText(gmTools.coalesce(self.data['title'], u'')) 963 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
964 #----------------------------------------------------------------
966 pass
967 #------------------------------------------------------------ 968 from Gnumed.wxGladeWidgets import wxgPersonNameEAPnl 969
970 -class cPersonNameEAPnl(wxgPersonNameEAPnl.wxgPersonNameEAPnl, gmEditArea.cGenericEditAreaMixin):
971 """An edit area for editing/creating names of people. 972 973 Does NOT act on/listen to the current patient. 974 """
975 - def __init__(self, *args, **kwargs):
976 977 try: 978 data = kwargs['name'] 979 identity = gmPerson.cIdentity(aPK_obj = data['pk_identity']) 980 del kwargs['name'] 981 except KeyError: 982 data = None 983 identity = kwargs['identity'] 984 del kwargs['identity'] 985 986 wxgPersonNameEAPnl.wxgPersonNameEAPnl.__init__(self, *args, **kwargs) 987 gmEditArea.cGenericEditAreaMixin.__init__(self) 988 989 self.__identity = identity 990 991 self.mode = 'new' 992 self.data = data 993 if data is not None: 994 self.mode = 'edit'
995 996 #self.__init_ui() 997 #---------------------------------------------------------------- 998 # def __init_ui(self): 999 # # adjust phrasewheels etc 1000 #---------------------------------------------------------------- 1001 # generic Edit Area mixin API 1002 #----------------------------------------------------------------
1003 - def _valid_for_save(self):
1004 validity = True 1005 1006 if self._PRW_lastname.GetValue().strip() == u'': 1007 validity = False 1008 self._PRW_lastname.display_as_valid(False) 1009 self._PRW_lastname.SetFocus() 1010 else: 1011 self._PRW_lastname.display_as_valid(True) 1012 1013 if self._PRW_firstname.GetValue().strip() == u'': 1014 validity = False 1015 self._PRW_firstname.display_as_valid(False) 1016 self._PRW_firstname.SetFocus() 1017 else: 1018 self._PRW_firstname.display_as_valid(True) 1019 1020 return validity
1021 #----------------------------------------------------------------
1022 - def _save_as_new(self):
1023 1024 first = self._PRW_firstname.GetValue().strip() 1025 last = self._PRW_lastname.GetValue().strip() 1026 active = self._CHBOX_active.GetValue() 1027 1028 data = self.__identity.add_name(first, last, active) 1029 1030 old_nick = self.data['preferred'] 1031 new_nick = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'') 1032 if active: 1033 data['preferred'] = gmTools.coalesce(new_nick, old_nick) 1034 else: 1035 data['preferred'] = new_nick 1036 data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1037 data.save() 1038 1039 self.data = data 1040 return True
1041 #----------------------------------------------------------------
1042 - def _save_as_update(self):
1043 """The knack here is that we can only update a few fields. 1044 1045 Otherwise we need to clone the name and update that. 1046 """ 1047 first = self._PRW_firstname.GetValue().strip() 1048 last = self._PRW_lastname.GetValue().strip() 1049 active = self._CHBOX_active.GetValue() 1050 1051 current_name = self.data['firstnames'].strip() + self.data['lastnames'].strip() 1052 new_name = first + last 1053 1054 # editable fields only ? 1055 if new_name == current_name: 1056 self.data['active_name'] = self._CHBOX_active.GetValue() 1057 self.data['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'') 1058 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1059 self.data.save() 1060 # else clone name and update that 1061 else: 1062 name = self.__identity.add_name(first, last, active) 1063 name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'') 1064 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1065 name.save() 1066 self.data = name 1067 1068 return True
1069 #----------------------------------------------------------------
1070 - def _refresh_as_new(self):
1071 self._PRW_firstname.SetText(value = u'', data = None) 1072 self._PRW_lastname.SetText(value = u'', data = None) 1073 self._PRW_nick.SetText(value = u'', data = None) 1074 self._TCTRL_comment.SetValue(u'') 1075 self._CHBOX_active.SetValue(False) 1076 1077 self._PRW_firstname.SetFocus()
1078 #----------------------------------------------------------------
1080 self._refresh_as_new() 1081 self._PRW_firstname.SetText(value = u'', data = None) 1082 self._PRW_nick.SetText(gmTools.coalesce(self.data['preferred'], u'')) 1083 1084 self._PRW_lastname.SetFocus()
1085 #----------------------------------------------------------------
1086 - def _refresh_from_existing(self):
1087 self._PRW_firstname.SetText(self.data['firstnames']) 1088 self._PRW_lastname.SetText(self.data['lastnames']) 1089 self._PRW_nick.SetText(gmTools.coalesce(self.data['preferred'], u'')) 1090 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u'')) 1091 self._CHBOX_active.SetValue(self.data['active_name']) 1092 1093 self._TCTRL_comment.SetFocus()
1094 #------------------------------------------------------------ 1095 # list manager 1096 #------------------------------------------------------------
1097 -class cPersonNamesManagerPnl(gmListWidgets.cGenericListManagerPnl):
1098 """A list for managing a person's names. 1099 1100 Does NOT act on/listen to the current patient. 1101 """
1102 - def __init__(self, *args, **kwargs):
1103 1104 try: 1105 self.__identity = kwargs['identity'] 1106 del kwargs['identity'] 1107 except KeyError: 1108 self.__identity = None 1109 1110 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1111 1112 self.new_callback = self._add_name 1113 self.edit_callback = self._edit_name 1114 self.delete_callback = self._del_name 1115 self.refresh_callback = self.refresh 1116 1117 self.__init_ui() 1118 self.refresh()
1119 #-------------------------------------------------------- 1120 # external API 1121 #--------------------------------------------------------
1122 - def refresh(self, *args, **kwargs):
1123 if self.__identity is None: 1124 self._LCTRL_items.set_string_items() 1125 return 1126 1127 names = self.__identity.get_names() 1128 self._LCTRL_items.set_string_items ( 1129 items = [ [ 1130 gmTools.bool2str(n['active_name'], 'X', ''), 1131 n['lastnames'], 1132 n['firstnames'], 1133 gmTools.coalesce(n['preferred'], u''), 1134 gmTools.coalesce(n['comment'], u'') 1135 ] for n in names ] 1136 ) 1137 self._LCTRL_items.set_column_widths() 1138 self._LCTRL_items.set_data(data = names)
1139 #-------------------------------------------------------- 1140 # internal helpers 1141 #--------------------------------------------------------
1142 - def __init_ui(self):
1143 self._LCTRL_items.set_columns(columns = [ 1144 _('Active'), 1145 _('Lastname'), 1146 _('Firstname(s)'), 1147 _('Preferred Name'), 1148 _('Comment') 1149 ]) 1150 self._BTN_edit.SetLabel(_('Clone and &edit'))
1151 #--------------------------------------------------------
1152 - def _add_name(self):
1153 #ea = cPersonNameEAPnl(self, -1, name = self.__identity.get_active_name()) 1154 ea = cPersonNameEAPnl(self, -1, identity = self.__identity) 1155 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1156 dlg.SetTitle(_('Adding new name')) 1157 if dlg.ShowModal() == wx.ID_OK: 1158 dlg.Destroy() 1159 return True 1160 dlg.Destroy() 1161 return False
1162 #--------------------------------------------------------
1163 - def _edit_name(self, name):
1164 ea = cPersonNameEAPnl(self, -1, name = name) 1165 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1166 dlg.SetTitle(_('Cloning name')) 1167 if dlg.ShowModal() == wx.ID_OK: 1168 dlg.Destroy() 1169 return True 1170 dlg.Destroy() 1171 return False
1172 #--------------------------------------------------------
1173 - def _del_name(self, name):
1174 1175 if len(self.__identity.get_names()) == 1: 1176 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True) 1177 return False 1178 1179 go_ahead = gmGuiHelpers.gm_show_question ( 1180 _( 'It is often advisable to keep old names around and\n' 1181 'just create a new "currently active" name.\n' 1182 '\n' 1183 'This allows finding the patient by both the old\n' 1184 'and the new name (think before/after marriage).\n' 1185 '\n' 1186 'Do you still want to really delete\n' 1187 "this name from the patient ?" 1188 ), 1189 _('Deleting name') 1190 ) 1191 if not go_ahead: 1192 return False 1193 1194 self.__identity.delete_name(name = name) 1195 return True
1196 #-------------------------------------------------------- 1197 # properties 1198 #--------------------------------------------------------
1199 - def _get_identity(self):
1200 return self.__identity
1201
1202 - def _set_identity(self, identity):
1203 self.__identity = identity 1204 self.refresh()
1205 1206 identity = property(_get_identity, _set_identity)
1207 #------------------------------------------------------------
1208 -class cPersonIDsManagerPnl(gmListWidgets.cGenericListManagerPnl):
1209 """A list for managing a person's external IDs. 1210 1211 Does NOT act on/listen to the current patient. 1212 """
1213 - def __init__(self, *args, **kwargs):
1214 1215 try: 1216 self.__identity = kwargs['identity'] 1217 del kwargs['identity'] 1218 except KeyError: 1219 self.__identity = None 1220 1221 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1222 1223 self.new_callback = self._add_id 1224 self.edit_callback = self._edit_id 1225 self.delete_callback = self._del_id 1226 self.refresh_callback = self.refresh 1227 1228 self.__init_ui() 1229 self.refresh()
1230 #-------------------------------------------------------- 1231 # external API 1232 #--------------------------------------------------------
1233 - def refresh(self, *args, **kwargs):
1234 if self.__identity is None: 1235 self._LCTRL_items.set_string_items() 1236 return 1237 1238 ids = self.__identity.get_external_ids() 1239 self._LCTRL_items.set_string_items ( 1240 items = [ [ 1241 i['name'], 1242 i['value'], 1243 gmTools.coalesce(i['issuer'], u''), 1244 gmTools.coalesce(i['comment'], u'') 1245 ] for i in ids 1246 ] 1247 ) 1248 self._LCTRL_items.set_column_widths() 1249 self._LCTRL_items.set_data(data = ids)
1250 #-------------------------------------------------------- 1251 # internal helpers 1252 #--------------------------------------------------------
1253 - def __init_ui(self):
1254 self._LCTRL_items.set_columns(columns = [ 1255 _('ID type'), 1256 _('Value'), 1257 _('Issuer'), 1258 _('Comment') 1259 ])
1260 #--------------------------------------------------------
1261 - def _add_id(self):
1262 ea = cExternalIDEditAreaPnl(self, -1) 1263 ea.identity = self.__identity 1264 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea) 1265 dlg.SetTitle(_('Adding new external ID')) 1266 if dlg.ShowModal() == wx.ID_OK: 1267 dlg.Destroy() 1268 return True 1269 dlg.Destroy() 1270 return False
1271 #--------------------------------------------------------
1272 - def _edit_id(self, ext_id):
1273 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id) 1274 ea.identity = self.__identity 1275 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1276 dlg.SetTitle(_('Editing external ID')) 1277 if dlg.ShowModal() == wx.ID_OK: 1278 dlg.Destroy() 1279 return True 1280 dlg.Destroy() 1281 return False
1282 #--------------------------------------------------------
1283 - def _del_id(self, ext_id):
1284 go_ahead = gmGuiHelpers.gm_show_question ( 1285 _( 'Do you really want to delete this\n' 1286 'external ID from the patient ?'), 1287 _('Deleting external ID') 1288 ) 1289 if not go_ahead: 1290 return False 1291 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id']) 1292 return True
1293 #-------------------------------------------------------- 1294 # properties 1295 #--------------------------------------------------------
1296 - def _get_identity(self):
1297 return self.__identity
1298
1299 - def _set_identity(self, identity):
1300 self.__identity = identity 1301 self.refresh()
1302 1303 identity = property(_get_identity, _set_identity)
1304 #------------------------------------------------------------ 1305 # integrated panels 1306 #------------------------------------------------------------ 1307 from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl 1308
1309 -class cPersonIdentityManagerPnl(wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl):
1310 """A panel for editing identity data for a person. 1311 1312 - provides access to: 1313 - identity EA 1314 - name list manager 1315 - external IDs list manager 1316 1317 Does NOT act on/listen to the current patient. 1318 """
1319 - def __init__(self, *args, **kwargs):
1320 1321 wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl.__init__(self, *args, **kwargs) 1322 1323 self.__identity = None 1324 self.refresh()
1325 #-------------------------------------------------------- 1326 # external API 1327 #--------------------------------------------------------
1328 - def refresh(self):
1329 self._PNL_names.identity = self.__identity 1330 self._PNL_ids.identity = self.__identity 1331 # this is an Edit Area: 1332 self._PNL_identity.mode = 'new' 1333 self._PNL_identity.data = self.__identity 1334 if self.__identity is not None: 1335 self._PNL_identity.mode = 'edit' 1336 self._PNL_identity._refresh_from_existing()
1337 #-------------------------------------------------------- 1338 # properties 1339 #--------------------------------------------------------
1340 - def _get_identity(self):
1341 return self.__identity
1342
1343 - def _set_identity(self, identity):
1344 self.__identity = identity 1345 self.refresh()
1346 1347 identity = property(_get_identity, _set_identity) 1348 #-------------------------------------------------------- 1349 # event handlers 1350 #--------------------------------------------------------
1352 if not self._PNL_identity.save(): 1353 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save identity. Incomplete information.'), beep = True)
1354 #self._PNL_identity.refresh() 1355 #--------------------------------------------------------
1356 - def _on_reload_identity_button_pressed(self, event):
1357 self._PNL_identity.refresh()
1358 1359 #============================================================ 1360 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl 1361
1362 -class cPersonSocialNetworkManagerPnl(wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl):
1363 - def __init__(self, *args, **kwargs):
1364 1365 wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl.__init__(self, *args, **kwargs) 1366 1367 self.__identity = None 1368 self._PRW_provider.selection_only = False 1369 self.refresh()
1370 #-------------------------------------------------------- 1371 # external API 1372 #--------------------------------------------------------
1373 - def refresh(self):
1374 1375 tt = _('Link another person in this database as the emergency contact:\n\nEnter person name part or identifier and hit <enter>.') 1376 1377 if self.__identity is None: 1378 self._TCTRL_er_contact.SetValue(u'') 1379 self._TCTRL_person.person = None 1380 self._TCTRL_person.SetToolTipString(tt) 1381 1382 self._PRW_provider.SetText(value = u'', data = None) 1383 return 1384 1385 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], u'')) 1386 if self.__identity['pk_emergency_contact'] is not None: 1387 ident = gmPerson.cIdentity(aPK_obj = self.__identity['pk_emergency_contact']) 1388 self._TCTRL_person.person = ident 1389 tt = u'%s\n\n%s\n\n%s' % ( 1390 tt, 1391 ident['description_gender'], 1392 u'\n'.join([ 1393 u'%s: %s%s' % ( 1394 c['l10n_comm_type'], 1395 c['url'], 1396 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), u'', u'') 1397 ) 1398 for c in ident.get_comm_channels() 1399 ]) 1400 ) 1401 else: 1402 self._TCTRL_person.person = None 1403 1404 self._TCTRL_person.SetToolTipString(tt) 1405 1406 if self.__identity['pk_primary_provider'] is None: 1407 self._PRW_provider.SetText(value = u'', data = None) 1408 else: 1409 self._PRW_provider.SetData(data = self.__identity['pk_primary_provider'])
1410 #-------------------------------------------------------- 1411 # properties 1412 #--------------------------------------------------------
1413 - def _get_identity(self):
1414 return self.__identity
1415
1416 - def _set_identity(self, identity):
1417 self.__identity = identity 1418 self.refresh()
1419 1420 identity = property(_get_identity, _set_identity) 1421 #-------------------------------------------------------- 1422 # event handlers 1423 #--------------------------------------------------------
1424 - def _on_save_button_pressed(self, event):
1425 if self.__identity is not None: 1426 self.__identity['emergency_contact'] = self._TCTRL_er_contact.GetValue().strip() 1427 if self._TCTRL_person.person is not None: 1428 self.__identity['pk_emergency_contact'] = self._TCTRL_person.person.ID 1429 if self._PRW_provider.GetValue().strip == u'': 1430 self.__identity['pk_primary_provider'] = None 1431 else: 1432 self.__identity['pk_primary_provider'] = self._PRW_provider.GetData() 1433 1434 self.__identity.save() 1435 gmDispatcher.send(signal = 'statustext', msg = _('Emergency data and primary provider saved.'), beep = False) 1436 1437 event.Skip()
1438 #--------------------------------------------------------
1439 - def _on_reload_button_pressed(self, event):
1440 self.refresh()
1441 #--------------------------------------------------------
1442 - def _on_remove_contact_button_pressed(self, event):
1443 event.Skip() 1444 1445 if self.__identity is None: 1446 return 1447 1448 self._TCTRL_person.person = None 1449 1450 self.__identity['pk_emergency_contact'] = None 1451 self.__identity.save()
1452 #--------------------------------------------------------
1453 - def _on_button_activate_contact_pressed(self, event):
1454 ident = self._TCTRL_person.person 1455 if ident is not None: 1456 from Gnumed.wxpython import gmPatSearchWidgets 1457 gmPatSearchWidgets.set_active_patient(patient = ident, forced_reload = False) 1458 1459 event.Skip()
1460 #============================================================ 1461 # new-patient widgets 1462 #============================================================
1463 -def create_new_person(parent=None, activate=False):
1464 1465 dbcfg = gmCfg.cCfgSQL() 1466 1467 def_region = dbcfg.get2 ( 1468 option = u'person.create.default_region', 1469 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1470 bias = u'user' 1471 ) 1472 def_country = None 1473 1474 if def_region is None: 1475 def_country = dbcfg.get2 ( 1476 option = u'person.create.default_country', 1477 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1478 bias = u'user' 1479 ) 1480 else: 1481 countries = gmDemographicRecord.get_country_for_region(region = def_region) 1482 if len(countries) == 1: 1483 def_country = countries[0]['code_country'] 1484 1485 if parent is None: 1486 parent = wx.GetApp().GetTopWindow() 1487 1488 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region) 1489 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 1490 dlg.SetTitle(_('Adding new person')) 1491 ea._PRW_lastname.SetFocus() 1492 result = dlg.ShowModal() 1493 pat = ea.data 1494 dlg.Destroy() 1495 1496 if result != wx.ID_OK: 1497 return False 1498 1499 _log.debug('created new person [%s]', pat.ID) 1500 1501 if activate: 1502 from Gnumed.wxpython import gmPatSearchWidgets 1503 gmPatSearchWidgets.set_active_patient(patient = pat) 1504 1505 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin') 1506 1507 return True
1508 #============================================================ 1509 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl 1510
1511 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
1512
1513 - def __init__(self, *args, **kwargs):
1514 1515 try: 1516 self.default_region = kwargs['region'] 1517 del kwargs['region'] 1518 except KeyError: 1519 self.default_region = None 1520 1521 try: 1522 self.default_country = kwargs['country'] 1523 del kwargs['country'] 1524 except KeyError: 1525 self.default_country = None 1526 1527 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs) 1528 gmEditArea.cGenericEditAreaMixin.__init__(self) 1529 1530 self.mode = 'new' 1531 self.data = None 1532 self._address = None 1533 1534 self.__init_ui() 1535 self.__register_interests()
1536 #---------------------------------------------------------------- 1537 # internal helpers 1538 #----------------------------------------------------------------
1539 - def __init_ui(self):
1540 self._PRW_lastname.final_regex = '.+' 1541 self._PRW_firstnames.final_regex = '.+' 1542 self._PRW_address_searcher.selection_only = False 1543 1544 # only if we would support None on selection_only's: 1545 # self._PRW_external_id_type.selection_only = True 1546 1547 if self.default_country is not None: 1548 match = self._PRW_country._data2match(data = self.default_country) 1549 if match is not None: 1550 self._PRW_country.SetText(value = match['field_label'], data = match['data']) 1551 1552 if self.default_region is not None: 1553 self._PRW_region.SetText(value = self.default_region)
1554 #----------------------------------------------------------------
1555 - def __perhaps_invalidate_address_searcher(self, ctrl=None, field=None):
1556 1557 adr = self._PRW_address_searcher.address 1558 if adr is None: 1559 return True 1560 1561 if ctrl.GetValue().strip() != adr[field]: 1562 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None) 1563 return True 1564 1565 return False
1566 #----------------------------------------------------------------
1568 adr = self._PRW_address_searcher.address 1569 if adr is None: 1570 return True 1571 1572 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode']) 1573 1574 self._PRW_street.SetText(value = adr['street'], data = adr['street']) 1575 self._PRW_street.set_context(context = u'zip', val = adr['postcode']) 1576 1577 self._TCTRL_number.SetValue(adr['number']) 1578 self._TCTRL_unit.SetValue(gmTools.coalesce(adr['subunit'], u'')) 1579 1580 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb']) 1581 self._PRW_urb.set_context(context = u'zip', val = adr['postcode']) 1582 1583 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state']) 1584 self._PRW_region.set_context(context = u'zip', val = adr['postcode']) 1585 1586 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country']) 1587 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
1588 #----------------------------------------------------------------
1589 - def __identity_valid_for_save(self):
1590 error = False 1591 1592 # name fields 1593 if self._PRW_lastname.GetValue().strip() == u'': 1594 error = True 1595 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 1596 self._PRW_lastname.display_as_valid(False) 1597 else: 1598 self._PRW_lastname.display_as_valid(True) 1599 1600 if self._PRW_firstnames.GetValue().strip() == '': 1601 error = True 1602 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 1603 self._PRW_firstnames.display_as_valid(False) 1604 else: 1605 self._PRW_firstnames.display_as_valid(True) 1606 1607 # gender 1608 if self._PRW_gender.GetData() is None: 1609 error = True 1610 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 1611 self._PRW_gender.display_as_valid(False) 1612 else: 1613 self._PRW_gender.display_as_valid(True) 1614 1615 # dob validation 1616 if not _validate_dob_field(self._PRW_dob): 1617 error = True 1618 1619 # TOB validation if non-empty 1620 # if self._TCTRL_tob.GetValue().strip() != u'': 1621 1622 return (not error)
1623 #----------------------------------------------------------------
1624 - def __address_valid_for_save(self, empty_address_is_valid=False):
1625 1626 # existing address ? if so set other fields 1627 if self._PRW_address_searcher.GetData() is not None: 1628 wx.CallAfter(self.__set_fields_from_address_searcher) 1629 return True 1630 1631 # must either all contain something or none of them 1632 fields_to_fill = ( 1633 self._TCTRL_number, 1634 self._PRW_zip, 1635 self._PRW_street, 1636 self._PRW_urb, 1637 self._PRW_region, 1638 self._PRW_country 1639 ) 1640 no_of_filled_fields = 0 1641 1642 for field in fields_to_fill: 1643 if field.GetValue().strip() != u'': 1644 no_of_filled_fields += 1 1645 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 1646 field.Refresh() 1647 1648 # empty address ? 1649 if no_of_filled_fields == 0: 1650 if empty_address_is_valid: 1651 return True 1652 else: 1653 return None 1654 1655 # incompletely filled address ? 1656 if no_of_filled_fields != len(fields_to_fill): 1657 for field in fields_to_fill: 1658 if field.GetValue().strip() == u'': 1659 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 1660 field.SetFocus() 1661 field.Refresh() 1662 msg = _('To properly create an address, all the related fields must be filled in.') 1663 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 1664 return False 1665 1666 # fields which must contain a selected item 1667 # FIXME: they must also contain an *acceptable combination* which 1668 # FIXME: can only be tested against the database itself ... 1669 strict_fields = ( 1670 self._PRW_region, 1671 self._PRW_country 1672 ) 1673 error = False 1674 for field in strict_fields: 1675 if field.GetData() is None: 1676 error = True 1677 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 1678 field.SetFocus() 1679 else: 1680 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 1681 field.Refresh() 1682 1683 if error: 1684 msg = _('This field must contain an item selected from the dropdown list.') 1685 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 1686 return False 1687 1688 return True
1689 #----------------------------------------------------------------
1690 - def __register_interests(self):
1691 1692 # identity 1693 self._PRW_firstnames.add_callback_on_lose_focus(self._on_leaving_firstname) 1694 1695 # address 1696 self._PRW_address_searcher.add_callback_on_lose_focus(self._on_leaving_adress_searcher) 1697 1698 # invalidate address searcher when any field edited 1699 self._PRW_street.add_callback_on_lose_focus(self._invalidate_address_searcher) 1700 wx.EVT_KILL_FOCUS(self._TCTRL_number, self._invalidate_address_searcher) 1701 wx.EVT_KILL_FOCUS(self._TCTRL_unit, self._invalidate_address_searcher) 1702 self._PRW_urb.add_callback_on_lose_focus(self._invalidate_address_searcher) 1703 self._PRW_region.add_callback_on_lose_focus(self._invalidate_address_searcher) 1704 1705 self._PRW_zip.add_callback_on_lose_focus(self._on_leaving_zip) 1706 self._PRW_country.add_callback_on_lose_focus(self._on_leaving_country)
1707 #---------------------------------------------------------------- 1708 # event handlers 1709 #----------------------------------------------------------------
1710 - def _on_leaving_firstname(self):
1711 """Set the gender according to entered firstname. 1712 1713 Matches are fetched from existing records in backend. 1714 """ 1715 # only set if not already set so as to not 1716 # overwrite a change by the user 1717 if self._PRW_gender.GetData() is not None: 1718 return True 1719 1720 firstname = self._PRW_firstnames.GetValue().strip() 1721 if firstname == u'': 1722 return True 1723 1724 gender = gmPerson.map_firstnames2gender(firstnames = firstname) 1725 if gender is None: 1726 return True 1727 1728 wx.CallAfter(self._PRW_gender.SetData, gender) 1729 return True
1730 #----------------------------------------------------------------
1731 - def _on_leaving_zip(self):
1732 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode') 1733 1734 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'') 1735 self._PRW_street.set_context(context = u'zip', val = zip_code) 1736 self._PRW_urb.set_context(context = u'zip', val = zip_code) 1737 self._PRW_region.set_context(context = u'zip', val = zip_code) 1738 self._PRW_country.set_context(context = u'zip', val = zip_code) 1739 1740 return True
1741 #----------------------------------------------------------------
1742 - def _on_leaving_country(self):
1743 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country') 1744 1745 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'') 1746 self._PRW_region.set_context(context = u'country', val = country) 1747 1748 return True
1749 #----------------------------------------------------------------
1750 - def _invalidate_address_searcher(self, *args, **kwargs):
1751 mapping = [ 1752 (self._PRW_street, 'street'), 1753 (self._TCTRL_number, 'number'), 1754 (self._TCTRL_unit, 'subunit'), 1755 (self._PRW_urb, 'urb'), 1756 (self._PRW_region, 'l10n_state') 1757 ] 1758 # loop through fields and invalidate address searcher if different 1759 for ctrl, field in mapping: 1760 if self.__perhaps_invalidate_address_searcher(ctrl, field): 1761 return True 1762 1763 return True
1764 #----------------------------------------------------------------
1766 if self._PRW_address_searcher.address is None: 1767 return True 1768 1769 wx.CallAfter(self.__set_fields_from_address_searcher) 1770 return True
1771 #---------------------------------------------------------------- 1772 # generic Edit Area mixin API 1773 #----------------------------------------------------------------
1774 - def _valid_for_save(self):
1775 if self._PRW_primary_provider.GetValue().strip() == u'': 1776 self._PRW_primary_provider.display_as_valid(True) 1777 else: 1778 if self._PRW_primary_provider.GetData() is None: 1779 self._PRW_primary_provider.display_as_valid(False) 1780 else: 1781 self._PRW_primary_provider.display_as_valid(True) 1782 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
1783 #----------------------------------------------------------------
1784 - def _save_as_new(self):
1785 1786 if self._PRW_dob.GetValue().strip() == u'': 1787 if not _empty_dob_allowed(): 1788 self._PRW_dob.display_as_valid(False) 1789 self._PRW_dob.SetFocus() 1790 return False 1791 1792 # identity 1793 new_identity = gmPerson.create_identity ( 1794 gender = self._PRW_gender.GetData(), 1795 dob = self._PRW_dob.GetData(), 1796 lastnames = self._PRW_lastname.GetValue().strip(), 1797 firstnames = self._PRW_firstnames.GetValue().strip() 1798 ) 1799 _log.debug('identity created: %s' % new_identity) 1800 1801 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip()) 1802 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u'')) 1803 #TOB 1804 prov = self._PRW_primary_provider.GetData() 1805 if prov is not None: 1806 new_identity['pk_primary_provider'] = prov 1807 new_identity.save() 1808 1809 name = new_identity.get_active_name() 1810 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1811 name.save() 1812 1813 # address 1814 is_valid = self.__address_valid_for_save(empty_address_is_valid = False) 1815 if is_valid is True: 1816 # because we currently only check for non-emptiness 1817 # we must still deal with database errors 1818 try: 1819 new_identity.link_address ( 1820 number = self._TCTRL_number.GetValue().strip(), 1821 street = self._PRW_street.GetValue().strip(), 1822 postcode = self._PRW_zip.GetValue().strip(), 1823 urb = self._PRW_urb.GetValue().strip(), 1824 state = self._PRW_region.GetData(), 1825 country = self._PRW_country.GetData(), 1826 subunit = gmTools.none_if(self._TCTRL_unit.GetValue().strip(), u'') 1827 ) 1828 except gmPG2.dbapi.InternalError: 1829 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip()) 1830 _log.debug('(sub)unit: >>%s<<', self._TCTRL_unit.GetValue().strip()) 1831 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip()) 1832 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip()) 1833 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip()) 1834 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip()) 1835 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip()) 1836 _log.exception('cannot link address') 1837 gmGuiHelpers.gm_show_error ( 1838 aTitle = _('Saving address'), 1839 aMessage = _( 1840 'Cannot save this address.\n' 1841 '\n' 1842 'You will have to add it via the Demographics plugin.\n' 1843 ) 1844 ) 1845 elif is_valid is False: 1846 gmGuiHelpers.gm_show_error ( 1847 aTitle = _('Saving address'), 1848 aMessage = _( 1849 'Address not saved.\n' 1850 '\n' 1851 'You will have to add it via the Demographics plugin.\n' 1852 ) 1853 ) 1854 # else it is None which means empty address which we ignore 1855 1856 # phone 1857 channel_name = self._PRW_channel_type.GetValue().strip() 1858 pk_channel_type = self._PRW_channel_type.GetData() 1859 if pk_channel_type is None: 1860 if channel_name == u'': 1861 channel_name = u'homephone' 1862 new_identity.link_comm_channel ( 1863 comm_medium = channel_name, 1864 pk_channel_type = pk_channel_type, 1865 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''), 1866 is_confidential = False 1867 ) 1868 1869 # external ID 1870 pk_type = self._PRW_external_id_type.GetData() 1871 id_value = self._TCTRL_external_id_value.GetValue().strip() 1872 if (pk_type is not None) and (id_value != u''): 1873 new_identity.add_external_id(value = id_value, pk_type = pk_type) 1874 1875 # occupation 1876 new_identity.link_occupation ( 1877 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'') 1878 ) 1879 1880 self.data = new_identity 1881 return True
1882 #----------------------------------------------------------------
1883 - def _save_as_update(self):
1884 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
1885 #----------------------------------------------------------------
1886 - def _refresh_as_new(self):
1887 # FIXME: button "empty out" 1888 return
1889 #----------------------------------------------------------------
1890 - def _refresh_from_existing(self):
1891 return # there is no forward button so nothing to do here
1892 #----------------------------------------------------------------
1894 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
1895 1896 #============================================================ 1897 # patient demographics editing classes 1898 #============================================================
1899 -class cPersonDemographicsEditorNb(wx.Notebook):
1900 """Notebook displaying demographics editing pages: 1901 1902 - Contacts (addresses, phone numbers, etc) 1903 - Identity 1904 - Social network (significant others, GP, etc) 1905 1906 Does NOT act on/listen to the current patient. 1907 """ 1908 #--------------------------------------------------------
1909 - def __init__(self, parent, id):
1910 1911 wx.Notebook.__init__ ( 1912 self, 1913 parent = parent, 1914 id = id, 1915 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER, 1916 name = self.__class__.__name__ 1917 ) 1918 1919 self.__identity = None 1920 self.__do_layout() 1921 self.SetSelection(0)
1922 #-------------------------------------------------------- 1923 # public API 1924 #--------------------------------------------------------
1925 - def refresh(self):
1926 """Populate fields in pages with data from model.""" 1927 for page_idx in range(self.GetPageCount()): 1928 page = self.GetPage(page_idx) 1929 page.identity = self.__identity 1930 1931 return True
1932 #-------------------------------------------------------- 1933 # internal API 1934 #--------------------------------------------------------
1935 - def __do_layout(self):
1936 """Build patient edition notebook pages.""" 1937 1938 # contacts page 1939 new_page = gmPersonContactWidgets.cPersonContactsManagerPnl(self, -1) 1940 new_page.identity = self.__identity 1941 self.AddPage ( 1942 page = new_page, 1943 text = _('Contacts'), 1944 select = True 1945 ) 1946 1947 # identity page 1948 new_page = cPersonIdentityManagerPnl(self, -1) 1949 new_page.identity = self.__identity 1950 self.AddPage ( 1951 page = new_page, 1952 text = _('Identity'), 1953 select = False 1954 ) 1955 1956 # social network page 1957 new_page = cPersonSocialNetworkManagerPnl(self, -1) 1958 new_page.identity = self.__identity 1959 self.AddPage ( 1960 page = new_page, 1961 text = _('Social network'), 1962 select = False 1963 )
1964 #-------------------------------------------------------- 1965 # properties 1966 #--------------------------------------------------------
1967 - def _get_identity(self):
1968 return self.__identity
1969
1970 - def _set_identity(self, identity):
1971 self.__identity = identity
1972 1973 identity = property(_get_identity, _set_identity)
1974 #============================================================ 1975 # old occupation widgets 1976 #============================================================ 1977 # FIXME: support multiple occupations 1978 # FIXME: redo with wxGlade 1979
1980 -class cPatOccupationsPanel(wx.Panel):
1981 """Page containing patient occupations edition fields. 1982 """
1983 - def __init__(self, parent, id, ident=None):
1984 """ 1985 Creates a new instance of BasicPatDetailsPage 1986 @param parent - The parent widget 1987 @type parent - A wx.Window instance 1988 @param id - The widget id 1989 @type id - An integer 1990 """ 1991 wx.Panel.__init__(self, parent, id) 1992 self.__ident = ident 1993 self.__do_layout()
1994 #--------------------------------------------------------
1995 - def __do_layout(self):
1996 PNL_form = wx.Panel(self, -1) 1997 # occupation 1998 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 1999 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 2000 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient")) 2001 # known since 2002 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated')) 2003 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY) 2004 2005 # layout input widgets 2006 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4) 2007 SZR_input.AddGrowableCol(1) 2008 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 2009 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 2010 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED) 2011 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND) 2012 PNL_form.SetSizerAndFit(SZR_input) 2013 2014 # layout page 2015 SZR_main = wx.BoxSizer(wx.VERTICAL) 2016 SZR_main.Add(PNL_form, 1, wx.EXPAND) 2017 self.SetSizer(SZR_main)
2018 #--------------------------------------------------------
2019 - def set_identity(self, identity):
2020 return self.refresh(identity=identity)
2021 #--------------------------------------------------------
2022 - def refresh(self, identity=None):
2023 if identity is not None: 2024 self.__ident = identity 2025 jobs = self.__ident.get_occupations() 2026 if len(jobs) > 0: 2027 self.PRW_occupation.SetText(jobs[0]['l10n_occupation']) 2028 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y')) 2029 return True
2030 #--------------------------------------------------------
2031 - def save(self):
2032 if self.PRW_occupation.IsModified(): 2033 new_job = self.PRW_occupation.GetValue().strip() 2034 jobs = self.__ident.get_occupations() 2035 for job in jobs: 2036 if job['l10n_occupation'] == new_job: 2037 continue 2038 self.__ident.unlink_occupation(occupation = job['l10n_occupation']) 2039 self.__ident.link_occupation(occupation = new_job) 2040 return True
2041 #============================================================
2042 -class cNotebookedPatEditionPanel(wx.Panel, gmRegetMixin.cRegetOnPaintMixin):
2043 """Patient demographics plugin for main notebook. 2044 2045 Hosts another notebook with pages for Identity, Contacts, etc. 2046 2047 Acts on/listens to the currently active patient. 2048 """ 2049 #--------------------------------------------------------
2050 - def __init__(self, parent, id):
2051 wx.Panel.__init__ (self, parent = parent, id = id, style = wx.NO_BORDER) 2052 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 2053 self.__do_layout() 2054 self.__register_interests()
2055 #-------------------------------------------------------- 2056 # public API 2057 #-------------------------------------------------------- 2058 #-------------------------------------------------------- 2059 # internal helpers 2060 #--------------------------------------------------------
2061 - def __do_layout(self):
2062 """Arrange widgets.""" 2063 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1) 2064 2065 szr_main = wx.BoxSizer(wx.VERTICAL) 2066 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND) 2067 self.SetSizerAndFit(szr_main)
2068 #-------------------------------------------------------- 2069 # event handling 2070 #--------------------------------------------------------
2071 - def __register_interests(self):
2072 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 2073 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
2074 #--------------------------------------------------------
2075 - def _on_pre_patient_selection(self):
2076 self._schedule_data_reget()
2077 #--------------------------------------------------------
2078 - def _on_post_patient_selection(self):
2079 self._schedule_data_reget() 2080 print "_on_post_patient_selection: scheduled"
2081 # reget mixin API 2082 #--------------------------------------------------------
2083 - def _populate_with_data(self):
2084 """Populate fields in pages with data from model.""" 2085 pat = gmPerson.gmCurrentPatient() 2086 if pat.connected: 2087 self.__patient_notebook.identity = pat 2088 else: 2089 self.__patient_notebook.identity = None 2090 self.__patient_notebook.refresh() 2091 return True
2092 #============================================================ 2093 #============================================================ 2094 if __name__ == "__main__": 2095 2096 #--------------------------------------------------------
2097 - def test_organizer_pnl():
2098 app = wx.PyWidgetTester(size = (600, 400)) 2099 app.SetWidget(cKOrganizerSchedulePnl) 2100 app.MainLoop()
2101 #--------------------------------------------------------
2102 - def test_person_names_pnl():
2103 app = wx.PyWidgetTester(size = (600, 400)) 2104 widget = cPersonNamesManagerPnl(app.frame, -1) 2105 widget.identity = activate_patient() 2106 app.frame.Show(True) 2107 app.MainLoop()
2108 #--------------------------------------------------------
2109 - def test_person_ids_pnl():
2110 app = wx.PyWidgetTester(size = (600, 400)) 2111 widget = cPersonIDsManagerPnl(app.frame, -1) 2112 widget.identity = activate_patient() 2113 app.frame.Show(True) 2114 app.MainLoop()
2115 #--------------------------------------------------------
2116 - def test_pat_ids_pnl():
2117 app = wx.PyWidgetTester(size = (600, 400)) 2118 widget = cPersonIdentityManagerPnl(app.frame, -1) 2119 widget.identity = activate_patient() 2120 app.frame.Show(True) 2121 app.MainLoop()
2122 #--------------------------------------------------------
2123 - def test_name_ea_pnl():
2124 app = wx.PyWidgetTester(size = (600, 400)) 2125 app.SetWidget(cPersonNameEAPnl, name = activate_patient().get_active_name()) 2126 app.MainLoop()
2127 #--------------------------------------------------------
2128 - def test_cPersonDemographicsEditorNb():
2129 app = wx.PyWidgetTester(size = (600, 400)) 2130 widget = cPersonDemographicsEditorNb(app.frame, -1) 2131 widget.identity = activate_patient() 2132 widget.refresh() 2133 app.frame.Show(True) 2134 app.MainLoop()
2135 #--------------------------------------------------------
2136 - def activate_patient():
2137 patient = gmPersonSearch.ask_for_patient() 2138 if patient is None: 2139 print "No patient. Exiting gracefully..." 2140 sys.exit(0) 2141 from Gnumed.wxpython import gmPatSearchWidgets 2142 gmPatSearchWidgets.set_active_patient(patient=patient) 2143 return patient
2144 #-------------------------------------------------------- 2145 if len(sys.argv) > 1 and sys.argv[1] == 'test': 2146 2147 gmI18N.activate_locale() 2148 gmI18N.install_domain(domain='gnumed') 2149 gmPG2.get_connection() 2150 2151 # app = wx.PyWidgetTester(size = (400, 300)) 2152 # app.SetWidget(cNotebookedPatEditionPanel, -1) 2153 # app.frame.Show(True) 2154 # app.MainLoop() 2155 2156 # phrasewheels 2157 # test_organizer_pnl() 2158 2159 # identity related widgets 2160 #test_person_names_pnl() 2161 test_person_ids_pnl() 2162 #test_pat_ids_pnl() 2163 #test_name_ea_pnl() 2164 2165 #test_cPersonDemographicsEditorNb() 2166 2167 #============================================================ 2168