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