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