1
2 """GNUmed visual progress notes handling widgets."""
3
4 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
5 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
6
7 import sys
8 import logging
9 import os
10 import os.path
11 import shutil
12 import random
13 import threading
14
15
16 import wx
17 import wx.lib.agw.supertooltip as agw_stt
18 import wx.lib.statbmp as wx_genstatbmp
19
20
21 if __name__ == '__main__':
22 sys.path.insert(0, '../../')
23
24 from Gnumed.pycommon import gmI18N
25
26 if __name__ == '__main__':
27 gmI18N.activate_locale()
28 gmI18N.install_domain()
29
30 from Gnumed.pycommon import gmDispatcher
31 from Gnumed.pycommon import gmTools
32 from Gnumed.pycommon import gmDateTime
33 from Gnumed.pycommon import gmShellAPI
34 from Gnumed.pycommon import gmCfg
35 from Gnumed.pycommon import gmMatchProvider
36 from Gnumed.pycommon import gmMimeLib
37 from Gnumed.pycommon import gmWorkerThread
38 from Gnumed.pycommon import gmPG2
39
40 from Gnumed.business import gmPerson
41 from Gnumed.business import gmEMRStructItems
42 from Gnumed.business import gmPraxis
43 from Gnumed.business import gmForms
44 from Gnumed.business import gmDocuments
45
46 from Gnumed.wxpython import gmPhraseWheel
47 from Gnumed.wxpython import gmGuiHelpers
48 from Gnumed.wxpython import gmCfgWidgets
49 from Gnumed.wxpython import gmDocumentWidgets
50
51
52 _log = logging.getLogger('gm.ui')
53
54
55
56
87
88
89 cmd = gmCfgWidgets.configure_string_option (
90 message = _(
91 'Enter the shell command with which to start\n'
92 'the image editor for visual progress notes.\n'
93 '\n'
94 'Any "%(img)s" included with the arguments\n'
95 'will be replaced by the file name of the\n'
96 'note template.'
97 ),
98 option = 'external.tools.visual_soap_editor_cmd',
99 bias = 'user',
100 default_value = None,
101 validator = is_valid
102 )
103
104 return cmd
105
106
108 if parent is None:
109 parent = wx.GetApp().GetTopWindow()
110
111 dlg = wx.FileDialog (
112 parent = parent,
113 message = _('Choose file to use as template for new visual progress note'),
114 defaultDir = os.path.expanduser('~'),
115 defaultFile = '',
116
117 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
118 )
119 result = dlg.ShowModal()
120
121 if result == wx.ID_CANCEL:
122 dlg.DestroyLater()
123 return None
124
125 full_filename = dlg.GetPath()
126 dlg.Hide()
127 dlg.DestroyLater()
128 return full_filename
129
130
132
133 if parent is None:
134 parent = wx.GetApp().GetTopWindow()
135
136 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
137 parent,
138 -1,
139 caption = _('Visual progress note source'),
140 question = _('From which source do you want to pick the image template ?'),
141 button_defs = [
142 {'label': _('Database'), 'tooltip': _('List of templates in the database.'), 'default': True},
143 {'label': _('File'), 'tooltip': _('Files in the filesystem.'), 'default': False},
144 {'label': _('Device'), 'tooltip': _('Image capture devices (scanners, cameras, etc)'), 'default': False}
145 ]
146 )
147 result = dlg.ShowModal()
148 dlg.DestroyLater()
149
150
151 if result == wx.ID_YES:
152 _log.debug('visual progress note template from: database template')
153 from Gnumed.wxpython import gmFormWidgets
154 template = gmFormWidgets.manage_form_templates (
155 parent = parent,
156 template_types = [gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE],
157 active_only = True
158 )
159 if template is None:
160 return (None, None)
161 filename = template.save_to_file()
162 if filename is None:
163 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export visual progress note template for [%s].') % template['name_long'])
164 return (None, None)
165 return (filename, True)
166
167
168 if result == wx.ID_NO:
169 _log.debug('visual progress note template from: disk file')
170 fname = select_file_as_visual_progress_note_template(parent = parent)
171 if fname is None:
172 return (None, None)
173
174 ext = os.path.splitext(fname)[1]
175 tmp_name = gmTools.get_unique_filename(suffix = ext)
176 _log.debug('visual progress note from file: [%s] -> [%s]', fname, tmp_name)
177 shutil.copy2(fname, tmp_name)
178 return (tmp_name, False)
179
180
181 if result == wx.ID_CANCEL:
182 _log.debug('visual progress note template from: image capture device')
183 fnames = gmDocumentWidgets.acquire_images_from_capture_device(device = None, calling_window = parent)
184 if fnames is None:
185 return (None, None)
186 if len(fnames) == 0:
187 return (None, None)
188 return (fnames[0], False)
189
190 _log.debug('no visual progress note template source selected')
191 return (None, None)
192
193
195 """This assumes <filename> contains an image which can be handled by the configured image editor."""
196
197 if doc_part is not None:
198 filename = doc_part.save_to_file()
199 if filename is None:
200 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export visual progress note to file.'))
201 return None
202
203 dbcfg = gmCfg.cCfgSQL()
204 editor = dbcfg.get2 (
205 option = 'external.tools.visual_soap_editor_cmd',
206 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
207 bias = 'user'
208 )
209
210 if editor is None:
211 _log.error('no editor for visual progress notes configured, trying mimetype editor')
212 gmDispatcher.send(signal = 'statustext', msg = _('Editor for visual progress note not configured.'), beep = False)
213 mimetype = gmMimeLib.guess_mimetype(filename = filename)
214 editor = gmMimeLib.get_editor_cmd(mimetype = mimetype, filename = filename)
215 if editor is None:
216 _log.error('no editor for mimetype <%s> configured, trying mimetype viewer', mimetype)
217 success, msg = gmMimeLib.call_viewer_on_file(aFile = filename, block = True)
218 if not success:
219 _log.debug('problem running mimetype <%s> viewer', mimetype)
220 gmGuiHelpers.gm_show_error (
221 _( 'There is no editor for visual progress notes defined.\n'
222 'Also, there is no editor command defined for the file type\n'
223 '\n'
224 ' [%s].\n'
225 '\n'
226 'Therefor GNUmed attempted to at least *show* this\n'
227 'visual progress note. That failed as well, however:\n'
228 '\n'
229 '%s'
230 ) % (mimetype, msg),
231 _('Editing visual progress note')
232 )
233 editor = configure_visual_progress_note_editor()
234 if editor is None:
235 gmDispatcher.send(signal = 'statustext', msg = _('Editor for visual progress note not configured.'), beep = True)
236 return None
237
238 if '%(img)s' in editor:
239 editor = editor % {'img': filename}
240 else:
241 editor = '%s %s' % (editor, filename)
242
243 if discard_unmodified:
244 original_stat = os.stat(filename)
245 original_md5 = gmTools.file2md5(filename)
246
247 success = gmShellAPI.run_command_in_shell(editor, blocking = True)
248 if not success:
249 success, msg = gmMimeLib.call_viewer_on_file(aFile = filename, block = True)
250 if not success:
251 _log.debug('problem running mimetype <%s> viewer', mimetype)
252 gmGuiHelpers.gm_show_error (
253 _( 'There was a problem running the editor\n'
254 '\n'
255 ' [%s] (%s)\n'
256 '\n'
257 'on the visual progress note.\n'
258 '\n'
259 'Therefor GNUmed attempted to at least *show* it.\n'
260 'That failed as well, however:\n'
261 '\n'
262 '%s'
263 ) % (editor, mimetype, msg),
264 _('Editing visual progress note')
265 )
266 editor = configure_visual_progress_note_editor()
267 if editor is None:
268 gmDispatcher.send(signal = 'statustext', msg = _('Editor for visual progress note not configured.'), beep = True)
269 return None
270
271 try:
272 open(filename, 'r').close()
273 except Exception:
274 _log.exception('problem accessing visual progress note file [%s]', filename)
275 gmGuiHelpers.gm_show_error (
276 _( 'There was a problem reading the visual\n'
277 'progress note from the file:\n'
278 '\n'
279 ' [%s]\n'
280 '\n'
281 ) % filename,
282 _('Saving visual progress note')
283 )
284 return None
285
286 if discard_unmodified:
287 modified_stat = os.stat(filename)
288
289 if original_stat.st_size == modified_stat.st_size:
290 modified_md5 = gmTools.file2md5(filename)
291
292 if original_md5 == modified_md5:
293 _log.debug('visual progress note (template) not modified')
294
295 msg = _(
296 'You either created a visual progress note from a template\n'
297 'in the database (rather than from a file on disk) or you\n'
298 'edited an existing visual progress note.\n'
299 '\n'
300 'The template/original was not modified at all, however.\n'
301 '\n'
302 'Do you still want to save the unmodified image as a\n'
303 'visual progress note into the EMR of the patient ?\n'
304 )
305 save_unmodified = gmGuiHelpers.gm_show_question (
306 msg,
307 _('Saving visual progress note')
308 )
309 if not save_unmodified:
310 _log.debug('user discarded unmodified note')
311 return
312
313 if doc_part is not None:
314 _log.debug('updating visual progress note')
315 doc_part.update_data_from_file(fname = filename)
316 doc_part.set_reviewed(technically_abnormal = False, clinically_relevant = True)
317 return None
318
319 if not isinstance(episode, gmEMRStructItems.cEpisode):
320 if episode is None:
321 episode = _('visual progress notes')
322 pat = gmPerson.gmCurrentPatient()
323 emr = pat.emr
324 episode = emr.add_episode(episode_name = episode.strip(), pk_health_issue = health_issue, is_open = False)
325
326 doc = gmDocumentWidgets.save_file_as_new_document (
327 filename = filename,
328 document_type = gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE,
329 episode = episode,
330 unlock_patient = False,
331 pk_org_unit = gmPraxis.gmCurrentPraxisBranch()['pk_org_unit']
332 )
333 doc.set_reviewed(technically_abnormal = False, clinically_relevant = True)
334
335 return doc
336
337
339 """Phrasewheel to allow selection of visual SOAP template."""
340
342
343 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
344
345 query = """
346 SELECT
347 pk AS data,
348 name_short AS list_label,
349 name_sort AS field_label
350 FROM
351 ref.paperwork_templates
352 WHERE
353 fk_template_type = (SELECT pk FROM ref.form_types WHERE name = '%s') AND (
354 name_long %%(fragment_condition)s
355 OR
356 name_short %%(fragment_condition)s
357 )
358 ORDER BY list_label
359 LIMIT 15
360 """ % gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE
361
362 mp = gmMatchProvider.cMatchProvider_SQL2(queries = [query])
363 mp.setThresholds(2, 3, 5)
364
365 self.matcher = mp
366 self.selection_only = True
367
373
374
375 from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl
376
378 """A panel displaying a number of images (visual progress note thumbnails)."""
379
384
385
386
387
388 - def refresh(self, document_folder=None, episodes=None, encounter=None, do_async=False):
389 if not self:
390
391 return
392
393 if document_folder is None:
394 self.clear()
395 self.GetParent().Layout()
396 return
397
398 soap_docs = document_folder.get_visual_progress_notes(episodes = episodes, encounter = encounter)
399 if len(soap_docs) == 0:
400 self.clear()
401 self.GetParent().Layout()
402 return
403
404 if not do_async:
405 cookie, parts_list = self._worker__export_doc_parts(docs = soap_docs)
406 self.__show_exported_parts(parts_list = parts_list)
407 return
408
409 self.__worker_cookie = '%sCookie-%s' % (self.__class__.__name__, random.random())
410 _log.debug('starting worker thread, cookie: %s', self.__worker_cookie)
411 gmWorkerThread.execute_in_worker_thread (
412 payload_function = self._worker__export_doc_parts,
413 payload_kwargs = {'docs': soap_docs, 'cookie': self.__worker_cookie},
414 completion_callback = self._forwarder__show_exported_doc_parts,
415 worker_name = self.__class__.__name__
416 )
417
418
420 while self._SZR_soap and len(self._SZR_soap.GetChildren()) > 0:
421 self._SZR_soap.Detach(0)
422 for bmp in self.__bitmaps:
423 bmp.Unbind(wx.EVT_LEFT_UP)
424 bmp.DestroyLater()
425 self.__bitmaps = []
426
427
428
429
431 wx.CallAfter (
432 edit_visual_progress_note,
433 doc_part = evt.GetEventObject().doc_part,
434 discard_unmodified = True
435 )
436
437
438
439
441
442 _log.debug('cookie [%s]', cookie)
443 conn = gmPG2.get_connection(readonly = True, connection_name = threading.current_thread().name, pooled = False)
444 parts_list = []
445 for soap_doc in docs:
446 parts = soap_doc.parts
447 if len(parts) == 0:
448 continue
449 parts_counter = ''
450 if len(parts) > 1:
451 parts_counter = _(' [part 1 of %s]') % len(parts)
452 part = parts[0]
453 fname = part.save_to_file(conn = conn)
454 if fname is None:
455 continue
456 tt_header = _('Created: %s%s') % (gmDateTime.pydt_strftime(part['date_generated'], '%Y %b %d'), parts_counter)
457 tt_footer = gmTools.coalesce(part['doc_comment'], '').strip()
458 parts_list.append([fname, part, tt_header, tt_footer])
459 conn.close()
460 _log.debug('worker finished')
461 return (cookie, parts_list)
462
463
465
466 cookie, parts_list = worker_result
467
468 if cookie != self.__worker_cookie:
469 _log.debug('received results from old worker [%s], I am [%s], ignoring', cookie, self.__worker_cookie)
470 return
471 if len(parts_list) == 0:
472 return
473 wx.CallAfter(self.__show_exported_parts, parts_list = parts_list)
474
475
477 self.clear()
478 for part_def in parts_list:
479 fname, part, tt_header, tt_footer = part_def
480
481
482 img = gmGuiHelpers.file2scaled_image (
483 filename = fname,
484 height = 30
485 )
486 bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER)
487 bmp.doc_part = part
488 img = gmGuiHelpers.file2scaled_image (
489 filename = fname,
490 height = 150
491 )
492 tip = agw_stt.SuperToolTip (
493 '',
494 bodyImage = img,
495 header = tt_header,
496 footer = tt_footer
497 )
498 tip.SetTopGradientColor('white')
499 tip.SetMiddleGradientColor('white')
500 tip.SetBottomGradientColor('white')
501 tip.SetTarget(bmp)
502 bmp.Bind(wx.EVT_LEFT_UP, self._on_bitmap_leftclicked)
503
504 self._SZR_soap.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM | wx.EXPAND, 3)
505 self.__bitmaps.append(bmp)
506 self.GetParent().Layout()
507
508
509
510
511 if __name__ == '__main__':
512
513 if len(sys.argv) < 2:
514 sys.exit()
515
516 if sys.argv[1] != 'test':
517 sys.exit()
518
519 gmI18N.activate_locale()
520 gmI18N.install_domain(domain = 'gnumed')
521
522
523