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