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