Following
Change-Id: I54e578098684a027c9bf2f192a5af3fecb4e65d7
Author: Michael Weghorn <m.weghorn@posteo.de>
Date: Fri Sep 18 15:44:17 2026 +0200
toolkit: Rename TableControl_Impl to TableControl
, also rename the header and source file to match
with the new class name.
(See that commit's commit message for more background.)
Change-Id: I5d7c4251902689718b9b2f3217671cad5b071201
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211163
Tested-by: Jenkins
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
566 lines
21 KiB
C++
566 lines
21 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <controls/table/AccessibleTableControlObjType.hxx>
|
|
#include <controls/table/tabledatawindow.hxx>
|
|
#include <controls/table/tablemodel.hxx>
|
|
#include <controls/table/tablecontrolinterface.hxx>
|
|
|
|
#include <com/sun/star/accessibility/XAccessible.hpp>
|
|
#include <vcl/ctrl.hxx>
|
|
#include <vcl/seleng.hxx>
|
|
|
|
#include <vector>
|
|
|
|
class ScrollBar;
|
|
class ScrollBarBox;
|
|
|
|
namespace accessibility
|
|
{
|
|
class AccessibleGridControl;
|
|
}
|
|
|
|
namespace svt::table
|
|
{
|
|
struct MutableColumnMetrics : public ColumnMetrics
|
|
{
|
|
MutableColumnMetrics()
|
|
: ColumnMetrics()
|
|
{
|
|
}
|
|
|
|
MutableColumnMetrics(tools::Long const i_startPixel, tools::Long const i_endPixel)
|
|
: ColumnMetrics(i_startPixel, i_endPixel)
|
|
{
|
|
}
|
|
|
|
tools::Long getStart() const { return nStartPixel; }
|
|
tools::Long getEnd() const { return nEndPixel; }
|
|
|
|
void move(tools::Long const i_offset)
|
|
{
|
|
nStartPixel += i_offset;
|
|
nEndPixel += i_offset;
|
|
}
|
|
|
|
tools::Long getWidth() const { return nEndPixel - nStartPixel; }
|
|
};
|
|
|
|
struct ColumnInfoPositionLess
|
|
{
|
|
bool operator()(MutableColumnMetrics const& i_lhs, MutableColumnMetrics const& i_rhs)
|
|
{
|
|
return i_lhs.getEnd() < i_rhs.getStart();
|
|
}
|
|
};
|
|
|
|
typedef ::std::vector<MutableColumnMetrics> ColumnPositions;
|
|
|
|
class TableFunctionSet;
|
|
|
|
/** a basic control which manages table-like data, i.e. a number of cells
|
|
organized in <code>m</code> rows and <code>n</code> columns.
|
|
|
|
The control itself does not do any assumptions about the concrete data
|
|
it displays, this is encapsulated in an instance supporting the
|
|
->ITableModel interface.
|
|
|
|
Also, the control does not do any assumptions about how the model's
|
|
content is rendered. This is the responsibility of a component
|
|
supporting the ->ITableRenderer interface (the renderer is obtained from
|
|
the model).
|
|
|
|
The control supports the concept of a <em>current</em> (or <em>active</em>
|
|
cell).
|
|
*/
|
|
class TableControl final : public Control, public ITableModelListener
|
|
{
|
|
friend class TableGeometry;
|
|
friend class TableRowGeometry;
|
|
friend class TableColumnGeometry;
|
|
friend class SuspendInvariants;
|
|
|
|
private:
|
|
/// the model of the table control
|
|
PTableModel m_pModel;
|
|
/// the input handler to use, usually the input handler as provided by ->m_pModel
|
|
PTableInputHandler m_pInputHandler;
|
|
/// info about the widths of our columns
|
|
ColumnPositions m_aColumnWidths;
|
|
|
|
/// the height of a single row in the table, measured in pixels
|
|
tools::Long m_nRowHeightPixel;
|
|
/// the height of the column header row in the table, measured in pixels
|
|
tools::Long m_nColHeaderHeightPixel;
|
|
/// the width of the row header column in the table, measured in pixels
|
|
tools::Long m_nRowHeaderWidthPixel;
|
|
|
|
/// the number of columns in the table control. Cached model value.
|
|
TableSize m_nColumnCount;
|
|
|
|
/// the number of rows in the table control. Cached model value.
|
|
TableSize m_nRowCount;
|
|
|
|
sal_Int32 m_nCurColumn;
|
|
sal_Int32 m_nCurRow;
|
|
sal_Int32 m_nLeftColumn;
|
|
sal_Int32 m_nTopRow;
|
|
|
|
sal_Int32 m_nCursorHidden;
|
|
|
|
/** the window to contain all data content, including header bars
|
|
|
|
The window's upper left corner is at position (0,0), relative to the
|
|
table control, which is the direct parent of the data window.
|
|
*/
|
|
VclPtr<TableDataWindow> m_pDataWindow;
|
|
/// the vertical scrollbar, if any
|
|
VclPtr<ScrollBar> m_pVScroll;
|
|
/// the horizontal scrollbar, if any
|
|
VclPtr<ScrollBar> m_pHScroll;
|
|
VclPtr<ScrollBarBox> m_pScrollCorner;
|
|
//selection engine - for determining selection range, e.g. single, multiple
|
|
std::unique_ptr<SelectionEngine> m_pSelEngine;
|
|
//vector which contains the selected rows
|
|
std::vector<sal_Int32> m_aSelectedRows;
|
|
//part of selection engine
|
|
std::unique_ptr<TableFunctionSet> m_pTableFunctionSet;
|
|
//part of selection engine
|
|
sal_Int32 m_nAnchor;
|
|
bool m_bUpdatingColWidths;
|
|
|
|
rtl::Reference<accessibility::AccessibleGridControl> m_xAccessibleTable;
|
|
|
|
public:
|
|
/// sets a new table model
|
|
void SetModel(const PTableModel& _pModel);
|
|
|
|
const PTableInputHandler& getInputHandler() const { return m_pInputHandler; }
|
|
|
|
sal_Int32 getCurRow() const { return m_nCurRow; }
|
|
|
|
sal_Int32 getAnchor() const { return m_nAnchor; }
|
|
void setAnchor(sal_Int32 const i_anchor) { m_nAnchor = i_anchor; }
|
|
|
|
sal_Int32 getTopRow() const { return m_nTopRow; }
|
|
sal_Int32 getLeftColumn() const { return m_nLeftColumn; }
|
|
|
|
public:
|
|
explicit TableControl(vcl::Window* pParent, WinBits nStyle);
|
|
virtual ~TableControl() override;
|
|
virtual void dispose() override;
|
|
|
|
// Window overridables
|
|
virtual void GetFocus() override;
|
|
virtual void LoseFocus() override;
|
|
virtual void KeyInput(const KeyEvent& rKEvt) override;
|
|
virtual void StateChanged(StateChangedType i_nStateChange) override;
|
|
virtual void Resize() override;
|
|
|
|
/** Creates and returns the accessible object of the whole GridControl. */
|
|
virtual rtl::Reference<comphelper::OAccessible> CreateAccessible() override;
|
|
|
|
/** to be called when the anti-impl instance has been resized
|
|
*/
|
|
void onResize();
|
|
|
|
void Select();
|
|
|
|
/** paints the table control content which intersects with the given rectangle */
|
|
void doPaintContent(vcl::RenderContext& rRenderContext, const tools::Rectangle& _rUpdateRect);
|
|
|
|
/** moves the cursor to the cell with the given coordinates
|
|
|
|
To ease the caller's code, the coordinates must not necessarily denote a
|
|
valid position. If they don't, <FALSE/> will be returned.
|
|
*/
|
|
bool GoToCell(sal_Int32 _nColumn, sal_Int32 _nRow);
|
|
|
|
/** ensures that the given coordinate is visible
|
|
@param _nColumn
|
|
the column position which should be visible. Must be non-negative, and smaller
|
|
than the column count.
|
|
@param _nRow
|
|
the row position which should be visibleMust be non-negative, and smaller
|
|
than the row count.
|
|
*/
|
|
void ensureVisible(sal_Int32 _nColumn, sal_Int32 _nRow);
|
|
|
|
/** retrieves the content of the given cell, converted to a string
|
|
*/
|
|
OUString GetAccessibleCellText(sal_Int32 const i_row, sal_Int32 const i_col);
|
|
|
|
/** returns the position of the current row in the selection vector */
|
|
static int getRowSelectedNumber(const ::std::vector<sal_Int32>& selectedRows,
|
|
sal_Int32 current);
|
|
|
|
void invalidateRect(const tools::Rectangle& rInvalidateRect);
|
|
|
|
/** ??? */
|
|
void invalidateSelectedRegion(sal_Int32 _nPrevRow, sal_Int32 _nCurRow);
|
|
|
|
/** invalidates the part of the data window which is covered by the given rows
|
|
@param i_firstRow
|
|
the index of the first row to include in the invalidation
|
|
@param i_lastRow
|
|
the index of the last row to include in the invalidation, or ROW_INVALID if the invalidation
|
|
should happen down to the bottom of the data window.
|
|
*/
|
|
void invalidateRowRange(sal_Int32 const i_firstRow, sal_Int32 const i_lastRow);
|
|
|
|
/** invalidates the part of the data window which is covered by the given row */
|
|
void invalidateRow(sal_Int32 const i_row) { invalidateRowRange(i_row, i_row); }
|
|
|
|
/** invalidates all selected rows */
|
|
void invalidateSelectedRows();
|
|
|
|
void checkCursorPosition();
|
|
|
|
sal_Int32 GetRowCount() const;
|
|
sal_Int32 GetColumnCount() const;
|
|
|
|
OUString GetRowName(sal_Int32 nIndex) const;
|
|
OUString GetColumnName(sal_Int32 nIndex) const;
|
|
bool HasRowHeader();
|
|
bool HasColumnHeader();
|
|
|
|
bool hasRowSelection() const { return !m_aSelectedRows.empty(); }
|
|
size_t GetSelectedRowCount() const { return m_aSelectedRows.size(); }
|
|
sal_Int32 GetSelectedRowIndex(size_t const i_selectionIndex) const;
|
|
|
|
/** removes the given row index from m_aSelectedRows
|
|
|
|
@return
|
|
<TRUE/> if and only if the row was previously marked as selected
|
|
*/
|
|
bool markRowAsDeselected(sal_Int32 const i_rowIndex);
|
|
|
|
/** marks the given row as selected, by putting it into m_aSelectedRows
|
|
@return
|
|
<TRUE/> if and only if the row was previously <em>not</em> marked as selected
|
|
*/
|
|
bool markRowAsSelected(sal_Int32 const i_rowIndex);
|
|
|
|
/** marks all rows as deselected
|
|
@return
|
|
<TRUE/> if and only if the selection actually changed by this operation
|
|
*/
|
|
bool markAllRowsAsDeselected();
|
|
|
|
/** marks all rows as selected
|
|
@return
|
|
<FALSE/> if and only if all rows were selected already.
|
|
*/
|
|
bool markAllRowsAsSelected();
|
|
|
|
void commitAccessibleEvent(sal_Int16 const i_eventID);
|
|
// temporary methods
|
|
// Those do not really belong into the public API - they're intended for firing A11Y-related events. However,
|
|
// firing those events should be an implementation internal to the TableControl,
|
|
// instead of something triggered externally.
|
|
void commitCellEvent(sal_Int16 const i_eventID, const css::uno::Any& i_newValue,
|
|
const css::uno::Any& i_oldValue);
|
|
void commitTableEvent(sal_Int16 const i_eventID, const css::uno::Any& i_newValue,
|
|
const css::uno::Any& i_oldValue);
|
|
|
|
/** hides the cell cursor
|
|
|
|
The method cares for successive calls, that is, for every call to
|
|
->hideCursor(), you need one call to ->showCursor. Only if the number
|
|
of both calls matches, the cursor is really shown.
|
|
|
|
@see showCursor
|
|
*/
|
|
void hideCursor();
|
|
|
|
/** shows the cell cursor
|
|
|
|
@see hideCursor
|
|
*/
|
|
void showCursor();
|
|
|
|
/** dispatches an action to the table control
|
|
|
|
@return
|
|
<TRUE/> if the action could be dispatched successfully, <FALSE/> otherwise. Usual
|
|
failure conditions include some other instance vetoing the action, or impossibility
|
|
to execute the action at all (for instance moving up one row when already positioned
|
|
on the very first row).
|
|
|
|
@see TableControlAction
|
|
*/
|
|
bool dispatchAction(TableControlAction _eAction);
|
|
|
|
/** returns selection engine*/
|
|
SelectionEngine* getSelEngine();
|
|
|
|
/** returns the table model
|
|
|
|
The returned model is guaranteed to not be <NULL/>.
|
|
*/
|
|
PTableModel GetModel() const;
|
|
|
|
/** retrieves the current column
|
|
|
|
The current col is the one which contains the active cell.
|
|
|
|
@return
|
|
the column index of the active cell, or ->COL_INVALID
|
|
if there is no active cell, e.g. because the table does
|
|
not contain any rows or columns.
|
|
*/
|
|
sal_Int32 GetCurrentColumn() const;
|
|
|
|
/** retrieves the current row
|
|
|
|
The current row is the one which contains the active cell.
|
|
|
|
@return
|
|
the row index of the active cell, or ->ROW_INVALID
|
|
if there is no active cell, e.g. because the table does
|
|
not contain any rows or columns.
|
|
*/
|
|
sal_Int32 GetCurrentRow() const;
|
|
|
|
/// activates the given cell
|
|
void activateCell(sal_Int32 const i_col, sal_Int32 const i_row);
|
|
|
|
/// retrieves the size of the table window, in pixels
|
|
Size getTableSizePixel() const;
|
|
|
|
/// sets a new mouse pointer for the table window
|
|
void setPointer(PointerStyle i_pointer);
|
|
|
|
/// captures the mouse to the table window
|
|
void captureMouse();
|
|
|
|
/// releases the mouse, after it had previously been captured
|
|
void releaseMouse();
|
|
|
|
/// invalidates the table window
|
|
void invalidate(TableArea const i_what);
|
|
|
|
/// calculates a width, given in pixels, into an AppFont-based width
|
|
tools::Long pixelWidthToAppFont(tools::Long const i_pixels) const;
|
|
|
|
/// hides a previously shown tracking rectangle
|
|
void hideTracking();
|
|
|
|
/// shows a tracking rectangle
|
|
void showTracking(tools::Rectangle const& i_location, ShowTrackFlags const i_flags);
|
|
|
|
sal_Int32 getRowAtPoint(const Point& rPoint) const;
|
|
sal_Int32 getColAtPoint(const Point& rPoint) const;
|
|
|
|
/// does a hit test for the given pixel coordinates
|
|
TableCell hitTest(const Point& rPoint) const;
|
|
|
|
/// retrieves the metrics for a given column
|
|
ColumnMetrics getColumnMetrics(sal_Int32 const i_column) const;
|
|
|
|
/// determines whether a given row is selected
|
|
bool IsRowSelected(sal_Int32 i_row) const;
|
|
|
|
void SelectRow(sal_Int32 nRowIndex, bool bSelect);
|
|
void SelectAllRows(bool bSelect);
|
|
|
|
tools::Long appFontWidthToPixel(tools::Long const i_appFontUnits) const;
|
|
|
|
TableDataWindow& getDataWindow() { return *m_pDataWindow; }
|
|
const TableDataWindow& getDataWindow() const { return *m_pDataWindow; }
|
|
ScrollBar* getHorzScrollbar() { return m_pHScroll; }
|
|
ScrollBar* getVertScrollbar() { return m_pVScroll; }
|
|
|
|
bool ConvertPointToCellAddress(sal_Int32& rRow, sal_Int32& rColPos, const Point& rPoint);
|
|
tools::Rectangle calcHeaderRect(bool bColHeader);
|
|
tools::Rectangle calcHeaderCellRect(bool bColHeader, sal_Int32 nPos);
|
|
tools::Rectangle calcTableRect() const;
|
|
tools::Rectangle calcCellRect(sal_Int32 nRow, sal_Int32 nCol) const;
|
|
|
|
// A11Y
|
|
const rtl::Reference<accessibility::AccessibleGridControl>&
|
|
getAccessible(const rtl::Reference<comphelper::OAccessible>& rpParent);
|
|
void disposeAccessible();
|
|
sal_Int32 GetAccessibleControlCount() const;
|
|
OUString GetAccessibleObjectName(AccessibleTableControlObjType eObjType, sal_Int32 nRow,
|
|
sal_Int32 nCol) const;
|
|
OUString GetAccessibleObjectDescription(AccessibleTableControlObjType eObjType) const;
|
|
void FillAccessibleStateSet(sal_Int64& rStateSet, AccessibleTableControlObjType eObjType) const;
|
|
void FillAccessibleStateSetForCell(sal_Int64& rStateSet, sal_Int32 nRow,
|
|
sal_uInt16 nColumnPos) const;
|
|
|
|
// ITableModelListener
|
|
virtual void rowsInserted(sal_Int32 first, sal_Int32 last) override;
|
|
virtual void rowsRemoved(sal_Int32 first, sal_Int32 last) override;
|
|
virtual void columnInserted() override;
|
|
virtual void columnRemoved() override;
|
|
virtual void allColumnsRemoved() override;
|
|
virtual void cellsUpdated(sal_Int32 const i_firstRow, sal_Int32 const i_lastRow) override;
|
|
virtual void columnChanged(sal_Int32 const i_column,
|
|
ColumnAttributeGroup const i_attributeGroup) override;
|
|
virtual void tableMetricsChanged() override;
|
|
|
|
private:
|
|
void impl_commitAccessibleEvent(sal_Int16 const i_eventID, css::uno::Any const& i_newValue);
|
|
|
|
/** toggles the cursor visibility
|
|
|
|
The method is not bound to the classes public invariants, as it's used in
|
|
situations where the they must not necessarily be fulfilled.
|
|
*/
|
|
void impl_ni_doSwitchCursor(bool _bOn);
|
|
|
|
/** returns the number of visible rows.
|
|
|
|
@param _bAcceptPartialRow
|
|
specifies whether a possible only partially visible last row is
|
|
counted, too.
|
|
*/
|
|
TableSize impl_getVisibleRows(bool _bAcceptPartialRow) const;
|
|
|
|
/** returns the number of visible columns
|
|
|
|
The value may change with different horizontal scroll positions, as
|
|
different columns have different widths. For instance, if your control is
|
|
100 pixels wide, and has three columns of width 50, 50, 100, respectively,
|
|
then this method will return either "2" or "1", depending on which column
|
|
is the first visible one.
|
|
|
|
@param _bAcceptPartialRow
|
|
specifies whether a possible only partially visible last row is
|
|
counted, too.
|
|
*/
|
|
TableSize impl_getVisibleColumns(bool _bAcceptPartialCol) const;
|
|
|
|
/** updates all cached model values
|
|
|
|
The method is not bound to the classes public invariants, as it's used in
|
|
situations where the they must not necessarily be fulfilled.
|
|
*/
|
|
void impl_ni_updateCachedModelValues();
|
|
|
|
/** updates the cached table metrics (row height etc.) */
|
|
void impl_ni_updateCachedTableMetrics();
|
|
|
|
/** does a relayout of the table control
|
|
|
|
Column widths, and consequently the availability of the vertical and horizontal scrollbar, are updated
|
|
with a call to this method.
|
|
|
|
@param i_assumeInflexibleColumnsUpToIncluding
|
|
the index of a column up to which all columns should be considered as inflexible, or
|
|
<code>COL_INVALID</code>.
|
|
*/
|
|
void impl_ni_relayout(sal_Int32 const i_assumeInflexibleColumnsUpToIncluding = COL_INVALID);
|
|
|
|
/** calculates the new width of our columns, taking into account their min and max widths, and their relative
|
|
flexibility.
|
|
|
|
@param i_assumeInflexibleColumnsUpToIncluding
|
|
the index of a column up to which all columns should be considered as inflexible, or
|
|
<code>COL_INVALID</code>.
|
|
|
|
@param i_assumeVerticalScrollbar
|
|
controls whether or not we should assume the presence of a vertical scrollbar. If <true/>, and
|
|
if the model has a VerticalScrollbarVisibility != ScrollbarShowNever, the method will leave
|
|
space for a vertical scrollbar.
|
|
|
|
@return
|
|
the overall width of the grid, which is available for columns
|
|
*/
|
|
tools::Long
|
|
impl_ni_calculateColumnWidths(sal_Int32 const i_assumeInflexibleColumnsUpToIncluding,
|
|
bool const i_assumeVerticalScrollbar,
|
|
::std::vector<tools::Long>& o_newColWidthsPixel) const;
|
|
|
|
/** positions all child windows, e.g. the both scrollbars, the corner window, and the data window */
|
|
void impl_ni_positionChildWindows(tools::Rectangle const& i_dataCellPlayground,
|
|
bool const i_verticalScrollbar,
|
|
bool const i_horizontalScrollbar);
|
|
|
|
/** scrolls the view by the given number of rows
|
|
|
|
The method is not bound to the classes public invariants, as it's used in
|
|
situations where the they must not necessarily be fulfilled.
|
|
|
|
@return
|
|
the number of rows by which the viewport was scrolled. This may differ
|
|
from the given numbers to scroll in case the begin or the end of the
|
|
row range were reached.
|
|
*/
|
|
TableSize impl_ni_ScrollRows(TableSize _nRowDelta);
|
|
|
|
/** equivalent to impl_ni_ScrollRows, but checks the instances invariants beforehand (in a non-product build only) */
|
|
TableSize impl_scrollRows(TableSize const i_rowDelta);
|
|
|
|
/** scrolls the view by the given number of columns
|
|
|
|
The method is not bound to the classes public invariants, as it's used in
|
|
situations where the they must not necessarily be fulfilled.
|
|
|
|
@return
|
|
the number of columns by which the viewport was scrolled. This may differ
|
|
from the given numbers to scroll in case the begin or the end of the
|
|
column range were reached.
|
|
*/
|
|
TableSize impl_ni_ScrollColumns(TableSize _nColumnDelta);
|
|
|
|
/** equivalent to impl_ni_ScrollColumns, but checks the instances invariants beforehand (in a non-product build only) */
|
|
TableSize impl_scrollColumns(TableSize const i_columnDelta);
|
|
|
|
/** retrieves the area occupied by the totality of (at least partially) visible cells
|
|
|
|
The returned area includes row and column headers. Also, it takes into
|
|
account the fact that there might be less columns than would normally
|
|
find room in the control.
|
|
|
|
As a result of respecting the partial visibility of rows and columns,
|
|
the returned area might be larger than the data window's output size.
|
|
*/
|
|
tools::Rectangle impl_getAllVisibleCellsArea() const;
|
|
|
|
/** retrieves the area occupied by all (at least partially) visible data cells.
|
|
|
|
Effectively, the returned area is the same as returned by ->impl_getAllVisibleCellsArea,
|
|
minus the row and column header areas.
|
|
*/
|
|
tools::Rectangle impl_getAllVisibleDataCellArea() const;
|
|
|
|
/** retrieves the column which covers the given ordinate */
|
|
sal_Int32 impl_getColumnForOrdinate(tools::Long const i_ordinate) const;
|
|
|
|
/** retrieves the row which covers the given abscissa
|
|
*/
|
|
sal_Int32 impl_getRowForAbscissa(tools::Long const i_abscissa) const;
|
|
|
|
/// invalidates the window area occupied by the given column
|
|
void impl_invalidateColumn(sal_Int32 const i_column);
|
|
|
|
DECL_LINK(OnScroll, ScrollBar*, void);
|
|
DECL_LINK(OnUpdateScrollbars, void*, void);
|
|
|
|
DECL_LINK(ImplSelectHdl, LinkParamNone*, void);
|
|
};
|
|
|
|
} // namespace svt::table
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|