1 """GNUmed GUI helper classes and functions.
2
3 This module provides some convenient wxPython GUI
4 helper thingies that are widely used throughout
5 GNUmed.
6 """
7
8 __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
9 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
10
11 import os
12 import logging
13 import sys
14 import io
15 import time
16 import datetime as pyDT
17
18
19 import wx
20
21
22 if __name__ == '__main__':
23 sys.path.insert(0, '../../')
24 from Gnumed.pycommon import gmMatchProvider
25 from Gnumed.pycommon import gmExceptions
26 from Gnumed.pycommon import gmLog2
27 from Gnumed.pycommon import gmTools
28 from Gnumed.pycommon import gmDispatcher
29 from Gnumed.wxpython import gmPhraseWheel
30
31
32 _log = logging.getLogger('gm.main')
33
35
37
38 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
39
40 items = [
41 {'list_label': _('Yes: + / ! / 1'), 'field_label': _('yes'), 'data': True, 'weight': 0},
42 {'list_label': _('No: - / 0'), 'field_label': _('no'), 'data': False, 'weight': 1},
43 {'list_label': _('Unknown: ?'), 'field_label': _('unknown'), 'data': None, 'weight': 2},
44 ]
45 mp = gmMatchProvider.cMatchProvider_FixedList(items)
46 mp.setThresholds(1, 1, 2)
47 mp.word_separators = '[ :/]+'
48 mp.word_separators = None
49 mp.ignored_chars = r"[.'\\(){}\[\]<>~#*$%^_=&@\t23456]+" + r'"'
50
51 self.matcher = mp
52
53 from Gnumed.wxGladeWidgets import wxg2ButtonQuestionDlg
54
126
127
128 from Gnumed.wxGladeWidgets import wxg3ButtonQuestionDlg
129
214
215
216 from Gnumed.wxGladeWidgets import wxgMultilineTextEntryDlg
217
218 -class cMultilineTextEntryDlg(wxgMultilineTextEntryDlg.wxgMultilineTextEntryDlg):
219 """Editor for a bit of text."""
220
221 - def __init__(self, *args, **kwargs):
222
223 try:
224 title = kwargs['title']
225 del kwargs['title']
226 except KeyError:
227 title = None
228
229 try:
230 msg = kwargs['msg']
231 del kwargs['msg']
232 except KeyError:
233 msg = None
234
235 try:
236 data = kwargs['data']
237 del kwargs['data']
238 except KeyError:
239 data = None
240
241 try:
242 self.original_text = kwargs['text']
243 del kwargs['text']
244 except KeyError:
245 self.original_text = None
246
247 wxgMultilineTextEntryDlg.wxgMultilineTextEntryDlg.__init__(self, *args, **kwargs)
248
249 if title is not None:
250 self.SetTitle(gmTools.decorate_window_title(title))
251
252 if self.original_text is not None:
253 self._TCTRL_text.SetValue(self.original_text)
254 self._BTN_restore.Enable(True)
255
256 if msg is None:
257 self._LBL_msg.Hide()
258 else:
259 self._LBL_msg.SetLabel(msg)
260 self.Layout()
261 self.Refresh()
262
263 if data is None:
264 self._TCTRL_data.Hide()
265 else:
266 self._TCTRL_data.SetValue(data)
267 self.Layout()
268 self.Refresh()
269
270 self._TCTRL_text.SetFocus()
271
272
273
274 - def _get_value(self):
275 return self._TCTRL_text.GetValue()
276
277 value = property(_get_value, lambda x:x)
278
280 return self._CHBOX_is_already_formatted.IsChecked()
281
282 is_user_formatted = property(_get_is_user_formatted, lambda x:x)
283
286
287 enable_user_formatting = property(lambda x:x, _set_enable_user_formatting)
288
289
290
292
293 if self.IsModal():
294 self.EndModal(wx.ID_SAVE)
295 else:
296 self.Close()
297
300
302 if self.original_text is not None:
303 self._TCTRL_text.SetValue(self.original_text)
304
305
307
308 if wx.TheClipboard.IsOpened():
309 return False
310
311 if not wx.TheClipboard.Open():
312 return False
313
314 data_obj = wx.TextDataObject()
315 got_it = wx.TheClipboard.GetData(data_obj)
316 if got_it:
317 txt = data_obj.Text
318 wx.TheClipboard.Close()
319 return txt
320
321 wx.TheClipboard.Close()
322 return None
323
324
326
327 if wx.TheClipboard.IsOpened():
328 return False
329
330 if not wx.TheClipboard.Open():
331 return False
332
333 data_obj = wx.TextDataObject()
334 got_it = wx.TheClipboard.GetData(data_obj)
335 if got_it:
336 clipboard_text_content = data_obj.Text
337 wx.TheClipboard.Close()
338 if check_for_filename:
339 try:
340 io.open(clipboard_text_content).close()
341 return clipboard_text_content
342 except IOError:
343 _log.exception('clipboard does not seem to hold filename: %s', clipboard_text_content)
344 fname = gmTools.get_unique_filename(prefix = 'gm-clipboard-', suffix = '.txt')
345 target_file = io.open(fname, mode = 'wt', encoding = 'utf8')
346 target_file.write(clipboard_text_content)
347 target_file.close()
348 return fname
349
350 data_obj = wx.BitmapDataObject()
351 got_it = wx.TheClipboard.GetData(data_obj)
352 if got_it:
353 fname = gmTools.get_unique_filename(prefix = 'gm-clipboard-', suffix = '.png')
354 bmp = data_obj.Bitmap.SaveFile(fname, wx.BITMAP_TYPE_PNG)
355 wx.TheClipboard.Close()
356 return fname
357
358 wx.TheClipboard.Close()
359 return None
360
361
362 -def text2clipboard(text=None, announce_result=False):
363 if wx.TheClipboard.IsOpened():
364 return False
365 if not wx.TheClipboard.Open():
366 return False
367 data_obj = wx.TextDataObject()
368 data_obj.SetText(text)
369 wx.TheClipboard.SetData(data_obj)
370 wx.TheClipboard.Close()
371 if announce_result:
372 gmDispatcher.send(signal = 'statustext', msg = _('The text has been copied into the clipboard.'), beep = False)
373 return True
374
375
377 f = io.open(filename, mode = 'rt', encoding = 'utf8')
378 result = text2clipboard(text = f.read(), announce_result = False)
379 f.close()
380 if announce_result:
381 gm_show_info (
382 title = _('file2clipboard'),
383 info = _('The file [%s] has been copied into the clipboard.') % filename
384 )
385 return result
386
387
389 """Generic file drop target class.
390
391 Protocol:
392 Widgets being declared file drop targets
393 must provide the method:
394
395 def _drop_target_consume_filenames(self, filenames)
396
397 or declare a callback during __init__() of this class.
398 """
399
400 - def __init__(self, target=None, on_drop_callback=None):
401 if target is not None:
402 try:
403 on_drop_callback = getattr(target, '_drop_target_consume_filenames')
404 except AttributeError:
405 _log.exception('[%s._drop_target_consume_filenames()] does not exist, cannot set as drop target callback', target)
406 raise
407 if not callable(on_drop_callback):
408 _log.error('[%s] not callable, cannot set as drop target callback', on_drop_callback)
409 raise AttributeError('[%s] not callable, cannot set as drop target callback', on_drop_callback)
410 self._on_drop_callback = on_drop_callback
411 wx.FileDropTarget.__init__(self)
412 _log.debug('setting up [%s] as file drop target', self._on_drop_callback)
413
414
416 self._on_drop_callback(filenames)
417
418
420 img_data = None
421 bitmap = None
422 rescaled_height = height
423 try:
424 img_data = wx.Image(filename, wx.BITMAP_TYPE_ANY)
425 current_width = img_data.GetWidth()
426 current_height = img_data.GetHeight()
427
428
429
430
431 rescaled_width = (float(current_width) / current_height) * rescaled_height
432 img_data.Rescale(rescaled_width, rescaled_height, quality = wx.IMAGE_QUALITY_HIGH)
433 bitmap = wx.Bitmap(img_data)
434 del img_data
435 except Exception:
436 _log.exception('cannot load image from [%s]', filename)
437 del img_data
438 del bitmap
439 return None
440 return bitmap
441
442
444 """Take screenshot of widget.
445
446 <settle_time> in milliseconds
447 """
448 assert (isinstance(widget, wx.Window)), '<widget> must be (sub)class of wx.Window'
449
450 if filename is None:
451 filename = gmTools.get_unique_filename (
452 prefix = 'gm-screenshot-%s-' % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'),
453 suffix = '.png'
454
455
456 )
457 else:
458 filename = gmTools.fname_sanitize(filename)
459
460 _log.debug('filename: %s', filename)
461 _log.debug('widget: %s', widget)
462 _log.debug('display size: %s', wx.DisplaySize())
463
464
465 if settle_time is not None:
466 for wait_slice in range(int(settle_time // 100)):
467 wx.SafeYield()
468 time.sleep(0.1)
469
470 widget_rect_on_screen = widget.GetScreenRect()
471 client_area_origin_on_screen = widget.ClientToScreen((0, 0))
472 widget_rect_local = widget.GetRect()
473 widget_rect_client_area = widget.GetClientRect()
474 client_area_origin_local = widget.GetClientAreaOrigin()
475
476 _log.debug('widget.GetScreenRect(): %s', widget_rect_on_screen)
477 _log.debug('widget.ClientToScreen(0, 0): %s', client_area_origin_on_screen)
478 _log.debug('widget.GetRect(): %s', widget_rect_local)
479 _log.debug('widget.GetClientRect(): %s', widget_rect_client_area)
480 _log.debug('widget.GetClientAreaOrigin(): %s', client_area_origin_local)
481
482 width2snap = widget_rect_local.width
483 height2snap = widget_rect_local.height
484 border_x = client_area_origin_on_screen.x - widget_rect_local.x
485 x2snap_from = 0 - border_x
486 title_and_menu_height = client_area_origin_on_screen.y - widget_rect_on_screen.y
487 y2snap_from = 0 - title_and_menu_height
488
489
490
491
492 _log.debug('left (x) border: %s', border_x)
493 _log.debug('top (y) border: %s', title_and_menu_height)
494 _log.debug('x2snap_from: %s', x2snap_from)
495 _log.debug('y2snap_from: %s', y2snap_from)
496 _log.debug('width2snap: %s', width2snap)
497 _log.debug('height2snap: %s', height2snap)
498
499
500 window_dc = wx.WindowDC(widget)
501 wxbmp = __snapshot_to_bitmap (
502 source_dc = window_dc,
503 x2snap_from = x2snap_from,
504 y2snap_from = y2snap_from,
505 width2snap = width2snap,
506 height2snap = height2snap
507 )
508 window_dc.Destroy()
509 del window_dc
510 wxbmp.SaveFile(filename, wx.BITMAP_TYPE_PNG)
511 del wxbmp
512
513 x2snap_on_screen = widget_rect_on_screen.x
514 y2snap_on_screen = widget_rect_on_screen.y
515 sane_x2snap_on_screen = max(0, x2snap_on_screen)
516 sane_y2snap_on_screen = max(0, y2snap_on_screen)
517
518 _log.debug('x2snap_on_screen: %s', x2snap_on_screen)
519 _log.debug('y2snap_on_screen: %s', y2snap_on_screen)
520 _log.debug('sane x2snap_on_screen: %s', sane_x2snap_on_screen)
521 _log.debug('sane x2snap_on_screen: %s', sane_y2snap_on_screen)
522
523 screen_dc = wx.ScreenDC()
524
525
526 wxbmp = __snapshot_to_bitmap (
527 source_dc = screen_dc,
528 x2snap_from = sane_x2snap_on_screen,
529 y2snap_from = sane_y2snap_on_screen,
530 width2snap = width2snap,
531 height2snap = height2snap
532 )
533 screen_dc.Destroy()
534 del screen_dc
535 wxbmp.SaveFile(filename + '.screendc.png', wx.BITMAP_TYPE_PNG)
536 del wxbmp
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % filename)
563 return filename
564
565
566 -def __snapshot_to_bitmap(source_dc=None, x2snap_from=0, y2snap_from=0, width2snap=1, height2snap=1):
567 _log.debug('taking screenshot from %sx%s for %sx%s on [%s]', x2snap_from, y2snap_from, width2snap, height2snap, source_dc)
568 target_dc = wx.MemoryDC()
569 _log.debug('target DC: %s', target_dc)
570 wxbmp = wx.Bitmap(width2snap, height2snap)
571 target_dc.SelectObject(wxbmp)
572 target_dc.Clear()
573 target_dc.Blit (
574 0, 0,
575 width2snap, height2snap,
576 source_dc,
577 x2snap_from, y2snap_from
578 )
579 target_dc.SelectObject(wx.NullBitmap)
580 target_dc.Destroy()
581 del target_dc
582 return wxbmp
583
584
585 -def gm_show_error(aMessage=None, aTitle = None, error=None, title=None):
586
587 if error is None:
588 error = aMessage
589 if error is None:
590 error = _('programmer forgot to specify error message')
591 error += _("\n\nPlease consult the error log for all the gory details !")
592 if title is None:
593 title = aTitle
594 if title is None:
595 title = _('generic error message')
596 dlg = wx.MessageDialog (
597 parent = None,
598 message = error,
599 caption = gmTools.decorate_window_title(title),
600 style = wx.OK | wx.ICON_ERROR | wx.STAY_ON_TOP
601 )
602 dlg.ShowModal()
603 dlg.DestroyLater()
604 return True
605
606
607 -def gm_show_info(aMessage=None, aTitle=None, info=None, title=None):
608
609 if info is None:
610 info = aMessage
611 if info is None:
612 info = _('programmer forgot to specify info message')
613 if title is None:
614 title = aTitle
615 if title is None:
616 title = _('generic info message')
617 dlg = wx.MessageDialog (
618 parent = None,
619 message = info,
620 caption = gmTools.decorate_window_title(title),
621 style = wx.OK | wx.ICON_INFORMATION | wx.STAY_ON_TOP
622 )
623 dlg.ShowModal()
624 dlg.DestroyLater()
625 return True
626
627
629 if aMessage is None:
630 aMessage = _('programmer forgot to specify warning')
631 if aTitle is None:
632 aTitle = _('generic warning message')
633
634 dlg = wx.MessageDialog (
635 parent = None,
636 message = aMessage,
637 caption = gmTools.decorate_window_title(aTitle),
638 style = wx.OK | wx.ICON_EXCLAMATION | wx.STAY_ON_TOP
639 )
640 dlg.ShowModal()
641 dlg.DestroyLater()
642 return True
643
644
645 -def gm_show_question(aMessage='programmer forgot to specify question', aTitle='generic user question dialog', cancel_button=False, question=None, title=None):
646 if cancel_button:
647 style = wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION | wx.STAY_ON_TOP
648 else:
649 style = wx.YES_NO | wx.ICON_QUESTION | wx.STAY_ON_TOP
650
651 if question is None:
652 question = aMessage
653 if title is None:
654 title = aTitle
655 title = gmTools.decorate_window_title(title)
656
657 dlg = wx.MessageDialog(None, question, title, style)
658 btn_pressed = dlg.ShowModal()
659 dlg.DestroyLater()
660
661 if btn_pressed == wx.ID_YES:
662 return True
663 elif btn_pressed == wx.ID_NO:
664 return False
665 else:
666 return None
667
668
669 if __name__ == '__main__':
670
671 if len(sys.argv) < 2:
672 sys.exit()
673
674 if sys.argv[1] != 'test':
675 sys.exit()
676
677 from Gnumed.pycommon import gmI18N
678 gmI18N.activate_locale()
679 gmI18N.install_domain(domain='gnumed')
680
681
683 app = wx.App()
684 img = file2scaled_image(filename = sys.argv[2])
685 print(img)
686 print(img.Height)
687 print(img.Width)
688
690 app = wx.PyWidgetTester(size = (200, 50))
691 prw = cThreeValuedLogicPhraseWheel(app.frame, -1)
692 app.frame.Show(True)
693 app.MainLoop()
694
695 return True
696
698 app = wx.PyWidgetTester(size = (200, 50))
699 result = clipboard2file()
700 if result is False:
701 print("problem opening clipboard")
702 return
703 if result is None:
704 print("no data in clipboard")
705 return
706 print("file:", result)
707
708
709
710 test_clipboard()
711
712
713