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