| 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, gmI18N, gmMatchProvider, gmPG2, gmTools, gmCfg
27 from Gnumed.pycommon import gmDateTime, gmShellAPI
28
29 from Gnumed.business import gmDemographicRecord
30 from Gnumed.business import gmPersonSearch
31 from Gnumed.business import gmSurgery
32 from Gnumed.business import gmPerson
33
34 from Gnumed.wxpython import gmPhraseWheel
35 from Gnumed.wxpython import gmRegetMixin
36 from Gnumed.wxpython import gmAuthWidgets
37 from Gnumed.wxpython import gmPersonContactWidgets
38 from Gnumed.wxpython import gmEditArea
39 from Gnumed.wxpython import gmListWidgets
40 from Gnumed.wxpython import gmDateTimeInput
41 from Gnumed.wxpython import gmDataMiningWidgets
42 from Gnumed.wxpython import gmGuiHelpers
43
44
45 # constant defs
46 _log = logging.getLogger('gm.ui')
47
48
49 try:
50 _('dummy-no-need-to-translate-but-make-epydoc-happy')
51 except NameError:
52 _ = lambda x:x
53
54 #============================================================
55 # image tags related widgets
56 #------------------------------------------------------------
58 if tag_image is not None:
59 if tag_image['is_in_use']:
60 gmGuiHelpers.gm_show_info (
61 aTitle = _('Editing tag'),
62 aMessage = _(
63 'Cannot edit the image tag\n'
64 '\n'
65 ' "%s"\n'
66 '\n'
67 'because it is currently in use.\n'
68 ) % tag_image['l10n_description']
69 )
70 return False
71
72 ea = cTagImageEAPnl(parent = parent, id = -1)
73 ea.data = tag_image
74 ea.mode = gmTools.coalesce(tag_image, 'new', 'edit')
75 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
76 dlg.SetTitle(gmTools.coalesce(tag_image, _('Adding new tag'), _('Editing tag')))
77 if dlg.ShowModal() == wx.ID_OK:
78 dlg.Destroy()
79 return True
80 dlg.Destroy()
81 return False
82 #------------------------------------------------------------
84
85 if parent is None:
86 parent = wx.GetApp().GetTopWindow()
87 #------------------------------------------------------------
88 def go_to_openclipart_org(tag_image):
89 webbrowser.open (
90 url = u'http://www.openclipart.org',
91 new = False,
92 autoraise = True
93 )
94 webbrowser.open (
95 url = u'http://www.google.com',
96 new = False,
97 autoraise = True
98 )
99 return True
100 #------------------------------------------------------------
101 def edit(tag_image=None):
102 return edit_tag_image(parent = parent, tag_image = tag_image, single_entry = (tag_image is not None))
103 #------------------------------------------------------------
104 def delete(tag):
105 if tag['is_in_use']:
106 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this tag. It is in use.'), beep = True)
107 return False
108
109 return gmDemographicRecord.delete_tag_image(tag_image = tag['pk_tag_image'])
110 #------------------------------------------------------------
111 def refresh(lctrl):
112 tags = gmDemographicRecord.get_tag_images(order_by = u'l10n_description')
113 items = [ [
114 t['l10n_description'],
115 gmTools.bool2subst(t['is_in_use'], u'X', u''),
116 u'%s' % t['size'],
117 t['pk_tag_image']
118 ] for t in tags ]
119 lctrl.set_string_items(items)
120 lctrl.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE])
121 lctrl.set_data(tags)
122 #------------------------------------------------------------
123 msg = _('\nTags with images registered with GNUmed.\n')
124
125 tag = gmListWidgets.get_choices_from_list (
126 parent = parent,
127 msg = msg,
128 caption = _('Showing tags with images.'),
129 columns = [_('Tag name'), _('In use'), _('Image size'), u'#'],
130 single_selection = True,
131 new_callback = edit,
132 edit_callback = edit,
133 delete_callback = delete,
134 refresh_callback = refresh,
135 left_extra_button = (_('WWW'), _('Go to www.openclipart.org for images.'), go_to_openclipart_org)
136 )
137
138 return tag
139 #------------------------------------------------------------
140 from Gnumed.wxGladeWidgets import wxgTagImageEAPnl
141
143
145
146 try:
147 data = kwargs['tag_image']
148 del kwargs['tag_image']
149 except KeyError:
150 data = None
151
152 wxgTagImageEAPnl.wxgTagImageEAPnl.__init__(self, *args, **kwargs)
153 gmEditArea.cGenericEditAreaMixin.__init__(self)
154
155 self.mode = 'new'
156 self.data = data
157 if data is not None:
158 self.mode = 'edit'
159
160 self.__selected_image_file = None
161 #----------------------------------------------------------------
162 # generic Edit Area mixin API
163 #----------------------------------------------------------------
165
166 valid = True
167
168 if self.mode == u'new':
169 if self.__selected_image_file is None:
170 valid = False
171 gmDispatcher.send(signal = 'statustext', msg = _('Must pick an image file for a new tag.'), beep = True)
172 self._BTN_pick_image.SetFocus()
173
174 if self.__selected_image_file is not None:
175 try:
176 open(self.__selected_image_file).close()
177 except StandardError:
178 valid = False
179 self.__selected_image_file = None
180 gmDispatcher.send(signal = 'statustext', msg = _('Cannot open the image file [%s].') % self.__selected_image_file, beep = True)
181 self._BTN_pick_image.SetFocus()
182
183 if self._TCTRL_description.GetValue().strip() == u'':
184 valid = False
185 self.display_tctrl_as_valid(self._TCTRL_description, False)
186 self._TCTRL_description.SetFocus()
187 else:
188 self.display_tctrl_as_valid(self._TCTRL_description, True)
189
190 return (valid is True)
191 #----------------------------------------------------------------
193
194 dbo_conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Creating tag with image'))
195 if dbo_conn is None:
196 return False
197
198 data = gmDemographicRecord.create_tag_image(description = self._TCTRL_description.GetValue().strip(), link_obj = dbo_conn)
199 dbo_conn.close()
200
201 data['filename'] = self._TCTRL_filename.GetValue().strip()
202 data.save()
203 data.update_image_from_file(filename = self.__selected_image_file)
204
205 # must be done very late or else the property access
206 # will refresh the display such that later field
207 # access will return empty values
208 self.data = data
209 return True
210 #----------------------------------------------------------------
212
213 # this is somewhat fake as it never actually uses the gm-dbo conn
214 # (although it does verify it)
215 dbo_conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Updating tag with image'))
216 if dbo_conn is None:
217 return False
218 dbo_conn.close()
219
220 self.data['description'] = self._TCTRL_description.GetValue().strip()
221 self.data['filename'] = self._TCTRL_filename.GetValue().strip()
222 self.data.save()
223
224 if self.__selected_image_file is not None:
225 open(self.__selected_image_file).close()
226 self.data.update_image_from_file(filename = self.__selected_image_file)
227 self.__selected_image_file = None
228
229 return True
230 #----------------------------------------------------------------
232 self._TCTRL_description.SetValue(u'')
233 self._TCTRL_filename.SetValue(u'')
234 self._BMP_image.SetBitmap(bitmap = wx.EmptyBitmap(100, 100))
235
236 self.__selected_image_file = None
237
238 self._TCTRL_description.SetFocus()
239 #----------------------------------------------------------------
242 #----------------------------------------------------------------
244 self._TCTRL_description.SetValue(self.data['l10n_description'])
245 self._TCTRL_filename.SetValue(gmTools.coalesce(self.data['filename'], u''))
246 fname = self.data.export_image2file()
247 if fname is None:
248 self._BMP_image.SetBitmap(bitmap = wx.EmptyBitmap(100, 100))
249 else:
250 self._BMP_image.SetBitmap(bitmap = gmGuiHelpers.file2scaled_image(filename = fname, height = 100))
251
252 self.__selected_image_file = None
253
254 self._TCTRL_description.SetFocus()
255 #----------------------------------------------------------------
256 # event handlers
257 #----------------------------------------------------------------
269
270 #============================================================
271 from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl
272
274
276 wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl.__init__(self, *args, **kwargs)
277 self._SZR_bitmaps = self.GetSizer()
278 self.__bitmaps = []
279
280 self.__context_popup = wx.Menu()
281
282 item = self.__context_popup.Append(-1, _('&Edit comment'))
283 self.Bind(wx.EVT_MENU, self.__edit_tag, item)
284
285 item = self.__context_popup.Append(-1, _('&Remove tag'))
286 self.Bind(wx.EVT_MENU, self.__remove_tag, item)
287 #--------------------------------------------------------
288 # external API
289 #--------------------------------------------------------
291
292 self.clear()
293
294 for tag in patient.get_tags(order_by = u'l10n_description'):
295 fname = tag.export_image2file()
296 if fname is None:
297 _log.warning('cannot export image data of tag [%s]', tag['l10n_description'])
298 continue
299 img = gmGuiHelpers.file2scaled_image(filename = fname, height = 20)
300 bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER)
301 bmp.SetToolTipString(u'%s%s' % (
302 tag['l10n_description'],
303 gmTools.coalesce(tag['comment'], u'', u'\n\n%s')
304 ))
305 bmp.tag = tag
306 bmp.Bind(wx.EVT_RIGHT_UP, self._on_bitmap_rightclicked)
307 # FIXME: add context menu for Delete/Clone/Add/Configure
308 self._SZR_bitmaps.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, 1) # | wx.EXPAND
309 self.__bitmaps.append(bmp)
310
311 self.GetParent().Layout()
312 #--------------------------------------------------------
314 for child_idx in range(len(self._SZR_bitmaps.GetChildren())):
315 self._SZR_bitmaps.Detach(child_idx)
316 for bmp in self.__bitmaps:
317 bmp.Destroy()
318 self.__bitmaps = []
319 #--------------------------------------------------------
320 # internal helpers
321 #--------------------------------------------------------
323 if self.__current_tag is None:
324 return
325 pat = gmPerson.gmCurrentPatient()
326 if not pat.connected:
327 return
328 pat.remove_tag(tag = self.__current_tag['pk_identity_tag'])
329 #--------------------------------------------------------
331 if self.__current_tag is None:
332 return
333
334 msg = _('Edit the comment on tag [%s]') % self.__current_tag['l10n_description']
335 comment = wx.GetTextFromUser (
336 message = msg,
337 caption = _('Editing tag comment'),
338 default_value = gmTools.coalesce(self.__current_tag['comment'], u''),
339 parent = self
340 )
341
342 if comment == u'':
343 return
344
345 if comment.strip() == self.__current_tag['comment']:
346 return
347
348 if comment == u' ':
349 self.__current_tag['comment'] = None
350 else:
351 self.__current_tag['comment'] = comment.strip()
352
353 self.__current_tag.save()
354 #--------------------------------------------------------
355 # event handlers
356 #--------------------------------------------------------
361 #============================================================
362 #============================================================
364
366
367 kwargs['message'] = _("Today's KOrganizer appointments ...")
368 kwargs['button_defs'] = [
369 {'label': _('Reload'), 'tooltip': _('Reload appointments from KOrganizer')},
370 {'label': u''},
371 {'label': u''},
372 {'label': u''},
373 {'label': u'KOrganizer', 'tooltip': _('Launch KOrganizer')}
374 ]
375 gmDataMiningWidgets.cPatientListingPnl.__init__(self, *args, **kwargs)
376
377 self.fname = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp', 'korganizer2gnumed.csv'))
378 self.reload_cmd = 'konsolekalendar --view --export-type csv --export-file %s' % self.fname
379
380 #--------------------------------------------------------
384 #--------------------------------------------------------
386 """Reload appointments from KOrganizer."""
387 found, cmd = gmShellAPI.detect_external_binary(binary = 'korganizer')
388
389 if not found:
390 gmDispatcher.send(signal = 'statustext', msg = _('KOrganizer is not installed.'), beep = True)
391 return
392
393 gmShellAPI.run_command_in_shell(command = cmd, blocking = False)
394 #--------------------------------------------------------
396 try: os.remove(self.fname)
397 except OSError: pass
398 gmShellAPI.run_command_in_shell(command=self.reload_cmd, blocking=True)
399 try:
400 csv_file = codecs.open(self.fname , mode = 'rU', encoding = 'utf8', errors = 'replace')
401 except IOError:
402 gmDispatcher.send(signal = u'statustext', msg = _('Cannot access KOrganizer transfer file [%s]') % self.fname, beep = True)
403 return
404
405 csv_lines = gmTools.unicode_csv_reader (
406 csv_file,
407 delimiter = ','
408 )
409 # start_date, start_time, end_date, end_time, title (patient), ort, comment, UID
410 self._LCTRL_items.set_columns ([
411 _('Place'),
412 _('Start'),
413 u'',
414 u'',
415 _('Patient'),
416 _('Comment')
417 ])
418 items = []
419 data = []
420 for line in csv_lines:
421 items.append([line[5], line[0], line[1], line[3], line[4], line[6]])
422 data.append([line[4], line[7]])
423
424 self._LCTRL_items.set_string_items(items = items)
425 self._LCTRL_items.set_column_widths()
426 self._LCTRL_items.set_data(data = data)
427 self._LCTRL_items.patient_key = 0
428 #--------------------------------------------------------
429 # notebook plugins API
430 #--------------------------------------------------------
432 self.reload_appointments()
433 #============================================================
434 # occupation related widgets / functions
435 #============================================================
437
438 pat = gmPerson.gmCurrentPatient()
439 curr_jobs = pat.get_occupations()
440 if len(curr_jobs) > 0:
441 old_job = curr_jobs[0]['l10n_occupation']
442 update = curr_jobs[0]['modified_when'].strftime('%m/%Y')
443 else:
444 old_job = u''
445 update = u''
446
447 msg = _(
448 'Please enter the primary occupation of the patient.\n'
449 '\n'
450 'Currently recorded:\n'
451 '\n'
452 ' %s (last updated %s)'
453 ) % (old_job, update)
454
455 new_job = wx.GetTextFromUser (
456 message = msg,
457 caption = _('Editing primary occupation'),
458 default_value = old_job,
459 parent = None
460 )
461 if new_job.strip() == u'':
462 return
463
464 for job in curr_jobs:
465 # unlink all but the new job
466 if job['l10n_occupation'] != new_job:
467 pat.unlink_occupation(occupation = job['l10n_occupation'])
468 # and link the new one
469 pat.link_occupation(occupation = new_job)
470
471 #------------------------------------------------------------
473
475 query = u"select distinct name, _(name) from dem.occupation where _(name) %(fragment_condition)s"
476 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
477 mp.setThresholds(1, 3, 5)
478 gmPhraseWheel.cPhraseWheel.__init__ (
479 self,
480 *args,
481 **kwargs
482 )
483 self.SetToolTipString(_("Type or select an occupation."))
484 self.capitalisation_mode = gmTools.CAPS_FIRST
485 self.matcher = mp
486
487 #============================================================
488 # identity widgets / functions
489 #============================================================
491 # ask user for assurance
492 go_ahead = gmGuiHelpers.gm_show_question (
493 _('Are you sure you really, positively want\n'
494 'to disable the following person ?\n'
495 '\n'
496 ' %s %s %s\n'
497 ' born %s\n'
498 '\n'
499 '%s\n'
500 ) % (
501 identity['firstnames'],
502 identity['lastnames'],
503 identity['gender'],
504 identity['dob'],
505 gmTools.bool2subst (
506 identity.is_patient,
507 _('This patient DID receive care.'),
508 _('This person did NOT receive care.')
509 )
510 ),
511 _('Disabling person')
512 )
513 if not go_ahead:
514 return True
515
516 # get admin connection
517 conn = gmAuthWidgets.get_dbowner_connection (
518 procedure = _('Disabling patient')
519 )
520 # - user cancelled
521 if conn is False:
522 return True
523 # - error
524 if conn is None:
525 return False
526
527 # now disable patient
528 gmPG2.run_rw_queries(queries = [{'cmd': u"update dem.identity set deleted=True where pk=%s", 'args': [identity['pk_identity']]}])
529
530 return True
531
532 #------------------------------------------------------------
533 # phrasewheels
534 #------------------------------------------------------------
536
538 query = u"select distinct lastnames, lastnames from dem.names where lastnames %(fragment_condition)s order by lastnames limit 25"
539 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
540 mp.setThresholds(3, 5, 9)
541 gmPhraseWheel.cPhraseWheel.__init__ (
542 self,
543 *args,
544 **kwargs
545 )
546 self.SetToolTipString(_("Type or select a last name (family name/surname)."))
547 self.capitalisation_mode = gmTools.CAPS_NAMES
548 self.matcher = mp
549 #------------------------------------------------------------
551
553 query = u"""
554 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
555 union
556 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
557 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
558 mp.setThresholds(3, 5, 9)
559 gmPhraseWheel.cPhraseWheel.__init__ (
560 self,
561 *args,
562 **kwargs
563 )
564 self.SetToolTipString(_("Type or select a first name (forename/Christian name/given name)."))
565 self.capitalisation_mode = gmTools.CAPS_NAMES
566 self.matcher = mp
567 #------------------------------------------------------------
569
571 query = u"""
572 (select distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20)
573 union
574 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
575 union
576 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
577 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
578 mp.setThresholds(3, 5, 9)
579 gmPhraseWheel.cPhraseWheel.__init__ (
580 self,
581 *args,
582 **kwargs
583 )
584 self.SetToolTipString(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name)."))
585 # nicknames CAN start with lower case !
586 #self.capitalisation_mode = gmTools.CAPS_NAMES
587 self.matcher = mp
588 #------------------------------------------------------------
590
592 query = u"select distinct title, title from dem.identity where title %(fragment_condition)s"
593 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
594 mp.setThresholds(1, 3, 9)
595 gmPhraseWheel.cPhraseWheel.__init__ (
596 self,
597 *args,
598 **kwargs
599 )
600 self.SetToolTipString(_("Type or select a title. Note that the title applies to the person, not to a particular name !"))
601 self.matcher = mp
602 #------------------------------------------------------------
604 """Let user select a gender."""
605
606 _gender_map = None
607
609
610 if cGenderSelectionPhraseWheel._gender_map is None:
611 cmd = u"""
612 select tag, l10n_label, sort_weight
613 from dem.v_gender_labels
614 order by sort_weight desc"""
615 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True)
616 cGenderSelectionPhraseWheel._gender_map = {}
617 for gender in rows:
618 cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = {
619 'data': gender[idx['tag']],
620 'label': gender[idx['l10n_label']],
621 'weight': gender[idx['sort_weight']]
622 }
623
624 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = cGenderSelectionPhraseWheel._gender_map.values())
625 mp.setThresholds(1, 1, 3)
626
627 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
628 self.selection_only = True
629 self.matcher = mp
630 self.picklist_delay = 50
631 #------------------------------------------------------------
633
635 query = u"""
636 select distinct pk, (name || coalesce(' (%s ' || issuer || ')', '')) as label
637 from dem.enum_ext_id_types
638 where name %%(fragment_condition)s
639 order by label limit 25""" % _('issued by')
640 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
641 mp.setThresholds(1, 3, 5)
642 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
643 self.SetToolTipString(_("Enter or select a type for the external ID."))
644 self.matcher = mp
645 #------------------------------------------------------------
647
649 query = u"""
650 select distinct issuer, issuer
651 from dem.enum_ext_id_types
652 where issuer %(fragment_condition)s
653 order by issuer limit 25"""
654 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
655 mp.setThresholds(1, 3, 5)
656 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
657 self.SetToolTipString(_("Type or select an ID issuer."))
658 self.capitalisation_mode = gmTools.CAPS_FIRST
659 self.matcher = mp
660 #------------------------------------------------------------
661 # edit areas
662 #------------------------------------------------------------
663 from Gnumed.wxGladeWidgets import wxgExternalIDEditAreaPnl
664
666 """An edit area for editing/creating external IDs.
667
668 Does NOT act on/listen to the current patient.
669 """
671
672 try:
673 self.ext_id = kwargs['external_id']
674 del kwargs['external_id']
675 except:
676 self.ext_id = None
677
678 wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl.__init__(self, *args, **kwargs)
679
680 self.identity = None
681
682 self.__register_events()
683
684 self.refresh()
685 #--------------------------------------------------------
686 # external API
687 #--------------------------------------------------------
689 if ext_id is not None:
690 self.ext_id = ext_id
691
692 if self.ext_id is not None:
693 self._PRW_type.SetText(value = self.ext_id['name'], data = self.ext_id['pk_type'])
694 self._TCTRL_value.SetValue(self.ext_id['value'])
695 self._PRW_issuer.SetText(self.ext_id['issuer'])
696 self._TCTRL_comment.SetValue(gmTools.coalesce(self.ext_id['comment'], u''))
697 # FIXME: clear fields
698 # else:
699 # pass
700 #--------------------------------------------------------
702
703 if not self.__valid_for_save():
704 return False
705
706 # strip out " (issued by ...)" added by phrasewheel
707 type = regex.split(' \(%s .+\)$' % _('issued by'), self._PRW_type.GetValue().strip(), 1)[0]
708
709 # add new external ID
710 if self.ext_id is None:
711 self.identity.add_external_id (
712 type_name = type,
713 value = self._TCTRL_value.GetValue().strip(),
714 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''),
715 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
716 )
717 # edit old external ID
718 else:
719 self.identity.update_external_id (
720 pk_id = self.ext_id['pk_id'],
721 type = type,
722 value = self._TCTRL_value.GetValue().strip(),
723 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''),
724 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
725 )
726
727 return True
728 #--------------------------------------------------------
729 # internal helpers
730 #--------------------------------------------------------
732 self._PRW_type.add_callback_on_lose_focus(self._on_type_set)
733 #--------------------------------------------------------
735 """Set the issuer according to the selected type.
736
737 Matches are fetched from existing records in backend.
738 """
739 pk_curr_type = self._PRW_type.GetData()
740 if pk_curr_type is None:
741 return True
742 rows, idx = gmPG2.run_ro_queries(queries = [{
743 'cmd': u"select issuer from dem.enum_ext_id_types where pk = %s",
744 'args': [pk_curr_type]
745 }])
746 if len(rows) == 0:
747 return True
748 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0])
749 return True
750 #--------------------------------------------------------
752
753 no_errors = True
754
755 # do not test .GetData() because adding external IDs
756 # will create types if necessary
757 # if self._PRW_type.GetData() is None:
758 if self._PRW_type.GetValue().strip() == u'':
759 self._PRW_type.SetBackgroundColour('pink')
760 self._PRW_type.SetFocus()
761 self._PRW_type.Refresh()
762 else:
763 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
764 self._PRW_type.Refresh()
765
766 if self._TCTRL_value.GetValue().strip() == u'':
767 self._TCTRL_value.SetBackgroundColour('pink')
768 self._TCTRL_value.SetFocus()
769 self._TCTRL_value.Refresh()
770 no_errors = False
771 else:
772 self._TCTRL_value.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
773 self._TCTRL_value.Refresh()
774
775 return no_errors
776 #------------------------------------------------------------
777 from Gnumed.wxGladeWidgets import wxgIdentityEAPnl
778
780 """An edit area for editing/creating title/gender/dob/dod etc."""
781
783
784 try:
785 data = kwargs['identity']
786 del kwargs['identity']
787 except KeyError:
788 data = None
789
790 wxgIdentityEAPnl.wxgIdentityEAPnl.__init__(self, *args, **kwargs)
791 gmEditArea.cGenericEditAreaMixin.__init__(self)
792
793 self.mode = 'new'
794 self.data = data
795 if data is not None:
796 self.mode = 'edit'
797
798 # self.__init_ui()
799 #----------------------------------------------------------------
800 # def __init_ui(self):
801 # # adjust phrasewheels etc
802 #----------------------------------------------------------------
803 # generic Edit Area mixin API
804 #----------------------------------------------------------------
806
807 has_error = False
808
809 if self._PRW_gender.GetData() is None:
810 self._PRW_gender.SetFocus()
811 has_error = True
812
813 if not self._PRW_dob.is_valid_timestamp():
814 val = self._PRW_dob.GetValue().strip()
815 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val)
816 self._PRW_dob.SetBackgroundColour('pink')
817 self._PRW_dob.Refresh()
818 self._PRW_dob.SetFocus()
819 has_error = True
820 else:
821 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
822 self._PRW_dob.Refresh()
823
824 if not self._PRW_dod.is_valid_timestamp(allow_empty = True):
825 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.'))
826 self._PRW_dod.SetFocus()
827 has_error = True
828
829 return (has_error is False)
830 #----------------------------------------------------------------
834 #----------------------------------------------------------------
836
837 self.data['gender'] = self._PRW_gender.GetData()
838
839 if self._PRW_dob.GetValue().strip() == u'':
840 self.data['dob'] = None
841 else:
842 self.data['dob'] = self._PRW_dob.GetData().get_pydt()
843
844 self.data['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'')
845 self.data['deceased'] = self._PRW_dod.GetData()
846 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
847
848 self.data.save()
849 return True
850 #----------------------------------------------------------------
853 #----------------------------------------------------------------
855
856 self._LBL_info.SetLabel(u'ID: #%s' % (
857 self.data.ID
858 # FIXME: add 'deleted' status
859 ))
860 self._PRW_dob.SetText (
861 value = self.data.get_formatted_dob(format = '%Y-%m-%d %H:%M', encoding = gmI18N.get_encoding()),
862 data = self.data['dob']
863 )
864 self._PRW_dod.SetText(value = self.data['deceased'])
865 self._PRW_gender.SetData(self.data['gender'])
866 #self._PRW_ethnicity.SetValue()
867 self._PRW_title.SetText(gmTools.coalesce(self.data['title'], u''))
868 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
869 #----------------------------------------------------------------
872 #------------------------------------------------------------
873 from Gnumed.wxGladeWidgets import wxgNameGenderDOBEditAreaPnl
874
876 """An edit area for editing/creating name/gender/dob.
877
878 Does NOT act on/listen to the current patient.
879 """
881
882 self.__name = kwargs['name']
883 del kwargs['name']
884 self.__identity = gmPerson.cIdentity(aPK_obj = self.__name['pk_identity'])
885
886 wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl.__init__(self, *args, **kwargs)
887
888 self.__register_interests()
889 self.refresh()
890 #--------------------------------------------------------
891 # external API
892 #--------------------------------------------------------
894 if self.__name is None:
895 return
896
897 self._PRW_title.SetText(gmTools.coalesce(self.__name['title'], u''))
898 self._PRW_firstname.SetText(self.__name['firstnames'])
899 self._PRW_lastname.SetText(self.__name['lastnames'])
900 self._PRW_nick.SetText(gmTools.coalesce(self.__name['preferred'], u''))
901 self._PRW_dob.SetText (
902 value = self.__identity.get_formatted_dob(format = '%Y-%m-%d %H:%M', encoding = gmI18N.get_encoding()),
903 data = self.__identity['dob']
904 )
905 self._PRW_gender.SetData(self.__name['gender'])
906 self._CHBOX_active.SetValue(self.__name['active_name'])
907 self._PRW_dod.SetText(data = self.__identity['deceased'])
908 self._TCTRL_comment.SetValue(gmTools.coalesce(self.__name['comment'], u''))
909 # FIXME: clear fields
910 # else:
911 # pass
912 #--------------------------------------------------------
914
915 if not self.__valid_for_save():
916 return False
917
918 self.__identity['gender'] = self._PRW_gender.GetData()
919 if self._PRW_dob.GetValue().strip() == u'':
920 self.__identity['dob'] = None
921 else:
922 self.__identity['dob'] = self._PRW_dob.GetData().get_pydt()
923 self.__identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'')
924 self.__identity['deceased'] = self._PRW_dod.GetData()
925 self.__identity.save_payload()
926
927 active = self._CHBOX_active.GetValue()
928 first = self._PRW_firstname.GetValue().strip()
929 last = self._PRW_lastname.GetValue().strip()
930 old_nick = self.__name['preferred']
931
932 # is it a new name ?
933 old_name = self.__name['firstnames'] + self.__name['lastnames']
934 if (first + last) != old_name:
935 self.__name = self.__identity.add_name(first, last, active)
936
937 self.__name['active_name'] = active
938 self.__name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'')
939 self.__name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
940
941 self.__name.save_payload()
942
943 return True
944 #--------------------------------------------------------
945 # event handling
946 #--------------------------------------------------------
948 self._PRW_firstname.add_callback_on_lose_focus(self._on_name_set)
949 #--------------------------------------------------------
951 """Set the gender according to entered firstname.
952
953 Matches are fetched from existing records in backend.
954 """
955 firstname = self._PRW_firstname.GetValue().strip()
956 if firstname == u'':
957 return True
958 rows, idx = gmPG2.run_ro_queries(queries = [{
959 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
960 'args': [firstname]
961 }])
962 if len(rows) == 0:
963 return True
964 wx.CallAfter(self._PRW_gender.SetData, rows[0][0])
965 return True
966 #--------------------------------------------------------
967 # internal helpers
968 #--------------------------------------------------------
970
971 has_error = False
972
973 if self._PRW_gender.GetData() is None:
974 self._PRW_gender.display_as_valid(False)
975 self._PRW_gender.SetFocus()
976 has_error = True
977 else:
978 self._PRW_gender.display_as_valid(True)
979
980 if not self._PRW_dob.is_valid_timestamp():
981 val = self._PRW_dob.GetValue().strip()
982 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val)
983 self._PRW_dob.display_as_valid(False)
984 self._PRW_dob.SetFocus()
985 has_error = True
986 else:
987 self._PRW_dob.display_as_valid(True)
988
989 if not self._PRW_dod.is_valid_timestamp():
990 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.'))
991 self._PRW_dod.display_as_valid(False)
992 self._PRW_dod.SetFocus()
993 has_error = True
994 else:
995 self._PRW_dod.display_as_valid(True)
996
997 if self._PRW_lastname.GetValue().strip() == u'':
998 self._PRW_lastname.display_as_valid(False)
999 self._PRW_lastname.SetFocus()
1000 has_error = True
1001 else:
1002 self._PRW_lastname.display_as_valid(True)
1003
1004 if self._PRW_firstname.GetValue().strip() == u'':
1005 self._PRW_firstname.display_as_valid(False)
1006 self._PRW_firstname.SetFocus()
1007 has_error = True
1008 else:
1009 self._PRW_firstname.display_as_valid(True)
1010
1011 return (has_error is False)
1012 #------------------------------------------------------------
1013 # list manager
1014 #------------------------------------------------------------
1016 """A list for managing a person's names.
1017
1018 Does NOT act on/listen to the current patient.
1019 """
1021
1022 try:
1023 self.__identity = kwargs['identity']
1024 del kwargs['identity']
1025 except KeyError:
1026 self.__identity = None
1027
1028 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs)
1029
1030 self.new_callback = self._add_name
1031 self.edit_callback = self._edit_name
1032 self.delete_callback = self._del_name
1033 self.refresh_callback = self.refresh
1034
1035 self.__init_ui()
1036 self.refresh()
1037 #--------------------------------------------------------
1038 # external API
1039 #--------------------------------------------------------
1041 if self.__identity is None:
1042 self._LCTRL_items.set_string_items()
1043 return
1044
1045 names = self.__identity.get_names()
1046 self._LCTRL_items.set_string_items (
1047 items = [ [
1048 gmTools.bool2str(n['active_name'], 'X', ''),
1049 n['lastnames'],
1050 n['firstnames'],
1051 gmTools.coalesce(n['preferred'], u''),
1052 gmTools.coalesce(n['comment'], u'')
1053 ] for n in names ]
1054 )
1055 self._LCTRL_items.set_column_widths()
1056 self._LCTRL_items.set_data(data = names)
1057 #--------------------------------------------------------
1058 # internal helpers
1059 #--------------------------------------------------------
1061 self._LCTRL_items.set_columns(columns = [
1062 _('Active'),
1063 _('Lastname'),
1064 _('Firstname(s)'),
1065 _('Preferred Name'),
1066 _('Comment')
1067 ])
1068 self._BTN_edit.SetLabel(_('Clone and &edit'))
1069 #--------------------------------------------------------
1071 ea = cNameGenderDOBEditAreaPnl(self, -1, name = self.__identity.get_active_name())
1072 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
1073 dlg.SetTitle(_('Adding new name'))
1074 if dlg.ShowModal() == wx.ID_OK:
1075 dlg.Destroy()
1076 return True
1077 dlg.Destroy()
1078 return False
1079 #--------------------------------------------------------
1081 ea = cNameGenderDOBEditAreaPnl(self, -1, name = name)
1082 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
1083 dlg.SetTitle(_('Cloning name'))
1084 if dlg.ShowModal() == wx.ID_OK:
1085 dlg.Destroy()
1086 return True
1087 dlg.Destroy()
1088 return False
1089 #--------------------------------------------------------
1091
1092 if len(self.__identity.get_names()) == 1:
1093 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True)
1094 return False
1095
1096 go_ahead = gmGuiHelpers.gm_show_question (
1097 _( 'It is often advisable to keep old names around and\n'
1098 'just create a new "currently active" name.\n'
1099 '\n'
1100 'This allows finding the patient by both the old\n'
1101 'and the new name (think before/after marriage).\n'
1102 '\n'
1103 'Do you still want to really delete\n'
1104 "this name from the patient ?"
1105 ),
1106 _('Deleting name')
1107 )
1108 if not go_ahead:
1109 return False
1110
1111 self.__identity.delete_name(name = name)
1112 return True
1113 #--------------------------------------------------------
1114 # properties
1115 #--------------------------------------------------------
1118
1122
1123 identity = property(_get_identity, _set_identity)
1124 #------------------------------------------------------------
1126 """A list for managing a person's external IDs.
1127
1128 Does NOT act on/listen to the current patient.
1129 """
1131
1132 try:
1133 self.__identity = kwargs['identity']
1134 del kwargs['identity']
1135 except KeyError:
1136 self.__identity = None
1137
1138 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs)
1139
1140 self.new_callback = self._add_id
1141 self.edit_callback = self._edit_id
1142 self.delete_callback = self._del_id
1143 self.refresh_callback = self.refresh
1144
1145 self.__init_ui()
1146 self.refresh()
1147 #--------------------------------------------------------
1148 # external API
1149 #--------------------------------------------------------
1151 if self.__identity is None:
1152 self._LCTRL_items.set_string_items()
1153 return
1154
1155 ids = self.__identity.get_external_ids()
1156 self._LCTRL_items.set_string_items (
1157 items = [ [
1158 i['name'],
1159 i['value'],
1160 gmTools.coalesce(i['issuer'], u''),
1161 gmTools.coalesce(i['comment'], u'')
1162 ] for i in ids
1163 ]
1164 )
1165 self._LCTRL_items.set_column_widths()
1166 self._LCTRL_items.set_data(data = ids)
1167 #--------------------------------------------------------
1168 # internal helpers
1169 #--------------------------------------------------------
1171 self._LCTRL_items.set_columns(columns = [
1172 _('ID type'),
1173 _('Value'),
1174 _('Issuer'),
1175 _('Comment')
1176 ])
1177 #--------------------------------------------------------
1179 ea = cExternalIDEditAreaPnl(self, -1)
1180 ea.identity = self.__identity
1181 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
1182 dlg.SetTitle(_('Adding new external ID'))
1183 if dlg.ShowModal() == wx.ID_OK:
1184 dlg.Destroy()
1185 return True
1186 dlg.Destroy()
1187 return False
1188 #--------------------------------------------------------
1190 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id)
1191 ea.identity = self.__identity
1192 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
1193 dlg.SetTitle(_('Editing external ID'))
1194 if dlg.ShowModal() == wx.ID_OK:
1195 dlg.Destroy()
1196 return True
1197 dlg.Destroy()
1198 return False
1199 #--------------------------------------------------------
1201 go_ahead = gmGuiHelpers.gm_show_question (
1202 _( 'Do you really want to delete this\n'
1203 'external ID from the patient ?'),
1204 _('Deleting external ID')
1205 )
1206 if not go_ahead:
1207 return False
1208 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
1209 return True
1210 #--------------------------------------------------------
1211 # properties
1212 #--------------------------------------------------------
1215
1219
1220 identity = property(_get_identity, _set_identity)
1221 #------------------------------------------------------------
1222 # integrated panels
1223 #------------------------------------------------------------
1224 from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl
1225
1227 """A panel for editing identity data for a person.
1228
1229 - provides access to:
1230 - name
1231 - external IDs
1232
1233 Does NOT act on/listen to the current patient.
1234 """
1236
1237 wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl.__init__(self, *args, **kwargs)
1238
1239 self.__identity = None
1240 self.refresh()
1241 #--------------------------------------------------------
1242 # external API
1243 #--------------------------------------------------------
1245 self._PNL_names.identity = self.__identity
1246 self._PNL_ids.identity = self.__identity
1247 # this is an Edit Area:
1248 self._PNL_identity.mode = 'new'
1249 self._PNL_identity.data = self.__identity
1250 if self.__identity is not None:
1251 self._PNL_identity.mode = 'edit'
1252 #--------------------------------------------------------
1253 # properties
1254 #--------------------------------------------------------
1257
1261
1262 identity = property(_get_identity, _set_identity)
1263 #--------------------------------------------------------
1264 # event handlers
1265 #--------------------------------------------------------
1269 #--------------------------------------------------------
1272
1273 #============================================================
1274 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl
1275
1276 -class cPersonSocialNetworkManagerPnl(wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl):
1278
1279 wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl.__init__(self, *args, **kwargs)
1280
1281 self.__identity = None
1282 self._PRW_provider.selection_only = False
1283 self.refresh()
1284 #--------------------------------------------------------
1285 # external API
1286 #--------------------------------------------------------
1288
1289 tt = _("Link another person in this database as the emergency contact.")
1290
1291 if self.__identity is None:
1292 self._TCTRL_er_contact.SetValue(u'')
1293 self._TCTRL_person.person = None
1294 self._TCTRL_person.SetToolTipString(tt)
1295
1296 self._PRW_provider.SetText(value = u'', data = None)
1297 return
1298
1299 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], u''))
1300 if self.__identity['pk_emergency_contact'] is not None:
1301 ident = gmPerson.cIdentity(aPK_obj = self.__identity['pk_emergency_contact'])
1302 self._TCTRL_person.person = ident
1303 tt = u'%s\n\n%s\n\n%s' % (
1304 tt,
1305 ident['description_gender'],
1306 u'\n'.join([
1307 u'%s: %s%s' % (
1308 c['l10n_comm_type'],
1309 c['url'],
1310 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), u'', u'')
1311 )
1312 for c in ident.get_comm_channels()
1313 ])
1314 )
1315 else:
1316 self._TCTRL_person.person = None
1317
1318 self._TCTRL_person.SetToolTipString(tt)
1319
1320 if self.__identity['pk_primary_provider'] is None:
1321 self._PRW_provider.SetText(value = u'', data = None)
1322 else:
1323 self._PRW_provider.SetData(data = self.__identity['pk_primary_provider'])
1324 #--------------------------------------------------------
1325 # properties
1326 #--------------------------------------------------------
1329
1333
1334 identity = property(_get_identity, _set_identity)
1335 #--------------------------------------------------------
1336 # event handlers
1337 #--------------------------------------------------------
1351 #--------------------------------------------------------
1362 #--------------------------------------------------------
1370 #============================================================
1371 # new-patient widgets
1372 #============================================================
1374
1375 dbcfg = gmCfg.cCfgSQL()
1376
1377 def_region = dbcfg.get2 (
1378 option = u'person.create.default_region',
1379 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1380 bias = u'user'
1381 )
1382 def_country = None
1383
1384 if def_region is None:
1385 def_country = dbcfg.get2 (
1386 option = u'person.create.default_country',
1387 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1388 bias = u'user'
1389 )
1390 else:
1391 countries = gmDemographicRecord.get_country_for_region(region = def_region)
1392 if len(countries) == 1:
1393 def_country = countries[0]['l10n_country']
1394
1395 if parent is None:
1396 parent = wx.GetApp().GetTopWindow()
1397
1398 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region)
1399 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
1400 dlg.SetTitle(_('Adding new person'))
1401 ea._PRW_lastname.SetFocus()
1402 result = dlg.ShowModal()
1403 pat = ea.data
1404 dlg.Destroy()
1405
1406 if result != wx.ID_OK:
1407 return False
1408
1409 _log.debug('created new person [%s]', pat.ID)
1410
1411 if activate:
1412 from Gnumed.wxpython import gmPatSearchWidgets
1413 gmPatSearchWidgets.set_active_patient(patient = pat)
1414
1415 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin')
1416
1417 return True
1418 #============================================================
1419 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl
1420
1421 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
1422
1424
1425 try:
1426 self.default_region = kwargs['region']
1427 del kwargs['region']
1428 except KeyError:
1429 self.default_region = None
1430
1431 try:
1432 self.default_country = kwargs['country']
1433 del kwargs['country']
1434 except KeyError:
1435 self.default_country = None
1436
1437 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs)
1438 gmEditArea.cGenericEditAreaMixin.__init__(self)
1439
1440 self.mode = 'new'
1441 self.data = None
1442 self._address = None
1443
1444 self.__init_ui()
1445 self.__register_interests()
1446 #----------------------------------------------------------------
1447 # internal helpers
1448 #----------------------------------------------------------------
1450 self._PRW_lastname.final_regex = '.+'
1451 self._PRW_firstnames.final_regex = '.+'
1452 self._PRW_address_searcher.selection_only = False
1453
1454 # only if we would support None on selection_only's:
1455 # self._PRW_external_id_type.selection_only = True
1456
1457 if self.default_country is not None:
1458 self._PRW_country.SetText(value = self.default_country)
1459
1460 if self.default_region is not None:
1461 self._PRW_region.SetText(value = self.default_region)
1462 #----------------------------------------------------------------
1464
1465 adr = self._PRW_address_searcher.get_address()
1466 if adr is None:
1467 return True
1468
1469 if ctrl.GetValue().strip() != adr[field]:
1470 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None)
1471 return True
1472
1473 return False
1474 #----------------------------------------------------------------
1476 adr = self._PRW_address_searcher.get_address()
1477 if adr is None:
1478 return True
1479
1480 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode'])
1481
1482 self._PRW_street.SetText(value = adr['street'], data = adr['street'])
1483 self._PRW_street.set_context(context = u'zip', val = adr['postcode'])
1484
1485 self._TCTRL_number.SetValue(adr['number'])
1486
1487 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb'])
1488 self._PRW_urb.set_context(context = u'zip', val = adr['postcode'])
1489
1490 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state'])
1491 self._PRW_region.set_context(context = u'zip', val = adr['postcode'])
1492
1493 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country'])
1494 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
1495 #----------------------------------------------------------------
1497 error = False
1498
1499 # name fields
1500 if self._PRW_lastname.GetValue().strip() == u'':
1501 error = True
1502 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
1503 self._PRW_lastname.display_as_valid(False)
1504 else:
1505 self._PRW_lastname.display_as_valid(True)
1506
1507 if self._PRW_firstnames.GetValue().strip() == '':
1508 error = True
1509 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
1510 self._PRW_firstnames.display_as_valid(False)
1511 else:
1512 self._PRW_firstnames.display_as_valid(True)
1513
1514 # gender
1515 if self._PRW_gender.GetData() is None:
1516 error = True
1517 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
1518 self._PRW_gender.display_as_valid(False)
1519 else:
1520 self._PRW_gender.display_as_valid(True)
1521
1522 # dob validation
1523 # test this last so we can check empty field as last barrier against save
1524 # 1) valid timestamp ?
1525 if self._PRW_dob.is_valid_timestamp(allow_empty = False): # properly colors the field
1526 dob = self._PRW_dob.date
1527 # but year also usable ?
1528 msg = None
1529 if dob.year < 1900:
1530 msg = _(
1531 'DOB: %s\n'
1532 '\n'
1533 'While this is a valid point in time Python does\n'
1534 'not know how to deal with it.\n'
1535 '\n'
1536 'We suggest using January 1st 1901 instead and adding\n'
1537 'the true date of birth to the patient comment.\n'
1538 '\n'
1539 'Sorry for the inconvenience %s'
1540 ) % (dob, gmTools.u_frowning_face)
1541 elif dob > gmDateTime.pydt_now_here():
1542 msg = _(
1543 'DOB: %s\n'
1544 '\n'
1545 'Date of birth in the future !'
1546 ) % dob
1547
1548 if msg is not None:
1549 error = True
1550 gmGuiHelpers.gm_show_error (
1551 msg,
1552 _('Registering new person')
1553 )
1554 self._PRW_dob.display_as_valid(False)
1555 self._PRW_dob.SetFocus()
1556 # 2) invalid timestamp ?
1557 else:
1558 # is this the only error ?
1559 if error is False:
1560 # is it empty rather than invalid ?
1561 if self._PRW_dob.GetValue().strip() == u'':
1562 # maybe even allow empty DOB ?
1563 allow_empty_dob = gmGuiHelpers.gm_show_question (
1564 _(
1565 'Are you sure you want to register this person\n'
1566 'without a valid date of birth ?\n'
1567 '\n'
1568 'This can be useful for temporary staff members\n'
1569 'but will provoke nag screens if this person\n'
1570 'becomes a patient.\n'
1571 ),
1572 _('Registering new person')
1573 )
1574 if allow_empty_dob:
1575 self._PRW_dob.display_as_valid(True)
1576 else:
1577 error = True
1578 self._PRW_dob.SetFocus()
1579
1580 # TOB validation if non-empty
1581 # if self._TCTRL_tob.GetValue().strip() != u'':
1582
1583 return (not error)
1584 #----------------------------------------------------------------
1586
1587 # existing address ? if so set other fields
1588 if self._PRW_address_searcher.GetData() is not None:
1589 wx.CallAfter(self.__set_fields_from_address_searcher)
1590 return True
1591
1592 # must either all contain something or none of them
1593 fields_to_fill = (
1594 self._TCTRL_number,
1595 self._PRW_zip,
1596 self._PRW_street,
1597 self._PRW_urb,
1598 self._PRW_region,
1599 self._PRW_country
1600 )
1601 no_of_filled_fields = 0
1602
1603 for field in fields_to_fill:
1604 if field.GetValue().strip() != u'':
1605 no_of_filled_fields += 1
1606 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
1607 field.Refresh()
1608
1609 # empty address ?
1610 if no_of_filled_fields == 0:
1611 if empty_address_is_valid:
1612 return True
1613 else:
1614 return None
1615
1616 # incompletely filled address ?
1617 if no_of_filled_fields != len(fields_to_fill):
1618 for field in fields_to_fill:
1619 if field.GetValue().strip() == u'':
1620 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
1621 field.SetFocus()
1622 field.Refresh()
1623 msg = _('To properly create an address, all the related fields must be filled in.')
1624 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
1625 return False
1626
1627 # fields which must contain a selected item
1628 # FIXME: they must also contain an *acceptable combination* which
1629 # FIXME: can only be tested against the database itself ...
1630 strict_fields = (
1631 self._PRW_region,
1632 self._PRW_country
1633 )
1634 error = False
1635 for field in strict_fields:
1636 if field.GetData() is None:
1637 error = True
1638 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
1639 field.SetFocus()
1640 else:
1641 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
1642 field.Refresh()
1643
1644 if error:
1645 msg = _('This field must contain an item selected from the dropdown list.')
1646 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
1647 return False
1648
1649 return True
1650 #----------------------------------------------------------------
1652
1653 # identity
1654 self._PRW_firstnames.add_callback_on_lose_focus(self._on_leaving_firstname)
1655
1656 # address
1657 self._PRW_address_searcher.add_callback_on_lose_focus(self._on_leaving_adress_searcher)
1658
1659 # invalidate address searcher when any field edited
1660 self._PRW_street.add_callback_on_lose_focus(self._invalidate_address_searcher)
1661 wx.EVT_KILL_FOCUS(self._TCTRL_number, self._invalidate_address_searcher)
1662 self._PRW_urb.add_callback_on_lose_focus(self._invalidate_address_searcher)
1663 self._PRW_region.add_callback_on_lose_focus(self._invalidate_address_searcher)
1664
1665 self._PRW_zip.add_callback_on_lose_focus(self._on_leaving_zip)
1666 self._PRW_country.add_callback_on_lose_focus(self._on_leaving_country)
1667 #----------------------------------------------------------------
1668 # event handlers
1669 #----------------------------------------------------------------
1671 """Set the gender according to entered firstname.
1672
1673 Matches are fetched from existing records in backend.
1674 """
1675 # only set if not already set so as to not
1676 # overwrite a change by the user
1677 if self._PRW_gender.GetData() is not None:
1678 return True
1679
1680 firstname = self._PRW_firstnames.GetValue().strip()
1681 if firstname == u'':
1682 return True
1683
1684 gender = gmPerson.map_firstnames2gender(firstnames = firstname)
1685 if gender is None:
1686 return True
1687
1688 wx.CallAfter(self._PRW_gender.SetData, gender)
1689 return True
1690 #----------------------------------------------------------------
1692 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode')
1693
1694 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'')
1695 self._PRW_street.set_context(context = u'zip', val = zip_code)
1696 self._PRW_urb.set_context(context = u'zip', val = zip_code)
1697 self._PRW_region.set_context(context = u'zip', val = zip_code)
1698 self._PRW_country.set_context(context = u'zip', val = zip_code)
1699
1700 return True
1701 #----------------------------------------------------------------
1703 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country')
1704
1705 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'')
1706 self._PRW_region.set_context(context = u'country', val = country)
1707
1708 return True
1709 #----------------------------------------------------------------
1711 mapping = [
1712 (self._PRW_street, 'street'),
1713 (self._TCTRL_number, 'number'),
1714 (self._PRW_urb, 'urb'),
1715 (self._PRW_region, 'l10n_state')
1716 ]
1717
1718 # loop through fields and invalidate address searcher if different
1719 for ctrl, field in mapping:
1720 if self.__perhaps_invalidate_address_searcher(ctrl, field):
1721 return True
1722
1723 return True
1724 #----------------------------------------------------------------
1726 adr = self._PRW_address_searcher.get_address()
1727 if adr is None:
1728 return True
1729
1730 wx.CallAfter(self.__set_fields_from_address_searcher)
1731 return True
1732 #----------------------------------------------------------------
1733 # generic Edit Area mixin API
1734 #----------------------------------------------------------------
1736 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
1737 #----------------------------------------------------------------
1739
1740 # identity
1741 new_identity = gmPerson.create_identity (
1742 gender = self._PRW_gender.GetData(),
1743 dob = self._PRW_dob.GetData(),
1744 lastnames = self._PRW_lastname.GetValue().strip(),
1745 firstnames = self._PRW_firstnames.GetValue().strip()
1746 )
1747 _log.debug('identity created: %s' % new_identity)
1748
1749 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip())
1750 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u''))
1751 #TOB
1752 new_identity.save()
1753
1754 name = new_identity.get_active_name()
1755 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1756 name.save()
1757
1758 # address
1759 is_valid = self.__address_valid_for_save(empty_address_is_valid = False)
1760 if is_valid is True:
1761 # because we currently only check for non-emptiness
1762 # we must still deal with database errors
1763 try:
1764 new_identity.link_address (
1765 number = self._TCTRL_number.GetValue().strip(),
1766 street = self._PRW_street.GetValue().strip(),
1767 postcode = self._PRW_zip.GetValue().strip(),
1768 urb = self._PRW_urb.GetValue().strip(),
1769 state = self._PRW_region.GetData(),
1770 country = self._PRW_country.GetData()
1771 )
1772 except gmPG2.dbapi.InternalError:
1773 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip())
1774 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip())
1775 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip())
1776 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip())
1777 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip())
1778 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip())
1779 _log.exception('cannot link address')
1780 gmGuiHelpers.gm_show_error (
1781 aTitle = _('Saving address'),
1782 aMessage = _(
1783 'Cannot save this address.\n'
1784 '\n'
1785 'You will have to add it via the Demographics plugin.\n'
1786 )
1787 )
1788 elif is_valid is False:
1789 gmGuiHelpers.gm_show_error (
1790 aTitle = _('Saving address'),
1791 aMessage = _(
1792 'Address not saved.\n'
1793 '\n'
1794 'You will have to add it via the Demographics plugin.\n'
1795 )
1796 )
1797 # else it is None which means empty address which we ignore
1798
1799 # phone
1800 new_identity.link_comm_channel (
1801 comm_medium = u'homephone',
1802 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''),
1803 is_confidential = False
1804 )
1805
1806 # external ID
1807 pk_type = self._PRW_external_id_type.GetData()
1808 id_value = self._TCTRL_external_id_value.GetValue().strip()
1809 if (pk_type is not None) and (id_value != u''):
1810 new_identity.add_external_id(value = id_value, pk_type = pk_type)
1811
1812 # occupation
1813 new_identity.link_occupation (
1814 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'')
1815 )
1816
1817 self.data = new_identity
1818 return True
1819 #----------------------------------------------------------------
1822 #----------------------------------------------------------------
1826 #----------------------------------------------------------------
1829 #----------------------------------------------------------------
1832
1833 #============================================================
1834 # patient demographics editing classes
1835 #============================================================
1837 """Notebook displaying demographics editing pages:
1838
1839 - Identity
1840 - Contacts (addresses, phone numbers, etc)
1841 - Social network (significant others, GP, etc)
1842
1843 Does NOT act on/listen to the current patient.
1844 """
1845 #--------------------------------------------------------
1847
1848 wx.Notebook.__init__ (
1849 self,
1850 parent = parent,
1851 id = id,
1852 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
1853 name = self.__class__.__name__
1854 )
1855
1856 self.__identity = None
1857 self.__do_layout()
1858 self.SetSelection(0)
1859 #--------------------------------------------------------
1860 # public API
1861 #--------------------------------------------------------
1863 """Populate fields in pages with data from model."""
1864 for page_idx in range(self.GetPageCount()):
1865 page = self.GetPage(page_idx)
1866 page.identity = self.__identity
1867
1868 return True
1869 #--------------------------------------------------------
1870 # internal API
1871 #--------------------------------------------------------
1873 """Build patient edition notebook pages."""
1874
1875 # contacts page
1876 new_page = gmPersonContactWidgets.cPersonContactsManagerPnl(self, -1)
1877 new_page.identity = self.__identity
1878 self.AddPage (
1879 page = new_page,
1880 text = _('Contacts'),
1881 select = True
1882 )
1883
1884 # identity page
1885 new_page = cPersonIdentityManagerPnl(self, -1)
1886 new_page.identity = self.__identity
1887 self.AddPage (
1888 page = new_page,
1889 text = _('Identity'),
1890 select = False
1891 )
1892
1893 # social network page
1894 new_page = cPersonSocialNetworkManagerPnl(self, -1)
1895 new_page.identity = self.__identity
1896 self.AddPage (
1897 page = new_page,
1898 text = _('Social network'),
1899 select = False
1900 )
1901 #--------------------------------------------------------
1902 # properties
1903 #--------------------------------------------------------
1906
1908 self.__identity = identity
1909
1910 identity = property(_get_identity, _set_identity)
1911 #============================================================
1912 # old occupation widgets
1913 #============================================================
1914 # FIXME: support multiple occupations
1915 # FIXME: redo with wxGlade
1916
1918 """Page containing patient occupations edition fields.
1919 """
1921 """
1922 Creates a new instance of BasicPatDetailsPage
1923 @param parent - The parent widget
1924 @type parent - A wx.Window instance
1925 @param id - The widget id
1926 @type id - An integer
1927 """
1928 wx.Panel.__init__(self, parent, id)
1929 self.__ident = ident
1930 self.__do_layout()
1931 #--------------------------------------------------------
1933 PNL_form = wx.Panel(self, -1)
1934 # occupation
1935 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
1936 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
1937 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient"))
1938 # known since
1939 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
1940 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
1941
1942 # layout input widgets
1943 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
1944 SZR_input.AddGrowableCol(1)
1945 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
1946 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
1947 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
1948 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
1949 PNL_form.SetSizerAndFit(SZR_input)
1950
1951 # layout page
1952 SZR_main = wx.BoxSizer(wx.VERTICAL)
1953 SZR_main.Add(PNL_form, 1, wx.EXPAND)
1954 self.SetSizer(SZR_main)
1955 #--------------------------------------------------------
1958 #--------------------------------------------------------
1960 if identity is not None:
1961 self.__ident = identity
1962 jobs = self.__ident.get_occupations()
1963 if len(jobs) > 0:
1964 self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
1965 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
1966 return True
1967 #--------------------------------------------------------
1969 if self.PRW_occupation.IsModified():
1970 new_job = self.PRW_occupation.GetValue().strip()
1971 jobs = self.__ident.get_occupations()
1972 for job in jobs:
1973 if job['l10n_occupation'] == new_job:
1974 continue
1975 self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
1976 self.__ident.link_occupation(occupation = new_job)
1977 return True
1978 #============================================================
1980 """Patient demographics plugin for main notebook.
1981
1982 Hosts another notebook with pages for Identity, Contacts, etc.
1983
1984 Acts on/listens to the currently active patient.
1985 """
1986 #--------------------------------------------------------
1988 wx.Panel.__init__ (self, parent = parent, id = id, style = wx.NO_BORDER)
1989 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1990 self.__do_layout()
1991 self.__register_interests()
1992 #--------------------------------------------------------
1993 # public API
1994 #--------------------------------------------------------
1995 #--------------------------------------------------------
1996 # internal helpers
1997 #--------------------------------------------------------
1999 """Arrange widgets."""
2000 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
2001
2002 szr_main = wx.BoxSizer(wx.VERTICAL)
2003 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
2004 self.SetSizerAndFit(szr_main)
2005 #--------------------------------------------------------
2006 # event handling
2007 #--------------------------------------------------------
2009 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
2010 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
2011 #--------------------------------------------------------
2014 #--------------------------------------------------------
2017 #--------------------------------------------------------
2018 # reget mixin API
2019 #--------------------------------------------------------
2029
2030
2031 #============================================================
2032 #============================================================
2033 #============================================================
2034 #============================================================
2035 # outdated, delete soon:
2036 # new-patient wizard classes
2037 #============================================================
2039 """
2040 Wizard page for entering patient's basic demographic information
2041 """
2042
2043 form_fields = (
2044 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation',
2045 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment'
2046 )
2047
2049 """
2050 Creates a new instance of BasicPatDetailsPage
2051 @param parent - The parent widget
2052 @type parent - A wx.Window instance
2053 @param tile - The title of the page
2054 @type title - A StringType instance
2055 """
2056 wx.wizard.WizardPageSimple.__init__(self, parent) #, bitmap = gmGuiHelpers.gm_icon(_('oneperson'))
2057 self.__title = title
2058 self.__do_layout()
2059 self.__register_interests()
2060 #--------------------------------------------------------
2062 PNL_form = wx.Panel(self, -1)
2063
2064 # last name
2065 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name'))
2066 STT_lastname.SetForegroundColour('red')
2067 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1)
2068 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)'))
2069
2070 # first name
2071 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)'))
2072 STT_firstname.SetForegroundColour('red')
2073 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1)
2074 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name'))
2075
2076 # nickname
2077 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name'))
2078 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1)
2079
2080 # DOB
2081 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth'))
2082 STT_dob.SetForegroundColour('red')
2083 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1)
2084 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one"))
2085
2086 # gender
2087 STT_gender = wx.StaticText(PNL_form, -1, _('Gender'))
2088 STT_gender.SetForegroundColour('red')
2089 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1)
2090 self.PRW_gender.SetToolTipString(_("Required: gender of patient"))
2091
2092 # title
2093 STT_title = wx.StaticText(PNL_form, -1, _('Title'))
2094 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1)
2095
2096 # zip code
2097 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code'))
2098 STT_zip_code.SetForegroundColour('orange')
2099 self.PRW_zip_code = gmPersonContactWidgets.cZipcodePhraseWheel(parent = PNL_form, id = -1)
2100 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code"))
2101
2102 # street
2103 STT_street = wx.StaticText(PNL_form, -1, _('Street'))
2104 STT_street.SetForegroundColour('orange')
2105 self.PRW_street = gmPersonContactWidgets.cStreetPhraseWheel(parent = PNL_form, id = -1)
2106 self.PRW_street.SetToolTipString(_("primary/home address: name of street"))
2107
2108 # address number
2109 STT_address_number = wx.StaticText(PNL_form, -1, _('Number'))
2110 STT_address_number.SetForegroundColour('orange')
2111 self.TTC_address_number = wx.TextCtrl(PNL_form, -1)
2112 self.TTC_address_number.SetToolTipString(_("primary/home address: address number"))
2113
2114 # town
2115 STT_town = wx.StaticText(PNL_form, -1, _('Place'))
2116 STT_town.SetForegroundColour('orange')
2117 self.PRW_town = gmPersonContactWidgets.cUrbPhraseWheel(parent = PNL_form, id = -1)
2118 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/..."))
2119
2120 # state
2121 STT_state = wx.StaticText(PNL_form, -1, _('Region'))
2122 STT_state.SetForegroundColour('orange')
2123 self.PRW_state = gmPersonContactWidgets.cStateSelectionPhraseWheel(parent=PNL_form, id=-1)
2124 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/..."))
2125
2126 # country
2127 STT_country = wx.StaticText(PNL_form, -1, _('Country'))
2128 STT_country.SetForegroundColour('orange')
2129 self.PRW_country = gmPersonContactWidgets.cCountryPhraseWheel(parent = PNL_form, id = -1)
2130 self.PRW_country.SetToolTipString(_("primary/home address: country"))
2131
2132 # phone
2133 STT_phone = wx.StaticText(PNL_form, -1, _('Phone'))
2134 self.TTC_phone = wx.TextCtrl(PNL_form, -1)
2135 self.TTC_phone.SetToolTipString(_("phone number at home"))
2136
2137 # occupation
2138 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
2139 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
2140
2141 # comment
2142 STT_comment = wx.StaticText(PNL_form, -1, _('Comment'))
2143 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1)
2144 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.'))
2145
2146 # form main validator
2147 self.form_DTD = cFormDTD(fields = self.__class__.form_fields)
2148 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD))
2149
2150 # layout input widgets
2151 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4)
2152 SZR_input.AddGrowableCol(1)
2153 SZR_input.Add(STT_lastname, 0, wx.SHAPED)
2154 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND)
2155 SZR_input.Add(STT_firstname, 0, wx.SHAPED)
2156 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND)
2157 SZR_input.Add(STT_nick, 0, wx.SHAPED)
2158 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND)
2159 SZR_input.Add(STT_dob, 0, wx.SHAPED)
2160 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND)
2161 SZR_input.Add(STT_gender, 0, wx.SHAPED)
2162 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND)
2163 SZR_input.Add(STT_title, 0, wx.SHAPED)
2164 SZR_input.Add(self.PRW_title, 1, wx.EXPAND)
2165 SZR_input.Add(STT_zip_code, 0, wx.SHAPED)
2166 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND)
2167 SZR_input.Add(STT_street, 0, wx.SHAPED)
2168 SZR_input.Add(self.PRW_street, 1, wx.EXPAND)
2169 SZR_input.Add(STT_address_number, 0, wx.SHAPED)
2170 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND)
2171 SZR_input.Add(STT_town, 0, wx.SHAPED)
2172 SZR_input.Add(self.PRW_town, 1, wx.EXPAND)
2173 SZR_input.Add(STT_state, 0, wx.SHAPED)
2174 SZR_input.Add(self.PRW_state, 1, wx.EXPAND)
2175 SZR_input.Add(STT_country, 0, wx.SHAPED)
2176 SZR_input.Add(self.PRW_country, 1, wx.EXPAND)
2177 SZR_input.Add(STT_phone, 0, wx.SHAPED)
2178 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND)
2179 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
2180 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
2181 SZR_input.Add(STT_comment, 0, wx.SHAPED)
2182 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND)
2183
2184 PNL_form.SetSizerAndFit(SZR_input)
2185
2186 # layout page
2187 SZR_main = makePageTitle(self, self.__title)
2188 SZR_main.Add(PNL_form, 1, wx.EXPAND)
2189 #--------------------------------------------------------
2190 # event handling
2191 #--------------------------------------------------------
2193 self.PRW_firstname.add_callback_on_lose_focus(self.on_name_set)
2194 self.PRW_country.add_callback_on_selection(self.on_country_selected)
2195 self.PRW_zip_code.add_callback_on_lose_focus(self.on_zip_set)
2196 #--------------------------------------------------------
2198 """Set the states according to entered country."""
2199 self.PRW_state.set_context(context=u'country', val=data)
2200 return True
2201 #--------------------------------------------------------
2203 """Set the gender according to entered firstname.
2204
2205 Matches are fetched from existing records in backend.
2206 """
2207 firstname = self.PRW_firstname.GetValue().strip()
2208 rows, idx = gmPG2.run_ro_queries(queries = [{
2209 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
2210 'args': [firstname]
2211 }])
2212 if len(rows) == 0:
2213 return True
2214 wx.CallAfter(self.PRW_gender.SetData, rows[0][0])
2215 return True
2216 #--------------------------------------------------------
2218 """Set the street, town, state and country according to entered zip code."""
2219 zip_code = self.PRW_zip_code.GetValue().strip()
2220 self.PRW_street.set_context(context=u'zip', val=zip_code)
2221 self.PRW_town.set_context(context=u'zip', val=zip_code)
2222 self.PRW_state.set_context(context=u'zip', val=zip_code)
2223 self.PRW_country.set_context(context=u'zip', val=zip_code)
2224 return True
2225 #============================================================
2227 """
2228 Utility function to create the main sizer of a wizard's page.
2229
2230 @param wizPg The wizard page widget
2231 @type wizPg A wx.WizardPageSimple instance
2232 @param title The wizard page's descriptive title
2233 @type title A StringType instance
2234 """
2235 sizer = wx.BoxSizer(wx.VERTICAL)
2236 wizPg.SetSizer(sizer)
2237 title = wx.StaticText(wizPg, -1, title)
2238 title.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD))
2239 sizer.Add(title, 0, wx.ALIGN_CENTRE|wx.ALL, 2)
2240 sizer.Add(wx.StaticLine(wizPg, -1), 0, wx.EXPAND|wx.ALL, 2)
2241 return sizer
2242 #============================================================
2244 """
2245 Wizard to create a new patient.
2246
2247 TODO:
2248 - write pages for different "themes" of patient creation
2249 - make it configurable which pages are loaded
2250 - make available sets of pages that apply to a country
2251 - make loading of some pages depend upon values in earlier pages, eg
2252 when the patient is female and older than 13 include a page about
2253 "female" data (number of kids etc)
2254
2255 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable()
2256 """
2257 #--------------------------------------------------------
2258 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
2259 """
2260 Creates a new instance of NewPatientWizard
2261 @param parent - The parent widget
2262 @type parent - A wx.Window instance
2263 """
2264 id_wiz = wx.NewId()
2265 wx.wizard.Wizard.__init__(self, parent, id_wiz, title) #images.getWizTest1Bitmap()
2266 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY)
2267 self.__subtitle = subtitle
2268 self.__do_layout()
2269 #--------------------------------------------------------
2271 """Create new patient.
2272
2273 activate, too, if told to do so (and patient successfully created)
2274 """
2275 while True:
2276
2277 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details):
2278 return False
2279
2280 try:
2281 # retrieve DTD and create patient
2282 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD)
2283 except:
2284 _log.exception('cannot add new patient - missing identity fields')
2285 gmGuiHelpers.gm_show_error (
2286 _('Cannot create new patient.\n'
2287 'Missing parts of the identity.'
2288 ),
2289 _('Adding new patient')
2290 )
2291 continue
2292
2293 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2294
2295 try:
2296 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2297 except:
2298 _log.exception('cannot finalize new patient - missing address fields')
2299 gmGuiHelpers.gm_show_error (
2300 _('Cannot add address for the new patient.\n'
2301 'You must either enter all of the address fields or\n'
2302 'none at all. The relevant fields are marked in yellow.\n'
2303 '\n'
2304 'You will need to add the address details in the\n'
2305 'demographics module.'
2306 ),
2307 _('Adding new patient')
2308 )
2309 break
2310
2311 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2312
2313 break
2314
2315 if activate:
2316 from Gnumed.wxpython import gmPatSearchWidgets
2317 gmPatSearchWidgets.set_active_patient(patient = ident)
2318
2319 return ident
2320 #--------------------------------------------------------
2321 # internal helpers
2322 #--------------------------------------------------------
2324 """Arrange widgets.
2325 """
2326 # Create the wizard pages
2327 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle )
2328 self.FitToPage(self.basic_pat_details)
2329 #============================================================
2330 #============================================================
2332 """
2333 This validator is used to ensure that the user has entered all
2334 the required conditional values in the page (eg., to properly
2335 create an address, all the related fields must be filled).
2336 """
2337 #--------------------------------------------------------
2339 """
2340 Validator initialization.
2341 @param dtd The object containing the data model.
2342 @type dtd A cFormDTD instance
2343 """
2344 # initialize parent class
2345 wx.PyValidator.__init__(self)
2346 # validator's storage object
2347 self.form_DTD = dtd
2348 #--------------------------------------------------------
2350 """
2351 Standard cloner.
2352 Note that every validator must implement the Clone() method.
2353 """
2354 return cBasicPatDetailsPageValidator(dtd = self.form_DTD) # FIXME: probably need new instance of DTD ?
2355 #--------------------------------------------------------
2357 """
2358 Validate the contents of the given text control.
2359 """
2360 _pnl_form = self.GetWindow().GetParent()
2361
2362 error = False
2363
2364 # name fields
2365 if _pnl_form.PRW_lastname.GetValue().strip() == '':
2366 error = True
2367 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
2368 _pnl_form.PRW_lastname.SetBackgroundColour('pink')
2369 _pnl_form.PRW_lastname.Refresh()
2370 else:
2371 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2372 _pnl_form.PRW_lastname.Refresh()
2373
2374 if _pnl_form.PRW_firstname.GetValue().strip() == '':
2375 error = True
2376 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
2377 _pnl_form.PRW_firstname.SetBackgroundColour('pink')
2378 _pnl_form.PRW_firstname.Refresh()
2379 else:
2380 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2381 _pnl_form.PRW_firstname.Refresh()
2382
2383 # gender
2384 if _pnl_form.PRW_gender.GetData() is None:
2385 error = True
2386 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
2387 _pnl_form.PRW_gender.SetBackgroundColour('pink')
2388 _pnl_form.PRW_gender.Refresh()
2389 else:
2390 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2391 _pnl_form.PRW_gender.Refresh()
2392
2393 # dob validation
2394 if (
2395 (_pnl_form.PRW_dob.GetValue().strip() == u'')
2396 or (not _pnl_form.PRW_dob.is_valid_timestamp())
2397 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900)
2398 ):
2399 error = True
2400 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue()
2401 gmDispatcher.send(signal = 'statustext', msg = msg)
2402 _pnl_form.PRW_dob.SetBackgroundColour('pink')
2403 _pnl_form.PRW_dob.Refresh()
2404 else:
2405 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2406 _pnl_form.PRW_dob.Refresh()
2407
2408 # address
2409 is_any_field_filled = False
2410 address_fields = (
2411 _pnl_form.TTC_address_number,
2412 _pnl_form.PRW_zip_code,
2413 _pnl_form.PRW_street,
2414 _pnl_form.PRW_town
2415 )
2416 for field in address_fields:
2417 if field.GetValue().strip() == u'':
2418 if is_any_field_filled:
2419 error = True
2420 msg = _('To properly create an address, all the related fields must be filled in.')
2421 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2422 field.SetBackgroundColour('pink')
2423 field.SetFocus()
2424 field.Refresh()
2425 else:
2426 is_any_field_filled = True
2427 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2428 field.Refresh()
2429
2430 address_fields = (
2431 _pnl_form.PRW_state,
2432 _pnl_form.PRW_country
2433 )
2434 for field in address_fields:
2435 if field.GetData() is None:
2436 if is_any_field_filled:
2437 error = True
2438 msg = _('To properly create an address, all the related fields must be filled in.')
2439 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2440 field.SetBackgroundColour('pink')
2441 field.SetFocus()
2442 field.Refresh()
2443 else:
2444 is_any_field_filled = True
2445 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2446 field.Refresh()
2447
2448 return (not error)
2449 #--------------------------------------------------------
2451 """
2452 Transfer data from validator to window.
2453 The default implementation returns False, indicating that an error
2454 occurred. We simply return True, as we don't do any data transfer.
2455 """
2456 _pnl_form = self.GetWindow().GetParent()
2457 # fill in controls with values from self.form_DTD
2458 _pnl_form.PRW_gender.SetData(self.form_DTD['gender'])
2459 _pnl_form.PRW_dob.SetText(self.form_DTD['dob'])
2460 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames'])
2461 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames'])
2462 _pnl_form.PRW_title.SetText(self.form_DTD['title'])
2463 _pnl_form.PRW_nick.SetText(self.form_DTD['nick'])
2464 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation'])
2465 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number'])
2466 _pnl_form.PRW_street.SetText(self.form_DTD['street'])
2467 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code'])
2468 _pnl_form.PRW_town.SetText(self.form_DTD['town'])
2469 _pnl_form.PRW_state.SetData(self.form_DTD['state'])
2470 _pnl_form.PRW_country.SetData(self.form_DTD['country'])
2471 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone'])
2472 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment'])
2473 return True # Prevent wxDialog from complaining
2474 #--------------------------------------------------------
2476 """
2477 Transfer data from window to validator.
2478 The default implementation returns False, indicating that an error
2479 occurred. We simply return True, as we don't do any data transfer.
2480 """
2481 # FIXME: should be called automatically
2482 if not self.GetWindow().GetParent().Validate():
2483 return False
2484 try:
2485 _pnl_form = self.GetWindow().GetParent()
2486 # fill in self.form_DTD with values from controls
2487 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData()
2488 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData()
2489
2490 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue()
2491 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue()
2492 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue()
2493 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue()
2494
2495 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue()
2496
2497 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue()
2498 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue()
2499 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue()
2500 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue()
2501 self.form_DTD['state'] = _pnl_form.PRW_state.GetData()
2502 self.form_DTD['country'] = _pnl_form.PRW_country.GetData()
2503
2504 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue()
2505
2506 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue()
2507 except:
2508 return False
2509 return True
2510 #============================================================
2512 """
2513 Utility class to test the new patient wizard.
2514 """
2515 #--------------------------------------------------------
2517 """
2518 Create a new instance of TestPanel.
2519 @param parent The parent widget
2520 @type parent A wx.Window instance
2521 """
2522 wx.Panel.__init__(self, parent, id)
2523 wizard = cNewPatientWizard(self)
2524 print wizard.RunWizard()
2525 #============================================================
2526 if __name__ == "__main__":
2527
2528 #--------------------------------------------------------
2530 app = wx.PyWidgetTester(size = (600, 400))
2531 app.SetWidget(cKOrganizerSchedulePnl)
2532 app.MainLoop()
2533 #--------------------------------------------------------
2535 app = wx.PyWidgetTester(size = (600, 400))
2536 widget = cPersonNamesManagerPnl(app.frame, -1)
2537 widget.identity = activate_patient()
2538 app.frame.Show(True)
2539 app.MainLoop()
2540 #--------------------------------------------------------
2542 app = wx.PyWidgetTester(size = (600, 400))
2543 widget = cPersonIDsManagerPnl(app.frame, -1)
2544 widget.identity = activate_patient()
2545 app.frame.Show(True)
2546 app.MainLoop()
2547 #--------------------------------------------------------
2549 app = wx.PyWidgetTester(size = (600, 400))
2550 widget = cPersonIdentityManagerPnl(app.frame, -1)
2551 widget.identity = activate_patient()
2552 app.frame.Show(True)
2553 app.MainLoop()
2554 #--------------------------------------------------------
2556 app = wx.PyWidgetTester(size = (600, 400))
2557 app.SetWidget(cNameGenderDOBEditAreaPnl, name = activate_patient().get_active_name())
2558 app.MainLoop()
2559 #--------------------------------------------------------
2561 app = wx.PyWidgetTester(size = (600, 400))
2562 widget = cPersonContactsManagerPnl(app.frame, -1)
2563 widget.identity = activate_patient()
2564 app.frame.Show(True)
2565 app.MainLoop()
2566 #--------------------------------------------------------
2568 app = wx.PyWidgetTester(size = (600, 400))
2569 widget = cPersonDemographicsEditorNb(app.frame, -1)
2570 widget.identity = activate_patient()
2571 widget.refresh()
2572 app.frame.Show(True)
2573 app.MainLoop()
2574 #--------------------------------------------------------
2576 patient = gmPersonSearch.ask_for_patient()
2577 if patient is None:
2578 print "No patient. Exiting gracefully..."
2579 sys.exit(0)
2580 from Gnumed.wxpython import gmPatSearchWidgets
2581 gmPatSearchWidgets.set_active_patient(patient=patient)
2582 return patient
2583 #--------------------------------------------------------
2584 if len(sys.argv) > 1 and sys.argv[1] == 'test':
2585
2586 gmI18N.activate_locale()
2587 gmI18N.install_domain(domain='gnumed')
2588 gmPG2.get_connection()
2589
2590 # a = cFormDTD(fields = cBasicPatDetailsPage.form_fields)
2591
2592 # app = wx.PyWidgetTester(size = (400, 300))
2593 # app.SetWidget(cNotebookedPatEditionPanel, -1)
2594 # app.SetWidget(TestWizardPanel, -1)
2595 # app.frame.Show(True)
2596 # app.MainLoop()
2597
2598 # phrasewheels
2599 # test_zipcode_prw()
2600 # test_state_prw()
2601 # test_street_prw()
2602 # test_organizer_pnl()
2603 #test_address_type_prw()
2604 #test_suburb_prw()
2605 test_urb_prw()
2606 #test_address_prw()
2607
2608 # contacts related widgets
2609 #test_address_ea_pnl()
2610 #test_person_adrs_pnl()
2611 #test_person_comms_pnl()
2612 #test_pat_contacts_pnl()
2613
2614 # identity related widgets
2615 #test_person_names_pnl()
2616 #test_person_ids_pnl()
2617 #test_pat_ids_pnl()
2618 #test_name_ea_pnl()
2619
2620 #test_cPersonDemographicsEditorNb()
2621
2622 #============================================================
2623
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Wed May 4 03:58:35 2011 | http://epydoc.sourceforge.net |