Gnumed.wxpython.gmListWidgets

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

# Source Code for Module Gnumed.wxpython.gmListWidgets

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