Package Gnumed :: Package wxpython :: Module gmDemographicsWidgets
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmDemographicsWidgets

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