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

Source Code for Module Gnumed.wxpython.gmListWidgets

   1  """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 thread 
  25  import time 
  26  import re as regex 
  27   
  28   
  29  import wx 
  30  import wx.lib.mixins.listctrl as listmixins 
  31   
  32   
  33  _log = logging.getLogger('gm.list_ui') 
  34  #================================================================ 
  35  # FIXME: configurable callback on double-click action 
  36   
37 -def get_choices_from_list ( 38 parent=None, 39 msg=None, 40 caption=None, 41 columns=None, 42 choices=None, 43 data=None, 44 selections=None, 45 edit_callback=None, 46 new_callback=None, 47 delete_callback=None, 48 refresh_callback=None, 49 single_selection=False, 50 can_return_empty=False, 51 ignore_OK_button=False, 52 left_extra_button=None, 53 middle_extra_button=None, 54 right_extra_button=None, 55 list_tooltip_callback=None):
56 """Let user select item(s) from a list. 57 58 - new_callback: () 59 - edit_callback: (item data) 60 - delete_callback: (item data) 61 - refresh_callback: (listctrl) 62 - list_tooltip_callback: (item data) 63 64 - left/middle/right_extra_button: (label, tooltip, <callback> [, wants_list_ctrl]) 65 wants_list_ctrl is optional 66 <callback> is called with item_data (or listctrl) as the only argument 67 68 returns: 69 on [CANCEL]: None 70 on [OK]: 71 if any items selected: 72 list of selected items 73 else: 74 if can_return_empty is True: 75 empty list 76 else: 77 None 78 """ 79 if caption is None: 80 caption = _('generic multi choice dialog') 81 82 if single_selection: 83 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg, style = wx.LC_SINGLE_SEL) 84 else: 85 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg) 86 87 dlg.refresh_callback = refresh_callback 88 dlg.edit_callback = edit_callback 89 dlg.new_callback = new_callback 90 dlg.delete_callback = delete_callback 91 dlg.list_tooltip_callback = list_tooltip_callback 92 93 dlg.ignore_OK_button = ignore_OK_button 94 dlg.left_extra_button = left_extra_button 95 dlg.middle_extra_button = middle_extra_button 96 dlg.right_extra_button = right_extra_button 97 98 dlg.set_columns(columns = columns) 99 100 if refresh_callback is None: 101 dlg.set_string_items(items = choices) # list ctrl will refresh anyway if possible 102 dlg.set_column_widths() 103 104 if data is not None: 105 dlg.set_data(data = data) # can override data set if refresh_callback is not None 106 107 if selections is not None: 108 dlg.set_selections(selections = selections) 109 dlg.can_return_empty = can_return_empty 110 111 btn_pressed = dlg.ShowModal() 112 sels = dlg.get_selected_item_data(only_one = single_selection) 113 dlg.Destroy() 114 115 if btn_pressed == wx.ID_OK: 116 if can_return_empty and (sels is None): 117 return [] 118 return sels 119 120 return None
121 #---------------------------------------------------------------- 122 from Gnumed.wxGladeWidgets import wxgGenericListSelectorDlg 123
124 -class cGenericListSelectorDlg(wxgGenericListSelectorDlg.wxgGenericListSelectorDlg):
125 """A dialog holding a list and a few buttons to act on the items.""" 126 127 # FIXME: configurable callback on double-click action 128
129 - def __init__(self, *args, **kwargs):
130 131 try: 132 msg = kwargs['msg'] 133 del kwargs['msg'] 134 except KeyError: msg = None 135 136 wxgGenericListSelectorDlg.wxgGenericListSelectorDlg.__init__(self, *args, **kwargs) 137 138 self.message = msg 139 140 self.left_extra_button = None 141 self.middle_extra_button = None 142 self.right_extra_button = None 143 144 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled) 145 self.new_callback = None # called when NEW button pressed, no argument passed in 146 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in 147 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in 148 149 self.can_return_empty = False 150 self.ignore_OK_button = False # by default do show/use the OK button
151 #------------------------------------------------------------
152 - def set_columns(self, columns=None):
153 self._LCTRL_items.set_columns(columns = columns)
154 #------------------------------------------------------------
155 - def set_column_widths(self, widths=None):
156 self._LCTRL_items.set_column_widths(widths = widths)
157 #------------------------------------------------------------
158 - def set_string_items(self, items = None):
159 self._LCTRL_items.set_string_items(items = items) 160 self._LCTRL_items.set_column_widths() 161 self._LCTRL_items.Select(0)
162 #------------------------------------------------------------
163 - def set_selections(self, selections = None):
164 self._LCTRL_items.set_selections(selections = selections) 165 if selections is None: 166 return 167 if len(selections) == 0: 168 return 169 if self.ignore_OK_button: 170 return 171 self._BTN_ok.Enable(True) 172 self._BTN_ok.SetDefault()
173 #------------------------------------------------------------
174 - def set_data(self, data = None):
175 self._LCTRL_items.set_data(data = data)
176 #------------------------------------------------------------
177 - def get_selected_item_data(self, only_one=False):
178 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
179 #------------------------------------------------------------ 180 # event handlers 181 #------------------------------------------------------------
182 - def _on_list_item_selected(self, event):
183 if not self.__ignore_OK_button: 184 self._BTN_ok.SetDefault() 185 self._BTN_ok.Enable(True) 186 187 if self.edit_callback is not None: 188 self._BTN_edit.Enable(True) 189 190 if self.delete_callback is not None: 191 self._BTN_delete.Enable(True)
192 #------------------------------------------------------------
193 - def _on_list_item_deselected(self, event):
194 if self._LCTRL_items.get_selected_items(only_one=True) == -1: 195 if not self.can_return_empty: 196 self._BTN_cancel.SetDefault() 197 self._BTN_ok.Enable(False) 198 self._BTN_edit.Enable(False) 199 self._BTN_delete.Enable(False)
200 #------------------------------------------------------------
201 - def _on_new_button_pressed(self, event):
202 if not self.new_callback(): 203 self._LCTRL_items.SetFocus() 204 return 205 if self.refresh_callback is None: 206 self._LCTRL_items.SetFocus() 207 return 208 wx.BeginBusyCursor() 209 try: 210 self.refresh_callback(lctrl = self._LCTRL_items) 211 finally: 212 wx.EndBusyCursor() 213 self._LCTRL_items.set_column_widths() 214 self._LCTRL_items.SetFocus()
215 #------------------------------------------------------------
216 - def _on_edit_button_pressed(self, event):
217 # if the edit button *can* be pressed there are *supposed* 218 # to be both an item selected and an editor configured 219 if not self.edit_callback(self._LCTRL_items.get_selected_item_data(only_one=True)): 220 self._LCTRL_items.SetFocus() 221 return 222 if self.refresh_callback is None: 223 self._LCTRL_items.SetFocus() 224 return 225 wx.BeginBusyCursor() 226 try: 227 self.refresh_callback(lctrl = self._LCTRL_items) 228 finally: 229 wx.EndBusyCursor() 230 self._LCTRL_items.set_column_widths() 231 self._LCTRL_items.SetFocus()
232 #------------------------------------------------------------
233 - def _on_delete_button_pressed(self, event):
234 # if the delete button *can* be pressed there are *supposed* 235 # to be both an item selected and a deletor configured 236 item_data = self._LCTRL_items.get_selected_item_data(only_one=True) 237 if item_data is None: 238 self._LCTRL_items.SetFocus() 239 return 240 if not self.delete_callback(item_data): 241 self._LCTRL_items.SetFocus() 242 return 243 if self.refresh_callback is None: 244 self._LCTRL_items.SetFocus() 245 return 246 wx.BeginBusyCursor() 247 try: 248 self.refresh_callback(lctrl = self._LCTRL_items) 249 finally: 250 wx.EndBusyCursor() 251 self._LCTRL_items.set_column_widths() 252 self._LCTRL_items.SetFocus()
253 #------------------------------------------------------------
254 - def _on_left_extra_button_pressed(self, event):
255 if self.__left_extra_button_wants_list: 256 item_data = self._LCTRL_items 257 else: 258 item_data = self._LCTRL_items.get_selected_item_data(only_one=True) 259 if not self.__left_extra_button_callback(item_data): 260 self._LCTRL_items.SetFocus() 261 return 262 if self.refresh_callback is None: 263 self._LCTRL_items.SetFocus() 264 return 265 wx.BeginBusyCursor() 266 try: 267 self.refresh_callback(lctrl = self._LCTRL_items) 268 finally: 269 wx.EndBusyCursor() 270 self._LCTRL_items.set_column_widths() 271 self._LCTRL_items.SetFocus()
272 #------------------------------------------------------------
273 - def _on_middle_extra_button_pressed(self, event):
274 if self.__middle_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.__middle_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 - def _on_right_extra_button_pressed(self, event):
293 if self.__right_extra_button_wants_list: 294 item_data = self._LCTRL_items 295 else: 296 item_data = self._LCTRL_items.get_selected_item_data(only_one=True) 297 if not self.__right_extra_button_callback(item_data): 298 self._LCTRL_items.SetFocus() 299 return 300 if self.refresh_callback is None: 301 self._LCTRL_items.SetFocus() 302 return 303 wx.BeginBusyCursor() 304 try: 305 self.refresh_callback(lctrl = self._LCTRL_items) 306 finally: 307 wx.EndBusyCursor() 308 self._LCTRL_items.set_column_widths() 309 self._LCTRL_items.SetFocus()
310 #------------------------------------------------------------ 311 # properties 312 #------------------------------------------------------------
313 - def _set_ignore_OK_button(self, ignored):
314 self.__ignore_OK_button = ignored 315 if self.__ignore_OK_button: 316 self._BTN_ok.Hide() 317 self._BTN_ok.Enable(False) 318 else: 319 self._BTN_ok.Show() 320 if self._LCTRL_items.get_selected_items(only_one=True) == -1: 321 if self.can_return_empty: 322 self._BTN_ok.Enable(True) 323 else: 324 self._BTN_ok.Enable(False) 325 self._BTN_cancel.SetDefault()
326 327 ignore_OK_button = property(lambda x:x, _set_ignore_OK_button) 328 #------------------------------------------------------------
329 - def _set_left_extra_button(self, definition):
330 if definition is None: 331 self._BTN_extra_left.Enable(False) 332 self._BTN_extra_left.Hide() 333 self.__left_extra_button_callback = None 334 self.__left_extra_button_wants_list = False 335 return 336 337 if len(definition) == 3: 338 (label, tooltip, callback) = definition 339 wants_list = False 340 else: 341 (label, tooltip, callback, wants_list) = definition 342 343 if not callable(callback): 344 raise ValueError('<left extra button> callback is not a callable: %s' % callback) 345 self.__left_extra_button_callback = callback 346 self.__left_extra_button_wants_list = wants_list 347 self._BTN_extra_left.SetLabel(label) 348 self._BTN_extra_left.SetToolTipString(tooltip) 349 self._BTN_extra_left.Enable(True) 350 self._BTN_extra_left.Show()
351 352 left_extra_button = property(lambda x:x, _set_left_extra_button) 353 #------------------------------------------------------------
354 - def _set_middle_extra_button(self, definition):
355 if definition is None: 356 self._BTN_extra_middle.Enable(False) 357 self._BTN_extra_middle.Hide() 358 self.__middle_extra_button_callback = None 359 self.__middle_extra_button_wants_list = False 360 return 361 362 if len(definition) == 3: 363 (label, tooltip, callback) = definition 364 wants_list = False 365 else: 366 (label, tooltip, callback, wants_list) = definition 367 368 if not callable(callback): 369 raise ValueError('<middle extra button> callback is not a callable: %s' % callback) 370 self.__middle_extra_button_callback = callback 371 self.__middle_extra_button_wants_list = wants_list 372 self._BTN_extra_middle.SetLabel(label) 373 self._BTN_extra_middle.SetToolTipString(tooltip) 374 self._BTN_extra_middle.Enable(True) 375 self._BTN_extra_middle.Show()
376 377 middle_extra_button = property(lambda x:x, _set_middle_extra_button) 378 #------------------------------------------------------------
379 - def _set_right_extra_button(self, definition):
380 if definition is None: 381 self._BTN_extra_right.Enable(False) 382 self._BTN_extra_right.Hide() 383 self.__right_extra_button_callback = None 384 self.__right_extra_button_wants_list = False 385 return 386 387 if len(definition) == 3: 388 (label, tooltip, callback) = definition 389 wants_list = False 390 else: 391 (label, tooltip, callback, wants_list) = definition 392 393 if not callable(callback): 394 raise ValueError('<right extra button> callback is not a callable: %s' % callback) 395 self.__right_extra_button_callback = callback 396 self.__right_extra_button_wants_list = wants_list 397 self._BTN_extra_right.SetLabel(label) 398 self._BTN_extra_right.SetToolTipString(tooltip) 399 self._BTN_extra_right.Enable(True) 400 self._BTN_extra_right.Show()
401 402 right_extra_button = property(lambda x:x, _set_right_extra_button) 403 #------------------------------------------------------------
404 - def _get_new_callback(self):
405 return self.__new_callback
406
407 - def _set_new_callback(self, callback):
408 if callback is not None: 409 if self.refresh_callback is None: 410 raise ValueError('refresh callback must be set before new callback can be set') 411 if not callable(callback): 412 raise ValueError('<new> callback is not a callable: %s' % callback) 413 self.__new_callback = callback 414 415 if callback is None: 416 self._BTN_new.Enable(False) 417 self._BTN_new.Hide() 418 else: 419 self._BTN_new.Enable(True) 420 self._BTN_new.Show()
421 422 new_callback = property(_get_new_callback, _set_new_callback) 423 #------------------------------------------------------------
424 - def _get_edit_callback(self):
425 return self.__edit_callback
426
427 - def _set_edit_callback(self, callback):
428 if callback is not None: 429 if not callable(callback): 430 raise ValueError('<edit> callback is not a callable: %s' % callback) 431 self.__edit_callback = callback 432 433 if callback is None: 434 self._BTN_edit.Enable(False) 435 self._BTN_edit.Hide() 436 else: 437 self._BTN_edit.Enable(True) 438 self._BTN_edit.Show()
439 440 edit_callback = property(_get_edit_callback, _set_edit_callback) 441 #------------------------------------------------------------
442 - def _get_delete_callback(self):
443 return self.__delete_callback
444
445 - def _set_delete_callback(self, callback):
446 if callback is not None: 447 if self.refresh_callback is None: 448 raise ValueError('refresh callback must be set before delete callback can be set') 449 if not callable(callback): 450 raise ValueError('<delete> callback is not a callable: %s' % callback) 451 self.__delete_callback = callback 452 453 if callback is None: 454 self._BTN_delete.Enable(False) 455 self._BTN_delete.Hide() 456 else: 457 self._BTN_delete.Enable(True) 458 self._BTN_delete.Show()
459 460 delete_callback = property(_get_delete_callback, _set_delete_callback) 461 #------------------------------------------------------------
462 - def _get_refresh_callback(self):
463 return self.__refresh_callback
464
466 wx.BeginBusyCursor() 467 try: 468 self.refresh_callback(lctrl = self._LCTRL_items) 469 finally: 470 wx.EndBusyCursor() 471 self._LCTRL_items.set_column_widths()
472
473 - def _set_refresh_callback(self, callback):
474 if callback is not None: 475 if not callable(callback): 476 raise ValueError('<refresh> callback is not a callable: %s' % callback) 477 self.__refresh_callback = callback 478 if callback is not None: 479 wx.CallAfter(self._set_refresh_callback_helper)
480 481 refresh_callback = property(_get_refresh_callback, _set_refresh_callback) 482 #------------------------------------------------------------
483 - def _set_list_tooltip_callback(self, callback):
484 self._LCTRL_items.item_tooltip_callback = callback
485 486 list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback) 487 #def _get_tooltip(self, item): # inside a class 488 #def _get_tooltip(item): # outside a class 489 #------------------------------------------------------------
490 - def _set_message(self, message):
491 if message is None: 492 self._LBL_message.Hide() 493 return 494 self._LBL_message.SetLabel(message) 495 self._LBL_message.Show()
496 497 message = property(lambda x:x, _set_message)
498 #================================================================ 499 from Gnumed.wxGladeWidgets import wxgGenericListManagerPnl 500
501 -class cGenericListManagerPnl(wxgGenericListManagerPnl.wxgGenericListManagerPnl):
502 """A panel holding a generic multi-column list and action buttions.""" 503
504 - def __init__(self, *args, **kwargs):
505 506 try: 507 msg = kwargs['msg'] 508 del kwargs['msg'] 509 except KeyError: msg = None 510 511 wxgGenericListManagerPnl.wxgGenericListManagerPnl.__init__(self, *args, **kwargs) 512 513 if msg is None: 514 self._LBL_message.Hide() 515 else: 516 self._LBL_message.SetLabel(msg) 517 518 # new/edit/delete must return True/False to enable refresh 519 self.__new_callback = None # called when NEW button pressed, no argument passed in 520 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in 521 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in 522 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled) 523 524 self.__select_callback = None # called when an item is selected, data of topmost selected item passed in 525 526 self.left_extra_button = None 527 self.middle_extra_button = None 528 self.right_extra_button = None
529 #------------------------------------------------------------ 530 # external API 531 #------------------------------------------------------------
532 - def set_columns(self, columns=None):
533 self._LCTRL_items.set_columns(columns = columns)
534 #------------------------------------------------------------
535 - def set_string_items(self, items = None):
536 self._LCTRL_items.set_string_items(items = items) 537 self._LCTRL_items.set_column_widths() 538 539 if (items is None) or (len(items) == 0): 540 self._BTN_edit.Enable(False) 541 self._BTN_remove.Enable(False) 542 else: 543 self._LCTRL_items.Select(0)
544 #------------------------------------------------------------
545 - def set_selections(self, selections = None):
546 self._LCTRL_items.set_selections(selections = selections)
547 #------------------------------------------------------------
548 - def set_data(self, data = None):
549 self._LCTRL_items.set_data(data = data)
550 #------------------------------------------------------------
551 - def get_selected_item_data(self, only_one=False):
552 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
553 #------------------------------------------------------------ 554 # event handlers 555 #------------------------------------------------------------
556 - def _on_list_item_selected(self, event):
557 if self.edit_callback is not None: 558 self._BTN_edit.Enable(True) 559 if self.delete_callback is not None: 560 self._BTN_remove.Enable(True) 561 if self.__select_callback is not None: 562 item = self._LCTRL_items.get_selected_item_data(only_one=True) 563 self.__select_callback(item)
564 #------------------------------------------------------------
565 - def _on_list_item_deselected(self, event):
566 if self._LCTRL_items.get_selected_items(only_one=True) == -1: 567 self._BTN_edit.Enable(False) 568 self._BTN_remove.Enable(False) 569 if self.__select_callback is not None: 570 self.__select_callback(None)
571 #------------------------------------------------------------
572 - def _on_add_button_pressed(self, event):
573 if not self.new_callback(): 574 return 575 if self.refresh_callback is None: 576 return 577 wx.BeginBusyCursor() 578 try: 579 self.refresh_callback(lctrl = self._LCTRL_items) 580 finally: 581 wx.EndBusyCursor()
582 #------------------------------------------------------------
583 - def _on_list_item_activated(self, event):
584 if self.edit_callback is None: 585 return 586 self._on_edit_button_pressed(event)
587 #------------------------------------------------------------
588 - def _on_edit_button_pressed(self, event):
589 item = self._LCTRL_items.get_selected_item_data(only_one=True) 590 if item is None: 591 return 592 if not self.edit_callback(item): 593 return 594 if self.refresh_callback is None: 595 return 596 wx.BeginBusyCursor() 597 try: 598 self.refresh_callback(lctrl = self._LCTRL_items) 599 finally: 600 wx.EndBusyCursor()
601 #------------------------------------------------------------
602 - def _on_remove_button_pressed(self, event):
603 item = self._LCTRL_items.get_selected_item_data(only_one=True) 604 if item is None: 605 return 606 if not self.delete_callback(item): 607 return 608 if self.refresh_callback is None: 609 return 610 wx.BeginBusyCursor() 611 try: 612 self.refresh_callback(lctrl = self._LCTRL_items) 613 finally: 614 wx.EndBusyCursor()
615 #------------------------------------------------------------
616 - def _on_left_extra_button_pressed(self, event):
617 item_data = self._LCTRL_items.get_selected_item_data(only_one=True) 618 if not self.__left_extra_button_callback(item_data): 619 self._LCTRL_items.SetFocus() 620 return 621 if self.refresh_callback is None: 622 self._LCTRL_items.SetFocus() 623 return 624 wx.BeginBusyCursor() 625 try: 626 self.refresh_callback(lctrl = self._LCTRL_items) 627 finally: 628 wx.EndBusyCursor() 629 self._LCTRL_items.set_column_widths() 630 self._LCTRL_items.SetFocus()
631 #------------------------------------------------------------
632 - def _on_middle_extra_button_pressed(self, event):
633 item_data = self._LCTRL_items.get_selected_item_data(only_one=True) 634 if not self.__middle_extra_button_callback(item_data): 635 self._LCTRL_items.SetFocus() 636 return 637 if self.refresh_callback is None: 638 self._LCTRL_items.SetFocus() 639 return 640 wx.BeginBusyCursor() 641 try: 642 self.refresh_callback(lctrl = self._LCTRL_items) 643 finally: 644 wx.EndBusyCursor() 645 self._LCTRL_items.set_column_widths() 646 self._LCTRL_items.SetFocus()
647 #------------------------------------------------------------
648 - def _on_right_extra_button_pressed(self, event):
649 item_data = self._LCTRL_items.get_selected_item_data(only_one=True) 650 if not self.__right_extra_button_callback(item_data): 651 self._LCTRL_items.SetFocus() 652 return 653 if self.refresh_callback is None: 654 self._LCTRL_items.SetFocus() 655 return 656 wx.BeginBusyCursor() 657 try: 658 self.refresh_callback(lctrl = self._LCTRL_items) 659 finally: 660 wx.EndBusyCursor() 661 self._LCTRL_items.set_column_widths() 662 self._LCTRL_items.SetFocus()
663 #------------------------------------------------------------ 664 # properties 665 #------------------------------------------------------------
666 - def _get_new_callback(self):
667 return self.__new_callback
668
669 - def _set_new_callback(self, callback):
670 if callback is not None: 671 if not callable(callback): 672 raise ValueError('<new> callback is not a callable: %s' % callback) 673 self.__new_callback = callback 674 self._BTN_add.Enable(callback is not None)
675 676 new_callback = property(_get_new_callback, _set_new_callback) 677 #------------------------------------------------------------
678 - def _get_select_callback(self):
679 return self.__select_callback
680
681 - def _set_select_callback(self, callback):
682 if callback is not None: 683 if not callable(callback): 684 raise ValueError('<select> callback is not a callable: %s' % callback) 685 self.__select_callback = callback
686 687 select_callback = property(_get_select_callback, _set_select_callback) 688 #------------------------------------------------------------
689 - def _get_message(self):
690 return self._LBL_message.GetLabel()
691
692 - def _set_message(self, msg):
693 if msg is None: 694 self._LBL_message.Hide() 695 self._LBL_message.SetLabel(u'') 696 else: 697 self._LBL_message.SetLabel(msg) 698 self._LBL_message.Show() 699 self.Layout()
700 701 message = property(_get_message, _set_message) 702 #------------------------------------------------------------
703 - def _set_left_extra_button(self, definition):
704 if definition is None: 705 self._BTN_extra_left.Enable(False) 706 self._BTN_extra_left.Hide() 707 self.__left_extra_button_callback = None 708 return 709 710 (label, tooltip, callback) = definition 711 if not callable(callback): 712 raise ValueError('<left extra button> callback is not a callable: %s' % callback) 713 self.__left_extra_button_callback = callback 714 self._BTN_extra_left.SetLabel(label) 715 self._BTN_extra_left.SetToolTipString(tooltip) 716 self._BTN_extra_left.Enable(True) 717 self._BTN_extra_left.Show()
718 719 left_extra_button = property(lambda x:x, _set_left_extra_button) 720 #------------------------------------------------------------
721 - def _set_middle_extra_button(self, definition):
722 if definition is None: 723 self._BTN_extra_middle.Enable(False) 724 self._BTN_extra_middle.Hide() 725 self.__middle_extra_button_callback = None 726 return 727 728 (label, tooltip, callback) = definition 729 if not callable(callback): 730 raise ValueError('<middle extra button> callback is not a callable: %s' % callback) 731 self.__middle_extra_button_callback = callback 732 self._BTN_extra_middle.SetLabel(label) 733 self._BTN_extra_middle.SetToolTipString(tooltip) 734 self._BTN_extra_middle.Enable(True) 735 self._BTN_extra_middle.Show()
736 737 middle_extra_button = property(lambda x:x, _set_middle_extra_button) 738 #------------------------------------------------------------
739 - def _set_right_extra_button(self, definition):
740 if definition is None: 741 self._BTN_extra_right.Enable(False) 742 self._BTN_extra_right.Hide() 743 self.__right_extra_button_callback = None 744 return 745 746 (label, tooltip, callback) = definition 747 if not callable(callback): 748 raise ValueError('<right extra button> callback is not a callable: %s' % callback) 749 self.__right_extra_button_callback = callback 750 self._BTN_extra_right.SetLabel(label) 751 self._BTN_extra_right.SetToolTipString(tooltip) 752 self._BTN_extra_right.Enable(True) 753 self._BTN_extra_right.Show()
754 755 right_extra_button = property(lambda x:x, _set_right_extra_button)
756 #================================================================ 757 from Gnumed.wxGladeWidgets import wxgItemPickerDlg 758
759 -class cItemPickerDlg(wxgItemPickerDlg.wxgItemPickerDlg):
760
761 - def __init__(self, *args, **kwargs):
762 763 try: 764 msg = kwargs['msg'] 765 del kwargs['msg'] 766 except KeyError: 767 msg = None 768 769 wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs) 770 771 if msg is None: 772 self._LBL_msg.Hide() 773 else: 774 self._LBL_msg.SetLabel(msg) 775 776 self.allow_duplicate_picks = True 777 778 self._LCTRL_left.activate_callback = self.__pick_selected 779 self.__extra_button_callback = None 780 781 self._LCTRL_left.SetFocus()
782 #------------------------------------------------------------ 783 # external API 784 #------------------------------------------------------------
785 - def set_columns(self, columns=None, columns_right=None):
786 self._LCTRL_left.set_columns(columns = columns) 787 if columns_right is None: 788 self._LCTRL_right.set_columns(columns = columns) 789 else: 790 if len(columns_right) < len(columns): 791 cols = columns 792 else: 793 cols = columns_right[:len(columns)] 794 self._LCTRL_right.set_columns(columns = cols)
795 #------------------------------------------------------------
796 - def set_string_items(self, items = None):
797 self._LCTRL_left.set_string_items(items = items) 798 self._LCTRL_left.set_column_widths() 799 self._LCTRL_right.set_string_items() 800 801 self._BTN_left2right.Enable(False) 802 self._BTN_right2left.Enable(False)
803 #------------------------------------------------------------
804 - def set_selections(self, selections = None):
805 self._LCTRL_left.set_selections(selections = selections)
806 #------------------------------------------------------------
807 - def set_choices(self, choices=None, data=None):
808 self.set_string_items(items = choices) 809 if data is not None: 810 self.set_data(data = data)
811 #------------------------------------------------------------
812 - def set_picks(self, picks=None, data=None):
813 self._LCTRL_right.set_string_items(picks) 814 self._LCTRL_right.set_column_widths() 815 if data is not None: 816 self._LCTRL_right.set_data(data = data)
817 #------------------------------------------------------------
818 - def set_data(self, data = None):
819 self._LCTRL_left.set_data(data = data)
820 #------------------------------------------------------------
821 - def get_picks(self):
822 return self._LCTRL_right.get_item_data()
823 824 picks = property(get_picks, lambda x:x) 825 #------------------------------------------------------------
826 - def _set_extra_button(self, definition):
827 if definition is None: 828 self._BTN_extra.Enable(False) 829 self._BTN_extra.Hide() 830 self.__extra_button_callback = None 831 return 832 833 (label, tooltip, callback) = definition 834 if not callable(callback): 835 raise ValueError('<extra button> callback is not a callable: %s' % callback) 836 self.__extra_button_callback = callback 837 self._BTN_extra.SetLabel(label) 838 self._BTN_extra.SetToolTipString(tooltip) 839 self._BTN_extra.Enable(True) 840 self._BTN_extra.Show()
841 842 extra_button = property(lambda x:x, _set_extra_button) 843 #------------------------------------------------------------ 844 # internal helpers 845 #------------------------------------------------------------
846 - def __pick_selected(self, event=None):
847 if self._LCTRL_left.get_selected_items(only_one = True) == -1: 848 return 849 850 right_items = self._LCTRL_right.get_string_items() 851 right_data = self._LCTRL_right.get_item_data() 852 if right_data is None: 853 right_data = [] 854 855 selected_items = self._LCTRL_left.get_selected_string_items(only_one = False) 856 selected_data = self._LCTRL_left.get_selected_item_data(only_one = False) 857 858 if self.allow_duplicate_picks: 859 right_items.extend(selected_items) 860 right_data.extend(selected_data) 861 self._LCTRL_right.set_string_items(items = right_items) 862 self._LCTRL_right.set_data(data = right_data) 863 self._LCTRL_right.set_column_widths() 864 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount) 865 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data)) 866 return 867 868 for sel_item, sel_data in zip(selected_items, selected_data): 869 if sel_item in right_items: 870 continue 871 right_items.append(sel_item) 872 right_data.append(sel_data) 873 self._LCTRL_right.set_string_items(items = right_items) 874 self._LCTRL_right.set_data(data = right_data) 875 self._LCTRL_right.set_column_widths()
876 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount) 877 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data)) 878 #------------------------------------------------------------
879 - def __remove_selected_picks(self):
880 if self._LCTRL_right.get_selected_items(only_one = True) == -1: 881 return 882 883 for item_idx in self._LCTRL_right.get_selected_items(only_one = False): 884 self._LCTRL_right.remove_item(item_idx) 885 886 if self._LCTRL_right.GetItemCount() == 0: 887 self._BTN_right2left.Enable(False)
888 889 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount) 890 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data)) 891 #------------------------------------------------------------ 892 # event handlers 893 #------------------------------------------------------------
894 - def _on_left_list_item_selected(self, event):
895 self._BTN_left2right.Enable(True)
896 #------------------------------------------------------------
897 - def _on_left_list_item_deselected(self, event):
898 if self._LCTRL_left.get_selected_items(only_one = True) == -1: 899 self._BTN_left2right.Enable(False)
900 #------------------------------------------------------------
901 - def _on_right_list_item_selected(self, event):
902 self._BTN_right2left.Enable(True)
903 #------------------------------------------------------------
904 - def _on_right_list_item_deselected(self, event):
905 if self._LCTRL_right.get_selected_items(only_one = True) == -1: 906 self._BTN_right2left.Enable(False)
907 #------------------------------------------------------------
908 - def _on_button_left2right_pressed(self, event):
909 self.__pick_selected()
910 #------------------------------------------------------------
911 - def _on_button_right2left_pressed(self, event):
912 self.__remove_selected_picks()
913 #------------------------------------------------------------
914 - def _on_extra_button_pressed(self, event):
915 self.__extra_button_callback()
916 #------------------------------------------------------------
917 - def _set_left_item_tooltip_callback(self, callback):
918 self._LCTRL_left.item_tooltip_callback = callback
919 920 left_item_tooltip_callback = property(lambda x:x, _set_left_item_tooltip_callback) 921 #------------------------------------------------------------
922 - def _set_right_item_tooltip_callback(self, callback):
923 self._LCTRL_right.item_tooltip_callback = callback
924 925 right_item_tooltip_callback = property(lambda x:x, _set_right_item_tooltip_callback)
926 927 #================================================================
928 -class cReportListCtrl(wx.ListCtrl, listmixins.ListCtrlAutoWidthMixin, listmixins.ColumnSorterMixin):
929 930 # sorting: at set_string_items() time all items will be 931 # adorned with their initial row number as wxPython data, 932 # this is used later for a) sorting and b) to access 933 # GNUmed data objects associated with rows, 934 # the latter are ordered in initial row number order 935 # at set_data() time 936 937 map_item_idx2data_idx = wx.ListCtrl.GetItemData 938 939 sort_order_tags = { 940 True: u' [\u03b1\u0391 \u2192 \u03c9\u03A9]', 941 False: u' [\u03c9\u03A9 \u2192 \u03b1\u0391]' 942 } 943
944 - def __init__(self, *args, **kwargs):
945 946 self.debug = None 947 948 try: 949 kwargs['style'] = kwargs['style'] | wx.LC_REPORT 950 except KeyError: 951 kwargs['style'] = wx.LC_REPORT 952 953 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL) 954 955 wx.ListCtrl.__init__(self, *args, **kwargs) 956 listmixins.ListCtrlAutoWidthMixin.__init__(self) 957 958 # required for column sorting, MUST have this name 959 self._invalidate_sorting_metadata() # must be called after each (external/direct) list item update 960 listmixins.ColumnSorterMixin.__init__(self, 0) # must be called again after adding columns (why ?) 961 # for debugging sorting: 962 #self.Bind(wx.EVT_LIST_COL_CLICK, self._on_col_click, self) 963 964 self.__widths = None 965 self.__data = None 966 self.__activate_callback = None 967 self.__rightclick_callback = None 968 969 self.__item_tooltip_callback = None 970 self.__tt_last_item = None 971 self.__tt_static_part = _("""Select the items you want to work on. 972 973 A discontinuous selection may depend on your holding down a platform-dependent modifier key (<ctrl>, <alt>, etc) or key combination (eg. <ctrl-shift> or <ctrl-alt>) while clicking.""") 974 self.Bind(wx.EVT_MOTION, self._on_mouse_motion) 975 976 self.__next_line_to_search = 0 977 self.__search_data = None 978 self.__search_dlg = None 979 self.__searchable_cols = None 980 # self.Bind(wx.EVT_KILL_FOCUS, self._on_lost_focus) 981 self.Bind(wx.EVT_CHAR, self._on_char) 982 self.Bind(wx.EVT_FIND_CLOSE, self._on_search_dlg_closed) 983 self.Bind(wx.EVT_FIND, self._on_search_first_match) 984 self.Bind(wx.EVT_FIND_NEXT, self._on_search_next_match)
985 #------------------------------------------------------------ 986 # setters 987 #------------------------------------------------------------
988 - def set_columns(self, columns=None):
989 """(Re)define the columns. 990 991 Note that this will (have to) delete the items. 992 """ 993 self.ClearAll() 994 self.__tt_last_item = None 995 if columns is None: 996 return 997 for idx in range(len(columns)): 998 self.InsertColumn(idx, columns[idx]) 999 1000 self._invalidate_sorting_metadata()
1001 #------------------------------------------------------------
1002 - def set_column_widths(self, widths=None):
1003 """Set the column width policy. 1004 1005 widths = None: 1006 use previous policy if any or default policy 1007 widths != None: 1008 use this policy and remember it for later calls 1009 1010 This means there is no way to *revert* to the default policy :-( 1011 """ 1012 # explicit policy ? 1013 if widths is not None: 1014 self.__widths = widths 1015 for idx in range(len(self.__widths)): 1016 self.SetColumnWidth(col = idx, width = self.__widths[idx]) 1017 return 1018 1019 # previous policy ? 1020 if self.__widths is not None: 1021 for idx in range(len(self.__widths)): 1022 self.SetColumnWidth(col = idx, width = self.__widths[idx]) 1023 return 1024 1025 # default policy ! 1026 if self.GetItemCount() == 0: 1027 width_type = wx.LIST_AUTOSIZE_USEHEADER 1028 else: 1029 width_type = wx.LIST_AUTOSIZE 1030 for idx in range(self.GetColumnCount()): 1031 self.SetColumnWidth(col = idx, width = width_type)
1032 #------------------------------------------------------------
1033 - def set_string_items(self, items=None):
1034 """All item members must be unicode()able or None.""" 1035 1036 wx.BeginBusyCursor() 1037 self._invalidate_sorting_metadata() 1038 1039 # remove existing items 1040 loop = 0 1041 while True: 1042 if loop > 3: 1043 _log.debug('unable to delete list items after looping 3 times, continuing and hoping for the best') 1044 break 1045 loop += 1 1046 if self.debug is not None: 1047 _log.debug('[round %s] GetItemCount() before DeleteAllItems(): %s (%s, thread [%s])', loop, self.GetItemCount(), self.debug, thread.get_ident()) 1048 if not self.DeleteAllItems(): 1049 _log.debug('DeleteAllItems() failed (%s)', self.debug) 1050 item_count = self.GetItemCount() 1051 if self.debug is not None: 1052 _log.debug('GetItemCount() after DeleteAllItems(): %s (%s)', item_count, self.debug) 1053 if item_count == 0: 1054 break 1055 wx.SafeYield(None, True) 1056 _log.debug('GetItemCount() not 0 after DeleteAllItems() (%s)', self.debug) 1057 time.sleep(0.3) 1058 wx.SafeYield(None, True) 1059 1060 if items is None: 1061 self.data = None 1062 wx.EndBusyCursor() 1063 return 1064 1065 # insert new items 1066 for item in items: 1067 try: 1068 item[0] 1069 if not isinstance(item, basestring): 1070 is_numerically_iterable = True 1071 # do not iterate over individual chars in a string, however 1072 else: 1073 is_numerically_iterable = False 1074 except TypeError: 1075 is_numerically_iterable = False 1076 1077 if is_numerically_iterable: 1078 # cannot use errors='replace' since then 1079 # None/ints/unicode strings fail to get encoded 1080 col_val = unicode(item[0]) 1081 row_num = self.InsertStringItem(index = sys.maxint, label = col_val) 1082 for col_num in range(1, min(self.GetColumnCount(), len(item))): 1083 col_val = unicode(item[col_num]) 1084 self.SetStringItem(index = row_num, col = col_num, label = col_val) 1085 else: 1086 # cannot use errors='replace' since then None/ints/unicode strings fails to get encoded 1087 col_val = unicode(item) 1088 row_num = self.InsertStringItem(index = sys.maxint, label = col_val) 1089 1090 # set data to be a copy of items 1091 self.data = items 1092 1093 wx.EndBusyCursor()
1094 #------------------------------------------------------------
1095 - def set_data(self, data=None):
1096 """<data> assumed to be a list corresponding to the item indices""" 1097 if data is not None: 1098 item_count = self.GetItemCount() 1099 if len(data) != item_count: 1100 _log.debug('<data> length (%s) must be equal to number of list items (%s) (%s, thread [%s])', len(data), item_count, self.debug, thread.get_ident()) 1101 for item_idx in range(len(data)): 1102 self.SetItemData(item_idx, item_idx) 1103 self.__data = data 1104 self.__tt_last_item = None 1105 # string data (rows/visible list items) not modified, 1106 # so no need to call _update_sorting_metadata 1107 return
1108
1109 - def _get_data(self):
1110 # slower than "return self.__data" but helps with detecting 1111 # problems with len(__data)<>self.GetItemCount() 1112 return self.get_item_data() # returns all data if item_idx is None
1113 1114 data = property(_get_data, set_data) 1115 #------------------------------------------------------------
1116 - def set_selections(self, selections=None):
1117 self.Select(0, on = 0) 1118 if selections is None: 1119 return 1120 for idx in selections: 1121 self.Select(idx = idx, on = 1)
1122
1123 - def __get_selections(self):
1124 if self.__is_single_selection: 1125 return [self.GetFirstSelected()] 1126 selections = [] 1127 idx = self.GetFirstSelected() 1128 while idx != -1: 1129 selections.append(idx) 1130 idx = self.GetNextSelected(idx) 1131 return selections
1132 1133 selections = property(__get_selections, set_selections) 1134 #------------------------------------------------------------ 1135 # getters 1136 #------------------------------------------------------------
1137 - def get_column_labels(self):
1138 labels = [] 1139 for col_idx in self.GetColumnCount(): 1140 col = self.GetColumn(col = col_idx) 1141 labels.append(col.GetText()) 1142 return labels
1143 #------------------------------------------------------------
1144 - def get_item(self, item_idx=None):
1145 if item_idx is not None: 1146 return self.GetItem(item_idx)
1147 #------------------------------------------------------------
1148 - def get_items(self):
1149 return [ self.GetItem(item_idx) for item_idx in range(self.GetItemCount()) ]
1150 #------------------------------------------------------------
1151 - def get_string_items(self):
1152 return [ self.GetItemText(item_idx) for item_idx in range(self.GetItemCount()) ]
1153 #------------------------------------------------------------
1154 - def get_selected_items(self, only_one=False):
1155 1156 if self.__is_single_selection or only_one: 1157 return self.GetFirstSelected() 1158 1159 items = [] 1160 idx = self.GetFirstSelected() 1161 while idx != -1: 1162 items.append(idx) 1163 idx = self.GetNextSelected(idx) 1164 1165 return items
1166 #------------------------------------------------------------
1167 - def get_selected_string_items(self, only_one=False):
1168 1169 if self.__is_single_selection or only_one: 1170 return self.GetItemText(self.GetFirstSelected()) 1171 1172 items = [] 1173 idx = self.GetFirstSelected() 1174 while idx != -1: 1175 items.append(self.GetItemText(idx)) 1176 idx = self.GetNextSelected(idx) 1177 1178 return items
1179 #------------------------------------------------------------
1180 - def get_item_data(self, item_idx = None):
1181 if self.__data is None: # this isn't entirely clean 1182 return None 1183 1184 if item_idx is not None: 1185 return self.__data[self.map_item_idx2data_idx(item_idx)] 1186 1187 # if <idx> is None return all data up to item_count, 1188 # in case of len(__data) <> self.GetItemCount() this 1189 # gives the chance to figure out what is going on 1190 return [ self.__data[self.map_item_idx2data_idx(item_idx)] for item_idx in range(self.GetItemCount()) ]
1191 #------------------------------------------------------------
1192 - def get_selected_item_data(self, only_one=False):
1193 1194 if self.__is_single_selection or only_one: 1195 if self.__data is None: 1196 return None 1197 idx = self.GetFirstSelected() 1198 if idx == -1: 1199 return None 1200 return self.__data[self.map_item_idx2data_idx(idx)] 1201 1202 data = [] 1203 if self.__data is None: 1204 return data 1205 idx = self.GetFirstSelected() 1206 while idx != -1: 1207 data.append(self.__data[self.map_item_idx2data_idx(idx)]) 1208 idx = self.GetNextSelected(idx) 1209 1210 return data
1211 #------------------------------------------------------------
1212 - def deselect_selected_item(self):
1213 self.Select(idx = self.GetFirstSelected(), on = 0)
1214 #------------------------------------------------------------
1215 - def remove_item(self, item_idx=None):
1216 # do NOT remove the corresponding data because even if 1217 # the item pointing to this data instance is gone all 1218 # other items will still point to their corresponding 1219 # *initial* row numbers 1220 #if self.__data is not None: 1221 # del self.__data[self.map_item_idx2data_idx(item_idx)] 1222 self.DeleteItem(item_idx) 1223 self.__tt_last_item = None 1224 self._invalidate_sorting_metadata()
1225 #------------------------------------------------------------ 1226 # event handlers 1227 #------------------------------------------------------------
1228 - def _on_list_item_activated(self, event):
1229 event.Skip() 1230 if self.__activate_callback is not None: 1231 self.__activate_callback(event)
1232 #------------------------------------------------------------
1233 - def _on_list_item_rightclicked(self, event):
1234 event.Skip() 1235 if self.__rightclick_callback is not None: 1236 self.__rightclick_callback(event)
1237 #------------------------------------------------------------
1238 - def _on_char(self, evt):
1239 1240 if evt.GetModifiers() != wx.MOD_CMD: 1241 evt.Skip() 1242 return 1243 1244 if unichr(evt.GetRawKeyCode()) != u'f': 1245 evt.Skip() 1246 return 1247 1248 if self.__search_dlg is not None: 1249 self.__search_dlg.Close() 1250 return 1251 1252 if self.__searchable_cols is None: 1253 self.searchable_columns = None 1254 1255 if len(self.__searchable_cols) == 0: 1256 return 1257 1258 if self.__search_data is None: 1259 self.__search_data = wx.FindReplaceData() 1260 self.__search_dlg = wx.FindReplaceDialog ( 1261 self, 1262 self.__search_data, 1263 _('Search in list'), 1264 wx.FR_NOUPDOWN | wx.FR_NOMATCHCASE | wx.FR_NOWHOLEWORD 1265 ) 1266 self.__search_dlg.Show(True)
1267 #------------------------------------------------------------
1268 - def _on_mouse_motion(self, event):
1269 """Update tooltip on mouse motion. 1270 1271 for s in dir(wx): 1272 if s.startswith('LIST_HITTEST'): 1273 print s, getattr(wx, s) 1274 1275 LIST_HITTEST_ABOVE 1 1276 LIST_HITTEST_BELOW 2 1277 LIST_HITTEST_NOWHERE 4 1278 LIST_HITTEST_ONITEM 672 1279 LIST_HITTEST_ONITEMICON 32 1280 LIST_HITTEST_ONITEMLABEL 128 1281 LIST_HITTEST_ONITEMRIGHT 256 1282 LIST_HITTEST_ONITEMSTATEICON 512 1283 LIST_HITTEST_TOLEFT 1024 1284 LIST_HITTEST_TORIGHT 2048 1285 """ 1286 item_idx, where_flag = self.HitTest(wx.Point(event.X, event.Y)) 1287 1288 # pointer on item related area at all ? 1289 if where_flag not in [ 1290 wx.LIST_HITTEST_ONITEMLABEL, 1291 wx.LIST_HITTEST_ONITEMICON, 1292 wx.LIST_HITTEST_ONITEMSTATEICON, 1293 wx.LIST_HITTEST_ONITEMRIGHT, 1294 wx.LIST_HITTEST_ONITEM 1295 ]: 1296 self.__tt_last_item = None # not on any item 1297 self.SetToolTipString(self.__tt_static_part) 1298 return 1299 1300 # same item as last time around ? 1301 if self.__tt_last_item == item_idx: 1302 return 1303 1304 # remeber the new item we are on 1305 self.__tt_last_item = item_idx 1306 1307 # HitTest() can return -1 if it so pleases, meaning that no item 1308 # was hit or else that maybe there aren't any items (empty list) 1309 if item_idx == wx.NOT_FOUND: 1310 self.SetToolTipString(self.__tt_static_part) 1311 return 1312 1313 # do we *have* item data ? 1314 if self.__data is None: 1315 self.SetToolTipString(self.__tt_static_part) 1316 return 1317 1318 # under some circumstances the item_idx returned 1319 # by HitTest() may be out of bounds with respect to 1320 # self.__data, this hints at a sync problem between 1321 # setting display items and associated data 1322 if ( 1323 (item_idx > (len(self.__data) - 1)) 1324 or 1325 (item_idx < -1) 1326 ): 1327 self.SetToolTipString(self.__tt_static_part) 1328 print "*************************************************************" 1329 print "GNUmed has detected an inconsistency with list item tooltips." 1330 print "" 1331 print "This is not a big problem and you can keep working." 1332 print "" 1333 print "However, please send us the following so we can fix GNUmed:" 1334 print "" 1335 print "item idx: %s" % item_idx 1336 print 'where flag: %s' % where_flag 1337 print 'data list length: %s' % len(self.__data) 1338 print "*************************************************************" 1339 return 1340 1341 dyna_tt = None 1342 if self.__item_tooltip_callback is not None: 1343 dyna_tt = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(item_idx)]) 1344 1345 if dyna_tt is None: 1346 self.SetToolTipString(self.__tt_static_part) 1347 return 1348 1349 self.SetToolTipString(dyna_tt)
1350 #------------------------------------------------------------ 1351 # search related methods 1352 #------------------------------------------------------------
1353 - def _on_search_dlg_closed(self, evt):
1354 self.__search_dlg.Destroy() 1355 self.__search_dlg = None
1356 #------------------------------------------------------------ 1357 # def _on_lost_focus(self, evt): 1358 # evt.Skip() 1359 # if self.__search_dlg is None: 1360 # return 1361 ## print self.FindFocus() 1362 ## print self.__search_dlg 1363 # #self.__search_dlg.Close() 1364 #------------------------------------------------------------
1365 - def __on_search_match(self, search_term):
1366 for row_idx in range(self.__next_line_to_search, self.ItemCount): 1367 for col_idx in range(self.ColumnCount): 1368 if col_idx not in self.__searchable_cols: 1369 continue 1370 col_val = self.GetItem(row_idx, col_idx).GetText() 1371 if regex.search(search_term, col_val, regex.U | regex.I) is not None: 1372 self.Select(row_idx) 1373 self.EnsureVisible(row_idx) 1374 if row_idx == self.ItemCount - 1: 1375 # wrap around 1376 self.__next_line_to_search = 0 1377 else: 1378 self.__next_line_to_search = row_idx + 1 1379 return True 1380 # wrap around 1381 self.__next_line_to_search = 0 1382 return False
1383 #------------------------------------------------------------
1384 - def _on_search_first_match(self, evt):
1385 self.__on_search_match(evt.GetFindString())
1386 #------------------------------------------------------------
1387 - def _on_search_next_match(self, evt):
1388 self.__on_search_match(evt.GetFindString())
1389 #------------------------------------------------------------ 1390 # properties 1391 #------------------------------------------------------------
1392 - def _get_activate_callback(self):
1393 return self.__activate_callback
1394
1395 - def _set_activate_callback(self, callback):
1396 if callback is None: 1397 self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED) 1398 else: 1399 if not callable(callback): 1400 raise ValueError('<activate> callback is not a callable: %s' % callback) 1401 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated) 1402 self.__activate_callback = callback
1403 1404 activate_callback = property(_get_activate_callback, _set_activate_callback) 1405 #------------------------------------------------------------
1406 - def _get_rightclick_callback(self):
1407 return self.__rightclick_callback
1408
1409 - def _set_rightclick_callback(self, callback):
1410 if callback is None: 1411 self.Unbind(wx.EVT_LIST_ITEM_RIGHT_CLICK) 1412 else: 1413 if not callable(callback): 1414 raise ValueError('<rightclick> callback is not a callable: %s' % callback) 1415 self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self._on_list_item_rightclicked) 1416 self.__rightclick_callback = callback
1417 1418 rightclick_callback = property(_get_rightclick_callback, _set_rightclick_callback) 1419 #------------------------------------------------------------
1420 - def _set_item_tooltip_callback(self, callback):
1421 if callback is not None: 1422 if not callable(callback): 1423 raise ValueError('<item_tooltip> callback is not a callable: %s' % callback) 1424 self.__item_tooltip_callback = callback
1425 1426 # the callback must be a function which takes a single argument 1427 # the argument is the data for the item the tooltip is on 1428 # the callback must return None if no item tooltip is to be shown 1429 # otherwise it must return a string (possibly with \n) 1430 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback) 1431 #------------------------------------------------------------
1432 - def _set_searchable_cols(self, cols):
1433 # zero-based list of which columns to search 1434 if cols is None: 1435 self.__searchable_cols = range(self.ColumnCount) 1436 return 1437 # weed out columns to be searched which 1438 # don't exist and uniquify them 1439 new_cols = {} 1440 for col in cols: 1441 if col < self.ColumnCount: 1442 new_cols[col] = True 1443 self.__searchable_cols = new_cols.keys()
1444 1445 searchable_columns = property(lambda x:x, _set_searchable_cols) 1446 #------------------------------------------------------------ 1447 # ColumnSorterMixin API 1448 #------------------------------------------------------------
1449 - def GetListCtrl(self):
1450 if self.itemDataMap is None: 1451 self._update_sorting_metadata() 1452 return self # required
1453 #------------------------------------------------------------
1454 - def OnSortOrderChanged(self):
1455 self._cleanup_column_headers() 1456 # annotate sort column 1457 col_idx, is_ascending = self.GetSortState() 1458 col_state = self.GetColumn(col_idx) 1459 col_state.m_text += self.sort_order_tags[is_ascending] 1460 self.SetColumn(col_idx, col_state)
1461 #------------------------------------------------------------
1462 - def _generate_map_for_sorting(self):
1463 dict2sort = {} 1464 item_count = self.GetItemCount() 1465 if item_count == 0: 1466 return dict2sort 1467 col_count = self.GetColumnCount() 1468 for item_idx in range(item_count): 1469 dict2sort[item_idx] = () 1470 if col_count == 0: 1471 continue 1472 for col_idx in range(col_count): 1473 dict2sort[item_idx] += (self.GetItem(item_idx, col_idx).GetText(), ) 1474 1475 return dict2sort
1476 #------------------------------------------------------------
1477 - def _cleanup_column_headers(self):
1478 for col_idx in range(self.ColumnCount): 1479 col_state = self.GetColumn(col_idx) 1480 if col_state.m_text.endswith(self.sort_order_tags[True]): 1481 col_state.m_text = col_state.m_text[:-len(self.sort_order_tags[True])] 1482 if col_state.m_text.endswith(self.sort_order_tags[False]): 1483 col_state.m_text = col_state.m_text[:-len(self.sort_order_tags[False])] 1484 self.SetColumn(col_idx, col_state)
1485 #------------------------------------------------------------
1487 self.itemDataMap = None 1488 self.SetColumnCount(self.GetColumnCount()) 1489 self._cleanup_column_headers()
1490 #------------------------------------------------------------
1491 - def _update_sorting_metadata(self):
1492 self.itemDataMap = self._generate_map_for_sorting()
1493 #------------------------------------------------------------
1494 - def _on_col_click(self, event):
1495 # for debugging: 1496 # print "column clicked : %s" % (event.GetColumn()) 1497 # column, order = self.GetSortState() 1498 # print "column %s sort %s" % (column, order) 1499 # print self._colSortFlag 1500 # print self.itemDataMap 1501 event.Skip()
1502 1503 #================================================================ 1504 # main 1505 #---------------------------------------------------------------- 1506 if __name__ == '__main__': 1507 1508 if len(sys.argv) < 2: 1509 sys.exit() 1510 1511 if sys.argv[1] != 'test': 1512 sys.exit() 1513 1514 sys.path.insert(0, '../../') 1515 1516 from Gnumed.pycommon import gmI18N 1517 gmI18N.activate_locale() 1518 gmI18N.install_domain() 1519 1520 #------------------------------------------------------------
1521 - def test_wxMultiChoiceDialog():
1522 app = wx.PyWidgetTester(size = (400, 500)) 1523 dlg = wx.MultiChoiceDialog ( 1524 parent = None, 1525 message = 'test message', 1526 caption = 'test caption', 1527 choices = ['a', 'b', 'c', 'd', 'e'] 1528 ) 1529 dlg.ShowModal() 1530 sels = dlg.GetSelections() 1531 print "selected:" 1532 for sel in sels: 1533 print sel
1534 #------------------------------------------------------------
1535 - def test_get_choices_from_list():
1536 1537 def edit(argument): 1538 print "editor called with:" 1539 print argument
1540 1541 def refresh(lctrl): 1542 choices = ['a', 'b', 'c'] 1543 lctrl.set_string_items(choices) 1544 1545 app = wx.PyWidgetTester(size = (200, 50)) 1546 chosen = get_choices_from_list ( 1547 # msg = 'select a health issue\nfrom the list below\n', 1548 caption = 'select health issues', 1549 #choices = [['D.M.II', '4'], ['MS', '3'], ['Fraktur', '2']], 1550 #columns = ['issue', 'no of episodes'] 1551 columns = ['issue'], 1552 refresh_callback = refresh 1553 #, edit_callback = edit 1554 ) 1555 print "chosen:" 1556 print chosen 1557 #------------------------------------------------------------
1558 - def test_item_picker_dlg():
1559 app = wx.PyWidgetTester(size = (200, 50)) 1560 dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:') 1561 dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy']) 1562 #dlg.set_columns(['Plugins'], []) 1563 dlg.set_string_items(['patient', 'emr', 'docs']) 1564 result = dlg.ShowModal() 1565 print result 1566 print dlg.get_picks()
1567 #------------------------------------------------------------ 1568 #test_get_choices_from_list() 1569 #test_wxMultiChoiceDialog() 1570 test_item_picker_dlg() 1571 1572 #================================================================ 1573 # 1574