| Home | Trees | Indices | Help |
|
|---|
|
|
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 (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 #------------------------------------------------------------
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 #------------------------------------------------------------
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
149
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 #----------------------------------------------------------------
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 #----------------------------------------------------------------
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 #----------------------------------------------------------------
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 #----------------------------------------------------------------
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 #----------------------------------------------------------------
248 #----------------------------------------------------------------
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 #----------------------------------------------------------------
275
276 #============================================================
277 from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl
278
280
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
367 #============================================================
368 #============================================================
370
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 #--------------------------------------------------------
390 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
438 self.reload_appointments()
439 #============================================================
440 # occupation related widgets / functions
441 #============================================================
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 #------------------------------------------------------------
479
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 #============================================================
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 #------------------------------------------------------------
542
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 #------------------------------------------------------------
557
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 #------------------------------------------------------------
575
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 #------------------------------------------------------------
596
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 #------------------------------------------------------------
610 """Let user select a gender."""
611
612 _gender_map = None
613
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 #------------------------------------------------------------
640
642 query = u"""
643 select distinct pk, (name || coalesce(' (%s ' || issuer || ')', '')) as label
644 from dem.enum_ext_id_types
645 where name %%(fragment_condition)s
646 order by label limit 25""" % _('issued by')
647 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
648 mp.setThresholds(1, 3, 5)
649 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
650 self.SetToolTipString(_("Enter or select a type for the external ID."))
651 self.matcher = mp
652 #------------------------------------------------------------
654
656 query = u"""
657 select distinct issuer, issuer
658 from dem.enum_ext_id_types
659 where issuer %(fragment_condition)s
660 order by issuer limit 25"""
661 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
662 mp.setThresholds(1, 3, 5)
663 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
664 self.SetToolTipString(_("Type or select an ID issuer."))
665 self.capitalisation_mode = gmTools.CAPS_FIRST
666 self.matcher = mp
667 #------------------------------------------------------------
668 # edit areas
669 #------------------------------------------------------------
670 from Gnumed.wxGladeWidgets import wxgExternalIDEditAreaPnl
671
673 """An edit area for editing/creating external IDs.
674
675 Does NOT act on/listen to the current patient.
676 """
678
679 try:
680 self.ext_id = kwargs['external_id']
681 del kwargs['external_id']
682 except:
683 self.ext_id = None
684
685 wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl.__init__(self, *args, **kwargs)
686
687 self.identity = None
688
689 self.__register_events()
690
691 self.refresh()
692 #--------------------------------------------------------
693 # external API
694 #--------------------------------------------------------
696 if ext_id is not None:
697 self.ext_id = ext_id
698
699 if self.ext_id is not None:
700 self._PRW_type.SetText(value = self.ext_id['name'], data = self.ext_id['pk_type'])
701 self._TCTRL_value.SetValue(self.ext_id['value'])
702 self._PRW_issuer.SetText(self.ext_id['issuer'])
703 self._TCTRL_comment.SetValue(gmTools.coalesce(self.ext_id['comment'], u''))
704 # FIXME: clear fields
705 # else:
706 # pass
707 #--------------------------------------------------------
709
710 if not self.__valid_for_save():
711 return False
712
713 # strip out " (issued by ...)" added by phrasewheel
714 type = regex.split(' \(%s .+\)$' % _('issued by'), self._PRW_type.GetValue().strip(), 1)[0]
715
716 # add new external ID
717 if self.ext_id is None:
718 self.identity.add_external_id (
719 type_name = type,
720 value = self._TCTRL_value.GetValue().strip(),
721 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''),
722 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
723 )
724 # edit old external ID
725 else:
726 self.identity.update_external_id (
727 pk_id = self.ext_id['pk_id'],
728 type = type,
729 value = self._TCTRL_value.GetValue().strip(),
730 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''),
731 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
732 )
733
734 return True
735 #--------------------------------------------------------
736 # internal helpers
737 #--------------------------------------------------------
739 self._PRW_type.add_callback_on_lose_focus(self._on_type_set)
740 #--------------------------------------------------------
742 """Set the issuer according to the selected type.
743
744 Matches are fetched from existing records in backend.
745 """
746 pk_curr_type = self._PRW_type.GetData()
747 if pk_curr_type is None:
748 return True
749 rows, idx = gmPG2.run_ro_queries(queries = [{
750 'cmd': u"select issuer from dem.enum_ext_id_types where pk = %s",
751 'args': [pk_curr_type]
752 }])
753 if len(rows) == 0:
754 return True
755 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0])
756 return True
757 #--------------------------------------------------------
759
760 no_errors = True
761
762 # do not test .GetData() because adding external IDs
763 # will create types if necessary
764 # if self._PRW_type.GetData() is None:
765 if self._PRW_type.GetValue().strip() == u'':
766 self._PRW_type.SetBackgroundColour('pink')
767 self._PRW_type.SetFocus()
768 self._PRW_type.Refresh()
769 else:
770 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
771 self._PRW_type.Refresh()
772
773 if self._TCTRL_value.GetValue().strip() == u'':
774 self._TCTRL_value.SetBackgroundColour('pink')
775 self._TCTRL_value.SetFocus()
776 self._TCTRL_value.Refresh()
777 no_errors = False
778 else:
779 self._TCTRL_value.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
780 self._TCTRL_value.Refresh()
781
782 return no_errors
783 #------------------------------------------------------------
784 from Gnumed.wxGladeWidgets import wxgIdentityEAPnl
785
787 """An edit area for editing/creating title/gender/dob/dod etc."""
788
790
791 try:
792 data = kwargs['identity']
793 del kwargs['identity']
794 except KeyError:
795 data = None
796
797 wxgIdentityEAPnl.wxgIdentityEAPnl.__init__(self, *args, **kwargs)
798 gmEditArea.cGenericEditAreaMixin.__init__(self)
799
800 self.mode = 'new'
801 self.data = data
802 if data is not None:
803 self.mode = 'edit'
804
805 # self.__init_ui()
806 #----------------------------------------------------------------
807 # def __init_ui(self):
808 # # adjust phrasewheels etc
809 #----------------------------------------------------------------
810 # generic Edit Area mixin API
811 #----------------------------------------------------------------
813
814 has_error = False
815
816 if self._PRW_gender.GetData() is None:
817 self._PRW_gender.SetFocus()
818 has_error = True
819
820 if not self._PRW_dob.is_valid_timestamp():
821 val = self._PRW_dob.GetValue().strip()
822 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val)
823 self._PRW_dob.SetBackgroundColour('pink')
824 self._PRW_dob.Refresh()
825 self._PRW_dob.SetFocus()
826 has_error = True
827 else:
828 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
829 self._PRW_dob.Refresh()
830
831 if not self._PRW_dod.is_valid_timestamp(allow_empty = True):
832 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.'))
833 self._PRW_dod.SetFocus()
834 has_error = True
835
836 return (has_error is False)
837 #----------------------------------------------------------------
841 #----------------------------------------------------------------
843
844 self.data['gender'] = self._PRW_gender.GetData()
845
846 if self._PRW_dob.GetValue().strip() == u'':
847 self.data['dob'] = None
848 else:
849 self.data['dob'] = self._PRW_dob.GetData().get_pydt()
850
851 self.data['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'')
852 self.data['deceased'] = self._PRW_dod.GetData()
853 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
854
855 self.data.save()
856 return True
857 #----------------------------------------------------------------
860 #----------------------------------------------------------------
862
863 self._LBL_info.SetLabel(u'ID: #%s' % (
864 self.data.ID
865 # FIXME: add 'deleted' status
866 ))
867 self._PRW_dob.SetText (
868 value = self.data.get_formatted_dob(format = '%Y-%m-%d %H:%M', encoding = gmI18N.get_encoding()),
869 data = self.data['dob']
870 )
871 if self.data['deceased'] is None:
872 val = u''
873 else:
874 val = gmDateTime.pydt_strftime (
875 self.data['deceased'],
876 format = '%Y-%m-%d %H:%M',
877 accuracy = gmDateTime.acc_minutes
878 )
879 self._PRW_dod.SetText(value = val, data = self.data['deceased'])
880 self._PRW_gender.SetData(self.data['gender'])
881 #self._PRW_ethnicity.SetValue()
882 self._PRW_title.SetText(gmTools.coalesce(self.data['title'], u''))
883 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
884 #----------------------------------------------------------------
887 #------------------------------------------------------------
888 from Gnumed.wxGladeWidgets import wxgNameGenderDOBEditAreaPnl
889
891 """An edit area for editing/creating name/gender/dob.
892
893 Does NOT act on/listen to the current patient.
894 """
896
897 self.__name = kwargs['name']
898 del kwargs['name']
899 self.__identity = gmPerson.cIdentity(aPK_obj = self.__name['pk_identity'])
900
901 wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl.__init__(self, *args, **kwargs)
902
903 self.__register_interests()
904 self.refresh()
905 #--------------------------------------------------------
906 # external API
907 #--------------------------------------------------------
909 if self.__name is None:
910 return
911
912 self._PRW_title.SetText(gmTools.coalesce(self.__name['title'], u''))
913 self._PRW_firstname.SetText(self.__name['firstnames'])
914 self._PRW_lastname.SetText(self.__name['lastnames'])
915 self._PRW_nick.SetText(gmTools.coalesce(self.__name['preferred'], u''))
916 self._PRW_dob.SetText (
917 value = self.__identity.get_formatted_dob(format = '%Y-%m-%d %H:%M', encoding = gmI18N.get_encoding()),
918 data = self.__identity['dob']
919 )
920 self._PRW_gender.SetData(self.__name['gender'])
921 self._CHBOX_active.SetValue(self.__name['active_name'])
922 if self.__identity['deceased'] is None:
923 val = u''
924 else:
925 val = gmDateTime.pydt_strftime (
926 self.__identity['deceased'],
927 format = '%Y-%m-%d %H:%M',
928 accuracy = gmDateTime.acc_minutes
929 )
930 self._PRW_dod.SetText(value = val, data = self.__identity['deceased'])
931 self._TCTRL_comment.SetValue(gmTools.coalesce(self.__name['comment'], u''))
932 # FIXME: clear fields
933 # else:
934 # pass
935 #--------------------------------------------------------
937
938 if not self.__valid_for_save():
939 return False
940
941 self.__identity['gender'] = self._PRW_gender.GetData()
942 if self._PRW_dob.GetValue().strip() == u'':
943 self.__identity['dob'] = None
944 else:
945 self.__identity['dob'] = self._PRW_dob.GetData().get_pydt()
946 self.__identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'')
947 self.__identity['deceased'] = self._PRW_dod.GetData()
948 self.__identity.save_payload()
949
950 active = self._CHBOX_active.GetValue()
951 first = self._PRW_firstname.GetValue().strip()
952 last = self._PRW_lastname.GetValue().strip()
953 old_nick = self.__name['preferred']
954
955 # is it a new name ?
956 old_name = self.__name['firstnames'] + self.__name['lastnames']
957 if (first + last) != old_name:
958 self.__name = self.__identity.add_name(first, last, active)
959
960 self.__name['active_name'] = active
961 self.__name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'')
962 self.__name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
963
964 self.__name.save_payload()
965
966 return True
967 #--------------------------------------------------------
968 # event handling
969 #--------------------------------------------------------
971 self._PRW_firstname.add_callback_on_lose_focus(self._on_name_set)
972 #--------------------------------------------------------
974 """Set the gender according to entered firstname.
975
976 Matches are fetched from existing records in backend.
977 """
978 firstname = self._PRW_firstname.GetValue().strip()
979 if firstname == u'':
980 return True
981 rows, idx = gmPG2.run_ro_queries(queries = [{
982 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
983 'args': [firstname]
984 }])
985 if len(rows) == 0:
986 return True
987 wx.CallAfter(self._PRW_gender.SetData, rows[0][0])
988 return True
989 #--------------------------------------------------------
990 # internal helpers
991 #--------------------------------------------------------
993
994 has_error = False
995
996 if self._PRW_gender.GetData() is None:
997 self._PRW_gender.display_as_valid(False)
998 self._PRW_gender.SetFocus()
999 has_error = True
1000 else:
1001 self._PRW_gender.display_as_valid(True)
1002
1003 if not self._PRW_dob.is_valid_timestamp():
1004 val = self._PRW_dob.GetValue().strip()
1005 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val)
1006 self._PRW_dob.display_as_valid(False)
1007 self._PRW_dob.SetFocus()
1008 has_error = True
1009 else:
1010 self._PRW_dob.display_as_valid(True)
1011
1012 if not self._PRW_dod.is_valid_timestamp():
1013 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.'))
1014 self._PRW_dod.display_as_valid(False)
1015 self._PRW_dod.SetFocus()
1016 has_error = True
1017 else:
1018 self._PRW_dod.display_as_valid(True)
1019
1020 if self._PRW_lastname.GetValue().strip() == u'':
1021 self._PRW_lastname.display_as_valid(False)
1022 self._PRW_lastname.SetFocus()
1023 has_error = True
1024 else:
1025 self._PRW_lastname.display_as_valid(True)
1026
1027 if self._PRW_firstname.GetValue().strip() == u'':
1028 self._PRW_firstname.display_as_valid(False)
1029 self._PRW_firstname.SetFocus()
1030 has_error = True
1031 else:
1032 self._PRW_firstname.display_as_valid(True)
1033
1034 return (has_error is False)
1035 #------------------------------------------------------------
1036 # list manager
1037 #------------------------------------------------------------
1039 """A list for managing a person's names.
1040
1041 Does NOT act on/listen to the current patient.
1042 """
1044
1045 try:
1046 self.__identity = kwargs['identity']
1047 del kwargs['identity']
1048 except KeyError:
1049 self.__identity = None
1050
1051 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs)
1052
1053 self.new_callback = self._add_name
1054 self.edit_callback = self._edit_name
1055 self.delete_callback = self._del_name
1056 self.refresh_callback = self.refresh
1057
1058 self.__init_ui()
1059 self.refresh()
1060 #--------------------------------------------------------
1061 # external API
1062 #--------------------------------------------------------
1064 if self.__identity is None:
1065 self._LCTRL_items.set_string_items()
1066 return
1067
1068 names = self.__identity.get_names()
1069 self._LCTRL_items.set_string_items (
1070 items = [ [
1071 gmTools.bool2str(n['active_name'], 'X', ''),
1072 n['lastnames'],
1073 n['firstnames'],
1074 gmTools.coalesce(n['preferred'], u''),
1075 gmTools.coalesce(n['comment'], u'')
1076 ] for n in names ]
1077 )
1078 self._LCTRL_items.set_column_widths()
1079 self._LCTRL_items.set_data(data = names)
1080 #--------------------------------------------------------
1081 # internal helpers
1082 #--------------------------------------------------------
1084 self._LCTRL_items.set_columns(columns = [
1085 _('Active'),
1086 _('Lastname'),
1087 _('Firstname(s)'),
1088 _('Preferred Name'),
1089 _('Comment')
1090 ])
1091 self._BTN_edit.SetLabel(_('Clone and &edit'))
1092 #--------------------------------------------------------
1094 ea = cNameGenderDOBEditAreaPnl(self, -1, name = self.__identity.get_active_name())
1095 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
1096 dlg.SetTitle(_('Adding new name'))
1097 if dlg.ShowModal() == wx.ID_OK:
1098 dlg.Destroy()
1099 return True
1100 dlg.Destroy()
1101 return False
1102 #--------------------------------------------------------
1104 ea = cNameGenderDOBEditAreaPnl(self, -1, name = name)
1105 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
1106 dlg.SetTitle(_('Cloning name'))
1107 if dlg.ShowModal() == wx.ID_OK:
1108 dlg.Destroy()
1109 return True
1110 dlg.Destroy()
1111 return False
1112 #--------------------------------------------------------
1114
1115 if len(self.__identity.get_names()) == 1:
1116 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True)
1117 return False
1118
1119 go_ahead = gmGuiHelpers.gm_show_question (
1120 _( 'It is often advisable to keep old names around and\n'
1121 'just create a new "currently active" name.\n'
1122 '\n'
1123 'This allows finding the patient by both the old\n'
1124 'and the new name (think before/after marriage).\n'
1125 '\n'
1126 'Do you still want to really delete\n'
1127 "this name from the patient ?"
1128 ),
1129 _('Deleting name')
1130 )
1131 if not go_ahead:
1132 return False
1133
1134 self.__identity.delete_name(name = name)
1135 return True
1136 #--------------------------------------------------------
1137 # properties
1138 #--------------------------------------------------------
1141
1145
1146 identity = property(_get_identity, _set_identity)
1147 #------------------------------------------------------------
1149 """A list for managing a person's external IDs.
1150
1151 Does NOT act on/listen to the current patient.
1152 """
1154
1155 try:
1156 self.__identity = kwargs['identity']
1157 del kwargs['identity']
1158 except KeyError:
1159 self.__identity = None
1160
1161 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs)
1162
1163 self.new_callback = self._add_id
1164 self.edit_callback = self._edit_id
1165 self.delete_callback = self._del_id
1166 self.refresh_callback = self.refresh
1167
1168 self.__init_ui()
1169 self.refresh()
1170 #--------------------------------------------------------
1171 # external API
1172 #--------------------------------------------------------
1174 if self.__identity is None:
1175 self._LCTRL_items.set_string_items()
1176 return
1177
1178 ids = self.__identity.get_external_ids()
1179 self._LCTRL_items.set_string_items (
1180 items = [ [
1181 i['name'],
1182 i['value'],
1183 gmTools.coalesce(i['issuer'], u''),
1184 gmTools.coalesce(i['comment'], u'')
1185 ] for i in ids
1186 ]
1187 )
1188 self._LCTRL_items.set_column_widths()
1189 self._LCTRL_items.set_data(data = ids)
1190 #--------------------------------------------------------
1191 # internal helpers
1192 #--------------------------------------------------------
1194 self._LCTRL_items.set_columns(columns = [
1195 _('ID type'),
1196 _('Value'),
1197 _('Issuer'),
1198 _('Comment')
1199 ])
1200 #--------------------------------------------------------
1202 ea = cExternalIDEditAreaPnl(self, -1)
1203 ea.identity = self.__identity
1204 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
1205 dlg.SetTitle(_('Adding new external ID'))
1206 if dlg.ShowModal() == wx.ID_OK:
1207 dlg.Destroy()
1208 return True
1209 dlg.Destroy()
1210 return False
1211 #--------------------------------------------------------
1213 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id)
1214 ea.identity = self.__identity
1215 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
1216 dlg.SetTitle(_('Editing external ID'))
1217 if dlg.ShowModal() == wx.ID_OK:
1218 dlg.Destroy()
1219 return True
1220 dlg.Destroy()
1221 return False
1222 #--------------------------------------------------------
1224 go_ahead = gmGuiHelpers.gm_show_question (
1225 _( 'Do you really want to delete this\n'
1226 'external ID from the patient ?'),
1227 _('Deleting external ID')
1228 )
1229 if not go_ahead:
1230 return False
1231 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
1232 return True
1233 #--------------------------------------------------------
1234 # properties
1235 #--------------------------------------------------------
1238
1242
1243 identity = property(_get_identity, _set_identity)
1244 #------------------------------------------------------------
1245 # integrated panels
1246 #------------------------------------------------------------
1247 from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl
1248
1250 """A panel for editing identity data for a person.
1251
1252 - provides access to:
1253 - name
1254 - external IDs
1255
1256 Does NOT act on/listen to the current patient.
1257 """
1259
1260 wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl.__init__(self, *args, **kwargs)
1261
1262 self.__identity = None
1263 self.refresh()
1264 #--------------------------------------------------------
1265 # external API
1266 #--------------------------------------------------------
1268 self._PNL_names.identity = self.__identity
1269 self._PNL_ids.identity = self.__identity
1270 # this is an Edit Area:
1271 self._PNL_identity.mode = 'new'
1272 self._PNL_identity.data = self.__identity
1273 if self.__identity is not None:
1274 self._PNL_identity.mode = 'edit'
1275 #--------------------------------------------------------
1276 # properties
1277 #--------------------------------------------------------
1280
1284
1285 identity = property(_get_identity, _set_identity)
1286 #--------------------------------------------------------
1287 # event handlers
1288 #--------------------------------------------------------
1292 #--------------------------------------------------------
1295
1296 #============================================================
1297 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl
1298
1299 -class cPersonSocialNetworkManagerPnl(wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl):
1301
1302 wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl.__init__(self, *args, **kwargs)
1303
1304 self.__identity = None
1305 self._PRW_provider.selection_only = False
1306 self.refresh()
1307 #--------------------------------------------------------
1308 # external API
1309 #--------------------------------------------------------
1311
1312 tt = _("Link another person in this database as the emergency contact.")
1313
1314 if self.__identity is None:
1315 self._TCTRL_er_contact.SetValue(u'')
1316 self._TCTRL_person.person = None
1317 self._TCTRL_person.SetToolTipString(tt)
1318
1319 self._PRW_provider.SetText(value = u'', data = None)
1320 return
1321
1322 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], u''))
1323 if self.__identity['pk_emergency_contact'] is not None:
1324 ident = gmPerson.cIdentity(aPK_obj = self.__identity['pk_emergency_contact'])
1325 self._TCTRL_person.person = ident
1326 tt = u'%s\n\n%s\n\n%s' % (
1327 tt,
1328 ident['description_gender'],
1329 u'\n'.join([
1330 u'%s: %s%s' % (
1331 c['l10n_comm_type'],
1332 c['url'],
1333 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), u'', u'')
1334 )
1335 for c in ident.get_comm_channels()
1336 ])
1337 )
1338 else:
1339 self._TCTRL_person.person = None
1340
1341 self._TCTRL_person.SetToolTipString(tt)
1342
1343 if self.__identity['pk_primary_provider'] is None:
1344 self._PRW_provider.SetText(value = u'', data = None)
1345 else:
1346 self._PRW_provider.SetData(data = self.__identity['pk_primary_provider'])
1347 #--------------------------------------------------------
1348 # properties
1349 #--------------------------------------------------------
1352
1356
1357 identity = property(_get_identity, _set_identity)
1358 #--------------------------------------------------------
1359 # event handlers
1360 #--------------------------------------------------------
1374 #--------------------------------------------------------
1385 #--------------------------------------------------------
1393 #============================================================
1394 # new-patient widgets
1395 #============================================================
1397
1398 dbcfg = gmCfg.cCfgSQL()
1399
1400 def_region = dbcfg.get2 (
1401 option = u'person.create.default_region',
1402 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1403 bias = u'user'
1404 )
1405 def_country = None
1406
1407 if def_region is None:
1408 def_country = dbcfg.get2 (
1409 option = u'person.create.default_country',
1410 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1411 bias = u'user'
1412 )
1413 else:
1414 countries = gmDemographicRecord.get_country_for_region(region = def_region)
1415 if len(countries) == 1:
1416 def_country = countries[0]['l10n_country']
1417
1418 if parent is None:
1419 parent = wx.GetApp().GetTopWindow()
1420
1421 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region)
1422 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
1423 dlg.SetTitle(_('Adding new person'))
1424 ea._PRW_lastname.SetFocus()
1425 result = dlg.ShowModal()
1426 pat = ea.data
1427 dlg.Destroy()
1428
1429 if result != wx.ID_OK:
1430 return False
1431
1432 _log.debug('created new person [%s]', pat.ID)
1433
1434 if activate:
1435 from Gnumed.wxpython import gmPatSearchWidgets
1436 gmPatSearchWidgets.set_active_patient(patient = pat)
1437
1438 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin')
1439
1440 return True
1441 #============================================================
1442 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl
1443
1444 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
1445
1447
1448 try:
1449 self.default_region = kwargs['region']
1450 del kwargs['region']
1451 except KeyError:
1452 self.default_region = None
1453
1454 try:
1455 self.default_country = kwargs['country']
1456 del kwargs['country']
1457 except KeyError:
1458 self.default_country = None
1459
1460 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs)
1461 gmEditArea.cGenericEditAreaMixin.__init__(self)
1462
1463 self.mode = 'new'
1464 self.data = None
1465 self._address = None
1466
1467 self.__init_ui()
1468 self.__register_interests()
1469 #----------------------------------------------------------------
1470 # internal helpers
1471 #----------------------------------------------------------------
1473 self._PRW_lastname.final_regex = '.+'
1474 self._PRW_firstnames.final_regex = '.+'
1475 self._PRW_address_searcher.selection_only = False
1476
1477 # only if we would support None on selection_only's:
1478 # self._PRW_external_id_type.selection_only = True
1479
1480 if self.default_country is not None:
1481 self._PRW_country.SetText(value = self.default_country)
1482
1483 if self.default_region is not None:
1484 self._PRW_region.SetText(value = self.default_region)
1485 #----------------------------------------------------------------
1487
1488 adr = self._PRW_address_searcher.get_address()
1489 if adr is None:
1490 return True
1491
1492 if ctrl.GetValue().strip() != adr[field]:
1493 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None)
1494 return True
1495
1496 return False
1497 #----------------------------------------------------------------
1499 adr = self._PRW_address_searcher.get_address()
1500 if adr is None:
1501 return True
1502
1503 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode'])
1504
1505 self._PRW_street.SetText(value = adr['street'], data = adr['street'])
1506 self._PRW_street.set_context(context = u'zip', val = adr['postcode'])
1507
1508 self._TCTRL_number.SetValue(adr['number'])
1509
1510 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb'])
1511 self._PRW_urb.set_context(context = u'zip', val = adr['postcode'])
1512
1513 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state'])
1514 self._PRW_region.set_context(context = u'zip', val = adr['postcode'])
1515
1516 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country'])
1517 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
1518 #----------------------------------------------------------------
1520 error = False
1521
1522 # name fields
1523 if self._PRW_lastname.GetValue().strip() == u'':
1524 error = True
1525 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
1526 self._PRW_lastname.display_as_valid(False)
1527 else:
1528 self._PRW_lastname.display_as_valid(True)
1529
1530 if self._PRW_firstnames.GetValue().strip() == '':
1531 error = True
1532 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
1533 self._PRW_firstnames.display_as_valid(False)
1534 else:
1535 self._PRW_firstnames.display_as_valid(True)
1536
1537 # gender
1538 if self._PRW_gender.GetData() is None:
1539 error = True
1540 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
1541 self._PRW_gender.display_as_valid(False)
1542 else:
1543 self._PRW_gender.display_as_valid(True)
1544
1545 # dob validation
1546 # test this last so we can check empty field as last barrier against save
1547 # 1) valid timestamp ?
1548 if self._PRW_dob.is_valid_timestamp(allow_empty = False): # properly colors the field
1549 dob = self._PRW_dob.date
1550 # but year also usable ?
1551 msg = None
1552 if dob.year < 1900:
1553 msg = _(
1554 'DOB: %s\n'
1555 '\n'
1556 'While this is a valid point in time Python does\n'
1557 'not know how to deal with it.\n'
1558 '\n'
1559 'We suggest using January 1st 1901 instead and adding\n'
1560 'the true date of birth to the patient comment.\n'
1561 '\n'
1562 'Sorry for the inconvenience %s'
1563 ) % (dob, gmTools.u_frowning_face)
1564 elif dob > gmDateTime.pydt_now_here():
1565 msg = _(
1566 'DOB: %s\n'
1567 '\n'
1568 'Date of birth in the future !'
1569 ) % dob
1570
1571 if msg is not None:
1572 error = True
1573 gmGuiHelpers.gm_show_error (
1574 msg,
1575 _('Registering new person')
1576 )
1577 self._PRW_dob.display_as_valid(False)
1578 self._PRW_dob.SetFocus()
1579 # 2) invalid timestamp ?
1580 else:
1581 # is this the only error ?
1582 if error is False:
1583 # is it empty rather than invalid ?
1584 if self._PRW_dob.GetValue().strip() == u'':
1585 # maybe even allow empty DOB ?
1586 allow_empty_dob = gmGuiHelpers.gm_show_question (
1587 _(
1588 'Are you sure you want to register this person\n'
1589 'without a valid date of birth ?\n'
1590 '\n'
1591 'This can be useful for temporary staff members\n'
1592 'but will provoke nag screens if this person\n'
1593 'becomes a patient.\n'
1594 ),
1595 _('Registering new person')
1596 )
1597 if allow_empty_dob:
1598 self._PRW_dob.display_as_valid(True)
1599 else:
1600 error = True
1601 self._PRW_dob.SetFocus()
1602
1603 # TOB validation if non-empty
1604 # if self._TCTRL_tob.GetValue().strip() != u'':
1605
1606 return (not error)
1607 #----------------------------------------------------------------
1609
1610 # existing address ? if so set other fields
1611 if self._PRW_address_searcher.GetData() is not None:
1612 wx.CallAfter(self.__set_fields_from_address_searcher)
1613 return True
1614
1615 # must either all contain something or none of them
1616 fields_to_fill = (
1617 self._TCTRL_number,
1618 self._PRW_zip,
1619 self._PRW_street,
1620 self._PRW_urb,
1621 self._PRW_region,
1622 self._PRW_country
1623 )
1624 no_of_filled_fields = 0
1625
1626 for field in fields_to_fill:
1627 if field.GetValue().strip() != u'':
1628 no_of_filled_fields += 1
1629 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
1630 field.Refresh()
1631
1632 # empty address ?
1633 if no_of_filled_fields == 0:
1634 if empty_address_is_valid:
1635 return True
1636 else:
1637 return None
1638
1639 # incompletely filled address ?
1640 if no_of_filled_fields != len(fields_to_fill):
1641 for field in fields_to_fill:
1642 if field.GetValue().strip() == u'':
1643 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
1644 field.SetFocus()
1645 field.Refresh()
1646 msg = _('To properly create an address, all the related fields must be filled in.')
1647 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
1648 return False
1649
1650 # fields which must contain a selected item
1651 # FIXME: they must also contain an *acceptable combination* which
1652 # FIXME: can only be tested against the database itself ...
1653 strict_fields = (
1654 self._PRW_region,
1655 self._PRW_country
1656 )
1657 error = False
1658 for field in strict_fields:
1659 if field.GetData() is None:
1660 error = True
1661 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
1662 field.SetFocus()
1663 else:
1664 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
1665 field.Refresh()
1666
1667 if error:
1668 msg = _('This field must contain an item selected from the dropdown list.')
1669 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
1670 return False
1671
1672 return True
1673 #----------------------------------------------------------------
1675
1676 # identity
1677 self._PRW_firstnames.add_callback_on_lose_focus(self._on_leaving_firstname)
1678
1679 # address
1680 self._PRW_address_searcher.add_callback_on_lose_focus(self._on_leaving_adress_searcher)
1681
1682 # invalidate address searcher when any field edited
1683 self._PRW_street.add_callback_on_lose_focus(self._invalidate_address_searcher)
1684 wx.EVT_KILL_FOCUS(self._TCTRL_number, self._invalidate_address_searcher)
1685 self._PRW_urb.add_callback_on_lose_focus(self._invalidate_address_searcher)
1686 self._PRW_region.add_callback_on_lose_focus(self._invalidate_address_searcher)
1687
1688 self._PRW_zip.add_callback_on_lose_focus(self._on_leaving_zip)
1689 self._PRW_country.add_callback_on_lose_focus(self._on_leaving_country)
1690 #----------------------------------------------------------------
1691 # event handlers
1692 #----------------------------------------------------------------
1694 """Set the gender according to entered firstname.
1695
1696 Matches are fetched from existing records in backend.
1697 """
1698 # only set if not already set so as to not
1699 # overwrite a change by the user
1700 if self._PRW_gender.GetData() is not None:
1701 return True
1702
1703 firstname = self._PRW_firstnames.GetValue().strip()
1704 if firstname == u'':
1705 return True
1706
1707 gender = gmPerson.map_firstnames2gender(firstnames = firstname)
1708 if gender is None:
1709 return True
1710
1711 wx.CallAfter(self._PRW_gender.SetData, gender)
1712 return True
1713 #----------------------------------------------------------------
1715 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode')
1716
1717 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'')
1718 self._PRW_street.set_context(context = u'zip', val = zip_code)
1719 self._PRW_urb.set_context(context = u'zip', val = zip_code)
1720 self._PRW_region.set_context(context = u'zip', val = zip_code)
1721 self._PRW_country.set_context(context = u'zip', val = zip_code)
1722
1723 return True
1724 #----------------------------------------------------------------
1726 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country')
1727
1728 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'')
1729 self._PRW_region.set_context(context = u'country', val = country)
1730
1731 return True
1732 #----------------------------------------------------------------
1734 mapping = [
1735 (self._PRW_street, 'street'),
1736 (self._TCTRL_number, 'number'),
1737 (self._PRW_urb, 'urb'),
1738 (self._PRW_region, 'l10n_state')
1739 ]
1740
1741 # loop through fields and invalidate address searcher if different
1742 for ctrl, field in mapping:
1743 if self.__perhaps_invalidate_address_searcher(ctrl, field):
1744 return True
1745
1746 return True
1747 #----------------------------------------------------------------
1749 adr = self._PRW_address_searcher.get_address()
1750 if adr is None:
1751 return True
1752
1753 wx.CallAfter(self.__set_fields_from_address_searcher)
1754 return True
1755 #----------------------------------------------------------------
1756 # generic Edit Area mixin API
1757 #----------------------------------------------------------------
1759 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
1760 #----------------------------------------------------------------
1762
1763 # identity
1764 new_identity = gmPerson.create_identity (
1765 gender = self._PRW_gender.GetData(),
1766 dob = self._PRW_dob.GetData(),
1767 lastnames = self._PRW_lastname.GetValue().strip(),
1768 firstnames = self._PRW_firstnames.GetValue().strip()
1769 )
1770 _log.debug('identity created: %s' % new_identity)
1771
1772 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip())
1773 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u''))
1774 #TOB
1775 new_identity.save()
1776
1777 name = new_identity.get_active_name()
1778 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1779 name.save()
1780
1781 # address
1782 is_valid = self.__address_valid_for_save(empty_address_is_valid = False)
1783 if is_valid is True:
1784 # because we currently only check for non-emptiness
1785 # we must still deal with database errors
1786 try:
1787 new_identity.link_address (
1788 number = self._TCTRL_number.GetValue().strip(),
1789 street = self._PRW_street.GetValue().strip(),
1790 postcode = self._PRW_zip.GetValue().strip(),
1791 urb = self._PRW_urb.GetValue().strip(),
1792 state = self._PRW_region.GetData(),
1793 country = self._PRW_country.GetData()
1794 )
1795 except gmPG2.dbapi.InternalError:
1796 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip())
1797 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip())
1798 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip())
1799 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip())
1800 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip())
1801 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip())
1802 _log.exception('cannot link address')
1803 gmGuiHelpers.gm_show_error (
1804 aTitle = _('Saving address'),
1805 aMessage = _(
1806 'Cannot save this address.\n'
1807 '\n'
1808 'You will have to add it via the Demographics plugin.\n'
1809 )
1810 )
1811 elif is_valid is False:
1812 gmGuiHelpers.gm_show_error (
1813 aTitle = _('Saving address'),
1814 aMessage = _(
1815 'Address not saved.\n'
1816 '\n'
1817 'You will have to add it via the Demographics plugin.\n'
1818 )
1819 )
1820 # else it is None which means empty address which we ignore
1821
1822 # phone
1823 new_identity.link_comm_channel (
1824 comm_medium = u'homephone',
1825 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''),
1826 is_confidential = False
1827 )
1828
1829 # external ID
1830 pk_type = self._PRW_external_id_type.GetData()
1831 id_value = self._TCTRL_external_id_value.GetValue().strip()
1832 if (pk_type is not None) and (id_value != u''):
1833 new_identity.add_external_id(value = id_value, pk_type = pk_type)
1834
1835 # occupation
1836 new_identity.link_occupation (
1837 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'')
1838 )
1839
1840 self.data = new_identity
1841 return True
1842 #----------------------------------------------------------------
1845 #----------------------------------------------------------------
1849 #----------------------------------------------------------------
1852 #----------------------------------------------------------------
1855
1856 #============================================================
1857 # patient demographics editing classes
1858 #============================================================
1860 """Notebook displaying demographics editing pages:
1861
1862 - Identity
1863 - Contacts (addresses, phone numbers, etc)
1864 - Social network (significant others, GP, etc)
1865
1866 Does NOT act on/listen to the current patient.
1867 """
1868 #--------------------------------------------------------
1870
1871 wx.Notebook.__init__ (
1872 self,
1873 parent = parent,
1874 id = id,
1875 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
1876 name = self.__class__.__name__
1877 )
1878
1879 self.__identity = None
1880 self.__do_layout()
1881 self.SetSelection(0)
1882 #--------------------------------------------------------
1883 # public API
1884 #--------------------------------------------------------
1886 """Populate fields in pages with data from model."""
1887 for page_idx in range(self.GetPageCount()):
1888 page = self.GetPage(page_idx)
1889 page.identity = self.__identity
1890
1891 return True
1892 #--------------------------------------------------------
1893 # internal API
1894 #--------------------------------------------------------
1896 """Build patient edition notebook pages."""
1897
1898 # contacts page
1899 new_page = gmPersonContactWidgets.cPersonContactsManagerPnl(self, -1)
1900 new_page.identity = self.__identity
1901 self.AddPage (
1902 page = new_page,
1903 text = _('Contacts'),
1904 select = True
1905 )
1906
1907 # identity page
1908 new_page = cPersonIdentityManagerPnl(self, -1)
1909 new_page.identity = self.__identity
1910 self.AddPage (
1911 page = new_page,
1912 text = _('Identity'),
1913 select = False
1914 )
1915
1916 # social network page
1917 new_page = cPersonSocialNetworkManagerPnl(self, -1)
1918 new_page.identity = self.__identity
1919 self.AddPage (
1920 page = new_page,
1921 text = _('Social network'),
1922 select = False
1923 )
1924 #--------------------------------------------------------
1925 # properties
1926 #--------------------------------------------------------
1929
1931 self.__identity = identity
1932
1933 identity = property(_get_identity, _set_identity)
1934 #============================================================
1935 # old occupation widgets
1936 #============================================================
1937 # FIXME: support multiple occupations
1938 # FIXME: redo with wxGlade
1939
1941 """Page containing patient occupations edition fields.
1942 """
1944 """
1945 Creates a new instance of BasicPatDetailsPage
1946 @param parent - The parent widget
1947 @type parent - A wx.Window instance
1948 @param id - The widget id
1949 @type id - An integer
1950 """
1951 wx.Panel.__init__(self, parent, id)
1952 self.__ident = ident
1953 self.__do_layout()
1954 #--------------------------------------------------------
1956 PNL_form = wx.Panel(self, -1)
1957 # occupation
1958 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
1959 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
1960 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient"))
1961 # known since
1962 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
1963 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
1964
1965 # layout input widgets
1966 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
1967 SZR_input.AddGrowableCol(1)
1968 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
1969 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
1970 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
1971 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
1972 PNL_form.SetSizerAndFit(SZR_input)
1973
1974 # layout page
1975 SZR_main = wx.BoxSizer(wx.VERTICAL)
1976 SZR_main.Add(PNL_form, 1, wx.EXPAND)
1977 self.SetSizer(SZR_main)
1978 #--------------------------------------------------------
1981 #--------------------------------------------------------
1983 if identity is not None:
1984 self.__ident = identity
1985 jobs = self.__ident.get_occupations()
1986 if len(jobs) > 0:
1987 self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
1988 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
1989 return True
1990 #--------------------------------------------------------
1992 if self.PRW_occupation.IsModified():
1993 new_job = self.PRW_occupation.GetValue().strip()
1994 jobs = self.__ident.get_occupations()
1995 for job in jobs:
1996 if job['l10n_occupation'] == new_job:
1997 continue
1998 self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
1999 self.__ident.link_occupation(occupation = new_job)
2000 return True
2001 #============================================================
2003 """Patient demographics plugin for main notebook.
2004
2005 Hosts another notebook with pages for Identity, Contacts, etc.
2006
2007 Acts on/listens to the currently active patient.
2008 """
2009 #--------------------------------------------------------
2011 wx.Panel.__init__ (self, parent = parent, id = id, style = wx.NO_BORDER)
2012 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
2013 self.__do_layout()
2014 self.__register_interests()
2015 #--------------------------------------------------------
2016 # public API
2017 #--------------------------------------------------------
2018 #--------------------------------------------------------
2019 # internal helpers
2020 #--------------------------------------------------------
2022 """Arrange widgets."""
2023 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
2024
2025 szr_main = wx.BoxSizer(wx.VERTICAL)
2026 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
2027 self.SetSizerAndFit(szr_main)
2028 #--------------------------------------------------------
2029 # event handling
2030 #--------------------------------------------------------
2032 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
2033 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
2034 #--------------------------------------------------------
2037 #--------------------------------------------------------
2040 #--------------------------------------------------------
2041 # reget mixin API
2042 #--------------------------------------------------------
2052
2053
2054 #============================================================
2055 #============================================================
2056 #============================================================
2057 #============================================================
2058 # outdated, delete soon:
2059 # new-patient wizard classes
2060 #============================================================
2062 """
2063 Wizard page for entering patient's basic demographic information
2064 """
2065
2066 form_fields = (
2067 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation',
2068 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment'
2069 )
2070
2072 """
2073 Creates a new instance of BasicPatDetailsPage
2074 @param parent - The parent widget
2075 @type parent - A wx.Window instance
2076 @param tile - The title of the page
2077 @type title - A StringType instance
2078 """
2079 wx.wizard.WizardPageSimple.__init__(self, parent) #, bitmap = gmGuiHelpers.gm_icon(_('oneperson'))
2080 self.__title = title
2081 self.__do_layout()
2082 self.__register_interests()
2083 #--------------------------------------------------------
2085 PNL_form = wx.Panel(self, -1)
2086
2087 # last name
2088 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name'))
2089 STT_lastname.SetForegroundColour('red')
2090 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1)
2091 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)'))
2092
2093 # first name
2094 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)'))
2095 STT_firstname.SetForegroundColour('red')
2096 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1)
2097 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name'))
2098
2099 # nickname
2100 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name'))
2101 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1)
2102
2103 # DOB
2104 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth'))
2105 STT_dob.SetForegroundColour('red')
2106 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1)
2107 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one"))
2108
2109 # gender
2110 STT_gender = wx.StaticText(PNL_form, -1, _('Gender'))
2111 STT_gender.SetForegroundColour('red')
2112 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1)
2113 self.PRW_gender.SetToolTipString(_("Required: gender of patient"))
2114
2115 # title
2116 STT_title = wx.StaticText(PNL_form, -1, _('Title'))
2117 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1)
2118
2119 # zip code
2120 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code'))
2121 STT_zip_code.SetForegroundColour('orange')
2122 self.PRW_zip_code = gmPersonContactWidgets.cZipcodePhraseWheel(parent = PNL_form, id = -1)
2123 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code"))
2124
2125 # street
2126 STT_street = wx.StaticText(PNL_form, -1, _('Street'))
2127 STT_street.SetForegroundColour('orange')
2128 self.PRW_street = gmPersonContactWidgets.cStreetPhraseWheel(parent = PNL_form, id = -1)
2129 self.PRW_street.SetToolTipString(_("primary/home address: name of street"))
2130
2131 # address number
2132 STT_address_number = wx.StaticText(PNL_form, -1, _('Number'))
2133 STT_address_number.SetForegroundColour('orange')
2134 self.TTC_address_number = wx.TextCtrl(PNL_form, -1)
2135 self.TTC_address_number.SetToolTipString(_("primary/home address: address number"))
2136
2137 # town
2138 STT_town = wx.StaticText(PNL_form, -1, _('Place'))
2139 STT_town.SetForegroundColour('orange')
2140 self.PRW_town = gmPersonContactWidgets.cUrbPhraseWheel(parent = PNL_form, id = -1)
2141 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/..."))
2142
2143 # state
2144 STT_state = wx.StaticText(PNL_form, -1, _('Region'))
2145 STT_state.SetForegroundColour('orange')
2146 self.PRW_state = gmPersonContactWidgets.cStateSelectionPhraseWheel(parent=PNL_form, id=-1)
2147 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/..."))
2148
2149 # country
2150 STT_country = wx.StaticText(PNL_form, -1, _('Country'))
2151 STT_country.SetForegroundColour('orange')
2152 self.PRW_country = gmPersonContactWidgets.cCountryPhraseWheel(parent = PNL_form, id = -1)
2153 self.PRW_country.SetToolTipString(_("primary/home address: country"))
2154
2155 # phone
2156 STT_phone = wx.StaticText(PNL_form, -1, _('Phone'))
2157 self.TTC_phone = wx.TextCtrl(PNL_form, -1)
2158 self.TTC_phone.SetToolTipString(_("phone number at home"))
2159
2160 # occupation
2161 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
2162 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
2163
2164 # comment
2165 STT_comment = wx.StaticText(PNL_form, -1, _('Comment'))
2166 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1)
2167 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.'))
2168
2169 # form main validator
2170 self.form_DTD = cFormDTD(fields = self.__class__.form_fields)
2171 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD))
2172
2173 # layout input widgets
2174 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4)
2175 SZR_input.AddGrowableCol(1)
2176 SZR_input.Add(STT_lastname, 0, wx.SHAPED)
2177 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND)
2178 SZR_input.Add(STT_firstname, 0, wx.SHAPED)
2179 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND)
2180 SZR_input.Add(STT_nick, 0, wx.SHAPED)
2181 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND)
2182 SZR_input.Add(STT_dob, 0, wx.SHAPED)
2183 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND)
2184 SZR_input.Add(STT_gender, 0, wx.SHAPED)
2185 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND)
2186 SZR_input.Add(STT_title, 0, wx.SHAPED)
2187 SZR_input.Add(self.PRW_title, 1, wx.EXPAND)
2188 SZR_input.Add(STT_zip_code, 0, wx.SHAPED)
2189 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND)
2190 SZR_input.Add(STT_street, 0, wx.SHAPED)
2191 SZR_input.Add(self.PRW_street, 1, wx.EXPAND)
2192 SZR_input.Add(STT_address_number, 0, wx.SHAPED)
2193 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND)
2194 SZR_input.Add(STT_town, 0, wx.SHAPED)
2195 SZR_input.Add(self.PRW_town, 1, wx.EXPAND)
2196 SZR_input.Add(STT_state, 0, wx.SHAPED)
2197 SZR_input.Add(self.PRW_state, 1, wx.EXPAND)
2198 SZR_input.Add(STT_country, 0, wx.SHAPED)
2199 SZR_input.Add(self.PRW_country, 1, wx.EXPAND)
2200 SZR_input.Add(STT_phone, 0, wx.SHAPED)
2201 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND)
2202 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
2203 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
2204 SZR_input.Add(STT_comment, 0, wx.SHAPED)
2205 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND)
2206
2207 PNL_form.SetSizerAndFit(SZR_input)
2208
2209 # layout page
2210 SZR_main = makePageTitle(self, self.__title)
2211 SZR_main.Add(PNL_form, 1, wx.EXPAND)
2212 #--------------------------------------------------------
2213 # event handling
2214 #--------------------------------------------------------
2216 self.PRW_firstname.add_callback_on_lose_focus(self.on_name_set)
2217 self.PRW_country.add_callback_on_selection(self.on_country_selected)
2218 self.PRW_zip_code.add_callback_on_lose_focus(self.on_zip_set)
2219 #--------------------------------------------------------
2221 """Set the states according to entered country."""
2222 self.PRW_state.set_context(context=u'country', val=data)
2223 return True
2224 #--------------------------------------------------------
2226 """Set the gender according to entered firstname.
2227
2228 Matches are fetched from existing records in backend.
2229 """
2230 firstname = self.PRW_firstname.GetValue().strip()
2231 rows, idx = gmPG2.run_ro_queries(queries = [{
2232 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
2233 'args': [firstname]
2234 }])
2235 if len(rows) == 0:
2236 return True
2237 wx.CallAfter(self.PRW_gender.SetData, rows[0][0])
2238 return True
2239 #--------------------------------------------------------
2241 """Set the street, town, state and country according to entered zip code."""
2242 zip_code = self.PRW_zip_code.GetValue().strip()
2243 self.PRW_street.set_context(context=u'zip', val=zip_code)
2244 self.PRW_town.set_context(context=u'zip', val=zip_code)
2245 self.PRW_state.set_context(context=u'zip', val=zip_code)
2246 self.PRW_country.set_context(context=u'zip', val=zip_code)
2247 return True
2248 #============================================================
2250 """
2251 Utility function to create the main sizer of a wizard's page.
2252
2253 @param wizPg The wizard page widget
2254 @type wizPg A wx.WizardPageSimple instance
2255 @param title The wizard page's descriptive title
2256 @type title A StringType instance
2257 """
2258 sizer = wx.BoxSizer(wx.VERTICAL)
2259 wizPg.SetSizer(sizer)
2260 title = wx.StaticText(wizPg, -1, title)
2261 title.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD))
2262 sizer.Add(title, 0, wx.ALIGN_CENTRE|wx.ALL, 2)
2263 sizer.Add(wx.StaticLine(wizPg, -1), 0, wx.EXPAND|wx.ALL, 2)
2264 return sizer
2265 #============================================================
2267 """
2268 Wizard to create a new patient.
2269
2270 TODO:
2271 - write pages for different "themes" of patient creation
2272 - make it configurable which pages are loaded
2273 - make available sets of pages that apply to a country
2274 - make loading of some pages depend upon values in earlier pages, eg
2275 when the patient is female and older than 13 include a page about
2276 "female" data (number of kids etc)
2277
2278 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable()
2279 """
2280 #--------------------------------------------------------
2281 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
2282 """
2283 Creates a new instance of NewPatientWizard
2284 @param parent - The parent widget
2285 @type parent - A wx.Window instance
2286 """
2287 id_wiz = wx.NewId()
2288 wx.wizard.Wizard.__init__(self, parent, id_wiz, title) #images.getWizTest1Bitmap()
2289 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY)
2290 self.__subtitle = subtitle
2291 self.__do_layout()
2292 #--------------------------------------------------------
2294 """Create new patient.
2295
2296 activate, too, if told to do so (and patient successfully created)
2297 """
2298 while True:
2299
2300 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details):
2301 return False
2302
2303 try:
2304 # retrieve DTD and create patient
2305 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD)
2306 except:
2307 _log.exception('cannot add new patient - missing identity fields')
2308 gmGuiHelpers.gm_show_error (
2309 _('Cannot create new patient.\n'
2310 'Missing parts of the identity.'
2311 ),
2312 _('Adding new patient')
2313 )
2314 continue
2315
2316 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2317
2318 try:
2319 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2320 except:
2321 _log.exception('cannot finalize new patient - missing address fields')
2322 gmGuiHelpers.gm_show_error (
2323 _('Cannot add address for the new patient.\n'
2324 'You must either enter all of the address fields or\n'
2325 'none at all. The relevant fields are marked in yellow.\n'
2326 '\n'
2327 'You will need to add the address details in the\n'
2328 'demographics module.'
2329 ),
2330 _('Adding new patient')
2331 )
2332 break
2333
2334 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2335
2336 break
2337
2338 if activate:
2339 from Gnumed.wxpython import gmPatSearchWidgets
2340 gmPatSearchWidgets.set_active_patient(patient = ident)
2341
2342 return ident
2343 #--------------------------------------------------------
2344 # internal helpers
2345 #--------------------------------------------------------
2347 """Arrange widgets.
2348 """
2349 # Create the wizard pages
2350 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle )
2351 self.FitToPage(self.basic_pat_details)
2352 #============================================================
2353 #============================================================
2355 """
2356 This validator is used to ensure that the user has entered all
2357 the required conditional values in the page (eg., to properly
2358 create an address, all the related fields must be filled).
2359 """
2360 #--------------------------------------------------------
2362 """
2363 Validator initialization.
2364 @param dtd The object containing the data model.
2365 @type dtd A cFormDTD instance
2366 """
2367 # initialize parent class
2368 wx.PyValidator.__init__(self)
2369 # validator's storage object
2370 self.form_DTD = dtd
2371 #--------------------------------------------------------
2373 """
2374 Standard cloner.
2375 Note that every validator must implement the Clone() method.
2376 """
2377 return cBasicPatDetailsPageValidator(dtd = self.form_DTD) # FIXME: probably need new instance of DTD ?
2378 #--------------------------------------------------------
2380 """
2381 Validate the contents of the given text control.
2382 """
2383 _pnl_form = self.GetWindow().GetParent()
2384
2385 error = False
2386
2387 # name fields
2388 if _pnl_form.PRW_lastname.GetValue().strip() == '':
2389 error = True
2390 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
2391 _pnl_form.PRW_lastname.SetBackgroundColour('pink')
2392 _pnl_form.PRW_lastname.Refresh()
2393 else:
2394 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2395 _pnl_form.PRW_lastname.Refresh()
2396
2397 if _pnl_form.PRW_firstname.GetValue().strip() == '':
2398 error = True
2399 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
2400 _pnl_form.PRW_firstname.SetBackgroundColour('pink')
2401 _pnl_form.PRW_firstname.Refresh()
2402 else:
2403 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2404 _pnl_form.PRW_firstname.Refresh()
2405
2406 # gender
2407 if _pnl_form.PRW_gender.GetData() is None:
2408 error = True
2409 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
2410 _pnl_form.PRW_gender.SetBackgroundColour('pink')
2411 _pnl_form.PRW_gender.Refresh()
2412 else:
2413 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2414 _pnl_form.PRW_gender.Refresh()
2415
2416 # dob validation
2417 if (
2418 (_pnl_form.PRW_dob.GetValue().strip() == u'')
2419 or (not _pnl_form.PRW_dob.is_valid_timestamp())
2420 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900)
2421 ):
2422 error = True
2423 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue()
2424 gmDispatcher.send(signal = 'statustext', msg = msg)
2425 _pnl_form.PRW_dob.SetBackgroundColour('pink')
2426 _pnl_form.PRW_dob.Refresh()
2427 else:
2428 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2429 _pnl_form.PRW_dob.Refresh()
2430
2431 # address
2432 is_any_field_filled = False
2433 address_fields = (
2434 _pnl_form.TTC_address_number,
2435 _pnl_form.PRW_zip_code,
2436 _pnl_form.PRW_street,
2437 _pnl_form.PRW_town
2438 )
2439 for field in address_fields:
2440 if field.GetValue().strip() == u'':
2441 if is_any_field_filled:
2442 error = True
2443 msg = _('To properly create an address, all the related fields must be filled in.')
2444 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2445 field.SetBackgroundColour('pink')
2446 field.SetFocus()
2447 field.Refresh()
2448 else:
2449 is_any_field_filled = True
2450 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2451 field.Refresh()
2452
2453 address_fields = (
2454 _pnl_form.PRW_state,
2455 _pnl_form.PRW_country
2456 )
2457 for field in address_fields:
2458 if field.GetData() is None:
2459 if is_any_field_filled:
2460 error = True
2461 msg = _('To properly create an address, all the related fields must be filled in.')
2462 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2463 field.SetBackgroundColour('pink')
2464 field.SetFocus()
2465 field.Refresh()
2466 else:
2467 is_any_field_filled = True
2468 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2469 field.Refresh()
2470
2471 return (not error)
2472 #--------------------------------------------------------
2474 """
2475 Transfer data from validator to window.
2476 The default implementation returns False, indicating that an error
2477 occurred. We simply return True, as we don't do any data transfer.
2478 """
2479 _pnl_form = self.GetWindow().GetParent()
2480 # fill in controls with values from self.form_DTD
2481 _pnl_form.PRW_gender.SetData(self.form_DTD['gender'])
2482 _pnl_form.PRW_dob.SetText(self.form_DTD['dob'])
2483 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames'])
2484 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames'])
2485 _pnl_form.PRW_title.SetText(self.form_DTD['title'])
2486 _pnl_form.PRW_nick.SetText(self.form_DTD['nick'])
2487 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation'])
2488 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number'])
2489 _pnl_form.PRW_street.SetText(self.form_DTD['street'])
2490 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code'])
2491 _pnl_form.PRW_town.SetText(self.form_DTD['town'])
2492 _pnl_form.PRW_state.SetData(self.form_DTD['state'])
2493 _pnl_form.PRW_country.SetData(self.form_DTD['country'])
2494 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone'])
2495 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment'])
2496 return True # Prevent wxDialog from complaining
2497 #--------------------------------------------------------
2499 """
2500 Transfer data from window to validator.
2501 The default implementation returns False, indicating that an error
2502 occurred. We simply return True, as we don't do any data transfer.
2503 """
2504 # FIXME: should be called automatically
2505 if not self.GetWindow().GetParent().Validate():
2506 return False
2507 try:
2508 _pnl_form = self.GetWindow().GetParent()
2509 # fill in self.form_DTD with values from controls
2510 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData()
2511 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData()
2512
2513 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue()
2514 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue()
2515 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue()
2516 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue()
2517
2518 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue()
2519
2520 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue()
2521 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue()
2522 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue()
2523 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue()
2524 self.form_DTD['state'] = _pnl_form.PRW_state.GetData()
2525 self.form_DTD['country'] = _pnl_form.PRW_country.GetData()
2526
2527 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue()
2528
2529 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue()
2530 except:
2531 return False
2532 return True
2533 #============================================================
2535 """
2536 Utility class to test the new patient wizard.
2537 """
2538 #--------------------------------------------------------
2540 """
2541 Create a new instance of TestPanel.
2542 @param parent The parent widget
2543 @type parent A wx.Window instance
2544 """
2545 wx.Panel.__init__(self, parent, id)
2546 wizard = cNewPatientWizard(self)
2547 print wizard.RunWizard()
2548 #============================================================
2549 if __name__ == "__main__":
2550
2551 #--------------------------------------------------------
2553 app = wx.PyWidgetTester(size = (600, 400))
2554 app.SetWidget(cKOrganizerSchedulePnl)
2555 app.MainLoop()
2556 #--------------------------------------------------------
2558 app = wx.PyWidgetTester(size = (600, 400))
2559 widget = cPersonNamesManagerPnl(app.frame, -1)
2560 widget.identity = activate_patient()
2561 app.frame.Show(True)
2562 app.MainLoop()
2563 #--------------------------------------------------------
2565 app = wx.PyWidgetTester(size = (600, 400))
2566 widget = cPersonIDsManagerPnl(app.frame, -1)
2567 widget.identity = activate_patient()
2568 app.frame.Show(True)
2569 app.MainLoop()
2570 #--------------------------------------------------------
2572 app = wx.PyWidgetTester(size = (600, 400))
2573 widget = cPersonIdentityManagerPnl(app.frame, -1)
2574 widget.identity = activate_patient()
2575 app.frame.Show(True)
2576 app.MainLoop()
2577 #--------------------------------------------------------
2579 app = wx.PyWidgetTester(size = (600, 400))
2580 app.SetWidget(cNameGenderDOBEditAreaPnl, name = activate_patient().get_active_name())
2581 app.MainLoop()
2582 #--------------------------------------------------------
2584 app = wx.PyWidgetTester(size = (600, 400))
2585 widget = cPersonContactsManagerPnl(app.frame, -1)
2586 widget.identity = activate_patient()
2587 app.frame.Show(True)
2588 app.MainLoop()
2589 #--------------------------------------------------------
2591 app = wx.PyWidgetTester(size = (600, 400))
2592 widget = cPersonDemographicsEditorNb(app.frame, -1)
2593 widget.identity = activate_patient()
2594 widget.refresh()
2595 app.frame.Show(True)
2596 app.MainLoop()
2597 #--------------------------------------------------------
2599 patient = gmPersonSearch.ask_for_patient()
2600 if patient is None:
2601 print "No patient. Exiting gracefully..."
2602 sys.exit(0)
2603 from Gnumed.wxpython import gmPatSearchWidgets
2604 gmPatSearchWidgets.set_active_patient(patient=patient)
2605 return patient
2606 #--------------------------------------------------------
2607 if len(sys.argv) > 1 and sys.argv[1] == 'test':
2608
2609 gmI18N.activate_locale()
2610 gmI18N.install_domain(domain='gnumed')
2611 gmPG2.get_connection()
2612
2613 # a = cFormDTD(fields = cBasicPatDetailsPage.form_fields)
2614
2615 # app = wx.PyWidgetTester(size = (400, 300))
2616 # app.SetWidget(cNotebookedPatEditionPanel, -1)
2617 # app.SetWidget(TestWizardPanel, -1)
2618 # app.frame.Show(True)
2619 # app.MainLoop()
2620
2621 # phrasewheels
2622 # test_zipcode_prw()
2623 # test_state_prw()
2624 # test_street_prw()
2625 # test_organizer_pnl()
2626 #test_address_type_prw()
2627 #test_suburb_prw()
2628 test_urb_prw()
2629 #test_address_prw()
2630
2631 # contacts related widgets
2632 #test_address_ea_pnl()
2633 #test_person_adrs_pnl()
2634 #test_person_comms_pnl()
2635 #test_pat_contacts_pnl()
2636
2637 # identity related widgets
2638 #test_person_names_pnl()
2639 #test_person_ids_pnl()
2640 #test_pat_ids_pnl()
2641 #test_name_ea_pnl()
2642
2643 #test_cPersonDemographicsEditorNb()
2644
2645 #============================================================
2646
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Jun 7 03:58:42 2011 | http://epydoc.sourceforge.net |