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 on_drop_callback = getattr(target, '_drop_target_consume_filenames')
403 if not callable(on_drop_callback):
404 _log.error('[%s] not callable, cannot set as drop target callback', on_drop_callback)
405 raise AttributeError('[%s] not callable, cannot set as drop target callback', on_drop_callback)
406
407 self._on_drop_callback = on_drop_callback
408 wx.FileDropTarget.__init__(self)
409 _log.debug('setting up [%s] as file drop target', self._on_drop_callback)
410
411
413 self._on_drop_callback(filenames)
414
415
417 img_data = None
418 bitmap = None
419 rescaled_height = height
420 try:
421 img_data = wx.Image(filename, wx.BITMAP_TYPE_ANY)
422 current_width = img_data.GetWidth()
423 current_height = img_data.GetHeight()
424
425
426
427
428 rescaled_width = (float(current_width) / current_height) * rescaled_height
429 img_data.Rescale(rescaled_width, rescaled_height, quality = wx.IMAGE_QUALITY_HIGH)
430 bitmap = wx.Bitmap(img_data)
431 del img_data
432 except Exception:
433 _log.exception('cannot load image from [%s]', filename)
434 del img_data
435 del bitmap
436 return None
437 return bitmap
438
439
441 """Take screenshot of widget.
442
443 <settle_time> in milliseconds
444 """
445 assert (isinstance(widget, wx.Window)), '<widget> must be (sub)class of wx.Window'
446
447 if filename is None:
448 filename = gmTools.get_unique_filename (
449 prefix = 'gm-screenshot-%s-' % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'),
450 suffix = '.png'
451
452
453 )
454 else:
455 filename = gmTools.fname_sanitize(filename)
456
457 _log.debug('filename: %s', filename)
458 _log.debug('widget: %s', widget)
459 _log.debug('display size: %s', wx.DisplaySize())
460
461
462 if settle_time is not None:
463 for wait_slice in range(int(settle_time // 100)):
464 wx.SafeYield()
465 time.sleep(0.1)
466
467 widget_rect_on_screen = widget.GetScreenRect()
468 client_area_origin_on_screen = widget.ClientToScreen((0, 0))
469 widget_rect_local = widget.GetRect()
470 widget_rect_client_area = widget.GetClientRect()
471 client_area_origin_local = widget.GetClientAreaOrigin()
472
473 _log.debug('widget.GetScreenRect(): %s', widget_rect_on_screen)
474 _log.debug('widget.ClientToScreen(0, 0): %s', client_area_origin_on_screen)
475 _log.debug('widget.GetRect(): %s', widget_rect_local)
476 _log.debug('widget.GetClientRect(): %s', widget_rect_client_area)
477 _log.debug('widget.GetClientAreaOrigin(): %s', client_area_origin_local)
478
479 width2snap = widget_rect_local.width
480 height2snap = widget_rect_local.height
481 border_x = client_area_origin_on_screen.x - widget_rect_local.x
482 x2snap_from = 0 - border_x
483 title_and_menu_height = client_area_origin_on_screen.y - widget_rect_on_screen.y
484 y2snap_from = 0 - title_and_menu_height
485
486
487
488
489 _log.debug('left (x) border: %s', border_x)
490 _log.debug('top (y) border: %s', title_and_menu_height)
491 _log.debug('x2snap_from: %s', x2snap_from)
492 _log.debug('y2snap_from: %s', y2snap_from)
493 _log.debug('width2snap: %s', width2snap)
494 _log.debug('height2snap: %s', height2snap)
495
496
497 window_dc = wx.WindowDC(widget)
498 wxbmp = __snapshot_to_bitmap (
499 source_dc = window_dc,
500 x2snap_from = x2snap_from,
501 y2snap_from = y2snap_from,
502 width2snap = width2snap,
503 height2snap = height2snap
504 )
505 window_dc.Destroy()
506 del window_dc
507 wxbmp.SaveFile(filename, wx.BITMAP_TYPE_PNG)
508 del wxbmp
509
510 x2snap_on_screen = widget_rect_on_screen.x
511 y2snap_on_screen = widget_rect_on_screen.y
512 sane_x2snap_on_screen = max(0, x2snap_on_screen)
513 sane_y2snap_on_screen = max(0, y2snap_on_screen)
514
515 _log.debug('x2snap_on_screen: %s', x2snap_on_screen)
516 _log.debug('y2snap_on_screen: %s', y2snap_on_screen)
517 _log.debug('sane x2snap_on_screen: %s', sane_x2snap_on_screen)
518 _log.debug('sane x2snap_on_screen: %s', sane_y2snap_on_screen)
519
520 screen_dc = wx.ScreenDC()
521
522
523 wxbmp = __snapshot_to_bitmap (
524 source_dc = screen_dc,
525 x2snap_from = sane_x2snap_on_screen,
526 y2snap_from = sane_y2snap_on_screen,
527 width2snap = width2snap,
528 height2snap = height2snap
529 )
530 screen_dc.Destroy()
531 del screen_dc
532 wxbmp.SaveFile(filename + '.screendc.png', wx.BITMAP_TYPE_PNG)
533 del wxbmp
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % filename)
560 return filename
561
562
563 -def __snapshot_to_bitmap(source_dc=None, x2snap_from=0, y2snap_from=0, width2snap=1, height2snap=1):
564 _log.debug('taking screenshot from %sx%s for %sx%s on [%s]', x2snap_from, y2snap_from, width2snap, height2snap, source_dc)
565 target_dc = wx.MemoryDC()
566 _log.debug('target DC: %s', target_dc)
567 wxbmp = wx.Bitmap(width2snap, height2snap)
568 target_dc.SelectObject(wxbmp)
569 target_dc.Clear()
570 target_dc.Blit (
571 0, 0,
572 width2snap, height2snap,
573 source_dc,
574 x2snap_from, y2snap_from
575 )
576 target_dc.SelectObject(wx.NullBitmap)
577 target_dc.Destroy()
578 del target_dc
579 return wxbmp
580
581
582 -def gm_show_error(aMessage=None, aTitle = None, error=None, title=None):
583
584 if error is None:
585 error = aMessage
586 if error is None:
587 error = _('programmer forgot to specify error message')
588 error += _("\n\nPlease consult the error log for all the gory details !")
589 if title is None:
590 title = aTitle
591 if title is None:
592 title = _('generic error message')
593 dlg = wx.MessageDialog (
594 parent = None,
595 message = error,
596 caption = gmTools.decorate_window_title(title),
597 style = wx.OK | wx.ICON_ERROR | wx.STAY_ON_TOP
598 )
599 dlg.ShowModal()
600 dlg.DestroyLater()
601 return True
602
603
604 -def gm_show_info(aMessage=None, aTitle=None, info=None, title=None):
605
606 if info is None:
607 info = aMessage
608 if info is None:
609 info = _('programmer forgot to specify info message')
610 if title is None:
611 title = aTitle
612 if title is None:
613 title = _('generic info message')
614 dlg = wx.MessageDialog (
615 parent = None,
616 message = info,
617 caption = gmTools.decorate_window_title(title),
618 style = wx.OK | wx.ICON_INFORMATION | wx.STAY_ON_TOP
619 )
620 dlg.ShowModal()
621 dlg.DestroyLater()
622 return True
623
624
626 if aMessage is None:
627 aMessage = _('programmer forgot to specify warning')
628 if aTitle is None:
629 aTitle = _('generic warning message')
630
631 dlg = wx.MessageDialog (
632 parent = None,
633 message = aMessage,
634 caption = gmTools.decorate_window_title(aTitle),
635 style = wx.OK | wx.ICON_EXCLAMATION | wx.STAY_ON_TOP
636 )
637 dlg.ShowModal()
638 dlg.DestroyLater()
639 return True
640
641
642 -def gm_show_question(aMessage='programmer forgot to specify question', aTitle='generic user question dialog', cancel_button=False, question=None, title=None):
643 if cancel_button:
644 style = wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION | wx.STAY_ON_TOP
645 else:
646 style = wx.YES_NO | wx.ICON_QUESTION | wx.STAY_ON_TOP
647
648 if question is None:
649 question = aMessage
650 if title is None:
651 title = aTitle
652 title = gmTools.decorate_window_title(title)
653
654 dlg = wx.MessageDialog(None, question, title, style)
655 btn_pressed = dlg.ShowModal()
656 dlg.DestroyLater()
657
658 if btn_pressed == wx.ID_YES:
659 return True
660 elif btn_pressed == wx.ID_NO:
661 return False
662 else:
663 return None
664
665
666 if __name__ == '__main__':
667
668 if len(sys.argv) < 2:
669 sys.exit()
670
671 if sys.argv[1] != 'test':
672 sys.exit()
673
674 from Gnumed.pycommon import gmI18N
675 gmI18N.activate_locale()
676 gmI18N.install_domain(domain='gnumed')
677
678
680 app = wx.App()
681 img = file2scaled_image(filename = sys.argv[2])
682 print(img)
683 print(img.Height)
684 print(img.Width)
685
687 app = wx.PyWidgetTester(size = (200, 50))
688 prw = cThreeValuedLogicPhraseWheel(app.frame, -1)
689 app.frame.Show(True)
690 app.MainLoop()
691
692 return True
693
695 app = wx.PyWidgetTester(size = (200, 50))
696 result = clipboard2file()
697 if result is False:
698 print("problem opening clipboard")
699 return
700 if result is None:
701 print("no data in clipboard")
702 return
703 print("file:", result)
704
705
706
707 test_clipboard()
708
709
710