Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  tablecontrol_impl.cxx   Sprache: 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 .
 */



#include <controls/table/AccessibleGridControl.hxx>
#include <controls/table/tablecontrol.hxx>
#include <controls/table/defaultinputhandler.hxx>
#include <controls/table/tablemodel.hxx>

#include "tabledatawindow.hxx"
#include "tablecontrol_impl.hxx"
#include "tablegeometry.hxx"

#include <com/sun/star/accessibility/XAccessible.hpp>
#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp>
#include <com/sun/star/accessibility/AccessibleEventId.hpp>
#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp>

#include <comphelper/flagguard.hxx>
#include <vcl/toolkit/scrbar.hxx>
#include <vcl/seleng.hxx>
#include <vcl/settings.hxx>
#include <vcl/image.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <tools/debug.hxx>

#include <cstdlib>
#include <numeric>

#define MIN_COLUMN_WIDTH_PIXEL  4


namespace svt::table
{


    using ::com::sun::star::accessibility::AccessibleTableModelChange;
    using ::com::sun::star::uno::Any;
    using ::com::sun::star::accessibility::XAccessible;
    using ::com::sun::star::uno::Reference;

    namespace AccessibleEventId = css::accessibility::AccessibleEventId;
    namespace AccessibleTableModelChangeType = css::accessibility::AccessibleTableModelChangeType;


    //= SuppressCursor

    namespace {

    class SuppressCursor
    {
    private:
        ITableControl&  m_rTable;

    public:
        explicit SuppressCursor( ITableControl& _rTable )
            :m_rTable( _rTable )
        {
            m_rTable.hideCursor();
        }
        ~SuppressCursor()
        {
            m_rTable.showCursor();
        }
    };


    //= EmptyTableModel

    /** default implementation of an ->ITableModel, used as fallback when no
        real model is present

        Instances of this class are static in any way, and provide the least
        necessary default functionality for a table model.
    */

    class EmptyTableModel : public ITableModel
    {
    public:
        EmptyTableModel()
        {
        }

        // ITableModel overridables
        virtual TableSize           getColumnCount() const override
        {
            return 0;
        }
        virtual TableSize           getRowCount() const override
        {
            return 0;
        }
        virtual bool                hasColumnHeaders() const override
        {
            return false;
        }
        virtual bool                hasRowHeaders() const override
        {
            return false;
        }
        virtual PColumnModel        getColumnModel( ColPos ) override
        {
            OSL_FAIL( "EmptyTableModel::getColumnModel: invalid call!" );
            return PColumnModel();
        }
        virtual PTableRenderer      getRenderer() const override
        {
            return PTableRenderer();
        }
        virtual PTableInputHandler  getInputHandler() const override
        {
            return PTableInputHandler();
        }
        virtual TableMetrics        getRowHeight() const override
        {
            return 5 * 100;
        }
        virtual TableMetrics        getColumnHeaderHeight() const override
        {
            return 0;
        }
        virtual TableMetrics        getRowHeaderWidth() const override
        {
            return 0;
        }
        virtual ScrollbarVisibility getVerticalScrollbarVisibility() const override
        {
            return ScrollbarShowNever;
        }
        virtual ScrollbarVisibility getHorizontalScrollbarVisibility() const override
        {
            return ScrollbarShowNever;
        }
        virtual void addTableModelListener( const PTableModelListener& ) override {}
        virtual void removeTableModelListener( const PTableModelListener& ) override {}
        virtual ::std::optional< ::Color > getLineColor() const override
        {
            return ::std::optional< ::Color >();
        }
        virtual ::std::optional< ::Color > getHeaderBackgroundColor() const override
        {
            return ::std::optional< ::Color >();
        }
        virtual ::std::optional< ::Color > getHeaderTextColor() const override
        {
            return ::std::optional< ::Color >();
        }
        virtual ::std::optional< ::Color >    getActiveSelectionBackColor() const override
        {
            return ::std::optional< ::Color >();
        }
        virtual ::std::optional< ::Color >    getInactiveSelectionBackColor() const override
        {
            return ::std::optional< ::Color >();
        }
        virtual ::std::optional< ::Color >    getActiveSelectionTextColor() const override
        {
            return ::std::optional< ::Color >();
        }
        virtual ::std::optional< ::Color >    getInactiveSelectionTextColor() const override
        {
            return ::std::optional< ::Color >();
        }
        virtual ::std::optional< ::Color > getTextColor() const override
        {
            return ::std::optional< ::Color >();
        }
        virtual ::std::optional< ::Color > getTextLineColor() const override
        {
            return ::std::optional< ::Color >();
        }
        virtual ::std::optional< ::std::vector< ::Color > > getRowBackgroundColors() const override
        {
            return ::std::optional< ::std::vector< ::Color > >();
        }
        virtual css::style::VerticalAlignment getVerticalAlign() const override
        {
            return css::style::VerticalAlignment(0);
        }
        virtual ITableDataSort* getSortAdapter() override
        {
            return nullptr;
        }
        virtual bool isEnabled() const override
        {
            return true;
        }
        virtual void getCellContent( ColPos const, RowPos const, css::uno::Any& o_cellContent ) override
        {
            o_cellContent.clear();
        }
        virtual void getCellToolTip( ColPos const, RowPos const, css::uno::Any& ) override
        {
        }
        virtual Any getRowHeading( RowPos const ) const override
        {
            return Any();
        }
    };

    }

    TableControl_Impl::TableControl_Impl( TableControl& _rAntiImpl )
        :m_rAntiImpl            ( _rAntiImpl                    )
        ,m_pModel               ( std::make_shared<EmptyTableModel>() )
        ,m_pInputHandler        (                               )
        ,m_nRowHeightPixel      ( 15                            )
        ,m_nColHeaderHeightPixel( 0                             )
        ,m_nRowHeaderWidthPixel ( 0                             )
        ,m_nColumnCount         ( 0                             )
        ,m_nRowCount            ( 0                             )
        ,m_nCurColumn           ( COL_INVALID                   )
        ,m_nCurRow              ( ROW_INVALID                   )
        ,m_nLeftColumn          ( 0                             )
        ,m_nTopRow              ( 0                             )
        ,m_nCursorHidden        ( 1                             )
        ,m_pDataWindow          ( VclPtr<TableDataWindow>::Create( *this )  )
        ,m_pVScroll             ( nullptr                          )
        ,m_pHScroll             ( nullptr                          )
        ,m_pScrollCorner        ( nullptr                          )
        ,m_aSelectedRows        (                               )
        ,m_pTableFunctionSet    ( new TableFunctionSet( this  ) )
        ,m_nAnchor              ( -1                            )
        ,m_bUpdatingColWidths   ( false                         )
    {
        m_pSelEngine.reset( new SelectionEngine( m_pDataWindow.get(), m_pTableFunctionSet.get() ) );
        m_pSelEngine->SetSelectionMode(SelectionMode::Single);
        m_pDataWindow->SetPosPixel( Point( 0, 0 ) );
        m_pDataWindow->Show();
    }

    TableControl_Impl::~TableControl_Impl()
    {
        m_pVScroll.disposeAndClear();
        m_pHScroll.disposeAndClear();
        m_pScrollCorner.disposeAndClear();
        m_pDataWindow.disposeAndClear();
        m_pTableFunctionSet.reset();
        m_pSelEngine.reset();
    }

    void TableControl_Impl::setModel( const PTableModel& _pModel )
    {
        SuppressCursor aHideCursor( *this );

        if ( m_pModel )
            m_pModel->removeTableModelListener( shared_from_this() );

        m_pModel = _pModel;
        if ( !m_pModel)
            m_pModel = std::make_shared<EmptyTableModel>();

        m_pModel->addTableModelListener( shared_from_this() );

        m_nCurRow = ROW_INVALID;
        m_nCurColumn = COL_INVALID;

        // recalc some model-dependent cached info
        impl_ni_updateCachedModelValues();
        impl_ni_relayout();

        // completely invalidate
        m_rAntiImpl.Invalidate();

        // reset cursor to (0,0)
        if ( m_nRowCount ) m_nCurRow = 0;
        if ( m_nColumnCount ) m_nCurColumn = 0;
    }


    namespace
    {
        bool lcl_adjustSelectedRows( ::std::vector< RowPos >& io_selectionIndexes, RowPos const i_firstAffectedRowIndex, TableSize const i_offset )
        {
            bool didChanges = false;
            for (auto & selectionIndex : io_selectionIndexes)
            {
                if ( selectionIndex < i_firstAffectedRowIndex )
                    continue;
                selectionIndex += i_offset;
                didChanges = true;
            }
            return didChanges;
        }
    }


    void TableControl_Impl::rowsInserted( RowPos i_first, RowPos i_last )
    {
        OSL_PRECOND( i_last >= i_first, "TableControl_Impl::rowsInserted: invalid row indexes!" );

        TableSize const insertedRows = i_last - i_first + 1;

        // adjust selection, if necessary
        bool const selectionChanged = lcl_adjustSelectedRows( m_aSelectedRows, i_first, insertedRows );

        // adjust our cached row count
        m_nRowCount = m_pModel->getRowCount();

        // if the rows have been inserted before the current row, adjust this
        if ( i_first <= m_nCurRow )
            goTo( m_nCurColumn, m_nCurRow + insertedRows );

        // relayout, since the scrollbar need might have changed
        impl_ni_relayout();

        // notify A1YY events
        impl_commitAccessibleEvent(
            AccessibleEventId::TABLE_MODEL_CHANGED,
            Any(AccessibleTableModelChange(AccessibleTableModelChangeType::ROWS_INSERTED, i_first,
                                           i_last, -1, -1)));

        // schedule repaint
        invalidateRowRange( i_first, ROW_INVALID );

        // call selection handlers, if necessary
        if ( selectionChanged )
            m_rAntiImpl.Select();
    }


    void TableControl_Impl::rowsRemoved( RowPos i_first, RowPos i_last )
    {
        sal_Int32 firstRemovedRow = i_first;
        sal_Int32 lastRemovedRow = i_last;

        // adjust selection, if necessary
        bool selectionChanged = false;
        if ( i_first == -1 )
        {
            selectionChanged = markAllRowsAsDeselected();

            firstRemovedRow = 0;
            lastRemovedRow = m_nRowCount - 1;
        }
        else
        {
            ENSURE_OR_RETURN_VOID( i_last >= i_first, "TableControl_Impl::rowsRemoved: illegal indexes!" );

            for ( sal_Int32 row = i_first; row <= i_last; ++row )
            {
                if ( markRowAsDeselected( row ) )
                    selectionChanged = true;
            }

            if ( lcl_adjustSelectedRows( m_aSelectedRows, i_last + 1, i_first - i_last - 1 ) )
                selectionChanged = true;
        }

        // adjust cached row count
        m_nRowCount = m_pModel->getRowCount();

        // adjust the current row, if it is larger than the row count now
        if ( m_nCurRow >= m_nRowCount )
        {
            if ( m_nRowCount > 0 )
                goTo( m_nCurColumn, m_nRowCount - 1 );
            else
            {
                m_nCurRow = ROW_INVALID;
                m_nTopRow = 0;
            }
        }
        else if ( m_nRowCount == 0 )
        {
            m_nTopRow = 0;
        }


        // relayout, since the scrollbar need might have changed
        impl_ni_relayout();

        // notify A11Y events
        commitTableEvent(
            AccessibleEventId::TABLE_MODEL_CHANGED,
            Any(AccessibleTableModelChange(AccessibleTableModelChangeType::ROWS_REMOVED,
                                           firstRemovedRow, lastRemovedRow, -1, -1)),
            Any());

        // schedule a repaint
        invalidateRowRange( firstRemovedRow, ROW_INVALID );

        // call selection handlers, if necessary
        if ( selectionChanged )
            m_rAntiImpl.Select();
    }


    void TableControl_Impl::columnInserted()
    {
        m_nColumnCount = m_pModel->getColumnCount();
        impl_ni_relayout();

        m_rAntiImpl.Invalidate();
   }


    void TableControl_Impl::columnRemoved()
    {
        m_nColumnCount = m_pModel->getColumnCount();

        // adjust the current column, if it is larger than the column count now
        if ( m_nCurColumn >= m_nColumnCount )
        {
            if ( m_nColumnCount > 0 )
                goTo( m_nCurColumn - 1, m_nCurRow );
            else
                m_nCurColumn = COL_INVALID;
        }

        impl_ni_relayout();

        m_rAntiImpl.Invalidate();
    }


    void TableControl_Impl::allColumnsRemoved()
    {
        m_nColumnCount = m_pModel->getColumnCount();
        impl_ni_relayout();

        m_rAntiImpl.Invalidate();
    }


    void TableControl_Impl::cellsUpdated( RowPos const i_firstRow, RowPos const i_lastRow )
    {
        invalidateRowRange( i_firstRow, i_lastRow );
    }


    void TableControl_Impl::tableMetricsChanged()
    {
        impl_ni_updateCachedTableMetrics();
        impl_ni_relayout();
        m_rAntiImpl.Invalidate();
    }


    void TableControl_Impl::impl_invalidateColumn( ColPos const i_column )
    {
        tools::Rectangle const aAllCellsArea( impl_getAllVisibleCellsArea() );

        const TableColumnGeometry aColumn( *this, aAllCellsArea, i_column );
        if ( aColumn.isValid() )
            m_rAntiImpl.Invalidate( aColumn.getRect() );
    }


    void TableControl_Impl::columnChanged( ColPos const i_column, ColumnAttributeGroup const i_attributeGroup )
    {
        ColumnAttributeGroup nGroup( i_attributeGroup );
        if ( nGroup & ColumnAttributeGroup::APPEARANCE )
        {
            impl_invalidateColumn( i_column );
            nGroup &= ~ColumnAttributeGroup::APPEARANCE;
        }

        if ( nGroup & ColumnAttributeGroup::WIDTH )
        {
            if ( !m_bUpdatingColWidths )
            {
                impl_ni_relayout( i_column );
                invalidate( TableArea::All );
            }

            nGroup &= ~ColumnAttributeGroup::WIDTH;
        }

        OSL_ENSURE( ( nGroup == ColumnAttributeGroup::NONE ) || ( i_attributeGroup == ColumnAttributeGroup::ALL ),
            "TableControl_Impl::columnChanged: don't know how to handle this change!" );
    }


    tools::Rectangle TableControl_Impl::impl_getAllVisibleCellsArea() const
    {
        tools::Rectangle aArea( Point( 0, 0 ), Size( 0, 0 ) );

        // determine the right-most border of the last column which is
        // at least partially visible
        aArea.SetRight( m_nRowHeaderWidthPixel );
        if ( !m_aColumnWidths.empty() )
        {
            // the number of pixels which are scrolled out of the left hand
            // side of the window
            const tools::Long nScrolledOutLeft = m_nLeftColumn == 0 ? 0 : m_aColumnWidths[ m_nLeftColumn - 1 ].getEnd();

            ColumnPositions::const_reverse_iterator loop = m_aColumnWidths.rbegin();
            do
            {
                aArea.SetRight(loop->getEnd() - nScrolledOutLeft);
                ++loop;
            }
            while ( (   loop != m_aColumnWidths.rend() )
                 && (   loop->getEnd() - nScrolledOutLeft >= aArea.Right() )
                 );
        }
        // so far, aArea.Right() denotes the first pixel *after* the cell area
        aArea.AdjustRight( -1 );

        // determine the last row which is at least partially visible
        aArea.SetBottom(
                m_nColHeaderHeightPixel
            +   impl_getVisibleRows( true ) * m_nRowHeightPixel
            -   1 );

        return aArea;
    }


    tools::Rectangle TableControl_Impl::impl_getAllVisibleDataCellArea() const
    {
        tools::Rectangle aArea( impl_getAllVisibleCellsArea() );
        aArea.SetLeft( m_nRowHeaderWidthPixel );
        aArea.SetTop( m_nColHeaderHeightPixel );
        return aArea;
    }


    void TableControl_Impl::impl_ni_updateCachedTableMetrics()
    {
        m_nRowHeightPixel = m_rAntiImpl.LogicToPixel(Size(0, m_pModel->getRowHeight()), MapMode(MapUnit::MapAppFont)).Height();

        m_nColHeaderHeightPixel = 0;
        if ( m_pModel->hasColumnHeaders() )
           m_nColHeaderHeightPixel = m_rAntiImpl.LogicToPixel(Size(0, m_pModel->getColumnHeaderHeight()), MapMode(MapUnit::MapAppFont)).Height();

        m_nRowHeaderWidthPixel = 0;
        if ( m_pModel->hasRowHeaders() )
            m_nRowHeaderWidthPixel = m_rAntiImpl.LogicToPixel(Size(m_pModel->getRowHeaderWidth(), 0), MapMode(MapUnit::MapAppFont)).Width();
    }


    void TableControl_Impl::impl_ni_updateCachedModelValues()
    {
        m_pInputHandler = m_pModel->getInputHandler();
        if ( !m_pInputHandler )
            m_pInputHandler = std::make_shared<DefaultInputHandler>();

        m_nColumnCount = m_pModel->getColumnCount();
        if ( m_nLeftColumn >= m_nColumnCount )
            m_nLeftColumn = ( m_nColumnCount > 0 ) ? m_nColumnCount - 1 : 0;

        m_nRowCount = m_pModel->getRowCount();
        if ( m_nTopRow >= m_nRowCount )
            m_nTopRow = ( m_nRowCount > 0 ) ? m_nRowCount - 1 : 0;

        impl_ni_updateCachedTableMetrics();
    }


    namespace
    {

        /// determines whether a scrollbar is needed for the given values
        bool lcl_determineScrollbarNeed( tools::Long const i_position, ScrollbarVisibility const i_visibility,
            tools::Long const i_availableSpace, tools::Long const i_neededSpace )
        {
            if ( i_visibility == ScrollbarShowNever )
                return false;
            if ( i_visibility == ScrollbarShowAlways )
                return true;
            if ( i_position > 0 )
                return true;
            if ( i_availableSpace >= i_neededSpace )
                return false;
            return true;
        }


        void lcl_setButtonRepeat( vcl::Window& _rWindow )
        {
            AllSettings aSettings = _rWindow.GetSettings();
            MouseSettings aMouseSettings = aSettings.GetMouseSettings();

            aMouseSettings.SetButtonRepeat( 0 );
            aSettings.SetMouseSettings( aMouseSettings );

            _rWindow.SetSettings( aSettings, true );
        }


        bool lcl_updateScrollbar( vcl::Window& _rParent, VclPtr<ScrollBar>& _rpBar,
            bool const i_needBar, tools::Long _nVisibleUnits,
            tools::Long _nPosition, tools::Long _nRange,
            bool _bHorizontal, const Link<ScrollBar*,void>& _rScrollHandler )
        {
            // do we currently have the scrollbar?
            bool bHaveBar = _rpBar != nullptr;

            // do we need to correct the scrollbar visibility?
            if ( bHaveBar && !i_needBar )
            {
                if ( _rpBar->IsTracking() )
                    _rpBar->EndTracking();
                _rpBar.disposeAndClear();
            }
            else if ( !bHaveBar && i_needBar )
            {
                _rpBar = VclPtr<ScrollBar>::Create(

                    &_rParent,
                    WB_DRAG | ( _bHorizontal ? WB_HSCROLL : WB_VSCROLL )
                );
                _rpBar->SetScrollHdl( _rScrollHandler );
                // get some speed into the scrolling...
                lcl_setButtonRepeat( *_rpBar );
            }

            if ( _rpBar )
            {
                _rpBar->SetRange( Range( 0, _nRange ) );
                _rpBar->SetVisibleSize( _nVisibleUnits );
                _rpBar->SetPageSize( _nVisibleUnits );
                _rpBar->SetLineSize( 1 );
                _rpBar->SetThumbPos( _nPosition );
                _rpBar->Show();
            }

            return ( bHaveBar != i_needBar );
        }


        /** returns the number of rows fitting into the given range,
            for the given row height. Partially fitting rows are counted, too, if the
            respective parameter says so.
        */

        TableSize lcl_getRowsFittingInto( tools::Long _nOverallHeight, tools::Long _nRowHeightPixel, bool _bAcceptPartialRow )
        {
            return  _bAcceptPartialRow
                ?   ( _nOverallHeight + ( _nRowHeightPixel - 1 ) ) / _nRowHeightPixel
                :   _nOverallHeight / _nRowHeightPixel;
        }


        /** returns the number of columns fitting into the given area,
            with the first visible column as given. Partially fitting columns are counted, too,
            if the respective parameter says so.
        */

        TableSize lcl_getColumnsVisibleWithin( const tools::Rectangle& _rArea, ColPos _nFirstVisibleColumn,
            const TableControl_Impl& _rControl, bool _bAcceptPartialRow )
        {
            TableSize visibleColumns = 0;
            TableColumnGeometry aColumn( _rControl, _rArea, _nFirstVisibleColumn );
            while ( aColumn.isValid() )
            {
                if ( !_bAcceptPartialRow )
                    if ( aColumn.getRect().Right() > _rArea.Right() )
                        // this column is only partially visible, and this is not allowed
                        break;

                aColumn.moveRight();
                ++visibleColumns;
            }
            return visibleColumns;
        }

    }


    tools::Long TableControl_Impl::impl_ni_calculateColumnWidths( ColPos const i_assumeInflexibleColumnsUpToIncluding,
        bool const i_assumeVerticalScrollbar, ::std::vector< tools::Long >& o_newColWidthsPixel ) const
    {
        // the available horizontal space
        tools::Long gridWidthPixel = m_rAntiImpl.GetOutputSizePixel().Width();
        ENSURE_OR_RETURN( !!m_pModel, "TableControl_Impl::impl_ni_calculateColumnWidths: not allowed without a model!", gridWidthPixel );
        if ( m_pModel->hasRowHeaders() && ( gridWidthPixel != 0 ) )
        {
            gridWidthPixel -= m_nRowHeaderWidthPixel;
        }

        if ( i_assumeVerticalScrollbar && ( m_pModel->getVerticalScrollbarVisibility() != ScrollbarShowNever ) )
        {
            tools::Long nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
            gridWidthPixel -= nScrollbarMetrics;
        }

        // no need to do anything without columns
        TableSize const colCount = m_pModel->getColumnCount();
        if ( colCount == 0 )
            return gridWidthPixel;

        // collect some meta data for our columns:
        // - their current (pixel) metrics
        tools::Long accumulatedCurrentWidth = 0;
        ::std::vector< tools::Long > currentColWidths;
        currentColWidths.reserve( colCount );
        typedef ::std::vector< ::std::pair< tools::Longlong > >   ColumnLimits;
        ColumnLimits effectiveColumnLimits;
        effectiveColumnLimits.reserve( colCount );
        tools::Long accumulatedMinWidth = 0;
        tools::Long accumulatedMaxWidth = 0;
        // - their relative flexibility
        ::std::vector< ::sal_Int32 > columnFlexibilities;
        columnFlexibilities.reserve( colCount );
        tools::Long flexibilityDenominator = 0;
        size_t flexibleColumnCount = 0;
        for ( ColPos col = 0; col < colCount; ++col )
        {
            PColumnModel const pColumn = m_pModel->getColumnModel( col );
            ENSURE_OR_THROW( !!pColumn, "invalid column returned by the model!" );

            // current width
            tools::Long const currentWidth = appFontWidthToPixel( pColumn->getWidth() );
            currentColWidths.push_back( currentWidth );

            // accumulated width
            accumulatedCurrentWidth += currentWidth;

            // flexibility
            ::sal_Int32 flexibility = pColumn->getFlexibility();
            OSL_ENSURE( flexibility >= 0, "TableControl_Impl::impl_ni_calculateColumnWidths: a column's flexibility should be non-negative." );
            if  (   ( flexibility < 0 )                                 // normalization
                ||  ( !pColumn->isResizable() )                         // column not resizable => no auto-resize
                ||  ( col <= i_assumeInflexibleColumnsUpToIncluding )   // column shall be treated as inflexible => respect this
                )
                flexibility = 0;

            // min/max width
            tools::Long effectiveMin = currentWidth, effectiveMax = currentWidth;
            // if the column is not flexible, it will not be asked for min/max, but we assume the current width as limit then
            if ( flexibility > 0 )
            {
                tools::Long const minWidth = appFontWidthToPixel( pColumn->getMinWidth() );
                if ( minWidth > 0 )
                    effectiveMin = minWidth;
                else
                    effectiveMin = MIN_COLUMN_WIDTH_PIXEL;

                tools::Long const maxWidth = appFontWidthToPixel( pColumn->getMaxWidth() );
                OSL_ENSURE( minWidth <= maxWidth, "TableControl_Impl::impl_ni_calculateColumnWidths: pretty undecided 'bout its width limits, this column!" );
                if ( ( maxWidth > 0 ) && ( maxWidth >= minWidth ) )
                    effectiveMax = maxWidth;
                else
                    effectiveMax = gridWidthPixel; // TODO: any better guess here?

                if ( effectiveMin == effectiveMax )
                    // if the min and the max are identical, this implies no flexibility at all
                    flexibility = 0;
            }

            columnFlexibilities.push_back( flexibility );
            flexibilityDenominator += flexibility;
            if ( flexibility > 0 )
                ++flexibleColumnCount;

            effectiveColumnLimits.emplace_back( effectiveMin, effectiveMax );
            accumulatedMinWidth += effectiveMin;
            accumulatedMaxWidth += effectiveMax;
        }

        o_newColWidthsPixel = currentColWidths;
        if ( flexibilityDenominator == 0 )
        {
            // no column is flexible => don't adjust anything
        }
        else if ( gridWidthPixel > accumulatedCurrentWidth )
        {   // we have space to give away ...
            tools::Long distributePixel = gridWidthPixel - accumulatedCurrentWidth;
            if ( gridWidthPixel > accumulatedMaxWidth )
            {
                // ... but the column's maximal widths are still less than we have
                // => set them all to max
                for ( svt::table::TableSize i = 0; i < colCount; ++i )
                {
                    o_newColWidthsPixel[i] = effectiveColumnLimits[i].second;
                }
            }
            else
            {
                bool startOver = false;
                do
                {
                    startOver = false;
                    // distribute the remaining space amongst all columns with a positive flexibility
                    for ( size_t i=0; i<o_newColWidthsPixel.size() && !startOver; ++i )
                    {
                        tools::Long const columnFlexibility = columnFlexibilities[i];
                        if ( columnFlexibility == 0 )
                            continue;

                        tools::Long newColWidth = currentColWidths[i] + columnFlexibility * distributePixel / flexibilityDenominator;

                        if ( newColWidth > effectiveColumnLimits[i].second )
                        {   // that was too much, we hit the col's maximum
                            // set the new width to exactly this maximum
                            newColWidth = effectiveColumnLimits[i].second;
                            // adjust the flexibility denominator ...
                            flexibilityDenominator -= columnFlexibility;
                            columnFlexibilities[i] = 0;
                            --flexibleColumnCount;
                            // ... and the remaining width ...
                            tools::Long const difference = newColWidth - currentColWidths[i];
                            distributePixel -= difference;
                            // ... this way, we ensure that the width not taken up by this column is consumed by the other
                            // flexible ones (if there are some)

                            // and start over with the first column, since there might be earlier columns which need
                            // to be recalculated now
                            startOver = true;
                        }

                        o_newColWidthsPixel[i] = newColWidth;
                    }
                }
                while ( startOver );

                // are there pixels left (might be caused by rounding errors)?
                distributePixel = gridWidthPixel - ::std::accumulate( o_newColWidthsPixel.begin(), o_newColWidthsPixel.end(), 0 );
                while ( ( distributePixel > 0 ) && ( flexibleColumnCount > 0 ) )
                {
                    // yes => ignore relative flexibilities, and subsequently distribute single pixels to all flexible
                    // columns which did not yet reach their maximum.
                    for ( size_t i=0; ( i < o_newColWidthsPixel.size() ) && ( distributePixel > 0 ); ++i )
                    {
                        if ( columnFlexibilities[i] == 0 )
                            continue;

                        OSL_ENSURE( o_newColWidthsPixel[i] <= effectiveColumnLimits[i].second,
                            "TableControl_Impl::impl_ni_calculateColumnWidths: inconsistency!" );
                        if ( o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first )
                        {
                            columnFlexibilities[i] = 0;
                            --flexibleColumnCount;
                            continue;
                        }

                        ++o_newColWidthsPixel[i];
                        --distributePixel;
                    }
                }
            }
        }
        else if ( gridWidthPixel < accumulatedCurrentWidth )
        {   // we need to take away some space from the columns which allow it ...
            tools::Long takeAwayPixel = accumulatedCurrentWidth - gridWidthPixel;
            if ( gridWidthPixel < accumulatedMinWidth )
            {
                // ... but the column's minimal widths are still more than we have
                // => set them all to min
                for ( svt::table::TableSize i = 0; i < colCount; ++i )
                {
                    o_newColWidthsPixel[i] = effectiveColumnLimits[i].first;
                }
            }
            else
            {
                bool startOver = false;
                do
                {
                    startOver = false;
                    // take away the space we need from the columns with a positive flexibility
                    for ( size_t i=0; i<o_newColWidthsPixel.size() && !startOver; ++i )
                    {
                        tools::Long const columnFlexibility = columnFlexibilities[i];
                        if ( columnFlexibility == 0 )
                            continue;

                        tools::Long newColWidth = currentColWidths[i] - columnFlexibility * takeAwayPixel / flexibilityDenominator;

                        if ( newColWidth < effectiveColumnLimits[i].first )
                        {   // that was too much, we hit the col's minimum
                            // set the new width to exactly this minimum
                            newColWidth = effectiveColumnLimits[i].first;
                            // adjust the flexibility denominator ...
                            flexibilityDenominator -= columnFlexibility;
                            columnFlexibilities[i] = 0;
                            --flexibleColumnCount;
                            // ... and the remaining width ...
                            tools::Long const difference = currentColWidths[i] - newColWidth;
                            takeAwayPixel -= difference;

                            // and start over with the first column, since there might be earlier columns which need
                            // to be recalculated now
                            startOver = true;
                        }

                        o_newColWidthsPixel[i] = newColWidth;
                    }
                }
                while ( startOver );

                // are there pixels left (might be caused by rounding errors)?
                takeAwayPixel = ::std::accumulate( o_newColWidthsPixel.begin(), o_newColWidthsPixel.end(), 0 ) - gridWidthPixel;
                while ( ( takeAwayPixel > 0 ) && ( flexibleColumnCount > 0 ) )
                {
                    // yes => ignore relative flexibilities, and subsequently take away pixels from all flexible
                    // columns which did not yet reach their minimum.
                    for ( size_t i=0; ( i < o_newColWidthsPixel.size() ) && ( takeAwayPixel > 0 ); ++i )
                    {
                        if ( columnFlexibilities[i] == 0 )
                            continue;

                        OSL_ENSURE( o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first,
                            "TableControl_Impl::impl_ni_calculateColumnWidths: inconsistency!" );
                        if ( o_newColWidthsPixel[i] <= effectiveColumnLimits[i].first )
                        {
                            columnFlexibilities[i] = 0;
                            --flexibleColumnCount;
                            continue;
                        }

                        --o_newColWidthsPixel[i];
                        --takeAwayPixel;
                    }
                }
            }
        }

        return gridWidthPixel;
    }


    void TableControl_Impl::impl_ni_relayout( ColPos const i_assumeInflexibleColumnsUpToIncluding )
    {
        ENSURE_OR_RETURN_VOID( !m_bUpdatingColWidths, "TableControl_Impl::impl_ni_relayout: recursive call detected!" );

        m_aColumnWidths.resize( 0 );
        if ( !m_pModel )
            return;

        ::comphelper::FlagRestorationGuard const aWidthUpdateFlag( m_bUpdatingColWidths, true );
        SuppressCursor aHideCursor( *this );

        // layouting steps:

        // 1. adjust column widths, leaving space for a vertical scrollbar
        // 2. determine need for a vertical scrollbar
        //    - V-YES: all fine, result from 1. is still valid
        //    - V-NO: result from 1. is still under consideration

        // 3. determine need for a horizontal scrollbar
        //   - H-NO: all fine, result from 2. is still valid
        //   - H-YES: reconsider need for a vertical scrollbar, if result of 2. was V-NO
        //     - V-YES: all fine, result from 1. is still valid
        //     - V-NO: redistribute the remaining space (if any) amongst all columns which allow it

        ::std::vector< tools::Long > newWidthsPixel;
        tools::Long gridWidthPixel = impl_ni_calculateColumnWidths( i_assumeInflexibleColumnsUpToIncluding, true, newWidthsPixel );

        // the width/height of a scrollbar, needed several times below
        tools::Long const nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();

        // determine the playground for the data cells (excluding headers)
        // TODO: what if the control is smaller than needed for the headers/scrollbars?
        tools::Rectangle aDataCellPlayground( Point( 0, 0 ), m_rAntiImpl.GetOutputSizePixel() );
        aDataCellPlayground.SetLeft( m_nRowHeaderWidthPixel );
        aDataCellPlayground.SetTop( m_nColHeaderHeightPixel );

        OSL_ENSURE( ( m_nRowCount == m_pModel->getRowCount() ) && ( m_nColumnCount == m_pModel->getColumnCount() ),
            "TableControl_Impl::impl_ni_relayout: how is this expected to work with invalid data?" );
        tools::Long const nAllColumnsWidth = ::std::accumulate( newWidthsPixel.begin(), newWidthsPixel.end(), 0 );

        ScrollbarVisibility const eVertScrollbar = m_pModel->getVerticalScrollbarVisibility();
        ScrollbarVisibility const eHorzScrollbar = m_pModel->getHorizontalScrollbarVisibility();

        // do we need a vertical scrollbar?
        bool bNeedVerticalScrollbar = lcl_determineScrollbarNeed(
            m_nTopRow, eVertScrollbar, aDataCellPlayground.GetHeight(), m_nRowHeightPixel * m_nRowCount );
        bool bFirstRoundVScrollNeed = false;
        if ( bNeedVerticalScrollbar )
        {
            aDataCellPlayground.AdjustRight( -nScrollbarMetrics );
            bFirstRoundVScrollNeed = true;
        }

        // do we need a horizontal scrollbar?
        bool const bNeedHorizontalScrollbar = lcl_determineScrollbarNeed(
            m_nLeftColumn, eHorzScrollbar, aDataCellPlayground.GetWidth(), nAllColumnsWidth );
        if ( bNeedHorizontalScrollbar )
        {
            aDataCellPlayground.AdjustBottom( -nScrollbarMetrics );

            // now that we just found that we need a horizontal scrollbar,
            // the need for a vertical one may have changed, since the horizontal
            // SB might just occupy enough space so that not all rows do fit
            // anymore
            if  ( !bFirstRoundVScrollNeed )
            {
                bNeedVerticalScrollbar = lcl_determineScrollbarNeed(
                    m_nTopRow, eVertScrollbar, aDataCellPlayground.GetHeight(), m_nRowHeightPixel * m_nRowCount );
                if ( bNeedVerticalScrollbar )
                {
                    aDataCellPlayground.AdjustRight( -nScrollbarMetrics );
                }
            }
        }

        // the initial call to impl_ni_calculateColumnWidths assumed that we need a vertical scrollbar. If, by now,
        // we know that this is not the case, re-calculate the column widths.
        if ( !bNeedVerticalScrollbar )
            gridWidthPixel = impl_ni_calculateColumnWidths( i_assumeInflexibleColumnsUpToIncluding, false, newWidthsPixel );

        // update the column objects with the new widths we finally calculated
        TableSize const colCount = m_pModel->getColumnCount();
        m_aColumnWidths.reserve( colCount );
        tools::Long accumulatedWidthPixel = m_nRowHeaderWidthPixel;
        bool anyColumnWidthChanged = false;
        for ( ColPos col = 0; col < colCount; ++col )
        {
            const tools::Long columnStart = accumulatedWidthPixel;
            const tools::Long columnEnd = columnStart + newWidthsPixel[col];
            m_aColumnWidths.emplace_back( columnStart, columnEnd );
            accumulatedWidthPixel = columnEnd;

            // and don't forget to forward this to the column models
            PColumnModel const pColumn = m_pModel->getColumnModel( col );
            ENSURE_OR_THROW( !!pColumn, "invalid column returned by the model!" );

            tools::Long const oldColumnWidthAppFont = pColumn->getWidth();
            tools::Long const newColumnWidthAppFont = pixelWidthToAppFont( newWidthsPixel[col] );
            pColumn->setWidth( newColumnWidthAppFont );

            anyColumnWidthChanged |= ( oldColumnWidthAppFont != newColumnWidthAppFont );
        }

        // if the column widths changed, ensure everything is repainted
        if ( anyColumnWidthChanged )
            invalidate( TableArea::All );

        // if the column resizing happened to leave some space at the right, but there are columns
        // scrolled out to the left, scroll them in
        while   (   ( m_nLeftColumn > 0 )
                &&  ( accumulatedWidthPixel - m_aColumnWidths[ m_nLeftColumn - 1 ].getStart() <= gridWidthPixel )
                )
        {
            --m_nLeftColumn;
        }

        // now adjust the column metrics, since they currently ignore the horizontal scroll position
        if ( m_nLeftColumn > 0 )
        {
            const tools::Long offsetPixel = m_aColumnWidths[ 0 ].getStart() - m_aColumnWidths[ m_nLeftColumn ].getStart();
            for (auto & columnWidth : m_aColumnWidths)
            {
                columnWidth.move( offsetPixel );
            }
        }

        // show or hide the scrollbars as needed, and position the data window
        impl_ni_positionChildWindows( aDataCellPlayground, bNeedVerticalScrollbar, bNeedHorizontalScrollbar );
    }


    void TableControl_Impl::impl_ni_positionChildWindows( tools::Rectangle const & i_dataCellPlayground,
        bool const i_verticalScrollbar, bool const i_horizontalScrollbar )
    {
        tools::Long const nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();

        // create or destroy the vertical scrollbar, as needed
        lcl_updateScrollbar(
            m_rAntiImpl,
            m_pVScroll,
            i_verticalScrollbar,
            lcl_getRowsFittingInto( i_dataCellPlayground.GetHeight(), m_nRowHeightPixel, false ),
                                                                    // visible units
            m_nTopRow,                                              // current position
            m_nRowCount,                                            // range
            false,                                                  // vertical
            LINK( this, TableControl_Impl, OnScroll )               // scroll handler
        );

        // position it
        if ( m_pVScroll )
        {
            tools::Rectangle aScrollbarArea(
                Point( i_dataCellPlayground.Right() + 1, 0 ),
                Size( nScrollbarMetrics, i_dataCellPlayground.Bottom() + 1 )
            );
            m_pVScroll->SetPosSizePixel(
                aScrollbarArea.TopLeft(), aScrollbarArea.GetSize() );
        }

        // create or destroy the horizontal scrollbar, as needed
        lcl_updateScrollbar(
            m_rAntiImpl,
            m_pHScroll,
            i_horizontalScrollbar,
            lcl_getColumnsVisibleWithin( i_dataCellPlayground, m_nLeftColumn, *thisfalse ),
                                                                    // visible units
            m_nLeftColumn,                                          // current position
            m_nColumnCount,                                         // range
            true,                                                   // horizontal
            LINK( this, TableControl_Impl, OnScroll )               // scroll handler
        );

        // position it
        if ( m_pHScroll )
        {
            TableSize const nVisibleUnits = lcl_getColumnsVisibleWithin( i_dataCellPlayground, m_nLeftColumn, *thisfalse );
            TableMetrics const nRange = m_nColumnCount;
            if( m_nLeftColumn + nVisibleUnits == nRange - 1 )
            {
                if ( m_aColumnWidths[ nRange - 1 ].getStart() - m_aColumnWidths[ m_nLeftColumn ].getEnd() + m_aColumnWidths[ nRange-1 ].getWidth() > i_dataCellPlayground.GetWidth() )
                {
                    m_pHScroll->SetVisibleSize( nVisibleUnits -1 );
                    m_pHScroll->SetPageSize( nVisibleUnits - 1 );
                }
            }
            tools::Rectangle aScrollbarArea(
                Point( 0, i_dataCellPlayground.Bottom() + 1 ),
                Size( i_dataCellPlayground.Right() + 1, nScrollbarMetrics )
            );
            m_pHScroll->SetPosSizePixel(
                aScrollbarArea.TopLeft(), aScrollbarArea.GetSize() );
        }

        // the corner window connecting the two scrollbars in the lower right corner
        bool bHaveScrollCorner = nullptr != m_pScrollCorner;
        bool bNeedScrollCorner = ( nullptr != m_pHScroll ) && ( nullptr != m_pVScroll );
        if ( bHaveScrollCorner && !bNeedScrollCorner )
        {
            m_pScrollCorner.disposeAndClear();
        }
        else if ( !bHaveScrollCorner && bNeedScrollCorner )
        {
            m_pScrollCorner = VclPtr<ScrollBarBox>::Create( &m_rAntiImpl );
            m_pScrollCorner->SetSizePixel( Size( nScrollbarMetrics, nScrollbarMetrics ) );
            m_pScrollCorner->SetPosPixel( Point( i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1 ) );
            m_pScrollCorner->Show();
        }
        else if(bHaveScrollCorner && bNeedScrollCorner)
        {
            m_pScrollCorner->SetPosPixel( Point( i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1 ) );
            m_pScrollCorner->Show();
        }

        // resize the data window
        m_pDataWindow->SetSizePixel( Size(
            i_dataCellPlayground.GetWidth() + m_nRowHeaderWidthPixel,
            i_dataCellPlayground.GetHeight() + m_nColHeaderHeightPixel
        ) );
    }


    void TableControl_Impl::onResize()
    {
        impl_ni_relayout();
        checkCursorPosition();
    }


    void TableControl_Impl::doPaintContent(vcl::RenderContext& rRenderContext, const tools::Rectangle& _rUpdateRect)
    {
        if (!getModel())
            return;
        PTableRenderer pRenderer = getModel()->getRenderer();
        DBG_ASSERT(!!pRenderer, "TableDataWindow::doPaintContent: invalid renderer!");
        if (!pRenderer)
            return;

        // our current style settings, to be passed to the renderer
        const StyleSettings& rStyle = rRenderContext.GetSettings().GetStyleSettings();
        m_nRowCount = m_pModel->getRowCount();
        // the area occupied by all (at least partially) visible cells, including
        // headers
        tools::Rectangle const aAllCellsWithHeaders( impl_getAllVisibleCellsArea() );

        // draw the header column area
        if (m_pModel->hasColumnHeaders())
        {
            TableRowGeometry const aHeaderRow(*this, tools::Rectangle(Point(0, 0), aAllCellsWithHeaders.BottomRight()), ROW_COL_HEADERS);
            tools::Rectangle const aColRect(aHeaderRow.getRect());
            pRenderer->PaintHeaderArea(rRenderContext, aColRect, truefalse, rStyle);
            // Note that strictly, aHeaderRow.getRect() also contains the intersection between column
            // and row header area. However, below we go to paint this intersection, again,
            // so this hopefully doesn't hurt if we already paint it here.

            for (TableCellGeometry aCell(aHeaderRow, m_nLeftColumn); aCell.isValid(); aCell.moveRight())
            {
                if (_rUpdateRect.GetIntersection(aCell.getRect()).IsEmpty())
                    continue;

                pRenderer->PaintColumnHeader(aCell.getColumn(), rRenderContext, aCell.getRect(), rStyle);
            }
        }
        // the area occupied by the row header, if any
        tools::Rectangle aRowHeaderArea;
        if (m_pModel->hasRowHeaders())
        {
            aRowHeaderArea = aAllCellsWithHeaders;
            aRowHeaderArea.SetRight( m_nRowHeaderWidthPixel - 1 );

            TableSize const nVisibleRows = impl_getVisibleRows(true);
            TableSize nActualRows = nVisibleRows;
            if (m_nTopRow + nActualRows > m_nRowCount)
                nActualRows = m_nRowCount - m_nTopRow;
            aRowHeaderArea.SetBottom( m_nColHeaderHeightPixel + m_nRowHeightPixel * nActualRows - 1 );

            pRenderer->PaintHeaderArea(rRenderContext, aRowHeaderArea, falsetrue, rStyle);
            // Note that strictly, aRowHeaderArea also contains the intersection between column
            // and row header area. However, below we go to paint this intersection, again,
            // so this hopefully doesn't hurt if we already paint it here.

            if (m_pModel->hasColumnHeaders())
            {
                TableCellGeometry const aIntersection(*this, tools::Rectangle(Point(0, 0), aAllCellsWithHeaders.BottomRight()),
                                                      COL_ROW_HEADERS, ROW_COL_HEADERS);
                tools::Rectangle const aInters(aIntersection.getRect());
                pRenderer->PaintHeaderArea(rRenderContext, aInters, truetrue, rStyle);
            }
        }

        // draw the table content row by row
        TableSize colCount = getModel()->getColumnCount();

        // paint all rows
        tools::Rectangle const aAllDataCellsArea(impl_getAllVisibleDataCellArea());
        for (TableRowGeometry aRowIterator(*this, aAllCellsWithHeaders, getTopRow()); aRowIterator.isValid(); aRowIterator.moveDown())
        {
            if (_rUpdateRect.GetIntersection(aRowIterator.getRect() ).IsEmpty())
                continue;

            bool const isControlFocused = m_rAntiImpl.HasControlFocus();
            bool const isSelectedRow = isRowSelected(aRowIterator.getRow());

            tools::Rectangle const aRect = aRowIterator.getRect().GetIntersection(aAllDataCellsArea);

            // give the renderer a chance to prepare the row
            pRenderer->PrepareRow(aRowIterator.getRow(), isControlFocused, isSelectedRow, rRenderContext, aRect, rStyle);

            // paint the row header
            if (m_pModel->hasRowHeaders())
            {
                const tools::Rectangle aCurrentRowHeader(aRowHeaderArea.GetIntersection(aRowIterator.getRect()));
                pRenderer->PaintRowHeader(rRenderContext, aCurrentRowHeader, rStyle);
            }

            if (!colCount)
                continue;

            // paint all cells in this row
            for (TableCellGeometry aCell(aRowIterator, m_nLeftColumn); aCell.isValid(); aCell.moveRight())
            {
                pRenderer->PaintCell(aCell.getColumn(), isSelectedRow, isControlFocused,
                                     rRenderContext, aCell.getRect(), rStyle);
            }
        }
    }

    void TableControl_Impl::hideCursor()
    {
        if ( ++m_nCursorHidden == 1 )
            impl_ni_doSwitchCursor( false );
    }


    void TableControl_Impl::showCursor()
    {
        DBG_ASSERT( m_nCursorHidden > 0, "TableControl_Impl::showCursor: cursor not hidden!" );
        if ( --m_nCursorHidden == 0 )
            impl_ni_doSwitchCursor( true );
    }


    bool TableControl_Impl::dispatchAction( TableControlAction _eAction )
    {
        bool bSuccess = false;
        bool selectionChanged = false;

        switch ( _eAction )
        {
        case TableControlAction::cursorDown:
        if ( m_pSelEngine->GetSelectionMode() == SelectionMode::Single )
        {
            //if other rows already selected, deselect them
            if(!m_aSelectedRows.empty())
            {
                invalidateSelectedRows();
                m_aSelectedRows.clear();
            }
            if ( m_nCurRow < m_nRowCount-1 )
            {
                ++m_nCurRow;
                m_aSelectedRows.push_back(m_nCurRow);
            }
            else
                m_aSelectedRows.push_back(m_nCurRow);
            invalidateRow( m_nCurRow );
            ensureVisible(m_nCurColumn,m_nCurRow);
            selectionChanged = true;
            bSuccess = true;
        }
        else
        {
            if ( m_nCurRow < m_nRowCount - 1 )
                bSuccess = goTo( m_nCurColumn, m_nCurRow + 1 );
        }
            break;

        case TableControlAction::cursorUp:
        if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
        {
            if(!m_aSelectedRows.empty())
            {
                invalidateSelectedRows();
                m_aSelectedRows.clear();
            }
            if(m_nCurRow>0)
            {
                --m_nCurRow;
                m_aSelectedRows.push_back(m_nCurRow);
                invalidateRow( m_nCurRow );
            }
            else
            {
                m_aSelectedRows.push_back(m_nCurRow);
                invalidateRow( m_nCurRow );
            }
            ensureVisible(m_nCurColumn,m_nCurRow);
            selectionChanged = true;
            bSuccess = true;
        }
        else
        {
            if ( m_nCurRow > 0 )
                bSuccess = goTo( m_nCurColumn, m_nCurRow - 1 );
        }
        break;
        case TableControlAction::cursorLeft:
            if ( m_nCurColumn > 0 )
                bSuccess = goTo( m_nCurColumn - 1, m_nCurRow );
            else
                if ( ( m_nCurColumn == 0) && ( m_nCurRow > 0 ) )
                    bSuccess = goTo( m_nColumnCount - 1, m_nCurRow - 1 );
            break;

        case TableControlAction::cursorRight:
            if ( m_nCurColumn < m_nColumnCount - 1 )
                bSuccess = goTo( m_nCurColumn + 1, m_nCurRow );
            else
                if ( ( m_nCurColumn == m_nColumnCount - 1 ) && ( m_nCurRow < m_nRowCount - 1 ) )
                    bSuccess = goTo( 0, m_nCurRow + 1 );
            break;

        case TableControlAction::cursorToLineStart:
            bSuccess = goTo( 0, m_nCurRow );
            break;

        case TableControlAction::cursorToLineEnd:
            bSuccess = goTo( m_nColumnCount - 1, m_nCurRow );
            break;

        case TableControlAction::cursorToFirstLine:
            bSuccess = goTo( m_nCurColumn, 0 );
            break;

        case TableControlAction::cursorToLastLine:
            bSuccess = goTo( m_nCurColumn, m_nRowCount - 1 );
            break;

        case TableControlAction::cursorPageUp:
        {
            RowPos nNewRow = ::std::max( RowPos(0), m_nCurRow - impl_getVisibleRows( false ) );
            bSuccess = goTo( m_nCurColumn, nNewRow );
        }
        break;

        case TableControlAction::cursorPageDown:
        {
            RowPos nNewRow = ::std::min( m_nRowCount - 1, m_nCurRow + impl_getVisibleRows( false ) );
            bSuccess = goTo( m_nCurColumn, nNewRow );
        }
        break;

        case TableControlAction::cursorTopLeft:
            bSuccess = goTo( 0, 0 );
            break;

        case TableControlAction::cursorBottomRight:
            bSuccess = goTo( m_nColumnCount - 1, m_nRowCount - 1 );
            break;

        case TableControlAction::cursorSelectRow:
        {
            if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
                return false;
            //pos is the position of the current row in the vector of selected rows, if current row is selected
            int pos = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
            //if current row is selected, it should be deselected, when ALT+SPACE are pressed
            if(pos>-1)
            {
                m_aSelectedRows.erase(m_aSelectedRows.begin()+pos);
                if(m_aSelectedRows.empty() && m_nAnchor != -1)
                    m_nAnchor = -1;
            }
            //else select the row->put it in the vector
            else
                m_aSelectedRows.push_back(m_nCurRow);
            invalidateRow( m_nCurRow );
            selectionChanged = true;
            bSuccess = true;
        }
            break;
        case TableControlAction::cursorSelectRowUp:
        {
            if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
                return false;
            else if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
            {
                //if there are other selected rows, deselect them
                return false;
            }
            else
            {
                //there are other selected rows
                if(!m_aSelectedRows.empty())
                {
                    //the anchor wasn't set -> a region is not selected, that's why clear all selection
                    //and select the current row
                    if(m_nAnchor==-1)
                    {
                        invalidateSelectedRows();
                        m_aSelectedRows.clear();
                        m_aSelectedRows.push_back(m_nCurRow);
                        invalidateRow( m_nCurRow );
                    }
                    else
                    {
                        //a region is already selected, prevRow is last selected row and the row above - nextRow - should be selected
                        int prevRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
                        int nextRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow-1);
                        if(prevRow>-1)
                        {
                             //if m_nCurRow isn't the upper one, can move up, otherwise not
                            if(m_nCurRow>0)
                                 m_nCurRow--;
                            else
                                 return true;
                             //if nextRow already selected, deselect it, otherwise select it
                            if(nextRow>-1 && m_aSelectedRows[nextRow] == m_nCurRow)
                            {
                                 m_aSelectedRows.erase(m_aSelectedRows.begin()+prevRow);
                                 invalidateRow( m_nCurRow + 1 );
                            }
                            else
                            {
                                 m_aSelectedRows.push_back(m_nCurRow);
                                 invalidateRow( m_nCurRow );
                            }
                        }
                        else
                        {
                            if(m_nCurRow>0)
                            {
                                m_aSelectedRows.push_back(m_nCurRow);
                                m_nCurRow--;
                                m_aSelectedRows.push_back(m_nCurRow);
                                invalidateSelectedRegion( m_nCurRow+1, m_nCurRow );
                            }
                        }
                    }
                }
                else
                {
                    //if nothing is selected and the current row isn't the upper one
                    //select the current and one row above
                    //otherwise select only the upper row
                    if(m_nCurRow>0)
                    {
                        m_aSelectedRows.push_back(m_nCurRow);
                        m_nCurRow--;
                        m_aSelectedRows.push_back(m_nCurRow);
                        invalidateSelectedRegion( m_nCurRow+1, m_nCurRow );
                    }
                    else
                    {
                        m_aSelectedRows.push_back(m_nCurRow);
                        invalidateRow( m_nCurRow );
                    }
                }
                m_pSelEngine->SetAnchor(true);
                m_nAnchor = m_nCurRow;
                ensureVisible(m_nCurColumn, m_nCurRow);
                selectionChanged = true;
                bSuccess = true;
            }
        }
        break;
        case TableControlAction::cursorSelectRowDown:
        {
            if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
                bSuccess = false;
            else if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
            {
                bSuccess = false;
            }
            else
            {
                if(!m_aSelectedRows.empty())
                {
                    //the anchor wasn't set -> a region is not selected, that's why clear all selection
                    //and select the current row
                    if(m_nAnchor==-1)
                    {
                        invalidateSelectedRows();
                        m_aSelectedRows.clear();
                        m_aSelectedRows.push_back(m_nCurRow);
                        invalidateRow( m_nCurRow );
                    }
                    else
                    {
                        //a region is already selected, prevRow is last selected row and the row beneath - nextRow - should be selected
                        int prevRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
                        int nextRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow+1);
                        if(prevRow>-1)
                        {
                             //if m_nCurRow isn't the last one, can move down, otherwise not
                             if(m_nCurRow<m_nRowCount-1)
                                 m_nCurRow++;
                             else
                                return true;
                             //if next row already selected, deselect it, otherwise select it
                             if(nextRow>-1 && m_aSelectedRows[nextRow] == m_nCurRow)
                             {
                                 m_aSelectedRows.erase(m_aSelectedRows.begin()+prevRow);
                                 invalidateRow( m_nCurRow - 1 );
                             }
                             else
                             {
                                 m_aSelectedRows.push_back(m_nCurRow);
                                 invalidateRow( m_nCurRow );
                             }
                        }
                        else
                        {
                            if(m_nCurRow<m_nRowCount-1)
                            {
                                m_aSelectedRows.push_back(m_nCurRow);
                                m_nCurRow++;
                                m_aSelectedRows.push_back(m_nCurRow);
                                invalidateSelectedRegion( m_nCurRow-1, m_nCurRow );
                            }
                        }
                    }
                }
                else
                {
                    //there wasn't any selection, select current and row beneath, otherwise only row beneath
                    if(m_nCurRow<m_nRowCount-1)
                    {
                        m_aSelectedRows.push_back(m_nCurRow);
                        m_nCurRow++;
                        m_aSelectedRows.push_back(m_nCurRow);
                        invalidateSelectedRegion( m_nCurRow-1, m_nCurRow );
                    }
                    else
                    {
                        m_aSelectedRows.push_back(m_nCurRow);
                        invalidateRow( m_nCurRow );
                    }
                }
                m_pSelEngine->SetAnchor(true);
                m_nAnchor = m_nCurRow;
                ensureVisible(m_nCurColumn, m_nCurRow);
                selectionChanged = true;
                bSuccess = true;
            }
        }
        break;

        case TableControlAction::cursorSelectRowAreaTop:
        {
            if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
                bSuccess = false;
            else if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
                bSuccess = false;
            else
            {
                //select the region between the current and the upper row
                RowPos iter = m_nCurRow;
                invalidateSelectedRegion( m_nCurRow, 0 );
                //put the rows in vector
                while(iter>=0)
                {
                    if ( !isRowSelected( iter ) )
                        m_aSelectedRows.push_back(iter);
                    --iter;
                }
                m_nCurRow = 0;
                m_nAnchor = m_nCurRow;
                m_pSelEngine->SetAnchor(true);
                ensureVisible(m_nCurColumn, 0);
                selectionChanged = true;
                bSuccess = true;
            }
        }
        break;

        case TableControlAction::cursorSelectRowAreaBottom:
        {
            if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
                return false;
            else if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
                return false;
            //select the region between the current and the last row
            RowPos iter = m_nCurRow;
            invalidateSelectedRegion( m_nCurRow, m_nRowCount-1 );
            //put the rows in the vector
            while(iter<=m_nRowCount)
            {
                if ( !isRowSelected( iter ) )
                    m_aSelectedRows.push_back(iter);
                ++iter;
            }
            m_nCurRow = m_nRowCount-1;
            m_nAnchor = m_nCurRow;
            m_pSelEngine->SetAnchor(true);
            ensureVisible(m_nCurColumn, m_nRowCount-1);
            selectionChanged = true;
            bSuccess = true;
        }
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=91 H=89 G=89

¤ Dauer der Verarbeitung: 0.28 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge