"""GNUmed list controls and widgets.
TODO:
From: Rob McMullen
To: address@hidden
Subject: Re: [wxPython-users] ANN: ColumnSizer mixin for ListCtrl
Thanks for all the suggestions, on and off line. There's an update
with a new name (ColumnAutoSizeMixin) and better sizing algorithm at:
http://trac.flipturn.org/browser/trunk/peppy/lib/column_autosize.py
"""
#================================================================
__author__ = "Karsten Hilbert "
__license__ = "GPL v2 or later"
import sys
import types
import logging
import wx
import wx.lib.mixins.listctrl as listmixins
_log = logging.getLogger('gm.scripting')
#================================================================
# FIXME: configurable callback on double-click action
def get_choices_from_list (
parent=None,
msg=None,
caption=None,
choices=None,
selections=None,
columns=None,
data=None,
edit_callback=None,
new_callback=None,
delete_callback=None,
refresh_callback=None,
single_selection=False,
can_return_empty=False,
ignore_OK_button=False,
left_extra_button=None,
middle_extra_button=None,
right_extra_button=None,
list_tooltip_callback=None):
"""Let user select item(s) from a list.
- new_callback: ()
- edit_callback: (item data)
- delete_callback: (item data)
- refresh_callback: (listctrl)
- list_tooltip_callback: (item data)
- left/middle/right_extra_button: (label, tooltip, )
is called with item_data as the only argument
returns:
on [CANCEL]: None
on [OK]:
if any items selected:
list of selected items
else:
if can_return_empty is True:
empty list
else:
None
"""
if caption is None:
caption = _('generic multi choice dialog')
if single_selection:
dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg, style = wx.LC_SINGLE_SEL)
else:
dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg)
dlg.refresh_callback = refresh_callback
dlg.edit_callback = edit_callback
dlg.new_callback = new_callback
dlg.delete_callback = delete_callback
dlg.list_tooltip_callback = list_tooltip_callback
dlg.ignore_OK_button = ignore_OK_button
dlg.left_extra_button = left_extra_button
dlg.middle_extra_button = middle_extra_button
dlg.right_extra_button = right_extra_button
dlg.set_columns(columns = columns)
if refresh_callback is None:
dlg.set_string_items(items = choices) # list ctrl will refresh anyway if possible
dlg.set_column_widths()
if data is not None:
dlg.set_data(data = data) # can override data set if refresh_callback is not None
if selections is not None:
dlg.set_selections(selections = selections)
dlg.can_return_empty = can_return_empty
btn_pressed = dlg.ShowModal()
sels = dlg.get_selected_item_data(only_one = single_selection)
dlg.Destroy()
if btn_pressed == wx.ID_OK:
if can_return_empty and (sels is None):
return []
return sels
return None
#----------------------------------------------------------------
from Gnumed.wxGladeWidgets import wxgGenericListSelectorDlg
class cGenericListSelectorDlg(wxgGenericListSelectorDlg.wxgGenericListSelectorDlg):
"""A dialog holding a list and a few buttons to act on the items."""
# FIXME: configurable callback on double-click action
def __init__(self, *args, **kwargs):
try:
msg = kwargs['msg']
del kwargs['msg']
except KeyError: msg = None
wxgGenericListSelectorDlg.wxgGenericListSelectorDlg.__init__(self, *args, **kwargs)
self.message = msg
self.left_extra_button = None
self.middle_extra_button = None
self.right_extra_button = None
self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled)
self.new_callback = None # called when NEW button pressed, no argument passed in
self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in
self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in
self.can_return_empty = False
self.ignore_OK_button = False # by default do show/use the OK button
#------------------------------------------------------------
def set_columns(self, columns=None):
self._LCTRL_items.set_columns(columns = columns)
#------------------------------------------------------------
def set_column_widths(self, widths=None):
self._LCTRL_items.set_column_widths(widths = widths)
#------------------------------------------------------------
def set_string_items(self, items = None):
self._LCTRL_items.set_string_items(items = items)
self._LCTRL_items.set_column_widths()
self._LCTRL_items.Select(0)
#------------------------------------------------------------
def set_selections(self, selections = None):
self._LCTRL_items.set_selections(selections = selections)
if selections is None:
return
if len(selections) == 0:
return
if self.ignore_OK_button:
return
self._BTN_ok.Enable(True)
self._BTN_ok.SetDefault()
#------------------------------------------------------------
def set_data(self, data = None):
self._LCTRL_items.set_data(data = data)
#------------------------------------------------------------
def get_selected_item_data(self, only_one=False):
return self._LCTRL_items.get_selected_item_data(only_one=only_one)
#------------------------------------------------------------
# event handlers
#------------------------------------------------------------
def _on_list_item_selected(self, event):
if not self.__ignore_OK_button:
self._BTN_ok.SetDefault()
self._BTN_ok.Enable(True)
if self.edit_callback is not None:
self._BTN_edit.Enable(True)
if self.delete_callback is not None:
self._BTN_delete.Enable(True)
#------------------------------------------------------------
def _on_list_item_deselected(self, event):
if self._LCTRL_items.get_selected_items(only_one=True) == -1:
if not self.can_return_empty:
self._BTN_cancel.SetDefault()
self._BTN_ok.Enable(False)
self._BTN_edit.Enable(False)
self._BTN_delete.Enable(False)
#------------------------------------------------------------
def _on_new_button_pressed(self, event):
if not self.new_callback():
self._LCTRL_items.SetFocus()
return
if self.refresh_callback is None:
self._LCTRL_items.SetFocus()
return
wx.BeginBusyCursor()
try:
self.refresh_callback(lctrl = self._LCTRL_items)
finally:
wx.EndBusyCursor()
self._LCTRL_items.set_column_widths()
self._LCTRL_items.SetFocus()
#------------------------------------------------------------
def _on_edit_button_pressed(self, event):
# if the edit button *can* be pressed there are *supposed*
# to be both an item selected and an editor configured
if not self.edit_callback(self._LCTRL_items.get_selected_item_data(only_one=True)):
self._LCTRL_items.SetFocus()
return
if self.refresh_callback is None:
self._LCTRL_items.SetFocus()
return
wx.BeginBusyCursor()
try:
self.refresh_callback(lctrl = self._LCTRL_items)
finally:
wx.EndBusyCursor()
self._LCTRL_items.set_column_widths()
self._LCTRL_items.SetFocus()
#------------------------------------------------------------
def _on_delete_button_pressed(self, event):
# if the delete button *can* be pressed there are *supposed*
# to be both an item selected and a deletor configured
item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
if item_data is None:
self._LCTRL_items.SetFocus()
return
if not self.delete_callback(item_data):
self._LCTRL_items.SetFocus()
return
if self.refresh_callback is None:
self._LCTRL_items.SetFocus()
return
wx.BeginBusyCursor()
try:
self.refresh_callback(lctrl = self._LCTRL_items)
finally:
wx.EndBusyCursor()
self._LCTRL_items.set_column_widths()
self._LCTRL_items.SetFocus()
#------------------------------------------------------------
def _on_left_extra_button_pressed(self, event):
item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
if not self.__left_extra_button_callback(item_data):
self._LCTRL_items.SetFocus()
return
if self.refresh_callback is None:
self._LCTRL_items.SetFocus()
return
wx.BeginBusyCursor()
try:
self.refresh_callback(lctrl = self._LCTRL_items)
finally:
wx.EndBusyCursor()
self._LCTRL_items.set_column_widths()
self._LCTRL_items.SetFocus()
#------------------------------------------------------------
def _on_middle_extra_button_pressed(self, event):
item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
if not self.__middle_extra_button_callback(item_data):
self._LCTRL_items.SetFocus()
return
if self.refresh_callback is None:
self._LCTRL_items.SetFocus()
return
wx.BeginBusyCursor()
try:
self.refresh_callback(lctrl = self._LCTRL_items)
finally:
wx.EndBusyCursor()
self._LCTRL_items.set_column_widths()
self._LCTRL_items.SetFocus()
#------------------------------------------------------------
def _on_right_extra_button_pressed(self, event):
item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
if not self.__right_extra_button_callback(item_data):
self._LCTRL_items.SetFocus()
return
if self.refresh_callback is None:
self._LCTRL_items.SetFocus()
return
wx.BeginBusyCursor()
try:
self.refresh_callback(lctrl = self._LCTRL_items)
finally:
wx.EndBusyCursor()
self._LCTRL_items.set_column_widths()
self._LCTRL_items.SetFocus()
#------------------------------------------------------------
# properties
#------------------------------------------------------------
def _set_ignore_OK_button(self, ignored):
self.__ignore_OK_button = ignored
if self.__ignore_OK_button:
self._BTN_ok.Hide()
self._BTN_ok.Enable(False)
else:
self._BTN_ok.Show()
if self._LCTRL_items.get_selected_items(only_one=True) == -1:
if self.can_return_empty:
self._BTN_ok.Enable(True)
else:
self._BTN_ok.Enable(False)
self._BTN_cancel.SetDefault()
ignore_OK_button = property(lambda x:x, _set_ignore_OK_button)
#------------------------------------------------------------
def _set_left_extra_button(self, definition):
if definition is None:
self._BTN_extra_left.Enable(False)
self._BTN_extra_left.Hide()
self.__left_extra_button_callback = None
return
(label, tooltip, callback) = definition
if not callable(callback):
raise ValueError(' callback is not a callable: %s' % callback)
self.__left_extra_button_callback = callback
self._BTN_extra_left.SetLabel(label)
self._BTN_extra_left.SetToolTipString(tooltip)
self._BTN_extra_left.Enable(True)
self._BTN_extra_left.Show()
left_extra_button = property(lambda x:x, _set_left_extra_button)
#------------------------------------------------------------
def _set_middle_extra_button(self, definition):
if definition is None:
self._BTN_extra_middle.Enable(False)
self._BTN_extra_middle.Hide()
self.__middle_extra_button_callback = None
return
(label, tooltip, callback) = definition
if not callable(callback):
raise ValueError(' callback is not a callable: %s' % callback)
self.__middle_extra_button_callback = callback
self._BTN_extra_middle.SetLabel(label)
self._BTN_extra_middle.SetToolTipString(tooltip)
self._BTN_extra_middle.Enable(True)
self._BTN_extra_middle.Show()
middle_extra_button = property(lambda x:x, _set_middle_extra_button)
#------------------------------------------------------------
def _set_right_extra_button(self, definition):
if definition is None:
self._BTN_extra_right.Enable(False)
self._BTN_extra_right.Hide()
self.__right_extra_button_callback = None
return
(label, tooltip, callback) = definition
if not callable(callback):
raise ValueError(' callback is not a callable: %s' % callback)
self.__right_extra_button_callback = callback
self._BTN_extra_right.SetLabel(label)
self._BTN_extra_right.SetToolTipString(tooltip)
self._BTN_extra_right.Enable(True)
self._BTN_extra_right.Show()
right_extra_button = property(lambda x:x, _set_right_extra_button)
#------------------------------------------------------------
def _get_new_callback(self):
return self.__new_callback
def _set_new_callback(self, callback):
if callback is not None:
if self.refresh_callback is None:
raise ValueError('refresh callback must be set before new callback can be set')
if not callable(callback):
raise ValueError(' callback is not a callable: %s' % callback)
self.__new_callback = callback
if callback is None:
self._BTN_new.Enable(False)
self._BTN_new.Hide()
else:
self._BTN_new.Enable(True)
self._BTN_new.Show()
new_callback = property(_get_new_callback, _set_new_callback)
#------------------------------------------------------------
def _get_edit_callback(self):
return self.__edit_callback
def _set_edit_callback(self, callback):
if callback is not None:
if not callable(callback):
raise ValueError(' callback is not a callable: %s' % callback)
self.__edit_callback = callback
if callback is None:
self._BTN_edit.Enable(False)
self._BTN_edit.Hide()
else:
self._BTN_edit.Enable(True)
self._BTN_edit.Show()
edit_callback = property(_get_edit_callback, _set_edit_callback)
#------------------------------------------------------------
def _get_delete_callback(self):
return self.__delete_callback
def _set_delete_callback(self, callback):
if callback is not None:
if self.refresh_callback is None:
raise ValueError('refresh callback must be set before delete callback can be set')
if not callable(callback):
raise ValueError(' callback is not a callable: %s' % callback)
self.__delete_callback = callback
if callback is None:
self._BTN_delete.Enable(False)
self._BTN_delete.Hide()
else:
self._BTN_delete.Enable(True)
self._BTN_delete.Show()
delete_callback = property(_get_delete_callback, _set_delete_callback)
#------------------------------------------------------------
def _get_refresh_callback(self):
return self.__refresh_callback
def _set_refresh_callback_helper(self):
wx.BeginBusyCursor()
try:
self.refresh_callback(lctrl = self._LCTRL_items)
finally:
wx.EndBusyCursor()
self._LCTRL_items.set_column_widths()
def _set_refresh_callback(self, callback):
if callback is not None:
if not callable(callback):
raise ValueError(' callback is not a callable: %s' % callback)
self.__refresh_callback = callback
if callback is not None:
wx.CallAfter(self._set_refresh_callback_helper)
refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
#------------------------------------------------------------
def _set_list_tooltip_callback(self, callback):
self._LCTRL_items.item_tooltip_callback = callback
list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback)
#def _get_tooltip(self, item): # inside a class
#def _get_tooltip(item): # outside a class
#------------------------------------------------------------
def _set_message(self, message):
if message is None:
self._LBL_message.Hide()
return
self._LBL_message.SetLabel(message)
self._LBL_message.Show()
message = property(lambda x:x, _set_message)
#================================================================
from Gnumed.wxGladeWidgets import wxgGenericListManagerPnl
class cGenericListManagerPnl(wxgGenericListManagerPnl.wxgGenericListManagerPnl):
"""A panel holding a generic multi-column list and action buttions."""
def __init__(self, *args, **kwargs):
try:
msg = kwargs['msg']
del kwargs['msg']
except KeyError: msg = None
wxgGenericListManagerPnl.wxgGenericListManagerPnl.__init__(self, *args, **kwargs)
if msg is None:
self._LBL_message.Hide()
else:
self._LBL_message.SetLabel(msg)
# new/edit/delete must return True/False to enable refresh
self.__new_callback = None # called when NEW button pressed, no argument passed in
self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in
self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in
self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled)
self.__select_callback = None # called when an item is selected, data of topmost selected item passed in
self.left_extra_button = None
self.middle_extra_button = None
self.right_extra_button = None
#------------------------------------------------------------
# external API
#------------------------------------------------------------
def set_columns(self, columns=None):
self._LCTRL_items.set_columns(columns = columns)
#------------------------------------------------------------
def set_string_items(self, items = None):
self._LCTRL_items.set_string_items(items = items)
self._LCTRL_items.set_column_widths()
if (items is None) or (len(items) == 0):
self._BTN_edit.Enable(False)
self._BTN_remove.Enable(False)
else:
self._LCTRL_items.Select(0)
#------------------------------------------------------------
def set_selections(self, selections = None):
self._LCTRL_items.set_selections(selections = selections)
#------------------------------------------------------------
def set_data(self, data = None):
self._LCTRL_items.set_data(data = data)
#------------------------------------------------------------
def get_selected_item_data(self, only_one=False):
return self._LCTRL_items.get_selected_item_data(only_one=only_one)
#------------------------------------------------------------
# event handlers
#------------------------------------------------------------
def _on_list_item_selected(self, event):
if self.edit_callback is not None:
self._BTN_edit.Enable(True)
if self.delete_callback is not None:
self._BTN_remove.Enable(True)
if self.__select_callback is not None:
item = self._LCTRL_items.get_selected_item_data(only_one=True)
self.__select_callback(item)
#------------------------------------------------------------
def _on_list_item_deselected(self, event):
if self._LCTRL_items.get_selected_items(only_one=True) == -1:
self._BTN_edit.Enable(False)
self._BTN_remove.Enable(False)
if self.__select_callback is not None:
self.__select_callback(None)
#------------------------------------------------------------
def _on_add_button_pressed(self, event):
if not self.new_callback():
return
if self.refresh_callback is None:
return
wx.BeginBusyCursor()
try:
self.refresh_callback(lctrl = self._LCTRL_items)
finally:
wx.EndBusyCursor()
#------------------------------------------------------------
def _on_list_item_activated(self, event):
if self.edit_callback is None:
return
self._on_edit_button_pressed(event)
#------------------------------------------------------------
def _on_edit_button_pressed(self, event):
item = self._LCTRL_items.get_selected_item_data(only_one=True)
if item is None:
return
if not self.edit_callback(item):
return
if self.refresh_callback is None:
return
wx.BeginBusyCursor()
try:
self.refresh_callback(lctrl = self._LCTRL_items)
finally:
wx.EndBusyCursor()
#------------------------------------------------------------
def _on_remove_button_pressed(self, event):
item = self._LCTRL_items.get_selected_item_data(only_one=True)
if item is None:
return
if not self.delete_callback(item):
return
if self.refresh_callback is None:
return
wx.BeginBusyCursor()
try:
self.refresh_callback(lctrl = self._LCTRL_items)
finally:
wx.EndBusyCursor()
#------------------------------------------------------------
def _on_left_extra_button_pressed(self, event):
item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
if not self.__left_extra_button_callback(item_data):
self._LCTRL_items.SetFocus()
return
if self.refresh_callback is None:
self._LCTRL_items.SetFocus()
return
wx.BeginBusyCursor()
try:
self.refresh_callback(lctrl = self._LCTRL_items)
finally:
wx.EndBusyCursor()
self._LCTRL_items.set_column_widths()
self._LCTRL_items.SetFocus()
#------------------------------------------------------------
def _on_middle_extra_button_pressed(self, event):
item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
if not self.__middle_extra_button_callback(item_data):
self._LCTRL_items.SetFocus()
return
if self.refresh_callback is None:
self._LCTRL_items.SetFocus()
return
wx.BeginBusyCursor()
try:
self.refresh_callback(lctrl = self._LCTRL_items)
finally:
wx.EndBusyCursor()
self._LCTRL_items.set_column_widths()
self._LCTRL_items.SetFocus()
#------------------------------------------------------------
def _on_right_extra_button_pressed(self, event):
item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
if not self.__right_extra_button_callback(item_data):
self._LCTRL_items.SetFocus()
return
if self.refresh_callback is None:
self._LCTRL_items.SetFocus()
return
wx.BeginBusyCursor()
try:
self.refresh_callback(lctrl = self._LCTRL_items)
finally:
wx.EndBusyCursor()
self._LCTRL_items.set_column_widths()
self._LCTRL_items.SetFocus()
#------------------------------------------------------------
# properties
#------------------------------------------------------------
def _get_new_callback(self):
return self.__new_callback
def _set_new_callback(self, callback):
if callback is not None:
if not callable(callback):
raise ValueError(' callback is not a callable: %s' % callback)
self.__new_callback = callback
self._BTN_add.Enable(callback is not None)
new_callback = property(_get_new_callback, _set_new_callback)
#------------------------------------------------------------
def _get_select_callback(self):
return self.__select_callback
def _set_select_callback(self, callback):
if callback is not None:
if not callable(callback):
raise ValueError('