1 """GNUmed provider inbox handling widgets.
2 """
3
4 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
5
6 import sys, logging
7
8
9 import wx
10
11
12 if __name__ == '__main__':
13 sys.path.insert(0, '../../')
14 from Gnumed.pycommon import gmI18N
15 from Gnumed.pycommon import gmExceptions
16 from Gnumed.pycommon import gmPG2
17 from Gnumed.pycommon import gmCfg
18 from Gnumed.pycommon import gmTools
19 from Gnumed.pycommon import gmDispatcher
20 from Gnumed.pycommon import gmMatchProvider
21 from Gnumed.pycommon import gmDateTime
22 from Gnumed.pycommon import gmNetworkTools
23
24 from Gnumed.business import gmPerson
25 from Gnumed.business import gmStaff
26 from Gnumed.business import gmSurgery
27 from Gnumed.business import gmProviderInbox
28
29 from Gnumed.wxpython import gmGuiHelpers
30 from Gnumed.wxpython import gmListWidgets
31 from Gnumed.wxpython import gmPlugin
32 from Gnumed.wxpython import gmRegetMixin
33 from Gnumed.wxpython import gmPhraseWheel
34 from Gnumed.wxpython import gmEditArea
35 from Gnumed.wxpython import gmAuthWidgets
36 from Gnumed.wxpython import gmPatSearchWidgets
37 from Gnumed.wxpython import gmVaccWidgets
38 from Gnumed.wxpython import gmCfgWidgets
39 from Gnumed.wxpython import gmDataPackWidgets
40
41
42 _log = logging.getLogger('gm.ui')
43
44 _indicator = {
45 -1: '',
46 0: '',
47 1: '*!!*'
48 }
49
86
99
100
101
103
104 if parent is None:
105 parent = wx.GetApp().GetTopWindow()
106
107 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('showing audit trail'))
108 if conn is None:
109 return False
110
111
112 def refresh(lctrl):
113 cmd = u'SELECT * FROM audit.v_audit_trail ORDER BY audit_when_ts'
114 rows, idx = gmPG2.run_ro_queries(link_obj = conn, queries = [{'cmd': cmd}], get_col_idx = False)
115 lctrl.set_string_items (
116 [ [
117 r['event_when'],
118 r['event_by'],
119 u'%s %s %s' % (
120 gmTools.coalesce(r['row_version_before'], gmTools.u_diameter),
121 gmTools.u_right_arrow,
122 gmTools.coalesce(r['row_version_after'], gmTools.u_diameter)
123 ),
124 r['event_table'],
125 r['event'],
126 r['pk_audit']
127 ] for r in rows ]
128 )
129
130 gmListWidgets.get_choices_from_list (
131 parent = parent,
132 msg = u'',
133 caption = _('GNUmed database audit log ...'),
134 columns = [ _('When'), _('Who'), _('Revisions'), _('Table'), _('Event'), '#' ],
135 single_selection = True,
136 refresh_callback = refresh
137 )
138
139
140
141
186
187 def edit(workplace=None):
188
189 dbcfg = gmCfg.cCfgSQL()
190
191 if workplace is None:
192 dlg = wx.TextEntryDialog (
193 parent = parent,
194 message = _('Enter a descriptive name for the new workplace:'),
195 caption = _('Configuring GNUmed workplaces ...'),
196 defaultValue = u'',
197 style = wx.OK | wx.CENTRE
198 )
199 dlg.ShowModal()
200 workplace = dlg.GetValue().strip()
201 if workplace == u'':
202 gmGuiHelpers.gm_show_error(_('Cannot save a new workplace without a name.'), _('Configuring GNUmed workplaces ...'))
203 return False
204 curr_plugins = []
205 else:
206 curr_plugins = gmTools.coalesce(dbcfg.get2 (
207 option = u'horstspace.notebook.plugin_load_order',
208 workplace = workplace,
209 bias = 'workplace'
210 ), []
211 )
212
213 msg = _(
214 'Pick the plugin(s) to be loaded the next time the client is restarted under the workplace:\n'
215 '\n'
216 ' [%s]\n'
217 ) % workplace
218
219 picker = gmListWidgets.cItemPickerDlg (
220 parent,
221 -1,
222 title = _('Configuring workplace plugins ...'),
223 msg = msg
224 )
225 picker.set_columns(['Available plugins'], ['Active plugins'])
226 available_plugins = gmPlugin.get_installed_plugins(plugin_dir = 'gui')
227 picker.set_choices(available_plugins)
228 picker.set_picks(picks = curr_plugins)
229 btn_pressed = picker.ShowModal()
230 if btn_pressed != wx.ID_OK:
231 picker.Destroy()
232 return False
233
234 new_plugins = picker.get_picks()
235 picker.Destroy()
236 if new_plugins == curr_plugins:
237 return True
238
239 if new_plugins is None:
240 return True
241
242 dbcfg.set (
243 option = u'horstspace.notebook.plugin_load_order',
244 value = new_plugins,
245 workplace = workplace
246 )
247
248 return True
249
250 def edit_old(workplace=None):
251
252 available_plugins = gmPlugin.get_installed_plugins(plugin_dir='gui')
253
254 dbcfg = gmCfg.cCfgSQL()
255
256 if workplace is None:
257 dlg = wx.TextEntryDialog (
258 parent = parent,
259 message = _('Enter a descriptive name for the new workplace:'),
260 caption = _('Configuring GNUmed workplaces ...'),
261 defaultValue = u'',
262 style = wx.OK | wx.CENTRE
263 )
264 dlg.ShowModal()
265 workplace = dlg.GetValue().strip()
266 if workplace == u'':
267 gmGuiHelpers.gm_show_error(_('Cannot save a new workplace without a name.'), _('Configuring GNUmed workplaces ...'))
268 return False
269 curr_plugins = []
270 choices = available_plugins
271 else:
272 curr_plugins = gmTools.coalesce(dbcfg.get2 (
273 option = u'horstspace.notebook.plugin_load_order',
274 workplace = workplace,
275 bias = 'workplace'
276 ), []
277 )
278 choices = curr_plugins[:]
279 for p in available_plugins:
280 if p not in choices:
281 choices.append(p)
282
283 sels = range(len(curr_plugins))
284 new_plugins = gmListWidgets.get_choices_from_list (
285 parent = parent,
286 msg = _(
287 '\n'
288 'Select the plugin(s) to be loaded the next time\n'
289 'the client is restarted under the workplace:\n'
290 '\n'
291 ' [%s]'
292 '\n'
293 ) % workplace,
294 caption = _('Configuring GNUmed workplaces ...'),
295 choices = choices,
296 selections = sels,
297 columns = [_('Plugins')],
298 single_selection = False
299 )
300
301 if new_plugins == curr_plugins:
302 return True
303
304 if new_plugins is None:
305 return True
306
307 dbcfg.set (
308 option = u'horstspace.notebook.plugin_load_order',
309 value = new_plugins,
310 workplace = workplace
311 )
312
313 return True
314
315 def clone(workplace=None):
316 if workplace is None:
317 return False
318
319 new_name = wx.GetTextFromUser (
320 message = _('Enter a name for the new workplace !'),
321 caption = _('Cloning workplace'),
322 default_value = u'%s-2' % workplace,
323 parent = parent
324 ).strip()
325
326 if new_name == u'':
327 return False
328
329 dbcfg = gmCfg.cCfgSQL()
330 opt = u'horstspace.notebook.plugin_load_order'
331
332 plugins = dbcfg.get2 (
333 option = opt,
334 workplace = workplace,
335 bias = 'workplace'
336 )
337
338 dbcfg.set (
339 option = opt,
340 value = plugins,
341 workplace = new_name
342 )
343
344
345
346 return True
347
348 def refresh(lctrl):
349 workplaces = gmSurgery.gmCurrentPractice().workplaces
350 curr_workplace = gmSurgery.gmCurrentPractice().active_workplace
351 try:
352 sels = [workplaces.index(curr_workplace)]
353 except ValueError:
354 sels = []
355
356 lctrl.set_string_items(workplaces)
357 lctrl.set_selections(selections = sels)
358
359 gmListWidgets.get_choices_from_list (
360 parent = parent,
361 msg = _(
362 '\nSelect the workplace to configure below.\n'
363 '\n'
364 'The currently active workplace is preselected.\n'
365 ),
366 caption = _('Configuring GNUmed workplaces ...'),
367 columns = [_('Workplace')],
368 single_selection = True,
369 refresh_callback = refresh,
370 edit_callback = edit,
371 new_callback = edit,
372 delete_callback = delete,
373 left_extra_button = (_('Clone'), _('Clone the selected workplace'), clone)
374 )
375
377
379
380 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
381
382 query = u"""
383 SELECT DISTINCT ON (label)
384 pk_type,
385 (l10n_type || ' (' || l10n_category || ')')
386 AS label
387 FROM
388 dem.v_inbox_item_type
389 WHERE
390 l10n_type %(fragment_condition)s
391 OR
392 type %(fragment_condition)s
393 OR
394 l10n_category %(fragment_condition)s
395 OR
396 category %(fragment_condition)s
397 ORDER BY label
398 LIMIT 50"""
399
400 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
401 mp.setThresholds(1, 2, 4)
402 self.matcher = mp
403 self.SetToolTipString(_('Select a message type.'))
404
417
419 wx.CallAfter(__display_clinical_reminders)
420
421 gmDispatcher.connect(signal = u'post_patient_selection', receiver = _display_clinical_reminders)
422
424 pat = gmPerson.gmCurrentPatient()
425 if not pat.connected:
426 return
427 for msg in pat.due_messages:
428 if msg['expiry_date'] is None:
429 exp = u''
430 else:
431 exp = _(' - expires %s') % gmDateTime.pydt_strftime (
432 msg['expiry_date'],
433 '%Y %b %d',
434 accuracy = gmDateTime.acc_days
435 )
436 txt = _(
437 'Due for %s (since %s%s):\n'
438 '%s'
439 '%s'
440 '\n'
441 'Patient: %s\n'
442 'Reminder by: %s'
443 ) % (
444 gmDateTime.format_interval_medically(msg['interval_due']),
445 gmDateTime.pydt_strftime(msg['due_date'], '%Y %b %d', accuracy = gmDateTime.acc_days),
446 exp,
447 gmTools.coalesce(msg['comment'], u'', u'\n%s\n'),
448 gmTools.coalesce(msg['data'], u'', u'\n%s\n'),
449 pat['description_gender'],
450 msg['modified_by']
451 )
452 gmGuiHelpers.gm_show_warning (
453 aTitle = _('Clinical reminder'),
454 aMessage = txt
455 )
456 for hint in pat.dynamic_hints:
457 txt = u'%s\n\n%s\n\n %s' % (
458 hint['title'],
459 gmTools.wrap(hint['hint'], width = 50, initial_indent = u' ', subsequent_indent = u' '),
460 hint['source']
461 )
462 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
463 None,
464 -1,
465 caption = _('Clinical hint'),
466 question = txt,
467 button_defs = [
468 {'label': _('OK'), 'tooltip': _('OK'), 'default': True},
469 {'label': _('More info'), 'tooltip': _('Go to [%s]') % hint['url']}
470 ]
471 )
472 button = dlg.ShowModal()
473 dlg.Destroy()
474 if button == wx.ID_NO:
475 gmNetworkTools.open_url_in_browser(hint['url'], autoraise = False)
476
477 return
478
479 from Gnumed.wxGladeWidgets import wxgInboxMessageEAPnl
480
481 -class cInboxMessageEAPnl(wxgInboxMessageEAPnl.wxgInboxMessageEAPnl, gmEditArea.cGenericEditAreaMixin):
482
502
508
509
510
512 validity = True
513
514 if self._TCTRL_subject.GetValue().strip() == u'':
515 validity = False
516 self.display_ctrl_as_valid(ctrl = self._TCTRL_subject, valid = False)
517 else:
518 self.display_ctrl_as_valid(ctrl = self._TCTRL_subject, valid = True)
519
520 if self._PRW_type.GetValue().strip() == u'':
521 validity = False
522 self._PRW_type.display_as_valid(False)
523 else:
524 self._PRW_type.display_as_valid(True)
525
526 missing_receiver = (
527 (self._CHBOX_send_to_me.IsChecked() is False)
528 and
529 (self._PRW_receiver.GetData() is None)
530 )
531
532 missing_patient = (
533 (self._CHBOX_active_patient.IsChecked() is False)
534 and
535 (self._PRW_patient.person is None)
536 )
537
538 if missing_receiver and missing_patient:
539 validity = False
540 self.display_ctrl_as_valid(ctrl = self._CHBOX_send_to_me, valid = False)
541 self._PRW_receiver.display_as_valid(False)
542 self.display_ctrl_as_valid(ctrl = self._CHBOX_active_patient, valid = False)
543 self.display_ctrl_as_valid(ctrl = self._PRW_patient, valid = False)
544 else:
545 self.display_ctrl_as_valid(ctrl = self._CHBOX_send_to_me, valid = True)
546 self._PRW_receiver.display_as_valid(True)
547 self.display_ctrl_as_valid(ctrl = self._CHBOX_active_patient, valid = True)
548 self.display_ctrl_as_valid(ctrl = self._PRW_patient, valid = True)
549
550 return validity
551
553
554 pat_id = None
555 if self._CHBOX_active_patient.GetValue() is True:
556 pat_id = gmPerson.gmCurrentPatient().ID
557 else:
558 if self._PRW_patient.person is not None:
559 pat_id = self._PRW_patient.person.ID
560
561 receiver = None
562 if self._CHBOX_send_to_me.IsChecked():
563 receiver = gmStaff.gmCurrentProvider()['pk_staff']
564 else:
565 if self._PRW_receiver.GetData() is not None:
566 receiver = self._PRW_receiver.GetData()
567
568 msg = gmProviderInbox.create_inbox_message (
569 patient = pat_id,
570 staff = receiver,
571 message_type = self._PRW_type.GetData(can_create = True),
572 subject = self._TCTRL_subject.GetValue().strip()
573 )
574
575 msg['data'] = self._TCTRL_message.GetValue().strip()
576
577 if self._PRW_due.is_valid_timestamp():
578 msg['due_date'] = self._PRW_due.date
579
580 if self._PRW_expiry.is_valid_timestamp():
581 msg['expiry_date'] = self._PRW_expiry.date
582
583 if self._RBTN_normal.GetValue() is True:
584 msg['importance'] = 0
585 elif self._RBTN_high.GetValue() is True:
586 msg['importance'] = 1
587 else:
588 msg['importance'] = -1
589
590 msg.save()
591 self.data = msg
592 return True
593
629
655
657 self._refresh_as_new()
658
712
713
714
716 if self._CHBOX_active_patient.IsChecked():
717 self._PRW_patient.Enable(False)
718 self._PRW_patient.person = None
719 else:
720 self._PRW_patient.Enable(True)
721
723 if self._CHBOX_send_to_me.IsChecked():
724 self._PRW_receiver.Enable(False)
725 self._PRW_receiver.SetData(data = gmStaff.gmCurrentProvider()['pk_staff'])
726 else:
727 self._PRW_receiver.Enable(True)
728 self._PRW_receiver.SetText(value = u'', data = None)
729
745
746 from Gnumed.wxGladeWidgets import wxgProviderInboxPnl
747
748 -class cProviderInboxPnl(wxgProviderInboxPnl.wxgProviderInboxPnl, gmRegetMixin.cRegetOnPaintMixin):
749
750 _item_handlers = {}
751
752
768
769
770
772 self.__populate_inbox()
773 return True
774
775
776
778 gmDispatcher.connect(signal = u'message_inbox_generic_mod_db', receiver = self._on_message_inbox_mod_db)
779 gmDispatcher.connect(signal = u'message_inbox_mod_db', receiver = self._on_message_inbox_mod_db)
780
781 gmDispatcher.connect(signal = u'reviewed_test_results_mod_db', receiver = self._on_message_inbox_mod_db)
782 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_message_inbox_mod_db)
783 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_message_inbox_mod_db)
784 gmDispatcher.connect(signal = u'doc_obj_review_mod_db', receiver = self._on_message_inbox_mod_db)
785 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
786
804
828
829
830
832 wx.CallAfter(self._schedule_data_reget)
833 wx.CallAfter(self._RBTN_active_patient.Enable)
834
836 wx.CallAfter(self._schedule_data_reget)
837 gmDispatcher.send(signal = u'request_user_attention', msg = _('Please check your GNUmed Inbox !'))
838
840 msg = self._LCTRL_provider_inbox.get_selected_item_data(only_one = True)
841 if msg is None:
842 return
843
844 handler_key = '%s.%s' % (msg['category'], msg['type'])
845 try:
846 handle_item = cProviderInboxPnl._item_handlers[handler_key]
847 except KeyError:
848 if msg['pk_patient'] is None:
849 gmGuiHelpers.gm_show_warning (
850 _('No double-click action pre-programmed into\n'
851 'GNUmed for message category and type:\n'
852 '\n'
853 ' [%s]\n'
854 ) % handler_key,
855 _('handling provider inbox item')
856 )
857 return False
858 handle_item = self._goto_patient
859
860 if not handle_item(pk_context = msg['pk_context'], pk_patient = msg['pk_patient']):
861 _log.error('item handler returned <False>')
862 _log.error('handler key: [%s]', handler_key)
863 _log.error('message: %s', str(msg))
864 return False
865
866 return True
867
870
872 msg = self._LCTRL_provider_inbox.get_selected_item_data(only_one = True)
873 if msg is None:
874 return
875
876 if msg['data'] is None:
877 tmp = _('Message: %s') % msg['comment']
878 else:
879 tmp = _('Message: %s\nData: %s') % (msg['comment'], msg['data'])
880
881 self._TXT_inbox_item_comment.SetValue(tmp)
882
884 tmp = self._LCTRL_provider_inbox.get_selected_item_data(only_one = True)
885 if tmp is None:
886 return
887 self.__focussed_msg = tmp
888
889
890 menu = wx.Menu(title = _('Inbox Message Actions:'))
891
892 if self.__focussed_msg['pk_patient'] is not None:
893 ID = wx.NewId()
894 menu.AppendItem(wx.MenuItem(menu, ID, _('Activate patient')))
895 wx.EVT_MENU(menu, ID, self._on_goto_patient)
896
897 if not self.__focussed_msg['is_virtual']:
898
899 ID = wx.NewId()
900 menu.AppendItem(wx.MenuItem(menu, ID, _('Delete')))
901 wx.EVT_MENU(menu, ID, self._on_delete_focussed_msg)
902
903 ID = wx.NewId()
904 menu.AppendItem(wx.MenuItem(menu, ID, _('Edit')))
905 wx.EVT_MENU(menu, ID, self._on_edit_focussed_msg)
906
907
908
909
910
911
912
913
914 self.PopupMenu(menu, wx.DefaultPosition)
915 menu.Destroy()
916
921
926
929
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
971 return self._goto_patient(pk_patient = self.__focussed_msg['pk_patient'])
972
974 if self.__focussed_msg['is_virtual']:
975 gmDispatcher.send(signal = 'statustext', msg = _('You must deal with the reason for this message to remove it from your inbox.'), beep = True)
976 return False
977
978 if not self.provider.inbox.delete_message(self.__focussed_msg['pk_inbox_message']):
979 gmDispatcher.send(signal='statustext', msg=_('Problem removing message from Inbox.'))
980 return False
981 return True
982
984 if self.__focussed_msg['is_virtual']:
985 gmDispatcher.send(signal = 'statustext', msg = _('This message cannot be edited because it is virtual.'))
986 return False
987 edit_inbox_message(parent = self, message = self.__focussed_msg, single_entry = True)
988 return True
989
991 if self.__focussed_msg['pk_staff'] is None:
992 gmDispatcher.send(signal = 'statustext', msg = _('This message is already visible to all providers.'))
993 return False
994 print "now distributing"
995 return True
996
998
999 wx.BeginBusyCursor()
1000
1001 msg = _('There is a message about patient [%s].\n\n'
1002 'However, I cannot find that\n'
1003 'patient in the GNUmed database.'
1004 ) % pk_patient
1005
1006 try:
1007 pat = gmPerson.cIdentity(aPK_obj = pk_patient)
1008 except gmExceptions.ConstructorError:
1009 wx.EndBusyCursor()
1010 _log.exception('patient [%s] not found', pk_patient)
1011 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
1012 return False
1013 except:
1014 wx.EndBusyCursor()
1015 raise
1016
1017 success = gmPatSearchWidgets.set_active_patient(patient = pat)
1018
1019 wx.EndBusyCursor()
1020
1021 if not success:
1022 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
1023 return False
1024
1025 return True
1026
1054
1056
1057 msg = _('Supposedly there are unreviewed results\n'
1058 'for patient [%s]. However, I cannot find\n'
1059 'that patient in the GNUmed database.'
1060 ) % pk_patient
1061
1062 wx.BeginBusyCursor()
1063
1064 try:
1065 pat = gmPerson.cIdentity(aPK_obj = pk_patient)
1066 except gmExceptions.ConstructorError:
1067 wx.EndBusyCursor()
1068 _log.exception('patient [%s] not found', pk_patient)
1069 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
1070 return False
1071
1072 success = gmPatSearchWidgets.set_active_patient(patient = pat)
1073
1074 wx.EndBusyCursor()
1075
1076 if not success:
1077 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
1078 return False
1079
1080 wx.CallAfter(gmDispatcher.send, signal = 'display_widget', name = 'gmMeasurementsGridPlugin')
1081 return True
1082
1111
1113
1114 if parent is None:
1115 parent = wx.GetApp().GetTopWindow()
1116
1117 def get_tooltip(item):
1118 if item is None:
1119 return None
1120 return item.format()
1121
1122 def switch_activation(item):
1123 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Switching clinical hint activation'))
1124 if conn is None:
1125 return False
1126 item['is_active'] = not item['is_active']
1127 return item.save(conn = conn)
1128
1129 def manage_data_packs(item):
1130 gmDataPackWidgets.manage_data_packs(parent = parent)
1131 return True
1132
1133 def refresh(lctrl):
1134 hints = gmProviderInbox.get_dynamic_hints(order_by = u'is_active DESC, source, hint')
1135 items = [ [
1136 gmTools.bool2subst(h['is_active'], gmTools.u_checkmark_thin, u''),
1137 h['title'],
1138 h['source'][:30],
1139 h['hint'][:60],
1140 gmTools.coalesce(h['url'], u'')[:60],
1141 h['lang'],
1142 h['pk']
1143 ] for h in hints ]
1144 lctrl.set_string_items(items)
1145 lctrl.set_data(hints)
1146
1147 gmListWidgets.get_choices_from_list (
1148 parent = parent,
1149 msg = _('\nDynamic hints registered with GNUmed.\n'),
1150 caption = _('Showing dynamic hints.'),
1151 columns = [ _('Active'), _('Title'), _('Source'), _('Hint'), u'URL', _('Language'), u'#' ],
1152 single_selection = True,
1153 refresh_callback = refresh,
1154 left_extra_button = (
1155 _('(De)-Activate'),
1156 _('Switch activation of the selected hint'),
1157 switch_activation
1158 ),
1159 right_extra_button = (
1160 _('Data packs'),
1161 _('Browse and install clinical hints data packs'),
1162 manage_data_packs
1163 ),
1164 list_tooltip_callback = get_tooltip
1165 )
1166
1167
1168 if __name__ == '__main__':
1169
1170 if len(sys.argv) < 2:
1171 sys.exit()
1172
1173 if sys.argv[1] != 'test':
1174 sys.exit()
1175
1176 gmI18N.activate_locale()
1177 gmI18N.install_domain(domain = 'gnumed')
1178
1182
1184 app = wx.PyWidgetTester(size = (800, 600))
1185 app.SetWidget(cProviderInboxPnl, -1)
1186 app.MainLoop()
1187
1189 app = wx.PyWidgetTester(size = (800, 600))
1190 app.SetWidget(cInboxMessageEAPnl, -1)
1191 app.MainLoop()
1192
1193
1194
1195
1196 test_msg_ea()
1197
1198
1199