1 __doc__ = """GNUmed list controls and widgets.
2
3 TODO:
4
5 From: Rob McMullen <rob.mcmullen@gmail.com>
6 To: wxPython-users@lists.wxwidgets.org
7 Subject: Re: [wxPython-users] ANN: ColumnSizer mixin for ListCtrl
8
9 Thanks for all the suggestions, on and off line. There's an update
10 with a new name (ColumnAutoSizeMixin) and better sizing algorithm at:
11
12 http://trac.flipturn.org/browser/trunk/peppy/lib/column_autosize.py
13
14 sorting: http://code.activestate.com/recipes/426407/
15 """
16
17 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
18 __license__ = "GPL v2 or later"
19
20
21 import sys
22 import types
23 import logging
24 import threading
25 import time
26 import locale
27 import os
28 import io
29 import csv
30 import re as regex
31 import datetime as pydt
32
33
34 import wx
35 import wx.lib.mixins.listctrl as listmixins
36
37
38 if __name__ == '__main__':
39 sys.path.insert(0, '../../')
40 from Gnumed.pycommon import gmTools
41 from Gnumed.pycommon import gmDispatcher
42 from Gnumed.wxpython.gmGuiHelpers import decorate_window_title, undecorate_window_title
43
44
45 _log = logging.getLogger('gm.list_ui')
46
47
48
49 -def get_choices_from_list (
50 parent=None,
51 msg=None,
52 caption=None,
53 columns=None,
54 choices=None,
55 data=None,
56 selections=None,
57 edit_callback=None,
58 new_callback=None,
59 delete_callback=None,
60 refresh_callback=None,
61 single_selection=False,
62 can_return_empty=False,
63 ignore_OK_button=False,
64 left_extra_button=None,
65 middle_extra_button=None,
66 right_extra_button=None,
67 list_tooltip_callback=None):
68 """Let user select item(s) from a list.
69
70 - new_callback: ()
71 - edit_callback: (item data)
72 - delete_callback: (item data)
73 - refresh_callback: (listctrl)
74 - list_tooltip_callback: (item data)
75
76 - left/middle/right_extra_button: (label, tooltip, <callback> [, wants_list_ctrl])
77 <wants_list_ctrl> is optional
78 <callback> is called with item_data (or listctrl) as the only argument
79 if <callback> returns TRUE, the listctrl will be refreshed, if a refresh_callback is available
80
81 returns:
82 on [CANCEL]: None
83 on [OK]:
84 if any items selected:
85 if single_selection:
86 the data of the selected item
87 else:
88 list of data of selected items
89 else:
90 if can_return_empty is True AND [OK] button was pressed:
91 empty list
92 else:
93 None
94 """
95 caption = decorate_window_title(gmTools.coalesce(caption, _('generic multi choice dialog')))
96
97 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg, single_selection = single_selection)
98 dlg.refresh_callback = refresh_callback
99 dlg.edit_callback = edit_callback
100 dlg.new_callback = new_callback
101 dlg.delete_callback = delete_callback
102 dlg.list_tooltip_callback = list_tooltip_callback
103
104 dlg.can_return_empty = can_return_empty
105 dlg.ignore_OK_button = ignore_OK_button
106 dlg.left_extra_button = left_extra_button
107 dlg.middle_extra_button = middle_extra_button
108 dlg.right_extra_button = right_extra_button
109
110 dlg.set_columns(columns = columns)
111
112 if refresh_callback is None:
113 dlg.set_string_items(items = choices)
114 dlg.set_column_widths()
115
116 if data is not None:
117 dlg.set_data(data = data)
118
119 if selections is not None:
120 if single_selection:
121 dlg.set_selections(selections = selections[:1])
122 else:
123 dlg.set_selections(selections = selections)
124
125 btn_pressed = dlg.ShowModal()
126 sels = dlg.get_selected_item_data(only_one = single_selection)
127 dlg.DestroyLater()
128
129 if btn_pressed == wx.ID_OK:
130 if can_return_empty and (sels is None):
131 return []
132 return sels
133
134 return None
135
136
137 from Gnumed.wxGladeWidgets import wxgGenericListSelectorDlg
138
140 """A dialog holding a list and a few buttons to act on the items."""
141
182
183
186
187
190
191
195
196
197
208
209
212
213
216
217
218
219
221 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
222 if not self.can_return_empty:
223 self._BTN_cancel.SetDefault()
224 self._BTN_ok.Enable(False)
225 self._BTN_edit.Enable(False)
226 self._BTN_delete.Enable(False)
227
228 event.Skip()
229
230
234
235
241
242
271
272
291
292
311
312
331
332
333
334
347
348
351
352
354 any_deleted = False
355 for item_data in self._LCTRL_items.get_selected_item_data(only_one = False):
356 if item_data is None:
357 continue
358 if self.__delete_callback(item_data):
359 any_deleted = True
360
361 self._LCTRL_items.SetFocus()
362
363 if any_deleted is False:
364 return
365
366 if self.__refresh_callback is None:
367 return
368
369 wx.BeginBusyCursor()
370 try:
371 self.__refresh_callback(lctrl = self._LCTRL_items)
372 self._LCTRL_items.set_column_widths()
373 finally:
374 wx.EndBusyCursor()
375
376
379
380
382 if not self.__edit_callback(self._LCTRL_items.get_selected_item_data(only_one = True)):
383 self._LCTRL_items.SetFocus()
384 return
385 if self.__refresh_callback is None:
386 self._LCTRL_items.SetFocus()
387 return
388 wx.BeginBusyCursor()
389 try:
390 self.__refresh_callback(lctrl = self._LCTRL_items)
391 self._LCTRL_items.set_column_widths()
392 self._LCTRL_items.SetFocus()
393 finally:
394 wx.EndBusyCursor()
395
396
399
400
402 if not self.__new_callback():
403 self._LCTRL_items.SetFocus()
404 return
405 if self.__refresh_callback is None:
406 self._LCTRL_items.SetFocus()
407 return
408 wx.BeginBusyCursor()
409 try:
410 self.__refresh_callback(lctrl = self._LCTRL_items)
411 self._LCTRL_items.set_column_widths()
412 self._LCTRL_items.SetFocus()
413 finally:
414 wx.EndBusyCursor()
415
416
417
418
432
433 ignore_OK_button = property(lambda x:x, _set_ignore_OK_button)
434
435
458
459 left_extra_button = property(lambda x:x, _set_left_extra_button)
460
461
484
485 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
486
487
510
511 right_extra_button = property(lambda x:x, _set_right_extra_button)
512
513
515 return self.__new_callback
516
518 if callback is not None:
519 if self.__refresh_callback is None:
520 raise ValueError('refresh callback must be set before new callback can be set')
521 if not callable(callback):
522 raise ValueError('<new> callback is not a callable: %s' % callback)
523 self.__new_callback = callback
524
525 if callback is None:
526 self._BTN_new.Enable(False)
527 self._BTN_new.Hide()
528 self._LCTRL_items.new_callback = None
529 else:
530 self._BTN_new.Enable(True)
531 self._BTN_new.Show()
532 self._LCTRL_items.new_callback = self._on_insert_key_pressed_in_listctrl
533
534 new_callback = property(_get_new_callback, _set_new_callback)
535
536
538 return self.__edit_callback
539
541 if callback is not None:
542 if not callable(callback):
543 raise ValueError('<edit> callback is not a callable: %s' % callback)
544 self.__edit_callback = callback
545
546 if callback is None:
547 self._BTN_edit.Enable(False)
548 self._BTN_edit.Hide()
549 self._LCTRL_items.edit_callback = None
550 else:
551 self._BTN_edit.Enable(True)
552 self._BTN_edit.Show()
553 self._LCTRL_items.edit_callback = self._on_edit_invoked_in_listctrl
554
555 edit_callback = property(_get_edit_callback, _set_edit_callback)
556
557
559 return self.__delete_callback
560
562 if callback is not None:
563 if self.__refresh_callback is None:
564 raise ValueError('refresh callback must be set before delete callback can be set')
565 if not callable(callback):
566 raise ValueError('<delete> callback is not a callable: %s' % callback)
567 self.__delete_callback = callback
568 if callback is None:
569 self._BTN_delete.Enable(False)
570 self._BTN_delete.Hide()
571 self._LCTRL_items.delete_callback = None
572 else:
573 self._BTN_delete.Enable(True)
574 self._BTN_delete.Show()
575 self._LCTRL_items.delete_callback = self._on_delete_key_pressed_in_listctrl
576
577 delete_callback = property(_get_delete_callback, _set_delete_callback)
578
579
581 return self.__refresh_callback
582
584 wx.BeginBusyCursor()
585 try:
586 self.__refresh_callback(lctrl = self._LCTRL_items)
587 finally:
588 wx.EndBusyCursor()
589 self._LCTRL_items.set_column_widths()
590
592 if callback is not None:
593 if not callable(callback):
594 raise ValueError('<refresh> callback is not a callable: %s' % callback)
595 self.__refresh_callback = callback
596 if callback is not None:
597 wx.CallAfter(self._set_refresh_callback_helper)
598
599 refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
600
601
603 return self.__select_callback
604
606 if callback is not None:
607 if not callable(callback):
608 raise ValueError('<select> callback is not a callable: %s' % callback)
609 self.__select_callback = callback
610
611 select_callback = property(_get_select_callback, _set_select_callback)
612
613
616
617 list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback)
618
619
620
622 if message is None:
623 self._LBL_message.Hide()
624 return
625 self._LBL_message.SetLabel(message)
626 self._LBL_message.Show()
627
628 message = property(lambda x:x, _set_message)
629
630
631 from Gnumed.wxGladeWidgets import wxgGenericListManagerPnl
632
634 """A panel holding a generic multi-column list and action buttions."""
635
662
663
664
665
668
669
677
678
679
680
683
684
687
688
691
692
693
694
696 event.Skip()
697 if self.__edit_callback is not None:
698 self._BTN_edit.Enable(True)
699 if self.__delete_callback is not None:
700 self._BTN_remove.Enable(True)
701 if self.__select_callback is not None:
702 item = self._LCTRL_items.get_selected_item_data(only_one = True)
703 self.__select_callback(item)
704
705
708
709
711 if not self.__delete_callback(self._LCTRL_items.get_selected_item_data(only_one = True)):
712 return
713 if self.__refresh_callback is None:
714 self._LCTRL_items.SetFocus()
715 return
716 wx.BeginBusyCursor()
717 try:
718 self.__refresh_callback(lctrl = self._LCTRL_items)
719 self._LCTRL_items.set_column_widths()
720 self._LCTRL_items.SetFocus()
721 finally:
722 wx.EndBusyCursor()
723
724
727
728
730 if not self.__edit_callback(self._LCTRL_items.get_selected_item_data(only_one = True)):
731 self._LCTRL_items.SetFocus()
732 return
733 if self.__refresh_callback is None:
734 self._LCTRL_items.SetFocus()
735 return
736 wx.BeginBusyCursor()
737 try:
738 self.__refresh_callback(lctrl = self._LCTRL_items)
739 self._LCTRL_items.set_column_widths()
740 self._LCTRL_items.SetFocus()
741 finally:
742 wx.EndBusyCursor()
743
744
747
748
750 if not self.__new_callback():
751 self._LCTRL_items.SetFocus()
752 return
753 if self.__refresh_callback is None:
754 self._LCTRL_items.SetFocus()
755 return
756 wx.BeginBusyCursor()
757 try:
758 self.__refresh_callback(lctrl = self._LCTRL_items)
759 self._LCTRL_items.set_column_widths()
760 self._LCTRL_items.SetFocus()
761 finally:
762 wx.EndBusyCursor()
763
764
765
766
768 event.Skip()
769 if self._LCTRL_items.get_selected_items(only_one = True) == -1:
770 self._BTN_edit.Enable(False)
771 self._BTN_remove.Enable(False)
772 if self.__select_callback is not None:
773 self.__select_callback(None)
774
775
777 event.Skip()
778 if self.__edit_callback is None:
779 return
780 self._on_edit_button_pressed(event)
781
782
793
794
808
809
814
815
831
832
848
849
865
866
867
868
870 return self.__new_callback
871
873 if callback is not None:
874 if self.__refresh_callback is None:
875 raise ValueError('refresh callback must be set before new callback can be set')
876 if not callable(callback):
877 raise ValueError('<new> callback is not a callable: %s' % callback)
878 self.__new_callback = callback
879
880 if callback is None:
881 self._BTN_add.Enable(False)
882 self._BTN_add.Hide()
883 self._LCTRL_items.new_callback = None
884 else:
885 self._BTN_add.Enable(True)
886 self._BTN_add.Show()
887 self._LCTRL_items.new_callback = self._on_insert_key_pressed_in_listctrl
888
889 new_callback = property(_get_new_callback, _set_new_callback)
890
891
893 return self.__edit_callback
894
896 if callback is not None:
897 if not callable(callback):
898 raise ValueError('<edit> callback is not a callable: %s' % callback)
899 self.__edit_callback = callback
900
901 if callback is None:
902 self._BTN_edit.Enable(False)
903 self._BTN_edit.Hide()
904 self._LCTRL_items.edit_callback = None
905 else:
906 self._BTN_edit.Enable(True)
907 self._BTN_edit.Show()
908 self._LCTRL_items.edit_callback = self._on_edit_invoked_in_listctrl
909
910 edit_callback = property(_get_edit_callback, _set_edit_callback)
911
912
914 return self.__delete_callback
915
917 if callback is not None:
918 if self.__refresh_callback is None:
919 raise ValueError('refresh callback must be set before delete callback can be set')
920 if not callable(callback):
921 raise ValueError('<delete> callback is not a callable: %s' % callback)
922 self.__delete_callback = callback
923 if callback is None:
924 self._BTN_remove.Enable(False)
925 self._BTN_remove.Hide()
926 self._LCTRL_items.delete_callback = None
927 else:
928 self._BTN_remove.Enable(True)
929 self._BTN_remove.Show()
930 self._LCTRL_items.delete_callback = self._on_delete_key_pressed_in_listctrl
931
932 delete_callback = property(_get_delete_callback, _set_delete_callback)
933
934
936 return self.__refresh_callback
937
939 wx.BeginBusyCursor()
940 try:
941 self.__refresh_callback(lctrl = self._LCTRL_items)
942 finally:
943 wx.EndBusyCursor()
944 self._LCTRL_items.set_column_widths()
945
947 if callback is not None:
948 if not callable(callback):
949 raise ValueError('<refresh> callback is not a callable: %s' % callback)
950 self.__refresh_callback = callback
951 if callback is not None:
952 wx.CallAfter(self._set_refresh_callback_helper)
953
954 refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
955
956
958 return self.__select_callback
959
961 if callback is not None:
962 if not callable(callback):
963 raise ValueError('<select> callback is not a callable: %s' % callback)
964 self.__select_callback = callback
965
966 select_callback = property(_get_select_callback, _set_select_callback)
967
968
971
973 if msg is None:
974 self._LBL_message.Hide()
975 self._LBL_message.SetLabel('')
976 else:
977 self._LBL_message.SetLabel(msg)
978 self._LBL_message.Show()
979 self.Layout()
980
981 message = property(_get_message, _set_message)
982
983
999
1000 left_extra_button = property(lambda x:x, _set_left_extra_button)
1001
1002
1018
1019 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
1020
1021
1037
1038 right_extra_button = property(lambda x:x, _set_right_extra_button)
1039
1040
1041 from Gnumed.wxGladeWidgets import wxgItemPickerDlg
1042
1044
1046
1047 try:
1048 msg = kwargs['msg']
1049 del kwargs['msg']
1050 except KeyError:
1051 msg = None
1052
1053 try:
1054 title = kwargs['title']
1055 except KeyError:
1056 title = self.__class__.__name__
1057 kwargs['title'] = decorate_window_title(title)
1058
1059 wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs)
1060
1061 if msg is None:
1062 self._LBL_msg.Hide()
1063 else:
1064 self._LBL_msg.SetLabel(msg)
1065
1066 self.ignore_dupes_on_picking = True
1067
1068 self._LCTRL_left.activate_callback = self.__pick_selected
1069 self.__extra_button_callback = None
1070
1071 self._LCTRL_left.SetFocus()
1072
1073
1074
1075
1076 - def set_columns(self, columns=None, columns_right=None):
1077 self._LCTRL_left.set_columns(columns = columns)
1078 if columns_right is None:
1079 self._LCTRL_right.set_columns(columns = columns)
1080 return
1081
1082 if len(columns_right) < len(columns):
1083 cols = columns
1084 else:
1085 cols = columns_right[:len(columns)]
1086 self._LCTRL_right.set_columns(columns = cols)
1087
1088
1096
1097
1100
1101
1102 - def set_choices(self, choices=None, data=None, reshow=True):
1106
1107
1108 - def set_picks(self, picks=None, data=None, reshow=True):
1113
1114
1117
1118
1121
1122 picks = property(get_picks, lambda x:x)
1123
1124
1140
1141 extra_button = property(lambda x:x, _set_extra_button)
1142
1143
1144
1145
1176
1177
1178
1179
1181 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
1182 return
1183
1184 for item_idx in self._LCTRL_right.get_selected_items(only_one = False):
1185 self._LCTRL_right.remove_item(item_idx)
1186
1187 if self._LCTRL_right.GetItemCount() == 0:
1188 self._BTN_right2left.Enable(False)
1189
1190
1191
1192
1193
1194
1195
1197 self._BTN_left2right.Enable(True)
1198
1202
1204 self._BTN_right2left.Enable(True)
1205
1209
1212
1215
1218
1221
1222 left_item_tooltip_callback = property(lambda x:x, _set_left_item_tooltip_callback)
1223
1226
1227 right_item_tooltip_callback = property(lambda x:x, _set_right_item_tooltip_callback)
1228
1229
1230 -class cReportListCtrl(listmixins.ListCtrlAutoWidthMixin, listmixins.ColumnSorterMixin, wx.ListCtrl):
1231
1232
1233
1234
1235
1236
1237
1238
1239 sort_order_tags = {
1240 True: ' [\u03b1\u0391 \u2192 \u03c9\u03A9]',
1241 False: ' [\u03c9\u03A9 \u2192 \u03b1\u0391]'
1242 }
1243
1245
1246 self.debug = None
1247 self.map_item_idx2data_idx = self.GetItemData
1248
1249 try:
1250 kwargs['style'] = kwargs['style'] | wx.LC_REPORT
1251 except KeyError:
1252 kwargs['style'] = wx.LC_REPORT
1253
1254 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL)
1255
1256 wx.ListCtrl.__init__(self, *args, **kwargs)
1257 listmixins.ListCtrlAutoWidthMixin.__init__(self)
1258
1259
1260 self._invalidate_sorting_metadata()
1261 listmixins.ColumnSorterMixin.__init__(self, 0)
1262 self.__secondary_sort_col = None
1263
1264 self.Bind(wx.EVT_LIST_COL_CLICK, self._on_col_click, self)
1265
1266
1267 self.__widths = None
1268 self.__data = None
1269
1270
1271 self.__select_callback = None
1272 self.__deselect_callback = None
1273 self.__activate_callback = None
1274 self.__new_callback = None
1275 self.__edit_callback = None
1276 self.__delete_callback = None
1277
1278
1279 self.__extend_popup_menu_callback = None
1280 self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self._on_list_item_rightclicked)
1281
1282
1283 self.__item_tooltip_callback = None
1284 self.__tt_last_item = None
1285
1286
1287
1288
1289
1290
1291
1292
1293 self.__tt_static_part_base = ''
1294 self.__tt_static_part = self.__tt_static_part_base
1295 self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
1296
1297
1298 self.__search_term = None
1299 self.__next_line_to_search = 0
1300 self.__searchable_cols = None
1301
1302
1303
1304 self.Bind(wx.EVT_CHAR, self._on_char)
1305 self.Bind(wx.EVT_LIST_KEY_DOWN, self._on_list_key_down)
1306
1307
1308
1309
1311 if self.debug is None:
1312 return False
1313 if not self.debug.endswith('_sizing'):
1314 return False
1315 _log.debug('[%s.%s]: *args = (%s), **kwargs = (%s)', self.debug, caller_name, str(args), str(kwargs))
1316 return True
1317
1318
1320 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1321 return super(cReportListCtrl, self).CacheBestSize(*args, **kwargs)
1322
1323
1324 - def Fit(self, *args, **kwargs):
1325 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1326 return super(cReportListCtrl, self).Fit(*args, **kwargs)
1327
1328
1330 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1331 return super(cReportListCtrl, self).FitInside(*args, **kwargs)
1332
1333
1337
1338
1342
1343
1347
1348
1350 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1351 return super(cReportListCtrl, self).SetClientSize(*args, **kwargs)
1352
1353
1357
1358
1362
1363
1365 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1366 return super(cReportListCtrl, self).SetMaxSize(*args, **kwargs)
1367
1368
1372
1373
1375 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1376 return super(cReportListCtrl, self).SetMinSize(*args, **kwargs)
1377
1378
1379 - def SetSize(self, *args, **kwargs):
1380 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1381 return super(cReportListCtrl, self).SetSize(*args, **kwargs)
1382
1383
1385 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1386 return super(cReportListCtrl, self).SetSizeHints(*args, **kwargs)
1387
1388
1392
1393
1395 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1396 return super(cReportListCtrl, self).SetSizeWH(*args, **kwargs)
1397
1398
1402
1403
1407
1408
1412
1413
1417
1418
1420 res = super(cReportListCtrl, self).GetAdjustedBestSize(*args, **kwargs)
1421 kwargs['sizing_function_result'] = res
1422 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1423 return res
1424
1425
1427 res = super(cReportListCtrl, self).GetEffectiveMinSize(*args, **kwargs)
1428 kwargs['sizing_function_result'] = res
1429 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1430 return res
1431
1432
1434 res = super(cReportListCtrl, self).GetBestSize(*args, **kwargs)
1435 kwargs['sizing_function_result'] = res
1436 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1437 return res
1438
1439
1441 res = super(cReportListCtrl, self).GetBestSizeTuple(*args, **kwargs)
1442 kwargs['sizing_function_result'] = res
1443 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1444 return res
1445
1446
1448 res = super(cReportListCtrl, self).GetBestVirtualSize(*args, **kwargs)
1449 kwargs['sizing_function_result'] = res
1450 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1451 return res
1452
1453
1455 res = super(cReportListCtrl, self).GetClientSize(*args, **kwargs)
1456 kwargs['sizing_function_result'] = res
1457 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1458 return res
1459
1460
1462 res = super(cReportListCtrl, self).GetClientSize(*args, **kwargs)
1463 kwargs['sizing_function_result'] = res
1464 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1465 return res
1466
1467
1469 res = super(cReportListCtrl, self).GetMaxClientSize(*args, **kwargs)
1470 kwargs['sizing_function_result'] = res
1471 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1472 return res
1473
1474
1476 res = super(cReportListCtrl, self).GetMaxHeight(*args, **kwargs)
1477 kwargs['sizing_function_result'] = res
1478 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1479 return res
1480
1481
1483 res = super(cReportListCtrl, self).GetMaxSize(*args, **kwargs)
1484 kwargs['sizing_function_result'] = res
1485 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1486 return res
1487
1488
1490 res = super(cReportListCtrl, self).GetMaxWidth(*args, **kwargs)
1491 kwargs['sizing_function_result'] = res
1492 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1493 return res
1494
1495
1497 res = super(cReportListCtrl, self).GetMinClientSize(*args, **kwargs)
1498 kwargs['sizing_function_result'] = res
1499 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1500 return res
1501
1502
1504 res = super(cReportListCtrl, self).GetMinHeight(*args, **kwargs)
1505 kwargs['sizing_function_result'] = res
1506 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1507 return res
1508
1509
1511 res = super(cReportListCtrl, self).GetMinSize(*args, **kwargs)
1512 kwargs['sizing_function_result'] = res
1513 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1514 return res
1515
1516
1518 res = super(cReportListCtrl, self).GetMinWidth(*args, **kwargs)
1519 kwargs['sizing_function_result'] = res
1520 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1521 return res
1522
1523
1524 - def GetSize(self, *args, **kwargs):
1525 res = super(cReportListCtrl, self).GetSize(*args, **kwargs)
1526 kwargs['sizing_function_result'] = res
1527 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1528 return res
1529
1530
1532 res = super(cReportListCtrl, self).GetVirtualSize(*args, **kwargs)
1533 kwargs['sizing_function_result'] = res
1534 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1535 return res
1536
1537
1539 res = super(cReportListCtrl, self).GetVirtualSizeTuple(*args, **kwargs)
1540 kwargs['sizing_function_result'] = res
1541 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1542 return res
1543
1544
1545
1546
1548 """(Re)define the columns.
1549
1550 Note that this will (have to) delete the items.
1551 """
1552 self.ClearAll()
1553 self.__tt_last_item = None
1554 if columns is None:
1555 return
1556 for idx in range(len(columns)):
1557 self.InsertColumn(idx, columns[idx])
1558
1559 listmixins.ColumnSorterMixin.__init__(self, 0)
1560 self._invalidate_sorting_metadata()
1561
1562
1564 """Set the column width policy.
1565
1566 widths = None:
1567 use previous policy if any or default policy
1568 widths != None:
1569 use this policy and remember it for later calls
1570
1571 options:
1572 wx.LIST_AUTOSIZE_USEHEADER
1573 wx.LIST_AUTOSIZE
1574
1575 This means there is no way to *revert* to the default policy :-(
1576 """
1577
1578 if widths is not None:
1579 self.__widths = widths
1580 for idx in range(len(self.__widths)):
1581 self.SetColumnWidth(idx, self.__widths[idx])
1582 return
1583
1584
1585 if self.__widths is not None:
1586 for idx in range(len(self.__widths)):
1587 self.SetColumnWidth(idx, self.__widths[idx])
1588 return
1589
1590
1591 if self.GetItemCount() == 0:
1592 width_type = wx.LIST_AUTOSIZE_USEHEADER
1593 else:
1594 width_type = wx.LIST_AUTOSIZE
1595 for idx in range(self.GetColumnCount()):
1596 self.SetColumnWidth(idx, width_type)
1597
1598
1600 if column != 'LAST':
1601 if column > self.ColumnCount:
1602 return
1603
1604 self.setResizeColumn(column)
1605
1606
1608 assert(col_idx > -1), '<col_idx> must be positive integer'
1609 if col_idx > self.ColumnCount:
1610 _log.warning('<col_idx>=%s, .ColumnCount=%s', col_idx, self.ColumnCount)
1611 return
1612
1613 sort_col_idx, is_ascending = self.GetSortState()
1614 col_state = self.GetColumn(col_idx)
1615 col_state.Text = label
1616 if col_idx == sort_col_idx:
1617 col_state.Text += self.sort_order_tags[is_ascending]
1618 self.SetColumn(col_idx, col_state)
1619
1620
1622 tries = 0
1623 while tries < max_tries:
1624 if self.debug is not None:
1625 if self.debug.endswith('_deleting'):
1626 _log.debug('[round %s] <%s>.GetItemCount() before DeleteAllItems(): %s (thread [%s])', tries, self.debug, self.GetItemCount(), threading.get_ident())
1627 if not self.DeleteAllItems():
1628 _log.error('<%s>.DeleteAllItems() failed', self.debug)
1629 item_count = self.GetItemCount()
1630 if item_count == 0:
1631 return True
1632 wx.SafeYield(None, True)
1633 _log.error('<%s>.GetItemCount() not 0 (rather: %s) after DeleteAllItems()', self.debug, item_count)
1634 time.sleep(0.3)
1635 wx.SafeYield(None, True)
1636 tries += 1
1637
1638 _log.error('<%s>: unable to delete list items after looping %s times', self.debug, max_tries)
1639 return False
1640
1641
1643 """All item members must be str()able or None."""
1644
1645 wx.BeginBusyCursor()
1646 self._invalidate_sorting_metadata()
1647
1648 if self.ItemCount == 0:
1649 topmost_visible = 0
1650 else:
1651 topmost_visible = self.GetFirstSelected()
1652 if topmost_visible == -1:
1653 topmost_visible = self.GetFocusedItem()
1654 if topmost_visible == -1:
1655 topmost_visible = self.TopItem
1656
1657 if not self.remove_items_safely(max_tries = 3):
1658 _log.error("cannot remove items (!?), continuing and hoping for the best")
1659
1660 if items is None:
1661 self.data = None
1662 wx.EndBusyCursor()
1663 return
1664
1665
1666 for item in items:
1667
1668
1669 if isinstance(item, str):
1670 self.InsertItem(index = sys.maxsize, label = item.replace('\r\n', ' [CRLF] ').replace('\n', ' [LF] '))
1671 continue
1672
1673 try:
1674
1675 col_val = str(item[0])
1676 row_num = self.InsertItem(index = sys.maxsize, label = col_val)
1677 for col_num in range(1, min(self.GetColumnCount(), len(item))):
1678 col_val = str(item[col_num]).replace('\r\n', ' [CRLF] ').replace('\n', ' [LF] ')
1679 self.SetItem(index = row_num, column = col_num, label = col_val)
1680 except (TypeError, KeyError, IndexError):
1681
1682
1683 col_val = str(item).replace('\r\n', ' [CRLF] ').replace('\n', ' [LF] ')
1684 self.InsertItem(index = sys.maxsize, label = col_val)
1685
1686 if reshow:
1687 if self.ItemCount > 0:
1688 if topmost_visible < self.ItemCount:
1689 self.EnsureVisible(topmost_visible)
1690 self.Focus(topmost_visible)
1691 else:
1692 self.EnsureVisible(self.ItemCount - 1)
1693 self.Focus(self.ItemCount - 1)
1694
1695
1696 self.data = items
1697
1698 wx.EndBusyCursor()
1699
1700
1702 if self.ItemCount == 0:
1703 return []
1704
1705 rows = []
1706 for row_idx in range(self.ItemCount):
1707 row = []
1708 for col_idx in range(self.ColumnCount):
1709 row.append(self.GetItem(row_idx, col_idx).GetText())
1710 rows.append(row)
1711 return rows
1712
1713
1714
1715
1716 string_items = property(get_string_items, set_string_items)
1717
1718
1720 if len(new_items) == 0:
1721 return
1722
1723 if new_data is None:
1724 new_data = new_items
1725
1726 existing_data = self.get_item_data()
1727 if existing_data is None:
1728 existing_data = []
1729
1730 if allow_dupes:
1731 self.set_string_items (
1732 items = self.string_items.extend(new_items),
1733 reshow = True
1734 )
1735 self.data = existing_data.extend(new_data)
1736 self.set_column_widths()
1737 return
1738
1739 existing_items = self.get_string_items()
1740 for new_item, new_data in zip(new_items, new_data):
1741 if new_item in existing_items:
1742 continue
1743 existing_items.append(new_item)
1744 existing_data.append(new_data)
1745 self.set_string_items (
1746 items = existing_items,
1747 reshow = True
1748 )
1749 self.data = existing_data
1750 self.set_column_widths()
1751
1752
1754 """<data> assumed to be a list corresponding to the item indices"""
1755 if data is not None:
1756 item_count = self.GetItemCount()
1757 if len(data) != item_count:
1758 _log.debug('<data> length (%s) must be equal to number of list items (%s) (%s, thread [%s])', len(data), item_count, self.debug, threading.get_ident())
1759 for item_idx in range(len(data)):
1760 self.SetItemData(item_idx, item_idx)
1761 self.__data = data
1762 self.__tt_last_item = None
1763
1764
1765 return
1766
1773
1774 data = property(_get_data, set_data)
1775
1776
1778
1779 if self.GetItemCount() > 0:
1780 self.Select(0, on = 0)
1781 if selections is None:
1782 return
1783 for idx in selections:
1784 self.Select(idx = idx, on = 1)
1785
1787 if self.ItemCount == 0:
1788 return []
1789 if self.__is_single_selection:
1790 return [self.GetFirstSelected()]
1791 selections = []
1792 idx = self.GetFirstSelected()
1793 while idx != -1:
1794 selections.append(idx)
1795 idx = self.GetNextSelected(idx)
1796 return selections
1797
1798 selections = property(__get_selections, set_selections)
1799
1800
1801
1802
1804 labels = []
1805 for col_idx in range(self.ColumnCount):
1806 col = self.GetColumn(col = col_idx)
1807 labels.append(col.Text)
1808 return labels
1809
1810 column_labels = property(get_column_labels, lambda x:x)
1811
1812
1814 if self.ItemCount == 0:
1815 _log.warning('no items')
1816 return None
1817 if item_idx is not None:
1818 return self.GetItem(item_idx)
1819 _log.error('get_item(None) called')
1820 return None
1821
1822
1824 if self.ItemCount == 0:
1825 return []
1826 return [ self.GetItem(item_idx) for item_idx in range(self.ItemCount) ]
1827
1828 items = property(get_items, lambda x:x)
1829
1830
1832
1833 if self.ItemCount == 0:
1834 if self.__is_single_selection or only_one:
1835 return None
1836 return []
1837
1838 if self.__is_single_selection or only_one:
1839 return self.GetFirstSelected()
1840
1841 items = []
1842 idx = self.GetFirstSelected()
1843 while idx != -1:
1844 items.append(idx)
1845 idx = self.GetNextSelected(idx)
1846
1847 return items
1848
1849 selected_items = property(get_selected_items, lambda x:x)
1850
1851
1853
1854 if self.ItemCount == 0:
1855 if self.__is_single_selection or only_one:
1856 return None
1857 return []
1858
1859 if self.__is_single_selection or only_one:
1860 return self.GetItemText(self.GetFirstSelected())
1861
1862 items = []
1863 idx = self.GetFirstSelected()
1864 while idx != -1:
1865 items.append(self.GetItemText(idx))
1866 idx = self.GetNextSelected(idx)
1867
1868 return items
1869
1870 selected_string_items = property(get_selected_string_items, lambda x:x)
1871
1872
1874
1875 if self.__data is None:
1876 return None
1877
1878 if item_idx is not None:
1879 return self.__data[self.map_item_idx2data_idx(item_idx)]
1880
1881
1882
1883
1884 return [ self.__data[self.map_item_idx2data_idx(item_idx)] for item_idx in range(self.GetItemCount()) ]
1885
1886 item_data = property(get_item_data, lambda x:x)
1887
1888
1890
1891 if self.__is_single_selection or only_one:
1892 if self.__data is None:
1893 return None
1894 idx = self.GetFirstSelected()
1895 if idx == -1:
1896 return None
1897 return self.__data[self.map_item_idx2data_idx(idx)]
1898
1899 data = []
1900 if self.__data is None:
1901 return data
1902 idx = self.GetFirstSelected()
1903 while idx != -1:
1904 data.append(self.__data[self.map_item_idx2data_idx(idx)])
1905 idx = self.GetNextSelected(idx)
1906
1907 return data
1908
1909 selected_item_data = property(get_selected_item_data, lambda x:x)
1910
1911
1913 self.Select(idx = self.GetFirstSelected(), on = 0)
1914
1915
1917
1918
1919
1920
1921
1922
1923 self.DeleteItem(item_idx)
1924 self.__tt_last_item = None
1925 self._invalidate_sorting_metadata()
1926
1927
1928
1929
1931
1932 if item_idx == -1:
1933 return
1934
1935 if self.ItemCount == 0:
1936 return
1937
1938 items = self.selected_items
1939 if self.__is_single_selection:
1940 if items is None:
1941 no_of_selected_items = 0
1942 else:
1943 no_of_selected_items = 1
1944 else:
1945 no_of_selected_items = len(items)
1946 if no_of_selected_items == 0:
1947 title = _('List Item Actions')
1948 elif no_of_selected_items == 1:
1949 title = _('List Item Actions (selected: 1 entry)')
1950 else:
1951 title = _('List Item Actions (selected: %s entries)') % no_of_selected_items
1952
1953
1954 self._context_menu = wx.Menu(title = title)
1955
1956 needs_separator = False
1957 if self.__new_callback is not None:
1958 menu_item = self._context_menu.Append(-1, _('Add (<INS>)'))
1959 self.Bind(wx.EVT_MENU, self._on_add_row, menu_item)
1960 needs_separator = True
1961 if self.__edit_callback is not None:
1962 menu_item = self._context_menu.Append(-1, _('&Edit'))
1963 self.Bind(wx.EVT_MENU, self._on_edit_row, menu_item)
1964 needs_separator = True
1965 if self.__delete_callback is not None:
1966 menu_item = self._context_menu.Append(-1, _('Delete (<DEL>)'))
1967 self.Bind(wx.EVT_MENU, self._on_delete_row, menu_item)
1968 needs_separator = True
1969 if needs_separator:
1970 self._context_menu.AppendSeparator()
1971
1972 menu_item = self._context_menu.Append(-1, _('Find (<CTRL-F>)'))
1973 self.Bind(wx.EVT_MENU, self._on_show_search_dialog, menu_item)
1974 if self.__search_term is not None:
1975 if self.__search_term.strip() != '':
1976 menu_item = self._context_menu.Append(-1, _('Find next [%s] (<CTRL-N>)') % self.__search_term)
1977 self.Bind(wx.EVT_MENU, self._on_search_match, menu_item)
1978 self._context_menu.AppendSeparator()
1979
1980 col_headers = []
1981 self._rclicked_row_idx = item_idx
1982 self._rclicked_row_data = self.get_item_data(item_idx = self._rclicked_row_idx)
1983 self._rclicked_row_cells = []
1984 self._rclicked_row_cells_w_hdr = []
1985 for col_idx in range(self.ColumnCount):
1986 cell_content = self.GetItem(self._rclicked_row_idx, col_idx).Text.strip()
1987 col_header = self.GetColumn(col_idx).Text.strip()
1988 col_headers.append(col_header)
1989 self._rclicked_row_cells.append(cell_content)
1990 self._rclicked_row_cells_w_hdr.append('%s: %s' % (col_header, cell_content))
1991
1992
1993 save_menu = wx.Menu()
1994
1995 menu_item = save_menu.Append(-1, _('&All rows'))
1996 self.Bind(wx.EVT_MENU, self._all_rows2file, menu_item)
1997 menu_item = save_menu.Append(-1, _('All rows as &CSV'))
1998 self.Bind(wx.EVT_MENU, self._all_rows2csv, menu_item)
1999 menu_item = save_menu.Append(-1, _('&Tooltips of all rows'))
2000 self.Bind(wx.EVT_MENU, self._all_row_tooltips2file, menu_item)
2001 menu_item = save_menu.Append(-1, _('&Data of all rows'))
2002 self.Bind(wx.EVT_MENU, self._all_row_data2file, menu_item)
2003
2004 if no_of_selected_items > 1:
2005 save_menu.AppendSeparator()
2006 menu_item = save_menu.Append(-1, _('&Selected rows'))
2007 self.Bind(wx.EVT_MENU, self._selected_rows2file, menu_item)
2008 menu_item = save_menu.Append(-1, _('&Selected rows as CSV'))
2009 self.Bind(wx.EVT_MENU, self._selected_rows2csv, menu_item)
2010 menu_item = save_menu.Append(-1, _('&Tooltips of selected rows'))
2011 self.Bind(wx.EVT_MENU, self._selected_row_tooltips2file, menu_item)
2012 menu_item = save_menu.Append(-1, _('&Data of selected rows'))
2013 self.Bind(wx.EVT_MENU, self._selected_row_data2file, menu_item)
2014
2015
2016 clip_menu = wx.Menu()
2017
2018 if no_of_selected_items > 1:
2019
2020 menu_item = clip_menu.Append(-1, _('Tooltips of selected rows'))
2021 self.Bind(wx.EVT_MENU, self._tooltips2clipboard, menu_item)
2022
2023 menu_item = clip_menu.Append(-1, _('Data (formatted as text) of selected rows'))
2024 self.Bind(wx.EVT_MENU, self._datas2clipboard, menu_item)
2025
2026 menu_item = clip_menu.Append(-1, _('Content (as one line each) of selected rows'))
2027 self.Bind(wx.EVT_MENU, self._rows2clipboard, menu_item)
2028 clip_menu.AppendSeparator()
2029
2030
2031 menu_item = clip_menu.Append(-1, _('Tooltip of current row'))
2032 self.Bind(wx.EVT_MENU, self._tooltip2clipboard, menu_item)
2033
2034 if hasattr(self._rclicked_row_data, 'format'):
2035 menu_item = clip_menu.Append(-1, _('Data (formatted as text) of current row'))
2036 self.Bind(wx.EVT_MENU, self._data2clipboard, menu_item)
2037
2038 menu_item = clip_menu.Append(-1, _('Content (as one line) of current row'))
2039 self.Bind(wx.EVT_MENU, self._row2clipboard, menu_item)
2040
2041 menu_item = clip_menu.Append(-1, _('Content (one line per column) of current row'))
2042 self.Bind(wx.EVT_MENU, self._row_list2clipboard, menu_item)
2043
2044 clip_menu.AppendSeparator()
2045 for col_idx in range(self.ColumnCount):
2046 col_content = self._rclicked_row_cells[col_idx].strip()
2047
2048 if col_content == '':
2049 continue
2050 col_header = col_headers[col_idx]
2051 if col_header == '':
2052
2053
2054
2055
2056
2057
2058 menu_item = clip_menu.Append(-1, _('Column &%s (current row): "%s" [#%s]') % (col_idx+1, shorten_text(col_content, 35), col_idx))
2059 self.Bind(wx.EVT_MENU, self._col2clipboard, menu_item)
2060 else:
2061 col_menu = wx.Menu()
2062
2063 menu_item = col_menu.Append(-1, '"%s: %s" [#%s]' % (shorten_text(col_header, 8), shorten_text(col_content, 35), col_idx))
2064 self.Bind(wx.EVT_MENU, self._col_w_hdr2clipboard, menu_item)
2065
2066 menu_item = col_menu.Append(-1, '"%s" [#%s]' % (shorten_text(col_content, 35), col_idx))
2067 self.Bind(wx.EVT_MENU, self._col2clipboard, menu_item)
2068 clip_menu.Append(-1, _('Column &%s (current row): %s') % (col_idx+1, col_header), col_menu)
2069
2070
2071 clip_add_menu = wx.Menu()
2072
2073 if no_of_selected_items > 1:
2074
2075 menu_item = clip_add_menu.Append(-1, _('Tooltips of selected rows'))
2076 self.Bind(wx.EVT_MENU, self._add_tooltips2clipboard, menu_item)
2077
2078 menu_item = clip_add_menu.Append(-1, _('Data (formatted as text) of selected rows'))
2079 self.Bind(wx.EVT_MENU, self._add_datas2clipboard, menu_item)
2080
2081 menu_item = clip_add_menu.Append(-1, _('Content (as one line each) of selected rows'))
2082 self.Bind(wx.EVT_MENU, self._add_rows2clipboard, menu_item)
2083 clip_add_menu.AppendSeparator()
2084
2085
2086 menu_item = clip_add_menu.Append(-1, _('Tooltip of current row'))
2087 self.Bind(wx.EVT_MENU, self._add_tooltip2clipboard, menu_item)
2088
2089 if hasattr(self._rclicked_row_data, 'format'):
2090 menu_item = clip_add_menu.Append(-1, _('Data (formatted as text) of current row'))
2091 self.Bind(wx.EVT_MENU, self._add_data2clipboard, menu_item)
2092
2093 menu_item = clip_add_menu.Append(-1, _('Content (as one line) of current row'))
2094 self.Bind(wx.EVT_MENU, self._add_row2clipboard, menu_item)
2095
2096 menu_item = clip_add_menu.Append(-1, _('Content (one line per column) of current row'))
2097 self.Bind(wx.EVT_MENU, self._add_row_list2clipboard, menu_item)
2098
2099 clip_add_menu.AppendSeparator()
2100 for col_idx in range(self.ColumnCount):
2101 col_content = self._rclicked_row_cells[col_idx].strip()
2102
2103 if col_content == '':
2104 continue
2105 col_header = col_headers[col_idx]
2106 if col_header == '':
2107
2108 menu_item = clip_add_menu.Append(-1, _('Column &%s (current row): "%s" [#%s]') % (col_idx+1, shorten_text(col_content, 35), col_idx))
2109 self.Bind(wx.EVT_MENU, self._add_col2clipboard, menu_item)
2110 else:
2111 col_add_menu = wx.Menu()
2112
2113 menu_item = col_add_menu.Append(-1, '"%s: %s" [#%s]' % (shorten_text(col_header, 8), shorten_text(col_content, 35), col_idx))
2114 self.Bind(wx.EVT_MENU, self._add_col_w_hdr2clipboard, menu_item)
2115
2116 menu_item = col_add_menu.Append(-1, '"%s" [#%s]' % (shorten_text(col_content, 35), col_idx))
2117 self.Bind(wx.EVT_MENU, self._add_col2clipboard, menu_item)
2118 clip_add_menu.Append(-1, _('Column &%s (current row): %s') % (col_idx+1, col_header), col_add_menu)
2119
2120
2121 screenshot_menu = wx.Menu()
2122
2123 menu_item = screenshot_menu.Append(-1, _('&Save'))
2124 self.Bind(wx.EVT_MENU, self._visible_rows_screenshot2file, menu_item)
2125
2126 menu_item = screenshot_menu.Append(-1, _('E&xport area'))
2127 self.Bind(wx.EVT_MENU, self._visible_rows_screenshot2export_area, menu_item)
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145 self._context_menu.Append(-1, _('Screen&shot ...'), screenshot_menu)
2146 self._context_menu.Append(-1, _('Save to &file...'), save_menu)
2147 self._context_menu.Append(-1, _('Copy to &clipboard...'), clip_menu)
2148 self._context_menu.Append(-1, _('Append (&+) to clipboard...'), clip_add_menu)
2149
2150 if self.__extend_popup_menu_callback is not None:
2151 self._context_menu.AppendSeparator()
2152 self.__extend_popup_menu_callback(menu = self._context_menu)
2153
2154
2155 self.PopupMenu(self._context_menu, wx.DefaultPosition)
2156 self._context_menu.Destroy()
2157 return
2158
2159
2161 if self.__delete_callback is None:
2162 return
2163
2164 no_items = len(self.get_selected_items(only_one = False))
2165 if no_items == 0:
2166 return
2167
2168 if no_items > 1:
2169 question = _(
2170 '%s list items are selected.\n'
2171 '\n'
2172 'Really delete all %s items ?'
2173 ) % (no_items, no_items)
2174 title = _('Deleting list items')
2175 style = wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION | wx.STAY_ON_TOP
2176 dlg = wx.MessageDialog(None, question, title, style)
2177 btn_pressed = dlg.ShowModal()
2178 dlg.DestroyLater()
2179 if btn_pressed == wx.ID_NO:
2180 self.SetFocus()
2181 return
2182 if btn_pressed == wx.ID_CANCEL:
2183 self.SetFocus()
2184 return
2185
2186 self.__delete_callback()
2187 return
2188
2189
2191 if self.__new_callback is None:
2192 return
2193 self.__new_callback()
2194
2195
2197 if self.__edit_callback is None:
2198 return
2199 self.__edit_callback()
2200
2201
2203
2204 if self.__search_term is None:
2205
2206 default = ''
2207 else:
2208
2209 default = self.__search_term
2210 search_term = wx.GetTextFromUser (
2211 _('Enter the search term:'),
2212 _('List search'),
2213 default_value = default
2214 )
2215 if search_term.strip() == '':
2216
2217 self.__search_term = None
2218 self.__tt_static_part = self.__tt_static_part_base
2219 return
2220
2221
2222 self.__search_term = search_term
2223 self.__tt_static_part = _(
2224 'Current search term: [[%s]]\n'
2225 '\n'
2226 '%s'
2227 ) % (
2228 search_term,
2229 self.__tt_static_part_base
2230 )
2231 self.__search_match()
2232
2233
2234
2235
2237 event.Skip()
2238 if self.__activate_callback is not None:
2239 self.__activate_callback(event)
2240 return
2241
2242 self.__handle_edit()
2243
2244
2246 if self.__select_callback is not None:
2247 self.__select_callback(event)
2248 else:
2249 event.Skip()
2250
2251
2253 if self.__deselect_callback is not None:
2254 self.__deselect_callback(event)
2255 else:
2256 event.Skip()
2257
2258
2260 event.Skip()
2261 self.__show_context_menu(event.Index)
2262
2263
2265 evt.Skip()
2266
2267 if evt.KeyCode == wx.WXK_DELETE:
2268 self.__handle_delete()
2269 return
2270
2271 if evt.KeyCode == wx.WXK_INSERT:
2272 self.__handle_insert()
2273 return
2274
2275 if evt.KeyCode == wx.WXK_MENU:
2276 self.__show_context_menu(evt.Index)
2277 return
2278
2279
2281
2282 if chr(evt.GetRawKeyCode()) == 'f':
2283 if evt.GetModifiers() == wx.MOD_CMD:
2284
2285 self.__show_search_dialog()
2286 return
2287
2288 if chr(evt.GetRawKeyCode()) == 'n':
2289 if evt.GetModifiers() == wx.MOD_CMD:
2290
2291 self.__search_match()
2292 return
2293
2294 evt.Skip()
2295 return
2296
2297
2299 """Update tooltip on mouse motion.
2300
2301 for s in dir(wx):
2302 if s.startswith('LIST_HITTEST'):
2303 print s, getattr(wx, s)
2304
2305 LIST_HITTEST_ABOVE 1
2306 LIST_HITTEST_BELOW 2
2307 LIST_HITTEST_NOWHERE 4
2308 LIST_HITTEST_ONITEM 672
2309 LIST_HITTEST_ONITEMICON 32
2310 LIST_HITTEST_ONITEMLABEL 128
2311 LIST_HITTEST_ONITEMRIGHT 256
2312 LIST_HITTEST_ONITEMSTATEICON 512
2313 LIST_HITTEST_TOLEFT 1024
2314 LIST_HITTEST_TORIGHT 2048
2315 """
2316 item_idx, where_flag = self.HitTest(wx.Point(event.X, event.Y))
2317
2318
2319 if where_flag not in [
2320 wx.LIST_HITTEST_ONITEMLABEL,
2321 wx.LIST_HITTEST_ONITEMICON,
2322 wx.LIST_HITTEST_ONITEMSTATEICON,
2323 wx.LIST_HITTEST_ONITEMRIGHT,
2324 wx.LIST_HITTEST_ONITEM
2325 ]:
2326 self.__tt_last_item = None
2327 self.SetToolTip(self.__tt_static_part)
2328 return
2329
2330
2331 if self.__tt_last_item == item_idx:
2332 return
2333
2334
2335 self.__tt_last_item = item_idx
2336
2337
2338
2339 if item_idx == wx.NOT_FOUND:
2340 self.SetToolTip(self.__tt_static_part)
2341 return
2342
2343
2344 if self.__data is None:
2345 self.SetToolTip(self.__tt_static_part)
2346 return
2347
2348
2349
2350
2351
2352 if (
2353 (item_idx > (len(self.__data) - 1))
2354 or
2355 (item_idx < -1)
2356 ):
2357 self.SetToolTip(self.__tt_static_part)
2358 print("*************************************************************")
2359 print("GNUmed has detected an inconsistency with list item tooltips.")
2360 print("")
2361 print("This is not a big problem and you can keep working.")
2362 print("")
2363 print("However, please send us the following so we can fix GNUmed:")
2364 print("")
2365 print("item idx: %s" % item_idx)
2366 print('where flag: %s' % where_flag)
2367 print('data list length: %s' % len(self.__data))
2368 print("*************************************************************")
2369 return
2370
2371 dyna_tt = None
2372 if self.__item_tooltip_callback is not None:
2373 dyna_tt = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(item_idx)])
2374
2375 if dyna_tt is None:
2376 self.SetToolTip(self.__tt_static_part)
2377 return
2378
2379 self.SetToolTip(dyna_tt)
2380
2381
2382
2383
2385 evt.Skip()
2386 self.__handle_insert()
2387
2388
2390 evt.Skip()
2391 self.__handle_edit()
2392
2393
2395 evt.Skip()
2396 self.__handle_delete()
2397
2398
2400 evt.Skip()
2401 self.__show_search_dialog()
2402
2403
2405 evt.Skip()
2406 self.__search_match()
2407
2408
2410
2411 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-all_rows-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2412 txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
2413
2414 col_labels = self.column_labels
2415 line = '%s' % ' || '.join(col_labels)
2416 txt_file.write('%s\n' % line)
2417 txt_file.write(('=' * len(line)) + '\n')
2418
2419 for item_idx in range(self.ItemCount):
2420 fields = []
2421 for col_idx in range(self.ColumnCount):
2422 fields.append(self.GetItem(item_idx, col_idx).Text)
2423 txt_file.write('%s\n' % ' || '.join(fields))
2424
2425 txt_file.close()
2426 gmDispatcher.send(signal = 'statustext', msg = _('All rows saved to [%s].') % txt_name)
2427
2428
2430
2431 csv_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-all_rows-%s.csv' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2432 csv_file = io.open(csv_name, mode = 'wt', encoding = 'utf8')
2433 csv_writer = csv.writer(csv_file)
2434 csv_writer.writerow([ l for l in self.column_labels ])
2435 for item_idx in range(self.ItemCount):
2436 fields = []
2437 for col_idx in range(self.ColumnCount):
2438 fields.append(self.GetItem(item_idx, col_idx).Text)
2439 csv_writer.writerow([ f for f in fields ])
2440 csv_file.close()
2441 gmDispatcher.send(signal = 'statustext', msg = _('All rows saved to [%s].') % csv_name)
2442
2443
2460
2461
2463
2464 if self.__data is None:
2465 return
2466
2467 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-list_data-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2468 txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
2469
2470 for data in self.data:
2471 if hasattr(data, 'format'):
2472 txt = data.format()
2473 if type(txt) is list:
2474 txt = '\n'.join(txt)
2475 else:
2476 txt = '%s' % data
2477 txt_file.write('%s\n\n' % txt)
2478
2479 txt_file.close()
2480 gmDispatcher.send(signal = 'statustext', msg = _('All data saved to [%s].') % txt_name)
2481
2482
2484
2485 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-some_rows-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2486 txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
2487
2488 col_labels = self.column_labels
2489 line = '%s' % ' || '.join(col_labels)
2490 txt_file.write('%s\n' % line)
2491 txt_file.write(('=' * len(line)) + '\n')
2492
2493 items = self.selected_items
2494 if self.__is_single_selection:
2495 items = [items]
2496
2497 for item_idx in items:
2498 fields = []
2499 for col_idx in range(self.ColumnCount):
2500 fields.append(self.GetItem(item_idx, col_idx).Text)
2501 txt_file.write('%s\n' % ' || '.join(fields))
2502
2503 txt_file.close()
2504 gmDispatcher.send(signal = 'statustext', msg = _('Selected rows saved to [%s].') % txt_name)
2505
2506
2508
2509 csv_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-some_rows-%s.csv' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2510 csv_file = io.open(csv_name, mode = 'wt', encoding = 'utf8')
2511 csv_writer = csv.writer(csv_file)
2512 csv_writer.writerow([ l for l in self.column_labels ])
2513 items = self.selected_items
2514 if self.__is_single_selection:
2515 items = [items]
2516 for item_idx in items:
2517 fields = []
2518 for col_idx in range(self.ColumnCount):
2519 fields.append(self.GetItem(item_idx, col_idx).Text)
2520 csv_writer.writerow([ f for f in fields ])
2521 csv_file.close()
2522 gmDispatcher.send(signal = 'statustext', msg = _('Selected rows saved to [%s].') % csv_name)
2523
2524
2541
2542
2544
2545 if self.__data is None:
2546 return
2547
2548 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-list_data-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2549 txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
2550
2551 for data in self.selected_item_data:
2552 if hasattr(data, 'format'):
2553 txt = data.format()
2554 if type(txt) is list:
2555 txt = '\n'.join(txt)
2556 else:
2557 txt = '%s' % data
2558 txt_file.write('%s\n\n' % txt)
2559
2560 txt_file.close()
2561 gmDispatcher.send(signal = 'statustext', msg = _('Selected data saved to [%s].') % txt_name)
2562
2563
2577
2578
2593
2594
2614
2615
2641
2642
2672
2673
2706
2707
2709 if wx.TheClipboard.IsOpened():
2710 _log.debug('clipboard already open')
2711 return
2712 if not wx.TheClipboard.Open():
2713 _log.debug('cannot open clipboard')
2714 return
2715 data_obj = wx.TextDataObject()
2716 data_obj.SetText(' // '.join(self._rclicked_row_cells))
2717 wx.TheClipboard.SetData(data_obj)
2718 wx.TheClipboard.Close()
2719
2720
2722 if wx.TheClipboard.IsOpened():
2723 _log.debug('clipboard already open')
2724 return
2725 if not wx.TheClipboard.Open():
2726 _log.debug('cannot open clipboard')
2727 return
2728
2729 rows = []
2730 for item_idx in self.selected_items:
2731 rows.append(' // '.join([ self.GetItem(item_idx, col_idx).Text.strip() for col_idx in range(self.ColumnCount) ]))
2732
2733 data_obj = wx.TextDataObject()
2734 data_obj.SetText('\n\n'.join(rows))
2735 wx.TheClipboard.SetData(data_obj)
2736 wx.TheClipboard.Close()
2737
2738
2740 if wx.TheClipboard.IsOpened():
2741 _log.debug('clipboard already open')
2742 return
2743 if not wx.TheClipboard.Open():
2744 _log.debug('cannot open clipboard')
2745 return
2746 data_obj = wx.TextDataObject()
2747
2748 txt = ''
2749
2750 got_it = wx.TheClipboard.GetData(data_obj)
2751 if got_it:
2752 txt = data_obj.Text + '\n'
2753
2754
2755 txt += ' // '.join(self._rclicked_row_cells)
2756
2757
2758 data_obj.SetText(txt)
2759 wx.TheClipboard.SetData(data_obj)
2760 wx.TheClipboard.Close()
2761
2762
2764 if wx.TheClipboard.IsOpened():
2765 _log.debug('clipboard already open')
2766 return
2767 if not wx.TheClipboard.Open():
2768 _log.debug('cannot open clipboard')
2769 return
2770
2771 rows = []
2772 for item_idx in self.selected_items:
2773 rows.append(' // '.join([ self.GetItem(item_idx, col_idx).Text.strip() for col_idx in range(self.ColumnCount) ]))
2774
2775 data_obj = wx.TextDataObject()
2776
2777 txt = ''
2778
2779 got_it = wx.TheClipboard.GetData(data_obj)
2780 if got_it:
2781 txt = data_obj.Text + '\n'
2782 txt += '\n\n'.join(rows)
2783
2784 data_obj.SetText(txt)
2785 wx.TheClipboard.SetData(data_obj)
2786 wx.TheClipboard.Close()
2787
2788
2790 if wx.TheClipboard.IsOpened():
2791 _log.debug('clipboard already open')
2792 return
2793 if not wx.TheClipboard.Open():
2794 _log.debug('cannot open clipboard')
2795 return
2796 data_obj = wx.TextDataObject()
2797 data_obj.SetText('\n'.join(self._rclicked_row_cells_w_hdr))
2798 wx.TheClipboard.SetData(data_obj)
2799 wx.TheClipboard.Close()
2800
2801
2803 if wx.TheClipboard.IsOpened():
2804 _log.debug('clipboard already open')
2805 return
2806 if not wx.TheClipboard.Open():
2807 _log.debug('cannot open clipboard')
2808 return
2809 data_obj = wx.TextDataObject()
2810
2811 txt = ''
2812
2813 got_it = wx.TheClipboard.GetData(data_obj)
2814 if got_it:
2815 txt = data_obj.Text + '\n'
2816
2817
2818 txt += '\n'.join(self._rclicked_row_cells_w_hdr)
2819
2820
2821 data_obj.SetText(txt)
2822 wx.TheClipboard.SetData(data_obj)
2823 wx.TheClipboard.Close()
2824
2825
2827 if wx.TheClipboard.IsOpened():
2828 _log.debug('clipboard already open')
2829 return
2830 if not wx.TheClipboard.Open():
2831 _log.debug('cannot open clipboard')
2832 return
2833 data_obj = wx.TextDataObject()
2834 txt = self._rclicked_row_data.format()
2835 if type(txt) == type([]):
2836 txt = '\n'.join(txt)
2837 data_obj.SetText(txt)
2838 wx.TheClipboard.SetData(data_obj)
2839 wx.TheClipboard.Close()
2840
2841
2843 if wx.TheClipboard.IsOpened():
2844 _log.debug('clipboard already open')
2845 return
2846 if not wx.TheClipboard.Open():
2847 _log.debug('cannot open clipboard')
2848 return
2849
2850 data_as_txt = []
2851 for data in self.selected_item_data:
2852 if hasattr(data, 'format'):
2853 txt = data.format()
2854 if type(txt) is list:
2855 txt = '\n'.join(txt)
2856 else:
2857 txt = '%s' % data
2858 data_as_txt.append(txt)
2859
2860 data_obj = wx.TextDataObject()
2861 data_obj.SetText('\n\n'.join(data_as_txt))
2862 wx.TheClipboard.SetData(data_obj)
2863 wx.TheClipboard.Close()
2864
2865
2867 if wx.TheClipboard.IsOpened():
2868 _log.debug('clipboard already open')
2869 return
2870 if not wx.TheClipboard.Open():
2871 _log.debug('cannot open clipboard')
2872 return
2873 data_obj = wx.TextDataObject()
2874
2875 txt = ''
2876
2877 got_it = wx.TheClipboard.GetData(data_obj)
2878 if got_it:
2879 txt = data_obj.Text + '\n'
2880
2881
2882 tmp = self._rclicked_row_data.format()
2883 if type(tmp) == type([]):
2884 txt += '\n'.join(tmp)
2885 else:
2886 txt += tmp
2887
2888
2889 data_obj.SetText(txt)
2890 wx.TheClipboard.SetData(data_obj)
2891 wx.TheClipboard.Close()
2892
2893
2895 if wx.TheClipboard.IsOpened():
2896 _log.debug('clipboard already open')
2897 return
2898 if not wx.TheClipboard.Open():
2899 _log.debug('cannot open clipboard')
2900 return
2901
2902 data_as_txt = []
2903 for data in self.selected_item_data:
2904 if hasattr(data, 'format'):
2905 txt = data.format()
2906 if type(txt) is list:
2907 txt = '\n'.join(txt)
2908 else:
2909 txt = '%s' % data
2910 data_as_txt.append(txt)
2911
2912 data_obj = wx.TextDataObject()
2913 txt = ''
2914
2915 got_it = wx.TheClipboard.GetData(data_obj)
2916 if got_it:
2917 txt = data_obj.Text + '\n'
2918 txt += '\n'.join(data_as_txt)
2919
2920
2921 data_obj.SetText(txt)
2922 wx.TheClipboard.SetData(data_obj)
2923 wx.TheClipboard.Close()
2924
2925
2927 if wx.TheClipboard.IsOpened():
2928 _log.debug('clipboard already open')
2929 return
2930 if not wx.TheClipboard.Open():
2931 _log.debug('cannot open clipboard')
2932 return
2933 data_obj = wx.TextDataObject()
2934
2935
2936 col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.rsplit('#', 1)[1].rstrip(']'))
2937 txt = self._rclicked_row_cells[col_idx]
2938
2939 data_obj.SetText(txt)
2940 wx.TheClipboard.SetData(data_obj)
2941 wx.TheClipboard.Close()
2942
2943
2945 if wx.TheClipboard.IsOpened():
2946 _log.debug('clipboard already open')
2947 return
2948 if not wx.TheClipboard.Open():
2949 _log.debug('cannot open clipboard')
2950 return
2951 data_obj = wx.TextDataObject()
2952
2953 txt = ''
2954
2955 got_it = wx.TheClipboard.GetData(data_obj)
2956 if got_it:
2957 txt = data_obj.Text + '\n'
2958
2959
2960
2961 col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.rsplit('#', 1)[1].rstrip(']'))
2962 txt += self._rclicked_row_cells[col_idx]
2963
2964
2965 data_obj.SetText(txt)
2966 wx.TheClipboard.SetData(data_obj)
2967 wx.TheClipboard.Close()
2968
2969
2971 if wx.TheClipboard.IsOpened():
2972 _log.debug('clipboard already open')
2973 return
2974 if not wx.TheClipboard.Open():
2975 _log.debug('cannot open clipboard')
2976 return
2977 data_obj = wx.TextDataObject()
2978
2979
2980 col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.rsplit('#', 1)[1].rstrip(']'))
2981 txt = self._rclicked_row_cells_w_hdr[col_idx]
2982
2983 data_obj.SetText(txt)
2984 wx.TheClipboard.SetData(data_obj)
2985 wx.TheClipboard.Close()
2986
2987
2989 if wx.TheClipboard.IsOpened():
2990 _log.debug('clipboard already open')
2991 return
2992 if not wx.TheClipboard.Open():
2993 _log.debug('cannot open clipboard')
2994 return
2995 data_obj = wx.TextDataObject()
2996
2997 txt = ''
2998
2999 got_it = wx.TheClipboard.GetData(data_obj)
3000 if got_it:
3001 txt = data_obj.Text + '\n'
3002
3003
3004
3005 col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.rsplit('#', 1)[1].rstrip(']'))
3006 txt += self._rclicked_row_cells_w_hdr[col_idx]
3007
3008
3009 data_obj.SetText(txt)
3010 wx.TheClipboard.SetData(data_obj)
3011 wx.TheClipboard.Close()
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3026
3027 if self.__search_term is None:
3028
3029 return False
3030 if self.__search_term.strip() == '':
3031
3032 return False
3033 if self.__searchable_cols is None:
3034
3035 self.searchable_columns = None
3036 if len(self.__searchable_cols) == 0:
3037
3038 return False
3039
3040
3041 for row_idx in range(self.__next_line_to_search, self.ItemCount):
3042 for col_idx in range(self.ColumnCount):
3043 if col_idx not in self.__searchable_cols:
3044 continue
3045 col_val = self.GetItem(row_idx, col_idx).GetText()
3046 if regex.search(self.__search_term, col_val, regex.U | regex.I) is not None:
3047 self.Select(row_idx)
3048 self.EnsureVisible(row_idx)
3049 if row_idx == self.ItemCount - 1:
3050
3051 self.__next_line_to_search = 0
3052 else:
3053 self.__next_line_to_search = row_idx + 1
3054 return True
3055
3056 self.__next_line_to_search = 0
3057 return False
3058
3059
3061
3062
3063 if cols is None:
3064
3065 self.__searchable_cols = range(self.ColumnCount)
3066 return
3067
3068
3069 new_cols = {}
3070 for col in cols:
3071 if col < self.ColumnCount:
3072 new_cols[col] = True
3073 self.__searchable_cols = list(new_cols)
3074
3075 searchable_columns = property(lambda x:x, _set_searchable_cols)
3076
3077
3078
3079
3081 return self.__activate_callback
3082
3084 if callback is None:
3085 self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED)
3086 self.__activate_callback = None
3087 return
3088 if not callable(callback):
3089 raise ValueError('<activate> callback is not a callable: %s' % callback)
3090 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated)
3091 self.__activate_callback = callback
3092
3093 activate_callback = property(_get_activate_callback, _set_activate_callback)
3094
3095
3097 return self.__select_callback
3098
3100 if callback is None:
3101 self.Unbind(wx.EVT_LIST_ITEM_SELECTED)
3102 self.__select_callback = None
3103 return
3104 if not callable(callback):
3105 raise ValueError('<selected> callback is not a callable: %s' % callback)
3106 self.Bind(wx.EVT_LIST_ITEM_SELECTED, self._on_list_item_selected)
3107 self.__select_callback = callback
3108
3109 select_callback = property(_get_select_callback, _set_select_callback)
3110
3111
3113 return self.__deselect_callback
3114
3116 if callback is None:
3117 self.Unbind(wx.EVT_LIST_ITEM_DESELECTED)
3118 self.__deselect_callback = None
3119 return
3120 if not callable(callback):
3121 raise ValueError('<deselected> callback is not a callable: %s' % callback)
3122 self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self._on_list_item_deselected)
3123 self.__deselect_callback = callback
3124
3125 deselect_callback = property(_get_deselect_callback, _set_deselect_callback)
3126
3127
3129 return self.__delete_callback
3130
3132 if callback is None:
3133
3134 self.__delete_callback = None
3135 return
3136 if not callable(callback):
3137 raise ValueError('<delete> callback is not a callable: %s' % callback)
3138
3139 self.__delete_callback = callback
3140
3141 delete_callback = property(_get_delete_callback, _set_delete_callback)
3142
3143
3145 return self.__new_callback
3146
3148 if callback is None:
3149 self.__new_callback = None
3150 return
3151 if not callable(callback):
3152 raise ValueError('<new> callback is not a callable: %s' % callback)
3153 self.__new_callback = callback
3154
3155 new_callback = property(_get_new_callback, _set_new_callback)
3156
3157
3159 return self.__edit_callback
3160
3162 if callback is None:
3163 self.__edit_callback = None
3164 return
3165 if not callable(callback):
3166 raise ValueError('<edit> callback is not a callable: %s' % callback)
3167 self.__edit_callback = callback
3168
3169 edit_callback = property(_get_edit_callback, _set_edit_callback)
3170
3171
3177
3178
3179
3180
3181
3182 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
3183
3184
3186 if callback is not None:
3187 if not callable(callback):
3188 raise ValueError('<extend_popup_menu> callback is not a callable: %s' % callback)
3189 self.__extend_popup_menu_callback = callback
3190
3191 extend_popup_menu_callback = property(lambda x:x, _set_extend_popup_menu_callback)
3192
3193
3194
3195
3197 if self.itemDataMap is None:
3198 self._update_sorting_metadata()
3199 return self
3200
3201
3203 col_idx, is_ascending = self.GetSortState()
3204 if col_idx == -1:
3205 _log.debug('outside any column (idx: -1) clicked, ignoring')
3206 return
3207 self._remove_sorting_indicators_from_column_labels()
3208 col_state = self.GetColumn(col_idx)
3209 col_state.Text += self.sort_order_tags[is_ascending]
3210 self.SetColumn(col_idx, col_state)
3211
3212
3214 return (primary_item1_idx, primary_item2_idx)
3215
3216 if self.__secondary_sort_col is None:
3217 return (primary_item1_idx, primary_item2_idx)
3218 if self.__secondary_sort_col == primary_sort_col:
3219 return (primary_item1_idx, primary_item2_idx)
3220
3221 secondary_val1 = self.itemDataMap[primary_item1_idx][self.__secondary_sort_col]
3222 secondary_val2 = self.itemDataMap[primary_item2_idx][self.__secondary_sort_col]
3223
3224 if type(secondary_val1) == type('') and type(secondary_val2) == type(''):
3225 secondary_cmp_result = locale.strcoll(secondary_val1, secondary_val2)
3226 elif type(secondary_val1) == type('') or type(secondary_val2) == type(''):
3227 secondary_cmp_result = locale.strcoll(str(secondary_val1), str(secondary_val2))
3228 else:
3229 secondary_cmp_result = cmp(secondary_val1, secondary_val2)
3230
3231 if secondary_cmp_result == 0:
3232 return (primary_item1_idx, primary_item2_idx)
3233
3234
3235 currently_ascending = self._colSortFlag[primary_sort_col]
3236 if currently_ascending:
3237 secondary_val1, secondary_val2 = min(secondary_val1, secondary_val2), max(secondary_val1, secondary_val2)
3238 else:
3239 secondary_val1, secondary_val2 = max(secondary_val1, secondary_val2), min(secondary_val1, secondary_val2)
3240 return (secondary_val1, secondary_val2)
3241
3242
3244
3245
3246
3247 sort_col, is_ascending = self.GetSortState()
3248 data1 = self.itemDataMap[item1][sort_col]
3249 data2 = self.itemDataMap[item2][sort_col]
3250 if type(data1) == type('') and type(data2) == type(''):
3251 cmp_result = locale.strcoll(data1, data2)
3252 elif type(data1) == type('') or type(data2) == type(''):
3253 cmp_result = locale.strcoll(str(data1), str(data2))
3254 else:
3255 cmp_result = cmp(data1, data2)
3256
3257
3258 if not is_ascending:
3259 cmp_result = -1 * cmp_result
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273 return cmp_result
3274
3275
3276
3277
3278
3279
3280
3281
3282
3284 dict2sort = {}
3285 item_count = self.GetItemCount()
3286 if item_count == 0:
3287 return dict2sort
3288 col_count = self.GetColumnCount()
3289 for item_idx in range(item_count):
3290 dict2sort[item_idx] = ()
3291 if col_count == 0:
3292 continue
3293 for col_idx in range(col_count):
3294 dict2sort[item_idx] += (self.GetItem(item_idx, col_idx).GetText(), )
3295
3296
3297
3298 return dict2sort
3299
3300
3306
3307
3309 for col_idx in range(self.ColumnCount):
3310 self._remove_sorting_indicator_from_column_label(col_idx)
3311
3312
3314 assert (col_idx > -1), '<col_idx> must be non-negative integer'
3315 if col_idx > self.ColumnCount:
3316 _log.warning('<col_idx>=%s, but .ColumnCount=%s', col_idx, self.ColumnCount)
3317 return
3318
3319 col_state = self.GetColumn(col_idx)
3320 cleaned_header = self.__remove_sorting_indicator(col_state.Text)
3321 if col_state.Text == cleaned_header:
3322 return
3323
3324 col_state.Text = cleaned_header
3325 self.SetColumn(col_idx, col_state)
3326
3327
3332
3333
3337
3338
3344
3345
3346
3347
3348
3349
3350
3351
3352
3354 return self.__secondary_sort_col
3355
3357 if col is None:
3358 self.__secondary_sort_col = None
3359 return
3360 if col > self.GetColumnCount():
3361 raise ValueError('cannot secondary-sort on col [%s], there are only [%s] columns', col, self.GetColumnCount())
3362 self.__secondary_sort_col = col
3363
3364 secondary_sort_column = property(__get_secondary_sort_col, __set_secondary_sort_col)
3365
3366
3368 title = undecorate_window_title(gmTools.coalesce(self.container_title, '').rstrip())
3369 if title != '':
3370 return title
3371
3372 if self.ColumnCount == 0:
3373 return _('list')
3374
3375 col_labels = []
3376 for col_idx in range(self.ColumnCount):
3377 col_label = self.GetColumn(col_idx).Text.strip()
3378 if col_label != '':
3379 col_labels.append(col_label)
3380 return _('list') + '-[%s]' % ']_['.join(col_labels)
3381
3382 useful_title = property(__get_useful_title)
3383
3384
3386 if widget is None:
3387 widget = self
3388 if hasattr(widget, 'GetTitle'):
3389 title = widget.GetTitle().strip()
3390 if title != '':
3391 return title
3392
3393 parent = widget.GetParent()
3394 if parent is None:
3395 return None
3396
3397 return self.__get_container_title(widget = parent)
3398
3399 container_title = property(__get_container_title)
3400
3401
3403 if widget is None:
3404 widget = self
3405 if isinstance(widget, wx.Dialog):
3406 return widget
3407
3408 parent = widget.GetParent()
3409 if parent is None:
3410 return None
3411
3412 return self.__get_containing_dlg(widget = parent)
3413
3414 containing_dlg = property(__get_containing_dlg)
3415
3416
3417 -def shorten_text(text=None, max_length=None):
3418 if len(text) <= max_length:
3419 return text
3420 return text[:max_length-1] + '\u2026'
3421
3422
3423
3424
3425 if __name__ == '__main__':
3426
3427 if len(sys.argv) < 2:
3428 sys.exit()
3429
3430 if sys.argv[1] != 'test':
3431 sys.exit()
3432
3433 sys.path.insert(0, '../../')
3434
3435 from Gnumed.pycommon import gmI18N
3436 gmI18N.activate_locale()
3437 gmI18N.install_domain()
3438
3439
3441 app = wx.PyWidgetTester(size = (400, 500))
3442 dlg = wx.MultiChoiceDialog (
3443 parent = None,
3444 message = 'test message',
3445 caption = 'test caption',
3446 choices = ['a', 'b', 'c', 'd', 'e']
3447 )
3448 dlg.ShowModal()
3449 sels = dlg.GetSelections()
3450 print("selected:")
3451 for sel in sels:
3452 print(sel)
3453
3454
3456
3457 def edit(argument):
3458 print("editor called with:")
3459 print(argument)
3460
3461 def refresh(lctrl):
3462 choices = ['a', 'b', 'c']
3463 lctrl.set_string_items(choices)
3464
3465 app = wx.App()
3466 chosen = get_choices_from_list (
3467
3468 caption = 'select health issues',
3469
3470
3471 columns = ['issue'],
3472 refresh_callback = refresh,
3473 single_selection = False
3474
3475 )
3476 print("chosen:")
3477 print(chosen)
3478
3479
3481
3482 app = wx.App(size = (200, 50))
3483 dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:')
3484 dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy'])
3485
3486 dlg.set_string_items(['patient', 'emr', 'docs'])
3487 result = dlg.ShowModal()
3488 print(result)
3489 print(dlg.get_picks())
3490
3491
3492 test_get_choices_from_list()
3493
3494
3495
3496
3497