Package Gnumed :: Package wxpython :: Module gmListWidgets
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmListWidgets

   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  # FIXME: configurable callback on double-click action 
  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) # list ctrl will refresh anyway if possible 114 dlg.set_column_widths() 115 116 if data is not None: 117 dlg.set_data(data = data) # can override data set if refresh_callback is not None 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
139 -class cGenericListSelectorDlg(wxgGenericListSelectorDlg.wxgGenericListSelectorDlg):
140 """A dialog holding a list and a few buttons to act on the items.""" 141
142 - def __init__(self, *args, **kwargs):
143 144 try: 145 msg = kwargs['msg'] 146 del kwargs['msg'] 147 except KeyError: 148 msg = None 149 150 try: 151 title = kwargs['title'] 152 except KeyError: 153 title = self.__class__.__name__ 154 kwargs['title'] = decorate_window_title(title) 155 156 try: 157 single_selection = kwargs['single_selection'] 158 del kwargs['single_selection'] 159 except KeyError: 160 single_selection = False 161 162 wxgGenericListSelectorDlg.wxgGenericListSelectorDlg.__init__(self, *args, **kwargs) 163 164 self.message = msg 165 166 self.left_extra_button = None 167 self.middle_extra_button = None 168 self.right_extra_button = None 169 170 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled) 171 self.new_callback = None # called when NEW button pressed, no argument passed in 172 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in 173 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in 174 175 self.can_return_empty = False 176 self.ignore_OK_button = False # by default do show/use the OK button 177 178 self.select_callback = None # called when an item is selected, data of topmost selected item passed in 179 self._LCTRL_items.select_callback = self._on_list_item_selected_in_listctrl 180 if single_selection: 181 self._LCTRL_items.SetSingleStyle(wx.LC_SINGLE_SEL, add = True)
182 183 #------------------------------------------------------------
184 - def set_columns(self, columns=None):
185 self._LCTRL_items.set_columns(columns = columns)
186 187 #------------------------------------------------------------
188 - def set_column_widths(self, widths=None):
189 self._LCTRL_items.set_column_widths(widths = widths)
190 191 #------------------------------------------------------------
192 - def set_string_items(self, items=None, reshow=True):
193 self._LCTRL_items.set_string_items(items = items, reshow = reshow) 194 self._LCTRL_items.set_column_widths()
195 #self._LCTRL_items.Select(0) 196 197 #------------------------------------------------------------
198 - def set_selections(self, selections = None):
199 self._LCTRL_items.set_selections(selections = selections) 200 if selections is None: 201 return 202 if len(selections) == 0: 203 return 204 if self.ignore_OK_button: 205 return 206 self._BTN_ok.Enable(True) 207 self._BTN_ok.SetDefault()
208 209 #------------------------------------------------------------
210 - def set_data(self, data = None):
211 self._LCTRL_items.set_data(data = data)
212 213 #------------------------------------------------------------
214 - def get_selected_item_data(self, only_one=False):
215 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
216 217 #------------------------------------------------------------ 218 # event handlers 219 #------------------------------------------------------------
220 - def _on_list_item_deselected(self, event):
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 #------------------------------------------------------------
231 - def _on_new_button_pressed(self, event):
232 self.__do_insert() 233 event.Skip()
234 235 #------------------------------------------------------------
236 - def _on_edit_button_pressed(self, event):
237 # if the edit button *can* be pressed there are *supposed* 238 # to be both an item selected and an editor configured 239 self.__do_edit() 240 event.Skip()
241 242 #------------------------------------------------------------
243 - def _on_delete_button_pressed(self, event):
244 # if the delete button *can* be pressed there are *supposed* 245 # to be both an item selected and a deletor configured 246 247 no_items = len(self._LCTRL_items.get_selected_items(only_one = False)) 248 if no_items == 0: 249 return 250 251 if no_items > 1: 252 style = wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION | wx.STAY_ON_TOP 253 title = _('Deleting list items') 254 question = _( 255 'You have selected %s list items.\n' 256 '\n' 257 'Really delete all %s items ?' 258 ) % (no_items, no_items) 259 dlg = wx.MessageDialog(None, question, title, style) 260 btn_pressed = dlg.ShowModal() 261 dlg.DestroyLater() 262 if btn_pressed == wx.ID_NO: 263 self._LCTRL_items.SetFocus() 264 return 265 if btn_pressed == wx.ID_CANCEL: 266 self._LCTRL_items.SetFocus() 267 return 268 269 self.__do_delete() 270 event.Skip()
271 272 #------------------------------------------------------------
273 - def _on_left_extra_button_pressed(self, event):
274 if self.__left_extra_button_wants_list: 275 item_data = self._LCTRL_items 276 else: 277 item_data = self._LCTRL_items.get_selected_item_data(only_one=True) 278 if not self.__left_extra_button_callback(item_data): 279 self._LCTRL_items.SetFocus() 280 return 281 if self.__refresh_callback is None: 282 self._LCTRL_items.SetFocus() 283 return 284 wx.BeginBusyCursor() 285 try: 286 self.__refresh_callback(lctrl = self._LCTRL_items) 287 finally: 288 wx.EndBusyCursor() 289 self._LCTRL_items.set_column_widths() 290 self._LCTRL_items.SetFocus()
291 292 #------------------------------------------------------------
293 - def _on_middle_extra_button_pressed(self, event):
294 if self.__middle_extra_button_wants_list: 295 item_data = self._LCTRL_items 296 else: 297 item_data = self._LCTRL_items.get_selected_item_data(only_one=True) 298 if not self.__middle_extra_button_callback(item_data): 299 self._LCTRL_items.SetFocus() 300 return 301 if self.__refresh_callback is None: 302 self._LCTRL_items.SetFocus() 303 return 304 wx.BeginBusyCursor() 305 try: 306 self.__refresh_callback(lctrl = self._LCTRL_items) 307 finally: 308 wx.EndBusyCursor() 309 self._LCTRL_items.set_column_widths() 310 self._LCTRL_items.SetFocus()
311 312 #------------------------------------------------------------
313 - def _on_right_extra_button_pressed(self, event):
314 if self.__right_extra_button_wants_list: 315 item_data = self._LCTRL_items 316 else: 317 item_data = self._LCTRL_items.get_selected_item_data(only_one=True) 318 if not self.__right_extra_button_callback(item_data): 319 self._LCTRL_items.SetFocus() 320 return 321 if self.__refresh_callback is None: 322 self._LCTRL_items.SetFocus() 323 return 324 wx.BeginBusyCursor() 325 try: 326 self.__refresh_callback(lctrl = self._LCTRL_items) 327 finally: 328 wx.EndBusyCursor() 329 self._LCTRL_items.set_column_widths() 330 self._LCTRL_items.SetFocus()
331 332 #------------------------------------------------------------ 333 # internal helpers 334 #------------------------------------------------------------
335 - def _on_list_item_selected_in_listctrl(self, event):
336 event.Skip() 337 if not self.__ignore_OK_button: 338 self._BTN_ok.SetDefault() 339 self._BTN_ok.Enable(True) 340 if self.edit_callback is not None: 341 self._BTN_edit.Enable(True) 342 if self.delete_callback is not None: 343 self._BTN_delete.Enable(True) 344 if self.__select_callback is not None: 345 item = self._LCTRL_items.get_selected_item_data(only_one = True) 346 self.__select_callback(item)
347 348 #------------------------------------------------------------
350 self.__do_delete()
351 352 #------------------------------------------------------------
353 - def __do_delete(self):
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 #------------------------------------------------------------
378 self.__do_edit()
379 380 #------------------------------------------------------------
381 - def __do_edit(self):
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 #------------------------------------------------------------
398 self.__do_insert()
399 400 #------------------------------------------------------------
401 - def __do_insert(self):
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 # properties 418 #------------------------------------------------------------
419 - def _set_ignore_OK_button(self, ignored):
420 self.__ignore_OK_button = ignored 421 if self.__ignore_OK_button: 422 self._BTN_ok.Hide() 423 self._BTN_ok.Enable(False) 424 else: 425 self._BTN_ok.Show() 426 if self._LCTRL_items.get_selected_items(only_one=True) == -1: 427 if self.can_return_empty: 428 self._BTN_ok.Enable(True) 429 else: 430 self._BTN_ok.Enable(False) 431 self._BTN_cancel.SetDefault()
432 433 ignore_OK_button = property(lambda x:x, _set_ignore_OK_button) 434 435 #------------------------------------------------------------
436 - def _set_left_extra_button(self, definition):
437 if definition is None: 438 self._BTN_extra_left.Enable(False) 439 self._BTN_extra_left.Hide() 440 self.__left_extra_button_callback = None 441 self.__left_extra_button_wants_list = False 442 return 443 444 if len(definition) == 3: 445 (label, tooltip, callback) = definition 446 wants_list = False 447 else: 448 (label, tooltip, callback, wants_list) = definition 449 450 if not callable(callback): 451 raise ValueError('<left extra button> callback is not a callable: %s' % callback) 452 self.__left_extra_button_callback = callback 453 self.__left_extra_button_wants_list = wants_list 454 self._BTN_extra_left.SetLabel(label) 455 self._BTN_extra_left.SetToolTip(tooltip) 456 self._BTN_extra_left.Enable(True) 457 self._BTN_extra_left.Show()
458 459 left_extra_button = property(lambda x:x, _set_left_extra_button) 460 461 #------------------------------------------------------------
462 - def _set_middle_extra_button(self, definition):
463 if definition is None: 464 self._BTN_extra_middle.Enable(False) 465 self._BTN_extra_middle.Hide() 466 self.__middle_extra_button_callback = None 467 self.__middle_extra_button_wants_list = False 468 return 469 470 if len(definition) == 3: 471 (label, tooltip, callback) = definition 472 wants_list = False 473 else: 474 (label, tooltip, callback, wants_list) = definition 475 476 if not callable(callback): 477 raise ValueError('<middle extra button> callback is not a callable: %s' % callback) 478 self.__middle_extra_button_callback = callback 479 self.__middle_extra_button_wants_list = wants_list 480 self._BTN_extra_middle.SetLabel(label) 481 self._BTN_extra_middle.SetToolTip(tooltip) 482 self._BTN_extra_middle.Enable(True) 483 self._BTN_extra_middle.Show()
484 485 middle_extra_button = property(lambda x:x, _set_middle_extra_button) 486 487 #------------------------------------------------------------
488 - def _set_right_extra_button(self, definition):
489 if definition is None: 490 self._BTN_extra_right.Enable(False) 491 self._BTN_extra_right.Hide() 492 self.__right_extra_button_callback = None 493 self.__right_extra_button_wants_list = False 494 return 495 496 if len(definition) == 3: 497 (label, tooltip, callback) = definition 498 wants_list = False 499 else: 500 (label, tooltip, callback, wants_list) = definition 501 502 if not callable(callback): 503 raise ValueError('<right extra button> callback is not a callable: %s' % callback) 504 self.__right_extra_button_callback = callback 505 self.__right_extra_button_wants_list = wants_list 506 self._BTN_extra_right.SetLabel(label) 507 self._BTN_extra_right.SetToolTip(tooltip) 508 self._BTN_extra_right.Enable(True) 509 self._BTN_extra_right.Show()
510 511 right_extra_button = property(lambda x:x, _set_right_extra_button) 512 513 #------------------------------------------------------------
514 - def _get_new_callback(self):
515 return self.__new_callback
516
517 - def _set_new_callback(self, callback):
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 #------------------------------------------------------------
537 - def _get_edit_callback(self):
538 return self.__edit_callback
539
540 - def _set_edit_callback(self, callback):
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 #------------------------------------------------------------
558 - def _get_delete_callback(self):
559 return self.__delete_callback
560
561 - def _set_delete_callback(self, callback):
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 #------------------------------------------------------------
580 - def _get_refresh_callback(self):
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
591 - def _set_refresh_callback(self, callback):
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 #------------------------------------------------------------
602 - def _get_select_callback(self):
603 return self.__select_callback
604
605 - def _set_select_callback(self, callback):
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 #------------------------------------------------------------
614 - def _set_list_tooltip_callback(self, callback):
615 self._LCTRL_items.item_tooltip_callback = callback
616 617 list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback) 618 #def _get_tooltip(self, item): # inside a class 619 #def _get_tooltip(item): # outside a class 620 #------------------------------------------------------------
621 - def _set_message(self, message):
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
633 -class cGenericListManagerPnl(wxgGenericListManagerPnl.wxgGenericListManagerPnl):
634 """A panel holding a generic multi-column list and action buttions.""" 635
636 - def __init__(self, *args, **kwargs):
637 638 try: 639 msg = kwargs['msg'] 640 del kwargs['msg'] 641 except KeyError: msg = None 642 643 wxgGenericListManagerPnl.wxgGenericListManagerPnl.__init__(self, *args, **kwargs) 644 645 if msg is None: 646 self._LBL_message.Hide() 647 else: 648 self._LBL_message.SetLabel(msg) 649 650 self.left_extra_button = None 651 self.middle_extra_button = None 652 self.right_extra_button = None 653 654 # new/edit/delete must return True/False to enable refresh 655 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled) 656 self.new_callback = None # called when NEW button pressed, no argument passed in 657 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in 658 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in 659 660 self.select_callback = None # called when an item is selected, data of topmost selected item passed in 661 self._LCTRL_items.select_callback = self._on_list_item_selected_in_listctrl
662 663 #------------------------------------------------------------ 664 # external API 665 #------------------------------------------------------------
666 - def set_columns(self, columns=None):
667 self._LCTRL_items.set_columns(columns = columns)
668 669 #------------------------------------------------------------
670 - def set_string_items(self, items=None, reshow=True):
671 self._LCTRL_items.set_string_items(items = items, reshow = reshow) 672 self._LCTRL_items.set_column_widths() 673 674 if (items is None) or (len(items) == 0): 675 self._BTN_edit.Enable(False) 676 self._BTN_remove.Enable(False)
677 #else: 678 # self._LCTRL_items.Select(0) 679 680 #------------------------------------------------------------
681 - def set_selections(self, selections = None):
682 self._LCTRL_items.set_selections(selections = selections)
683 684 #------------------------------------------------------------
685 - def set_data(self, data = None):
686 self._LCTRL_items.set_data(data = data)
687 688 #------------------------------------------------------------
689 - def get_selected_item_data(self, only_one=False):
690 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
691 692 #------------------------------------------------------------ 693 # internal helpers 694 #------------------------------------------------------------
695 - def _on_list_item_selected_in_listctrl(self, event):
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 #------------------------------------------------------------
707 self.__do_delete()
708 709 #------------------------------------------------------------
710 - def __do_delete(self):
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 #------------------------------------------------------------
726 self.__do_edit()
727 728 #------------------------------------------------------------
729 - def __do_edit(self):
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 #------------------------------------------------------------
746 self.__do_insert()
747 748 #------------------------------------------------------------
749 - def __do_insert(self):
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 # event handlers 766 #------------------------------------------------------------
767 - def _on_list_item_deselected(self, event):
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 #------------------------------------------------------------
776 - def _on_list_item_activated(self, event):
777 event.Skip() 778 if self.__edit_callback is None: 779 return 780 self._on_edit_button_pressed(event)
781 782 #------------------------------------------------------------
783 - def _on_add_button_pressed(self, event):
784 if not self.__new_callback(): 785 return 786 if self.__refresh_callback is None: 787 return 788 wx.BeginBusyCursor() 789 try: 790 self.__refresh_callback(lctrl = self._LCTRL_items) 791 finally: 792 wx.EndBusyCursor()
793 794 #------------------------------------------------------------
795 - def _on_edit_button_pressed(self, event):
796 item = self._LCTRL_items.get_selected_item_data(only_one = True) 797 if item is None: 798 return 799 if not self.__edit_callback(item): 800 return 801 if self.__refresh_callback is None: 802 return 803 wx.BeginBusyCursor() 804 try: 805 self.__refresh_callback(lctrl = self._LCTRL_items) 806 finally: 807 wx.EndBusyCursor()
808 809 #------------------------------------------------------------
810 - def _on_remove_button_pressed(self, event):
811 if self._LCTRL_items.get_selected_items(only_one = True) is None: 812 return 813 self.__do_delete()
814 815 #------------------------------------------------------------
816 - def _on_left_extra_button_pressed(self, event):
817 item_data = self._LCTRL_items.get_selected_item_data(only_one = True) 818 if not self.__left_extra_button_callback(item_data): 819 self._LCTRL_items.SetFocus() 820 return 821 if self.__refresh_callback is None: 822 self._LCTRL_items.SetFocus() 823 return 824 wx.BeginBusyCursor() 825 try: 826 self.__refresh_callback(lctrl = self._LCTRL_items) 827 finally: 828 self._LCTRL_items.set_column_widths() 829 self._LCTRL_items.SetFocus() 830 wx.EndBusyCursor()
831 832 #------------------------------------------------------------
833 - def _on_middle_extra_button_pressed(self, event):
834 item_data = self._LCTRL_items.get_selected_item_data(only_one = True) 835 if not self.__middle_extra_button_callback(item_data): 836 self._LCTRL_items.SetFocus() 837 return 838 if self.__refresh_callback is None: 839 self._LCTRL_items.SetFocus() 840 return 841 wx.BeginBusyCursor() 842 try: 843 self.__refresh_callback(lctrl = self._LCTRL_items) 844 finally: 845 self._LCTRL_items.set_column_widths() 846 self._LCTRL_items.SetFocus() 847 wx.EndBusyCursor()
848 849 #------------------------------------------------------------
850 - def _on_right_extra_button_pressed(self, event):
851 item_data = self._LCTRL_items.get_selected_item_data(only_one=True) 852 if not self.__right_extra_button_callback(item_data): 853 self._LCTRL_items.SetFocus() 854 return 855 if self.__refresh_callback is None: 856 self._LCTRL_items.SetFocus() 857 return 858 wx.BeginBusyCursor() 859 try: 860 self.__refresh_callback(lctrl = self._LCTRL_items) 861 finally: 862 wx.EndBusyCursor() 863 self._LCTRL_items.set_column_widths() 864 self._LCTRL_items.SetFocus()
865 866 #------------------------------------------------------------ 867 # properties 868 #------------------------------------------------------------
869 - def _get_new_callback(self):
870 return self.__new_callback
871
872 - def _set_new_callback(self, callback):
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 #------------------------------------------------------------
892 - def _get_edit_callback(self):
893 return self.__edit_callback
894
895 - def _set_edit_callback(self, callback):
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 #------------------------------------------------------------
913 - def _get_delete_callback(self):
914 return self.__delete_callback
915
916 - def _set_delete_callback(self, callback):
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 #------------------------------------------------------------
935 - def _get_refresh_callback(self):
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
946 - def _set_refresh_callback(self, callback):
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 #------------------------------------------------------------
957 - def _get_select_callback(self):
958 return self.__select_callback
959
960 - def _set_select_callback(self, callback):
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 #------------------------------------------------------------
969 - def _get_message(self):
970 return self._LBL_message.GetLabel()
971
972 - def _set_message(self, msg):
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 #------------------------------------------------------------
984 - def _set_left_extra_button(self, definition):
985 if definition is None: 986 self._BTN_extra_left.Enable(False) 987 self._BTN_extra_left.Hide() 988 self.__left_extra_button_callback = None 989 return 990 991 (label, tooltip, callback) = definition 992 if not callable(callback): 993 raise ValueError('<left extra button> callback is not a callable: %s' % callback) 994 self.__left_extra_button_callback = callback 995 self._BTN_extra_left.SetLabel(label) 996 self._BTN_extra_left.SetToolTip(tooltip) 997 self._BTN_extra_left.Enable(True) 998 self._BTN_extra_left.Show()
999 1000 left_extra_button = property(lambda x:x, _set_left_extra_button) 1001 1002 #------------------------------------------------------------
1003 - def _set_middle_extra_button(self, definition):
1004 if definition is None: 1005 self._BTN_extra_middle.Enable(False) 1006 self._BTN_extra_middle.Hide() 1007 self.__middle_extra_button_callback = None 1008 return 1009 1010 (label, tooltip, callback) = definition 1011 if not callable(callback): 1012 raise ValueError('<middle extra button> callback is not a callable: %s' % callback) 1013 self.__middle_extra_button_callback = callback 1014 self._BTN_extra_middle.SetLabel(label) 1015 self._BTN_extra_middle.SetToolTip(tooltip) 1016 self._BTN_extra_middle.Enable(True) 1017 self._BTN_extra_middle.Show()
1018 1019 middle_extra_button = property(lambda x:x, _set_middle_extra_button) 1020 1021 #------------------------------------------------------------
1022 - def _set_right_extra_button(self, definition):
1023 if definition is None: 1024 self._BTN_extra_right.Enable(False) 1025 self._BTN_extra_right.Hide() 1026 self.__right_extra_button_callback = None 1027 return 1028 1029 (label, tooltip, callback) = definition 1030 if not callable(callback): 1031 raise ValueError('<right extra button> callback is not a callable: %s' % callback) 1032 self.__right_extra_button_callback = callback 1033 self._BTN_extra_right.SetLabel(label) 1034 self._BTN_extra_right.SetToolTip(tooltip) 1035 self._BTN_extra_right.Enable(True) 1036 self._BTN_extra_right.Show()
1037 1038 right_extra_button = property(lambda x:x, _set_right_extra_button)
1039 1040 #================================================================ 1041 from Gnumed.wxGladeWidgets import wxgItemPickerDlg 1042
1043 -class cItemPickerDlg(wxgItemPickerDlg.wxgItemPickerDlg):
1044
1045 - def __init__(self, *args, **kwargs):
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 # external API 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 #------------------------------------------------------------
1089 - def set_string_items(self, items=None, reshow=True):
1090 self._LCTRL_left.set_string_items(items = items, reshow = reshow) 1091 self._LCTRL_left.set_column_widths() 1092 self._LCTRL_right.set_string_items(reshow = False) 1093 1094 self._BTN_left2right.Enable(False) 1095 self._BTN_right2left.Enable(False)
1096 1097 #------------------------------------------------------------
1098 - def set_selections(self, selections = None):
1099 self._LCTRL_left.set_selections(selections = selections)
1100 1101 #------------------------------------------------------------
1102 - def set_choices(self, choices=None, data=None, reshow=True):
1103 self.set_string_items(items = choices, reshow = reshow) 1104 if data is not None: 1105 self.set_data(data = data)
1106 1107 #------------------------------------------------------------
1108 - def set_picks(self, picks=None, data=None, reshow=True):
1109 self._LCTRL_right.set_string_items(picks, reshow = reshow) 1110 self._LCTRL_right.set_column_widths() 1111 if data is not None: 1112 self._LCTRL_right.set_data(data = data)
1113 1114 #------------------------------------------------------------
1115 - def set_data(self, data = None):
1116 self._LCTRL_left.set_data(data = data)
1117 1118 #------------------------------------------------------------
1119 - def get_picks(self):
1120 return self._LCTRL_right.get_item_data()
1121 1122 picks = property(get_picks, lambda x:x) 1123 1124 #------------------------------------------------------------
1125 - def _set_extra_button(self, definition):
1126 if definition is None: 1127 self._BTN_extra.Enable(False) 1128 self._BTN_extra.Hide() 1129 self.__extra_button_callback = None 1130 return 1131 1132 (label, tooltip, callback) = definition 1133 if not callable(callback): 1134 raise ValueError('<extra button> callback is not a callable: %s' % callback) 1135 self.__extra_button_callback = callback 1136 self._BTN_extra.SetLabel(label) 1137 self._BTN_extra.SetToolTip(tooltip) 1138 self._BTN_extra.Enable(True) 1139 self._BTN_extra.Show()
1140 1141 extra_button = property(lambda x:x, _set_extra_button) 1142 1143 #------------------------------------------------------------ 1144 # internal helpers 1145 #------------------------------------------------------------
1146 - def __pick_selected(self, event=None):
1147 if self._LCTRL_left.get_selected_items(only_one = True) == -1: 1148 return 1149 1150 right_items = self._LCTRL_right.get_string_items() 1151 right_data = self._LCTRL_right.get_item_data() 1152 if right_data is None: 1153 right_data = [] 1154 1155 selected_items = self._LCTRL_left.get_selected_string_items(only_one = False) 1156 selected_data = self._LCTRL_left.get_selected_item_data(only_one = False) 1157 1158 if self.ignore_dupes_on_picking is False: 1159 right_items.extend(selected_items) 1160 right_data.extend(selected_data) 1161 self._LCTRL_right.set_string_items(items = right_items, reshow = True) 1162 self._LCTRL_right.set_data(data = right_data) 1163 self._LCTRL_right.set_column_widths() 1164 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount) 1165 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data)) 1166 return 1167 1168 for sel_item, sel_data in zip(selected_items, selected_data): 1169 if sel_item in right_items: 1170 continue 1171 right_items.append(sel_item) 1172 right_data.append(sel_data) 1173 self._LCTRL_right.set_string_items(items = right_items, reshow = True) 1174 self._LCTRL_right.set_data(data = right_data) 1175 self._LCTRL_right.set_column_widths()
1176 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount) 1177 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data)) 1178 1179 #------------------------------------------------------------
1180 - def __remove_selected_picks(self):
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 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount) 1191 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data)) 1192 1193 #------------------------------------------------------------ 1194 # event handlers 1195 #------------------------------------------------------------
1196 - def _on_left_list_item_selected(self, event):
1197 self._BTN_left2right.Enable(True)
1198 #------------------------------------------------------------
1199 - def _on_left_list_item_deselected(self, event):
1200 if self._LCTRL_left.get_selected_items(only_one = True) == -1: 1201 self._BTN_left2right.Enable(False)
1202 #------------------------------------------------------------
1203 - def _on_right_list_item_selected(self, event):
1204 self._BTN_right2left.Enable(True)
1205 #------------------------------------------------------------
1206 - def _on_right_list_item_deselected(self, event):
1207 if self._LCTRL_right.get_selected_items(only_one = True) == -1: 1208 self._BTN_right2left.Enable(False)
1209 #------------------------------------------------------------
1210 - def _on_button_left2right_pressed(self, event):
1211 self.__pick_selected()
1212 #------------------------------------------------------------
1213 - def _on_button_right2left_pressed(self, event):
1214 self.__remove_selected_picks()
1215 #------------------------------------------------------------
1216 - def _on_extra_button_pressed(self, event):
1217 self.__extra_button_callback()
1218 #------------------------------------------------------------
1219 - def _set_left_item_tooltip_callback(self, callback):
1220 self._LCTRL_left.item_tooltip_callback = callback
1221 1222 left_item_tooltip_callback = property(lambda x:x, _set_left_item_tooltip_callback) 1223 #------------------------------------------------------------
1224 - def _set_right_item_tooltip_callback(self, callback):
1225 self._LCTRL_right.item_tooltip_callback = callback
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 # sorting: at set_string_items() time all items will be 1233 # adorned with their initial row number as wxPython data, 1234 # this is used later for a) sorting and b) to access 1235 # GNUmed data objects associated with rows, 1236 # the latter are ordered in initial row number order 1237 # at set_data() time 1238 1239 sort_order_tags = { 1240 True: ' [\u03b1\u0391 \u2192 \u03c9\u03A9]', 1241 False: ' [\u03c9\u03A9 \u2192 \u03b1\u0391]' 1242 } 1243
1244 - def __init__(self, *args, **kwargs):
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 # required for column sorting 1260 self._invalidate_sorting_metadata() # must be called after each (external/direct) list item update 1261 listmixins.ColumnSorterMixin.__init__(self, 0) # must be called again after adding columns (why ?) 1262 self.__secondary_sort_col = None 1263 # apparently, this MUST be bound under wxp3 - but why ?? 1264 self.Bind(wx.EVT_LIST_COL_CLICK, self._on_col_click, self) 1265 1266 # cols/rows 1267 self.__widths = None 1268 self.__data = None 1269 1270 # event callbacks 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 # context menu 1279 self.__extend_popup_menu_callback = None 1280 self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self._on_list_item_rightclicked) # (also handled by MENU key on EVT_LIST_KEY_DOWN) 1281 1282 # row tooltips 1283 self.__item_tooltip_callback = None 1284 self.__tt_last_item = None 1285 # self.__tt_static_part_base = _( 1286 # u'Select the items you want to work on.\n' 1287 # u'\n' 1288 # u'A discontinuous selection may depend on your holding ' 1289 # u'down a platform-dependent modifier key (<CTRL>, <ALT>, ' 1290 # u'etc) or key combination (eg. <CTRL-SHIFT> or <CTRL-ALT>) ' 1291 # u'while clicking.' 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 # search related: 1298 self.__search_term = None 1299 self.__next_line_to_search = 0 1300 self.__searchable_cols = None 1301 1302 # general event handling 1303 # self.Bind(wx.EVT_KILL_FOCUS, self._on_lost_focus) 1304 self.Bind(wx.EVT_CHAR, self._on_char) # CTRL-F / CTRL-N (LIST_KEY_DOWN does not support modifiers) 1305 self.Bind(wx.EVT_LIST_KEY_DOWN, self._on_list_key_down) # context menu key -> context menu / DEL / INS
1306 1307 #------------------------------------------------------------ 1308 # debug sizing 1309 #------------------------------------------------------------
1310 - def __log_sizing(self, caller_name, *args, **kwargs):
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 #------------------------------------------------------------
1319 - def CacheBestSize(self, *args, **kwargs):
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 #------------------------------------------------------------
1329 - def FitInside(self, *args, **kwargs):
1330 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs) 1331 return super(cReportListCtrl, self).FitInside(*args, **kwargs)
1332 1333 #------------------------------------------------------------
1334 - def InvalidateBestSize(self, *args, **kwargs):
1335 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs) 1336 return super(cReportListCtrl, self).InvalidateBestSize(*args, **kwargs)
1337 1338 #------------------------------------------------------------
1339 - def SetBestFittingSize(self, *args, **kwargs):
1340 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs) 1341 return super(cReportListCtrl, self).SetBestFittingSize(*args, **kwargs)
1342 1343 #------------------------------------------------------------
1344 - def SetInitialSize(self, *args, **kwargs):
1345 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs) 1346 return super(cReportListCtrl, self).SetInitialSize(*args, **kwargs)
1347 1348 #------------------------------------------------------------
1349 - def SetClientSize(self, *args, **kwargs):
1350 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs) 1351 return super(cReportListCtrl, self).SetClientSize(*args, **kwargs)
1352 1353 #------------------------------------------------------------
1354 - def SetClientSizeWH(self, *args, **kwargs):
1355 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs) 1356 return super(cReportListCtrl, self).SetClientSizeWH(*args, **kwargs)
1357 1358 #------------------------------------------------------------
1359 - def SetMaxClientSize(self, *args, **kwargs):
1360 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs) 1361 return super(cReportListCtrl, self).SetMaxClientSize(*args, **kwargs)
1362 1363 #------------------------------------------------------------
1364 - def SetMaxSize(self, *args, **kwargs):
1365 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs) 1366 return super(cReportListCtrl, self).SetMaxSize(*args, **kwargs)
1367 1368 #------------------------------------------------------------
1369 - def SetMinClientSize(self, *args, **kwargs):
1370 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs) 1371 return super(cReportListCtrl, self).SetMinClientSize(*args, **kwargs)
1372 1373 #------------------------------------------------------------
1374 - def SetMinSize(self, *args, **kwargs):
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 #------------------------------------------------------------
1384 - def SetSizeHints(self, *args, **kwargs):
1385 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs) 1386 return super(cReportListCtrl, self).SetSizeHints(*args, **kwargs)
1387 1388 #------------------------------------------------------------
1389 - def SetSizeHintsSz(self, *args, **kwargs):
1390 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs) 1391 return super(cReportListCtrl, self).SetSizeHintsSz(*args, **kwargs)
1392 1393 #------------------------------------------------------------
1394 - def SetSizeWH(self, *args, **kwargs):
1395 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs) 1396 return super(cReportListCtrl, self).SetSizeWH(*args, **kwargs)
1397 1398 #------------------------------------------------------------
1399 - def SetVirtualSize(self, *args, **kwargs):
1400 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs) 1401 return super(cReportListCtrl, self).SetVirtualSize(*args, **kwargs)
1402 1403 #------------------------------------------------------------
1404 - def SetVirtualSizeHints(self, *args, **kwargs):
1405 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs) 1406 return super(cReportListCtrl, self).SetVirtualSizeHints(self, *args, **kwargs)
1407 1408 #------------------------------------------------------------
1409 - def SetVirtualSizeHintsSz(self, *args, **kwargs):
1410 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs) 1411 return super(cReportListCtrl, self).SetVirtualSizeHintsSz(*args, **kwargs)
1412 1413 #------------------------------------------------------------
1414 - def SetVirtualSizeWH(self, *args, **kwargs):
1415 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs) 1416 return super(cReportListCtrl, self).SetVirtualSizeWH(*args, **kwargs)
1417 1418 #------------------------------------------------------------
1419 - def GetAdjustedBestSize(self, *args, **kwargs):
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 #------------------------------------------------------------
1426 - def GetEffectiveMinSize(self, *args, **kwargs):
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 #------------------------------------------------------------
1433 - def GetBestSize(self, *args, **kwargs):
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 #------------------------------------------------------------
1440 - def GetBestSizeTuple(self, *args, **kwargs):
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 #------------------------------------------------------------
1447 - def GetBestVirtualSize(self, *args, **kwargs):
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 #------------------------------------------------------------
1454 - def GetClientSize(self, *args, **kwargs):
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 #------------------------------------------------------------
1461 - def GetClientSize(self, *args, **kwargs):
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 #------------------------------------------------------------
1468 - def GetMaxClientSize(self, *args, **kwargs):
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 #------------------------------------------------------------
1475 - def GetMaxHeight(self, *args, **kwargs):
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 #------------------------------------------------------------
1482 - def GetMaxSize(self, *args, **kwargs):
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 #------------------------------------------------------------
1489 - def GetMaxWidth(self, *args, **kwargs):
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 #------------------------------------------------------------
1496 - def GetMinClientSize(self, *args, **kwargs):
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 #------------------------------------------------------------
1503 - def GetMinHeight(self, *args, **kwargs):
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 #------------------------------------------------------------
1510 - def GetMinSize(self, *args, **kwargs):
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 #------------------------------------------------------------
1517 - def GetMinWidth(self, *args, **kwargs):
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 #------------------------------------------------------------
1531 - def GetVirtualSize(self, *args, **kwargs):
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 #------------------------------------------------------------
1538 - def GetVirtualSizeTuple(self, *args, **kwargs):
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 # setters 1546 #------------------------------------------------------------
1547 - def set_columns(self, columns=None):
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 #------------------------------------------------------------
1563 - def set_column_widths(self, widths=None):
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 # explicit policy ? 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 # previous policy ? 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 # default policy ! 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 #------------------------------------------------------------
1599 - def set_resize_column(self, column='LAST'):
1600 if column != 'LAST': 1601 if column > self.ColumnCount: 1602 return 1603 # this column will take up all remaining space courtesy of the width mixin 1604 self.setResizeColumn(column)
1605 1606 #------------------------------------------------------------
1607 - def set_column_label(self, col_idx, label):
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 #------------------------------------------------------------
1621 - def remove_items_safely(self, max_tries=3):
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 #------------------------------------------------------------
1642 - def set_string_items(self, items=None, reshow=True, unwrap=True):
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 # insert new items 1666 for item in items: 1667 # item is a single string 1668 # (typical special case: items=rows are a list-of-strings) 1669 if isinstance(item, str): 1670 self.InsertItem(index = sys.maxsize, label = item.replace('\r\n', ' [CRLF] ').replace('\n', ' [LF] ')) 1671 continue 1672 # item is something else, either ... 1673 try: 1674 # ... an iterable 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 # ... an *empty* iterable [IndexError] 1682 # ... or not iterable (None, int, instance, dict [KeyError] ...) 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 # set data to be a copy of items 1696 self.data = items 1697 1698 wx.EndBusyCursor()
1699 1700 #--------------------------
1701 - def get_string_items(self):
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 # old: only returned first column 1714 #return [ self.GetItemText(item_idx) for item_idx in range(self.GetItemCount()) ] 1715 1716 string_items = property(get_string_items, set_string_items) 1717 1718 #------------------------------------------------------------
1719 - def append_string_items_and_data(self, new_items, new_data=None, allow_dupes=False):
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 #------------------------------------------------------------
1753 - def set_data(self, data=None):
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 # string data (rows/visible list items) not modified, 1764 # so no need to call _update_sorting_metadata 1765 return
1766
1767 - def _get_data(self):
1768 # slower than "return self.__data" but helps with detecting 1769 # problems with len(__data) != self.GetItemCount(), 1770 # also takes care of returning data in the order corresponding 1771 # to the order get_string_items returns rows 1772 return self.get_item_data() # returns all data since item_idx is None
1773 1774 data = property(_get_data, set_data) 1775 1776 #------------------------------------------------------------
1777 - def set_selections(self, selections=None):
1778 # not sure why this is done: 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
1786 - def __get_selections(self):
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 # getters 1802 #------------------------------------------------------------
1803 - def get_column_labels(self):
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 #------------------------------------------------------------
1813 - def get_item(self, item_idx=None):
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 #------------------------------------------------------------
1823 - def get_items(self):
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 #------------------------------------------------------------
1831 - def get_selected_items(self, only_one=False):
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 #------------------------------------------------------------
1852 - def get_selected_string_items(self, only_one=False):
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 #------------------------------------------------------------
1873 - def get_item_data(self, item_idx=None):
1874 1875 if self.__data is None: # this isn't entirely clean 1876 return None 1877 1878 if item_idx is not None: 1879 return self.__data[self.map_item_idx2data_idx(item_idx)] 1880 1881 # if <idx> is None return all data up to item_count, 1882 # in case of len(__data) != self.GetItemCount() this 1883 # gives the chance to figure out what is going on 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 #------------------------------------------------------------
1889 - def get_selected_item_data(self, only_one=False):
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 #------------------------------------------------------------
1912 - def deselect_selected_item(self):
1913 self.Select(idx = self.GetFirstSelected(), on = 0)
1914 1915 #------------------------------------------------------------
1916 - def remove_item(self, item_idx=None):
1917 # do NOT remove the corresponding data because even if 1918 # the item pointing to this data instance is gone all 1919 # other items will still point to their corresponding 1920 # *initial* row numbers 1921 #if self.__data is not None: 1922 # del self.__data[self.map_item_idx2data_idx(item_idx)] 1923 self.DeleteItem(item_idx) 1924 self.__tt_last_item = None 1925 self._invalidate_sorting_metadata()
1926 1927 #------------------------------------------------------------ 1928 # internal helpers 1929 #------------------------------------------------------------
1930 - def __show_context_menu(self, item_idx):
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 # build context menu 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 # save to file 1993 save_menu = wx.Menu() 1994 # save / all rows 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 # save / selected rows (if >1) 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 # put into clipboard 2016 clip_menu = wx.Menu() 2017 # clipboard / selected rows (if >1) 2018 if no_of_selected_items > 1: 2019 # row tooltips 2020 menu_item = clip_menu.Append(-1, _('Tooltips of selected rows')) 2021 self.Bind(wx.EVT_MENU, self._tooltips2clipboard, menu_item) 2022 # row data as formatted text if available 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 # all fields of the list row as one line of text 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 # clipboard / the right-clicked row 2030 # row tooltip 2031 menu_item = clip_menu.Append(-1, _('Tooltip of current row')) 2032 self.Bind(wx.EVT_MENU, self._tooltip2clipboard, menu_item) 2033 # row data as formatted text if available 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 # all fields of the list row as one line of text 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 # all fields of the list row as multiple lines of text 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 # each field of the list row as text with and without header 2044 clip_menu.AppendSeparator() 2045 for col_idx in range(self.ColumnCount): 2046 col_content = self._rclicked_row_cells[col_idx].strip() 2047 # skip empty field 2048 if col_content == '': 2049 continue 2050 col_header = col_headers[col_idx] 2051 if col_header == '': 2052 # skip one-character fields without header, 2053 # actually, no, because in ideographic languages 2054 # one character may mean a lot 2055 #if len(col_content) == 1: 2056 # continue 2057 # without column header 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 # with full column header 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 # without column header 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 # append to topmost clipboard item 2071 clip_add_menu = wx.Menu() 2072 # clipboard - add / selected rows (if >1) 2073 if no_of_selected_items > 1: 2074 # row tooltips 2075 menu_item = clip_add_menu.Append(-1, _('Tooltips of selected rows')) 2076 self.Bind(wx.EVT_MENU, self._add_tooltips2clipboard, menu_item) 2077 # row data as formatted text if available 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 # all fields of the list row as one line of text 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 # clipboard - add / the right-clicked row 2085 # row tooltip 2086 menu_item = clip_add_menu.Append(-1, _('Tooltip of current row')) 2087 self.Bind(wx.EVT_MENU, self._add_tooltip2clipboard, menu_item) 2088 # row data as formatted text if available 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 # all fields of the list row as one line of text 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 # all fields of the list row as multiple lines of text 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 # each field of the list row as text with and without header 2099 clip_add_menu.AppendSeparator() 2100 for col_idx in range(self.ColumnCount): 2101 col_content = self._rclicked_row_cells[col_idx].strip() 2102 # skip empty field 2103 if col_content == '': 2104 continue 2105 col_header = col_headers[col_idx] 2106 if col_header == '': 2107 # without column header 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 # with full column header 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 # without column header 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 # screenshot menu 2121 screenshot_menu = wx.Menu() 2122 # screenshot / save to file 2123 menu_item = screenshot_menu.Append(-1, _('&Save')) 2124 self.Bind(wx.EVT_MENU, self._visible_rows_screenshot2file, menu_item) 2125 # screenshot / into export area 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 # 3) copy item to export area 2130 # put into file 2131 # current row 2132 # - fields as one line 2133 # - fields as list 2134 # - data formatted 2135 # - tooltip 2136 # selected rows 2137 # - fields as lines each 2138 # - all data formatted 2139 # - all tooltips 2140 # - as CSV 2141 # send signal 2142 2143 # show menu 2144 #self._context_menu.Append(-1, _('Copy to e&xport area...'), exp_menu) 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 # show menu 2155 self.PopupMenu(self._context_menu, wx.DefaultPosition) 2156 self._context_menu.Destroy() 2157 return
2158 2159 #------------------------------------------------------------
2160 - def __handle_delete(self):
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 #------------------------------------------------------------
2190 - def __handle_insert(self):
2191 if self.__new_callback is None: 2192 return 2193 self.__new_callback()
2194 2195 #------------------------------------------------------------
2196 - def __handle_edit(self):
2197 if self.__edit_callback is None: 2198 return 2199 self.__edit_callback()
2200 2201 #------------------------------------------------------------
2202 - def __show_search_dialog(self):
2203 #print "showing search dlg" 2204 if self.__search_term is None: 2205 #print "no prev search term" 2206 default = '' 2207 else: 2208 #print "prev search term:", self.__search_term 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 #print "empty search term" 2217 self.__search_term = None 2218 self.__tt_static_part = self.__tt_static_part_base 2219 return 2220 2221 #print "search term:", search_term 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 # event handlers 2235 #------------------------------------------------------------
2236 - def _on_list_item_activated(self, event):
2237 event.Skip() 2238 if self.__activate_callback is not None: 2239 self.__activate_callback(event) 2240 return 2241 # default double-click / ENTER action: edit 2242 self.__handle_edit()
2243 2244 #------------------------------------------------------------
2245 - def _on_list_item_selected(self, event):
2246 if self.__select_callback is not None: 2247 self.__select_callback(event) 2248 else: 2249 event.Skip()
2250 2251 #------------------------------------------------------------
2252 - def _on_list_item_deselected(self, event):
2253 if self.__deselect_callback is not None: 2254 self.__deselect_callback(event) 2255 else: 2256 event.Skip()
2257 2258 #------------------------------------------------------------
2259 - def _on_list_item_rightclicked(self, event):
2260 event.Skip() 2261 self.__show_context_menu(event.Index)
2262 2263 #------------------------------------------------------------
2264 - def _on_list_key_down(self, evt):
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 #------------------------------------------------------------
2280 - def _on_char(self, evt):
2281 2282 if chr(evt.GetRawKeyCode()) == 'f': 2283 if evt.GetModifiers() == wx.MOD_CMD: 2284 #print "search dialog invoked" 2285 self.__show_search_dialog() 2286 return 2287 2288 if chr(evt.GetRawKeyCode()) == 'n': 2289 if evt.GetModifiers() == wx.MOD_CMD: 2290 #print "search-next key invoked" 2291 self.__search_match() 2292 return 2293 2294 evt.Skip() 2295 return
2296 2297 #------------------------------------------------------------
2298 - def _on_mouse_motion(self, event):
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 # pointer on item related area at all ? 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 # not on any item 2327 self.SetToolTip(self.__tt_static_part) 2328 return 2329 2330 # same item as last time around ? 2331 if self.__tt_last_item == item_idx: 2332 return 2333 2334 # remeber the new item we are on 2335 self.__tt_last_item = item_idx 2336 2337 # HitTest() can return -1 if it so pleases, meaning that no item 2338 # was hit or else that maybe there aren't any items (empty list) 2339 if item_idx == wx.NOT_FOUND: 2340 self.SetToolTip(self.__tt_static_part) 2341 return 2342 2343 # do we *have* item data ? 2344 if self.__data is None: 2345 self.SetToolTip(self.__tt_static_part) 2346 return 2347 2348 # under some circumstances the item_idx returned 2349 # by HitTest() may be out of bounds with respect to 2350 # self.__data, this hints at a sync problem between 2351 # setting display items and associated data 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 # context menu event hendlers 2383 #------------------------------------------------------------
2384 - def _on_add_row(self, evt):
2385 evt.Skip() 2386 self.__handle_insert()
2387 2388 #------------------------------------------------------------
2389 - def _on_edit_row(self, evt):
2390 evt.Skip() 2391 self.__handle_edit()
2392 2393 #------------------------------------------------------------
2394 - def _on_delete_row(self, evt):
2395 evt.Skip() 2396 self.__handle_delete()
2397 2398 #------------------------------------------------------------
2399 - def _on_show_search_dialog(self, evt):
2400 evt.Skip() 2401 self.__show_search_dialog()
2402 2403 #------------------------------------------------------------
2404 - def _on_search_match(self, evt):
2405 evt.Skip() 2406 self.__search_match()
2407 2408 #------------------------------------------------------------
2409 - def _all_rows2file(self, evt):
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 #------------------------------------------------------------
2429 - def _all_rows2csv(self, evt):
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 #------------------------------------------------------------
2444 - def _all_row_tooltips2file(self, evt):
2445 2446 if (self.__data is None) or (self.__item_tooltip_callback is None): 2447 return 2448 2449 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-list_tooltips-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S')) 2450 txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8') 2451 2452 for data in self.data: 2453 tt = self.__item_tooltip_callback(data) 2454 if tt is None: 2455 continue 2456 txt_file.write('%s\n\n' % tt) 2457 2458 txt_file.close() 2459 gmDispatcher.send(signal = 'statustext', msg = _('All tooltips saved to [%s].') % txt_name)
2460 2461 #------------------------------------------------------------
2462 - def _all_row_data2file(self, evt):
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 #------------------------------------------------------------
2483 - def _selected_rows2file(self, evt):
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 #------------------------------------------------------------
2507 - def _selected_rows2csv(self, evt):
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 #------------------------------------------------------------
2525 - def _selected_row_tooltips2file(self, evt):
2526 2527 if (self.__data is None) or (self.__item_tooltip_callback is None): 2528 return 2529 2530 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-list_tooltips-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S')) 2531 txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8') 2532 2533 for data in self.selected_item_data: 2534 tt = self.__item_tooltip_callback(data) 2535 if tt is None: 2536 continue 2537 txt_file.write('%s\n\n' % tt) 2538 2539 txt_file.close() 2540 gmDispatcher.send(signal = 'statustext', msg = _('Selected tooltips saved to [%s].') % txt_name)
2541 2542 #------------------------------------------------------------
2543 - def _selected_row_data2file(self, evt):
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 #------------------------------------------------------------
2564 - def _visible_rows_screenshot2file(self, evt):
2565 dlg = self.containing_dlg 2566 if dlg is None: 2567 widget2screenshot = self 2568 else: 2569 widget2screenshot = dlg 2570 png_name = os.path.join ( 2571 gmTools.gmPaths().home_dir, 2572 'gnumed', 2573 'gm-%s-%s.png' % (self.useful_title, pydt.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')) 2574 ) 2575 from Gnumed.wxpython.gmGuiHelpers import save_screenshot_to_file 2576 save_screenshot_to_file(filename = png_name, widget = widget2screenshot, settle_time = 500)
2577 2578 #------------------------------------------------------------
2580 dlg = self.containing_dlg 2581 if dlg is None: 2582 widget2screenshot = self 2583 else: 2584 widget2screenshot = dlg 2585 png_name = os.path.join ( 2586 gmTools.gmPaths().home_dir, 2587 'gnumed', 2588 'gm-%s-%s.png' % (self.useful_title, pydt.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')) 2589 ) 2590 from Gnumed.wxpython.gmGuiHelpers import save_screenshot_to_file 2591 screenshot_file = save_screenshot_to_file(widget = widget2screenshot, settle_time = 500) 2592 gmDispatcher.send(signal = 'add_file_to_export_area', filename = screenshot_file, hint = _('GMd screenshot'))
2593 2594 #------------------------------------------------------------
2595 - def _tooltip2clipboard(self, evt):
2596 if wx.TheClipboard.IsOpened(): 2597 _log.debug('clipboard already open') 2598 return 2599 if not wx.TheClipboard.Open(): 2600 _log.debug('cannot open clipboard') 2601 return 2602 data_obj = wx.TextDataObject() 2603 2604 if (self.__data is None) or (self.__item_tooltip_callback is None): 2605 txt = self.__tt_static_part 2606 else: 2607 txt = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(self._rclicked_row_idx)]) 2608 if txt is None: 2609 txt = self.__tt_static_part 2610 2611 data_obj.SetText(txt) 2612 wx.TheClipboard.SetData(data_obj) 2613 wx.TheClipboard.Close()
2614 2615 #------------------------------------------------------------
2616 - def _tooltips2clipboard(self, evt):
2617 if wx.TheClipboard.IsOpened(): 2618 _log.debug('clipboard already open') 2619 return 2620 if not wx.TheClipboard.Open(): 2621 _log.debug('cannot open clipboard') 2622 return 2623 2624 if (self.__data is None) or (self.__item_tooltip_callback is None): 2625 return 2626 2627 tts = [] 2628 for data in self.selected_item_data: 2629 tt = self.__item_tooltip_callback(data) 2630 if tt is None: 2631 continue 2632 tts.append(tt) 2633 2634 if len(tts) == 0: 2635 return 2636 2637 data_obj = wx.TextDataObject() 2638 data_obj.SetText('\n\n'.join(tts)) 2639 wx.TheClipboard.SetData(data_obj) 2640 wx.TheClipboard.Close()
2641 2642 #------------------------------------------------------------
2643 - def _add_tooltip2clipboard(self, evt):
2644 if wx.TheClipboard.IsOpened(): 2645 _log.debug('clipboard already open') 2646 return 2647 if not wx.TheClipboard.Open(): 2648 _log.debug('cannot open clipboard') 2649 return 2650 data_obj = wx.TextDataObject() 2651 2652 txt = '' 2653 # get previous text 2654 got_it = wx.TheClipboard.GetData(data_obj) 2655 if got_it: 2656 txt = data_obj.Text + '\n' 2657 2658 # add text 2659 if (self.__data is None) or (self.__item_tooltip_callback is None): 2660 txt += self.__tt_static_part 2661 else: 2662 tmp = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(self._rclicked_row_idx)]) 2663 if tmp is None: 2664 txt += self.__tt_static_part 2665 else: 2666 txt += tmp 2667 2668 # set text 2669 data_obj.SetText(txt) 2670 wx.TheClipboard.SetData(data_obj) 2671 wx.TheClipboard.Close()
2672 2673 #------------------------------------------------------------
2674 - def _add_tooltips2clipboard(self, evt):
2675 if wx.TheClipboard.IsOpened(): 2676 _log.debug('clipboard already open') 2677 return 2678 if not wx.TheClipboard.Open(): 2679 _log.debug('cannot open clipboard') 2680 return 2681 2682 if (self.__data is None) or (self.__item_tooltip_callback is None): 2683 return 2684 2685 tts = [] 2686 for data in self.selected_item_data: 2687 tt = self.__item_tooltip_callback(data) 2688 if tt is None: 2689 continue 2690 tts.append(tt) 2691 2692 if len(tts) == 0: 2693 return 2694 2695 data_obj = wx.TextDataObject() 2696 txt = '' 2697 # get previous text 2698 got_it = wx.TheClipboard.GetData(data_obj) 2699 if got_it: 2700 txt = data_obj.Text + '\n\n' 2701 txt += '\n\n'.join(tts) 2702 2703 data_obj.SetText(txt) 2704 wx.TheClipboard.SetData(data_obj) 2705 wx.TheClipboard.Close()
2706 2707 #------------------------------------------------------------
2708 - def _row2clipboard(self, evt):
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 #------------------------------------------------------------
2721 - def _rows2clipboard(self, evt):
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 #------------------------------------------------------------
2739 - def _add_row2clipboard(self, evt):
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 # get previous text 2750 got_it = wx.TheClipboard.GetData(data_obj) 2751 if got_it: 2752 txt = data_obj.Text + '\n' 2753 2754 # add text 2755 txt += ' // '.join(self._rclicked_row_cells) 2756 2757 # set text 2758 data_obj.SetText(txt) 2759 wx.TheClipboard.SetData(data_obj) 2760 wx.TheClipboard.Close()
2761 2762 #------------------------------------------------------------
2763 - def _add_rows2clipboard(self, evt):
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 # get previous text 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 #------------------------------------------------------------
2789 - def _row_list2clipboard(self, evt):
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 #------------------------------------------------------------
2802 - def _add_row_list2clipboard(self, evt):
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 # get previous text 2813 got_it = wx.TheClipboard.GetData(data_obj) 2814 if got_it: 2815 txt = data_obj.Text + '\n' 2816 2817 # add text 2818 txt += '\n'.join(self._rclicked_row_cells_w_hdr) 2819 2820 # set text 2821 data_obj.SetText(txt) 2822 wx.TheClipboard.SetData(data_obj) 2823 wx.TheClipboard.Close()
2824 2825 #------------------------------------------------------------
2826 - def _data2clipboard(self, evt):
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 #------------------------------------------------------------
2842 - def _datas2clipboard(self, evt):
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 #------------------------------------------------------------
2866 - def _add_data2clipboard(self, evt):
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 # get previous text 2877 got_it = wx.TheClipboard.GetData(data_obj) 2878 if got_it: 2879 txt = data_obj.Text + '\n' 2880 2881 # add text 2882 tmp = self._rclicked_row_data.format() 2883 if type(tmp) == type([]): 2884 txt += '\n'.join(tmp) 2885 else: 2886 txt += tmp 2887 2888 # set text 2889 data_obj.SetText(txt) 2890 wx.TheClipboard.SetData(data_obj) 2891 wx.TheClipboard.Close()
2892 2893 #------------------------------------------------------------
2894 - def _add_datas2clipboard(self, evt):
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 # get previous text 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 # set text 2921 data_obj.SetText(txt) 2922 wx.TheClipboard.SetData(data_obj) 2923 wx.TheClipboard.Close()
2924 2925 #------------------------------------------------------------
2926 - def _col2clipboard(self, evt):
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 #col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.split(u':', 1)[0].rstrip(u':')) - 1 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 #------------------------------------------------------------
2944 - def _add_col2clipboard(self, evt):
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 # get previous text 2955 got_it = wx.TheClipboard.GetData(data_obj) 2956 if got_it: 2957 txt = data_obj.Text + '\n' 2958 2959 # add text 2960 #col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.split(u':', 1)[0].rstrip(u':')) - 1 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 # set text 2965 data_obj.SetText(txt) 2966 wx.TheClipboard.SetData(data_obj) 2967 wx.TheClipboard.Close()
2968 2969 #------------------------------------------------------------
2970 - def _col_w_hdr2clipboard(self, evt):
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 #col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.split(u':', 1)[0].rstrip(u':')) - 1 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 #------------------------------------------------------------
2988 - def _add_col_w_hdr2clipboard(self, evt):
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 # get previous text 2999 got_it = wx.TheClipboard.GetData(data_obj) 3000 if got_it: 3001 txt = data_obj.Text + '\n' 3002 3003 # add text 3004 #col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.split(u':', 1)[0].rstrip(u':')) - 1 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 # set text 3009 data_obj.SetText(txt) 3010 wx.TheClipboard.SetData(data_obj) 3011 wx.TheClipboard.Close()
3012 3013 #------------------------------------------------------------ 3014 # search related methods 3015 #------------------------------------------------------------ 3016 # def _on_lost_focus(self, evt): 3017 # evt.Skip() 3018 # if self.__search_dlg is None: 3019 # return 3020 ## print self.FindFocus() 3021 ## print self.__search_dlg 3022 # #self.__search_dlg.Close() 3023 3024 #------------------------------------------------------------
3025 - def __search_match(self):
3026 #print "search_match" 3027 if self.__search_term is None: 3028 #print "no search term" 3029 return False 3030 if self.__search_term.strip() == '': 3031 #print "empty search term" 3032 return False 3033 if self.__searchable_cols is None: 3034 #print "searchable cols not initialized, now setting" 3035 self.searchable_columns = None 3036 if len(self.__searchable_cols) == 0: 3037 #print "no cols to search" 3038 return False 3039 3040 #print "on searching for match on:", self.__search_term 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 # wrap around 3051 self.__next_line_to_search = 0 3052 else: 3053 self.__next_line_to_search = row_idx + 1 3054 return True 3055 # wrap around 3056 self.__next_line_to_search = 0 3057 return False
3058 3059 #------------------------------------------------------------
3060 - def _set_searchable_cols(self, cols):
3061 #print "setting searchable cols to:", cols 3062 # zero-based list of which columns to search 3063 if cols is None: 3064 #print "setting searchable cols to:", range(self.ColumnCount) 3065 self.__searchable_cols = range(self.ColumnCount) 3066 return 3067 # weed out columns to be searched which 3068 # don't exist and uniquify them 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 # properties 3079 #------------------------------------------------------------
3080 - def _get_activate_callback(self):
3081 return self.__activate_callback
3082
3083 - def _set_activate_callback(self, callback):
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 #------------------------------------------------------------
3096 - def _get_select_callback(self):
3097 return self.__select_callback
3098
3099 - def _set_select_callback(self, callback):
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 #------------------------------------------------------------
3112 - def _get_deselect_callback(self):
3113 return self.__deselect_callback
3114
3115 - def _set_deselect_callback(self, callback):
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 #------------------------------------------------------------
3128 - def _get_delete_callback(self):
3129 return self.__delete_callback
3130
3131 - def _set_delete_callback(self, callback):
3132 if callback is None: 3133 #self.Unbind(wx.EVT_LIST_ITEM_SELECTED) 3134 self.__delete_callback = None 3135 return 3136 if not callable(callback): 3137 raise ValueError('<delete> callback is not a callable: %s' % callback) 3138 #self.Bind(wx.EVT_LIST_ITEM_SELECTED, self._on_list_item_selected) 3139 self.__delete_callback = callback
3140 3141 delete_callback = property(_get_delete_callback, _set_delete_callback) 3142 3143 #------------------------------------------------------------
3144 - def _get_new_callback(self):
3145 return self.__new_callback
3146
3147 - def _set_new_callback(self, callback):
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 #------------------------------------------------------------
3158 - def _get_edit_callback(self):
3159 return self.__edit_callback
3160
3161 - def _set_edit_callback(self, callback):
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 #------------------------------------------------------------
3172 - def _set_item_tooltip_callback(self, callback):
3173 if callback is not None: 3174 if not callable(callback): 3175 raise ValueError('<item_tooltip> callback is not a callable: %s' % callback) 3176 self.__item_tooltip_callback = callback
3177 3178 # the callback must be a function which takes a single argument 3179 # the argument is the data for the item the tooltip is on 3180 # the callback must return None if no item tooltip is to be shown 3181 # otherwise it must return a string (possibly with \n) 3182 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback) 3183 3184 #------------------------------------------------------------
3185 - def _set_extend_popup_menu_callback(self, callback):
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 # ColumnSorterMixin API 3195 #------------------------------------------------------------
3196 - def GetListCtrl(self):
3197 if self.itemDataMap is None: 3198 self._update_sorting_metadata() 3199 return self # required
3200 3201 #------------------------------------------------------------
3202 - def OnSortOrderChanged(self):
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 #------------------------------------------------------------
3213 - def GetSecondarySortValues(self, primary_sort_col, primary_item1_idx, primary_item2_idx):
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 # make the secondary column always sort ascending 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 #------------------------------------------------------------
3243 - def _unicode_aware_column_sorter(self, item1, item2):
3244 # http://jtauber.com/blog/2006/01/27/python_unicode_collation_algorithm/ 3245 # http://stackoverflow.com/questions/1097908/how-do-i-sort-unicode-strings-alphabetically-in-python 3246 # PyICU 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 #direction = u'ASC' 3258 if not is_ascending: 3259 cmp_result = -1 * cmp_result 3260 #direction = u'DESC' 3261 # debug: 3262 # if cmp_result < 0: 3263 # op1 = u'\u2191 ' # up 3264 # op2 = u'\u2193' # down 3265 # elif cmp_result > 0: 3266 # op2 = u'\u2191 ' # up 3267 # op1 = u'\u2193' # down 3268 # else: 3269 # op1 = u' = ' 3270 # op2 = u'' 3271 # print u'%s: [%s]%s[%s]%s (%s)' % (direction, data1, op1, data2, op2, cmp_result) 3272 3273 return cmp_result
3274 3275 # defining our own column sorter does not seem to make 3276 # a difference over the default one until we resort to 3277 # something other than locale.strcoll/strxform/cmp for 3278 # actual sorting 3279 #def GetColumnSorter(self): 3280 # return self._unicode_aware_column_sorter 3281 3282 #------------------------------------------------------------
3283 - def _generate_map_for_sorting(self):
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 # debugging: 3296 #print u'[%s:%s] ' % (item_idx, col_idx), self.GetItem(item_idx, col_idx).GetText() 3297 3298 return dict2sort
3299 3300 #------------------------------------------------------------
3301 - def __remove_sorting_indicator(self, text):
3302 for tag in self.sort_order_tags.values(): 3303 if text.endswith(tag): 3304 text = text[:-len(tag)] 3305 return text
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 #------------------------------------------------------------
3329 self.itemDataMap = None 3330 self.SetColumnCount(self.GetColumnCount()) 3331 self._remove_sorting_indicators_from_column_labels()
3332 3333 #------------------------------------------------------------
3334 - def _update_sorting_metadata(self):
3335 # MUST have this name 3336 self.itemDataMap = self._generate_map_for_sorting()
3337 3338 #------------------------------------------------------------
3339 - def _on_col_click(self, event):
3340 # this MUST NOT call event.Skip() or else the column sorter mixin# 3341 # will not kick in anymore under wxP 3 3342 #event.Skip() 3343 pass
3344 # debugging: 3345 # sort_col, order = self.GetSortState() 3346 # print u'col clicked: %s / sort col: %s / sort direction: %s / sort flags: %s' % (event.GetColumn(), sort_col, order, self._colSortFlag) 3347 # if self.itemDataMap is not None: 3348 # print u'sort items data map:' 3349 # for key, item in self.itemDataMap.items(): 3350 # print key, u' -- ', item 3351 3352 #------------------------------------------------------------
3353 - def __get_secondary_sort_col(self):
3354 return self.__secondary_sort_col
3355
3356 - def __set_secondary_sort_col(self, col):
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 #------------------------------------------------------------
3367 - def __get_useful_title(self):
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 #------------------------------------------------------------
3385 - def __get_container_title(self, widget=None):
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 #------------------------------------------------------------
3402 - def __get_containing_dlg(self, widget=None):
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 # main 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 #------------------------------------------------------------
3440 - def test_wxMultiChoiceDialog():
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 #------------------------------------------------------------
3455 - def test_get_choices_from_list():
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 # msg = 'select a health issue\nfrom the list below\n', 3468 caption = 'select health issues', 3469 #choices = [['D.M.II', '4'], ['MS', '3'], ['Fraktur', '2']], 3470 #columns = ['issue', 'no of episodes'] 3471 columns = ['issue'], 3472 refresh_callback = refresh, 3473 single_selection = False 3474 #, edit_callback = edit 3475 ) 3476 print("chosen:") 3477 print(chosen) 3478 3479 #------------------------------------------------------------
3480 - def test_item_picker_dlg():
3481 #app = wx.PyWidgetTester(size = (200, 50)) 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 #dlg.set_columns(['Plugins'], []) 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 #test_wxMultiChoiceDialog() 3494 #test_item_picker_dlg() 3495 3496 #================================================================ 3497