Gnumed.wxpython.gmListWidgets

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

# Source Code for Module Gnumed.wxpython.gmListWidgets

       1  __doc__ = """GNUmed list controls and widgets.
       2
       3  TODO:
       4
       5          From: Rob McMullen <rob.mcmullen@gmail.com>
       6          To: wxPython-users@lists.wxwidgets.org
       7          Subject: Re: [wxPython-users] ANN: ColumnSizer mixin for ListCtrl
       8
       9          Thanks for all the suggestions, on and off line.  There's an update
      10          with a new name (ColumnAutoSizeMixin) and better sizing algorithm at:
      11
      12          http://trac.flipturn.org/browser/trunk/peppy/lib/column_autosize.py
      13
      14          sorting: http://code.activestate.com/recipes/426407/
      15  """
      16  #================================================================
      17  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
      18  __license__ = "GPL v2 or later"
      19
      20
      21  import sys
      22  import types
      23  import logging
      24  import threading
      25  import time
      26  import locale
      27  import os
      28  import io
      29  import csv
      30  import re as regex
      31  import datetime as pydt
      32
      33
      34  import wx
      35  import wx.lib.mixins.listctrl as listmixins
      36
      37
      38  if __name__ == '__main__':
      39          sys.path.insert(0, '../../')
      40  from Gnumed.pycommon import gmTools
      41  from Gnumed.pycommon import gmDispatcher
      42
      43
      44  _log = logging.getLogger('gm.list_ui')
      45  #================================================================
      46  # FIXME: configurable callback on double-click action
      47
      48 -def get_choices_from_list (
      49                          parent=None,
      50                          msg=None,
      51                          caption=None,
      52                          columns=None,
      53                          choices=None,
      54                          data=None,
      55                          selections=None,
      56                          edit_callback=None,
      57                          new_callback=None,
      58                          delete_callback=None,
      59                          refresh_callback=None,
      60                          single_selection=False,
      61                          can_return_empty=False,
      62                          ignore_OK_button=False,
      63                          left_extra_button=None,
      64                          middle_extra_button=None,
      65                          right_extra_button=None,
      66                          list_tooltip_callback=None):
      67          """Let user select item(s) from a list.
      68
      69          - new_callback: ()
      70          - edit_callback: (item data)
      71          - delete_callback: (item data)
      72          - refresh_callback: (listctrl)
      73          - list_tooltip_callback: (item data)
      74
      75          - left/middle/right_extra_button: (label, tooltip, <callback> [, wants_list_ctrl])
      76                  <wants_list_ctrl> is optional
      77                  <callback> is called with item_data (or listctrl) as the only argument
      78                  if <callback> returns TRUE, the listctrl will be refreshed, if a refresh_callback is available
      79
      80          returns:
      81                  on [CANCEL]: None
      82                  on [OK]:
      83                          if any items selected:
      84                                  if single_selection:
      85                                          the data of the selected item
      86                                  else:
      87                                          list of data of selected items
      88                          else:
      89                                  if can_return_empty is True AND [OK] button was pressed:
      90                                          empty list
      91                                  else:
      92                                          None
      93          """
      94          if caption is None:
      95                  caption = u'GMd: ' + _('generic multi choice dialog')
      96          else:
      97                  if not caption.startswith('GMd: '):
      98                          caption = 'GMd: %s' % caption
      99
     100          dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg, single_selection = single_selection)
     101          dlg.refresh_callback = refresh_callback
     102          dlg.edit_callback = edit_callback
     103          dlg.new_callback = new_callback
     104          dlg.delete_callback = delete_callback
     105          dlg.list_tooltip_callback = list_tooltip_callback
     106
     107          dlg.can_return_empty = can_return_empty
     108          dlg.ignore_OK_button = ignore_OK_button
     109          dlg.left_extra_button = left_extra_button
     110          dlg.middle_extra_button = middle_extra_button
     111          dlg.right_extra_button = right_extra_button
     112
     113          dlg.set_columns(columns = columns)
     114
     115          if refresh_callback is None:
     116                  dlg.set_string_items(items = choices)           # list ctrl will refresh anyway if possible
     117                  dlg.set_column_widths()
     118
     119          if data is not None:
     120                  dlg.set_data(data = data)                                       # can override data set if refresh_callback is not None
     121
     122          if selections is not None:
     123                  if single_selection:
     124                          dlg.set_selections(selections = selections[:1])
     125                  else:
     126                          dlg.set_selections(selections = selections)
     127
     128          btn_pressed = dlg.ShowModal()
     129          sels = dlg.get_selected_item_data(only_one = single_selection)
     130          dlg.Destroy()
     131
     132          if btn_pressed == wx.ID_OK:
     133                  if can_return_empty and (sels is None):
     134                          return []
     135                  return sels
     136
     137          return None
     138
     139  #----------------------------------------------------------------
     140  from Gnumed.wxGladeWidgets import wxgGenericListSelectorDlg
     141
     142 -class cGenericListSelectorDlg(wxgGenericListSelectorDlg.wxgGenericListSelectorDlg):
     143          """A dialog holding a list and a few buttons to act on the items."""
     144
     145 -        def __init__(self, *args, **kwargs):
     146
     147                  try:
     148                          msg = kwargs['msg']
     149                          del kwargs['msg']
     150                  except KeyError:
     151                          msg = None
     152
     153                  try:
     154                          title = kwargs['title']
     155                          if not title.startswith('GMd: '):
     156                                  kwargs['title'] = 'GMd: %s' % title
     157                  except KeyError:
     158                          kwargs['title'] = 'GMd: %s' % self.__class__.__name__
     159
     160                  try:
     161                          single_selection = kwargs['single_selection']
     162                          del kwargs['single_selection']
     163                  except KeyError:
     164                          single_selection = False
     165
     166                  wxgGenericListSelectorDlg.wxgGenericListSelectorDlg.__init__(self, *args, **kwargs)
     167
     168                  self.message = msg
     169
     170                  self.left_extra_button = None
     171                  self.middle_extra_button = None
     172                  self.right_extra_button = None
     173
     174                  self.refresh_callback = None                    # called when new/edit/delete callbacks return True (IOW were not cancelled)
     175                  self.new_callback = None                                # called when NEW button pressed, no argument passed in
     176                  self.edit_callback = None                               # called when EDIT button pressed, data of topmost selected item passed in
     177                  self.delete_callback = None                             # called when DELETE button pressed, data of topmost selected item passed in
     178
     179                  self.can_return_empty = False
     180                  self.ignore_OK_button = False                   # by default do show/use the OK button
     181
     182                  self.select_callback = None                             # called when an item is selected, data of topmost selected item passed in
     183                  self._LCTRL_items.select_callback = self._on_list_item_selected_in_listctrl
     184                  if single_selection:
     185                          self._LCTRL_items.SetSingleStyle(wx.LC_SINGLE_SEL, add = True)
     186
     187          #------------------------------------------------------------
     188 -        def set_columns(self, columns=None):
     189                  self._LCTRL_items.set_columns(columns = columns)
     190
     191          #------------------------------------------------------------
     192 -        def set_column_widths(self, widths=None):
     193                  self._LCTRL_items.set_column_widths(widths = widths)
     194
     195          #------------------------------------------------------------
     196 -        def set_string_items(self, items=None, reshow=True):
     197                  self._LCTRL_items.set_string_items(items = items, reshow = reshow)
     198                  self._LCTRL_items.set_column_widths()
     199                  #self._LCTRL_items.Select(0)
     200
     201          #------------------------------------------------------------
     202 -        def set_selections(self, selections = None):
     203                  self._LCTRL_items.set_selections(selections = selections)
     204                  if selections is None:
     205                          return
     206                  if len(selections) == 0:
     207                          return
     208                  if self.ignore_OK_button:
     209                          return
     210                  self._BTN_ok.Enable(True)
     211                  self._BTN_ok.SetDefault()
     212
     213          #------------------------------------------------------------
     214 -        def set_data(self, data = None):
     215                  self._LCTRL_items.set_data(data = data)
     216
     217          #------------------------------------------------------------
     218 -        def get_selected_item_data(self, only_one=False):
     219                  return self._LCTRL_items.get_selected_item_data(only_one=only_one)
     220
     221          #------------------------------------------------------------
     222          # event handlers
     223          #------------------------------------------------------------
     224 -        def _on_list_item_deselected(self, event):
     225                  if self._LCTRL_items.get_selected_items(only_one=True) == -1:
     226                          if not self.can_return_empty:
     227                                  self._BTN_cancel.SetDefault()
     228                                  self._BTN_ok.Enable(False)
     229                          self._BTN_edit.Enable(False)
     230                          self._BTN_delete.Enable(False)
     231
     232                  event.Skip()
     233
     234          #------------------------------------------------------------
     235 -        def _on_new_button_pressed(self, event):
     236                  self.__do_insert()
     237                  event.Skip()
     238
     239          #------------------------------------------------------------
     240 -        def _on_edit_button_pressed(self, event):
     241                  # if the edit button *can* be pressed there are *supposed*
     242                  # to be both an item selected and an editor configured
     243                  self.__do_edit()
     244                  event.Skip()
     245
     246          #------------------------------------------------------------
     247 -        def _on_delete_button_pressed(self, event):
     248                  # if the delete button *can* be pressed there are *supposed*
     249                  # to be both an item selected and a deletor configured
     250
     251                  no_items = len(self._LCTRL_items.get_selected_items(only_one = False))
     252                  if no_items == 0:
     253                          return
     254
     255                  if no_items > 1:
     256                          style = wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION | wx.STAY_ON_TOP
     257                          title = _('Deleting list items')
     258                          question = _(
     259                                  'You have selected %s list items.\n'
     260                                  '\n'
     261                                  'Really delete all %s items ?'
     262                          ) % (no_items, no_items)
     263                          dlg = wx.MessageDialog(None, question, title, style)
     264                          btn_pressed = dlg.ShowModal()
     265                          dlg.Destroy()
     266                          if btn_pressed == wx.ID_NO:
     267                                  self._LCTRL_items.SetFocus()
     268                                  return
     269                          if btn_pressed == wx.ID_CANCEL:
     270                                  self._LCTRL_items.SetFocus()
     271                                  return
     272
     273                  self.__do_delete()
     274                  event.Skip()
     275
     276          #------------------------------------------------------------
     277 -        def _on_left_extra_button_pressed(self, event):
     278                  if self.__left_extra_button_wants_list:
     279                          item_data = self._LCTRL_items
     280                  else:
     281                          item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
     282                  if not self.__left_extra_button_callback(item_data):
     283                          self._LCTRL_items.SetFocus()
     284                          return
     285                  if self.__refresh_callback is None:
     286                          self._LCTRL_items.SetFocus()
     287                          return
     288                  wx.BeginBusyCursor()
     289                  try:
     290                          self.__refresh_callback(lctrl = self._LCTRL_items)
     291                  finally:
     292                          wx.EndBusyCursor()
     293                  self._LCTRL_items.set_column_widths()
     294                  self._LCTRL_items.SetFocus()
     295
     296          #------------------------------------------------------------
     297 -        def _on_middle_extra_button_pressed(self, event):
     298                  if self.__middle_extra_button_wants_list:
     299                          item_data = self._LCTRL_items
     300                  else:
     301                          item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
     302                  if not self.__middle_extra_button_callback(item_data):
     303                          self._LCTRL_items.SetFocus()
     304                          return
     305                  if self.__refresh_callback is None:
     306                          self._LCTRL_items.SetFocus()
     307                          return
     308                  wx.BeginBusyCursor()
     309                  try:
     310                          self.__refresh_callback(lctrl = self._LCTRL_items)
     311                  finally:
     312                          wx.EndBusyCursor()
     313                  self._LCTRL_items.set_column_widths()
     314                  self._LCTRL_items.SetFocus()
     315
     316          #------------------------------------------------------------
     317 -        def _on_right_extra_button_pressed(self, event):
     318                  if self.__right_extra_button_wants_list:
     319                          item_data = self._LCTRL_items
     320                  else:
     321                          item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
     322                  if not self.__right_extra_button_callback(item_data):
     323                          self._LCTRL_items.SetFocus()
     324                          return
     325                  if self.__refresh_callback is None:
     326                          self._LCTRL_items.SetFocus()
     327                          return
     328                  wx.BeginBusyCursor()
     329                  try:
     330                          self.__refresh_callback(lctrl = self._LCTRL_items)
     331                  finally:
     332                          wx.EndBusyCursor()
     333                  self._LCTRL_items.set_column_widths()
     334                  self._LCTRL_items.SetFocus()
     335
     336          #------------------------------------------------------------
     337          # internal helpers
     338          #------------------------------------------------------------
     339 -        def _on_list_item_selected_in_listctrl(self, event):
     340                  event.Skip()
     341                  if not self.__ignore_OK_button:
     342                          self._BTN_ok.SetDefault()
     343                          self._BTN_ok.Enable(True)
     344                  if self.edit_callback is not None:
     345                          self._BTN_edit.Enable(True)
     346                  if self.delete_callback is not None:
     347                          self._BTN_delete.Enable(True)
     348                  if self.__select_callback is not None:
     349                          item = self._LCTRL_items.get_selected_item_data(only_one = True)
     350                          self.__select_callback(item)
     351
     352          #------------------------------------------------------------
     353 -        def _on_delete_key_pressed_in_listctrl(self):
     354                  self.__do_delete()
     355
     356          #------------------------------------------------------------
     357 -        def __do_delete(self):
     358                  any_deleted = False
     359                  for item_data in self._LCTRL_items.get_selected_item_data(only_one = False):
     360                          if item_data is None:
     361                                  continue
     362                          if self.__delete_callback(item_data):
     363                                  any_deleted = True
     364
     365                  self._LCTRL_items.SetFocus()
     366
     367                  if any_deleted is False:
     368                          return
     369
     370                  if self.__refresh_callback is None:
     371                          return
     372
     373                  wx.BeginBusyCursor()
     374                  try:
     375                          self.__refresh_callback(lctrl = self._LCTRL_items)
     376                          self._LCTRL_items.set_column_widths()
     377                  finally:
     378                          wx.EndBusyCursor()
     379
     380          #------------------------------------------------------------
     381 -        def _on_edit_invoked_in_listctrl(self):
     382                  self.__do_edit()
     383
     384          #------------------------------------------------------------
     385 -        def __do_edit(self):
     386                  if not self.__edit_callback(self._LCTRL_items.get_selected_item_data(only_one = True)):
     387                          self._LCTRL_items.SetFocus()
     388                          return
     389                  if self.__refresh_callback is None:
     390                          self._LCTRL_items.SetFocus()
     391                          return
     392                  wx.BeginBusyCursor()
     393                  try:
     394                          self.__refresh_callback(lctrl = self._LCTRL_items)
     395                          self._LCTRL_items.set_column_widths()
     396                          self._LCTRL_items.SetFocus()
     397                  finally:
     398                          wx.EndBusyCursor()
     399
     400          #------------------------------------------------------------
     401 -        def _on_insert_key_pressed_in_listctrl(self):
     402                  self.__do_insert()
     403
     404          #------------------------------------------------------------
     405 -        def __do_insert(self):
     406                  if not self.__new_callback():
     407                          self._LCTRL_items.SetFocus()
     408                          return
     409                  if self.__refresh_callback is None:
     410                          self._LCTRL_items.SetFocus()
     411                          return
     412                  wx.BeginBusyCursor()
     413                  try:
     414                          self.__refresh_callback(lctrl = self._LCTRL_items)
     415                          self._LCTRL_items.set_column_widths()
     416                          self._LCTRL_items.SetFocus()
     417                  finally:
     418                          wx.EndBusyCursor()
     419
     420          #------------------------------------------------------------
     421          # properties
     422          #------------------------------------------------------------
     423 -        def _set_ignore_OK_button(self, ignored):
     424                  self.__ignore_OK_button = ignored
     425                  if self.__ignore_OK_button:
     426                          self._BTN_ok.Hide()
     427                          self._BTN_ok.Enable(False)
     428                  else:
     429                          self._BTN_ok.Show()
     430                          if self._LCTRL_items.get_selected_items(only_one=True) == -1:
     431                                  if self.can_return_empty:
     432                                          self._BTN_ok.Enable(True)
     433                                  else:
     434                                          self._BTN_ok.Enable(False)
     435                                          self._BTN_cancel.SetDefault()
     436
     437          ignore_OK_button = property(lambda x:x, _set_ignore_OK_button)
     438
     439          #------------------------------------------------------------
     440 -        def _set_left_extra_button(self, definition):
     441                  if definition is None:
     442                          self._BTN_extra_left.Enable(False)
     443                          self._BTN_extra_left.Hide()
     444                          self.__left_extra_button_callback = None
     445                          self.__left_extra_button_wants_list = False
     446                          return
     447
     448                  if len(definition) == 3:
     449                          (label, tooltip, callback) = definition
     450                          wants_list = False
     451                  else:
     452                          (label, tooltip, callback, wants_list) = definition
     453
     454                  if not callable(callback):
     455                          raise ValueError('<left extra button> callback is not a callable: %s' % callback)
     456                  self.__left_extra_button_callback = callback
     457                  self.__left_extra_button_wants_list = wants_list
     458                  self._BTN_extra_left.SetLabel(label)
     459                  self._BTN_extra_left.SetToolTip(tooltip)
     460                  self._BTN_extra_left.Enable(True)
     461                  self._BTN_extra_left.Show()
     462
     463          left_extra_button = property(lambda x:x, _set_left_extra_button)
     464
     465          #------------------------------------------------------------
     466 -        def _set_middle_extra_button(self, definition):
     467                  if definition is None:
     468                          self._BTN_extra_middle.Enable(False)
     469                          self._BTN_extra_middle.Hide()
     470                          self.__middle_extra_button_callback = None
     471                          self.__middle_extra_button_wants_list = False
     472                          return
     473
     474                  if len(definition) == 3:
     475                          (label, tooltip, callback) = definition
     476                          wants_list = False
     477                  else:
     478                          (label, tooltip, callback, wants_list) = definition
     479
     480                  if not callable(callback):
     481                          raise ValueError('<middle extra button> callback is not a callable: %s' % callback)
     482                  self.__middle_extra_button_callback = callback
     483                  self.__middle_extra_button_wants_list = wants_list
     484                  self._BTN_extra_middle.SetLabel(label)
     485                  self._BTN_extra_middle.SetToolTip(tooltip)
     486                  self._BTN_extra_middle.Enable(True)
     487                  self._BTN_extra_middle.Show()
     488
     489          middle_extra_button = property(lambda x:x, _set_middle_extra_button)
     490
     491          #------------------------------------------------------------
     492 -        def _set_right_extra_button(self, definition):
     493                  if definition is None:
     494                          self._BTN_extra_right.Enable(False)
     495                          self._BTN_extra_right.Hide()
     496                          self.__right_extra_button_callback = None
     497                          self.__right_extra_button_wants_list = False
     498                          return
     499
     500                  if len(definition) == 3:
     501                          (label, tooltip, callback) = definition
     502                          wants_list = False
     503                  else:
     504                          (label, tooltip, callback, wants_list) = definition
     505
     506                  if not callable(callback):
     507                          raise ValueError('<right extra button> callback is not a callable: %s' % callback)
     508                  self.__right_extra_button_callback = callback
     509                  self.__right_extra_button_wants_list = wants_list
     510                  self._BTN_extra_right.SetLabel(label)
     511                  self._BTN_extra_right.SetToolTip(tooltip)
     512                  self._BTN_extra_right.Enable(True)
     513                  self._BTN_extra_right.Show()
     514
     515          right_extra_button = property(lambda x:x, _set_right_extra_button)
     516
     517          #------------------------------------------------------------
     518 -        def _get_new_callback(self):
     519                  return self.__new_callback
     520
     521 -        def _set_new_callback(self, callback):
     522                  if callback is not None:
     523                          if self.__refresh_callback is None:
     524                                  raise ValueError('refresh callback must be set before new callback can be set')
     525                          if not callable(callback):
     526                                  raise ValueError('<new> callback is not a callable: %s' % callback)
     527                  self.__new_callback = callback
     528
     529                  if callback is None:
     530                          self._BTN_new.Enable(False)
     531                          self._BTN_new.Hide()
     532                          self._LCTRL_items.new_callback = None
     533                  else:
     534                          self._BTN_new.Enable(True)
     535                          self._BTN_new.Show()
     536                          self._LCTRL_items.new_callback = self._on_insert_key_pressed_in_listctrl
     537
     538          new_callback = property(_get_new_callback, _set_new_callback)
     539
     540          #------------------------------------------------------------
     541 -        def _get_edit_callback(self):
     542                  return self.__edit_callback
     543
     544 -        def _set_edit_callback(self, callback):
     545                  if callback is not None:
     546                          if not callable(callback):
     547                                  raise ValueError('<edit> callback is not a callable: %s' % callback)
     548                  self.__edit_callback = callback
     549
     550                  if callback is None:
     551                          self._BTN_edit.Enable(False)
     552                          self._BTN_edit.Hide()
     553                          self._LCTRL_items.edit_callback = None
     554                  else:
     555                          self._BTN_edit.Enable(True)
     556                          self._BTN_edit.Show()
     557                          self._LCTRL_items.edit_callback = self._on_edit_invoked_in_listctrl
     558
     559          edit_callback = property(_get_edit_callback, _set_edit_callback)
     560
     561          #------------------------------------------------------------
     562 -        def _get_delete_callback(self):
     563                  return self.__delete_callback
     564
     565 -        def _set_delete_callback(self, callback):
     566                  if callback is not None:
     567                          if self.__refresh_callback is None:
     568                                  raise ValueError('refresh callback must be set before delete callback can be set')
     569                          if not callable(callback):
     570                                  raise ValueError('<delete> callback is not a callable: %s' % callback)
     571                  self.__delete_callback = callback
     572                  if callback is None:
     573                          self._BTN_delete.Enable(False)
     574                          self._BTN_delete.Hide()
     575                          self._LCTRL_items.delete_callback = None
     576                  else:
     577                          self._BTN_delete.Enable(True)
     578                          self._BTN_delete.Show()
     579                          self._LCTRL_items.delete_callback = self._on_delete_key_pressed_in_listctrl
     580
     581          delete_callback = property(_get_delete_callback, _set_delete_callback)
     582
     583          #------------------------------------------------------------
     584 -        def _get_refresh_callback(self):
     585                  return self.__refresh_callback
     586
     587 -        def _set_refresh_callback_helper(self):
     588                  wx.BeginBusyCursor()
     589                  try:
     590                          self.__refresh_callback(lctrl = self._LCTRL_items)
     591                  finally:
     592                          wx.EndBusyCursor()
     593                  self._LCTRL_items.set_column_widths()
     594
     595 -        def _set_refresh_callback(self, callback):
     596                  if callback is not None:
     597                          if not callable(callback):
     598                                  raise ValueError('<refresh> callback is not a callable: %s' % callback)
     599                  self.__refresh_callback = callback
     600                  if callback is not None:
     601                          wx.CallAfter(self._set_refresh_callback_helper)
     602
     603          refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
     604
     605          #------------------------------------------------------------
     606 -        def _get_select_callback(self):
     607                  return self.__select_callback
     608
     609 -        def _set_select_callback(self, callback):
     610                  if callback is not None:
     611                          if not callable(callback):
     612                                  raise ValueError('<select> callback is not a callable: %s' % callback)
     613                  self.__select_callback = callback
     614
     615          select_callback = property(_get_select_callback, _set_select_callback)
     616
     617          #------------------------------------------------------------
     618 -        def _set_list_tooltip_callback(self, callback):
     619                  self._LCTRL_items.item_tooltip_callback = callback
     620
     621          list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback)
     622          #def _get_tooltip(self, item):          # inside a class
     623          #def _get_tooltip(item):                        # outside a class
     624          #------------------------------------------------------------
     625 -        def _set_message(self, message):
     626                  if message is None:
     627                          self._LBL_message.Hide()
     628                          return
     629                  self._LBL_message.SetLabel(message)
     630                  self._LBL_message.Show()
     631
     632          message = property(lambda x:x, _set_message)
     633
     634  #================================================================
     635  from Gnumed.wxGladeWidgets import wxgGenericListManagerPnl
     636
     637 -class cGenericListManagerPnl(wxgGenericListManagerPnl.wxgGenericListManagerPnl):
     638          """A panel holding a generic multi-column list and action buttions."""
     639
     640 -        def __init__(self, *args, **kwargs):
     641
     642                  try:
     643                          msg = kwargs['msg']
     644                          del kwargs['msg']
     645                  except KeyError: msg = None
     646
     647                  wxgGenericListManagerPnl.wxgGenericListManagerPnl.__init__(self, *args, **kwargs)
     648
     649                  if msg is None:
     650                          self._LBL_message.Hide()
     651                  else:
     652                          self._LBL_message.SetLabel(msg)
     653
     654                  self.left_extra_button = None
     655                  self.middle_extra_button = None
     656                  self.right_extra_button = None
     657
     658                  # new/edit/delete must return True/False to enable refresh
     659                  self.refresh_callback = None                    # called when new/edit/delete callbacks return True (IOW were not cancelled)
     660                  self.new_callback = None                                # called when NEW button pressed, no argument passed in
     661                  self.edit_callback = None                               # called when EDIT button pressed, data of topmost selected item passed in
     662                  self.delete_callback = None                             # called when DELETE button pressed, data of topmost selected item passed in
     663
     664                  self.select_callback = None                             # called when an item is selected, data of topmost selected item passed in
     665                  self._LCTRL_items.select_callback = self._on_list_item_selected_in_listctrl
     666
     667          #------------------------------------------------------------
     668          # external API
     669          #------------------------------------------------------------
     670 -        def set_columns(self, columns=None):
     671                  self._LCTRL_items.set_columns(columns = columns)
     672
     673          #------------------------------------------------------------
     674 -        def set_string_items(self, items=None, reshow=True):
     675                  self._LCTRL_items.set_string_items(items = items, reshow = reshow)
     676                  self._LCTRL_items.set_column_widths()
     677
     678                  if (items is None) or (len(items) == 0):
     679                          self._BTN_edit.Enable(False)
     680                          self._BTN_remove.Enable(False)
     681                  #else:
     682                  #       self._LCTRL_items.Select(0)
     683
     684          #------------------------------------------------------------
     685 -        def set_selections(self, selections = None):
     686                  self._LCTRL_items.set_selections(selections = selections)
     687
     688          #------------------------------------------------------------
     689 -        def set_data(self, data = None):
     690                  self._LCTRL_items.set_data(data = data)
     691
     692          #------------------------------------------------------------
     693 -        def get_selected_item_data(self, only_one=False):
     694                  return self._LCTRL_items.get_selected_item_data(only_one=only_one)
     695
     696          #------------------------------------------------------------
     697          # internal helpers
     698          #------------------------------------------------------------
     699 -        def _on_list_item_selected_in_listctrl(self, event):
     700                  event.Skip()
     701                  if self.__edit_callback is not None:
     702                          self._BTN_edit.Enable(True)
     703                  if self.__delete_callback is not None:
     704                          self._BTN_remove.Enable(True)
     705                  if self.__select_callback is not None:
     706                          item = self._LCTRL_items.get_selected_item_data(only_one = True)
     707                          self.__select_callback(item)
     708
     709          #------------------------------------------------------------
     710 -        def _on_delete_key_pressed_in_listctrl(self):
     711                  self.__do_delete()
     712
     713          #------------------------------------------------------------
     714 -        def __do_delete(self):
     715                  if not self.__delete_callback(self._LCTRL_items.get_selected_item_data(only_one = True)):
     716                          return
     717                  if self.__refresh_callback is None:
     718                          self._LCTRL_items.SetFocus()
     719                          return
     720                  wx.BeginBusyCursor()
     721                  try:
     722                          self.__refresh_callback(lctrl = self._LCTRL_items)
     723                          self._LCTRL_items.set_column_widths()
     724                          self._LCTRL_items.SetFocus()
     725                  finally:
     726                          wx.EndBusyCursor()
     727
     728          #------------------------------------------------------------
     729 -        def _on_edit_invoked_in_listctrl(self):
     730                  self.__do_edit()
     731
     732          #------------------------------------------------------------
     733 -        def __do_edit(self):
     734                  if not self.__edit_callback(self._LCTRL_items.get_selected_item_data(only_one = True)):
     735                          self._LCTRL_items.SetFocus()
     736                          return
     737                  if self.__refresh_callback is None:
     738                          self._LCTRL_items.SetFocus()
     739                          return
     740                  wx.BeginBusyCursor()
     741                  try:
     742                          self.__refresh_callback(lctrl = self._LCTRL_items)
     743                          self._LCTRL_items.set_column_widths()
     744                          self._LCTRL_items.SetFocus()
     745                  finally:
     746                          wx.EndBusyCursor()
     747
     748          #------------------------------------------------------------
     749 -        def _on_insert_key_pressed_in_listctrl(self):
     750                  self.__do_insert()
     751
     752          #------------------------------------------------------------
     753 -        def __do_insert(self):
     754                  if not self.__new_callback():
     755                          self._LCTRL_items.SetFocus()
     756                          return
     757                  if self.__refresh_callback is None:
     758                          self._LCTRL_items.SetFocus()
     759                          return
     760                  wx.BeginBusyCursor()
     761                  try:
     762                          self.__refresh_callback(lctrl = self._LCTRL_items)
     763                          self._LCTRL_items.set_column_widths()
     764                          self._LCTRL_items.SetFocus()
     765                  finally:
     766                          wx.EndBusyCursor()
     767
     768          #------------------------------------------------------------
     769          # event handlers
     770          #------------------------------------------------------------
     771 -        def _on_list_item_deselected(self, event):
     772                  event.Skip()
     773                  if self._LCTRL_items.get_selected_items(only_one = True) == -1:
     774                          self._BTN_edit.Enable(False)
     775                          self._BTN_remove.Enable(False)
     776                          if self.__select_callback is not None:
     777                                  self.__select_callback(None)
     778
     779          #------------------------------------------------------------
     780 -        def _on_list_item_activated(self, event):
     781                  event.Skip()
     782                  if self.__edit_callback is None:
     783                          return
     784                  self._on_edit_button_pressed(event)
     785
     786          #------------------------------------------------------------
     787 -        def _on_add_button_pressed(self, event):
     788                  if not self.__new_callback():
     789                          return
     790                  if self.__refresh_callback is None:
     791                          return
     792                  wx.BeginBusyCursor()
     793                  try:
     794                          self.__refresh_callback(lctrl = self._LCTRL_items)
     795                  finally:
     796                          wx.EndBusyCursor()
     797
     798          #------------------------------------------------------------
     799 -        def _on_edit_button_pressed(self, event):
     800                  item = self._LCTRL_items.get_selected_item_data(only_one = True)
     801                  if item is None:
     802                          return
     803                  if not self.__edit_callback(item):
     804                          return
     805                  if self.__refresh_callback is None:
     806                          return
     807                  wx.BeginBusyCursor()
     808                  try:
     809                          self.__refresh_callback(lctrl = self._LCTRL_items)
     810                  finally:
     811                          wx.EndBusyCursor()
     812
     813          #------------------------------------------------------------
     814 -        def _on_remove_button_pressed(self, event):
     815                  if self._LCTRL_items.get_selected_items(only_one = True) is None:
     816                          return
     817                  self.__do_delete()
     818
     819          #------------------------------------------------------------
     820 -        def _on_left_extra_button_pressed(self, event):
     821                  item_data = self._LCTRL_items.get_selected_item_data(only_one = True)
     822                  if not self.__left_extra_button_callback(item_data):
     823                          self._LCTRL_items.SetFocus()
     824                          return
     825                  if self.__refresh_callback is None:
     826                          self._LCTRL_items.SetFocus()
     827                          return
     828                  wx.BeginBusyCursor()
     829                  try:
     830                          self.__refresh_callback(lctrl = self._LCTRL_items)
     831                  finally:
     832                          self._LCTRL_items.set_column_widths()
     833                          self._LCTRL_items.SetFocus()
     834                          wx.EndBusyCursor()
     835
     836          #------------------------------------------------------------
     837 -        def _on_middle_extra_button_pressed(self, event):
     838                  item_data = self._LCTRL_items.get_selected_item_data(only_one = True)
     839                  if not self.__middle_extra_button_callback(item_data):
     840                          self._LCTRL_items.SetFocus()
     841                          return
     842                  if self.__refresh_callback is None:
     843                          self._LCTRL_items.SetFocus()
     844                          return
     845                  wx.BeginBusyCursor()
     846                  try:
     847                          self.__refresh_callback(lctrl = self._LCTRL_items)
     848                  finally:
     849                          self._LCTRL_items.set_column_widths()
     850                          self._LCTRL_items.SetFocus()
     851                          wx.EndBusyCursor()
     852
     853          #------------------------------------------------------------
     854 -        def _on_right_extra_button_pressed(self, event):
     855                  item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
     856                  if not self.__right_extra_button_callback(item_data):
     857                          self._LCTRL_items.SetFocus()
     858                          return
     859                  if self.__refresh_callback is None:
     860                          self._LCTRL_items.SetFocus()
     861                          return
     862                  wx.BeginBusyCursor()
     863                  try:
     864                          self.__refresh_callback(lctrl = self._LCTRL_items)
     865                  finally:
     866                          wx.EndBusyCursor()
     867                  self._LCTRL_items.set_column_widths()
     868                  self._LCTRL_items.SetFocus()
     869
     870          #------------------------------------------------------------
     871          # properties
     872          #------------------------------------------------------------
     873 -        def _get_new_callback(self):
     874                  return self.__new_callback
     875
     876 -        def _set_new_callback(self, callback):
     877                  if callback is not None:
     878                          if self.__refresh_callback is None:
     879                                  raise ValueError('refresh callback must be set before new callback can be set')
     880                          if not callable(callback):
     881                                  raise ValueError('<new> callback is not a callable: %s' % callback)
     882                  self.__new_callback = callback
     883
     884                  if callback is None:
     885                          self._BTN_add.Enable(False)
     886                          self._BTN_add.Hide()
     887                          self._LCTRL_items.new_callback = None
     888                  else:
     889                          self._BTN_add.Enable(True)
     890                          self._BTN_add.Show()
     891                          self._LCTRL_items.new_callback = self._on_insert_key_pressed_in_listctrl
     892
     893          new_callback = property(_get_new_callback, _set_new_callback)
     894
     895          #------------------------------------------------------------
     896 -        def _get_edit_callback(self):
     897                  return self.__edit_callback
     898
     899 -        def _set_edit_callback(self, callback):
     900                  if callback is not None:
     901                          if not callable(callback):
     902                                  raise ValueError('<edit> callback is not a callable: %s' % callback)
     903                  self.__edit_callback = callback
     904
     905                  if callback is None:
     906                          self._BTN_edit.Enable(False)
     907                          self._BTN_edit.Hide()
     908                          self._LCTRL_items.edit_callback = None
     909                  else:
     910                          self._BTN_edit.Enable(True)
     911                          self._BTN_edit.Show()
     912                          self._LCTRL_items.edit_callback = self._on_edit_invoked_in_listctrl
     913
     914          edit_callback = property(_get_edit_callback, _set_edit_callback)
     915
     916          #------------------------------------------------------------
     917 -        def _get_delete_callback(self):
     918                  return self.__delete_callback
     919
     920 -        def _set_delete_callback(self, callback):
     921                  if callback is not None:
     922                          if self.__refresh_callback is None:
     923                                  raise ValueError('refresh callback must be set before delete callback can be set')
     924                          if not callable(callback):
     925                                  raise ValueError('<delete> callback is not a callable: %s' % callback)
     926                  self.__delete_callback = callback
     927                  if callback is None:
     928                          self._BTN_remove.Enable(False)
     929                          self._BTN_remove.Hide()
     930                          self._LCTRL_items.delete_callback = None
     931                  else:
     932                          self._BTN_remove.Enable(True)
     933                          self._BTN_remove.Show()
     934                          self._LCTRL_items.delete_callback = self._on_delete_key_pressed_in_listctrl
     935
     936          delete_callback = property(_get_delete_callback, _set_delete_callback)
     937
     938          #------------------------------------------------------------
     939 -        def _get_refresh_callback(self):
     940                  return self.__refresh_callback
     941
     942 -        def _set_refresh_callback_helper(self):
     943                  wx.BeginBusyCursor()
     944                  try:
     945                          self.__refresh_callback(lctrl = self._LCTRL_items)
     946                  finally:
     947                          wx.EndBusyCursor()
     948                  self._LCTRL_items.set_column_widths()
     949
     950 -        def _set_refresh_callback(self, callback):
     951                  if callback is not None:
     952                          if not callable(callback):
     953                                  raise ValueError('<refresh> callback is not a callable: %s' % callback)
     954                  self.__refresh_callback = callback
     955                  if callback is not None:
     956                          wx.CallAfter(self._set_refresh_callback_helper)
     957
     958          refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
     959
     960          #------------------------------------------------------------
     961 -        def _get_select_callback(self):
     962                  return self.__select_callback
     963
     964 -        def _set_select_callback(self, callback):
     965                  if callback is not None:
     966                          if not callable(callback):
     967                                  raise ValueError('<select> callback is not a callable: %s' % callback)
     968                  self.__select_callback = callback
     969
     970          select_callback = property(_get_select_callback, _set_select_callback)
     971
     972          #------------------------------------------------------------
     973 -        def _get_message(self):
     974                  return self._LBL_message.GetLabel()
     975
     976 -        def _set_message(self, msg):
     977                  if msg is None:
     978                          self._LBL_message.Hide()
     979                          self._LBL_message.SetLabel('')
     980                  else:
     981                          self._LBL_message.SetLabel(msg)
     982                          self._LBL_message.Show()
     983                  self.Layout()
     984
     985          message = property(_get_message, _set_message)
     986
     987          #------------------------------------------------------------
     988 -        def _set_left_extra_button(self, definition):
     989                  if definition is None:
     990                          self._BTN_extra_left.Enable(False)
     991                          self._BTN_extra_left.Hide()
     992                          self.__left_extra_button_callback = None
     993                          return
     994
     995                  (label, tooltip, callback) = definition
     996                  if not callable(callback):
     997                          raise ValueError('<left extra button> callback is not a callable: %s' % callback)
     998                  self.__left_extra_button_callback = callback
     999                  self._BTN_extra_left.SetLabel(label)
    1000                  self._BTN_extra_left.SetToolTip(tooltip)
    1001                  self._BTN_extra_left.Enable(True)
    1002                  self._BTN_extra_left.Show()
    1003
    1004          left_extra_button = property(lambda x:x, _set_left_extra_button)
    1005
    1006          #------------------------------------------------------------
    1007 -        def _set_middle_extra_button(self, definition):
    1008                  if definition is None:
    1009                          self._BTN_extra_middle.Enable(False)
    1010                          self._BTN_extra_middle.Hide()
    1011                          self.__middle_extra_button_callback = None
    1012                          return
    1013
    1014                  (label, tooltip, callback) = definition
    1015                  if not callable(callback):
    1016                          raise ValueError('<middle extra button> callback is not a callable: %s' % callback)
    1017                  self.__middle_extra_button_callback = callback
    1018                  self._BTN_extra_middle.SetLabel(label)
    1019                  self._BTN_extra_middle.SetToolTip(tooltip)
    1020                  self._BTN_extra_middle.Enable(True)
    1021                  self._BTN_extra_middle.Show()
    1022
    1023          middle_extra_button = property(lambda x:x, _set_middle_extra_button)
    1024
    1025          #------------------------------------------------------------
    1026 -        def _set_right_extra_button(self, definition):
    1027                  if definition is None:
    1028                          self._BTN_extra_right.Enable(False)
    1029                          self._BTN_extra_right.Hide()
    1030                          self.__right_extra_button_callback = None
    1031                          return
    1032
    1033                  (label, tooltip, callback) = definition
    1034                  if not callable(callback):
    1035                          raise ValueError('<right extra button> callback is not a callable: %s' % callback)
    1036                  self.__right_extra_button_callback = callback
    1037                  self._BTN_extra_right.SetLabel(label)
    1038                  self._BTN_extra_right.SetToolTip(tooltip)
    1039                  self._BTN_extra_right.Enable(True)
    1040                  self._BTN_extra_right.Show()
    1041
    1042          right_extra_button = property(lambda x:x, _set_right_extra_button)
    1043
    1044  #================================================================
    1045  from Gnumed.wxGladeWidgets import wxgItemPickerDlg
    1046
    1047 -class cItemPickerDlg(wxgItemPickerDlg.wxgItemPickerDlg):
    1048
    1049 -        def __init__(self, *args, **kwargs):
    1050
    1051                  try:
    1052                          msg = kwargs['msg']
    1053                          del kwargs['msg']
    1054                  except KeyError:
    1055                          msg = None
    1056
    1057                  try:
    1058                          title = kwargs['title']
    1059                          if not title.startswith('GMd: '):
    1060                                  kwargs['title'] = 'GMd: %s' % title
    1061                  except KeyError:
    1062                          kwargs['title'] = 'GMd: %s' % self.__class__.__name__
    1063
    1064                  wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs)
    1065
    1066                  if msg is None:
    1067                          self._LBL_msg.Hide()
    1068                  else:
    1069                          self._LBL_msg.SetLabel(msg)
    1070
    1071                  self.ignore_dupes_on_picking = True
    1072
    1073                  self._LCTRL_left.activate_callback = self.__pick_selected
    1074                  self.__extra_button_callback = None
    1075
    1076                  self._LCTRL_left.SetFocus()
    1077
    1078          #------------------------------------------------------------
    1079          # external API
    1080          #------------------------------------------------------------
    1081 -        def set_columns(self, columns=None, columns_right=None):
    1082                  self._LCTRL_left.set_columns(columns = columns)
    1083                  if columns_right is None:
    1084                          self._LCTRL_right.set_columns(columns = columns)
    1085                          return
    1086
    1087                  if len(columns_right) < len(columns):
    1088                          cols = columns
    1089                  else:
    1090                          cols = columns_right[:len(columns)]
    1091                  self._LCTRL_right.set_columns(columns = cols)
    1092
    1093          #------------------------------------------------------------
    1094 -        def set_string_items(self, items=None, reshow=True):
    1095                  self._LCTRL_left.set_string_items(items = items, reshow = reshow)
    1096                  self._LCTRL_left.set_column_widths()
    1097                  self._LCTRL_right.set_string_items(reshow = False)
    1098
    1099                  self._BTN_left2right.Enable(False)
    1100                  self._BTN_right2left.Enable(False)
    1101
    1102          #------------------------------------------------------------
    1103 -        def set_selections(self, selections = None):
    1104                  self._LCTRL_left.set_selections(selections = selections)
    1105
    1106          #------------------------------------------------------------
    1107 -        def set_choices(self, choices=None, data=None, reshow=True):
    1108                  self.set_string_items(items = choices, reshow = reshow)
    1109                  if data is not None:
    1110                          self.set_data(data = data)
    1111
    1112          #------------------------------------------------------------
    1113 -        def set_picks(self, picks=None, data=None, reshow=True):
    1114                  self._LCTRL_right.set_string_items(picks, reshow = reshow)
    1115                  self._LCTRL_right.set_column_widths()
    1116                  if data is not None:
    1117                          self._LCTRL_right.set_data(data = data)
    1118
    1119          #------------------------------------------------------------
    1120 -        def set_data(self, data = None):
    1121                  self._LCTRL_left.set_data(data = data)
    1122
    1123          #------------------------------------------------------------
    1124 -        def get_picks(self):
    1125                  return self._LCTRL_right.get_item_data()
    1126
    1127          picks = property(get_picks, lambda x:x)
    1128
    1129          #------------------------------------------------------------
    1130 -        def _set_extra_button(self, definition):
    1131                  if definition is None:
    1132                          self._BTN_extra.Enable(False)
    1133                          self._BTN_extra.Hide()
    1134                          self.__extra_button_callback = None
    1135                          return
    1136
    1137                  (label, tooltip, callback) = definition
    1138                  if not callable(callback):
    1139                          raise ValueError('<extra button> callback is not a callable: %s' % callback)
    1140                  self.__extra_button_callback = callback
    1141                  self._BTN_extra.SetLabel(label)
    1142                  self._BTN_extra.SetToolTip(tooltip)
    1143                  self._BTN_extra.Enable(True)
    1144                  self._BTN_extra.Show()
    1145
    1146          extra_button = property(lambda x:x, _set_extra_button)
    1147
    1148          #------------------------------------------------------------
    1149          # internal helpers
    1150          #------------------------------------------------------------
    1151 -        def __pick_selected(self, event=None):
    1152                  if self._LCTRL_left.get_selected_items(only_one = True) == -1:
    1153                          return
    1154
    1155                  right_items = self._LCTRL_right.get_string_items()
    1156                  right_data = self._LCTRL_right.get_item_data()
    1157                  if right_data is None:
    1158                          right_data = []
    1159
    1160                  selected_items = self._LCTRL_left.get_selected_string_items(only_one = False)
    1161                  selected_data = self._LCTRL_left.get_selected_item_data(only_one = False)
    1162
    1163                  if self.ignore_dupes_on_picking is False:
    1164                          right_items.extend(selected_items)
    1165                          right_data.extend(selected_data)
    1166                          self._LCTRL_right.set_string_items(items = right_items, reshow = True)
    1167                          self._LCTRL_right.set_data(data = right_data)
    1168                          self._LCTRL_right.set_column_widths()
    1169  #                       print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
    1170  #                       print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
    1171                          return
    1172
    1173                  for sel_item, sel_data in zip(selected_items, selected_data):
    1174                          if sel_item in right_items:
    1175                                  continue
    1176                          right_items.append(sel_item)
    1177                          right_data.append(sel_data)
    1178                  self._LCTRL_right.set_string_items(items = right_items, reshow = True)
    1179                  self._LCTRL_right.set_data(data = right_data)
    1180                  self._LCTRL_right.set_column_widths()
    1181  #               print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
    1182  #               print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
    1183
    1184          #------------------------------------------------------------
    1185 -        def __remove_selected_picks(self):
    1186                  if self._LCTRL_right.get_selected_items(only_one = True) == -1:
    1187                          return
    1188
    1189                  for item_idx in self._LCTRL_right.get_selected_items(only_one = False):
    1190                          self._LCTRL_right.remove_item(item_idx)
    1191
    1192                  if self._LCTRL_right.GetItemCount() == 0:
    1193                          self._BTN_right2left.Enable(False)
    1194
    1195  #               print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
    1196  #               print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
    1197
    1198          #------------------------------------------------------------
    1199          # event handlers
    1200          #------------------------------------------------------------
    1201 -        def _on_left_list_item_selected(self, event):
    1202                  self._BTN_left2right.Enable(True)
    1203          #------------------------------------------------------------
    1204 -        def _on_left_list_item_deselected(self, event):
    1205                  if self._LCTRL_left.get_selected_items(only_one = True) == -1:
    1206                          self._BTN_left2right.Enable(False)
    1207          #------------------------------------------------------------
    1208 -        def _on_right_list_item_selected(self, event):
    1209                  self._BTN_right2left.Enable(True)
    1210          #------------------------------------------------------------
    1211 -        def _on_right_list_item_deselected(self, event):
    1212                  if self._LCTRL_right.get_selected_items(only_one = True) == -1:
    1213                          self._BTN_right2left.Enable(False)
    1214          #------------------------------------------------------------
    1215 -        def _on_button_left2right_pressed(self, event):
    1216                  self.__pick_selected()
    1217          #------------------------------------------------------------
    1218 -        def _on_button_right2left_pressed(self, event):
    1219                  self.__remove_selected_picks()
    1220          #------------------------------------------------------------
    1221 -        def _on_extra_button_pressed(self, event):
    1222                  self.__extra_button_callback()
    1223          #------------------------------------------------------------
    1224 -        def _set_left_item_tooltip_callback(self, callback):
    1225                  self._LCTRL_left.item_tooltip_callback = callback
    1226
    1227          left_item_tooltip_callback = property(lambda x:x, _set_left_item_tooltip_callback)
    1228          #------------------------------------------------------------
    1229 -        def _set_right_item_tooltip_callback(self, callback):
    1230                  self._LCTRL_right.item_tooltip_callback = callback
    1231
    1232          right_item_tooltip_callback = property(lambda x:x, _set_right_item_tooltip_callback)
    1233
    1234  #================================================================
    1235 -class cReportListCtrl(listmixins.ListCtrlAutoWidthMixin, listmixins.ColumnSorterMixin, wx.ListCtrl):
    1236
    1237          # sorting: at set_string_items() time all items will be
    1238          # adorned with their initial row number as wxPython data,
    1239          # this is used later for a) sorting and b) to access
    1240          # GNUmed data objects associated with rows,
    1241          # the latter are ordered in initial row number order
    1242          # at set_data() time
    1243
    1244          sort_order_tags = {
    1245                  True: ' [\u03b1\u0391 \u2192 \u03c9\u03A9]',
    1246                  False: ' [\u03c9\u03A9 \u2192 \u03b1\u0391]'
    1247          }
    1248
    1249 -        def __init__(self, *args, **kwargs):
    1250
    1251                  self.debug = None
    1252                  self.map_item_idx2data_idx = self.GetItemData
    1253
    1254                  try:
    1255                          kwargs['style'] = kwargs['style'] | wx.LC_REPORT
    1256                  except KeyError:
    1257                          kwargs['style'] = wx.LC_REPORT
    1258
    1259                  self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL)
    1260
    1261                  wx.ListCtrl.__init__(self, *args, **kwargs)
    1262                  listmixins.ListCtrlAutoWidthMixin.__init__(self)
    1263
    1264                  # required for column sorting
    1265                  self._invalidate_sorting_metadata()                                     # must be called after each (external/direct) list item update
    1266                  listmixins.ColumnSorterMixin.__init__(self, 0)          # must be called again after adding columns (why ?)
    1267                  self.__secondary_sort_col = None
    1268                  # apparently, this MUST be bound under wxP3 - but why ??
    1269                  self.Bind(wx.EVT_LIST_COL_CLICK, self._on_col_click, self)
    1270
    1271                  # cols/rows
    1272                  self.__widths = None
    1273                  self.__data = None
    1274
    1275                  # event callbacks
    1276                  self.__select_callback = None
    1277                  self.__deselect_callback = None
    1278                  self.__activate_callback = None
    1279                  self.__new_callback = None
    1280                  self.__edit_callback = None
    1281                  self.__delete_callback = None
    1282
    1283                  # context menu
    1284                  self.__extend_popup_menu_callback = None
    1285                  self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self._on_list_item_rightclicked)        # (also handled by MENU key on EVT_LIST_KEY_DOWN)
    1286
    1287                  # row tooltips
    1288                  self.__item_tooltip_callback = None
    1289                  self.__tt_last_item = None
    1290  #               self.__tt_static_part_base = _(
    1291  #                       u'Select the items you want to work on.\n'
    1292  #                       u'\n'
    1293  #                       u'A discontinuous selection may depend on your holding '
    1294  #                       u'down a platform-dependent modifier key (<CTRL>, <ALT>, '
    1295  #                       u'etc) or key combination (eg. <CTRL-SHIFT> or <CTRL-ALT>) '
    1296  #                       u'while clicking.'
    1297  #               )
    1298                  self.__tt_static_part_base = ''
    1299                  self.__tt_static_part = self.__tt_static_part_base
    1300                  self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
    1301
    1302                  # search related:
    1303                  self.__search_term = None
    1304                  self.__next_line_to_search = 0
    1305                  self.__searchable_cols = None
    1306
    1307                  # general event handling
    1308  #               self.Bind(wx.EVT_KILL_FOCUS, self._on_lost_focus)
    1309                  self.Bind(wx.EVT_CHAR, self._on_char)                                           # CTRL-F / CTRL-N (LIST_KEY_DOWN does not support modifiers)
    1310                  self.Bind(wx.EVT_LIST_KEY_DOWN, self._on_list_key_down)         # context menu key -> context menu / DEL / INS
    1311
    1312          #------------------------------------------------------------
    1313          # debug sizing
    1314          #------------------------------------------------------------
    1315 -        def __log_sizing(self, caller_name, *args, **kwargs):
    1316                  if self.debug is None:
    1317                          return False
    1318                  if not self.debug.endswith('_sizing'):
    1319                          return False
    1320                  _log.debug('[%s.%s]: *args = (%s), **kwargs = (%s)', self.debug, caller_name, str(args), str(kwargs))
    1321                  return True
    1322
    1323          #------------------------------------------------------------
    1324 -        def CacheBestSize(self, *args, **kwargs):
    1325                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1326                  return super(cReportListCtrl, self).CacheBestSize(*args, **kwargs)
    1327
    1328          #------------------------------------------------------------
    1329 -        def Fit(self, *args, **kwargs):
    1330                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1331                  return super(cReportListCtrl, self).Fit(*args, **kwargs)
    1332
    1333          #------------------------------------------------------------
    1334 -        def FitInside(self, *args, **kwargs):
    1335                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1336                  return super(cReportListCtrl, self).FitInside(*args, **kwargs)
    1337
    1338          #------------------------------------------------------------
    1339 -        def InvalidateBestSize(self, *args, **kwargs):
    1340                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1341                  return super(cReportListCtrl, self).InvalidateBestSize(*args, **kwargs)
    1342
    1343          #------------------------------------------------------------
    1344 -        def SetBestFittingSize(self, *args, **kwargs):
    1345                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1346                  return super(cReportListCtrl, self).SetBestFittingSize(*args, **kwargs)
    1347
    1348          #------------------------------------------------------------
    1349 -        def SetInitialSize(self, *args, **kwargs):
    1350                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1351                  return super(cReportListCtrl, self).SetInitialSize(*args, **kwargs)
    1352
    1353          #------------------------------------------------------------
    1354 -        def SetClientSize(self, *args, **kwargs):
    1355                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1356                  return super(cReportListCtrl, self).SetClientSize(*args, **kwargs)
    1357
    1358          #------------------------------------------------------------
    1359 -        def SetClientSizeWH(self, *args, **kwargs):
    1360                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1361                  return super(cReportListCtrl, self).SetClientSizeWH(*args, **kwargs)
    1362
    1363          #------------------------------------------------------------
    1364 -        def SetMaxClientSize(self, *args, **kwargs):
    1365                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1366                  return super(cReportListCtrl, self).SetMaxClientSize(*args, **kwargs)
    1367
    1368          #------------------------------------------------------------
    1369 -        def SetMaxSize(self, *args, **kwargs):
    1370                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1371                  return super(cReportListCtrl, self).SetMaxSize(*args, **kwargs)
    1372
    1373          #------------------------------------------------------------
    1374 -        def SetMinClientSize(self, *args, **kwargs):
    1375                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1376                  return super(cReportListCtrl, self).SetMinClientSize(*args, **kwargs)
    1377
    1378          #------------------------------------------------------------
    1379 -        def SetMinSize(self, *args, **kwargs):
    1380                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1381                  return super(cReportListCtrl, self).SetMinSize(*args, **kwargs)
    1382
    1383          #------------------------------------------------------------
    1384 -        def SetSize(self, *args, **kwargs):
    1385                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1386                  return super(cReportListCtrl, self).SetSize(*args, **kwargs)
    1387
    1388          #------------------------------------------------------------
    1389 -        def SetSizeHints(self, *args, **kwargs):
    1390                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1391                  return super(cReportListCtrl, self).SetSizeHints(*args, **kwargs)
    1392
    1393          #------------------------------------------------------------
    1394 -        def SetSizeHintsSz(self, *args, **kwargs):
    1395                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1396                  return super(cReportListCtrl, self).SetSizeHintsSz(*args, **kwargs)
    1397
    1398          #------------------------------------------------------------
    1399 -        def SetSizeWH(self, *args, **kwargs):
    1400                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1401                  return super(cReportListCtrl, self).SetSizeWH(*args, **kwargs)
    1402
    1403          #------------------------------------------------------------
    1404 -        def SetVirtualSize(self, *args, **kwargs):
    1405                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1406                  return super(cReportListCtrl, self).SetVirtualSize(*args, **kwargs)
    1407
    1408          #------------------------------------------------------------
    1409 -        def SetVirtualSizeHints(self, *args, **kwargs):
    1410                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1411                  return super(cReportListCtrl, self).SetVirtualSizeHints(self, *args, **kwargs)
    1412
    1413          #------------------------------------------------------------
    1414 -        def SetVirtualSizeHintsSz(self, *args, **kwargs):
    1415                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1416                  return super(cReportListCtrl, self).SetVirtualSizeHintsSz(*args, **kwargs)
    1417
    1418          #------------------------------------------------------------
    1419 -        def SetVirtualSizeWH(self, *args, **kwargs):
    1420                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1421                  return super(cReportListCtrl, self).SetVirtualSizeWH(*args, **kwargs)
    1422
    1423          #------------------------------------------------------------
    1424 -        def GetAdjustedBestSize(self, *args, **kwargs):
    1425                  res = super(cReportListCtrl, self).GetAdjustedBestSize(*args, **kwargs)
    1426                  kwargs['sizing_function_result'] = res
    1427                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1428                  return res
    1429
    1430          #------------------------------------------------------------
    1431 -        def GetEffectiveMinSize(self, *args, **kwargs):
    1432                  res = super(cReportListCtrl, self).GetEffectiveMinSize(*args, **kwargs)
    1433                  kwargs['sizing_function_result'] = res
    1434                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1435                  return res
    1436
    1437          #------------------------------------------------------------
    1438 -        def GetBestSize(self, *args, **kwargs):
    1439                  res = super(cReportListCtrl, self).GetBestSize(*args, **kwargs)
    1440                  kwargs['sizing_function_result'] = res
    1441                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1442                  return res
    1443
    1444          #------------------------------------------------------------
    1445 -        def GetBestSizeTuple(self, *args, **kwargs):
    1446                  res = super(cReportListCtrl, self).GetBestSizeTuple(*args, **kwargs)
    1447                  kwargs['sizing_function_result'] = res
    1448                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1449                  return res
    1450
    1451          #------------------------------------------------------------
    1452 -        def GetBestVirtualSize(self, *args, **kwargs):
    1453                  res = super(cReportListCtrl, self).GetBestVirtualSize(*args, **kwargs)
    1454                  kwargs['sizing_function_result'] = res
    1455                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1456                  return res
    1457
    1458          #------------------------------------------------------------
    1459 -        def GetClientSize(self, *args, **kwargs):
    1460                  res = super(cReportListCtrl, self).GetClientSize(*args, **kwargs)
    1461                  kwargs['sizing_function_result'] = res
    1462                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1463                  return res
    1464
    1465          #------------------------------------------------------------
    1466 -        def GetClientSize(self, *args, **kwargs):
    1467                  res = super(cReportListCtrl, self).GetClientSize(*args, **kwargs)
    1468                  kwargs['sizing_function_result'] = res
    1469                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1470                  return res
    1471
    1472          #------------------------------------------------------------
    1473 -        def GetMaxClientSize(self, *args, **kwargs):
    1474                  res = super(cReportListCtrl, self).GetMaxClientSize(*args, **kwargs)
    1475                  kwargs['sizing_function_result'] = res
    1476                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1477                  return res
    1478
    1479          #------------------------------------------------------------
    1480 -        def GetMaxHeight(self, *args, **kwargs):
    1481                  res = super(cReportListCtrl, self).GetMaxHeight(*args, **kwargs)
    1482                  kwargs['sizing_function_result'] = res
    1483                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1484                  return res
    1485
    1486          #------------------------------------------------------------
    1487 -        def GetMaxSize(self, *args, **kwargs):
    1488                  res = super(cReportListCtrl, self).GetMaxSize(*args, **kwargs)
    1489                  kwargs['sizing_function_result'] = res
    1490                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1491                  return res
    1492
    1493          #------------------------------------------------------------
    1494 -        def GetMaxWidth(self, *args, **kwargs):
    1495                  res = super(cReportListCtrl, self).GetMaxWidth(*args, **kwargs)
    1496                  kwargs['sizing_function_result'] = res
    1497                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1498                  return res
    1499
    1500          #------------------------------------------------------------
    1501 -        def GetMinClientSize(self, *args, **kwargs):
    1502                  res = super(cReportListCtrl, self).GetMinClientSize(*args, **kwargs)
    1503                  kwargs['sizing_function_result'] = res
    1504                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1505                  return res
    1506
    1507          #------------------------------------------------------------
    1508 -        def GetMinHeight(self, *args, **kwargs):
    1509                  res = super(cReportListCtrl, self).GetMinHeight(*args, **kwargs)
    1510                  kwargs['sizing_function_result'] = res
    1511                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1512                  return res
    1513
    1514          #------------------------------------------------------------
    1515 -        def GetMinSize(self, *args, **kwargs):
    1516                  res = super(cReportListCtrl, self).GetMinSize(*args, **kwargs)
    1517                  kwargs['sizing_function_result'] = res
    1518                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1519                  return res
    1520
    1521          #------------------------------------------------------------
    1522 -        def GetMinWidth(self, *args, **kwargs):
    1523                  res = super(cReportListCtrl, self).GetMinWidth(*args, **kwargs)
    1524                  kwargs['sizing_function_result'] = res
    1525                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1526                  return res
    1527
    1528          #------------------------------------------------------------
    1529 -        def GetSize(self, *args, **kwargs):
    1530                  res = super(cReportListCtrl, self).GetSize(*args, **kwargs)
    1531                  kwargs['sizing_function_result'] = res
    1532                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1533                  return res
    1534
    1535          #------------------------------------------------------------
    1536 -        def GetVirtualSize(self, *args, **kwargs):
    1537                  res = super(cReportListCtrl, self).GetVirtualSize(*args, **kwargs)
    1538                  kwargs['sizing_function_result'] = res
    1539                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1540                  return res
    1541
    1542          #------------------------------------------------------------
    1543 -        def GetVirtualSizeTuple(self, *args, **kwargs):
    1544                  res = super(cReportListCtrl, self).GetVirtualSizeTuple(*args, **kwargs)
    1545                  kwargs['sizing_function_result'] = res
    1546                  self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
    1547                  return res
    1548
    1549          #------------------------------------------------------------
    1550          # setters
    1551          #------------------------------------------------------------
    1552 -        def set_columns(self, columns=None):
    1553                  """(Re)define the columns.
    1554
    1555                  Note that this will (have to) delete the items.
    1556                  """
    1557                  self.ClearAll()
    1558                  self.__tt_last_item = None
    1559                  if columns is None:
    1560                          return
    1561                  for idx in range(len(columns)):
    1562                          self.InsertColumn(idx, columns[idx])
    1563
    1564                  listmixins.ColumnSorterMixin.__init__(self, 0)
    1565                  self._invalidate_sorting_metadata()
    1566
    1567          #------------------------------------------------------------
    1568 -        def set_column_widths(self, widths=None):
    1569                  """Set the column width policy.
    1570
    1571                  widths = None:
    1572                          use previous policy if any or default policy
    1573                  widths != None:
    1574                          use this policy and remember it for later calls
    1575
    1576                  options:
    1577                          wx.LIST_AUTOSIZE_USEHEADER
    1578                          wx.LIST_AUTOSIZE
    1579
    1580                  This means there is no way to *revert* to the default policy :-(
    1581                  """
    1582                  # explicit policy ?
    1583                  if widths is not None:
    1584                          self.__widths = widths
    1585                          for idx in range(len(self.__widths)):
    1586                                  self.SetColumnWidth(idx, self.__widths[idx])
    1587                          return
    1588
    1589                  # previous policy ?
    1590                  if self.__widths is not None:
    1591                          for idx in range(len(self.__widths)):
    1592                                  self.SetColumnWidth(idx, self.__widths[idx])
    1593                          return
    1594
    1595                  # default policy !
    1596                  if self.GetItemCount() == 0:
    1597                          width_type = wx.LIST_AUTOSIZE_USEHEADER
    1598                  else:
    1599                          width_type = wx.LIST_AUTOSIZE
    1600                  for idx in range(self.GetColumnCount()):
    1601                          self.SetColumnWidth(idx, width_type)
    1602
    1603          #------------------------------------------------------------
    1604 -        def set_resize_column(self, column='LAST'):
    1605                  if column != 'LAST':
    1606                          if column > self.ColumnCount:
    1607                                  return
    1608                  # this column will take up all remaining space courtesy of the width mixin
    1609                  self.setResizeColumn(column)
    1610
    1611          #------------------------------------------------------------
    1612 -        def remove_items_safely(self, max_tries=3):
    1613                  tries = 0
    1614                  while tries < max_tries:
    1615                          if self.debug is not None:
    1616                                  if self.debug.endswith('_deleting'):
    1617                                          _log.debug('[round %s] <%s>.GetItemCount() before DeleteAllItems(): %s (thread [%s])', tries, self.debug, self.GetItemCount(), threading.get_ident())
    1618                          if not self.DeleteAllItems():
    1619                                  _log.error('<%s>.DeleteAllItems() failed', self.debug)
    1620                          item_count = self.GetItemCount()
    1621                          if item_count == 0:
    1622                                  return True
    1623                          wx.SafeYield(None, True)
    1624                          _log.error('<%s>.GetItemCount() not 0 (rather: %s) after DeleteAllItems()', self.debug, item_count)
    1625                          time.sleep(0.3)
    1626                          wx.SafeYield(None, True)
    1627                          tries += 1
    1628
    1629                  _log.error('<%s>: unable to delete list items after looping %s times', self.debug, max_tries)
    1630                  return False
    1631
    1632          #------------------------------------------------------------
    1633 -        def set_string_items(self, items=None, reshow=True, unwrap=True):
    1634                  """All item members must be str()able or None."""
    1635
    1636                  wx.BeginBusyCursor()
    1637                  self._invalidate_sorting_metadata()
    1638
    1639                  if self.ItemCount == 0:
    1640                          topmost_visible = 0
    1641                  else:
    1642                          topmost_visible = self.GetFirstSelected()
    1643                          if topmost_visible == -1:
    1644                                  topmost_visible = self.GetFocusedItem()
    1645                          if topmost_visible == -1:
    1646                                  topmost_visible = self.TopItem
    1647
    1648                  if not self.remove_items_safely(max_tries = 3):
    1649                          _log.error("cannot remove items (!?), continuing and hoping for the best")
    1650
    1651                  if items is None:
    1652                          self.data = None
    1653                          wx.EndBusyCursor()
    1654                          return
    1655
    1656                  # insert new items
    1657                  for item in items:
    1658                          # item is a single string
    1659                          # (typical special case: items=rows are a list-of-strings)
    1660                          if isinstance(item, str):
    1661                                  self.InsertItem(index = sys.maxsize, label = item.replace('\r\n', ' [CRLF] ').replace('\n', ' [LF] '))
    1662                                  continue
    1663                          # item is something else, either ...
    1664                          try:
    1665                                  # ... an iterable
    1666                                  col_val = str(item[0])
    1667                                  row_num = self.InsertItem(index = sys.maxsize, label = col_val)
    1668                                  for col_num in range(1, min(self.GetColumnCount(), len(item))):
    1669                                          col_val = str(item[col_num]).replace('\r\n', ' [CRLF] ').replace('\n', ' [LF] ')
    1670                                          self.SetItem(index = row_num, column = col_num, label = col_val)
    1671                          except (TypeError, KeyError, IndexError):
    1672                                  # ... an *empty* iterable [IndexError]
    1673                                  # ... or not iterable (None, int, instance, dict [KeyError] ...)
    1674                                  col_val = str(item).replace('\r\n', ' [CRLF] ').replace('\n', ' [LF] ')
    1675                                  self.InsertItem(index = sys.maxsize, label = col_val)
    1676
    1677                  if reshow:
    1678                          if self.ItemCount > 0:
    1679                                  if topmost_visible < self.ItemCount:
    1680                                          self.EnsureVisible(topmost_visible)
    1681                                          self.Focus(topmost_visible)
    1682                                  else:
    1683                                          self.EnsureVisible(self.ItemCount - 1)
    1684                                          self.Focus(self.ItemCount - 1)
    1685
    1686                  # set data to be a copy of items
    1687                  self.data = items
    1688
    1689                  wx.EndBusyCursor()
    1690
    1691          #--------------------------
    1692 -        def get_string_items(self):
    1693                  if self.ItemCount == 0:
    1694                          return []
    1695
    1696                  rows = []
    1697                  for row_idx in range(self.ItemCount):
    1698                          row = []
    1699                          for col_idx in range(self.ColumnCount):
    1700                                  row.append(self.GetItem(row_idx, col_idx).GetText())
    1701                          rows.append(row)
    1702                  return rows
    1703
    1704                  # old: only returned first column
    1705                  #return [ self.GetItemText(item_idx) for item_idx in range(self.GetItemCount()) ]
    1706
    1707          string_items = property(get_string_items, set_string_items)
    1708
    1709          #------------------------------------------------------------
    1710 -        def append_string_items_and_data(self, new_items, new_data=None, allow_dupes=False):
    1711                  if len(new_items) == 0:
    1712                          return
    1713
    1714                  if new_data is None:
    1715                          new_data = new_items
    1716
    1717                  existing_data = self.get_item_data()
    1718                  if existing_data is None:
    1719                          existing_data = []
    1720
    1721                  if allow_dupes:
    1722                          self._LCTRL_right.set_string_items (
    1723                                  items = self.string_items.extend(new_items),
    1724                                  reshow = True
    1725                          )
    1726                          self.data = existing_data.extend(new_data)
    1727                          self.set_column_widths()
    1728                          return
    1729
    1730                  existing_items = self.get_string_items()
    1731                  for new_item, new_data in zip(new_items, new_data):
    1732                          if new_item in existing_items:
    1733                                  continue
    1734                          existing_items.append(new_item)
    1735                          existing_data.append(new_data)
    1736                  self.set_string_items (
    1737                          items = existing_items,
    1738                          reshow = True
    1739                  )
    1740                  self.data = existing_data
    1741                  self.set_column_widths()
    1742
    1743          #------------------------------------------------------------
    1744 -        def set_data(self, data=None):
    1745                  """<data> assumed to be a list corresponding to the item indices"""
    1746                  if data is not None:
    1747                          item_count = self.GetItemCount()
    1748                          if len(data) != item_count:
    1749                                  _log.debug('<data> length (%s) must be equal to number of list items (%s)  (%s, thread [%s])', len(data), item_count, self.debug, threading.get_ident())
    1750                          for item_idx in range(len(data)):
    1751                                  self.SetItemData(item_idx, item_idx)
    1752                  self.__data = data
    1753                  self.__tt_last_item = None
    1754                  # string data (rows/visible list items) not modified,
    1755                  # so no need to call _update_sorting_metadata
    1756                  return
    1757
    1758 -        def _get_data(self):
    1759                  # slower than "return self.__data" but helps with detecting
    1760                  # problems with len(__data) != self.GetItemCount(),
    1761                  # also takes care of returning data in the order corresponding
    1762                  # to the order get_string_items returns rows
    1763                  return self.get_item_data()             # returns all data since item_idx is None
    1764
    1765          data = property(_get_data, set_data)
    1766
    1767          #------------------------------------------------------------
    1768 -        def set_selections(self, selections=None):
    1769                  # not sure why this is done:
    1770                  if self.GetItemCount() > 0:
    1771                          self.Select(0, on = 0)
    1772                  if selections is None:
    1773                          return
    1774                  for idx in selections:
    1775                          self.Select(idx = idx, on = 1)
    1776
    1777 -        def __get_selections(self):
    1778                  if self.ItemCount == 0:
    1779                          return []
    1780                  if self.__is_single_selection:
    1781                          return [self.GetFirstSelected()]
    1782                  selections = []
    1783                  idx = self.GetFirstSelected()
    1784                  while idx != -1:
    1785                          selections.append(idx)
    1786                          idx = self.GetNextSelected(idx)
    1787                  return selections
    1788
    1789          selections = property(__get_selections, set_selections)
    1790
    1791          #------------------------------------------------------------
    1792          # getters
    1793          #------------------------------------------------------------
    1794 -        def get_column_labels(self):
    1795                  labels = []
    1796                  for col_idx in range(self.ColumnCount):
    1797                          col = self.GetColumn(col = col_idx)
    1798                          labels.append(col.Text)
    1799                  return labels
    1800
    1801          column_labels = property(get_column_labels, lambda x:x)
    1802
    1803          #------------------------------------------------------------
    1804 -        def get_item(self, item_idx=None):
    1805                  if self.ItemCount == 0:
    1806                          _log.warning('no items')
    1807                          return None
    1808                  if item_idx is not None:
    1809                          return self.GetItem(item_idx)
    1810                  _log.error('get_item(None) called')
    1811                  return None
    1812
    1813          #------------------------------------------------------------
    1814 -        def get_items(self):
    1815                  if self.ItemCount == 0:
    1816                          return []
    1817                  return [ self.GetItem(item_idx) for item_idx in range(self.ItemCount) ]
    1818
    1819          items = property(get_items, lambda x:x)
    1820
    1821          #------------------------------------------------------------
    1822 -        def get_selected_items(self, only_one=False):
    1823
    1824                  if self.ItemCount == 0:
    1825                          if self.__is_single_selection or only_one:
    1826                                  return None
    1827                          return []
    1828
    1829                  if self.__is_single_selection or only_one:
    1830                          return self.GetFirstSelected()
    1831
    1832                  items = []
    1833                  idx = self.GetFirstSelected()
    1834                  while idx != -1:
    1835                          items.append(idx)
    1836                          idx = self.GetNextSelected(idx)
    1837
    1838                  return items
    1839
    1840          selected_items = property(get_selected_items, lambda x:x)
    1841
    1842          #------------------------------------------------------------
    1843 -        def get_selected_string_items(self, only_one=False):
    1844
    1845                  if self.ItemCount == 0:
    1846                          if self.__is_single_selection or only_one:
    1847                                  return None
    1848                          return []
    1849
    1850                  if self.__is_single_selection or only_one:
    1851                          return self.GetItemText(self.GetFirstSelected())
    1852
    1853                  items = []
    1854                  idx = self.GetFirstSelected()
    1855                  while idx != -1:
    1856                          items.append(self.GetItemText(idx))
    1857                          idx = self.GetNextSelected(idx)
    1858
    1859                  return items
    1860
    1861          selected_string_items = property(get_selected_string_items, lambda x:x)
    1862
    1863          #------------------------------------------------------------
    1864 -        def get_item_data(self, item_idx=None):
    1865
    1866                  if self.__data is None: # this isn't entirely clean
    1867                          return None
    1868
    1869                  if item_idx is not None:
    1870                          return self.__data[self.map_item_idx2data_idx(item_idx)]
    1871
    1872                  # if <idx> is None return all data up to item_count,
    1873                  # in case of len(__data) != self.GetItemCount() this
    1874                  # gives the chance to figure out what is going on
    1875                  return [ self.__data[self.map_item_idx2data_idx(item_idx)] for item_idx in range(self.GetItemCount()) ]
    1876
    1877          item_data = property(get_item_data, lambda x:x)
    1878
    1879          #------------------------------------------------------------
    1880 -        def get_selected_item_data(self, only_one=False):
    1881
    1882                  if self.__is_single_selection or only_one:
    1883                          if self.__data is None:
    1884                                  return None
    1885                          idx = self.GetFirstSelected()
    1886                          if idx == -1:
    1887                                  return None
    1888                          return self.__data[self.map_item_idx2data_idx(idx)]
    1889
    1890                  data = []
    1891                  if self.__data is None:
    1892                          return data
    1893                  idx = self.GetFirstSelected()
    1894                  while idx != -1:
    1895                          data.append(self.__data[self.map_item_idx2data_idx(idx)])
    1896                          idx = self.GetNextSelected(idx)
    1897
    1898                  return data
    1899
    1900          selected_item_data = property(get_selected_item_data, lambda x:x)
    1901
    1902          #------------------------------------------------------------
    1903 -        def deselect_selected_item(self):
    1904                  self.Select(idx = self.GetFirstSelected(), on = 0)
    1905
    1906          #------------------------------------------------------------
    1907 -        def remove_item(self, item_idx=None):
    1908                  # do NOT remove the corresponding data because even if
    1909                  # the item pointing to this data instance is gone all
    1910                  # other items will still point to their corresponding
    1911                  # *initial* row numbers
    1912                  #if self.__data is not None:
    1913                  #       del self.__data[self.map_item_idx2data_idx(item_idx)]
    1914                  self.DeleteItem(item_idx)
    1915                  self.__tt_last_item = None
    1916                  self._invalidate_sorting_metadata()
    1917
    1918          #------------------------------------------------------------
    1919          # internal helpers
    1920          #------------------------------------------------------------
    2142
    2143          #------------------------------------------------------------
    2144 -        def __handle_delete(self):
    2145                  if self.__delete_callback is None:
    2146                          return
    2147
    2148                  no_items = len(self.get_selected_items(only_one = False))
    2149                  if no_items == 0:
    2150                          return
    2151
    2152                  if no_items > 1:
    2153                          question = _(
    2154                                  '%s list items are selected.\n'
    2155                                  '\n'
    2156                                  'Really delete all %s items ?'
    2157                          ) % (no_items, no_items)
    2158                          title = _('Deleting list items')
    2159                          style = wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION | wx.STAY_ON_TOP
    2160                          dlg = wx.MessageDialog(None, question, title, style)
    2161                          btn_pressed = dlg.ShowModal()
    2162                          dlg.Destroy()
    2163                          if btn_pressed == wx.ID_NO:
    2164                                  self.SetFocus()
    2165                                  return
    2166                          if btn_pressed == wx.ID_CANCEL:
    2167                                  self.SetFocus()
    2168                                  return
    2169
    2170                  self.__delete_callback()
    2171                  return
    2172
    2173          #------------------------------------------------------------
    2174 -        def __handle_insert(self):
    2175                  if self.__new_callback is None:
    2176                          return
    2177                  self.__new_callback()
    2178
    2179          #------------------------------------------------------------
    2180 -        def __handle_edit(self):
    2181                  if self.__edit_callback is None:
    2182                          return
    2183                  self.__edit_callback()
    2184
    2185          #------------------------------------------------------------
    2186 -        def __show_search_dialog(self):
    2187                  #print "showing search dlg"
    2188                  if self.__search_term is None:
    2189                          #print "no prev search term"
    2190                          default = ''
    2191                  else:
    2192                          #print "prev search term:", self.__search_term
    2193                          default = self.__search_term
    2194                  search_term = wx.GetTextFromUser (
    2195                          _('Enter the search term:'),
    2196                          _('List search'),
    2197                          default_value = default
    2198                  )
    2199                  if search_term.strip() == '':
    2200                          #print "empty search term"
    2201                          self.__search_term = None
    2202                          self.__tt_static_part = self.__tt_static_part_base
    2203                          return
    2204
    2205                  #print "search term:", search_term
    2206                  self.__search_term = search_term
    2207                  self.__tt_static_part = _(
    2208                          'Current search term: [[%s]]\n'
    2209                          '\n'
    2210                          '%s'
    2211                  ) % (
    2212                          search_term,
    2213                          self.__tt_static_part_base
    2214                  )
    2215                  self.__search_match()
    2216
    2217          #------------------------------------------------------------
    2218          # event handlers
    2219          #------------------------------------------------------------
    2220 -        def _on_list_item_activated(self, event):
    2221                  event.Skip()
    2222                  if self.__activate_callback is not None:
    2223                          self.__activate_callback(event)
    2224                          return
    2225                  # default double-click / ENTER action: edit
    2226                  self.__handle_edit()
    2227
    2228          #------------------------------------------------------------
    2229 -        def _on_list_item_selected(self, event):
    2230                  if self.__select_callback is not None:
    2231                          self.__select_callback(event)
    2232                  else:
    2233                          event.Skip()
    2234
    2235          #------------------------------------------------------------
    2236 -        def _on_list_item_deselected(self, event):
    2237                  if self.__deselect_callback is not None:
    2238                          self.__deselect_callback(event)
    2239                  else:
    2240                          event.Skip()
    2241
    2242          #------------------------------------------------------------
    2243 -        def _on_list_item_rightclicked(self, event):
    2244                  event.Skip()
    2245                  self.__show_context_menu(event.Index)
    2246
    2247          #------------------------------------------------------------
    2248 -        def _on_list_key_down(self, evt):
    2249                  evt.Skip()
    2250
    2251                  if evt.KeyCode == wx.WXK_DELETE:
    2252                          self.__handle_delete()
    2253                          return
    2254
    2255                  if evt.KeyCode == wx.WXK_INSERT:
    2256                          self.__handle_insert()
    2257                          return
    2258
    2259                  if evt.KeyCode == wx.WXK_MENU:
    2260                          self.__show_context_menu(evt.Index)
    2261                          return
    2262
    2263          #------------------------------------------------------------
    2264 -        def _on_char(self, evt):
    2265
    2266                  if chr(evt.GetRawKeyCode()) == 'f':
    2267                          if evt.GetModifiers() == wx.MOD_CMD:
    2268                                  #print "search dialog invoked"
    2269                                  self.__show_search_dialog()
    2270                                  return
    2271
    2272                  if chr(evt.GetRawKeyCode()) == 'n':
    2273                          if evt.GetModifiers() == wx.MOD_CMD:
    2274                                  #print "search-next key invoked"
    2275                                  self.__search_match()
    2276                                  return
    2277
    2278                  evt.Skip()
    2279                  return
    2280
    2281          #------------------------------------------------------------
    2282 -        def _on_mouse_motion(self, event):
    2283                  """Update tooltip on mouse motion.
    2284
    2285                          for s in dir(wx):
    2286                                  if s.startswith('LIST_HITTEST'):
    2287                                          print s, getattr(wx, s)
    2288
    2289                          LIST_HITTEST_ABOVE 1
    2290                          LIST_HITTEST_BELOW 2
    2291                          LIST_HITTEST_NOWHERE 4
    2292                          LIST_HITTEST_ONITEM 672
    2293                          LIST_HITTEST_ONITEMICON 32
    2294                          LIST_HITTEST_ONITEMLABEL 128
    2295                          LIST_HITTEST_ONITEMRIGHT 256
    2296                          LIST_HITTEST_ONITEMSTATEICON 512
    2297                          LIST_HITTEST_TOLEFT 1024
    2298                          LIST_HITTEST_TORIGHT 2048
    2299                  """
    2300                  item_idx, where_flag = self.HitTest(wx.Point(event.X, event.Y))
    2301
    2302                  # pointer on item related area at all ?
    2303                  if where_flag not in [
    2304                          wx.LIST_HITTEST_ONITEMLABEL,
    2305                          wx.LIST_HITTEST_ONITEMICON,
    2306                          wx.LIST_HITTEST_ONITEMSTATEICON,
    2307                          wx.LIST_HITTEST_ONITEMRIGHT,
    2308                          wx.LIST_HITTEST_ONITEM
    2309                  ]:
    2310                          self.__tt_last_item = None                                              # not on any item
    2311                          self.SetToolTip(self.__tt_static_part)
    2312                          return
    2313
    2314                  # same item as last time around ?
    2315                  if self.__tt_last_item == item_idx:
    2316                          return
    2317
    2318                  # remeber the new item we are on
    2319                  self.__tt_last_item = item_idx
    2320
    2321                  # HitTest() can return -1 if it so pleases, meaning that no item
    2322                  # was hit or else that maybe there aren't any items (empty list)
    2323                  if item_idx == wx.NOT_FOUND:
    2324                          self.SetToolTip(self.__tt_static_part)
    2325                          return
    2326
    2327                  # do we *have* item data ?
    2328                  if self.__data is None:
    2329                          self.SetToolTip(self.__tt_static_part)
    2330                          return
    2331
    2332                  # under some circumstances the item_idx returned
    2333                  # by HitTest() may be out of bounds with respect to
    2334                  # self.__data, this hints at a sync problem between
    2335                  # setting display items and associated data
    2336                  if (
    2337                          (item_idx > (len(self.__data) - 1))
    2338                                  or
    2339                          (item_idx < -1)
    2340                  ):
    2341                          self.SetToolTip(self.__tt_static_part)
    2342                          print("*************************************************************")
    2343                          print("GNUmed has detected an inconsistency with list item tooltips.")
    2344                          print("")
    2345                          print("This is not a big problem and you can keep working.")
    2346                          print("")
    2347                          print("However, please send us the following so we can fix GNUmed:")
    2348                          print("")
    2349                          print("item idx: %s" % item_idx)
    2350                          print('where flag: %s' % where_flag)
    2351                          print('data list length: %s' % len(self.__data))
    2352                          print("*************************************************************")
    2353                          return
    2354
    2355                  dyna_tt = None
    2356                  if self.__item_tooltip_callback is not None:
    2357                          dyna_tt = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(item_idx)])
    2358
    2359                  if dyna_tt is None:
    2360                          self.SetToolTip(self.__tt_static_part)
    2361                          return
    2362
    2363                  self.SetToolTip(dyna_tt)
    2364
    2365          #------------------------------------------------------------
    2366          # context menu event hendlers
    2367          #------------------------------------------------------------
    2368 -        def _on_add_row(self, evt):
    2369                  evt.Skip()
    2370                  self.__handle_insert()
    2371
    2372          #------------------------------------------------------------
    2373 -        def _on_edit_row(self, evt):
    2374                  evt.Skip()
    2375                  self.__handle_edit()
    2376
    2377          #------------------------------------------------------------
    2378 -        def _on_delete_row(self, evt):
    2379                  evt.Skip()
    2380                  self.__handle_delete()
    2381
    2382          #------------------------------------------------------------
    2383 -        def _on_show_search_dialog(self, evt):
    2384                  evt.Skip()
    2385                  self.__show_search_dialog()
    2386
    2387          #------------------------------------------------------------
    2388 -        def _on_search_match(self, evt):
    2389                  evt.Skip()
    2390                  self.__search_match()
    2391
    2392          #------------------------------------------------------------
    2393 -        def _all_rows2file(self, evt):
    2394
    2395                  txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-all_rows-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
    2396                  txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
    2397
    2398                  col_labels = self.column_labels
    2399                  line = '%s' % ' || '.join(col_labels)
    2400                  txt_file.write('%s\n' % line)
    2401                  txt_file.write(('=' * len(line)) + '\n')
    2402
    2403                  for item_idx in range(self.ItemCount):
    2404                          fields = []
    2405                          for col_idx in range(self.ColumnCount):
    2406                                  fields.append(self.GetItem(item_idx, col_idx).Text)
    2407                          txt_file.write('%s\n' % ' || '.join(fields))
    2408
    2409                  txt_file.close()
    2410                  gmDispatcher.send(signal = 'statustext', msg = _('All rows saved to [%s].') % txt_name)
    2411
    2412          #------------------------------------------------------------
    2413 -        def _all_rows2csv(self, evt):
    2414
    2415                  csv_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-all_rows-%s.csv' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
    2416                  csv_file = io.open(csv_name, mode = 'wb')
    2417
    2418                  csv_writer = csv.writer(csv_file)
    2419                  csv_writer.writerow([ l.encode('utf-8') for l in self.column_labels ])
    2420                  for item_idx in range(self.ItemCount):
    2421                          fields = []
    2422                          for col_idx in range(self.ColumnCount):
    2423                                  fields.append(self.GetItem(item_idx, col_idx).Text)
    2424                          csv_writer.writerow([ f.encode('utf-8') for f in fields ])
    2425
    2426                  csv_file.close()
    2427                  gmDispatcher.send(signal = 'statustext', msg = _('All rows saved to [%s].') % csv_name)
    2428
    2429          #------------------------------------------------------------
    2430 -        def _all_row_tooltips2file(self, evt):
    2431
    2432                  if (self.__data is None) or (self.__item_tooltip_callback is None):
    2433                          return
    2434
    2435                  txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-list_tooltips-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
    2436                  txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
    2437
    2438                  for data in self.data:
    2439                          tt = self.__item_tooltip_callback(data)
    2440                          if tt is None:
    2441                                  continue
    2442                          txt_file.write('%s\n\n' % tt)
    2443
    2444                  txt_file.close()
    2445                  gmDispatcher.send(signal = 'statustext', msg = _('All tooltips saved to [%s].') % txt_name)
    2446
    2447          #------------------------------------------------------------
    2448 -        def _all_row_data2file(self, evt):
    2449
    2450                  if self.__data is None:
    2451                          return
    2452
    2453                  txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-list_data-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
    2454                  txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
    2455
    2456                  for data in self.data:
    2457                          if hasattr(data, 'format'):
    2458                                  txt = data.format()
    2459                                  if type(txt) is list:
    2460                                          txt = '\n'.join(txt)
    2461                          else:
    2462                                  txt = '%s' % data
    2463                          txt_file.write('%s\n\n' % txt)
    2464
    2465                  txt_file.close()
    2466                  gmDispatcher.send(signal = 'statustext', msg = _('All data saved to [%s].') % txt_name)
    2467
    2468          #------------------------------------------------------------
    2469 -        def _selected_rows2file(self, evt):
    2470
    2471                  txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-some_rows-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
    2472                  txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
    2473
    2474                  col_labels = self.column_labels
    2475                  line = '%s' % ' || '.join(col_labels)
    2476                  txt_file.write('%s\n' % line)
    2477                  txt_file.write(('=' * len(line)) + '\n')
    2478
    2479                  items = self.selected_items
    2480                  if self.__is_single_selection:
    2481                          items = [items]
    2482
    2483                  for item_idx in items:
    2484                          fields = []
    2485                          for col_idx in range(self.ColumnCount):
    2486                                  fields.append(self.GetItem(item_idx, col_idx).Text)
    2487                          txt_file.write('%s\n' % ' || '.join(fields))
    2488
    2489                  txt_file.close()
    2490                  gmDispatcher.send(signal = 'statustext', msg = _('Selected rows saved to [%s].') % txt_name)
    2491
    2492          #------------------------------------------------------------
    2493 -        def _selected_rows2csv(self, evt):
    2494
    2495                  csv_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-some_rows-%s.csv' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
    2496                  csv_file = io.open(csv_name, mode = 'wb')
    2497
    2498                  csv_writer = csv.writer(csv_file)
    2499                  csv_writer.writerow([ l.encode('utf-8') for l in self.column_labels ])
    2500
    2501                  items = self.selected_items
    2502                  if self.__is_single_selection:
    2503                          items = [items]
    2504
    2505                  for item_idx in items:
    2506                          fields = []
    2507                          for col_idx in range(self.ColumnCount):
    2508                                  fields.append(self.GetItem(item_idx, col_idx).Text)
    2509                          csv_writer.writerow([ f.encode('utf-8') for f in fields ])
    2510
    2511                  csv_file.close()
    2512                  gmDispatcher.send(signal = 'statustext', msg = _('Selected rows saved to [%s].') % csv_name)
    2513
    2514          #------------------------------------------------------------
    2515 -        def _selected_row_tooltips2file(self, evt):
    2516
    2517                  if (self.__data is None) or (self.__item_tooltip_callback is None):
    2518                          return
    2519
    2520                  txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-list_tooltips-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
    2521                  txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
    2522
    2523                  for data in self.selected_item_data:
    2524                          tt = self.__item_tooltip_callback(data)
    2525                          if tt is None:
    2526                                  continue
    2527                          txt_file.write('%s\n\n' % tt)
    2528
    2529                  txt_file.close()
    2530                  gmDispatcher.send(signal = 'statustext', msg = _('Selected tooltips saved to [%s].') % txt_name)
    2531
    2532          #------------------------------------------------------------
    2533 -        def _selected_row_data2file(self, evt):
    2534
    2535                  if self.__data is None:
    2536                          return
    2537
    2538                  txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-list_data-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
    2539                  txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
    2540
    2541                  for data in self.selected_item_data:
    2542                          if hasattr(data, 'format'):
    2543                                  txt = data.format()
    2544                                  if type(txt) is list:
    2545                                          txt = '\n'.join(txt)
    2546                          else:
    2547                                  txt = '%s' % data
    2548                          txt_file.write('%s\n\n' % txt)
    2549
    2550                  txt_file.close()
    2551                  gmDispatcher.send(signal = 'statustext', msg = _('Selected data saved to [%s].') % txt_name)
    2552
    2553          #------------------------------------------------------------
    2554 -        def _tooltip2clipboard(self, evt):
    2555                  if wx.TheClipboard.IsOpened():
    2556                          _log.debug('clipboard already open')
    2557                          return
    2558                  if not wx.TheClipboard.Open():
    2559                          _log.debug('cannot open clipboard')
    2560                          return
    2561                  data_obj = wx.TextDataObject()
    2562
    2563                  if (self.__data is None) or (self.__item_tooltip_callback is None):
    2564                          txt = self.__tt_static_part
    2565                  else:
    2566                          txt = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(self._rclicked_row_idx)])
    2567                          if txt is None:
    2568                                  txt = self.__tt_static_part
    2569
    2570                  data_obj.SetText(txt)
    2571                  wx.TheClipboard.SetData(data_obj)
    2572                  wx.TheClipboard.Close()
    2573
    2574          #------------------------------------------------------------
    2575 -        def _tooltips2clipboard(self, evt):
    2576                  if wx.TheClipboard.IsOpened():
    2577                          _log.debug('clipboard already open')
    2578                          return
    2579                  if not wx.TheClipboard.Open():
    2580                          _log.debug('cannot open clipboard')
    2581                          return
    2582
    2583                  if (self.__data is None) or (self.__item_tooltip_callback is None):
    2584                          return
    2585
    2586                  tts = []
    2587                  for data in self.selected_item_data:
    2588                          tt = self.__item_tooltip_callback(data)
    2589                          if tt is None:
    2590                                  continue
    2591                          tts.append(tt)
    2592
    2593                  if len(tts) == 0:
    2594                          return
    2595
    2596                  data_obj = wx.TextDataObject()
    2597                  data_obj.SetText('\n\n'.join(tts))
    2598                  wx.TheClipboard.SetData(data_obj)
    2599                  wx.TheClipboard.Close()
    2600
    2601          #------------------------------------------------------------
    2602 -        def _add_tooltip2clipboard(self, evt):
    2603                  if wx.TheClipboard.IsOpened():
    2604                          _log.debug('clipboard already open')
    2605                          return
    2606                  if not wx.TheClipboard.Open():
    2607                          _log.debug('cannot open clipboard')
    2608                          return
    2609                  data_obj = wx.TextDataObject()
    2610
    2611                  txt = ''
    2612                  # get previous text
    2613                  got_it = wx.TheClipboard.GetData(data_obj)
    2614                  if got_it:
    2615                          txt = data_obj.Text + '\n'
    2616
    2617                  # add text
    2618                  if (self.__data is None) or (self.__item_tooltip_callback is None):
    2619                          txt += self.__tt_static_part
    2620                  else:
    2621                          tmp = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(self._rclicked_row_idx)])
    2622                          if tmp is None:
    2623                                  txt += self.__tt_static_part
    2624                          else:
    2625                                  txt += tmp
    2626
    2627                  # set text
    2628                  data_obj.SetText(txt)
    2629                  wx.TheClipboard.SetData(data_obj)
    2630                  wx.TheClipboard.Close()
    2631
    2632          #------------------------------------------------------------
    2633 -        def _add_tooltips2clipboard(self, evt):
    2634                  if wx.TheClipboard.IsOpened():
    2635                          _log.debug('clipboard already open')
    2636                          return
    2637                  if not wx.TheClipboard.Open():
    2638                          _log.debug('cannot open clipboard')
    2639                          return
    2640
    2641                  if (self.__data is None) or (self.__item_tooltip_callback is None):
    2642                          return
    2643
    2644                  tts = []
    2645                  for data in self.selected_item_data:
    2646                          tt = self.__item_tooltip_callback(data)
    2647                          if tt is None:
    2648                                  continue
    2649                          tts.append(tt)
    2650
    2651                  if len(tts) == 0:
    2652                          return
    2653
    2654                  data_obj = wx.TextDataObject()
    2655                  txt = ''
    2656                  # get previous text
    2657                  got_it = wx.TheClipboard.GetData(data_obj)
    2658                  if got_it:
    2659                          txt = data_obj.Text + '\n\n'
    2660                  txt += '\n\n'.join(tts)
    2661
    2662                  data_obj.SetText(txt)
    2663                  wx.TheClipboard.SetData(data_obj)
    2664                  wx.TheClipboard.Close()
    2665
    2666          #------------------------------------------------------------
    2667 -        def _row2clipboard(self, evt):
    2668                  if wx.TheClipboard.IsOpened():
    2669                          _log.debug('clipboard already open')
    2670                          return
    2671                  if not wx.TheClipboard.Open():
    2672                          _log.debug('cannot open clipboard')
    2673                          return
    2674                  data_obj = wx.TextDataObject()
    2675                  data_obj.SetText(' // '.join(self._rclicked_row_cells))
    2676                  wx.TheClipboard.SetData(data_obj)
    2677                  wx.TheClipboard.Close()
    2678
    2679          #------------------------------------------------------------
    2680 -        def _rows2clipboard(self, evt):
    2681                  if wx.TheClipboard.IsOpened():
    2682                          _log.debug('clipboard already open')
    2683                          return
    2684                  if not wx.TheClipboard.Open():
    2685                          _log.debug('cannot open clipboard')
    2686                          return
    2687
    2688                  rows = []
    2689                  for item_idx in self.selected_items:
    2690                          rows.append(' // '.join([ self.GetItem(item_idx, col_idx).Text.strip() for col_idx in range(self.ColumnCount) ]))
    2691
    2692                  data_obj = wx.TextDataObject()
    2693                  data_obj.SetText('\n\n'.join(rows))
    2694                  wx.TheClipboard.SetData(data_obj)
    2695                  wx.TheClipboard.Close()
    2696
    2697          #------------------------------------------------------------
    2698 -        def _add_row2clipboard(self, evt):
    2699                  if wx.TheClipboard.IsOpened():
    2700                          _log.debug('clipboard already open')
    2701                          return
    2702                  if not wx.TheClipboard.Open():
    2703                          _log.debug('cannot open clipboard')
    2704                          return
    2705                  data_obj = wx.TextDataObject()
    2706
    2707                  txt = ''
    2708                  # get previous text
    2709                  got_it = wx.TheClipboard.GetData(data_obj)
    2710                  if got_it:
    2711                          txt = data_obj.Text + '\n'
    2712
    2713                  # add text
    2714                  txt += ' // '.join(self._rclicked_row_cells)
    2715
    2716                  # set text
    2717                  data_obj.SetText(txt)
    2718                  wx.TheClipboard.SetData(data_obj)
    2719                  wx.TheClipboard.Close()
    2720
    2721          #------------------------------------------------------------
    2722 -        def _add_rows2clipboard(self, evt):
    2723                  if wx.TheClipboard.IsOpened():
    2724                          _log.debug('clipboard already open')
    2725                          return
    2726                  if not wx.TheClipboard.Open():
    2727                          _log.debug('cannot open clipboard')
    2728                          return
    2729
    2730                  rows = []
    2731                  for item_idx in self.selected_items:
    2732                          rows.append(' // '.join([ self.GetItem(item_idx, col_idx).Text.strip() for col_idx in range(self.ColumnCount) ]))
    2733
    2734                  data_obj = wx.TextDataObject()
    2735
    2736                  txt = ''
    2737                  # get previous text
    2738                  got_it = wx.TheClipboard.GetData(data_obj)
    2739                  if got_it:
    2740                          txt = data_obj.Text + '\n'
    2741                  txt += '\n\n'.join(rows)
    2742
    2743                  data_obj.SetText(txt)
    2744                  wx.TheClipboard.SetData(data_obj)
    2745                  wx.TheClipboard.Close()
    2746
    2747          #------------------------------------------------------------
    2748 -        def _row_list2clipboard(self, evt):
    2749                  if wx.TheClipboard.IsOpened():
    2750                          _log.debug('clipboard already open')
    2751                          return
    2752                  if not wx.TheClipboard.Open():
    2753                          _log.debug('cannot open clipboard')
    2754                          return
    2755                  data_obj = wx.TextDataObject()
    2756                  data_obj.SetText('\n'.join(self._rclicked_row_cells_w_hdr))
    2757                  wx.TheClipboard.SetData(data_obj)
    2758                  wx.TheClipboard.Close()
    2759
    2760          #------------------------------------------------------------
    2761 -        def _add_row_list2clipboard(self, evt):
    2762                  if wx.TheClipboard.IsOpened():
    2763                          _log.debug('clipboard already open')
    2764                          return
    2765                  if not wx.TheClipboard.Open():
    2766                          _log.debug('cannot open clipboard')
    2767                          return
    2768                  data_obj = wx.TextDataObject()
    2769
    2770                  txt = ''
    2771                  # get previous text
    2772                  got_it = wx.TheClipboard.GetData(data_obj)
    2773                  if got_it:
    2774                          txt = data_obj.Text + '\n'
    2775
    2776                  # add text
    2777                  txt += '\n'.join(self._rclicked_row_cells_w_hdr)
    2778
    2779                  # set text
    2780                  data_obj.SetText(txt)
    2781                  wx.TheClipboard.SetData(data_obj)
    2782                  wx.TheClipboard.Close()
    2783
    2784          #------------------------------------------------------------
    2785 -        def _data2clipboard(self, evt):
    2786                  if wx.TheClipboard.IsOpened():
    2787                          _log.debug('clipboard already open')
    2788                          return
    2789                  if not wx.TheClipboard.Open():
    2790                          _log.debug('cannot open clipboard')
    2791                          return
    2792                  data_obj = wx.TextDataObject()
    2793                  txt = self._rclicked_row_data.format()
    2794                  if type(txt) == type([]):
    2795                          txt = '\n'.join(txt)
    2796                  data_obj.SetText(txt)
    2797                  wx.TheClipboard.SetData(data_obj)
    2798                  wx.TheClipboard.Close()
    2799
    2800          #------------------------------------------------------------
    2801 -        def _datas2clipboard(self, evt):
    2802                  if wx.TheClipboard.IsOpened():
    2803                          _log.debug('clipboard already open')
    2804                          return
    2805                  if not wx.TheClipboard.Open():
    2806                          _log.debug('cannot open clipboard')
    2807                          return
    2808
    2809                  data_as_txt = []
    2810                  for data in self.selected_item_data:
    2811                          if hasattr(data, 'format'):
    2812                                  txt = data.format()
    2813                                  if type(txt) is list:
    2814                                          txt = '\n'.join(txt)
    2815                          else:
    2816                                  txt = '%s' % data
    2817                          data_as_txt.append(txt)
    2818
    2819                  data_obj = wx.TextDataObject()
    2820                  data_obj.SetText('\n\n'.join(data_as_txt))
    2821                  wx.TheClipboard.SetData(data_obj)
    2822                  wx.TheClipboard.Close()
    2823
    2824          #------------------------------------------------------------
    2825 -        def _add_data2clipboard(self, evt):
    2826                  if wx.TheClipboard.IsOpened():
    2827                          _log.debug('clipboard already open')
    2828                          return
    2829                  if not wx.TheClipboard.Open():
    2830                          _log.debug('cannot open clipboard')
    2831                          return
    2832                  data_obj = wx.TextDataObject()
    2833
    2834                  txt = ''
    2835                  # get previous text
    2836                  got_it = wx.TheClipboard.GetData(data_obj)
    2837                  if got_it:
    2838                          txt = data_obj.Text + '\n'
    2839
    2840                  # add text
    2841                  tmp = self._rclicked_row_data.format()
    2842                  if type(tmp) == type([]):
    2843                          txt += '\n'.join(tmp)
    2844                  else:
    2845                          txt += tmp
    2846
    2847                  # set text
    2848                  data_obj.SetText(txt)
    2849                  wx.TheClipboard.SetData(data_obj)
    2850                  wx.TheClipboard.Close()
    2851
    2852          #------------------------------------------------------------
    2853 -        def _add_datas2clipboard(self, evt):
    2854                  if wx.TheClipboard.IsOpened():
    2855                          _log.debug('clipboard already open')
    2856                          return
    2857                  if not wx.TheClipboard.Open():
    2858                          _log.debug('cannot open clipboard')
    2859                          return
    2860
    2861                  data_as_txt = []
    2862                  for data in self.selected_item_data:
    2863                          if hasattr(data, 'format'):
    2864                                  txt = data.format()
    2865                                  if type(txt) is list:
    2866                                          txt = '\n'.join(txt)
    2867                          else:
    2868                                  txt = '%s' % data
    2869                          data_as_txt.append(txt)
    2870
    2871                  data_obj = wx.TextDataObject()
    2872                  txt = ''
    2873                  # get previous text
    2874                  got_it = wx.TheClipboard.GetData(data_obj)
    2875                  if got_it:
    2876                          txt = data_obj.Text + '\n'
    2877                  txt += '\n'.join(data_as_txt)
    2878
    2879                  # set text
    2880                  data_obj.SetText(txt)
    2881                  wx.TheClipboard.SetData(data_obj)
    2882                  wx.TheClipboard.Close()
    2883
    2884          #------------------------------------------------------------
    2885 -        def _col2clipboard(self, evt):
    2886                  if wx.TheClipboard.IsOpened():
    2887                          _log.debug('clipboard already open')
    2888                          return
    2889                  if not wx.TheClipboard.Open():
    2890                          _log.debug('cannot open clipboard')
    2891                          return
    2892                  data_obj = wx.TextDataObject()
    2893
    2894                  #col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.split(u':', 1)[0].rstrip(u':')) - 1
    2895                  col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.rsplit('#', 1)[1].rstrip(']'))
    2896                  txt = self._rclicked_row_cells[col_idx]
    2897
    2898                  data_obj.SetText(txt)
    2899                  wx.TheClipboard.SetData(data_obj)
    2900                  wx.TheClipboard.Close()
    2901
    2902          #------------------------------------------------------------
    2903 -        def _add_col2clipboard(self, evt):
    2904                  if wx.TheClipboard.IsOpened():
    2905                          _log.debug('clipboard already open')
    2906                          return
    2907                  if not wx.TheClipboard.Open():
    2908                          _log.debug('cannot open clipboard')
    2909                          return
    2910                  data_obj = wx.TextDataObject()
    2911
    2912                  txt = ''
    2913                  # get previous text
    2914                  got_it = wx.TheClipboard.GetData(data_obj)
    2915                  if got_it:
    2916                          txt = data_obj.Text + '\n'
    2917
    2918                  # add text
    2919                  #col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.split(u':', 1)[0].rstrip(u':')) - 1
    2920                  col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.rsplit('#', 1)[1].rstrip(']'))
    2921                  txt += self._rclicked_row_cells[col_idx]
    2922
    2923                  # set text
    2924                  data_obj.SetText(txt)
    2925                  wx.TheClipboard.SetData(data_obj)
    2926                  wx.TheClipboard.Close()
    2927
    2928          #------------------------------------------------------------
    2929 -        def _col_w_hdr2clipboard(self, evt):
    2930                  if wx.TheClipboard.IsOpened():
    2931                          _log.debug('clipboard already open')
    2932                          return
    2933                  if not wx.TheClipboard.Open():
    2934                          _log.debug('cannot open clipboard')
    2935                          return
    2936                  data_obj = wx.TextDataObject()
    2937
    2938                  #col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.split(u':', 1)[0].rstrip(u':')) - 1
    2939                  col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.rsplit('#', 1)[1].rstrip(']'))
    2940                  txt = self._rclicked_row_cells_w_hdr[col_idx]
    2941
    2942                  data_obj.SetText(txt)
    2943                  wx.TheClipboard.SetData(data_obj)
    2944                  wx.TheClipboard.Close()
    2945
    2946          #------------------------------------------------------------
    2947 -        def _add_col_w_hdr2clipboard(self, evt):
    2948                  if wx.TheClipboard.IsOpened():
    2949                          _log.debug('clipboard already open')
    2950                          return
    2951                  if not wx.TheClipboard.Open():
    2952                          _log.debug('cannot open clipboard')
    2953                          return
    2954                  data_obj = wx.TextDataObject()
    2955
    2956                  txt = ''
    2957                  # get previous text
    2958                  got_it = wx.TheClipboard.GetData(data_obj)
    2959                  if got_it:
    2960                          txt = data_obj.Text + '\n'
    2961
    2962                  # add text
    2963                  #col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.split(u':', 1)[0].rstrip(u':')) - 1
    2964                  col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.rsplit('#', 1)[1].rstrip(']'))
    2965                  txt += self._rclicked_row_cells_w_hdr[col_idx]
    2966
    2967                  # set text
    2968                  data_obj.SetText(txt)
    2969                  wx.TheClipboard.SetData(data_obj)
    2970                  wx.TheClipboard.Close()
    2971
    2972          #------------------------------------------------------------
    2973          # search related methods
    2974          #------------------------------------------------------------
    2975  #       def _on_lost_focus(self, evt):
    2976  #               evt.Skip()
    2977  #               if self.__search_dlg is None:
    2978  #                       return
    2979  ##              print self.FindFocus()
    2980  ##              print self.__search_dlg
    2981  #               #self.__search_dlg.Close()
    2982
    2983          #------------------------------------------------------------
    2984 -        def __search_match(self):
    2985                  #print "search_match"
    2986                  if self.__search_term is None:
    2987                          #print "no search term"
    2988                          return False
    2989                  if self.__search_term.strip() == '':
    2990                          #print "empty search term"
    2991                          return False
    2992                  if self.__searchable_cols is None:
    2993                          #print "searchable cols not initialized, now setting"
    2994                          self.searchable_columns = None
    2995                  if len(self.__searchable_cols) == 0:
    2996                          #print "no cols to search"
    2997                          return False
    2998
    2999                  #print "on searching for match on:", self.__search_term
    3000                  for row_idx in range(self.__next_line_to_search, self.ItemCount):
    3001                          for col_idx in range(self.ColumnCount):
    3002                                  if col_idx not in self.__searchable_cols:
    3003                                          continue
    3004                                  col_val = self.GetItem(row_idx, col_idx).GetText()
    3005                                  if regex.search(self.__search_term, col_val, regex.U | regex.I) is not None:
    3006                                          self.Select(row_idx)
    3007                                          self.EnsureVisible(row_idx)
    3008                                          if row_idx == self.ItemCount - 1:
    3009                                                  # wrap around
    3010                                                  self.__next_line_to_search = 0
    3011                                          else:
    3012                                                  self.__next_line_to_search = row_idx + 1
    3013                                          return True
    3014                  # wrap around
    3015                  self.__next_line_to_search = 0
    3016                  return False
    3017
    3018          #------------------------------------------------------------
    3019 -        def _set_searchable_cols(self, cols):
    3020                  #print "setting searchable cols to:", cols
    3021                  # zero-based list of which columns to search
    3022                  if cols is None:
    3023                          #print "setting searchable cols to:", range(self.ColumnCount)
    3024                          self.__searchable_cols = range(self.ColumnCount)
    3025                          return
    3026                  # weed out columns to be searched which
    3027                  # don't exist and uniquify them
    3028                  new_cols = {}
    3029                  for col in cols:
    3030                          if col < self.ColumnCount:
    3031                                  new_cols[col] = True
    3032                  #print "actually setting searchable cols to:", new_cols.keys()
    3033                  self.__searchable_cols = new_cols.keys()
    3034
    3035          searchable_columns = property(lambda x:x, _set_searchable_cols)
    3036
    3037          #------------------------------------------------------------
    3038          # properties
    3039          #------------------------------------------------------------
    3040 -        def _get_activate_callback(self):
    3041                  return self.__activate_callback
    3042
    3043 -        def _set_activate_callback(self, callback):
    3044                  if callback is None:
    3045                          self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED)
    3046                          self.__activate_callback = None
    3047                          return
    3048                  if not callable(callback):
    3049                          raise ValueError('<activate> callback is not a callable: %s' % callback)
    3050                  self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated)
    3051                  self.__activate_callback = callback
    3052
    3053          activate_callback = property(_get_activate_callback, _set_activate_callback)
    3054
    3055          #------------------------------------------------------------
    3056 -        def _get_select_callback(self):
    3057                  return self.__select_callback
    3058
    3059 -        def _set_select_callback(self, callback):
    3060                  if callback is None:
    3061                          self.Unbind(wx.EVT_LIST_ITEM_SELECTED)
    3062                          self.__select_callback = None
    3063                          return
    3064                  if not callable(callback):
    3065                          raise ValueError('<selected> callback is not a callable: %s' % callback)
    3066                  self.Bind(wx.EVT_LIST_ITEM_SELECTED, self._on_list_item_selected)
    3067                  self.__select_callback = callback
    3068
    3069          select_callback = property(_get_select_callback, _set_select_callback)
    3070
    3071          #------------------------------------------------------------
    3072 -        def _get_deselect_callback(self):
    3073                  return self.__deselect_callback
    3074
    3075 -        def _set_deselect_callback(self, callback):
    3076                  if callback is None:
    3077                          self.Unbind(wx.EVT_LIST_ITEM_DESELECTED)
    3078                          self.__deselect_callback = None
    3079                          return
    3080                  if not callable(callback):
    3081                          raise ValueError('<deselected> callback is not a callable: %s' % callback)
    3082                  self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self._on_list_item_deselected)
    3083                  self.__deselect_callback = callback
    3084
    3085          deselect_callback = property(_get_deselect_callback, _set_deselect_callback)
    3086
    3087          #------------------------------------------------------------
    3088 -        def _get_delete_callback(self):
    3089                  return self.__delete_callback
    3090
    3091 -        def _set_delete_callback(self, callback):
    3092                  if callback is None:
    3093                          #self.Unbind(wx.EVT_LIST_ITEM_SELECTED)
    3094                          self.__delete_callback = None
    3095                          return
    3096                  if not callable(callback):
    3097                          raise ValueError('<delete> callback is not a callable: %s' % callback)
    3098                  #self.Bind(wx.EVT_LIST_ITEM_SELECTED, self._on_list_item_selected)
    3099                  self.__delete_callback = callback
    3100
    3101          delete_callback = property(_get_delete_callback, _set_delete_callback)
    3102
    3103          #------------------------------------------------------------
    3104 -        def _get_new_callback(self):
    3105                  return self.__new_callback
    3106
    3107 -        def _set_new_callback(self, callback):
    3108                  if callback is None:
    3109                          self.__new_callback = None
    3110                          return
    3111                  if not callable(callback):
    3112                          raise ValueError('<new> callback is not a callable: %s' % callback)
    3113                  self.__new_callback = callback
    3114
    3115          new_callback = property(_get_new_callback, _set_new_callback)
    3116
    3117          #------------------------------------------------------------
    3118 -        def _get_edit_callback(self):
    3119                  return self.__edit_callback
    3120
    3121 -        def _set_edit_callback(self, callback):
    3122                  if callback is None:
    3123                          self.__edit_callback = None
    3124                          return
    3125                  if not callable(callback):
    3126                          raise ValueError('<edit> callback is not a callable: %s' % callback)
    3127                  self.__edit_callback = callback
    3128
    3129          edit_callback = property(_get_edit_callback, _set_edit_callback)
    3130
    3131          #------------------------------------------------------------
    3132 -        def _set_item_tooltip_callback(self, callback):
    3133                  if callback is not None:
    3134                          if not callable(callback):
    3135                                  raise ValueError('<item_tooltip> callback is not a callable: %s' % callback)
    3136                  self.__item_tooltip_callback = callback
    3137
    3138          # the callback must be a function which takes a single argument
    3139          # the argument is the data for the item the tooltip is on
    3140          # the callback must return None if no item tooltip is to be shown
    3141          # otherwise it must return a string (possibly with \n)
    3142          item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
    3143
    3144          #------------------------------------------------------------
    3150
    3151          extend_popup_menu_callback = property(lambda x:x, _set_extend_popup_menu_callback)
    3152
    3153          #------------------------------------------------------------
    3154          # ColumnSorterMixin API
    3155          #------------------------------------------------------------
    3156 -        def GetListCtrl(self):
    3157                  if self.itemDataMap is None:
    3158                          self._update_sorting_metadata()
    3159                  return self                                                             # required
    3160
    3161          #------------------------------------------------------------
    3162 -        def OnSortOrderChanged(self):
    3163                  col_idx, is_ascending = self.GetSortState()
    3164                  if col_idx == -1:
    3165                          _log.debug('outside any column (idx: -1) clicked, ignoring')
    3166                          return
    3167                  self._remove_sorting_indicators_from_column_headers()
    3168                  col_state = self.GetColumn(col_idx)
    3169                  col_state.Text += self.sort_order_tags[is_ascending]
    3170                  self.SetColumn(col_idx, col_state)
    3171
    3172          #------------------------------------------------------------
    3173 -        def GetSecondarySortValues(self, primary_sort_col, primary_item1_idx, primary_item2_idx):
    3174                  return (primary_item1_idx, primary_item2_idx)
    3175
    3176                  if self.__secondary_sort_col is None:
    3177                          return (primary_item1_idx, primary_item2_idx)
    3178                  if self.__secondary_sort_col == primary_sort_col:
    3179                          return (primary_item1_idx, primary_item2_idx)
    3180
    3181                  secondary_val1 = self.itemDataMap[primary_item1_idx][self.__secondary_sort_col]
    3182                  secondary_val2 = self.itemDataMap[primary_item2_idx][self.__secondary_sort_col]
    3183
    3184                  if type(secondary_val1) == type('') and type(secondary_val2) == type(''):
    3185                          secondary_cmp_result = locale.strcoll(secondary_val1, secondary_val2)
    3186                  elif type(secondary_val1) == type('') or type(secondary_val2) == type(''):
    3187                          secondary_cmp_result = locale.strcoll(str(secondary_val1), str(secondary_val2))
    3188                  else:
    3189                          secondary_cmp_result = cmp(secondary_val1, secondary_val2)
    3190
    3191                  if secondary_cmp_result == 0:
    3192                          return (primary_item1_idx, primary_item2_idx)
    3193
    3194                  # make the secondary column always sort ascending
    3195                  currently_ascending = self._colSortFlag[primary_sort_col]
    3196                  if currently_ascending:
    3197                          secondary_val1, secondary_val2 = min(secondary_val1, secondary_val2), max(secondary_val1, secondary_val2)
    3198                  else:
    3199                          secondary_val1, secondary_val2 = max(secondary_val1, secondary_val2), min(secondary_val1, secondary_val2)
    3200                  return (secondary_val1, secondary_val2)
    3201
    3202          #------------------------------------------------------------
    3203 -        def _unicode_aware_column_sorter(self, item1, item2):
    3204                  # http://jtauber.com/blog/2006/01/27/python_unicode_collation_algorithm/
    3205                  # http://stackoverflow.com/questions/1097908/how-do-i-sort-unicode-strings-alphabetically-in-python
    3206                  # PyICU
    3207                  sort_col, is_ascending = self.GetSortState()
    3208                  data1 = self.itemDataMap[item1][sort_col]
    3209                  data2 = self.itemDataMap[item2][sort_col]
    3210                  if type(data1) == type('') and type(data2) == type(''):
    3211                          cmp_result = locale.strcoll(data1, data2)
    3212                  elif type(data1) == type('') or type(data2) == type(''):
    3213                          cmp_result = locale.strcoll(str(data1), str(data2))
    3214                  else:
    3215                          cmp_result = cmp(data1, data2)
    3216
    3217                  #direction = u'ASC'
    3218                  if not is_ascending:
    3219                          cmp_result = -1 * cmp_result
    3220                          #direction = u'DESC'
    3221                  # debug:
    3222  #               if cmp_result < 0:
    3223  #                       op1 = u'\u2191 ' # up
    3224  #                       op2 = u'\u2193' # down
    3225  #               elif cmp_result > 0:
    3226  #                       op2 = u'\u2191 ' # up
    3227  #                       op1 = u'\u2193' # down
    3228  #               else:
    3229  #                       op1 = u' = '
    3230  #                       op2 = u''
    3231  #               print u'%s: [%s]%s[%s]%s (%s)' % (direction, data1, op1, data2, op2, cmp_result)
    3232
    3233                  return cmp_result
    3234
    3235          # defining our own column sorter does not seem to make
    3236          # a difference over the default one until we resort to
    3237          # something other than locale.strcoll/strxform/cmp for
    3238          # actual sorting
    3239          #def GetColumnSorter(self):
    3240          #       return self._unicode_aware_column_sorter
    3241
    3242          #------------------------------------------------------------
    3243 -        def _generate_map_for_sorting(self):
    3244                  dict2sort = {}
    3245                  item_count = self.GetItemCount()
    3246                  if item_count == 0:
    3247                          return dict2sort
    3248                  col_count = self.GetColumnCount()
    3249                  for item_idx in range(item_count):
    3250                          dict2sort[item_idx] = ()
    3251                          if col_count == 0:
    3252                                  continue
    3253                          for col_idx in range(col_count):
    3254                                  dict2sort[item_idx] += (self.GetItem(item_idx, col_idx).GetText(), )
    3255                                  # debugging:
    3256                                  #print u'[%s:%s] ' % (item_idx, col_idx), self.GetItem(item_idx, col_idx).GetText()
    3257
    3258                  return dict2sort
    3259
    3260          #------------------------------------------------------------
    3261 -        def _remove_sorting_indicators_from_column_headers(self):
    3262                  for col_idx in range(self.ColumnCount):
    3263                          col_state = self.GetColumn(col_idx)
    3264                          initial_header = col_state.Text
    3265                          if col_state.Text.endswith(self.sort_order_tags[True]):
    3266                                  col_state.Text = col_state.Text[:-len(self.sort_order_tags[True])]
    3267                          if col_state.Text.endswith(self.sort_order_tags[False]):
    3268                                  col_state.Text = col_state.Text[:-len(self.sort_order_tags[False])]
    3269                          if col_state.Text == initial_header:
    3270                                  continue
    3271                          self.SetColumn(col_idx, col_state)
    3272
    3273          #------------------------------------------------------------
    3274 -        def _invalidate_sorting_metadata(self):
    3275                  self.itemDataMap = None
    3276                  self.SetColumnCount(self.GetColumnCount())
    3277                  self._remove_sorting_indicators_from_column_headers()
    3278
    3279          #------------------------------------------------------------
    3280 -        def _update_sorting_metadata(self):
    3281                  # MUST have this name
    3282                  self.itemDataMap = self._generate_map_for_sorting()
    3283
    3284          #------------------------------------------------------------
    3285 -        def _on_col_click(self, event):
    3286                  # this MUST NOT call event.Skip() or else the column sorter mixin#
    3287                  # will not kick in anymore under wxP 3
    3288                  #event.Skip()
    3289                  pass
    3290                  # debugging:
    3291  #               sort_col, order = self.GetSortState()
    3292  #               print u'col clicked: %s / sort col: %s / sort direction: %s / sort flags: %s' % (event.GetColumn(), sort_col, order, self._colSortFlag)
    3293  #               if self.itemDataMap is not None:
    3294  #                       print u'sort items data map:'
    3295  #                       for key, item in self.itemDataMap.items():
    3296  #                               print key, u' -- ', item
    3297
    3298          #------------------------------------------------------------
    3299 -        def __get_secondary_sort_col(self):
    3300                  return self.__secondary_sort_col
    3301
    3302 -        def __set_secondary_sort_col(self, col):
    3303                  if col is None:
    3304                          self.__secondary_sort_col = None
    3305                          return
    3306                  if col > self.GetColumnCount():
    3307                          raise ValueError('cannot secondary-sort on col [%s], there are only [%s] columns', col, self.GetColumnCount())
    3308                  self.__secondary_sort_col = col
    3309
    3310          secondary_sort_column = property(__get_secondary_sort_col, __set_secondary_sort_col)
    3311
    3312  #================================================================
    3313 -def shorten_text(text=None, max_length=None):
    3314          if len(text) <= max_length:
    3315                  return text
    3316          return text[:max_length-1] + '\u2026'
    3317
    3318  #================================================================
    3319  # main
    3320  #----------------------------------------------------------------
    3321  if __name__ == '__main__':
    3322
    3323          if len(sys.argv) < 2:
    3324                  sys.exit()
    3325
    3326          if sys.argv[1] != 'test':
    3327                  sys.exit()
    3328
    3329          sys.path.insert(0, '../../')
    3330
    3331          from Gnumed.pycommon import gmI18N
    3332          gmI18N.activate_locale()
    3333          gmI18N.install_domain()
    3334
    3335          #------------------------------------------------------------
    3336 -        def test_wxMultiChoiceDialog():
    3337                  app = wx.PyWidgetTester(size = (400, 500))
    3338                  dlg = wx.MultiChoiceDialog (
    3339                          parent = None,
    3340                          message = 'test message',
    3341                          caption = 'test caption',
    3342                          choices = ['a', 'b', 'c', 'd', 'e']
    3343                  )
    3344                  dlg.ShowModal()
    3345                  sels = dlg.GetSelections()
    3346                  print("selected:")
    3347                  for sel in sels:
    3348                          print(sel)
    3349
    3350          #------------------------------------------------------------
    3351 -        def test_get_choices_from_list():
    3352
    3353                  def edit(argument):
    3354                          print("editor called with:")
    3355                          print(argument)
    3356
    3357                  def refresh(lctrl):
    3358                          choices = ['a', 'b', 'c']
    3359                          lctrl.set_string_items(choices)
    3360
    3361                  app = wx.App()
    3362                  chosen = get_choices_from_list (
    3363  #                       msg = 'select a health issue\nfrom the list below\n',
    3364                          caption = 'select health issues',
    3365                          #choices = [['D.M.II', '4'], ['MS', '3'], ['Fraktur', '2']],
    3366                          #columns = ['issue', 'no of episodes']
    3367                          columns = ['issue'],
    3368                          refresh_callback = refresh,
    3369                          single_selection = False
    3370                          #, edit_callback = edit
    3371                  )
    3372                  print("chosen:")
    3373                  print(chosen)
    3374
    3375          #------------------------------------------------------------
    3376 -        def test_item_picker_dlg():
    3377                  #app = wx.PyWidgetTester(size = (200, 50))
    3378                  app = wx.App(size = (200, 50))
    3379                  dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:')
    3380                  dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy'])
    3381                  #dlg.set_columns(['Plugins'], [])
    3382                  dlg.set_string_items(['patient', 'emr', 'docs'])
    3383                  result = dlg.ShowModal()
    3384                  print(result)
    3385                  print(dlg.get_picks())
    3386
    3387          #------------------------------------------------------------
    3388          test_get_choices_from_list()
    3389          #test_wxMultiChoiceDialog()
    3390          #test_item_picker_dlg()
    3391
    3392  #================================================================
    3393