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