| Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed medical document handling widgets.
2 """
3 #================================================================
4 # $Source: /cvsroot/gnumed/gnumed/gnumed/client/wxpython/gmMedDocWidgets.py,v $
5 # $Id: gmMedDocWidgets.py,v 1.187 2010/01/17 19:48:20 ncq Exp $
6 __version__ = "$Revision: 1.187 $"
7 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
8
9 import os.path, sys, re as regex, logging
10
11
12 import wx
13
14
15 if __name__ == '__main__':
16 sys.path.insert(0, '../../')
17 from Gnumed.pycommon import gmI18N, gmCfg, gmPG2, gmMimeLib, gmExceptions, gmMatchProvider, gmDispatcher, gmDateTime, gmTools, gmShellAPI, gmHooks
18 from Gnumed.business import gmPerson, gmMedDoc, gmEMRStructItems, gmSurgery
19 from Gnumed.wxpython import gmGuiHelpers, gmRegetMixin, gmPhraseWheel, gmPlugin, gmEMRStructWidgets, gmListWidgets
20 from Gnumed.wxGladeWidgets import wxgScanIdxPnl, wxgReviewDocPartDlg, wxgSelectablySortedDocTreePnl, wxgEditDocumentTypesPnl, wxgEditDocumentTypesDlg
21
22
23 _log = logging.getLogger('gm.ui')
24 _log.info(__version__)
25
26
27 default_chunksize = 1 * 1024 * 1024 # 1 MB
28 #============================================================
30
31 #-----------------------------------
32 def delete_item(item):
33 doit = gmGuiHelpers.gm_show_question (
34 _( 'Are you sure you want to delete this\n'
35 'description from the document ?\n'
36 ),
37 _('Deleting document description')
38 )
39 if not doit:
40 return True
41
42 document.delete_description(pk = item[0])
43 return True
44 #-----------------------------------
45 def add_item():
46 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
47 parent,
48 -1,
49 title = _('Adding document description'),
50 msg = _('Below you can add a document description.\n')
51 )
52 result = dlg.ShowModal()
53 if result == wx.ID_SAVE:
54 document.add_description(dlg.value)
55
56 dlg.Destroy()
57 return True
58 #-----------------------------------
59 def edit_item(item):
60 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
61 parent,
62 -1,
63 title = _('Editing document description'),
64 msg = _('Below you can edit the document description.\n'),
65 text = item[1]
66 )
67 result = dlg.ShowModal()
68 if result == wx.ID_SAVE:
69 document.update_description(pk = item[0], description = dlg.value)
70
71 dlg.Destroy()
72 return True
73 #-----------------------------------
74 def refresh_list(lctrl):
75 descriptions = document.get_descriptions()
76
77 lctrl.set_string_items(items = [
78 u'%s%s' % ( (u' '.join(regex.split('\r\n+|\r+|\n+|\t+', desc[1])))[:30], gmTools.u_ellipsis )
79 for desc in descriptions
80 ])
81 lctrl.set_data(data = descriptions)
82 #-----------------------------------
83
84 gmListWidgets.get_choices_from_list (
85 parent = parent,
86 msg = _('Select the description you are interested in.\n'),
87 caption = _('Managing document descriptions'),
88 columns = [_('Description')],
89 edit_callback = edit_item,
90 new_callback = add_item,
91 delete_callback = delete_item,
92 refresh_callback = refresh_list,
93 single_selection = True,
94 can_return_empty = True
95 )
96
97 return True
98 #============================================================
100 wx.CallAfter(save_file_as_new_document, **kwargs)
101 #----------------------
102 -def save_file_as_new_document(parent=None, filename=None, document_type=None, unlock_patient=False, **kwargs):
103
104 pat = gmPerson.gmCurrentPatient()
105 if not pat.connected:
106 return None
107
108 emr = pat.get_emr()
109
110 all_epis = emr.get_episodes()
111 # FIXME: what to do here ? probably create dummy episode
112 if len(all_epis) == 0:
113 epi = emr.add_episode(episode_name = _('Documents'), is_open = False)
114 else:
115 # FIXME: parent=None map to toplevel window
116 dlg = gmEMRStructWidgets.cEpisodeListSelectorDlg(parent = parent, id = -1, episodes = all_epis)
117 dlg.SetTitle(_('Select the episode under which to file the document ...'))
118 btn_pressed = dlg.ShowModal()
119 epi = dlg.get_selected_item_data(only_one = True)
120 dlg.Destroy()
121
122 if btn_pressed == wx.ID_CANCEL:
123 if unlock_patient:
124 pat.locked = False
125 return None
126
127 doc_type = gmMedDoc.create_document_type(document_type = document_type)
128
129 docs_folder = pat.get_document_folder()
130 doc = docs_folder.add_document (
131 document_type = doc_type['pk_doc_type'],
132 encounter = emr.active_encounter['pk_encounter'],
133 episode = epi['pk_episode']
134 )
135 part = doc.add_part(file = filename)
136 part['filename'] = filename
137 part.save_payload()
138
139 if unlock_patient:
140 pat.locked = False
141
142 gmDispatcher.send(signal = 'statustext', msg = _('Imported new document from [%s].' % filename), beep = True)
143
144 return doc
145 #----------------------
146 gmDispatcher.connect(signal = u'import_document_from_file', receiver = _save_file_as_new_document)
147 #============================================================
149 """Let user select a document comment from all existing comments."""
151
152 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
153
154 context = {
155 u'ctxt_doc_type': {
156 u'where_part': u'and fk_type = %(pk_doc_type)s',
157 u'placeholder': u'pk_doc_type'
158 }
159 }
160
161 mp = gmMatchProvider.cMatchProvider_SQL2 (
162 queries = [u"""
163 select *
164 from (
165 select distinct on (comment) *
166 from (
167 -- keyed by doc type
168 select comment, comment as pk, 1 as rank
169 from blobs.doc_med
170 where
171 comment %(fragment_condition)s
172 %(ctxt_doc_type)s
173
174 union all
175
176 select comment, comment as pk, 2 as rank
177 from blobs.doc_med
178 where comment %(fragment_condition)s
179 ) as q_union
180 ) as q_distinct
181 order by rank, comment
182 limit 25"""],
183 context = context
184 )
185 mp.setThresholds(3, 5, 7)
186 mp.unset_context(u'pk_doc_type')
187
188 self.matcher = mp
189 self.picklist_delay = 50
190
191 self.SetToolTipString(_('Enter a comment on the document.'))
192 #============================================================
194 """A dialog showing a cEditDocumentTypesPnl."""
195
197 wxgEditDocumentTypesDlg.wxgEditDocumentTypesDlg.__init__(self, *args, **kwargs)
198
199 #============================================================
201 """A panel grouping together fields to edit the list of document types."""
202
204 wxgEditDocumentTypesPnl.wxgEditDocumentTypesPnl.__init__(self, *args, **kwargs)
205 self.__init_ui()
206 self.__register_interests()
207 self.repopulate_ui()
208 #--------------------------------------------------------
210 self._LCTRL_doc_type.set_columns([_('Type'), _('Translation'), _('User defined'), _('In use')])
211 self._LCTRL_doc_type.set_column_widths()
212 #--------------------------------------------------------
215 #--------------------------------------------------------
217 wx.CallAfter(self.repopulate_ui)
218 #--------------------------------------------------------
220
221 self._LCTRL_doc_type.DeleteAllItems()
222
223 doc_types = gmMedDoc.get_document_types()
224 pos = len(doc_types) + 1
225
226 for doc_type in doc_types:
227 row_num = self._LCTRL_doc_type.InsertStringItem(pos, label = doc_type['type'])
228 self._LCTRL_doc_type.SetStringItem(index = row_num, col = 1, label = doc_type['l10n_type'])
229 if doc_type['is_user_defined']:
230 self._LCTRL_doc_type.SetStringItem(index = row_num, col = 2, label = ' X ')
231 if doc_type['is_in_use']:
232 self._LCTRL_doc_type.SetStringItem(index = row_num, col = 3, label = ' X ')
233
234 if len(doc_types) > 0:
235 self._LCTRL_doc_type.set_data(data = doc_types)
236 self._LCTRL_doc_type.SetColumnWidth(col=0, width=wx.LIST_AUTOSIZE)
237 self._LCTRL_doc_type.SetColumnWidth(col=1, width=wx.LIST_AUTOSIZE)
238 self._LCTRL_doc_type.SetColumnWidth(col=2, width=wx.LIST_AUTOSIZE_USEHEADER)
239 self._LCTRL_doc_type.SetColumnWidth(col=3, width=wx.LIST_AUTOSIZE_USEHEADER)
240
241 self._TCTRL_type.SetValue('')
242 self._TCTRL_l10n_type.SetValue('')
243
244 self._BTN_set_translation.Enable(False)
245 self._BTN_delete.Enable(False)
246 self._BTN_add.Enable(False)
247 self._BTN_reassign.Enable(False)
248
249 self._LCTRL_doc_type.SetFocus()
250 #--------------------------------------------------------
251 # event handlers
252 #--------------------------------------------------------
254 doc_type = self._LCTRL_doc_type.get_selected_item_data()
255
256 self._TCTRL_type.SetValue(doc_type['type'])
257 self._TCTRL_l10n_type.SetValue(doc_type['l10n_type'])
258
259 self._BTN_set_translation.Enable(True)
260 self._BTN_delete.Enable(not bool(doc_type['is_in_use']))
261 self._BTN_add.Enable(False)
262 self._BTN_reassign.Enable(True)
263
264 return
265 #--------------------------------------------------------
267 self._BTN_set_translation.Enable(False)
268 self._BTN_delete.Enable(False)
269 self._BTN_reassign.Enable(False)
270
271 self._BTN_add.Enable(True)
272 # self._LCTRL_doc_type.deselect_selected_item()
273 return
274 #--------------------------------------------------------
281 #--------------------------------------------------------
298 #--------------------------------------------------------
308 #--------------------------------------------------------
340 #============================================================
342 """Let user select a document type."""
344
345 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
346
347 mp = gmMatchProvider.cMatchProvider_SQL2 (
348 queries = [
349 u"""select * from ((
350 select pk_doc_type, l10n_type, 1 as rank from blobs.v_doc_type where
351 is_user_defined is True and
352 l10n_type %(fragment_condition)s
353 ) union (
354 select pk_doc_type, l10n_type, 2 from blobs.v_doc_type where
355 is_user_defined is False and
356 l10n_type %(fragment_condition)s
357 )) as q1 order by q1.rank, q1.l10n_type
358 """]
359 )
360 mp.setThresholds(2, 4, 6)
361
362 self.matcher = mp
363 self.picklist_delay = 50
364
365 self.SetToolTipString(_('Select the document type.'))
366 #--------------------------------------------------------
372 #============================================================
375 """Support parts and docs now.
376 """
377 part = kwds['part']
378 del kwds['part']
379 wxgReviewDocPartDlg.wxgReviewDocPartDlg.__init__(self, *args, **kwds)
380
381 if isinstance(part, gmMedDoc.cMedDocPart):
382 self.__part = part
383 self.__doc = self.__part.get_containing_document()
384 self.__reviewing_doc = False
385 elif isinstance(part, gmMedDoc.cMedDoc):
386 self.__doc = part
387 self.__part = self.__doc.get_parts()[0]
388 self.__reviewing_doc = True
389 else:
390 raise ValueError('<part> must be gmMedDoc.cMedDoc or gmMedDoc.cMedDocPart instance, got <%s>' % type(part))
391
392 self.__init_ui_data()
393 #--------------------------------------------------------
394 # internal API
395 #--------------------------------------------------------
397 # FIXME: fix this
398 # associated episode (add " " to avoid popping up pick list)
399 self._PhWheel_episode.SetText('%s ' % self.__part['episode'], self.__part['pk_episode'])
400 self._PhWheel_doc_type.SetText(value = self.__part['l10n_type'], data = self.__part['pk_type'])
401 self._PhWheel_doc_type.add_callback_on_set_focus(self._on_doc_type_gets_focus)
402 self._PhWheel_doc_type.add_callback_on_lose_focus(self._on_doc_type_loses_focus)
403
404 if self.__reviewing_doc:
405 self._PRW_doc_comment.SetText(gmTools.coalesce(self.__part['doc_comment'], ''))
406 self._PRW_doc_comment.set_context(context = 'pk_doc_type', val = self.__part['pk_type'])
407 else:
408 self._PRW_doc_comment.SetText(gmTools.coalesce(self.__part['obj_comment'], ''))
409
410 fts = gmDateTime.cFuzzyTimestamp(timestamp = self.__part['date_generated'])
411 self._PhWheel_doc_date.SetText(fts.strftime('%Y-%m-%d'), fts)
412 self._TCTRL_reference.SetValue(gmTools.coalesce(self.__part['ext_ref'], ''))
413 if self.__reviewing_doc:
414 self._TCTRL_filename.Enable(False)
415 self._SPINCTRL_seq_idx.Enable(False)
416 else:
417 self._TCTRL_filename.SetValue(gmTools.coalesce(self.__part['filename'], ''))
418 self._SPINCTRL_seq_idx.SetValue(gmTools.coalesce(self.__part['seq_idx'], 0))
419
420 self._LCTRL_existing_reviews.InsertColumn(0, _('who'))
421 self._LCTRL_existing_reviews.InsertColumn(1, _('when'))
422 self._LCTRL_existing_reviews.InsertColumn(2, _('+/-'))
423 self._LCTRL_existing_reviews.InsertColumn(3, _('!'))
424 self._LCTRL_existing_reviews.InsertColumn(4, _('comment'))
425
426 self.__reload_existing_reviews()
427
428 if self._LCTRL_existing_reviews.GetItemCount() > 0:
429 self._LCTRL_existing_reviews.SetColumnWidth(col=0, width=wx.LIST_AUTOSIZE)
430 self._LCTRL_existing_reviews.SetColumnWidth(col=1, width=wx.LIST_AUTOSIZE)
431 self._LCTRL_existing_reviews.SetColumnWidth(col=2, width=wx.LIST_AUTOSIZE_USEHEADER)
432 self._LCTRL_existing_reviews.SetColumnWidth(col=3, width=wx.LIST_AUTOSIZE_USEHEADER)
433 self._LCTRL_existing_reviews.SetColumnWidth(col=4, width=wx.LIST_AUTOSIZE)
434
435 me = gmPerson.gmCurrentProvider()
436 if self.__part['pk_intended_reviewer'] == me['pk_staff']:
437 msg = _('(you are the primary reviewer)')
438 else:
439 msg = _('(someone else is the primary reviewer)')
440 self._TCTRL_responsible.SetValue(msg)
441
442 # init my review if any
443 if self.__part['reviewed_by_you']:
444 revs = self.__part.get_reviews()
445 for rev in revs:
446 if rev['is_your_review']:
447 self._ChBOX_abnormal.SetValue(bool(rev[2]))
448 self._ChBOX_relevant.SetValue(bool(rev[3]))
449 break
450
451 self._ChBOX_sign_all_pages.SetValue(self.__reviewing_doc)
452
453 return True
454 #--------------------------------------------------------
456 self._LCTRL_existing_reviews.DeleteAllItems()
457 revs = self.__part.get_reviews() # FIXME: this is ugly as sin, it should be dicts, not lists
458 if len(revs) == 0:
459 return True
460 # find special reviews
461 review_by_responsible_doc = None
462 reviews_by_others = []
463 for rev in revs:
464 if rev['is_review_by_responsible_reviewer'] and not rev['is_your_review']:
465 review_by_responsible_doc = rev
466 if not (rev['is_review_by_responsible_reviewer'] or rev['is_your_review']):
467 reviews_by_others.append(rev)
468 # display them
469 if review_by_responsible_doc is not None:
470 row_num = self._LCTRL_existing_reviews.InsertStringItem(sys.maxint, label=review_by_responsible_doc[0])
471 self._LCTRL_existing_reviews.SetItemTextColour(row_num, col=wx.BLUE)
472 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=0, label=review_by_responsible_doc[0])
473 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=1, label=review_by_responsible_doc[1].strftime('%x %H:%M'))
474 if review_by_responsible_doc['is_technically_abnormal']:
475 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=2, label=u'X')
476 if review_by_responsible_doc['clinically_relevant']:
477 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=3, label=u'X')
478 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=4, label=review_by_responsible_doc[6])
479 row_num += 1
480 for rev in reviews_by_others:
481 row_num = self._LCTRL_existing_reviews.InsertStringItem(sys.maxint, label=rev[0])
482 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=0, label=rev[0])
483 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=1, label=rev[1].strftime('%x %H:%M'))
484 if rev['is_technically_abnormal']:
485 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=2, label=u'X')
486 if rev['clinically_relevant']:
487 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=3, label=u'X')
488 self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=4, label=rev[6])
489 return True
490 #--------------------------------------------------------
491 # event handlers
492 #--------------------------------------------------------
565 #--------------------------------------------------------
567 state = self._ChBOX_review.GetValue()
568 self._ChBOX_abnormal.Enable(enable = state)
569 self._ChBOX_relevant.Enable(enable = state)
570 self._ChBOX_responsible.Enable(enable = state)
571 #--------------------------------------------------------
573 """Per Jim: Changing the doc type happens a lot more often
574 then correcting spelling, hence select-all on getting focus.
575 """
576 self._PhWheel_doc_type.SetSelection(-1, -1)
577 #--------------------------------------------------------
579 pk_doc_type = self._PhWheel_doc_type.GetData()
580 if pk_doc_type is None:
581 self._PRW_doc_comment.unset_context(context = 'pk_doc_type')
582 else:
583 self._PRW_doc_comment.set_context(context = 'pk_doc_type', val = pk_doc_type)
584 return True
585 #============================================================
588 wxgScanIdxPnl.wxgScanIdxPnl.__init__(self, *args, **kwds)
589 gmPlugin.cPatientChange_PluginMixin.__init__(self)
590
591 self._PhWheel_reviewer.matcher = gmPerson.cMatchProvider_Provider()
592
593 self.__init_ui_data()
594 self._PhWheel_doc_type.add_callback_on_lose_focus(self._on_doc_type_loses_focus)
595
596 # make me and listctrl a file drop target
597 dt = gmGuiHelpers.cFileDropTarget(self)
598 self.SetDropTarget(dt)
599 dt = gmGuiHelpers.cFileDropTarget(self._LBOX_doc_pages)
600 self._LBOX_doc_pages.SetDropTarget(dt)
601 self._LBOX_doc_pages.add_filenames = self.add_filenames_to_listbox
602
603 # do not import globally since we might want to use
604 # this module without requiring any scanner to be available
605 from Gnumed.pycommon import gmScanBackend
606 self.scan_module = gmScanBackend
607 #--------------------------------------------------------
608 # file drop target API
609 #--------------------------------------------------------
611 self.add_filenames(filenames=filenames)
612 #--------------------------------------------------------
614 pat = gmPerson.gmCurrentPatient()
615 if not pat.connected:
616 gmDispatcher.send(signal='statustext', msg=_('Cannot accept new documents. No active patient.'))
617 return
618
619 # dive into folders dropped onto us and extract files (one level deep only)
620 real_filenames = []
621 for pathname in filenames:
622 try:
623 files = os.listdir(pathname)
624 gmDispatcher.send(signal='statustext', msg=_('Extracting files from folder [%s] ...') % pathname)
625 for file in files:
626 fullname = os.path.join(pathname, file)
627 if not os.path.isfile(fullname):
628 continue
629 real_filenames.append(fullname)
630 except OSError:
631 real_filenames.append(pathname)
632
633 self.acquired_pages.extend(real_filenames)
634 self.__reload_LBOX_doc_pages()
635 #--------------------------------------------------------
638 #--------------------------------------------------------
639 # patient change plugin API
640 #--------------------------------------------------------
644 #--------------------------------------------------------
647 #--------------------------------------------------------
648 # internal API
649 #--------------------------------------------------------
651 # -----------------------------
652 self._PhWheel_episode.SetText('')
653 self._PhWheel_doc_type.SetText('')
654 # -----------------------------
655 # FIXME: make this configurable: either now() or last_date()
656 fts = gmDateTime.cFuzzyTimestamp()
657 self._PhWheel_doc_date.SetText(fts.strftime('%Y-%m-%d'), fts)
658 self._PRW_doc_comment.SetText('')
659 # FIXME: should be set to patient's primary doc
660 self._PhWheel_reviewer.selection_only = True
661 me = gmPerson.gmCurrentProvider()
662 self._PhWheel_reviewer.SetText (
663 value = u'%s (%s%s %s)' % (me['short_alias'], me['title'], me['firstnames'], me['lastnames']),
664 data = me['pk_staff']
665 )
666 # -----------------------------
667 # FIXME: set from config item
668 self._ChBOX_reviewed.SetValue(False)
669 self._ChBOX_abnormal.Disable()
670 self._ChBOX_abnormal.SetValue(False)
671 self._ChBOX_relevant.Disable()
672 self._ChBOX_relevant.SetValue(False)
673 # -----------------------------
674 self._TBOX_description.SetValue('')
675 # -----------------------------
676 # the list holding our page files
677 self._LBOX_doc_pages.Clear()
678 self.acquired_pages = []
679 #--------------------------------------------------------
681 self._LBOX_doc_pages.Clear()
682 if len(self.acquired_pages) > 0:
683 for i in range(len(self.acquired_pages)):
684 fname = self.acquired_pages[i]
685 self._LBOX_doc_pages.Append(_('part %s: %s' % (i+1, fname)), fname)
686 #--------------------------------------------------------
688 title = _('saving document')
689
690 if self.acquired_pages is None or len(self.acquired_pages) == 0:
691 dbcfg = gmCfg.cCfgSQL()
692 allow_empty = bool(dbcfg.get2 (
693 option = u'horstspace.scan_index.allow_partless_documents',
694 workplace = gmSurgery.gmCurrentPractice().active_workplace,
695 bias = 'user',
696 default = False
697 ))
698 if allow_empty:
699 save_empty = gmGuiHelpers.gm_show_question (
700 aMessage = _('No parts to save. Really save an empty document as a reference ?'),
701 aTitle = title
702 )
703 if not save_empty:
704 return False
705 else:
706 gmGuiHelpers.gm_show_error (
707 aMessage = _('No parts to save. Aquire some parts first.'),
708 aTitle = title
709 )
710 return False
711
712 doc_type_pk = self._PhWheel_doc_type.GetData(can_create = True)
713 if doc_type_pk is None:
714 gmGuiHelpers.gm_show_error (
715 aMessage = _('No document type applied. Choose a document type'),
716 aTitle = title
717 )
718 return False
719
720 # this should optional, actually
721 # if self._PRW_doc_comment.GetValue().strip() == '':
722 # gmGuiHelpers.gm_show_error (
723 # aMessage = _('No document comment supplied. Add a comment for this document.'),
724 # aTitle = title
725 # )
726 # return False
727
728 if self._PhWheel_episode.GetValue().strip() == '':
729 gmGuiHelpers.gm_show_error (
730 aMessage = _('You must select an episode to save this document under.'),
731 aTitle = title
732 )
733 return False
734
735 if self._PhWheel_reviewer.GetData() is None:
736 gmGuiHelpers.gm_show_error (
737 aMessage = _('You need to select from the list of staff members the doctor who is intended to sign the document.'),
738 aTitle = title
739 )
740 return False
741
742 return True
743 #--------------------------------------------------------
745
746 if not reconfigure:
747 dbcfg = gmCfg.cCfgSQL()
748 device = dbcfg.get2 (
749 option = 'external.xsane.default_device',
750 workplace = gmSurgery.gmCurrentPractice().active_workplace,
751 bias = 'workplace',
752 default = ''
753 )
754 if device.strip() == u'':
755 device = None
756 if device is not None:
757 return device
758
759 try:
760 devices = self.scan_module.get_devices()
761 except:
762 _log.exception('cannot retrieve list of image sources')
763 gmDispatcher.send(signal = 'statustext', msg = _('There is no scanner support installed on this machine.'))
764 return None
765
766 if devices is None:
767 # get_devices() not implemented for TWAIN yet
768 # XSane has its own chooser (so does TWAIN)
769 return None
770
771 if len(devices) == 0:
772 gmDispatcher.send(signal = 'statustext', msg = _('Cannot find an active scanner.'))
773 return None
774
775 # device_names = []
776 # for device in devices:
777 # device_names.append('%s (%s)' % (device[2], device[0]))
778
779 device = gmListWidgets.get_choices_from_list (
780 parent = self,
781 msg = _('Select an image capture device'),
782 caption = _('device selection'),
783 choices = [ '%s (%s)' % (d[2], d[0]) for d in devices ],
784 columns = [_('Device')],
785 data = devices,
786 single_selection = True
787 )
788 if device is None:
789 return None
790
791 # FIXME: add support for actually reconfiguring
792 return device[0]
793 #--------------------------------------------------------
794 # event handling API
795 #--------------------------------------------------------
797
798 chosen_device = self.get_device_to_use()
799
800 tmpdir = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
801 try:
802 gmTools.mkdir(tmpdir)
803 except:
804 tmpdir = None
805
806 # FIXME: configure whether to use XSane or sane directly
807 # FIXME: add support for xsane_device_settings argument
808 try:
809 fnames = self.scan_module.acquire_pages_into_files (
810 device = chosen_device,
811 delay = 5,
812 tmpdir = tmpdir,
813 calling_window = self
814 )
815 except ImportError:
816 gmGuiHelpers.gm_show_error (
817 aMessage = _(
818 'No pages could be acquired from the source.\n\n'
819 'This may mean the scanner driver is not properly installed\n\n'
820 'On Windows you must install the TWAIN Python module\n'
821 'while on Linux and MacOSX it is recommended to install\n'
822 'the XSane package.'
823 ),
824 aTitle = _('acquiring page')
825 )
826 return None
827
828 if len(fnames) == 0: # no pages scanned
829 return True
830
831 self.acquired_pages.extend(fnames)
832 self.__reload_LBOX_doc_pages()
833
834 return True
835 #--------------------------------------------------------
837 # patient file chooser
838 dlg = wx.FileDialog (
839 parent = None,
840 message = _('Choose a file'),
841 defaultDir = os.path.expanduser(os.path.join('~', 'gnumed')),
842 defaultFile = '',
843 wildcard = "%s (*)|*|TIFFs (*.tif)|*.tif|JPEGs (*.jpg)|*.jpg|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')),
844 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST | wx.MULTIPLE
845 )
846 result = dlg.ShowModal()
847 if result != wx.ID_CANCEL:
848 files = dlg.GetPaths()
849 for file in files:
850 self.acquired_pages.append(file)
851 self.__reload_LBOX_doc_pages()
852 dlg.Destroy()
853 #--------------------------------------------------------
855 # did user select a page ?
856 page_idx = self._LBOX_doc_pages.GetSelection()
857 if page_idx == -1:
858 gmGuiHelpers.gm_show_info (
859 aMessage = _('You must select a part before you can view it.'),
860 aTitle = _('displaying part')
861 )
862 return None
863 # now, which file was that again ?
864 page_fname = self._LBOX_doc_pages.GetClientData(page_idx)
865 (result, msg) = gmMimeLib.call_viewer_on_file(page_fname)
866 if not result:
867 gmGuiHelpers.gm_show_warning (
868 aMessage = _('Cannot display document part:\n%s') % msg,
869 aTitle = _('displaying part')
870 )
871 return None
872 return 1
873 #--------------------------------------------------------
875 page_idx = self._LBOX_doc_pages.GetSelection()
876 if page_idx == -1:
877 gmGuiHelpers.gm_show_info (
878 aMessage = _('You must select a part before you can delete it.'),
879 aTitle = _('deleting part')
880 )
881 return None
882 page_fname = self._LBOX_doc_pages.GetClientData(page_idx)
883
884 # 1) del item from self.acquired_pages
885 self.acquired_pages[page_idx:(page_idx+1)] = []
886
887 # 2) reload list box
888 self.__reload_LBOX_doc_pages()
889
890 # 3) kill file in the file system
891 do_delete = gmGuiHelpers.gm_show_question (
892 _(
893 """Do you want to permanently delete the file
894
895 [%s]
896
897 from your computer ?
898
899 If it is a temporary file for a page you just scanned
900 in this makes a lot of sense. In other cases you may
901 not want to lose the file.
902
903 Pressing [YES] will permanently remove the file
904 from your computer.""") % page_fname,
905 _('deleting part')
906 )
907 if do_delete:
908 try:
909 os.remove(page_fname)
910 except:
911 _log.exception('Error deleting file.')
912 gmGuiHelpers.gm_show_error (
913 aMessage = _('Cannot delete part in file [%s].\n\nYou may not have write access to it.') % page_fname,
914 aTitle = _('deleting part')
915 )
916
917 return 1
918 #--------------------------------------------------------
920
921 if not self.__valid_for_save():
922 return False
923
924 wx.BeginBusyCursor()
925
926 pat = gmPerson.gmCurrentPatient()
927 doc_folder = pat.get_document_folder()
928 emr = pat.get_emr()
929
930 # create new document
931 pk_episode = self._PhWheel_episode.GetData()
932 if pk_episode is None:
933 episode = emr.add_episode (
934 episode_name = self._PhWheel_episode.GetValue().strip(),
935 is_open = True
936 )
937 if episode is None:
938 wx.EndBusyCursor()
939 gmGuiHelpers.gm_show_error (
940 aMessage = _('Cannot start episode [%s].') % self._PhWheel_episode.GetValue().strip(),
941 aTitle = _('saving document')
942 )
943 return False
944 pk_episode = episode['pk_episode']
945
946 encounter = emr.active_encounter['pk_encounter']
947 document_type = self._PhWheel_doc_type.GetData()
948 new_doc = doc_folder.add_document(document_type, encounter, pk_episode)
949 if new_doc is None:
950 wx.EndBusyCursor()
951 gmGuiHelpers.gm_show_error (
952 aMessage = _('Cannot create new document.'),
953 aTitle = _('saving document')
954 )
955 return False
956
957 # update business object with metadata
958 # - date of generation
959 new_doc['clin_when'] = self._PhWheel_doc_date.GetData().get_pydt()
960 # - external reference
961 ref = gmMedDoc.get_ext_ref()
962 if ref is not None:
963 new_doc['ext_ref'] = ref
964 # - comment
965 comment = self._PRW_doc_comment.GetLineText(0).strip()
966 if comment != u'':
967 new_doc['comment'] = comment
968 # - save it
969 if not new_doc.save_payload():
970 wx.EndBusyCursor()
971 gmGuiHelpers.gm_show_error (
972 aMessage = _('Cannot update document metadata.'),
973 aTitle = _('saving document')
974 )
975 return False
976 # - long description
977 description = self._TBOX_description.GetValue().strip()
978 if description != '':
979 if not new_doc.add_description(description):
980 wx.EndBusyCursor()
981 gmGuiHelpers.gm_show_error (
982 aMessage = _('Cannot add document description.'),
983 aTitle = _('saving document')
984 )
985 return False
986
987 # add document parts from files
988 success, msg, filename = new_doc.add_parts_from_files(files=self.acquired_pages, reviewer=self._PhWheel_reviewer.GetData())
989 if not success:
990 wx.EndBusyCursor()
991 gmGuiHelpers.gm_show_error (
992 aMessage = msg,
993 aTitle = _('saving document')
994 )
995 return False
996
997 # set reviewed status
998 if self._ChBOX_reviewed.GetValue():
999 if not new_doc.set_reviewed (
1000 technically_abnormal = self._ChBOX_abnormal.GetValue(),
1001 clinically_relevant = self._ChBOX_relevant.GetValue()
1002 ):
1003 msg = _('Error setting "reviewed" status of new document.')
1004
1005 gmHooks.run_hook_script(hook = u'after_new_doc_created')
1006
1007 # inform user
1008 cfg = gmCfg.cCfgSQL()
1009 show_id = bool (
1010 cfg.get2 (
1011 option = 'horstspace.scan_index.show_doc_id',
1012 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1013 bias = 'user'
1014 )
1015 )
1016 wx.EndBusyCursor()
1017 if show_id and (ref is not None):
1018 msg = _(
1019 """The reference ID for the new document is:
1020
1021 <%s>
1022
1023 You probably want to write it down on the
1024 original documents.
1025
1026 If you don't care about the ID you can switch
1027 off this message in the GNUmed configuration.""") % ref
1028 gmGuiHelpers.gm_show_info (
1029 aMessage = msg,
1030 aTitle = _('saving document')
1031 )
1032 else:
1033 gmDispatcher.send(signal='statustext', msg=_('Successfully saved new document.'))
1034
1035 self.__init_ui_data()
1036 return True
1037 #--------------------------------------------------------
1040 #--------------------------------------------------------
1042 self._ChBOX_abnormal.Enable(enable = self._ChBOX_reviewed.GetValue())
1043 self._ChBOX_relevant.Enable(enable = self._ChBOX_reviewed.GetValue())
1044 #--------------------------------------------------------
1046 pk_doc_type = self._PhWheel_doc_type.GetData()
1047 if pk_doc_type is None:
1048 self._PRW_doc_comment.unset_context(context = 'pk_doc_type')
1049 else:
1050 self._PRW_doc_comment.set_context(context = 'pk_doc_type', val = pk_doc_type)
1051 return True
1052 #============================================================
1053 -class cSelectablySortedDocTreePnl(wxgSelectablySortedDocTreePnl.wxgSelectablySortedDocTreePnl):
1054 """A panel with a document tree which can be sorted."""
1055 #--------------------------------------------------------
1056 # inherited event handlers
1057 #--------------------------------------------------------
1059 self._doc_tree.sort_mode = 'age'
1060 self._doc_tree.SetFocus()
1061 self._rbtn_sort_by_age.SetValue(True)
1062 #--------------------------------------------------------
1064 self._doc_tree.sort_mode = 'review'
1065 self._doc_tree.SetFocus()
1066 self._rbtn_sort_by_review.SetValue(True)
1067 #--------------------------------------------------------
1069 self._doc_tree.sort_mode = 'episode'
1070 self._doc_tree.SetFocus()
1071 self._rbtn_sort_by_episode.SetValue(True)
1072 #--------------------------------------------------------
1077 #============================================================
1079 # FIXME: handle expansion state
1080 """This wx.TreeCtrl derivative displays a tree view of stored medical documents.
1081
1082 It listens to document and patient changes and updated itself accordingly.
1083 """
1084 _sort_modes = ['age', 'review', 'episode', 'type']
1085 _root_node_labels = None
1086 #--------------------------------------------------------
1088 """Set up our specialised tree.
1089 """
1090 kwds['style'] = wx.TR_NO_BUTTONS | wx.NO_BORDER
1091 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds)
1092
1093 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1094
1095 tmp = _('available documents (%s)')
1096 unsigned = _('unsigned (%s) on top') % u'\u270D'
1097 cDocTree._root_node_labels = {
1098 'age': tmp % _('most recent on top'),
1099 'review': tmp % unsigned,
1100 'episode': tmp % _('sorted by episode'),
1101 'type': tmp % _('sorted by type')
1102 }
1103
1104 self.root = None
1105 self.__sort_mode = 'age'
1106
1107 self.__build_context_menus()
1108 self.__register_interests()
1109 self._schedule_data_reget()
1110 #--------------------------------------------------------
1111 # external API
1112 #--------------------------------------------------------
1114
1115 node = self.GetSelection()
1116 node_data = self.GetPyData(node)
1117
1118 if not isinstance(node_data, gmMedDoc.cMedDocPart):
1119 return True
1120
1121 self.__display_part(part = node_data)
1122 return True
1123 #--------------------------------------------------------
1124 # properties
1125 #--------------------------------------------------------
1128 #-----
1130 if mode is None:
1131 mode = 'age'
1132
1133 if mode == self.__sort_mode:
1134 return
1135
1136 if mode not in cDocTree._sort_modes:
1137 raise ValueError('invalid document tree sort mode [%s], valid modes: %s' % (mode, cDocTree._sort_modes))
1138
1139 self.__sort_mode = mode
1140
1141 curr_pat = gmPerson.gmCurrentPatient()
1142 if not curr_pat.connected:
1143 return
1144
1145 self._schedule_data_reget()
1146 #-----
1147 sort_mode = property(_get_sort_mode, _set_sort_mode)
1148 #--------------------------------------------------------
1149 # reget-on-paint API
1150 #--------------------------------------------------------
1152 curr_pat = gmPerson.gmCurrentPatient()
1153 if not curr_pat.connected:
1154 gmDispatcher.send(signal = 'statustext', msg = _('Cannot load documents. No active patient.'))
1155 return False
1156
1157 if not self.__populate_tree():
1158 return False
1159
1160 return True
1161 #--------------------------------------------------------
1162 # internal helpers
1163 #--------------------------------------------------------
1165 # connect handlers
1166 wx.EVT_TREE_ITEM_ACTIVATED (self, self.GetId(), self._on_activate)
1167 wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self.__on_right_click)
1168
1169 # wx.EVT_LEFT_DCLICK(self.tree, self.OnLeftDClick)
1170
1171 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
1172 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1173 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_doc_mod_db)
1174 gmDispatcher.connect(signal = u'doc_page_mod_db', receiver = self._on_doc_page_mod_db)
1175 #--------------------------------------------------------
1250
1251 # document / description
1252 # self.__desc_menu = wx.Menu()
1253 # ID = wx.NewId()
1254 # self.__doc_context_menu.AppendMenu(ID, _('Descriptions ...'), self.__desc_menu)
1255
1256 # ID = wx.NewId()
1257 # self.__desc_menu.Append(ID, _('Add new description'))
1258 # wx.EVT_MENU(self.__desc_menu, ID, self.__add_doc_desc)
1259
1260 # ID = wx.NewId()
1261 # self.__desc_menu.Append(ID, _('Delete description'))
1262 # wx.EVT_MENU(self.__desc_menu, ID, self.__del_doc_desc)
1263
1264 # self.__desc_menu.AppendSeparator()
1265 #--------------------------------------------------------
1267
1268 wx.BeginBusyCursor()
1269
1270 # clean old tree
1271 if self.root is not None:
1272 self.DeleteAllItems()
1273
1274 # init new tree
1275 self.root = self.AddRoot(cDocTree._root_node_labels[self.__sort_mode], -1, -1)
1276 self.SetPyData(self.root, None)
1277 self.SetItemHasChildren(self.root, False)
1278
1279 # read documents from database
1280 curr_pat = gmPerson.gmCurrentPatient()
1281 docs_folder = curr_pat.get_document_folder()
1282 docs = docs_folder.get_documents()
1283 if docs is None:
1284 gmGuiHelpers.gm_show_error (
1285 aMessage = _('Error searching documents.'),
1286 aTitle = _('loading document list')
1287 )
1288 # avoid recursion of GUI updating
1289 wx.EndBusyCursor()
1290 return True
1291
1292 if len(docs) == 0:
1293 wx.EndBusyCursor()
1294 return True
1295
1296 # fill new tree from document list
1297 self.SetItemHasChildren(self.root, True)
1298
1299 # add our documents as first level nodes
1300 intermediate_nodes = {}
1301 for doc in docs:
1302
1303 parts = doc.get_parts()
1304
1305 cmt = gmTools.coalesce(doc['comment'], _('no comment available'))
1306 page_num = len(parts)
1307 ref = gmTools.coalesce(initial = doc['ext_ref'], instead = u'', template_initial = u', \u00BB%s\u00AB')
1308
1309 if doc.has_unreviewed_parts():
1310 review = gmTools.u_writing_hand
1311 else:
1312 review = u''
1313
1314 label = _('%s%7s %s: %s (%s part(s)%s)') % (
1315 review,
1316 doc['clin_when'].strftime('%m/%Y'),
1317 doc['l10n_type'][:26],
1318 cmt,
1319 page_num,
1320 ref
1321 )
1322
1323 # need intermediate branch level ?
1324 if self.__sort_mode == 'episode':
1325 if not intermediate_nodes.has_key(doc['episode']):
1326 intermediate_nodes[doc['episode']] = self.AppendItem(parent = self.root, text = doc['episode'])
1327 self.SetItemBold(intermediate_nodes[doc['episode']], bold = True)
1328 self.SetPyData(intermediate_nodes[doc['episode']], None)
1329 parent = intermediate_nodes[doc['episode']]
1330 elif self.__sort_mode == 'type':
1331 if not intermediate_nodes.has_key(doc['l10n_type']):
1332 intermediate_nodes[doc['l10n_type']] = self.AppendItem(parent = self.root, text = doc['l10n_type'])
1333 self.SetItemBold(intermediate_nodes[doc['l10n_type']], bold = True)
1334 self.SetPyData(intermediate_nodes[doc['l10n_type']], None)
1335 parent = intermediate_nodes[doc['l10n_type']]
1336 else:
1337 parent = self.root
1338
1339 doc_node = self.AppendItem(parent = parent, text = label)
1340 #self.SetItemBold(doc_node, bold = True)
1341 self.SetPyData(doc_node, doc)
1342 if len(parts) > 0:
1343 self.SetItemHasChildren(doc_node, True)
1344
1345 # now add parts as child nodes
1346 for part in parts:
1347
1348 pg = _('part %2s') % part['seq_idx']
1349 cmt = gmTools.coalesce(part['obj_comment'], u'', u': %s%%s%s' % (gmTools.u_left_double_angle_quote, gmTools.u_right_double_angle_quote))
1350 sz = gmTools.size2str(part['size'])
1351 rev = gmTools.bool2str (
1352 boolean = part['reviewed'] or part['reviewed_by_you'] or part['reviewed_by_intended_reviewer'],
1353 true_str = u'',
1354 false_str = gmTools.u_writing_hand
1355 )
1356
1357 # if part['clinically_relevant']:
1358 # rel = ' [%s]' % _('Cave')
1359 # else:
1360 # rel = ''
1361
1362 label = '%s%s (%s)%s' % (rev, pg, sz, cmt)
1363
1364 part_node = self.AppendItem(parent = doc_node, text = label)
1365 self.SetPyData(part_node, part)
1366
1367 self.__sort_nodes()
1368 self.SelectItem(self.root)
1369
1370 # FIXME: apply expansion state if available or else ...
1371 # FIXME: ... uncollapse to default state
1372 self.Expand(self.root)
1373 if self.__sort_mode in ['episode', 'type']:
1374 for key in intermediate_nodes.keys():
1375 self.Expand(intermediate_nodes[key])
1376
1377 wx.EndBusyCursor()
1378 return True
1379 #------------------------------------------------------------------------
1381 """Used in sorting items.
1382
1383 -1: 1 < 2
1384 0: 1 = 2
1385 1: 1 > 2
1386 """
1387 item1 = self.GetPyData(node1)
1388 item2 = self.GetPyData(node2)
1389
1390 # doc node
1391 if isinstance(item1, gmMedDoc.cMedDoc):
1392
1393 date_field = 'clin_when'
1394 #date_field = 'modified_when'
1395
1396 if self.__sort_mode == 'age':
1397 # reverse sort by date
1398 if item1[date_field] > item2[date_field]:
1399 return -1
1400 if item1[date_field] == item2[date_field]:
1401 return 0
1402 return 1
1403
1404 elif self.__sort_mode == 'episode':
1405 if item1['episode'] < item2['episode']:
1406 return -1
1407 if item1['episode'] == item2['episode']:
1408 # inner sort: reverse by date
1409 if item1[date_field] > item2[date_field]:
1410 return -1
1411 if item1[date_field] == item2[date_field]:
1412 return 0
1413 return 1
1414 return 1
1415
1416 elif self.__sort_mode == 'review':
1417 # equality
1418 if item1.has_unreviewed_parts() == item2.has_unreviewed_parts():
1419 # inner sort: reverse by date
1420 if item1[date_field] > item2[date_field]:
1421 return -1
1422 if item1[date_field] == item2[date_field]:
1423 return 0
1424 return 1
1425 if item1.has_unreviewed_parts():
1426 return -1
1427 return 1
1428
1429 elif self.__sort_mode == 'type':
1430 if item1['l10n_type'] < item2['l10n_type']:
1431 return -1
1432 if item1['l10n_type'] == item2['l10n_type']:
1433 # inner sort: reverse by date
1434 if item1[date_field] > item2[date_field]:
1435 return -1
1436 if item1[date_field] == item2[date_field]:
1437 return 0
1438 return 1
1439 return 1
1440
1441 else:
1442 _log.error('unknown document sort mode [%s], reverse-sorting by age', self.__sort_mode)
1443 # reverse sort by date
1444 if item1[date_field] > item2[date_field]:
1445 return -1
1446 if item1[date_field] == item2[date_field]:
1447 return 0
1448 return 1
1449
1450 # part node
1451 if isinstance(item1, gmMedDoc.cMedDocPart):
1452 # compare sequence IDs (= "page" numbers)
1453 # FIXME: wrong order ?
1454 if item1['seq_idx'] < item2['seq_idx']:
1455 return -1
1456 if item1['seq_idx'] == item2['seq_idx']:
1457 return 0
1458 return 1
1459
1460 # else sort alphabetically
1461 if None in [item1, item2]:
1462 if node1 < node2:
1463 return -1
1464 if node1 == node2:
1465 return 0
1466 else:
1467 if item1 < item2:
1468 return -1
1469 if item1 == item2:
1470 return 0
1471 return 1
1472 #------------------------------------------------------------------------
1473 # event handlers
1474 #------------------------------------------------------------------------
1478 #------------------------------------------------------------------------
1482 #------------------------------------------------------------------------
1484 # FIXME: self.__store_expansion_history_in_db
1485
1486 # empty out tree
1487 if self.root is not None:
1488 self.DeleteAllItems()
1489 self.root = None
1490 #------------------------------------------------------------------------
1492 # FIXME: self.__load_expansion_history_from_db (but not apply it !)
1493 self._schedule_data_reget()
1494 #------------------------------------------------------------------------
1496 node = event.GetItem()
1497 node_data = self.GetPyData(node)
1498
1499 # exclude pseudo root node
1500 if node_data is None:
1501 return None
1502
1503 # expand/collapse documents on activation
1504 if isinstance(node_data, gmMedDoc.cMedDoc):
1505 self.Toggle(node)
1506 return True
1507
1508 # string nodes are labels such as episodes which may or may not have children
1509 if type(node_data) == type('string'):
1510 self.Toggle(node)
1511 return True
1512
1513 self.__display_part(part = node_data)
1514 return True
1515 #--------------------------------------------------------
1517
1518 node = evt.GetItem()
1519 self.__curr_node_data = self.GetPyData(node)
1520
1521 # exclude pseudo root node
1522 if self.__curr_node_data is None:
1523 return None
1524
1525 # documents
1526 if isinstance(self.__curr_node_data, gmMedDoc.cMedDoc):
1527 self.__handle_doc_context()
1528
1529 # parts
1530 if isinstance(self.__curr_node_data, gmMedDoc.cMedDocPart):
1531 self.__handle_part_context()
1532
1533 del self.__curr_node_data
1534 evt.Skip()
1535 #--------------------------------------------------------
1537 self.__curr_node_data.set_as_active_photograph()
1538 #--------------------------------------------------------
1541 #--------------------------------------------------------
1544 #--------------------------------------------------------
1546 manage_document_descriptions(parent = self, document = self.__curr_node_data)
1547 #--------------------------------------------------------
1548 # internal API
1549 #--------------------------------------------------------
1551 if start_node is None:
1552 start_node = self.root
1553
1554 # protect against empty tree where not even
1555 # a root node exists
1556 if not start_node.IsOk():
1557 return True
1558
1559 self.SortChildren(start_node)
1560
1561 child_node, cookie = self.GetFirstChild(start_node)
1562 while child_node.IsOk():
1563 self.__sort_nodes(start_node = child_node)
1564 child_node, cookie = self.GetNextChild(start_node, cookie)
1565
1566 return
1567 #--------------------------------------------------------
1570 #--------------------------------------------------------
1572
1573 # make active patient photograph
1574 if self.__curr_node_data['type'] == 'patient photograph':
1575 ID = wx.NewId()
1576 self.__part_context_menu.Append(ID, _('Activate as current photo'))
1577 wx.EVT_MENU(self.__part_context_menu, ID, self.__activate_as_current_photo)
1578 else:
1579 ID = None
1580
1581 self.PopupMenu(self.__part_context_menu, wx.DefaultPosition)
1582
1583 if ID is not None:
1584 self.__part_context_menu.Delete(ID)
1585 #--------------------------------------------------------
1586 # part level context menu handlers
1587 #--------------------------------------------------------
1589 """Display document part."""
1590
1591 # sanity check
1592 if part['size'] == 0:
1593 _log.debug('cannot display part [%s] - 0 bytes', part['pk_obj'])
1594 gmGuiHelpers.gm_show_error (
1595 aMessage = _('Document part does not seem to exist in database !'),
1596 aTitle = _('showing document')
1597 )
1598 return None
1599
1600 wx.BeginBusyCursor()
1601
1602 cfg = gmCfg.cCfgSQL()
1603
1604 # get export directory for temporary files
1605 tmp_dir = gmTools.coalesce (
1606 cfg.get2 (
1607 option = "horstspace.tmp_dir",
1608 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1609 bias = 'workplace'
1610 ),
1611 os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
1612 )
1613 _log.debug("temporary directory [%s]", tmp_dir)
1614
1615 # determine database export chunk size
1616 chunksize = int(
1617 cfg.get2 (
1618 option = "horstspace.blob_export_chunk_size",
1619 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1620 bias = 'workplace',
1621 default = default_chunksize
1622 ))
1623
1624 # shall we force blocking during view ?
1625 block_during_view = bool( cfg.get2 (
1626 option = 'horstspace.document_viewer.block_during_view',
1627 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1628 bias = 'user',
1629 default = None
1630 ))
1631
1632 # display it
1633 successful, msg = part.display_via_mime (
1634 tmpdir = tmp_dir,
1635 chunksize = chunksize,
1636 block = block_during_view
1637 )
1638
1639 wx.EndBusyCursor()
1640
1641 if not successful:
1642 gmGuiHelpers.gm_show_error (
1643 aMessage = _('Cannot display document part:\n%s') % msg,
1644 aTitle = _('showing document')
1645 )
1646 return None
1647
1648 # handle review after display
1649 # 0: never
1650 # 1: always
1651 # 2: if no review by myself exists yet
1652 review_after_display = int(cfg.get2 (
1653 option = 'horstspace.document_viewer.review_after_display',
1654 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1655 bias = 'user',
1656 default = 2
1657 ))
1658 if review_after_display == 1: # always review
1659 self.__review_part(part=part)
1660 elif review_after_display == 2: # review if no review by me exists
1661 review_by_me = filter(lambda rev: rev['is_your_review'], part.get_reviews())
1662 if len(review_by_me) == 0:
1663 self.__review_part(part=part)
1664
1665 return True
1666 #--------------------------------------------------------
1668 dlg = cReviewDocPartDlg (
1669 parent = self,
1670 id = -1,
1671 part = part
1672 )
1673 dlg.ShowModal()
1674 dlg.Destroy()
1675 #--------------------------------------------------------
1677
1678 gmHooks.run_hook_script(hook = u'before_%s_doc_part' % action)
1679
1680 wx.BeginBusyCursor()
1681
1682 # detect wrapper
1683 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc' % action)
1684 if not found:
1685 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc.bat' % action)
1686 if not found:
1687 _log.error('neither of gm-%s_doc or gm-%s_doc.bat found', action, action)
1688 wx.EndBusyCursor()
1689 gmGuiHelpers.gm_show_error (
1690 _('Cannot %(l10n_action)s document part - %(l10n_action)s command not found.\n'
1691 '\n'
1692 'Either of gm_%(action)s_doc.sh or gm_%(action)s_doc.bat\n'
1693 'must be in the execution path. The command will\n'
1694 'be passed the filename to %(l10n_action)s.'
1695 ) % {'action': action, 'l10n_action': l10n_action},
1696 _('Processing document part: %s') % l10n_action
1697 )
1698 return
1699
1700 cfg = gmCfg.cCfgSQL()
1701
1702 # get export directory for temporary files
1703 tmp_dir = gmTools.coalesce (
1704 cfg.get2 (
1705 option = "horstspace.tmp_dir",
1706 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1707 bias = 'workplace'
1708 ),
1709 os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
1710 )
1711 _log.debug("temporary directory [%s]", tmp_dir)
1712
1713 # determine database export chunk size
1714 chunksize = int(cfg.get2 (
1715 option = "horstspace.blob_export_chunk_size",
1716 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1717 bias = 'workplace',
1718 default = default_chunksize
1719 ))
1720
1721 part_file = self.__curr_node_data.export_to_file (
1722 aTempDir = tmp_dir,
1723 aChunkSize = chunksize
1724 )
1725
1726 cmd = u'%s %s' % (external_cmd, part_file)
1727 success = gmShellAPI.run_command_in_shell (
1728 command = cmd,
1729 blocking = False
1730 )
1731
1732 wx.EndBusyCursor()
1733
1734 if not success:
1735 _log.error('%s command failed: [%s]', action, cmd)
1736 gmGuiHelpers.gm_show_error (
1737 _('Cannot %(l10n_action)s document part - %(l10n_action)s command failed.\n'
1738 '\n'
1739 'You may need to check and fix either of\n'
1740 ' gm_%(action)s_doc.sh (Unix/Mac) or\n'
1741 ' gm_%(action)s_doc.bat (Windows)\n'
1742 '\n'
1743 'The command is passed the filename to %(l10n_action)s.'
1744 ) % {'action': action, 'l10n_action': l10n_action},
1745 _('Processing document part: %s') % l10n_action
1746 )
1747 #--------------------------------------------------------
1748 # FIXME: icons in the plugin toolbar
1750 self.__process_part(action = u'print', l10n_action = _('print'))
1751 #--------------------------------------------------------
1753 self.__process_part(action = u'fax', l10n_action = _('fax'))
1754 #--------------------------------------------------------
1756 self.__process_part(action = u'mail', l10n_action = _('mail'))
1757 #--------------------------------------------------------
1758 # document level context menu handlers
1759 #--------------------------------------------------------
1761 enc = gmEMRStructItems.cEncounter(aPK_obj=self.__curr_node_data['pk_encounter'])
1762 dlg = gmEMRStructWidgets.cEncounterEditAreaDlg(parent=self, encounter=enc)
1763 dlg.ShowModal()
1764 #--------------------------------------------------------
1766
1767 gmHooks.run_hook_script(hook = u'before_%s_doc' % action)
1768
1769 wx.BeginBusyCursor()
1770
1771 # detect wrapper
1772 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc' % action)
1773 if not found:
1774 found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc.bat' % action)
1775 if not found:
1776 _log.error('neither of gm-%s_doc or gm-%s_doc.bat found', action, action)
1777 wx.EndBusyCursor()
1778 gmGuiHelpers.gm_show_error (
1779 _('Cannot %(l10n_action)s document - %(l10n_action)s command not found.\n'
1780 '\n'
1781 'Either of gm_%(action)s_doc.sh or gm_%(action)s_doc.bat\n'
1782 'must be in the execution path. The command will\n'
1783 'be passed a list of filenames to %(l10n_action)s.'
1784 ) % {'action': action, 'l10n_action': l10n_action},
1785 _('Processing document: %s') % l10n_action
1786 )
1787 return
1788
1789 cfg = gmCfg.cCfgSQL()
1790
1791 # get export directory for temporary files
1792 tmp_dir = gmTools.coalesce (
1793 cfg.get2 (
1794 option = "horstspace.tmp_dir",
1795 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1796 bias = 'workplace'
1797 ),
1798 os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
1799 )
1800 _log.debug("temporary directory [%s]", tmp_dir)
1801
1802 # determine database export chunk size
1803 chunksize = int(cfg.get2 (
1804 option = "horstspace.blob_export_chunk_size",
1805 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1806 bias = 'workplace',
1807 default = default_chunksize
1808 ))
1809
1810 part_files = self.__curr_node_data.export_parts_to_files (
1811 export_dir = tmp_dir,
1812 chunksize = chunksize
1813 )
1814
1815 cmd = external_cmd + u' ' + u' '.join(part_files)
1816 success = gmShellAPI.run_command_in_shell (
1817 command = cmd,
1818 blocking = False
1819 )
1820
1821 wx.EndBusyCursor()
1822
1823 if not success:
1824 _log.error('%s command failed: [%s]', action, cmd)
1825 gmGuiHelpers.gm_show_error (
1826 _('Cannot %(l10n_action)s document - %(l10n_action)s command failed.\n'
1827 '\n'
1828 'You may need to check and fix either of\n'
1829 ' gm_%(action)s_doc.sh (Unix/Mac) or\n'
1830 ' gm_%(action)s_doc.bat (Windows)\n'
1831 '\n'
1832 'The command is passed a list of filenames to %(l10n_action)s.'
1833 ) % {'action': action, 'l10n_action': l10n_action},
1834 _('Processing document: %s') % l10n_action
1835 )
1836 #--------------------------------------------------------
1837 # FIXME: icons in the plugin toolbar
1839 self.__process_doc(action = u'print', l10n_action = _('print'))
1840 #--------------------------------------------------------
1842 self.__process_doc(action = u'fax', l10n_action = _('fax'))
1843 #--------------------------------------------------------
1845 self.__process_doc(action = u'mail', l10n_action = _('mail'))
1846 #--------------------------------------------------------
1848
1849 gmHooks.run_hook_script(hook = u'before_external_doc_access')
1850
1851 wx.BeginBusyCursor()
1852
1853 # detect wrapper
1854 found, external_cmd = gmShellAPI.detect_external_binary(u'gm_access_external_doc.sh')
1855 if not found:
1856 found, external_cmd = gmShellAPI.detect_external_binary(u'gm_access_external_doc.bat')
1857 if not found:
1858 _log.error('neither of gm_access_external_doc.sh or .bat found')
1859 wx.EndBusyCursor()
1860 gmGuiHelpers.gm_show_error (
1861 _('Cannot access external document - access command not found.\n'
1862 '\n'
1863 'Either of gm_access_external_doc.sh or *.bat must be\n'
1864 'in the execution path. The command will be passed the\n'
1865 'document type and the reference URL for processing.'
1866 ),
1867 _('Accessing external document')
1868 )
1869 return
1870
1871 cmd = u'%s "%s" "%s"' % (external_cmd, self.__curr_node_data['type'], self.__curr_node_data['ext_ref'])
1872 success = gmShellAPI.run_command_in_shell (
1873 command = cmd,
1874 blocking = False
1875 )
1876
1877 wx.EndBusyCursor()
1878
1879 if not success:
1880 _log.error('External access command failed: [%s]', cmd)
1881 gmGuiHelpers.gm_show_error (
1882 _('Cannot access external document - access command failed.\n'
1883 '\n'
1884 'You may need to check and fix either of\n'
1885 ' gm_access_external_doc.sh (Unix/Mac) or\n'
1886 ' gm_access_external_doc.bat (Windows)\n'
1887 '\n'
1888 'The command is passed the document type and the\n'
1889 'external reference URL on the command line.'
1890 ),
1891 _('Accessing external document')
1892 )
1893 #--------------------------------------------------------
1895 """Export document into directory.
1896
1897 - one file per object
1898 - into subdirectory named after patient
1899 """
1900 pat = gmPerson.gmCurrentPatient()
1901 dname = '%s-%s%s' % (
1902 self.__curr_node_data['l10n_type'],
1903 self.__curr_node_data['clin_when'].strftime('%Y-%m-%d'),
1904 gmTools.coalesce(self.__curr_node_data['ext_ref'], '', '-%s').replace(' ', '_')
1905 )
1906 def_dir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'docs', pat['dirname'], dname))
1907 gmTools.mkdir(def_dir)
1908
1909 dlg = wx.DirDialog (
1910 parent = self,
1911 message = _('Save document into directory ...'),
1912 defaultPath = def_dir,
1913 style = wx.DD_DEFAULT_STYLE
1914 )
1915 result = dlg.ShowModal()
1916 dirname = dlg.GetPath()
1917 dlg.Destroy()
1918
1919 if result != wx.ID_OK:
1920 return True
1921
1922 wx.BeginBusyCursor()
1923
1924 cfg = gmCfg.cCfgSQL()
1925
1926 # determine database export chunk size
1927 chunksize = int(cfg.get2 (
1928 option = "horstspace.blob_export_chunk_size",
1929 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1930 bias = 'workplace',
1931 default = default_chunksize
1932 ))
1933
1934 fnames = self.__curr_node_data.export_parts_to_files(export_dir = dirname, chunksize = chunksize)
1935
1936 wx.EndBusyCursor()
1937
1938 gmDispatcher.send(signal='statustext', msg=_('Successfully exported %s parts into the directory [%s].') % (len(fnames), dirname))
1939
1940 return True
1941 #--------------------------------------------------------
1943 result = gmGuiHelpers.gm_show_question (
1944 aMessage = _('Are you sure you want to delete the document ?'),
1945 aTitle = _('Deleting document')
1946 )
1947 if result is True:
1948 curr_pat = gmPerson.gmCurrentPatient()
1949 emr = curr_pat.get_emr()
1950 enc = emr.active_encounter
1951 gmMedDoc.delete_document(document_id = self.__curr_node_data['pk_doc'], encounter_id = enc['pk_encounter'])
1952 #============================================================
1953 # main
1954 #------------------------------------------------------------
1955 if __name__ == '__main__':
1956
1957 gmI18N.activate_locale()
1958 gmI18N.install_domain(domain = 'gnumed')
1959
1960 #----------------------------------------
1961 #----------------------------------------
1962 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
1963 # test_*()
1964 pass
1965
1966 #============================================================
1967 # $Log: gmMedDocWidgets.py,v $
1968 # Revision 1.187 2010/01/17 19:48:20 ncq
1969 # - add tooltips on phrasewheels
1970 # - cleaner layout for document tree
1971 #
1972 # Revision 1.186 2009/12/25 22:03:56 ncq
1973 # - it is now gm-%s* rather than gm_%s*
1974 #
1975 # Revision 1.185 2009/12/22 12:02:40 ncq
1976 # - gm_%_* -> gm-%_*
1977 #
1978 # Revision 1.184 2009/11/15 01:07:11 ncq
1979 # - cleanup
1980 #
1981 # Revision 1.183 2009/09/13 18:45:25 ncq
1982 # - no more get-active-encounter()
1983 #
1984 # Revision 1.182 2009/07/30 12:04:35 ncq
1985 # - better sort context menu on docs/parts
1986 #
1987 # Revision 1.181 2009/07/16 11:31:20 ncq
1988 # - repopulate doc types list after setting tx
1989 #
1990 # Revision 1.180 2009/07/06 17:16:09 ncq
1991 # - comment
1992 #
1993 # Revision 1.179 2009/07/01 17:08:17 ncq
1994 # - make document comment field optional
1995 #
1996 # Revision 1.178 2009/06/11 12:37:25 ncq
1997 # - much simplified initial setup of list ctrls
1998 #
1999 # Revision 1.177 2009/06/10 21:01:10 ncq
2000 # - cleanup
2001 #
2002 # Revision 1.176 2009/05/13 12:20:20 ncq
2003 # - some cleanup
2004 #
2005 # Revision 1.175 2009/02/04 21:47:17 ncq
2006 # - properly sort tree
2007 #
2008 # Revision 1.174 2009/01/30 12:11:02 ncq
2009 # - comment
2010 #
2011 # Revision 1.173 2009/01/15 11:40:54 ncq
2012 # - implement managing document descriptions
2013 #
2014 # Revision 1.172 2009/01/11 19:19:19 ncq
2015 # - start supporting editing of document descriptions
2016 #
2017 # Revision 1.171 2008/12/09 23:33:14 ncq
2018 # - doc_med.date -> clin_when
2019 #
2020 # Revision 1.170 2008/11/23 12:46:03 ncq
2021 # - apply comment to doc or part, respectively, when
2022 # reviewing/signing docs or parts
2023 # - add hook after new doc was created
2024 # - only inform user in statusline of new doc created if not new id shown anyway
2025 #
2026 # Revision 1.169 2008/11/21 13:06:36 ncq
2027 # - missing cfg in doc deletion
2028 #
2029 # Revision 1.168 2008/10/22 12:21:57 ncq
2030 # - use %x in strftime where appropriate
2031 #
2032 # Revision 1.167 2008/10/12 16:23:19 ncq
2033 # - consultation -> encounter
2034 # - adjust to changed review view
2035 #
2036 # Revision 1.166 2008/08/20 14:54:46 ncq
2037 # - carefully work around wxMenu.Remove() doing something other
2038 # than what the documentation suggests it does
2039 #
2040 # Revision 1.165 2008/07/10 20:00:34 ncq
2041 # - a few Begin/EndBusyCursor
2042 #
2043 # Revision 1.164 2008/07/07 13:43:17 ncq
2044 # - current patient .connected
2045 #
2046 # Revision 1.163 2008/05/31 16:38:57 ncq
2047 # - add permalink handling
2048 #
2049 # Revision 1.162 2008/05/29 15:31:46 ncq
2050 # - transition doc types editor to db change signal listener
2051 #
2052 # Revision 1.161 2008/05/29 13:29:42 ncq
2053 # - doc type handling: improve layout, support type change across all docs
2054 #
2055 # Revision 1.160 2008/04/12 19:21:19 ncq
2056 # - build doc/part context menus only once as far as
2057 # possible in doc tree thereby preserving wx IDs
2058 # - handle print/fax/mail doc part
2059 # - properly call hook script
2060 #
2061 # Revision 1.159 2008/04/11 23:14:09 ncq
2062 # - centralize default_chunksize
2063 # - handle print/fax/mail document
2064 #
2065 # Revision 1.158 2008/04/11 12:28:55 ncq
2066 # - use signing hand again
2067 #
2068 # Revision 1.157 2008/04/02 10:21:25 ncq
2069 # - select-all on tabbing into doc type phrasewheel
2070 # - review -> sign
2071 # - use signing hand/not-checkmark unicode in some places
2072 #
2073 # Revision 1.156 2008/03/06 18:29:29 ncq
2074 # - standard lib logging only
2075 #
2076 # Revision 1.155 2008/02/25 17:38:05 ncq
2077 # - make parts listbox file drop target, too
2078 #
2079 # Revision 1.154 2008/01/27 21:16:45 ncq
2080 # - label changes per Jim
2081 # - allow partless docs
2082 #
2083 # Revision 1.153 2008/01/11 16:15:33 ncq
2084 # - first/last -> first-/lastnames
2085 #
2086 # Revision 1.152 2007/12/23 20:29:35 ncq
2087 # - store tmp docs in tmp/, not tmp/docs/
2088 #
2089 # Revision 1.151 2007/12/11 12:49:26 ncq
2090 # - explicit signal handling
2091 #
2092 # Revision 1.150 2007/11/05 11:41:46 ncq
2093 # - use blobs.delete_document()
2094 #
2095 # Revision 1.149 2007/10/31 22:07:18 ncq
2096 # - delete document from context menu
2097 #
2098 # Revision 1.148 2007/10/31 11:26:18 ncq
2099 # - hide less exceptions
2100 #
2101 # Revision 1.147 2007/10/29 13:22:32 ncq
2102 # - make cDocTree a lot more self contained:
2103 # - make it a reget mixin child
2104 # - make sort_mode a property scheduling reload on set
2105 # - listen to patient changes
2106 # - empty tree on pre_sel
2107 # - schedule reload on post_sel
2108 # - listen to doc and page changes and schedule appropriate reloads
2109 #
2110 # Revision 1.146 2007/10/12 07:27:02 ncq
2111 # - check in drop target fix
2112 #
2113 # Revision 1.145 2007/10/07 12:32:41 ncq
2114 # - workplace property now on gmSurgery.gmCurrentPractice() borg
2115 #
2116 # Revision 1.144 2007/09/07 10:57:54 ncq
2117 # - document review_after_display
2118 #
2119 # Revision 1.143 2007/08/29 14:43:06 ncq
2120 # - factor out forms/letters related code
2121 # - fix syntax error re stray, gmLog.L* consts
2122 #
2123 # Revision 1.142 2007/08/28 14:18:13 ncq
2124 # - no more gm_statustext()
2125 #
2126 # Revision 1.141 2007/08/20 22:12:49 ncq
2127 # - support _on_load_button_pressed in form template editor
2128 #
2129 # Revision 1.140 2007/08/20 16:23:52 ncq
2130 # - support editing form templates from create_new_letter
2131 # - cFormTemplateEditAreaDlg
2132 #
2133 # Revision 1.139 2007/08/20 14:29:31 ncq
2134 # - cleanup, start of test suite
2135 # - form template edit area
2136 #
2137 # Revision 1.138 2007/08/15 09:20:43 ncq
2138 # - use cOOoLetter.show()
2139 # - cleanup
2140 # - use gmTools.size2str()
2141 #
2142 # Revision 1.137 2007/08/13 22:11:38 ncq
2143 # - use cFormTemplate
2144 # - pass placeholder handler to form instance handler
2145 #
2146 # Revision 1.136 2007/08/12 00:10:55 ncq
2147 # - improve create_new_letter()
2148 # - (_)save_file_as_new_document() and listen for 'import_document_from_file'
2149 # - no more gmSignals.py
2150 #
2151 # Revision 1.135 2007/08/09 07:59:42 ncq
2152 # - streamline __display_part() with part.display_via_mime()
2153 #
2154 # Revision 1.134 2007/07/22 10:04:23 ncq
2155 # - streamline create_new_letter()
2156 #
2157 # Revision 1.133 2007/07/22 09:27:28 ncq
2158 # - create_new_letter()
2159 # - adjust to get_choice_from_list() changes
2160 # - tmp/ now in .gnumed/
2161 #
2162 # Revision 1.132 2007/07/11 21:10:31 ncq
2163 # - cleanup
2164 #
2165 # Revision 1.131 2007/06/28 12:39:37 ncq
2166 # - make pages listbox in scan/index panel be a drop target itself, too
2167 # - handle preset device = '' as unconfigured
2168 #
2169 # Revision 1.130 2007/06/18 20:38:32 ncq
2170 # - use gmListWidgets.get_choice_from_list()
2171 #
2172 # Revision 1.129 2007/06/12 13:24:48 ncq
2173 # - allow editing of encounter corresponding to a document
2174 #
2175 # Revision 1.128 2007/06/10 10:17:36 ncq
2176 # - gmScanBackend now uses exceptions for error handling
2177 # - improved error message when no sanner driver found
2178 #
2179 # Revision 1.127 2007/05/21 14:49:20 ncq
2180 # - use pat['dirname'] for export
2181 #
2182 # Revision 1.126 2007/05/21 13:05:25 ncq
2183 # - catch-all wildcard on UNIX must be *, not *.*
2184 #
2185 # Revision 1.125 2007/05/20 01:28:09 ncq
2186 # - only repopulate if we actually saved a new doc type
2187 #
2188 # Revision 1.124 2007/05/18 22:02:30 ncq
2189 # - create export/docs/<patient>/<doc>/ *subdir* for document export
2190 #
2191 # Revision 1.123 2007/05/14 13:11:24 ncq
2192 # - use statustext() signal
2193 #
2194 # Revision 1.122 2007/04/23 16:59:35 ncq
2195 # - make cReviewDocPartDlg accept documents as well as document
2196 # parts and dynamically adjust UI appropriately
2197 #
2198 # Revision 1.121 2007/04/23 01:08:04 ncq
2199 # - add "activate as current photo" to popup menu
2200 #
2201 # Revision 1.120 2007/04/21 19:40:06 ncq
2202 # - handle seq_idx spin ctrl in review doc (part)
2203 #
2204 # Revision 1.119 2007/04/02 18:39:52 ncq
2205 # - gmFuzzyTimestamp -> gmDateTime
2206 #
2207 # Revision 1.118 2007/03/31 21:51:05 ncq
2208 # - add xsane default device option
2209 #
2210 # Revision 1.117 2007/03/08 16:21:11 ncq
2211 # - support blobs.doc_obj.filename
2212 #
2213 # Revision 1.116 2007/02/22 17:41:13 ncq
2214 # - adjust to gmPerson changes
2215 #
2216 # Revision 1.115 2007/02/17 18:28:33 ncq
2217 # - factor out get_device_to_use() and use it in _scan_btn_pressed()
2218 # - support pre-setting device, only directly supported by XSane so far
2219 #
2220 # Revision 1.114 2007/02/17 14:13:11 ncq
2221 # - gmPerson.gmCurrentProvider().workplace now property
2222 #
2223 # Revision 1.113 2007/02/06 13:43:40 ncq
2224 # - no more aDelay in __init__()
2225 #
2226 # Revision 1.112 2007/02/05 12:15:23 ncq
2227 # - no more aMatchProvider/selection_only in cPhraseWheel.__init__()
2228 #
2229 # Revision 1.111 2007/02/04 15:55:14 ncq
2230 # - use SetText()
2231 #
2232 # Revision 1.110 2007/01/18 22:13:37 ncq
2233 # - tell user when we expand a folder to extract files
2234 #
2235 # Revision 1.109 2007/01/17 14:01:56 ncq
2236 # - when folder dropped onto scanidxpnl extract files one level into it
2237 #
2238 # Revision 1.108 2007/01/12 13:10:11 ncq
2239 # - use cFileDropTarget in ScanIdxPnl
2240 #
2241 # Revision 1.107 2007/01/10 23:01:07 ncq
2242 # - properly update document/object metadata
2243 #
2244 # Revision 1.106 2007/01/07 23:08:52 ncq
2245 # - improve cDocumentCommentPhraseWheel query and link it to the doc_type field
2246 # - add "export to disk" to doc tree context menu
2247 #
2248 # Revision 1.105 2007/01/06 23:42:35 ncq
2249 # - cDocumentCommentPhraseWeel and adjust to wxGlade based uses thereof
2250 #
2251 # Revision 1.104 2006/12/27 16:45:42 ncq
2252 # - adjust to acquire_pages_into_files() returning a list
2253 #
2254 # Revision 1.103 2006/12/21 10:55:09 ncq
2255 # - fix inverted is_in_use logic on enabling delete button
2256 #
2257 # Revision 1.102 2006/12/13 22:32:17 ncq
2258 # - need to explicitely check for "is_user_defined is True/False"
2259 #
2260 # Revision 1.101 2006/12/13 20:55:22 ncq
2261 # - is_user -> is_user_defined
2262 #
2263 # Revision 1.100 2006/12/11 21:40:12 ncq
2264 # - support in_use in doc type list ctrl
2265 # - slight improvement of doc type edit dialog logic
2266 #
2267 # Revision 1.99 2006/11/24 10:01:31 ncq
2268 # - gm_beep_statustext() -> gm_statustext()
2269 #
2270 # Revision 1.98 2006/11/06 10:01:17 ncq
2271 # - handle _on_description_modified() in edit-doc-types
2272 #
2273 # Revision 1.97 2006/10/31 17:22:49 ncq
2274 # - unicode()ify queries
2275 # - cleanup
2276 # - PgResult is now dict, so use it instead of index
2277 # - add missing os.path.expanduser()
2278 #
2279 # Revision 1.96 2006/10/25 07:46:44 ncq
2280 # - Format() -> strftime() since datetime.datetime does not have .Format()
2281 #
2282 # Revision 1.95 2006/10/24 13:26:11 ncq
2283 # - no more gmPG.
2284 # - use cMatchProvider_Provider() in scan/index panel
2285 #
2286 # Revision 1.94 2006/10/08 11:05:32 ncq
2287 # - properly use db cfg
2288 #
2289 # Revision 1.93 2006/09/19 12:00:42 ncq
2290 # - clear scan/idx panel on patient change
2291 #
2292 # Revision 1.92 2006/09/12 17:27:35 ncq
2293 # - support horstspace.document_viewer.block_during_view
2294 #
2295 # Revision 1.91 2006/09/01 15:03:26 ncq
2296 # - improve scanner device choice handling
2297 # - better tmp dir handling on document import/export
2298 #
2299 # Revision 1.90 2006/07/24 20:51:26 ncq
2300 # - get_by_user() -> get2()
2301 #
2302 # Revision 1.89 2006/07/10 21:57:43 ncq
2303 # - add bool() where needed
2304 #
2305 # Revision 1.88 2006/07/10 21:48:09 ncq
2306 # - handle cDocumentType
2307 # - implement actions in document type editor
2308 #
2309 # Revision 1.87 2006/07/07 12:08:16 ncq
2310 # - cleanup
2311 # - add document type editing panel and dialog
2312 #
2313 # Revision 1.86 2006/07/04 22:36:27 ncq
2314 # - doc type selector is now phrasewheel in properties editor
2315 #
2316 # Revision 1.85 2006/07/04 21:39:37 ncq
2317 # - add cDocumentTypeSelectionPhraseWheel and use it in scan-index-panel
2318 #
2319 # Revision 1.84 2006/06/26 13:07:57 ncq
2320 # - episode selection phrasewheel knows how to create episodes
2321 # when told to do so in GetData() so use that
2322 #
2323 # Revision 1.83 2006/06/21 15:54:17 ncq
2324 # - properly set reviewer on cMedDoc
2325 #
2326 # Revision 1.82 2006/06/17 14:10:32 ncq
2327 # - make review-after-display-if-needed the default
2328 #
2329 # Revision 1.81 2006/06/15 21:41:16 ncq
2330 # - episode selector phrasewheel returns PK, not instance
2331 #
2332 # Revision 1.80 2006/06/15 07:13:21 ncq
2333 # - used PK of episode instance in add_document
2334 #
2335 # Revision 1.79 2006/06/09 14:42:19 ncq
2336 # - allow review from document
2337 # - always apply review to all pages
2338 #
2339 # Revision 1.78 2006/06/05 21:36:20 ncq
2340 # - add ext_ref field to properties editor
2341 # - add repopulate_ui() to cScanIdxPnl since it's a notebook plugin
2342 # - add "type" sort mode to doc tree
2343 #
2344 # Revision 1.77 2006/06/04 21:51:43 ncq
2345 # - handle doc type/comment/date in properties editor dialog
2346 #
2347 # Revision 1.76 2006/05/31 12:17:04 ncq
2348 # - cleanup, string improvements
2349 # - review dialog:
2350 # - init review of current user if any
2351 # - do not list review of current user under reviews by other people ...
2352 # - implement save action
2353 #
2354 # Revision 1.75 2006/05/28 16:40:16 ncq
2355 # - add ' ' to initial episode SetValue() to avoid popping up pick list
2356 # - better labels in list of existing reviews
2357 # - handle checkbox for review
2358 # - start save handling
2359 # - use episode selection phrasewheel
2360 #
2361 # Revision 1.74 2006/05/25 22:22:39 ncq
2362 # - adjust to rearranged review dialog
2363 # - nicely resize review columns
2364 # - remove current users review from "other reviews" list
2365 # - let user edit own review below "other reviews" list
2366 # - properly use fuzzy timestamp in scan/index panel
2367 #
2368 # Revision 1.73 2006/05/24 10:34:51 ncq
2369 # - use cFuzzyTimestampInput
2370 #
2371 # Revision 1.72 2006/05/20 18:53:39 ncq
2372 # - cleanup
2373 # - mark closed episodes in phrasewheel
2374 # - add match provider to reviewer selection phrasewheel
2375 # - handle reviewer phrasewheel
2376 # - set reviewer in add_parts_from_files()
2377 # - signal successful document saving
2378 #
2379 # Revision 1.71 2006/05/16 15:54:39 ncq
2380 # - properly handle check boxes
2381 #
2382 # Revision 1.70 2006/05/15 13:36:00 ncq
2383 # - signal cleanup:
2384 # - activating_patient -> pre_patient_selection
2385 # - patient_selected -> post_patient_selection
2386 #
2387 # Revision 1.69 2006/05/15 07:02:28 ncq
2388 # - it -> is
2389 #
2390 # Revision 1.68 2006/05/14 21:44:22 ncq
2391 # - add get_workplace() to gmPerson.gmCurrentProvider and make use thereof
2392 # - remove use of gmWhoAmI.py
2393 #
2394 # Revision 1.67 2006/05/14 20:43:38 ncq
2395 # - properly use get_devices() in gmScanBackend
2396 #
2397 # Revision 1.66 2006/05/12 21:59:35 ncq
2398 # - set proper radiobutton in _on_sort_by_*()
2399 #
2400 # Revision 1.65 2006/05/12 12:18:11 ncq
2401 # - whoami -> whereami cleanup
2402 # - use gmCurrentProvider()
2403 #
2404 # Revision 1.64 2006/05/10 13:07:00 ncq
2405 # - set focus to doc tree widget after selecting sort mode
2406 # - collapse/expand doc tree nodes on ENTER/double-click
2407 #
2408 # Revision 1.63 2006/05/08 22:04:23 ncq
2409 # - sigh, doc_med content date must be timestamp after all so use proper formatting
2410 #
2411 # Revision 1.62 2006/05/08 18:21:29 ncq
2412 # - vastly improved document tree when sorting by episode
2413 #
2414 # Revision 1.61 2006/05/08 16:35:32 ncq
2415 # - cleanup
2416 # - add event handlers for sorting
2417 # - make tree really sort - wxPython seems to forget to call
2418 # SortChildren() so call it ourselves
2419 #
2420 # Revision 1.60 2006/05/07 15:34:01 ncq
2421 # - add cSelectablySortedDocTreePnl
2422 #
2423 # Revision 1.59 2006/05/01 18:49:30 ncq
2424 # - better named variables
2425 # - match provider in ScanIdxPnl
2426 # - episode handling on save
2427 # - as user before deleting files from disc
2428 # - fix node formatting in doc tree
2429 #
2430 # Revision 1.58 2006/04/30 15:52:53 shilbert
2431 # - event handler for document loading was added
2432 #
2433 # Revision 1.57 2006/02/27 15:42:14 ncq
2434 # - implement cancel button in review dialog
2435 # - invoke review after displaying doc part depending on cfg
2436 #
2437 # Revision 1.56 2006/02/13 19:10:14 ncq
2438 # - actually display previous reviews in list
2439 #
2440 # Revision 1.55 2006/02/13 08:29:19 ncq
2441 # - further work on the doc review control
2442 #
2443 # Revision 1.54 2006/02/10 16:33:19 ncq
2444 # - popup review dialog from doc part right-click menu
2445 #
2446 # Revision 1.53 2006/02/05 15:03:22 ncq
2447 # - doc tree:
2448 # - document part popup menu, stub for review dialog
2449 # - improved part display in doc tree
2450 # - start handling relevant/abnormal check boxes in scan/index
2451 #
2452 # Revision 1.52 2006/02/05 14:16:29 shilbert
2453 # - more checks for required values before commiting document to database
2454 #
2455 # Revision 1.51 2006/01/27 22:33:44 ncq
2456 # - display reviewed/signed status in document tree
2457 #
2458 # Revision 1.50 2006/01/24 22:32:14 ncq
2459 # - allow multiple files to be selected at once from file selection dialog
2460 #
2461 # Revision 1.49 2006/01/23 22:11:36 ncq
2462 # - improve display
2463 #
2464 # Revision 1.48 2006/01/23 17:36:32 ncq
2465 # - cleanup
2466 # - display/use full path with file name after "load file" in scan&index
2467 # - only add loaded file to file list if not cancelled
2468 #
2469 # Revision 1.47 2006/01/22 18:09:30 ncq
2470 # - improve file name string in scanned pages list
2471 # - force int() on int from db cfg
2472 #
2473 # Revision 1.46 2006/01/21 23:57:18 shilbert
2474 # - acquire file from filesystem has been added
2475 #
2476 # Revision 1.45 2006/01/16 22:10:10 ncq
2477 # - some cleanup
2478 #
2479 # Revision 1.44 2006/01/16 20:03:02 shilbert
2480 # *** empty log message ***
2481 #
2482 # Revision 1.43 2006/01/16 19:37:25 ncq
2483 # - use get_devices()
2484 #
2485 # Revision 1.42 2006/01/15 13:14:12 shilbert
2486 # - support for multiple image source finished
2487 #
2488 # Revision 1.41 2006/01/15 10:02:23 shilbert
2489 # - initial support for multiple image scanner devices
2490 #
2491 # Revision 1.40 2006/01/14 23:21:19 shilbert
2492 # - fix for correct doc type (pk) handling
2493 #
2494 # Revision 1.39 2006/01/14 10:34:53 shilbert
2495 # - fixed some some bugs which prevented document to be saved in DB
2496 #
2497 # Revision 1.38 2006/01/13 11:06:33 ncq
2498 # - properly use gmGuiHelpers
2499 # - properly fall back to default temporary directory
2500 #
2501 # Revision 1.37 2006/01/01 18:14:25 shilbert
2502 # - fixed indentation problem
2503 #
2504 # Revision 1.36 2006/01/01 17:44:43 ncq
2505 # - comment on proper user of emr.add_document()
2506 #
2507 # Revision 1.35 2006/01/01 17:23:29 ncq
2508 # - properly use backend option for temp dir to
2509 # temporarily export docs into for viewing
2510 #
2511 # Revision 1.34 2005/12/16 12:04:25 ncq
2512 # - fix silly indentation bug
2513 #
2514 # Revision 1.33 2005/12/14 17:01:03 ncq
2515 # - use document_folder class and other gmMedDoc.py goodies
2516 #
2517 # Revision 1.32 2005/12/14 15:54:01 ncq
2518 # - cleanup
2519 #
2520 # Revision 1.31 2005/12/14 15:40:54 ncq
2521 # - add my changes regarding new config handling
2522 #
2523 # Revision 1.30 2005/12/14 14:08:24 shilbert
2524 # - minor cleanup of ncq's changes
2525 #
2526 # Revision 1.29 2005/12/14 10:42:11 ncq
2527 # - use cCfgSQL.get_by_user in scan&index panel on showing document reference ID
2528 #
2529 # Revision 1.28 2005/12/13 21:44:31 ncq
2530 # - start _save_btn_pressed() so people see where we are going
2531 #
2532 # Revision 1.27 2005/12/06 17:59:12 ncq
2533 # - make scan/index panel work more
2534 #
2535 # Revision 1.26 2005/12/02 22:46:21 shilbert
2536 # - fixed inconsistent naming of vaiables which caused a bug
2537 #
2538 # Revision 1.25 2005/12/02 17:31:05 shilbert
2539 # - readd document types as per Ian's suggestion
2540 #
2541 # Revision 1.24 2005/12/02 02:09:02 shilbert
2542 # - quite a few feature updates within the scope of scan&idx panel
2543 #
2544 # Revision 1.23 2005/11/29 19:00:09 ncq
2545 # - some cleanup
2546 #
2547 # Revision 1.22 2005/11/27 12:46:21 ncq
2548 # - cleanup
2549 #
2550 # Revision 1.21 2005/11/27 01:57:28 shilbert
2551 # - moved some of the feature back in
2552 #
2553 # Revision 1.20 2005/11/26 21:08:00 shilbert
2554 # - some more iterations on the road
2555 #
2556 # Revision 1.19 2005/11/26 16:56:04 shilbert
2557 # - initial working version with scan /index documents support
2558 #
2559 # Revision 1.18 2005/11/26 16:38:55 shilbert
2560 # - slowly readding features
2561 #
2562 # Revision 1.17 2005/11/26 08:21:37 ncq
2563 # - scan/index wxGlade child class fleshed out a bit more
2564 #
2565 # Revision 1.16 2005/11/25 23:02:49 ncq
2566 # - start scan/idx panel inheriting from wxGlade base class
2567 #
2568 # Revision 1.15 2005/09/28 21:27:30 ncq
2569 # - a lot of wx2.6-ification
2570 #
2571 # Revision 1.14 2005/09/28 15:57:48 ncq
2572 # - a whole bunch of wx.Foo -> wx.Foo
2573 #
2574 # Revision 1.13 2005/09/26 18:01:51 ncq
2575 # - use proper way to import wx26 vs wx2.4
2576 # - note: THIS WILL BREAK RUNNING THE CLIENT IN SOME PLACES
2577 # - time for fixup
2578 #
2579 # Revision 1.12 2005/09/24 09:17:29 ncq
2580 # - some wx2.6 compatibility fixes
2581 #
2582 # Revision 1.11 2005/03/06 14:54:19 ncq
2583 # - szr.AddWindow() -> Add() such that wx2.5 works
2584 # - 'demographic record' -> get_identity()
2585 #
2586 # Revision 1.10 2005/01/31 10:37:26 ncq
2587 # - gmPatient.py -> gmPerson.py
2588 #
2589 # Revision 1.9 2004/10/17 15:57:36 ncq
2590 # - after pat.get_documents():
2591 # 1) separate len(docs) == 0 from docs is None
2592 # 2) only the second really is an error
2593 # 3) however, return True from it, too, as we
2594 # informed the user about the error already
2595 #
2596 # Revision 1.8 2004/10/17 00:05:36 sjtan
2597 #
2598 # fixup for paint event re-entry when notification dialog occurs over medDocTree graphics
2599 # area, and triggers another paint event, and another notification dialog , in a loop.
2600 # Fixup is set flag to stop _repopulate_tree, and to only unset this flag when
2601 # patient activating signal gmMedShowDocs to schedule_reget, which is overridden
2602 # to include resetting of flag, before calling mixin schedule_reget.
2603 #
2604 # Revision 1.7 2004/10/14 12:11:50 ncq
2605 # - __on_activate -> _on_activate
2606 #
2607 # Revision 1.6 2004/10/11 19:56:03 ncq
2608 # - cleanup, robustify, attach doc/part VO directly to node
2609 #
2610 # Revision 1.5 2004/10/01 13:34:26 ncq
2611 # - don't fail to display just because some metadata is missing
2612 #
2613 # Revision 1.4 2004/09/19 15:10:44 ncq
2614 # - lots of cleanup
2615 # - use status message instead of error box on missing patient
2616 # so that we don't get an endless loop
2617 # -> paint_event -> update_gui -> no-patient message -> paint_event -> ...
2618 #
2619 # Revision 1.3 2004/07/19 11:50:43 ncq
2620 # - cfg: what used to be called "machine" really is "workplace", so fix
2621 #
2622 # Revision 1.2 2004/07/18 20:30:54 ncq
2623 # - wxPython.true/false -> Python.True/False as Python tells us to do
2624 #
2625 # Revision 1.1 2004/06/26 23:39:34 ncq
2626 # - factored out widgets for re-use
2627 #
2628
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Feb 9 04:01:48 2010 | http://epydoc.sourceforge.net |