# region imports
from __future__ import annotations
from typing import Any, cast, Iterable, Sequence, TYPE_CHECKING, Tuple
import contextlib
# pylint: disable=useless-import-alias
from ooo.dyn.style.horizontal_alignment import HorizontalAlignment as HorizontalAlignment
from ooo.dyn.view.selection_type import SelectionType
from com.sun.star.awt.grid import XMutableGridDataModel
from ooodev.mock import mock_g
from ooodev.adapter.awt.grid.grid_selection_events import GridSelectionEvents
from ooodev.events.args.listener_event_args import ListenerEventArgs
from ooodev.loader import lo as mLo
from ooodev.utils.kind.dialog_control_kind import DialogControlKind
from ooodev.utils.kind.dialog_control_named_kind import DialogControlNamedKind
from ooodev.utils.table_helper import TableHelper
from ooodev.utils.type_var import Table
from ooodev.adapter.awt.grid.uno_control_grid_model_partial import UnoControlGridModelPartial
from ooodev.dialog.dl_control.ctl_base import DialogControlBase, _create_control
if TYPE_CHECKING:
from com.sun.star.awt.grid import UnoControlGrid # service
from com.sun.star.awt.grid import UnoControlGridModel # service
from com.sun.star.awt import XWindowPeer
from ooodev.dialog.dl_control.model.model_grid import ModelGrid
# endregion imports
[docs]class CtlGrid(DialogControlBase, UnoControlGridModelPartial, GridSelectionEvents):
"""Class for Grid Control"""
# pylint: disable=unused-argument
# region init
[docs] def __init__(self, ctl: UnoControlGrid) -> None:
"""
Constructor
Args:
ctl (UnoControlGrid): Grid Control
"""
# generally speaking EventArgs.event_data will contain the Event object for the UNO event raised.
DialogControlBase.__init__(self, ctl)
UnoControlGridModelPartial.__init__(self, self.get_model()) # type: ignore
generic_args = self._get_generic_args()
# EventArgs.event_data will contain the ActionEvent
GridSelectionEvents.__init__(self, trigger_args=generic_args, cb=self._on_grid_listener_add_remove)
self._model_ex = None
# endregion init
def __repr__(self) -> str:
if hasattr(self, "name"):
return f"CtlGrid({self.name})"
return "CtlGrid"
# region Lazy Listeners
def _on_grid_listener_add_remove(self, source: Any, event: ListenerEventArgs) -> None:
# will only ever fire once
self.view.addSelectionListener(self.events_listener_grid_selection)
event.remove_callback = True
# endregion Lazy Listeners
# region Overrides
[docs] def get_view_ctl(self) -> UnoControlGrid:
return cast("UnoControlGrid", super().get_view_ctl())
[docs] def get_uno_srv_name(self) -> str:
"""Returns ``com.sun.star.awt.UnoControlGrid``"""
return "com.sun.star.awt.UnoControlGrid"
[docs] def get_model(self) -> UnoControlGridModel:
"""Gets the Model for the control"""
return cast("UnoControlGridModel", self.get_view_ctl().getModel())
[docs] def get_control_kind(self) -> DialogControlKind:
"""Gets the control kind. Returns ``DialogControlKind.GRID_CONTROL``"""
return DialogControlKind.GRID_CONTROL
[docs] def get_control_named_kind(self) -> DialogControlNamedKind:
"""Gets the control named kind. Returns ``DialogControlNamedKind.GRID_CONTROL``"""
return DialogControlNamedKind.GRID_CONTROL
# endregion Overrides
# region Data
[docs] def set_table_data(
self,
data: Table,
*,
widths: Sequence[int] | None = None,
align: Iterable[HorizontalAlignment] | str | None = None,
row_header_width: int = 10,
has_colum_headers: bool | None = None,
has_row_headers: bool | None = None,
) -> None:
"""
Set the data in a table control. Preexisting data is cleared.
Args:
data (Table): 2D Sequence of data that is the data to set.
widths (Sequence[int] | None, optional): Specifies Column Widths. If number of widths is less then the number of columns,
the last width is used for the remaining columns.
If omitted then each column is auto-sized to fill out the table width.
align (Iterable[HorizontalAlignment] | str | None, optional): Specifies column alignments. See Note Below.
row_header_width (int, optional): Specifies the width of the row header. Defaults to ``10``.
has_colum_headers (bool | None, optional): Specifies if the data has a column header. If omitted the table's ShowColumnHeader property is used. Defaults to ``None``.
has_row_headers (bool | None, optional): Specifies if the data has a row header. If omitted the table's ShowRowHeader property is used. Defaults to ``None``.
Raises:
ValueError: if not a valid UnoControlGrid or if no data model.
Returns:
None:
Note:
``align`` can be a string of ``"L"``, ``"R"``, or ``"C"`` for left, right, or center alignment or
a list of ``HorizontalAlignment`` values. If ``align`` values is lest then the number of columns,
then the remaining columns will be aligned left.
If ``has_colum_headers`` is ``True`` then the first row of data is used for the column headers.
If ``table.Model.ShowColumnHeader`` is ``False``, then the column header row is not used.
If ``has_colum_headers`` is ``False`` and ``table.Model.ShowColumnHeader`` is ``True``
then the column headers are set to the default column names such as (A, B, C, D).
If ``has_row_headers`` is ``True`` then the first row of data is used for the row headers.
If ``table.Model.ShowRowHeader`` is ``False``, then the row header is not used.
If ``has_row_headers`` is ``False`` and ``table.Model.ShowRowHeader`` is ``True``
then the row headers are set to the default row names such as (1, 2, 3, 4).
Example:
.. code-block:: python
# other code
tab_sz = self._ctl_tab.getPosSize()
ctl_table1 = Dialogs.insert_table_control(
dialog_ctrl=self._tab_table,
x=tab_sz.X + self._padding,
y=tab_sz.Y + self._padding,
width=tab_sz.Width - (self._padding * 2),
height=300,
grid_lines=True,
col_header=True,
row_header=True,
)
tbl = ... # get data as 2d sequence
Dialogs.set_table_data(
table=ctl_table1,
data=tbl,
align="RLC", # first column right, second left, third center. All others left
widths=(75, 60, 100, 40), # does not need to add up to total width, a factor will be used to auto size where needed.
has_row_headers=True,
has_colum_headers=True,
)
"""
# set_table_data() will handle to many or to few widths
# widths are applied by using a scale factor to the table width
table = self.view
tbl_size = table.getSize()
model = cast("UnoControlGridModel", table.getModel())
data_model = model.GridDataModel
if not data_model:
raise ValueError("No data model")
data_model = mLo.Lo.qi(XMutableGridDataModel, data_model, True)
# Erase any pre-existing data and columns
data_model.removeAllRows()
if data_model.ColumnCount > 0:
# reverse indexes to start removing from the end
for i in range(data_model.ColumnCount - 1, -1, -1):
model.ColumnModel.removeColumn(i)
# Get the headers from data
use_col_headers = False
use_row_headers = False
if has_colum_headers is None:
if model.ShowColumnHeader:
use_col_headers = True
elif has_colum_headers:
use_col_headers = True
if has_row_headers is None:
if model.ShowRowHeader:
use_row_headers = True
elif has_row_headers:
use_row_headers = True
col_headers = data[0][1:] if has_row_headers else data[0]
# Create the columns
for i, header in enumerate(col_headers):
column = model.ColumnModel.createColumn()
if use_col_headers:
column.Title = str(header)
elif model.ShowColumnHeader:
column.Title = TableHelper.make_column_name(i, zero_index=True)
model.ColumnModel.addColumn(column)
# Manage row headers width
if has_row_headers and model.ShowRowHeader:
header_width_row = row_header_width
model.RowHeaderWidth = header_width_row
else:
header_width_row = 0
# Size the columns. Column sizing cannot be done before all the columns are added
len_col_headers = len(col_headers)
len_widths = 0
if widths:
len_widths = len(widths)
# Size the columns proportionally with their relative widths
rel_width = 0.0
# Compute the sum of the relative widths
for i, width in enumerate(widths):
if i + 1 >= len_col_headers:
break
rel_width += width
# if widths have less values then columns, add the rest with the last value of widths.
if len_widths < len_col_headers:
last_width = widths[-1]
for i in range(len_widths, len_col_headers):
rel_width += last_width
# Set absolute column widths
# initial testing showed that columns are sized using this factor method even
# if the factoring is not done here.
if rel_width > 0:
width_factor = (tbl_size.Width - header_width_row) / rel_width
else:
width_factor = 1.0
for i, width in enumerate(widths):
if i + 1 > len_col_headers:
break
model.ColumnModel.getColumn(i).ColumnWidth = int(width * width_factor)
# if widths have less values then columns, calculate the rest with the last value of widths.
if len_widths < len_col_headers:
last_width = widths[-1]
for i in range(len_widths, len_col_headers):
model.ColumnModel.getColumn(i).ColumnWidth = int(last_width * width_factor)
else:
# Size header and columns evenly
width = (tbl_size.Width - header_width_row) // len_col_headers
for i in range(len_col_headers):
model.ColumnModel.getColumn(i).ColumnWidth = width
# Initialize the column alignment
def get_align(s: str):
s = s.lower()
if s == "l":
return HorizontalAlignment.LEFT
elif s == "r":
return HorizontalAlignment.RIGHT
elif s == "c":
return HorizontalAlignment.CENTER
return HorizontalAlignment.LEFT
if align:
if isinstance(align, str):
align = [get_align(s) for s in align.replace(" ", "")]
elif not isinstance(align, list):
align = list(align)
else:
align = [HorizontalAlignment.LEFT for _ in range(len_col_headers)]
while len(align) > len_col_headers:
_ = align.pop()
while len(align) < len_col_headers:
align.append(HorizontalAlignment.LEFT)
# Feed the table with data
# skip column headers row
if use_col_headers is False:
rng_start = 0
else:
rng_start = 1
for i in range(rng_start, len(data)):
row = data[i][1:] if use_row_headers else data[i]
if not isinstance(row, tuple):
row = tuple(row)
row_header_text = ""
if use_row_headers and model.ShowRowHeader:
row_header_text = str(data[i][0])
elif model.ShowRowHeader:
if rng_start == 0:
row_header_text = str(i + 1)
else:
row_header_text = str(i)
data_model.addRow(row_header_text, row)
for i, alignment in enumerate(align):
model.ColumnModel.getColumn(i).HorizontalAlign = alignment # type: ignore
# endregion Data
# region Static Methods
[docs] @staticmethod
def create(win: XWindowPeer, **kwargs: Any) -> "CtlGrid":
"""
Creates a new instance of the control.
Keyword arguments are optional.
Extra Keyword args are passed to the control as property values.
Args:
win (XWindowPeer): Parent Window
Keyword Args:
x (int, UnitT, optional): X Position in Pixels or UnitT.
y (int, UnitT, optional): Y Position in Pixels or UnitT.
width (int, UnitT, optional): Width in Pixels or UnitT.
height (int, UnitT, optional): Height in Pixels or UnitT.
Returns:
CtlGrid: New instance of the control.
Note:
The `UnoControlDialogElement <https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1awt_1_1UnoControlDialogElement.html>`__
interface is not included when creating the control with a window peer.
"""
ctrl = _create_control("com.sun.star.awt.UnoControlGridModel", win, **kwargs)
return CtlGrid(ctl=ctrl)
# endregion Static Methods
# region Properties
@property
def model(self) -> UnoControlGridModel:
# pylint: disable=no-member
return cast("UnoControlGridModel", super().model)
@property
def model_ex(self) -> ModelGrid:
"""
Gets the extended Model for the control.
This is a wrapped instance for the model property.
It add some additional properties and methods to the model.
"""
# pylint: disable=no-member
if self._model_ex is None:
# pylint: disable=import-outside-toplevel
# pylint: disable=redefined-outer-name
from ooodev.dialog.dl_control.model.model_grid import ModelGrid
self._model_ex = ModelGrid(self.model)
return self._model_ex
@property
def view(self) -> UnoControlGrid:
# pylint: disable=no-member
return cast("UnoControlGrid", super().view)
@property
def horizontal_scrollbar(self) -> bool:
"""
Gets or sets if a horizontal scrollbar should be added to the dialog.
Same as ``h_scroll`` property.
"""
return self.h_scroll
@horizontal_scrollbar.setter
def horizontal_scrollbar(self, value: bool) -> None:
"""Sets the horizontal scrollbar"""
self.h_scroll = value
@property
def vertical_scrollbar(self) -> bool:
"""
Gets or sets if a vertical scrollbar should be added to the dialog.
Same as ``v_scroll`` property.
"""
return self.v_scroll
@vertical_scrollbar.setter
def vertical_scrollbar(self, value: bool) -> None:
self.v_scroll = value
@property
def list_count(self) -> int:
"""Gets the number of items in the combo box"""
with contextlib.suppress(Exception):
return self.model.GridDataModel.RowCount
return 0
@property
def list_index(self) -> int:
"""
Gets which row index is selected in the gird.
Returns:
Index of the first selected row or ``-1`` if no rows are selected.
"""
with contextlib.suppress(Exception):
model = self.model
if model.SelectionModel == SelectionType.SINGLE:
return self.view.getCurrentRow()
sel = cast(Tuple[int, ...], self.view.getSelectedRows())
if sel:
return sel[0]
return -1
# endregion Properties
if mock_g.FULL_IMPORT:
from ooodev.dialog.dl_control.model.model_grid import ModelGrid