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