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 #------------------------------------------------------------
886 -def _validate_dob_field(dob_prw):
887 888 # valid timestamp ? 889 if dob_prw.is_valid_timestamp(empty_is_valid = False): # properly colors the field 890 dob = dob_prw.date 891 # but year also usable ? 892 #if (dob.year > 1899) and (dob < gmDateTime.pydt_now_here()): 893 if (dob.year > pydt.MINYEAR) and (dob < gmDateTime.pydt_now_here()): 894 return True 895 896 if dob.year < 1800: 897 msg = _( 898 'DOB: %s\n' 899 '\n' 900 'While this is a valid point in time Python\n' 901 'may not know how to deal with it.\n' 902 '\n' 903 'We suggest using January 1st 1801 instead and adding\n' 904 'the true date of birth to the patient comment.\n' 905 '\n' 906 'Sorry for the inconvenience %s' 907 ) % (dob, gmTools.u_frowning_face) 908 else: 909 msg = _( 910 'DOB: %s\n' 911 '\n' 912 'Date of birth in the future !' 913 ) % dob 914 gmGuiHelpers.gm_show_error ( 915 msg, 916 _('Validating date of birth') 917 ) 918 dob_prw.display_as_valid(False) 919 dob_prw.SetFocus() 920 return False 921 922 # invalid timestamp but not empty 923 if dob_prw.GetValue().strip() != '': 924 dob_prw.display_as_valid(False) 925 gmDispatcher.send(signal = 'statustext', msg = _('Invalid date of birth.')) 926 dob_prw.SetFocus() 927 return False 928 929 # empty DOB field 930 dob_prw.display_as_valid(False) 931 return True
932 933 #------------------------------------------------------------
934 -def _validate_tob_field(ctrl):
935 936 val = ctrl.GetValue().strip() 937 938 if val == '': 939 return True 940 941 converted, hours = gmTools.input2int(val[:2], 0, 23) 942 if not converted: 943 return False 944 945 converted, minutes = gmTools.input2int(val[3:5], 0, 59) 946 if not converted: 947 return False 948 949 return True
950 951 #------------------------------------------------------------ 952 from Gnumed.wxGladeWidgets import wxgIdentityEAPnl 953
954 -class cIdentityEAPnl(wxgIdentityEAPnl.wxgIdentityEAPnl, gmEditArea.cGenericEditAreaMixin):
955 """An edit area for editing/creating title/gender/dob/dod etc.""" 956
957 - def __init__(self, *args, **kwargs):
958 959 try: 960 data = kwargs['identity'] 961 del kwargs['identity'] 962 except KeyError: 963 data = None 964 965 wxgIdentityEAPnl.wxgIdentityEAPnl.__init__(self, *args, **kwargs) 966 gmEditArea.cGenericEditAreaMixin.__init__(self) 967 968 self.mode = 'new' 969 self.data = data 970 if data is not None: 971 self.mode = 'edit'
972 973 # self.__init_ui() 974 #---------------------------------------------------------------- 975 # def __init_ui(self): 976 # # adjust phrasewheels etc 977 #---------------------------------------------------------------- 978 # generic Edit Area mixin API 979 #----------------------------------------------------------------
980 - def _valid_for_save(self):
981 982 has_error = False 983 984 if self._PRW_gender.GetData() is None: 985 self._PRW_gender.SetFocus() 986 has_error = True 987 988 if self.data is not None: 989 if not _validate_dob_field(self._PRW_dob): 990 has_error = True 991 992 # TOB validation 993 if _validate_tob_field(self._TCTRL_tob): 994 self.display_ctrl_as_valid(ctrl = self._TCTRL_tob, valid = True) 995 else: 996 has_error = True 997 self.display_ctrl_as_valid(ctrl = self._TCTRL_tob, valid = False) 998 999 if not self._PRW_dod.is_valid_timestamp(empty_is_valid = True): 1000 self.StatusText = _('Invalid date of death.') 1001 self._PRW_dod.SetFocus() 1002 has_error = True 1003 1004 return (has_error is False)
1005 #----------------------------------------------------------------
1006 - def _save_as_new(self):
1007 # not used yet 1008 return False
1009 #----------------------------------------------------------------
1010 - def _save_as_update(self):
1011 1012 if self._PRW_dob.GetValue().strip() == '': 1013 if not _empty_dob_allowed(): 1014 return False 1015 self.data['dob'] = None 1016 else: 1017 self.data['dob'] = self._PRW_dob.GetData() 1018 self.data['dob_is_estimated'] = self._CHBOX_estimated_dob.GetValue() 1019 val = self._TCTRL_tob.GetValue().strip() 1020 if val == '': 1021 self.data['tob'] = None 1022 else: 1023 self.data['tob'] = pydt.time(int(val[:2]), int(val[3:5])) 1024 self.data['gender'] = self._PRW_gender.GetData() 1025 self.data['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), '') 1026 self.data['deceased'] = self._PRW_dod.GetData() 1027 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '') 1028 1029 self.data.save() 1030 return True
1031 #----------------------------------------------------------------
1032 - def _refresh_as_new(self):
1033 pass
1034 #----------------------------------------------------------------
1035 - def _refresh_from_existing(self):
1036 1037 self._LBL_info.SetLabel('ID: #%s' % ( 1038 self.data.ID 1039 # FIXME: add 'deleted' status 1040 )) 1041 if self.data['dob'] is None: 1042 val = '' 1043 else: 1044 val = gmDateTime.pydt_strftime ( 1045 self.data['dob'], 1046 format = '%Y-%m-%d', 1047 accuracy = gmDateTime.acc_minutes 1048 ) 1049 self._PRW_dob.SetText(value = val, data = self.data['dob']) 1050 self._CHBOX_estimated_dob.SetValue(self.data['dob_is_estimated']) 1051 if self.data['tob'] is None: 1052 self._TCTRL_tob.SetValue('') 1053 else: 1054 self._TCTRL_tob.SetValue(self.data['tob'].strftime('%H:%M')) 1055 if self.data['deceased'] is None: 1056 val = '' 1057 else: 1058 val = gmDateTime.pydt_strftime ( 1059 self.data['deceased'], 1060 format = '%Y-%m-%d %H:%M', 1061 accuracy = gmDateTime.acc_minutes 1062 ) 1063 self._PRW_dod.SetText(value = val, data = self.data['deceased']) 1064 self._PRW_gender.SetData(self.data['gender']) 1065 #self._PRW_ethnicity.SetValue() 1066 self._PRW_title.SetText(gmTools.coalesce(self.data['title'], '')) 1067 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
1068 #----------------------------------------------------------------
1070 pass
1071 #------------------------------------------------------------ 1072 from Gnumed.wxGladeWidgets import wxgPersonNameEAPnl 1073
1074 -class cPersonNameEAPnl(wxgPersonNameEAPnl.wxgPersonNameEAPnl, gmEditArea.cGenericEditAreaMixin):
1075 """An edit area for editing/creating names of people. 1076 1077 Does NOT act on/listen to the current patient. 1078 """
1079 - def __init__(self, *args, **kwargs):
1080 1081 try: 1082 data = kwargs['name'] 1083 identity = gmPerson.cPerson(aPK_obj = data['pk_identity']) 1084 del kwargs['name'] 1085 except KeyError: 1086 data = None 1087 identity = kwargs['identity'] 1088 del kwargs['identity'] 1089 1090 wxgPersonNameEAPnl.wxgPersonNameEAPnl.__init__(self, *args, **kwargs) 1091 gmEditArea.cGenericEditAreaMixin.__init__(self) 1092 1093 self.__identity = identity 1094 1095 self.mode = 'new' 1096 self.data = data 1097 if data is not None: 1098 self.mode = 'edit'
1099 1100 #self.__init_ui() 1101 #---------------------------------------------------------------- 1102 # def __init_ui(self): 1103 # # adjust phrasewheels etc 1104 #---------------------------------------------------------------- 1105 # generic Edit Area mixin API 1106 #----------------------------------------------------------------
1107 - def _valid_for_save(self):
1108 validity = True 1109 1110 if self._PRW_lastname.GetValue().strip() == '': 1111 validity = False 1112 self._PRW_lastname.display_as_valid(False) 1113 self._PRW_lastname.SetFocus() 1114 else: 1115 self._PRW_lastname.display_as_valid(True) 1116 1117 if self._PRW_firstname.GetValue().strip() == '': 1118 validity = False 1119 self._PRW_firstname.display_as_valid(False) 1120 self._PRW_firstname.SetFocus() 1121 else: 1122 self._PRW_firstname.display_as_valid(True) 1123 1124 return validity
1125 #----------------------------------------------------------------
1126 - def _save_as_new(self):
1127 1128 first = self._PRW_firstname.GetValue().strip() 1129 last = self._PRW_lastname.GetValue().strip() 1130 active = self._CHBOX_active.GetValue() 1131 1132 try: 1133 data = self.__identity.add_name(first, last, active) 1134 except gmPG2.dbapi.IntegrityError as exc: 1135 _log.exception('cannot save new name') 1136 gmGuiHelpers.gm_show_error ( 1137 aTitle = _('Adding name'), 1138 aMessage = _( 1139 'Cannot add this name to the patient !\n' 1140 '\n' 1141 ' %s' 1142 ) % exc.pgerror 1143 ) 1144 return False 1145 1146 old_nick = self.__identity['active_name']['preferred'] 1147 new_nick = gmTools.none_if(self._PRW_nick.GetValue().strip(), '') 1148 if active: 1149 data['preferred'] = gmTools.coalesce(new_nick, old_nick) 1150 else: 1151 data['preferred'] = new_nick 1152 data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '') 1153 data.save() 1154 1155 self.data = data 1156 return True
1157 #----------------------------------------------------------------
1158 - def _save_as_update(self):
1159 """The knack here is that we can only update a few fields. 1160 1161 Otherwise we need to clone the name and update that. 1162 """ 1163 first = self._PRW_firstname.GetValue().strip() 1164 last = self._PRW_lastname.GetValue().strip() 1165 active = self._CHBOX_active.GetValue() 1166 1167 current_name = self.data['firstnames'].strip() + self.data['lastnames'].strip() 1168 new_name = first + last 1169 1170 # editable fields only ? 1171 if new_name == current_name: 1172 self.data['active_name'] = self._CHBOX_active.GetValue() 1173 self.data['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), '') 1174 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '') 1175 self.data.save() 1176 # else clone name and update that 1177 else: 1178 try: 1179 name = self.__identity.add_name(first, last, active) 1180 except gmPG2.dbapi.IntegrityError as exc: 1181 _log.exception('cannot clone name when editing existing name') 1182 gmGuiHelpers.gm_show_error ( 1183 aTitle = _('Editing name'), 1184 aMessage = _( 1185 'Cannot clone a copy of this name !\n' 1186 '\n' 1187 ' %s' 1188 ) % exc.pgerror 1189 ) 1190 return False 1191 name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), '') 1192 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '') 1193 name.save() 1194 self.data = name 1195 1196 return True
1197 #----------------------------------------------------------------
1198 - def _refresh_as_new(self):
1199 self._PRW_firstname.SetText(value = '', data = None) 1200 self._PRW_lastname.SetText(value = '', data = None) 1201 self._PRW_nick.SetText(value = '', data = None) 1202 self._TCTRL_comment.SetValue('') 1203 self._CHBOX_active.SetValue(False) 1204 1205 self._PRW_firstname.SetFocus()
1206 #----------------------------------------------------------------
1208 self._refresh_as_new() 1209 self._PRW_firstname.SetText(value = '', data = None) 1210 self._PRW_nick.SetText(gmTools.coalesce(self.data['preferred'], '')) 1211 1212 self._PRW_lastname.SetFocus()
1213 #----------------------------------------------------------------
1214 - def _refresh_from_existing(self):
1215 self._PRW_firstname.SetText(self.data['firstnames']) 1216 self._PRW_lastname.SetText(self.data['lastnames']) 1217 self._PRW_nick.SetText(gmTools.coalesce(self.data['preferred'], '')) 1218 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], '')) 1219 self._CHBOX_active.SetValue(self.data['active_name']) 1220 1221 self._TCTRL_comment.SetFocus()
1222 #------------------------------------------------------------ 1223 # list manager 1224 #------------------------------------------------------------
1225 -class cPersonNamesManagerPnl(gmListWidgets.cGenericListManagerPnl):
1226 """A list for managing a person's names. 1227 1228 Does NOT act on/listen to the current patient. 1229 """
1230 - def __init__(self, *args, **kwargs):
1231 1232 try: 1233 self.__identity = kwargs['identity'] 1234 del kwargs['identity'] 1235 except KeyError: 1236 self.__identity = None 1237 1238 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1239 1240 self.refresh_callback = self.refresh 1241 self.new_callback = self._add_name 1242 self.edit_callback = self._edit_name 1243 self.delete_callback = self._del_name 1244 1245 self.__init_ui() 1246 self.refresh()
1247 #-------------------------------------------------------- 1248 # external API 1249 #--------------------------------------------------------
1250 - def refresh(self, *args, **kwargs):
1251 if self.__identity is None: 1252 self._LCTRL_items.set_string_items() 1253 return 1254 1255 names = self.__identity.get_names() 1256 self._LCTRL_items.set_string_items ( 1257 items = [ [ 1258 gmTools.bool2str(n['active_name'], 'X', ''), 1259 n['lastnames'], 1260 n['firstnames'], 1261 gmTools.coalesce(n['preferred'], ''), 1262 gmTools.coalesce(n['comment'], '') 1263 ] for n in names ] 1264 ) 1265 self._LCTRL_items.set_column_widths() 1266 self._LCTRL_items.set_data(data = names)
1267 #-------------------------------------------------------- 1268 # internal helpers 1269 #--------------------------------------------------------
1270 - def __init_ui(self):
1271 self._LCTRL_items.set_columns(columns = [ 1272 _('Active'), 1273 _('Lastname'), 1274 _('Firstname(s)'), 1275 _('Preferred Name'), 1276 _('Comment') 1277 ])
1278 #--------------------------------------------------------
1279 - def _add_name(self):
1280 #ea = cPersonNameEAPnl(self, -1, name = self.__identity.get_active_name()) 1281 ea = cPersonNameEAPnl(self, -1, identity = self.__identity) 1282 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1283 dlg.SetTitle(_('Adding new name')) 1284 if dlg.ShowModal() == wx.ID_OK: 1285 dlg.DestroyLater() 1286 return True 1287 dlg.DestroyLater() 1288 return False
1289 #--------------------------------------------------------
1290 - def _edit_name(self, name):
1291 ea = cPersonNameEAPnl(self, -1, name = name) 1292 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1293 dlg.SetTitle(_('Editing name')) 1294 if dlg.ShowModal() == wx.ID_OK: 1295 dlg.DestroyLater() 1296 return True 1297 dlg.DestroyLater() 1298 return False
1299 #--------------------------------------------------------
1300 - def _del_name(self, name):
1301 1302 if len(self.__identity.get_names()) == 1: 1303 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete the only name of a person.'), beep = True) 1304 return False 1305 1306 if name['active_name']: 1307 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete the active name of a person.'), beep = True) 1308 return False 1309 1310 go_ahead = gmGuiHelpers.gm_show_question ( 1311 _( 'It is often advisable to keep old names around and\n' 1312 'just create a new "currently active" name.\n' 1313 '\n' 1314 'This allows finding the patient by both the old\n' 1315 'and the new name (think before/after marriage).\n' 1316 '\n' 1317 'Do you still want to really delete\n' 1318 "this name from the patient ?" 1319 ), 1320 _('Deleting name') 1321 ) 1322 if not go_ahead: 1323 return False 1324 1325 self.__identity.delete_name(name = name) 1326 return True
1327 #-------------------------------------------------------- 1328 # properties 1329 #--------------------------------------------------------
1330 - def _get_identity(self):
1331 return self.__identity
1332
1333 - def _set_identity(self, identity):
1334 self.__identity = identity 1335 self.refresh()
1336 1337 identity = property(_get_identity, _set_identity)
1338 1339 #------------------------------------------------------------
1340 -class cPersonIDsManagerPnl(gmListWidgets.cGenericListManagerPnl):
1341 """A list for managing a person's external IDs. 1342 1343 Does NOT act on/listen to the current patient. 1344 """
1345 - def __init__(self, *args, **kwargs):
1346 1347 try: 1348 self.__identity = kwargs['identity'] 1349 del kwargs['identity'] 1350 except KeyError: 1351 self.__identity = None 1352 1353 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1354 1355 self.refresh_callback = self.refresh 1356 self.new_callback = self._add_id 1357 self.edit_callback = self._edit_id 1358 self.delete_callback = self._del_id 1359 1360 self.__init_ui() 1361 self.refresh()
1362 #-------------------------------------------------------- 1363 # external API 1364 #--------------------------------------------------------
1365 - def refresh(self, *args, **kwargs):
1366 if self.__identity is None: 1367 self._LCTRL_items.set_string_items() 1368 return 1369 1370 ids = self.__identity.get_external_ids() 1371 self._LCTRL_items.set_string_items ( 1372 items = [ [ 1373 i['name'], 1374 i['value'], 1375 gmTools.coalesce(i['issuer'], ''), 1376 gmTools.coalesce(i['comment'], '') 1377 ] for i in ids 1378 ] 1379 ) 1380 self._LCTRL_items.set_column_widths() 1381 self._LCTRL_items.set_data(data = ids)
1382 #-------------------------------------------------------- 1383 # internal helpers 1384 #--------------------------------------------------------
1385 - def __init_ui(self):
1386 self._LCTRL_items.set_columns(columns = [ 1387 _('ID type'), 1388 _('Value'), 1389 _('Issuer'), 1390 _('Comment') 1391 ])
1392 #--------------------------------------------------------
1393 - def _add_id(self):
1394 ea = cExternalIDEditAreaPnl(self, -1) 1395 ea.id_holder = self.__identity 1396 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea) 1397 dlg.SetTitle(_('Adding new external ID')) 1398 if dlg.ShowModal() == wx.ID_OK: 1399 dlg.DestroyLater() 1400 return True 1401 dlg.DestroyLater() 1402 return False
1403 #--------------------------------------------------------
1404 - def _edit_id(self, ext_id):
1405 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id) 1406 ea.id_holder = self.__identity 1407 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1408 dlg.SetTitle(_('Editing external ID')) 1409 if dlg.ShowModal() == wx.ID_OK: 1410 dlg.DestroyLater() 1411 return True 1412 dlg.DestroyLater() 1413 return False
1414 #--------------------------------------------------------
1415 - def _del_id(self, ext_id):
1416 go_ahead = gmGuiHelpers.gm_show_question ( 1417 _( 'Do you really want to delete this\n' 1418 'external ID from the patient ?'), 1419 _('Deleting external ID') 1420 ) 1421 if not go_ahead: 1422 return False 1423 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id']) 1424 return True
1425 #-------------------------------------------------------- 1426 # properties 1427 #--------------------------------------------------------
1428 - def _get_identity(self):
1429 return self.__identity
1430
1431 - def _set_identity(self, identity):
1432 self.__identity = identity 1433 self.refresh()
1434 1435 identity = property(_get_identity, _set_identity)
1436 #------------------------------------------------------------ 1437 # integrated panels 1438 #------------------------------------------------------------ 1439 from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl 1440
1441 -class cPersonIdentityManagerPnl(wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl):
1442 """A panel for editing identity data for a person. 1443 1444 - provides access to: 1445 - identity EA 1446 - name list manager 1447 - external IDs list manager 1448 1449 Does NOT act on/listen to the current patient. 1450 """
1451 - def __init__(self, *args, **kwargs):
1452 1453 wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl.__init__(self, *args, **kwargs) 1454 1455 self.__identity = None 1456 self.refresh()
1457 #-------------------------------------------------------- 1458 # external API 1459 #--------------------------------------------------------
1460 - def refresh(self):
1461 self._PNL_names.identity = self.__identity 1462 self._PNL_ids.identity = self.__identity 1463 # this is an Edit Area: 1464 self._PNL_identity.mode = 'new' 1465 self._PNL_identity.data = self.__identity 1466 if self.__identity is not None: 1467 self._PNL_identity.mode = 'edit' 1468 self._PNL_identity._refresh_from_existing()
1469 #-------------------------------------------------------- 1470 # properties 1471 #--------------------------------------------------------
1472 - def _get_identity(self):
1473 return self.__identity
1474
1475 - def _set_identity(self, identity):
1476 self.__identity = identity 1477 self.refresh()
1478 1479 identity = property(_get_identity, _set_identity) 1480 #-------------------------------------------------------- 1481 # event handlers 1482 #--------------------------------------------------------
1484 if not self._PNL_identity.save(): 1485 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save identity. Incomplete information.'), beep = True)
1486 #self._PNL_identity.refresh() 1487 #--------------------------------------------------------
1488 - def _on_reload_identity_button_pressed(self, event):
1489 self._PNL_identity.refresh()
1490 1491 #============================================================ 1492 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl 1493
1494 -class cPersonSocialNetworkManagerPnl(wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl):
1495 - def __init__(self, *args, **kwargs):
1496 1497 wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl.__init__(self, *args, **kwargs) 1498 1499 self.__identity = None 1500 self._PRW_provider.selection_only = False 1501 self.refresh()
1502 #-------------------------------------------------------- 1503 # external API 1504 #--------------------------------------------------------
1505 - def refresh(self):
1506 1507 tt = _('Link another person in this database as the emergency contact:\n\nEnter person name part or identifier and hit <enter>.') 1508 1509 if self.__identity is None: 1510 self._TCTRL_er_contact.SetValue('') 1511 self._TCTRL_person.person = None 1512 self._TCTRL_person.SetToolTip(tt) 1513 1514 self._PRW_provider.SetText(value = '', data = None) 1515 return 1516 1517 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], '')) 1518 if self.__identity['pk_emergency_contact'] is not None: 1519 ident = gmPerson.cPerson(aPK_obj = self.__identity['pk_emergency_contact']) 1520 self._TCTRL_person.person = ident 1521 tt = '%s\n\n%s\n\n%s' % ( 1522 tt, 1523 ident['description_gender'], 1524 '\n'.join([ 1525 '%s: %s%s' % ( 1526 c['l10n_comm_type'], 1527 c['url'], 1528 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), '', '') 1529 ) 1530 for c in ident.get_comm_channels() 1531 ]) 1532 ) 1533 else: 1534 self._TCTRL_person.person = None 1535 1536 self._TCTRL_person.SetToolTip(tt) 1537 1538 if self.__identity['pk_primary_provider'] is None: 1539 self._PRW_provider.SetText(value = '', data = None) 1540 else: 1541 self._PRW_provider.SetData(data = self.__identity['pk_primary_provider']) 1542 1543 self._PNL_external_care.identity = self.__identity
1544 #-------------------------------------------------------- 1545 # properties 1546 #--------------------------------------------------------
1547 - def _get_identity(self):
1548 return self.__identity
1549
1550 - def _set_identity(self, identity):
1551 self.__identity = identity 1552 self.refresh()
1553 1554 identity = property(_get_identity, _set_identity) 1555 #-------------------------------------------------------- 1556 # event handlers 1557 #--------------------------------------------------------
1558 - def _on_save_button_pressed(self, event):
1559 if self.__identity is not None: 1560 self.__identity['emergency_contact'] = self._TCTRL_er_contact.GetValue().strip() 1561 if self._TCTRL_person.person is not None: 1562 self.__identity['pk_emergency_contact'] = self._TCTRL_person.person.ID 1563 if self._PRW_provider.GetValue().strip == '': 1564 self.__identity['pk_primary_provider'] = None 1565 else: 1566 self.__identity['pk_primary_provider'] = self._PRW_provider.GetData() 1567 1568 self.__identity.save() 1569 gmDispatcher.send(signal = 'statustext', msg = _('Emergency data and primary provider saved.'), beep = False) 1570 1571 event.Skip()
1572 #--------------------------------------------------------
1573 - def _on_reload_button_pressed(self, event):
1574 self.refresh()
1575 #--------------------------------------------------------
1576 - def _on_remove_contact_button_pressed(self, event):
1577 event.Skip() 1578 1579 if self.__identity is None: 1580 return 1581 1582 self._TCTRL_person.person = None 1583 1584 self.__identity['pk_emergency_contact'] = None 1585 self.__identity.save()
1586 #--------------------------------------------------------
1587 - def _on_button_activate_contact_pressed(self, event):
1588 ident = self._TCTRL_person.person 1589 if ident is not None: 1590 from Gnumed.wxpython.gmPatSearchWidgets import set_active_patient 1591 wx.CallAfter(set_active_patient, patient = ident) 1592 1593 event.Skip()
1594 1595 #============================================================ 1596 # patient demographics editing classes 1597 #============================================================
1598 -class cPersonDemographicsEditorNb(wx.Notebook):
1599 """Notebook displaying demographics editing pages: 1600 1601 - Identity (as per Jim/Rogerio 12/2011) 1602 - Contacts (addresses, phone numbers, etc) 1603 - Social network (significant others, GP, etc) 1604 1605 Does NOT act on/listen to the current patient. 1606 """ 1607 #--------------------------------------------------------
1608 - def __init__(self, parent, id):
1609 1610 wx.Notebook.__init__ ( 1611 self, 1612 parent = parent, 1613 id = id, 1614 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER, 1615 name = self.__class__.__name__ 1616 ) 1617 _log.debug('created wx.Notebook: %s with ID %s', self.__class__.__name__, self.Id) 1618 1619 self.__identity = None 1620 self.__do_layout() 1621 self.SetSelection(0)
1622 #-------------------------------------------------------- 1623 # public API 1624 #--------------------------------------------------------
1625 - def refresh(self):
1626 """Populate fields in pages with data from model.""" 1627 for page_idx in range(self.GetPageCount()): 1628 page = self.GetPage(page_idx) 1629 page.identity = self.__identity 1630 1631 return True
1632 #-------------------------------------------------------- 1633 # internal API 1634 #--------------------------------------------------------
1635 - def __do_layout(self):
1636 """Build patient edition notebook pages.""" 1637 1638 # identity page 1639 new_page = cPersonIdentityManagerPnl(self, -1) 1640 new_page.identity = self.__identity 1641 self.AddPage ( 1642 page = new_page, 1643 text = _('Identity'), 1644 select = False 1645 ) 1646 1647 # contacts page 1648 new_page = gmPersonContactWidgets.cPersonContactsManagerPnl(self, -1) 1649 new_page.identity = self.__identity 1650 self.AddPage ( 1651 page = new_page, 1652 text = _('Contacts'), 1653 select = True 1654 ) 1655 1656 # social network page 1657 new_page = cPersonSocialNetworkManagerPnl(self, -1) 1658 new_page.identity = self.__identity 1659 self.AddPage ( 1660 page = new_page, 1661 text = _('Social network'), 1662 select = False 1663 )
1664 #-------------------------------------------------------- 1665 # properties 1666 #--------------------------------------------------------
1667 - def _get_identity(self):
1668 return self.__identity
1669
1670 - def _set_identity(self, identity):
1671 self.__identity = identity
1672 1673 identity = property(_get_identity, _set_identity)
1674 1675 #============================================================ 1676 # old occupation widgets 1677 #============================================================ 1678 # FIXME: support multiple occupations 1679 # FIXME: redo with wxGlade 1680
1681 -class cPatOccupationsPanel(wx.Panel):
1682 """Page containing patient occupations edition fields. 1683 """
1684 - def __init__(self, parent, id, ident=None):
1685 """ 1686 Creates a new instance of BasicPatDetailsPage 1687 @param parent - The parent widget 1688 @type parent - A wx.Window instance 1689 @param id - The widget id 1690 @type id - An integer 1691 """ 1692 wx.Panel.__init__(self, parent, id) 1693 self.__ident = ident 1694 self.__do_layout()
1695 #--------------------------------------------------------
1696 - def __do_layout(self):
1697 PNL_form = wx.Panel(self, -1) 1698 # occupation 1699 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 1700 self.PRW_occupation = cOccupationPhraseWheel(PNL_form, -1) 1701 self.PRW_occupation.SetToolTip(_("primary occupation of the patient")) 1702 # known since 1703 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated')) 1704 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY) 1705 1706 # layout input widgets 1707 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4) 1708 SZR_input.AddGrowableCol(1) 1709 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 1710 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 1711 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED) 1712 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND) 1713 PNL_form.SetSizerAndFit(SZR_input) 1714 1715 # layout page 1716 SZR_main = wx.BoxSizer(wx.VERTICAL) 1717 SZR_main.Add(PNL_form, 1, wx.EXPAND) 1718 self.SetSizer(SZR_main)
1719 #--------------------------------------------------------
1720 - def set_identity(self, identity):
1721 return self.refresh(identity=identity)
1722 #--------------------------------------------------------
1723 - def refresh(self, identity=None):
1724 if identity is not None: 1725 self.__ident = identity 1726 jobs = self.__ident.get_occupations() 1727 if len(jobs) > 0: 1728 self.PRW_occupation.SetText(jobs[0]['l10n_occupation']) 1729 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y')) 1730 return True
1731 #--------------------------------------------------------
1732 - def save(self):
1733 if self.PRW_occupation.IsModified(): 1734 new_job = self.PRW_occupation.GetValue().strip() 1735 jobs = self.__ident.get_occupations() 1736 for job in jobs: 1737 if job['l10n_occupation'] == new_job: 1738 continue 1739 self.__ident.unlink_occupation(occupation = job['l10n_occupation']) 1740 self.__ident.link_occupation(occupation = new_job) 1741 return True
1742 1743 #============================================================
1744 -class cNotebookedPatEditionPanel(wx.Panel, gmRegetMixin.cRegetOnPaintMixin):
1745 """Patient demographics plugin for main notebook. 1746 1747 Hosts another notebook with pages for Identity, Contacts, etc. 1748 1749 Acts on/listens to the currently active patient. 1750 """ 1751 #--------------------------------------------------------
1752 - def __init__(self, parent, id):
1753 wx.Panel.__init__ (self, parent = parent, id = id, style = wx.NO_BORDER) 1754 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1755 self.__do_layout() 1756 self.__register_interests()
1757 #-------------------------------------------------------- 1758 # public API 1759 #-------------------------------------------------------- 1760 #-------------------------------------------------------- 1761 # internal helpers 1762 #--------------------------------------------------------
1763 - def __do_layout(self):
1764 """Arrange widgets.""" 1765 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1) 1766 1767 szr_main = wx.BoxSizer(wx.VERTICAL) 1768 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND) 1769 self.SetSizerAndFit(szr_main)
1770 #-------------------------------------------------------- 1771 # event handling 1772 #--------------------------------------------------------
1773 - def __register_interests(self):
1774 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection) 1775 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1776 #--------------------------------------------------------
1778 self.__patient_notebook.identity = None 1779 self.__patient_notebook.refresh()
1780 #--------------------------------------------------------
1781 - def _on_post_patient_selection(self):
1782 self._schedule_data_reget()
1783 #-------------------------------------------------------- 1784 # reget mixin API 1785 #--------------------------------------------------------
1786 - def _populate_with_data(self):
1787 """Populate fields in pages with data from model.""" 1788 pat = gmPerson.gmCurrentPatient() 1789 if pat.connected: 1790 self.__patient_notebook.identity = pat 1791 else: 1792 self.__patient_notebook.identity = None 1793 self.__patient_notebook.refresh() 1794 return True
1795 1796 #============================================================ 1797 #============================================================ 1798 if __name__ == "__main__": 1799 1800 #--------------------------------------------------------
1801 - def test_organizer_pnl():
1802 app = wx.PyWidgetTester(size = (600, 400)) 1803 app.SetWidget(cKOrganizerSchedulePnl) 1804 app.MainLoop()
1805 #--------------------------------------------------------
1806 - def test_person_names_pnl():
1807 app = wx.PyWidgetTester(size = (600, 400)) 1808 widget = cPersonNamesManagerPnl(app.frame, -1) 1809 widget.identity = activate_patient() 1810 app.frame.Show(True) 1811 app.MainLoop()
1812 #--------------------------------------------------------
1813 - def test_person_ids_pnl():
1814 app = wx.PyWidgetTester(size = (600, 400)) 1815 widget = cPersonIDsManagerPnl(app.frame, -1) 1816 widget.identity = activate_patient() 1817 app.frame.Show(True) 1818 app.MainLoop()
1819 #--------------------------------------------------------
1820 - def test_pat_ids_pnl():
1821 app = wx.PyWidgetTester(size = (600, 400)) 1822 widget = cPersonIdentityManagerPnl(app.frame, -1) 1823 widget.identity = activate_patient() 1824 app.frame.Show(True) 1825 app.MainLoop()
1826 #--------------------------------------------------------
1827 - def test_name_ea_pnl():
1828 app = wx.PyWidgetTester(size = (600, 400)) 1829 app.SetWidget(cPersonNameEAPnl, name = activate_patient().get_active_name()) 1830 app.MainLoop()
1831 #--------------------------------------------------------
1832 - def test_cPersonDemographicsEditorNb():
1833 app = wx.PyWidgetTester(size = (600, 400)) 1834 widget = cPersonDemographicsEditorNb(app.frame, -1) 1835 widget.identity = activate_patient() 1836 widget.refresh() 1837 app.frame.Show(True) 1838 app.MainLoop()
1839 #--------------------------------------------------------
1840 - def activate_patient():
1841 patient = gmPersonSearch.ask_for_patient() 1842 if patient is None: 1843 print("No patient. Exiting gracefully...") 1844 sys.exit(0) 1845 from Gnumed.wxpython.gmPatSearchWidgets import set_active_patient 1846 set_active_patient(patient = patient) 1847 return patient
1848 #-------------------------------------------------------- 1849 if len(sys.argv) > 1 and sys.argv[1] == 'test': 1850 1851 gmI18N.activate_locale() 1852 gmI18N.install_domain(domain='gnumed') 1853 gmPG2.get_connection() 1854 1855 # app = wx.PyWidgetTester(size = (400, 300)) 1856 # app.SetWidget(cNotebookedPatEditionPanel, -1) 1857 # app.frame.Show(True) 1858 # app.MainLoop() 1859 1860 # phrasewheels 1861 # test_organizer_pnl() 1862 1863 # identity related widgets 1864 #test_person_names_pnl() 1865 test_person_ids_pnl() 1866 #test_pat_ids_pnl() 1867 #test_name_ea_pnl() 1868 1869 #test_cPersonDemographicsEditorNb() 1870 1871 #============================================================ 1872