Source code for domdf_wxpython_tools.panel_listctrl.panel_listctrl

#!/usr/bin/env python
#
#  panel_listctrl.py
"""
A custom Panel that acts as a ListCtrl for other wx.Panel objects.

An example ListItem exists that provides two StaticText fields and
can be used as the basis for custom list items
"""
#
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU Lesser General Public License for more details.
#
#  You should have received a copy of the GNU Lesser General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#
# generated by wxGlade 0.9.2 on Thu Jan 16 16:34:51 2020
#

# stdlib
import pathlib
from typing import Dict, List, Union

# 3rd party
import wx  # type: ignore
from typing_extensions import Literal

# this package
from domdf_wxpython_tools.panel_listctrl.css_parser import parse_css, parse_css_file
from domdf_wxpython_tools.panel_listctrl.font_parser import parse_font

__all__ = ["PanelListCtrl", "PanelListItem"]

# begin wxGlade: dependencies
# end wxGlade

# begin wxGlade: extracode
# end wxGlade


[docs]class PanelListCtrl(wx.ScrolledWindow): def __init__( self, parent: wx.Window, id=wx.ID_ANY, # noqa: A002 # pylint: disable=redefined-builtin pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.TAB_TRAVERSAL, name=wx.PanelNameStr, left_padding=32 ): wx.ScrolledWindow.__init__(self, parent, id, pos=pos, size=size, style=style | wx.TAB_TRAVERSAL, name=name) self._items: List[PanelListItem] = [] self.parent = parent self.left_padding = left_padding self.SetScrollRate(10, 10) self.sizer_1 = wx.BoxSizer(wx.VERTICAL) self.SetSizer(self.sizer_1) self.sizer_1.Fit(self) self.Layout() self.Bind(wx.EVT_LIST_ITEM_SELECTED, self._on_selection_changed) self.Bind(wx.EVT_LIST_KEY_DOWN, self._on_key_down) self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_LISTBOX)) def _on_selection_changed(self, event): """ Handler for EVT_LIST_ITEM_SELECTED, triggered by clicking on an Item. """ for item in self._items: if item == event.GetEventObject(): item.SelectItem() else: item.DeselectItem() event.Skip() def _on_key_down(self, event) -> None: """ Handler for EVT_LIST_KEY_DOWN, triggered by pressing key on keyboard when an item is focused. """ key_code = event.GetKeyCode() index = self.GetItemPosition(event.GetEventObject()) self.DeselectAll() if key_code == wx.WXK_UP: index = index - 1 elif key_code == wx.WXK_DOWN: index = index + 1 if key_code == wx.WXK_PAGEUP: index = index - 5 elif key_code == wx.WXK_PAGEDOWN: index = index + 5 if index >= self.GetItemCount(): index = self.GetItemCount() - 1 elif index < 0: index = 0 self.Select(index) event.Skip() # TODO: Keyboard autocompletion?
[docs] def SetSelection(self, idx: int): """ Set the current selection to the item at the given index. :param idx: index of the item to select. """ item_to_select = self.GetItem(idx) for item in self._items: if item == item_to_select: item.SelectItem() else: item.DeselectItem()
[docs] def DeselectAll(self): """ Deselect all items. """ for i in range(self.GetItemCount()): self.Select(i, False)
[docs] def AcceptsFocus(self): # noqa: D400 return True
[docs] def AcceptsFocusFromKeyboard(self): return False
[docs] def Append(self, panel_list_item: "PanelListItem"): """ Append a 'PanelListItem' object, or an instance of a custom subclass, to the control. """ self.sizer_1.Add(panel_list_item, 0, wx.EXPAND, wx.TOP, 0) self._items.append(panel_list_item) self.sizer_1.Fit(self) self.Layout()
[docs] def AppendNewItem(self, text_dict: Dict, style_data) -> "PanelListItem": """ Append a new 'PanelListItem' object to the control, passing the 'text_dict' and 'style_data' parameters to the new object. :param text_dict: :param style_data: :return: The new PanelListItem object that was added to the control """ item = PanelListItem(self, text_dict, style_data, left_padding=self.left_padding) self.Append(item) return item
[docs] def Clear(self): """ Removes all items from the control """ for item in self._items: self.sizer_1.Hide(item) self.sizer_1.Remove(0) item.Destroy() self.Layout() self._items = [] event = wx.ListEvent(wx.wxEVT_LIST_DELETE_ALL_ITEMS) event.SetEventObject(self) wx.PostEvent(self, event) return True
[docs] def DeleteItem(self, item) -> bool: """ Deletes the specified item from the control. :param item: :return: :py:obj:`True` if the item was removed, :py:obj:`False` otherwise (usually because the item wasn't in the control) """ for index, widget in enumerate(self._items): if widget == item: self.sizer_1.Hide(self._items.pop(index)) self.sizer_1.Remove(index) item.Destroy() self.Layout() event = wx.ListEvent(wx.wxEVT_LIST_DELETE_ITEM) event.SetEventObject(self) wx.PostEvent(self, event) return True return False
[docs] def Focus(self, idx): """ Set Focus to the the given item. :param idx: """ for index, item in enumerate(self._items): if index == idx: item.SelectItem() else: item.DeselectItem()
[docs] def GetColumnCount(self): """ Returns the number of columns. """ return 1
# # def GetCountPerPage(self) -> int: # """ # GetCountPerPage() -> int # # Gets the number of items that can fit vertically in the visible area # of the list control (list or report view) or the total number of items # in the list control (icon or small icon view). # """ # return 0 #
[docs] def GetFirstSelected(self, *_) -> int: """ Returns the first selected item, or -1 when none is selected. """ for item in self._items: if item.IsSelected(): return item return -1
[docs] def GetFocusedItem(self): """ Gets the currently focused item or -1 if none is focused. :return: :rtype: """ for item in self._items: if item.IsSelected(): return item return -1
[docs] def GetItem(self, itemIdx, *_): """ Returns information about the item. See :meth:`~.SetItem` for more information. :param itemIdx: """ return self._items[itemIdx]
[docs] def GetItemBackgroundColour(self, item) -> wx.Colour: """ Returns the colour for this item. """ return item.GetBackgroundColour()
[docs] def GetItemCount(self) -> int: """ Returns the number of items in the list control. """ return len(self._items)
[docs] def GetItemPosition(self, item) -> int: """ Returns the position of the item, or ``-1`` if it is not found. """ if item in self._items: return self._items.index(item) return -1
[docs] def GetNextSelected(self, item): """ Returns subsequent selected items, or -1 when no more are selected. :param item: """ index_of_item = self.GetItemPosition(item) for index, item in enumerate(self._items): if index <= index_of_item: continue if item.IsSelected(): return item return -1
[docs] def GetSelectedItemCount(self) -> int: """ Returns the number of selected items in the list control. """ selected_item_count = 0 for item in self._items: if item.IsSelected(): selected_item_count += 1 return selected_item_count
# # def HitTest(self, point): # """ # HitTest(point) -> (long, flags) # # Determines which item (if any) is at the specified point, giving # details in flags. # """ # pass # # def InsertItem(self, *__args): with multiple overloads # """ # InsertItem(info) -> long # InsertItem(index, label) -> long # InsertItem(index, imageIndex) -> long # InsertItem(index, label, imageIndex) -> long # # Inserts an item, returning the index of the new item if successful, -1 # otherwise. # """ # return 0 # TODO: Trigger EVT_LIST_INSERT_ITEM #
[docs] def IsEmpty(self): """ Returns true if the control doesn't currently contain any items. :return: :rtype: """ return bool(self._items)
[docs] def IsSelected(self, idx): """ Returns ``:py:obj:`True``` if the item is selected. :param idx: """ return self._items[idx].IsSelected()
[docs] def RefreshItem(self, item): """ Redraws the given item. :param item: """ item.Layout() item.Refresh()
[docs] def RefreshItems(self, itemFrom, itemTo): """ Redraws the items between itemFrom and itemTo. :param itemFrom: :param itemTo: """ for index in range(itemFrom, itemTo + 1): self.RefreshItem(self._items[index])
# # def ScrollList(self, dx, dy) -> bool: # """ # ScrollList(dx, dy) -> bool # # Scrolls the list control. # """ # return False
[docs] def Select(self, idx, on: int = 1): """ Selects/deselects an item. :param idx: :param on: """ self._items[idx].SelectItem(on)
# def SortItems(self, fnSortCallBack) -> bool: # """ # SortItems(fnSortCallBack) -> bool # # Call this function to sort the items in the list control. # """ # return False @property def ColumnCount(self) -> int: """ Returns the number of columns. :rtype: int """ return 1 # # CountPerPage = property(lambda self: object(), lambda self, v: None, lambda self: None) # default # """GetCountPerPage() -> int # # Gets the number of items that can fit vertically in the visible area # of the list control (list or report view) or the total number of items # in the list control (icon or small icon view).""" # # @property def FocusedItem(self): """ Gets the currently focused item or -1 if none is focused. :return: :rtype: """ return self.GetFocusedItem() @property def ItemCount(self) -> int: """ Returns the number of items in the list control. :rtype: int """ return len(self._items)
# end of class RecentProjectsPanel
[docs]class PanelListItem(wx.Panel): """ :param parent: The PanelListCtrl the item is to go into :param text_dict: :param style_data: :param id: An identifier for the panel. ID_ANY is taken to mean a default. :param style: The window style. See wx.Panel. :param name: Window name :param left_padding: the spacing to the left of the text in the control """ def __init__( self, parent: PanelListCtrl, text_dict: Dict, style_data, id: int = wx.ID_ANY, # noqa: A002 # pylint: disable=redefined-builtin style: int = 0, name: str = wx.PanelNameStr, left_padding: int = 32, ): self.parent = parent if not isinstance(text_dict, dict): raise TypeError("'text_dict' must be a dict containing 'css class:text' pairs") if isinstance(style_data, pathlib.Path): # Filename provided style_data = parse_css_file(style_data) if isinstance(style_data, str): # Filename or css provided try: # CSS style_data = parse_css(style_data) except ValueError: # Filename style_data = parse_css_file(style_data) elif not isinstance(style_data, dict): raise TypeError( """'style_data' must be either: > A string or pathlib.Path object pointing to a css file, or > A dictionary containing the style data, or > A string containing css properties.""" ) self.style_data = style_data wx.Panel.__init__(self, parent, id, style=style | wx.TAB_TRAVERSAL | wx.WANTS_CHARS, name=name) self.selected = False # Bind events self.Bind(wx.EVT_LEFT_UP, self.OnClick) self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick) self.Bind(wx.EVT_MIDDLE_UP, self.OnMiddleClick) self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) # for wxMSW self.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnRightClick) # for wxGTK self.Bind(wx.EVT_RIGHT_UP, self.OnRightClick) # Background colour settings for panel if "li" in self.style_data and "background-color" in self.style_data["li"]: self._default_background = self.style_data["li"]["background-color"] else: self._default_background = wx.SystemSettings.GetColour(wx.SYS_COLOUR_LISTBOX) if "li::selection" in self.style_data and "background-color" in self.style_data["li::selection"]: self._selected_background = self.style_data["li::selection"]["background-color"] else: self._selected_background = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUHILIGHT) self._items = {} self._text_properties: Dict = {} self.outer_sizer = wx.BoxSizer(wx.HORIZONTAL) main_grid = wx.FlexGridSizer(len(text_dict), 2, 0, left_padding) for index, (css_class, text) in enumerate(text_dict.items()): widget = wx.StaticText(self, wx.ID_ANY, text, style=wx.ST_ELLIPSIZE_MIDDLE) self._items[css_class] = widget if index == 0: main_grid.Add((0, 0), 0, 0, 0) main_grid.Add(widget, 0, wx.TOP, 6) else: main_grid.Add((0, 0), 0, 0, 0) main_grid.Add(widget, 0, wx.TOP, 0 if wx.Platform == "__WXGTK__" else 2) widget.Bind(wx.EVT_LEFT_UP, self.OnClick) widget.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) widget.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick) widget.Bind(wx.EVT_MIDDLE_UP, self.OnMiddleClick) # for wxMSW widget.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnRightClick) # for wxGTK widget.Bind(wx.EVT_RIGHT_UP, self.OnRightClick) self.outer_sizer.Add(main_grid, 0, wx.BOTTOM, 4) self.SetSizer(self.outer_sizer) self.outer_sizer.Fit(self) self.Layout() self.Refresh()
[docs] def SelectItem(self, select: bool = True): """ Select (or deselect) the given item. :param select: If :py:obj:`False` the item is deselected. """ self.selected = select if select: self.SetFocus() self.Refresh()
[docs] def DeselectItem(self): # noqa: D102 self.selected = False self.Refresh() event = wx.ListEvent(wx.wxEVT_LIST_ITEM_DESELECTED) event.SetEventObject(self) wx.PostEvent(self, event)
[docs] def OnRightClick(self, _) -> None: # noqa: D102 event = wx.ListEvent(wx.wxEVT_LIST_ITEM_RIGHT_CLICK) event.SetEventObject(self) wx.PostEvent(self, event)
[docs] def OnMiddleClick(self, _) -> None: # noqa: D102 event = wx.ListEvent(wx.wxEVT_LIST_ITEM_RIGHT_CLICK) event.SetEventObject(self) wx.PostEvent(self, event)
[docs] def OnClick(self, _) -> None: # noqa: D102 event = wx.ListEvent(wx.wxEVT_LIST_ITEM_SELECTED) event.SetEventObject(self) wx.PostEvent(self, event)
[docs] def OnDoubleClick(self, _) -> None: # noqa: D102 event = wx.ListEvent(wx.wxEVT_LIST_ITEM_ACTIVATED) event.SetEventObject(self) wx.PostEvent(self, event)
[docs] def OnKeyDown(self, event) -> None: """ :param event: The wxPython event. """ key_event = event event = wx.ListEvent(wx.wxEVT_LIST_KEY_DOWN) event.SetEventObject(self) event.SetKeyCode(key_event.GetKeyCode()) wx.PostEvent(self, event)
[docs] def IsSelected(self) -> bool: """ Returns whether the :class:`~.PanelListItem` is selected. """ return self.selected
def _refresh_background_colour(self): if self.selected: wx.Panel.SetBackgroundColour(self, self._selected_background) else: wx.Panel.SetBackgroundColour(self, self._default_background) def _refresh_text(self): for classname, widget in self._items.items(): if self.selected: class_style_data = self.style_data[f"li p.{classname}::selection"] else: class_style_data = self.style_data[f"li p.{classname}"] colour, font_data = parse_font(class_style_data) widget.SetForegroundColour(colour) widget.SetFont(wx.Font(**font_data))
[docs] def SetBackgroundColour(self, colour) -> None: """ Set the background colour for the item. :param colour: """ self._default_background = colour
[docs] def SetSelectedBackgroundColour(self, colour) -> None: """ Set the background colour for the item when it is selected. :param colour: """ self._selected_background = colour
[docs] def GetBackgroundColour(self): return self._default_background
[docs] def GetCurrentBackgroundColour(self): """ Returns the current background colour of the :class`wx.Panel` class. """ return wx.Panel.GetBackgroundColour(self)
[docs] def Refresh(self, **kwargs): self._refresh_background_colour() self._refresh_text() wx.Panel.Refresh(self)
[docs] def GetContents(self): return self._items.values()
# end of class RecentProjectItem