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