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