"""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('