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

Source Code for Module Gnumed.wxpython.gmDemographicsWidgets

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