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.Destroy() 78 return True 79 dlg.Destroy() 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 gmDispatcher.send(signal = 'statustext', msg = _('Must pick an image file for a new tag.'), beep = True) 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 gmDispatcher.send(signal = 'statustext', msg = _('Cannot open the image file [%s].') % self.__selected_image_file, beep = True) 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.Destroy() 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 gmDispatcher.send(signal = 'statustext', msg = _('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 exc = gmPG2.make_pg_exception_fields_unicode(exc) 1131 gmGuiHelpers.gm_show_error ( 1132 aTitle = _('Adding name'), 1133 aMessage = _( 1134 'Cannot add this name to the patient !\n' 1135 '\n' 1136 ' %s' 1137 ) % exc.u_pgerror 1138 ) 1139 return False 1140 1141 old_nick = self.__identity['active_name']['preferred'] 1142 new_nick = gmTools.none_if(self._PRW_nick.GetValue().strip(), '') 1143 if active: 1144 data['preferred'] = gmTools.coalesce(new_nick, old_nick) 1145 else: 1146 data['preferred'] = new_nick 1147 data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '') 1148 data.save() 1149 1150 self.data = data 1151 return True
1152 #----------------------------------------------------------------
1153 - def _save_as_update(self):
1154 """The knack here is that we can only update a few fields. 1155 1156 Otherwise we need to clone the name and update that. 1157 """ 1158 first = self._PRW_firstname.GetValue().strip() 1159 last = self._PRW_lastname.GetValue().strip() 1160 active = self._CHBOX_active.GetValue() 1161 1162 current_name = self.data['firstnames'].strip() + self.data['lastnames'].strip() 1163 new_name = first + last 1164 1165 # editable fields only ? 1166 if new_name == current_name: 1167 self.data['active_name'] = self._CHBOX_active.GetValue() 1168 self.data['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), '') 1169 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '') 1170 self.data.save() 1171 # else clone name and update that 1172 else: 1173 try: 1174 name = self.__identity.add_name(first, last, active) 1175 except gmPG2.dbapi.IntegrityError as exc: 1176 _log.exception('cannot clone name when editing existing name') 1177 exc = gmPG2.make_pg_exception_fields_unicode(exc) 1178 gmGuiHelpers.gm_show_error ( 1179 aTitle = _('Editing name'), 1180 aMessage = _( 1181 'Cannot clone a copy of this name !\n' 1182 '\n' 1183 ' %s' 1184 ) % exc.u_pgerror 1185 # ) % str(exc) 1186 ) 1187 return False 1188 name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), '') 1189 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '') 1190 name.save() 1191 self.data = name 1192 1193 return True
1194 #----------------------------------------------------------------
1195 - def _refresh_as_new(self):
1196 self._PRW_firstname.SetText(value = '', data = None) 1197 self._PRW_lastname.SetText(value = '', data = None) 1198 self._PRW_nick.SetText(value = '', data = None) 1199 self._TCTRL_comment.SetValue('') 1200 self._CHBOX_active.SetValue(False) 1201 1202 self._PRW_firstname.SetFocus()
1203 #----------------------------------------------------------------
1205 self._refresh_as_new() 1206 self._PRW_firstname.SetText(value = '', data = None) 1207 self._PRW_nick.SetText(gmTools.coalesce(self.data['preferred'], '')) 1208 1209 self._PRW_lastname.SetFocus()
1210 #----------------------------------------------------------------
1211 - def _refresh_from_existing(self):
1212 self._PRW_firstname.SetText(self.data['firstnames']) 1213 self._PRW_lastname.SetText(self.data['lastnames']) 1214 self._PRW_nick.SetText(gmTools.coalesce(self.data['preferred'], '')) 1215 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], '')) 1216 self._CHBOX_active.SetValue(self.data['active_name']) 1217 1218 self._TCTRL_comment.SetFocus()
1219 #------------------------------------------------------------ 1220 # list manager 1221 #------------------------------------------------------------
1222 -class cPersonNamesManagerPnl(gmListWidgets.cGenericListManagerPnl):
1223 """A list for managing a person's names. 1224 1225 Does NOT act on/listen to the current patient. 1226 """
1227 - def __init__(self, *args, **kwargs):
1228 1229 try: 1230 self.__identity = kwargs['identity'] 1231 del kwargs['identity'] 1232 except KeyError: 1233 self.__identity = None 1234 1235 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1236 1237 self.refresh_callback = self.refresh 1238 self.new_callback = self._add_name 1239 self.edit_callback = self._edit_name 1240 self.delete_callback = self._del_name 1241 1242 self.__init_ui() 1243 self.refresh()
1244 #-------------------------------------------------------- 1245 # external API 1246 #--------------------------------------------------------
1247 - def refresh(self, *args, **kwargs):
1248 if self.__identity is None: 1249 self._LCTRL_items.set_string_items() 1250 return 1251 1252 names = self.__identity.get_names() 1253 self._LCTRL_items.set_string_items ( 1254 items = [ [ 1255 gmTools.bool2str(n['active_name'], 'X', ''), 1256 n['lastnames'], 1257 n['firstnames'], 1258 gmTools.coalesce(n['preferred'], ''), 1259 gmTools.coalesce(n['comment'], '') 1260 ] for n in names ] 1261 ) 1262 self._LCTRL_items.set_column_widths() 1263 self._LCTRL_items.set_data(data = names)
1264 #-------------------------------------------------------- 1265 # internal helpers 1266 #--------------------------------------------------------
1267 - def __init_ui(self):
1268 self._LCTRL_items.set_columns(columns = [ 1269 _('Active'), 1270 _('Lastname'), 1271 _('Firstname(s)'), 1272 _('Preferred Name'), 1273 _('Comment') 1274 ])
1275 #--------------------------------------------------------
1276 - def _add_name(self):
1277 #ea = cPersonNameEAPnl(self, -1, name = self.__identity.get_active_name()) 1278 ea = cPersonNameEAPnl(self, -1, identity = self.__identity) 1279 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1280 dlg.SetTitle(_('Adding new name')) 1281 if dlg.ShowModal() == wx.ID_OK: 1282 dlg.Destroy() 1283 return True 1284 dlg.Destroy() 1285 return False
1286 #--------------------------------------------------------
1287 - def _edit_name(self, name):
1288 ea = cPersonNameEAPnl(self, -1, name = name) 1289 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1290 dlg.SetTitle(_('Editing name')) 1291 if dlg.ShowModal() == wx.ID_OK: 1292 dlg.Destroy() 1293 return True 1294 dlg.Destroy() 1295 return False
1296 #--------------------------------------------------------
1297 - def _del_name(self, name):
1298 1299 if len(self.__identity.get_names()) == 1: 1300 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete the only name of a person.'), beep = True) 1301 return False 1302 1303 if name['active_name']: 1304 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete the active name of a person.'), beep = True) 1305 return False 1306 1307 go_ahead = gmGuiHelpers.gm_show_question ( 1308 _( 'It is often advisable to keep old names around and\n' 1309 'just create a new "currently active" name.\n' 1310 '\n' 1311 'This allows finding the patient by both the old\n' 1312 'and the new name (think before/after marriage).\n' 1313 '\n' 1314 'Do you still want to really delete\n' 1315 "this name from the patient ?" 1316 ), 1317 _('Deleting name') 1318 ) 1319 if not go_ahead: 1320 return False 1321 1322 self.__identity.delete_name(name = name) 1323 return True
1324 #-------------------------------------------------------- 1325 # properties 1326 #--------------------------------------------------------
1327 - def _get_identity(self):
1328 return self.__identity
1329
1330 - def _set_identity(self, identity):
1331 self.__identity = identity 1332 self.refresh()
1333 1334 identity = property(_get_identity, _set_identity)
1335 1336 #------------------------------------------------------------
1337 -class cPersonIDsManagerPnl(gmListWidgets.cGenericListManagerPnl):
1338 """A list for managing a person's external IDs. 1339 1340 Does NOT act on/listen to the current patient. 1341 """
1342 - def __init__(self, *args, **kwargs):
1343 1344 try: 1345 self.__identity = kwargs['identity'] 1346 del kwargs['identity'] 1347 except KeyError: 1348 self.__identity = None 1349 1350 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1351 1352 self.refresh_callback = self.refresh 1353 self.new_callback = self._add_id 1354 self.edit_callback = self._edit_id 1355 self.delete_callback = self._del_id 1356 1357 self.__init_ui() 1358 self.refresh()
1359 #-------------------------------------------------------- 1360 # external API 1361 #--------------------------------------------------------
1362 - def refresh(self, *args, **kwargs):
1363 if self.__identity is None: 1364 self._LCTRL_items.set_string_items() 1365 return 1366 1367 ids = self.__identity.get_external_ids() 1368 self._LCTRL_items.set_string_items ( 1369 items = [ [ 1370 i['name'], 1371 i['value'], 1372 gmTools.coalesce(i['issuer'], ''), 1373 gmTools.coalesce(i['comment'], '') 1374 ] for i in ids 1375 ] 1376 ) 1377 self._LCTRL_items.set_column_widths() 1378 self._LCTRL_items.set_data(data = ids)
1379 #-------------------------------------------------------- 1380 # internal helpers 1381 #--------------------------------------------------------
1382 - def __init_ui(self):
1383 self._LCTRL_items.set_columns(columns = [ 1384 _('ID type'), 1385 _('Value'), 1386 _('Issuer'), 1387 _('Comment') 1388 ])
1389 #--------------------------------------------------------
1390 - def _add_id(self):
1391 ea = cExternalIDEditAreaPnl(self, -1) 1392 ea.id_holder = self.__identity 1393 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea) 1394 dlg.SetTitle(_('Adding new external ID')) 1395 if dlg.ShowModal() == wx.ID_OK: 1396 dlg.Destroy() 1397 return True 1398 dlg.Destroy() 1399 return False
1400 #--------------------------------------------------------
1401 - def _edit_id(self, ext_id):
1402 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id) 1403 ea.id_holder = self.__identity 1404 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1405 dlg.SetTitle(_('Editing external ID')) 1406 if dlg.ShowModal() == wx.ID_OK: 1407 dlg.Destroy() 1408 return True 1409 dlg.Destroy() 1410 return False
1411 #--------------------------------------------------------
1412 - def _del_id(self, ext_id):
1413 go_ahead = gmGuiHelpers.gm_show_question ( 1414 _( 'Do you really want to delete this\n' 1415 'external ID from the patient ?'), 1416 _('Deleting external ID') 1417 ) 1418 if not go_ahead: 1419 return False 1420 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id']) 1421 return True
1422 #-------------------------------------------------------- 1423 # properties 1424 #--------------------------------------------------------
1425 - def _get_identity(self):
1426 return self.__identity
1427
1428 - def _set_identity(self, identity):
1429 self.__identity = identity 1430 self.refresh()
1431 1432 identity = property(_get_identity, _set_identity)
1433 #------------------------------------------------------------ 1434 # integrated panels 1435 #------------------------------------------------------------ 1436 from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl 1437
1438 -class cPersonIdentityManagerPnl(wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl):
1439 """A panel for editing identity data for a person. 1440 1441 - provides access to: 1442 - identity EA 1443 - name list manager 1444 - external IDs list manager 1445 1446 Does NOT act on/listen to the current patient. 1447 """
1448 - def __init__(self, *args, **kwargs):
1449 1450 wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl.__init__(self, *args, **kwargs) 1451 1452 self.__identity = None 1453 self.refresh()
1454 #-------------------------------------------------------- 1455 # external API 1456 #--------------------------------------------------------
1457 - def refresh(self):
1458 self._PNL_names.identity = self.__identity 1459 self._PNL_ids.identity = self.__identity 1460 # this is an Edit Area: 1461 self._PNL_identity.mode = 'new' 1462 self._PNL_identity.data = self.__identity 1463 if self.__identity is not None: 1464 self._PNL_identity.mode = 'edit' 1465 self._PNL_identity._refresh_from_existing()
1466 #-------------------------------------------------------- 1467 # properties 1468 #--------------------------------------------------------
1469 - def _get_identity(self):
1470 return self.__identity
1471
1472 - def _set_identity(self, identity):
1473 self.__identity = identity 1474 self.refresh()
1475 1476 identity = property(_get_identity, _set_identity) 1477 #-------------------------------------------------------- 1478 # event handlers 1479 #--------------------------------------------------------
1481 if not self._PNL_identity.save(): 1482 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save identity. Incomplete information.'), beep = True)
1483 #self._PNL_identity.refresh() 1484 #--------------------------------------------------------
1485 - def _on_reload_identity_button_pressed(self, event):
1486 self._PNL_identity.refresh()
1487 1488 #============================================================ 1489 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl 1490
1491 -class cPersonSocialNetworkManagerPnl(wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl):
1492 - def __init__(self, *args, **kwargs):
1493 1494 wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl.__init__(self, *args, **kwargs) 1495 1496 self.__identity = None 1497 self._PRW_provider.selection_only = False 1498 self.refresh()
1499 #-------------------------------------------------------- 1500 # external API 1501 #--------------------------------------------------------
1502 - def refresh(self):
1503 1504 tt = _('Link another person in this database as the emergency contact:\n\nEnter person name part or identifier and hit <enter>.') 1505 1506 if self.__identity is None: 1507 self._TCTRL_er_contact.SetValue('') 1508 self._TCTRL_person.person = None 1509 self._TCTRL_person.SetToolTip(tt) 1510 1511 self._PRW_provider.SetText(value = '', data = None) 1512 return 1513 1514 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], '')) 1515 if self.__identity['pk_emergency_contact'] is not None: 1516 ident = gmPerson.cPerson(aPK_obj = self.__identity['pk_emergency_contact']) 1517 self._TCTRL_person.person = ident 1518 tt = '%s\n\n%s\n\n%s' % ( 1519 tt, 1520 ident['description_gender'], 1521 '\n'.join([ 1522 '%s: %s%s' % ( 1523 c['l10n_comm_type'], 1524 c['url'], 1525 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), '', '') 1526 ) 1527 for c in ident.get_comm_channels() 1528 ]) 1529 ) 1530 else: 1531 self._TCTRL_person.person = None 1532 1533 self._TCTRL_person.SetToolTip(tt) 1534 1535 if self.__identity['pk_primary_provider'] is None: 1536 self._PRW_provider.SetText(value = '', data = None) 1537 else: 1538 self._PRW_provider.SetData(data = self.__identity['pk_primary_provider']) 1539 1540 self._PNL_external_care.identity = self.__identity
1541 #-------------------------------------------------------- 1542 # properties 1543 #--------------------------------------------------------
1544 - def _get_identity(self):
1545 return self.__identity
1546
1547 - def _set_identity(self, identity):
1548 self.__identity = identity 1549 self.refresh()
1550 1551 identity = property(_get_identity, _set_identity) 1552 #-------------------------------------------------------- 1553 # event handlers 1554 #--------------------------------------------------------
1555 - def _on_save_button_pressed(self, event):
1556 if self.__identity is not None: 1557 self.__identity['emergency_contact'] = self._TCTRL_er_contact.GetValue().strip() 1558 if self._TCTRL_person.person is not None: 1559 self.__identity['pk_emergency_contact'] = self._TCTRL_person.person.ID 1560 if self._PRW_provider.GetValue().strip == '': 1561 self.__identity['pk_primary_provider'] = None 1562 else: 1563 self.__identity['pk_primary_provider'] = self._PRW_provider.GetData() 1564 1565 self.__identity.save() 1566 gmDispatcher.send(signal = 'statustext', msg = _('Emergency data and primary provider saved.'), beep = False) 1567 1568 event.Skip()
1569 #--------------------------------------------------------
1570 - def _on_reload_button_pressed(self, event):
1571 self.refresh()
1572 #--------------------------------------------------------
1573 - def _on_remove_contact_button_pressed(self, event):
1574 event.Skip() 1575 1576 if self.__identity is None: 1577 return 1578 1579 self._TCTRL_person.person = None 1580 1581 self.__identity['pk_emergency_contact'] = None 1582 self.__identity.save()
1583 #--------------------------------------------------------
1584 - def _on_button_activate_contact_pressed(self, event):
1585 ident = self._TCTRL_person.person 1586 if ident is not None: 1587 from Gnumed.wxpython.gmPatSearchWidgets import set_active_patient 1588 wx.CallAfter(set_active_patient, patient = ident) 1589 1590 event.Skip()
1591 1592 #============================================================ 1593 # patient demographics editing classes 1594 #============================================================
1595 -class cPersonDemographicsEditorNb(wx.Notebook):
1596 """Notebook displaying demographics editing pages: 1597 1598 - Identity (as per Jim/Rogerio 12/2011) 1599 - Contacts (addresses, phone numbers, etc) 1600 - Social network (significant others, GP, etc) 1601 1602 Does NOT act on/listen to the current patient. 1603 """ 1604 #--------------------------------------------------------
1605 - def __init__(self, parent, id):
1606 1607 wx.Notebook.__init__ ( 1608 self, 1609 parent = parent, 1610 id = id, 1611 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER, 1612 name = self.__class__.__name__ 1613 ) 1614 _log.debug('created wx.Notebook: %s with ID %s', self.__class__.__name__, self.Id) 1615 1616 self.__identity = None 1617 self.__do_layout() 1618 self.SetSelection(0)
1619 #-------------------------------------------------------- 1620 # public API 1621 #--------------------------------------------------------
1622 - def refresh(self):
1623 """Populate fields in pages with data from model.""" 1624 for page_idx in range(self.GetPageCount()): 1625 page = self.GetPage(page_idx) 1626 page.identity = self.__identity 1627 1628 return True
1629 #-------------------------------------------------------- 1630 # internal API 1631 #--------------------------------------------------------
1632 - def __do_layout(self):
1633 """Build patient edition notebook pages.""" 1634 1635 # identity page 1636 new_page = cPersonIdentityManagerPnl(self, -1) 1637 new_page.identity = self.__identity 1638 self.AddPage ( 1639 page = new_page, 1640 text = _('Identity'), 1641 select = False 1642 ) 1643 1644 # contacts page 1645 new_page = gmPersonContactWidgets.cPersonContactsManagerPnl(self, -1) 1646 new_page.identity = self.__identity 1647 self.AddPage ( 1648 page = new_page, 1649 text = _('Contacts'), 1650 select = True 1651 ) 1652 1653 # social network page 1654 new_page = cPersonSocialNetworkManagerPnl(self, -1) 1655 new_page.identity = self.__identity 1656 self.AddPage ( 1657 page = new_page, 1658 text = _('Social network'), 1659 select = False 1660 )
1661 #-------------------------------------------------------- 1662 # properties 1663 #--------------------------------------------------------
1664 - def _get_identity(self):
1665 return self.__identity
1666
1667 - def _set_identity(self, identity):
1668 self.__identity = identity
1669 1670 identity = property(_get_identity, _set_identity)
1671 1672 #============================================================ 1673 # old occupation widgets 1674 #============================================================ 1675 # FIXME: support multiple occupations 1676 # FIXME: redo with wxGlade 1677
1678 -class cPatOccupationsPanel(wx.Panel):
1679 """Page containing patient occupations edition fields. 1680 """
1681 - def __init__(self, parent, id, ident=None):
1682 """ 1683 Creates a new instance of BasicPatDetailsPage 1684 @param parent - The parent widget 1685 @type parent - A wx.Window instance 1686 @param id - The widget id 1687 @type id - An integer 1688 """ 1689 wx.Panel.__init__(self, parent, id) 1690 self.__ident = ident 1691 self.__do_layout()
1692 #--------------------------------------------------------
1693 - def __do_layout(self):
1694 PNL_form = wx.Panel(self, -1) 1695 # occupation 1696 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 1697 self.PRW_occupation = cOccupationPhraseWheel(PNL_form, -1) 1698 self.PRW_occupation.SetToolTip(_("primary occupation of the patient")) 1699 # known since 1700 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated')) 1701 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY) 1702 1703 # layout input widgets 1704 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4) 1705 SZR_input.AddGrowableCol(1) 1706 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 1707 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 1708 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED) 1709 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND) 1710 PNL_form.SetSizerAndFit(SZR_input) 1711 1712 # layout page 1713 SZR_main = wx.BoxSizer(wx.VERTICAL) 1714 SZR_main.Add(PNL_form, 1, wx.EXPAND) 1715 self.SetSizer(SZR_main)
1716 #--------------------------------------------------------
1717 - def set_identity(self, identity):
1718 return self.refresh(identity=identity)
1719 #--------------------------------------------------------
1720 - def refresh(self, identity=None):
1721 if identity is not None: 1722 self.__ident = identity 1723 jobs = self.__ident.get_occupations() 1724 if len(jobs) > 0: 1725 self.PRW_occupation.SetText(jobs[0]['l10n_occupation']) 1726 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y')) 1727 return True
1728 #--------------------------------------------------------
1729 - def save(self):
1730 if self.PRW_occupation.IsModified(): 1731 new_job = self.PRW_occupation.GetValue().strip() 1732 jobs = self.__ident.get_occupations() 1733 for job in jobs: 1734 if job['l10n_occupation'] == new_job: 1735 continue 1736 self.__ident.unlink_occupation(occupation = job['l10n_occupation']) 1737 self.__ident.link_occupation(occupation = new_job) 1738 return True
1739 1740 #============================================================
1741 -class cNotebookedPatEditionPanel(wx.Panel, gmRegetMixin.cRegetOnPaintMixin):
1742 """Patient demographics plugin for main notebook. 1743 1744 Hosts another notebook with pages for Identity, Contacts, etc. 1745 1746 Acts on/listens to the currently active patient. 1747 """ 1748 #--------------------------------------------------------
1749 - def __init__(self, parent, id):
1750 wx.Panel.__init__ (self, parent = parent, id = id, style = wx.NO_BORDER) 1751 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1752 self.__do_layout() 1753 self.__register_interests()
1754 #-------------------------------------------------------- 1755 # public API 1756 #-------------------------------------------------------- 1757 #-------------------------------------------------------- 1758 # internal helpers 1759 #--------------------------------------------------------
1760 - def __do_layout(self):
1761 """Arrange widgets.""" 1762 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1) 1763 1764 szr_main = wx.BoxSizer(wx.VERTICAL) 1765 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND) 1766 self.SetSizerAndFit(szr_main)
1767 #-------------------------------------------------------- 1768 # event handling 1769 #--------------------------------------------------------
1770 - def __register_interests(self):
1771 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection) 1772 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1773 #--------------------------------------------------------
1775 self.__patient_notebook.identity = None 1776 self.__patient_notebook.refresh()
1777 #--------------------------------------------------------
1778 - def _on_post_patient_selection(self):
1779 self._schedule_data_reget()
1780 #-------------------------------------------------------- 1781 # reget mixin API 1782 #--------------------------------------------------------
1783 - def _populate_with_data(self):
1784 """Populate fields in pages with data from model.""" 1785 pat = gmPerson.gmCurrentPatient() 1786 if pat.connected: 1787 self.__patient_notebook.identity = pat 1788 else: 1789 self.__patient_notebook.identity = None 1790 self.__patient_notebook.refresh() 1791 return True
1792 1793 #============================================================ 1794 #============================================================ 1795 if __name__ == "__main__": 1796 1797 #--------------------------------------------------------
1798 - def test_organizer_pnl():
1799 app = wx.PyWidgetTester(size = (600, 400)) 1800 app.SetWidget(cKOrganizerSchedulePnl) 1801 app.MainLoop()
1802 #--------------------------------------------------------
1803 - def test_person_names_pnl():
1804 app = wx.PyWidgetTester(size = (600, 400)) 1805 widget = cPersonNamesManagerPnl(app.frame, -1) 1806 widget.identity = activate_patient() 1807 app.frame.Show(True) 1808 app.MainLoop()
1809 #--------------------------------------------------------
1810 - def test_person_ids_pnl():
1811 app = wx.PyWidgetTester(size = (600, 400)) 1812 widget = cPersonIDsManagerPnl(app.frame, -1) 1813 widget.identity = activate_patient() 1814 app.frame.Show(True) 1815 app.MainLoop()
1816 #--------------------------------------------------------
1817 - def test_pat_ids_pnl():
1818 app = wx.PyWidgetTester(size = (600, 400)) 1819 widget = cPersonIdentityManagerPnl(app.frame, -1) 1820 widget.identity = activate_patient() 1821 app.frame.Show(True) 1822 app.MainLoop()
1823 #--------------------------------------------------------
1824 - def test_name_ea_pnl():
1825 app = wx.PyWidgetTester(size = (600, 400)) 1826 app.SetWidget(cPersonNameEAPnl, name = activate_patient().get_active_name()) 1827 app.MainLoop()
1828 #--------------------------------------------------------
1829 - def test_cPersonDemographicsEditorNb():
1830 app = wx.PyWidgetTester(size = (600, 400)) 1831 widget = cPersonDemographicsEditorNb(app.frame, -1) 1832 widget.identity = activate_patient() 1833 widget.refresh() 1834 app.frame.Show(True) 1835 app.MainLoop()
1836 #--------------------------------------------------------
1837 - def activate_patient():
1838 patient = gmPersonSearch.ask_for_patient() 1839 if patient is None: 1840 print("No patient. Exiting gracefully...") 1841 sys.exit(0) 1842 from Gnumed.wxpython.gmPatSearchWidgets import set_active_patient 1843 set_active_patient(patient = patient) 1844 return patient
1845 #-------------------------------------------------------- 1846 if len(sys.argv) > 1 and sys.argv[1] == 'test': 1847 1848 gmI18N.activate_locale() 1849 gmI18N.install_domain(domain='gnumed') 1850 gmPG2.get_connection() 1851 1852 # app = wx.PyWidgetTester(size = (400, 300)) 1853 # app.SetWidget(cNotebookedPatEditionPanel, -1) 1854 # app.frame.Show(True) 1855 # app.MainLoop() 1856 1857 # phrasewheels 1858 # test_organizer_pnl() 1859 1860 # identity related widgets 1861 #test_person_names_pnl() 1862 test_person_ids_pnl() 1863 #test_pat_ids_pnl() 1864 #test_name_ea_pnl() 1865 1866 #test_cPersonDemographicsEditorNb() 1867 1868 #============================================================ 1869