diff options
Diffstat (limited to 'sc/source/ui/view/olinewin.cxx')
-rw-r--r-- | sc/source/ui/view/olinewin.cxx | 1040 |
1 files changed, 1040 insertions, 0 deletions
diff --git a/sc/source/ui/view/olinewin.cxx b/sc/source/ui/view/olinewin.cxx new file mode 100644 index 0000000000..1cc55a9f3f --- /dev/null +++ b/sc/source/ui/view/olinewin.cxx @@ -0,0 +1,1040 @@ +/* -*- 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 <sal/config.h> + +#include <vcl/image.hxx> +#include <vcl/taskpanelist.hxx> +#include <vcl/settings.hxx> +#include <vcl/syswin.hxx> +#include <osl/diagnose.h> + +#include <olinewin.hxx> +#include <olinetab.hxx> +#include <document.hxx> +#include <dbfunc.hxx> +#include <bitmaps.hlst> + +const tools::Long SC_OL_BITMAPSIZE = 12; +const tools::Long SC_OL_POSOFFSET = 2; + +const size_t SC_OL_NOLEVEL = static_cast< size_t >( -1 ); +const size_t SC_OL_HEADERENTRY = static_cast< size_t >( -1 ); + +ScOutlineWindow::ScOutlineWindow( vcl::Window* pParent, ScOutlineMode eMode, ScViewData* pViewData, ScSplitPos eWhich ) : + Window( pParent ), + mrViewData( *pViewData ), + meWhich( eWhich ), + mbHoriz( eMode == SC_OUTLINE_HOR ), + mbMirrorEntries( false ), // updated in SetHeaderSize + mbMirrorLevels( false ), // updated in SetHeaderSize + maLineColor( COL_BLACK ), + mnHeaderSize( 0 ), + mnHeaderPos( 0 ), + mnMainFirstPos( 0 ), + mnMainLastPos( 0 ), + mbMTActive( false ), + mbMTPressed( false ), + mnFocusLevel( 0 ), + mnFocusEntry( SC_OL_HEADERENTRY ), + mbDontDrawFocus( false ) +{ + EnableRTL( false ); // mirroring is done manually + + InitSettings(); + maFocusRect.SetEmpty(); + SetHeaderSize( 0 ); + + // insert the window into task pane list for "F6 cycling" + if( SystemWindow* pSysWin = GetSystemWindow() ) + if( TaskPaneList* pTaskPaneList = pSysWin->GetTaskPaneList() ) + pTaskPaneList->AddWindow( this ); +} + +ScOutlineWindow::~ScOutlineWindow() +{ + disposeOnce(); +} + +void ScOutlineWindow::dispose() +{ + // remove the window from task pane list + if( SystemWindow* pSysWin = GetSystemWindow() ) + if( TaskPaneList* pTaskPaneList = pSysWin->GetTaskPaneList() ) + pTaskPaneList->RemoveWindow( this ); + vcl::Window::dispose(); +} + +void ScOutlineWindow::SetHeaderSize( tools::Long nNewSize ) +{ + bool bLayoutRTL = GetDoc().IsLayoutRTL( GetTab() ); + mbMirrorEntries = bLayoutRTL && mbHoriz; + mbMirrorLevels = bLayoutRTL && !mbHoriz; + + bool bNew = (nNewSize != mnHeaderSize); + mnHeaderSize = nNewSize; + mnHeaderPos = mbMirrorEntries ? (GetOutputSizeEntry() - mnHeaderSize) : 0; + mnMainFirstPos = mbMirrorEntries ? 0 : mnHeaderSize; + mnMainLastPos = GetOutputSizeEntry() - (mbMirrorEntries ? mnHeaderSize : 0) - 1; + if ( bNew ) + Invalidate(); +} + +tools::Long ScOutlineWindow::GetDepthSize() const +{ + tools::Long nSize = GetLevelCount() * SC_OL_BITMAPSIZE; + if ( nSize > 0 ) + nSize += 2 * SC_OL_POSOFFSET + 1; + return nSize; +} + +void ScOutlineWindow::ScrollPixel( tools::Long nDiff ) +{ + HideFocus(); + mbDontDrawFocus = true; + + tools::Long nStart = mnMainFirstPos; + tools::Long nEnd = mnMainLastPos; + + tools::Long nInvStart, nInvEnd; + if (nDiff < 0) + { + nStart -= nDiff; + nInvStart = nEnd + nDiff; + nInvEnd = nEnd; + } + else + { + nEnd -= nDiff; + nInvStart = nStart; + nInvEnd = nStart + nDiff; + } + + ScrollRel( nDiff, nStart, nEnd ); + Invalidate( GetRectangle( 0, nInvStart, GetOutputSizeLevel() - 1, nInvEnd ) ); + + // if focus becomes invisible, move it to next visible button + ImplMoveFocusToVisible( nDiff < 0 ); + + mbDontDrawFocus = false; + ShowFocus(); +} + +void ScOutlineWindow::ScrollRel( tools::Long nEntryDiff, tools::Long nEntryStart, tools::Long nEntryEnd ) +{ + tools::Rectangle aRect( GetRectangle( 0, nEntryStart, GetOutputSizeLevel() - 1, nEntryEnd ) ); + if ( mbHoriz ) + Scroll( nEntryDiff, 0, aRect ); + else + Scroll( 0, nEntryDiff, aRect ); +} + +// internal ------------------------------------------------------------------- + +void ScOutlineWindow::InitSettings() +{ + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + SetBackground( rStyleSettings.GetFaceColor() ); + maLineColor = rStyleSettings.GetButtonTextColor(); + Invalidate(); +} + +const ScOutlineArray* ScOutlineWindow::GetOutlineArray() const +{ + const ScOutlineTable* pTable = GetDoc().GetOutlineTable( GetTab() ); + if ( !pTable ) return nullptr; + return mbHoriz ? &pTable->GetColArray() : &pTable->GetRowArray(); +} + +const ScOutlineEntry* ScOutlineWindow::GetOutlineEntry( size_t nLevel, size_t nEntry ) const +{ + const ScOutlineArray* pArray = GetOutlineArray(); + return pArray ? pArray->GetEntry( sal::static_int_cast<sal_uInt16>(nLevel), sal::static_int_cast<sal_uInt16>(nEntry) ) : nullptr; +} + +bool ScOutlineWindow::IsHidden( SCCOLROW nColRowIndex ) const +{ + return mbHoriz ? + GetDoc().ColHidden(static_cast<SCCOL>(nColRowIndex), GetTab()) : + GetDoc().RowHidden(static_cast<SCROW>(nColRowIndex), GetTab()); +} + +bool ScOutlineWindow::IsFiltered( SCCOLROW nColRowIndex ) const +{ + // columns cannot be filtered + return !mbHoriz && GetDoc().RowFiltered( static_cast<SCROW>(nColRowIndex), GetTab() ); +} + +bool ScOutlineWindow::IsFirstVisible( SCCOLROW nColRowIndex ) const +{ + bool bAllHidden = true; + for ( SCCOLROW nPos = 0; (nPos < nColRowIndex) && bAllHidden; ++nPos ) + bAllHidden = IsHidden( nPos ); + return bAllHidden; +} + +void ScOutlineWindow::GetVisibleRange( SCCOLROW& rnColRowStart, SCCOLROW& rnColRowEnd ) const +{ + if ( mbHoriz ) + { + rnColRowStart = mrViewData.GetPosX( WhichH( meWhich ) ); + rnColRowEnd = rnColRowStart + mrViewData.VisibleCellsX( WhichH( meWhich ) ); + } + else + { + rnColRowStart = mrViewData.GetPosY( WhichV( meWhich ) ); + rnColRowEnd = rnColRowStart + mrViewData.VisibleCellsY( WhichV( meWhich ) ); + } + + // include collapsed columns/rows in front of visible range + while ( (rnColRowStart > 0) && IsHidden( rnColRowStart - 1 ) ) + --rnColRowStart; +} + +Point ScOutlineWindow::GetPoint( tools::Long nLevelPos, tools::Long nEntryPos ) const +{ + return mbHoriz ? Point( nEntryPos, nLevelPos ) : Point( nLevelPos, nEntryPos ); +} + +tools::Rectangle ScOutlineWindow::GetRectangle( + tools::Long nLevelStart, tools::Long nEntryStart, tools::Long nLevelEnd, tools::Long nEntryEnd ) const +{ + return tools::Rectangle( GetPoint( nLevelStart, nEntryStart ), GetPoint( nLevelEnd, nEntryEnd ) ); +} + +tools::Long ScOutlineWindow::GetOutputSizeLevel() const +{ + Size aSize( GetOutputSizePixel() ); + return mbHoriz ? aSize.Height() : aSize.Width(); +} + +tools::Long ScOutlineWindow::GetOutputSizeEntry() const +{ + Size aSize( GetOutputSizePixel() ); + return mbHoriz ? aSize.Width() : aSize.Height(); +} + +size_t ScOutlineWindow::GetLevelCount() const +{ + const ScOutlineArray* pArray = GetOutlineArray(); + size_t nLevelCount = pArray ? pArray->GetDepth() : 0; + return nLevelCount ? (nLevelCount + 1) : 0; +} + +tools::Long ScOutlineWindow::GetLevelPos( size_t nLevel ) const +{ + // #i51970# must always return the *left* edge of the area used by a level + tools::Long nPos = static_cast< tools::Long >( SC_OL_POSOFFSET + nLevel * SC_OL_BITMAPSIZE ); + return mbMirrorLevels ? (GetOutputSizeLevel() - nPos - SC_OL_BITMAPSIZE) : nPos; +} + +size_t ScOutlineWindow::GetLevelFromPos( tools::Long nLevelPos ) const +{ + if( mbMirrorLevels ) nLevelPos = GetOutputSizeLevel() - nLevelPos - 1; + tools::Long nStart = SC_OL_POSOFFSET; + if ( nLevelPos < nStart ) return SC_OL_NOLEVEL; + size_t nLevel = static_cast< size_t >( (nLevelPos - nStart) / SC_OL_BITMAPSIZE ); + return (nLevel < GetLevelCount()) ? nLevel : SC_OL_NOLEVEL; +} + +tools::Long ScOutlineWindow::GetColRowPos( SCCOLROW nColRowIndex ) const +{ + tools::Long nDocPos = mbHoriz ? + mrViewData.GetScrPos( static_cast<SCCOL>(nColRowIndex), 0, meWhich, true ).X() : + mrViewData.GetScrPos( 0, static_cast<SCROW>(nColRowIndex), meWhich, true ).Y(); + return mnMainFirstPos + nDocPos; +} + +tools::Long ScOutlineWindow::GetHeaderEntryPos() const +{ + return mnHeaderPos + (mnHeaderSize - SC_OL_BITMAPSIZE) / 2; +} + +bool ScOutlineWindow::GetEntryPos( + size_t nLevel, size_t nEntry, + tools::Long& rnStartPos, tools::Long& rnEndPos, tools::Long& rnImagePos ) const +{ + const ScOutlineEntry* pEntry = GetOutlineEntry( nLevel, nEntry ); + if ( !pEntry || !pEntry->IsVisible() ) + return false; + + SCCOLROW nStart = pEntry->GetStart(); + SCCOLROW nEnd = pEntry->GetEnd(); + + tools::Long nEntriesSign = mbMirrorEntries ? -1 : 1; + + // --- common calculation --- + + rnStartPos = GetColRowPos( nStart ); + rnEndPos = GetColRowPos( nEnd + 1 ); + + bool bHidden = IsHidden( nStart ); + rnImagePos = bHidden ? + (rnStartPos - ( SC_OL_BITMAPSIZE / 2 ) * nEntriesSign) : + rnStartPos + nEntriesSign; + tools::Long nCenter = (rnStartPos + rnEndPos - SC_OL_BITMAPSIZE * nEntriesSign + + ( mbMirrorEntries ? 1 : 0 )) / 2; + rnImagePos = mbMirrorEntries ? std::max( rnImagePos, nCenter ) : std::min( rnImagePos, nCenter ); + + // --- refinements --- + + // do not cut leftmost/topmost image + if ( bHidden && IsFirstVisible( nStart ) ) + rnImagePos = rnStartPos; + + // do not cover previous collapsed image + bool bDoNoCover = !bHidden && nEntry; + const ScOutlineEntry* pPrevEntry = bDoNoCover ? GetOutlineEntry(nLevel, nEntry - 1) : nullptr; + if (pPrevEntry) + { + SCCOLROW nPrevEnd = pPrevEntry->GetEnd(); + if ( (nPrevEnd + 1 == nStart) && IsHidden( nPrevEnd ) ) + { + if ( IsFirstVisible( pPrevEntry->GetStart() ) ) + rnStartPos += SC_OL_BITMAPSIZE * nEntriesSign; + else + rnStartPos += ( SC_OL_BITMAPSIZE / 2 ) * nEntriesSign; + rnImagePos = rnStartPos; + } + } + + // restrict rnStartPos...rnEndPos to valid area + rnStartPos = std::max( rnStartPos, mnMainFirstPos ); + rnEndPos = std::max( rnEndPos, mnMainFirstPos ); + + if ( mbMirrorEntries ) + rnImagePos -= SC_OL_BITMAPSIZE - 1; // start pos aligns with right edge of bitmap + + // --- all rows filtered? --- + + bool bVisible = true; + if ( !mbHoriz ) + { + bVisible = false; + for ( SCCOLROW nRow = nStart; (nRow <= nEnd) && !bVisible; ++nRow ) + bVisible = !IsFiltered( nRow ); + } + return bVisible; +} + +bool ScOutlineWindow::GetImagePos( size_t nLevel, size_t nEntry, Point& rPos ) const +{ + bool bRet = nLevel < GetLevelCount(); + if ( bRet ) + { + tools::Long nLevelPos = GetLevelPos( nLevel ); + if ( nEntry == SC_OL_HEADERENTRY ) + rPos = GetPoint( nLevelPos, GetHeaderEntryPos() ); + else + { + tools::Long nStartPos, nEndPos, nImagePos; + bRet = GetEntryPos( nLevel, nEntry, nStartPos, nEndPos, nImagePos ); + rPos = GetPoint( nLevelPos, nImagePos ); + } + } + return bRet; +} + +bool ScOutlineWindow::IsButtonVisible( size_t nLevel, size_t nEntry ) const +{ + bool bRet = false; + if ( nEntry == SC_OL_HEADERENTRY ) + bRet = (mnHeaderSize > 0) && (nLevel < GetLevelCount()); + else + { + const ScOutlineEntry* pEntry = GetOutlineEntry( nLevel, nEntry ); + if ( pEntry && pEntry->IsVisible() ) + { + SCCOLROW nStart, nEnd; + GetVisibleRange( nStart, nEnd ); + bRet = (nStart <= pEntry->GetStart()) && (pEntry->GetStart() <= nEnd); + } + } + return bRet; +} + +bool ScOutlineWindow::ItemHit( const Point& rPos, size_t& rnLevel, size_t& rnEntry, bool& rbButton ) const +{ + const ScOutlineArray* pArray = GetOutlineArray(); + if ( !pArray ) return false; + + SCCOLROW nStartIndex, nEndIndex; + GetVisibleRange( nStartIndex, nEndIndex ); + + size_t nLevel = GetLevelFromPos( mbHoriz ? rPos.Y() : rPos.X() ); + if ( nLevel == SC_OL_NOLEVEL ) + return false; + + tools::Long nEntryMousePos = mbHoriz ? rPos.X() : rPos.Y(); + + // --- level buttons --- + + if ( mnHeaderSize > 0 ) + { + tools::Long nImagePos = GetHeaderEntryPos(); + if ( (nImagePos <= nEntryMousePos) && (nEntryMousePos < nImagePos + SC_OL_BITMAPSIZE) ) + { + rnLevel = nLevel; + rnEntry = SC_OL_HEADERENTRY; + rbButton = true; + return true; + } + } + + // --- expand/collapse buttons and expanded lines --- + + // search outline entries backwards + size_t nEntry = pArray->GetCount( sal::static_int_cast<sal_uInt16>(nLevel) ); + while ( nEntry ) + { + --nEntry; + + const ScOutlineEntry* pEntry = pArray->GetEntry( sal::static_int_cast<sal_uInt16>(nLevel), + sal::static_int_cast<sal_uInt16>(nEntry) ); + SCCOLROW nStart = pEntry->GetStart(); + SCCOLROW nEnd = pEntry->GetEnd(); + + if ( (nEnd >= nStartIndex) && (nStart <= nEndIndex) ) + { + tools::Long nStartPos, nEndPos, nImagePos; + if ( GetEntryPos( nLevel, nEntry, nStartPos, nEndPos, nImagePos ) ) + { + rnLevel = nLevel; + rnEntry = nEntry; + + // button? + if ( (nStart >= nStartIndex) && (nImagePos <= nEntryMousePos) && (nEntryMousePos < nImagePos + SC_OL_BITMAPSIZE) ) + { + rbButton = true; + return true; + } + + // line? + if ( mbMirrorEntries ) + ::std::swap( nStartPos, nEndPos ); // in RTL mode, nStartPos is the larger value + if ( (nStartPos <= nEntryMousePos) && (nEntryMousePos <= nEndPos) ) + { + rbButton = false; + return true; + } + } + } + } + + return false; +} + +bool ScOutlineWindow::ButtonHit( const Point& rPos, size_t& rnLevel, size_t& rnEntry ) const +{ + bool bButton; + bool bRet = ItemHit( rPos, rnLevel, rnEntry, bButton ); + return bRet && bButton; +} + +bool ScOutlineWindow::LineHit( const Point& rPos, size_t& rnLevel, size_t& rnEntry ) const +{ + bool bButton; + bool bRet = ItemHit( rPos, rnLevel, rnEntry, bButton ); + return bRet && !bButton; +} + +void ScOutlineWindow::DoFunction( size_t nLevel, size_t nEntry ) const +{ + ScDBFunc& rFunc = *mrViewData.GetView(); + if ( nEntry == SC_OL_HEADERENTRY ) + rFunc.SelectLevel( mbHoriz, sal::static_int_cast<sal_uInt16>(nLevel) ); + else + { + const ScOutlineEntry* pEntry = GetOutlineEntry( nLevel, nEntry ); + if ( pEntry ) + { + if ( pEntry->IsHidden() ) + rFunc.ShowOutline( mbHoriz, sal::static_int_cast<sal_uInt16>(nLevel), sal::static_int_cast<sal_uInt16>(nEntry) ); + else + rFunc.HideOutline( mbHoriz, sal::static_int_cast<sal_uInt16>(nLevel), sal::static_int_cast<sal_uInt16>(nEntry) ); + } + } +} + +void ScOutlineWindow::DoExpand( size_t nLevel, size_t nEntry ) const +{ + const ScOutlineEntry* pEntry = GetOutlineEntry( nLevel, nEntry ); + if ( pEntry && pEntry->IsHidden() ) + DoFunction( nLevel, nEntry ); +} + +void ScOutlineWindow::DoCollapse( size_t nLevel, size_t nEntry ) const +{ + const ScOutlineEntry* pEntry = GetOutlineEntry( nLevel, nEntry ); + if ( pEntry && !pEntry->IsHidden() ) + DoFunction( nLevel, nEntry ); +} + +void ScOutlineWindow::Resize() +{ + Window::Resize(); + SetHeaderSize( mnHeaderSize ); // recalculates header/group positions + if ( !IsFocusButtonVisible() ) + { + HideFocus(); + ShowFocus(); // calculates valid position + } +} + +void ScOutlineWindow::DataChanged( const DataChangedEvent& rDCEvt ) +{ + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + { + InitSettings(); + Invalidate(); + } + Window::DataChanged( rDCEvt ); +} + +// drawing -------------------------------------------------------------------- + +void ScOutlineWindow::SetEntryAreaClipRegion() +{ + GetOutDev()->SetClipRegion( vcl::Region(tools::Rectangle( + GetPoint( 0, mnMainFirstPos ), + GetPoint( GetOutputSizeLevel() - 1, mnMainLastPos )))); +} + +void ScOutlineWindow::DrawLineRel( + tools::Long nLevelStart, tools::Long nEntryStart, tools::Long nLevelEnd, tools::Long nEntryEnd ) +{ + GetOutDev()->DrawLine( GetPoint( nLevelStart, nEntryStart ), GetPoint( nLevelEnd, nEntryEnd ) ); +} + +void ScOutlineWindow::DrawRectRel( + tools::Long nLevelStart, tools::Long nEntryStart, tools::Long nLevelEnd, tools::Long nEntryEnd ) +{ + GetOutDev()->DrawRect( GetRectangle( nLevelStart, nEntryStart, nLevelEnd, nEntryEnd ) ); +} + +namespace +{ + Image GetImage(const OUString& rId) + { + return Image(StockImage::Yes, rId); + } +} + +void ScOutlineWindow::DrawImageRel(tools::Long nLevelPos, tools::Long nEntryPos, const OUString& rId) +{ + const Image& rImage = GetImage(rId); + GetOutDev()->SetLineColor(); + GetOutDev()->SetFillColor( GetBackground().GetColor() ); + Point aPos( GetPoint( nLevelPos, nEntryPos ) ); + GetOutDev()->DrawRect( tools::Rectangle( aPos, rImage.GetSizePixel() ) ); + GetOutDev()->DrawImage( aPos, rImage ); +} + +void ScOutlineWindow::DrawBorderRel( size_t nLevel, size_t nEntry, bool bPressed ) +{ + Point aPos; + if ( GetImagePos( nLevel, nEntry, aPos ) ) + { + OUString sId = bPressed ? RID_BMP_PRESSED : RID_BMP_NOTPRESSED; + bool bClip = (nEntry != SC_OL_HEADERENTRY); + if ( bClip ) + SetEntryAreaClipRegion(); + GetOutDev()->DrawImage(aPos, GetImage(sId)); + if ( bClip ) + GetOutDev()->SetClipRegion(); + } + mbMTPressed = bPressed; +} + +void ScOutlineWindow::ShowFocus() +{ + if ( !HasFocus() ) + return; + + // first move to a visible position + ImplMoveFocusToVisible( true ); + + if ( !IsFocusButtonVisible() ) + return; + + Point aPos; + if ( GetImagePos( mnFocusLevel, mnFocusEntry, aPos ) ) + { + aPos += Point( 1, 1 ); + maFocusRect = tools::Rectangle( aPos, Size( SC_OL_BITMAPSIZE - 2, SC_OL_BITMAPSIZE - 2 ) ); + bool bClip = (mnFocusEntry != SC_OL_HEADERENTRY); + if ( bClip ) + SetEntryAreaClipRegion(); + InvertTracking( maFocusRect, ShowTrackFlags::Small | ShowTrackFlags::TrackWindow ); + if ( bClip ) + GetOutDev()->SetClipRegion(); + } +} + +void ScOutlineWindow::HideFocus() +{ + if ( !maFocusRect.IsEmpty() ) + { + bool bClip = (mnFocusEntry != SC_OL_HEADERENTRY); + if ( bClip ) + SetEntryAreaClipRegion(); + InvertTracking( maFocusRect, ShowTrackFlags::Small | ShowTrackFlags::TrackWindow ); + if ( bClip ) + GetOutDev()->SetClipRegion(); + maFocusRect.SetEmpty(); + } +} + +constexpr OUString aLevelBmps[]= +{ + RID_BMP_LEVEL1, + RID_BMP_LEVEL2, + RID_BMP_LEVEL3, + RID_BMP_LEVEL4, + RID_BMP_LEVEL5, + RID_BMP_LEVEL6, + RID_BMP_LEVEL7, + RID_BMP_LEVEL8 +}; + +void ScOutlineWindow::Paint( vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& /* rRect */ ) +{ + tools::Long nEntriesSign = mbMirrorEntries ? -1 : 1; + tools::Long nLevelsSign = mbMirrorLevels ? -1 : 1; + + Size aSize = GetOutputSizePixel(); + tools::Long nLevelEnd = (mbHoriz ? aSize.Height() : aSize.Width()) - 1; + tools::Long nEntryEnd = (mbHoriz ? aSize.Width() : aSize.Height()) - 1; + + GetOutDev()->SetLineColor( maLineColor ); + tools::Long nBorderPos = mbMirrorLevels ? 0 : nLevelEnd; + DrawLineRel( nBorderPos, 0, nBorderPos, nEntryEnd ); + + const ScOutlineArray* pArray = GetOutlineArray(); + if ( !pArray ) return; + + size_t nLevelCount = GetLevelCount(); + + // --- draw header images --- + + if ( mnHeaderSize > 0 ) + { + tools::Long nEntryPos = GetHeaderEntryPos(); + for ( size_t nLevel = 0; nLevel < nLevelCount; ++nLevel ) + DrawImageRel(GetLevelPos(nLevel), nEntryPos, aLevelBmps[nLevel]); + + GetOutDev()->SetLineColor( maLineColor ); + tools::Long nLinePos = mnHeaderPos + (mbMirrorEntries ? 0 : (mnHeaderSize - 1)); + DrawLineRel( 0, nLinePos, nLevelEnd, nLinePos ); + } + + // --- draw lines & collapse/expand images --- + + SetEntryAreaClipRegion(); + + SCCOLROW nStartIndex, nEndIndex; + GetVisibleRange( nStartIndex, nEndIndex ); + + for ( size_t nLevel = 0; nLevel + 1 < nLevelCount; ++nLevel ) + { + tools::Long nLevelPos = GetLevelPos( nLevel ); + tools::Long nEntryPos1 = 0, nEntryPos2 = 0, nImagePos = 0; + + size_t nEntryCount = pArray->GetCount( sal::static_int_cast<sal_uInt16>(nLevel) ); + size_t nEntry; + + // first draw all lines in the current level + GetOutDev()->SetLineColor(); + GetOutDev()->SetFillColor( maLineColor ); + for ( nEntry = 0; nEntry < nEntryCount; ++nEntry ) + { + const ScOutlineEntry* pEntry = pArray->GetEntry( sal::static_int_cast<sal_uInt16>(nLevel), + sal::static_int_cast<sal_uInt16>(nEntry) ); + SCCOLROW nStart = pEntry->GetStart(); + SCCOLROW nEnd = pEntry->GetEnd(); + + // visible range? + bool bDraw = (nEnd >= nStartIndex) && (nStart <= nEndIndex); + // find output coordinates + if ( bDraw ) + bDraw = GetEntryPos( nLevel, nEntry, nEntryPos1, nEntryPos2, nImagePos ); + // draw, if not collapsed + if ( bDraw && !pEntry->IsHidden() ) + { + if ( nStart >= nStartIndex ) + nEntryPos1 += nEntriesSign; + nEntryPos2 -= 2 * nEntriesSign; + tools::Long nLinePos = nLevelPos; + if ( mbMirrorLevels ) + nLinePos += SC_OL_BITMAPSIZE - 1; // align with right edge of bitmap + DrawRectRel( nLinePos, nEntryPos1, nLinePos + nLevelsSign, nEntryPos2 ); + + if ( nEnd <= nEndIndex ) + DrawRectRel( nLinePos, nEntryPos2 - nEntriesSign, + nLinePos + ( SC_OL_BITMAPSIZE / 3 ) * nLevelsSign, nEntryPos2 ); + } + } + + // draw all images in the level from last to first + nEntry = nEntryCount; + while ( nEntry ) + { + --nEntry; + + const ScOutlineEntry* pEntry = pArray->GetEntry( sal::static_int_cast<sal_uInt16>(nLevel), + sal::static_int_cast<sal_uInt16>(nEntry) ); + SCCOLROW nStart = pEntry->GetStart(); + + // visible range? + bool bDraw = (nStartIndex <= nStart) && (nStart <= nEndIndex + 1); + // find output coordinates + if ( bDraw ) + bDraw = GetEntryPos( nLevel, nEntry, nEntryPos1, nEntryPos2, nImagePos ); + // draw, if not hidden by higher levels + if ( bDraw ) + { + OUString sImageId = pEntry->IsHidden() ? RID_BMP_PLUS : RID_BMP_MINUS; + DrawImageRel(nLevelPos, nImagePos, sImageId); + } + } + } + + GetOutDev()->SetClipRegion(); + + if ( !mbDontDrawFocus ) + ShowFocus(); +} + +// focus ---------------------------------------------------------------------- + +/** Increments or decrements a value and wraps at the specified limits. + @return true = value wrapped. */ +static bool lcl_RotateValue( size_t& rnValue, size_t nMin, size_t nMax, bool bForward ) +{ + OSL_ENSURE( nMin <= nMax, "lcl_RotateValue - invalid range" ); + OSL_ENSURE( nMax < static_cast< size_t >( -1 ), "lcl_RotateValue - range overflow" ); + bool bWrap = false; + if ( bForward ) + { + if ( rnValue < nMax ) + ++rnValue; + else + { + rnValue = nMin; + bWrap = true; + } + } + else + { + if ( rnValue > nMin ) + --rnValue; + else + { + rnValue = nMax; + bWrap = true; + } + } + return bWrap; +} + +bool ScOutlineWindow::IsFocusButtonVisible() const +{ + return IsButtonVisible( mnFocusLevel, mnFocusEntry ); +} + +bool ScOutlineWindow::ImplMoveFocusByEntry( bool bForward, bool bFindVisible ) +{ + const ScOutlineArray* pArray = GetOutlineArray(); + if ( !pArray ) + return false; + + bool bWrapped = false; + size_t nEntryCount = pArray->GetCount( sal::static_int_cast<sal_uInt16>(mnFocusLevel) ); + // #i29530# entry count may be decreased after changing active sheet + if( mnFocusEntry >= nEntryCount ) + mnFocusEntry = SC_OL_HEADERENTRY; + size_t nOldEntry = mnFocusEntry; + + do + { + if ( mnFocusEntry == SC_OL_HEADERENTRY ) + { + // move from header to first or last entry + if ( nEntryCount > 0 ) + mnFocusEntry = bForward ? 0 : (nEntryCount - 1); + /* wrapped, if forward from right header to first entry, + or if backward from left header to last entry */ + // Header and entries are now always in consistent order, + // so there's no need to check for mirroring here. + if ( !nEntryCount || !bForward ) + bWrapped = true; + } + else if ( lcl_RotateValue( mnFocusEntry, 0, nEntryCount - 1, bForward ) ) + { + // lcl_RotateValue returns true -> wrapped the entry range -> move to header + mnFocusEntry = SC_OL_HEADERENTRY; + /* wrapped, if forward from last entry to left header, + or if backward from first entry to right header */ + if ( bForward ) + bWrapped = true; + } + } + while ( bFindVisible && !IsFocusButtonVisible() && (nOldEntry != mnFocusEntry) ); + + return bWrapped; +} + +bool ScOutlineWindow::ImplMoveFocusByLevel( bool bForward ) +{ + const ScOutlineArray* pArray = GetOutlineArray(); + if ( !pArray ) + return false; + + bool bWrapped = false; + size_t nLevelCount = GetLevelCount(); + + if ( mnFocusEntry == SC_OL_HEADERENTRY ) + { + if ( nLevelCount > 0 ) + bWrapped = lcl_RotateValue( mnFocusLevel, 0, nLevelCount - 1, bForward ); + } + else + { + const ScOutlineEntry* pEntry = pArray->GetEntry( + mnFocusLevel, mnFocusEntry); + + if ( pEntry ) + { + SCCOLROW nStart = pEntry->GetStart(); + SCCOLROW nEnd = pEntry->GetEnd(); + size_t nNewLevel = mnFocusLevel; + size_t nNewEntry = 0; + + bool bFound = false; + if ( bForward && (mnFocusLevel + 2 < nLevelCount) ) + { + // next level -> find first child entry + nNewLevel = mnFocusLevel + 1; + bFound = pArray->GetEntryIndexInRange(nNewLevel, nStart, nEnd, nNewEntry); + } + else if ( !bForward && (mnFocusLevel > 0) ) + { + // previous level -> find parent entry + nNewLevel = mnFocusLevel - 1; + bFound = pArray->GetEntryIndex(nNewLevel, nStart, nNewEntry); + } + + if ( bFound && IsButtonVisible( nNewLevel, nNewEntry ) ) + { + mnFocusLevel = nNewLevel; + mnFocusEntry = nNewEntry; + } + } + } + + return bWrapped; +} + +bool ScOutlineWindow::ImplMoveFocusByTabOrder( bool bForward ) +{ + bool bRet = false; + size_t nOldLevel = mnFocusLevel; + size_t nOldEntry = mnFocusEntry; + + do + { + /* one level up, if backward from left header, + or one level down, if forward from right header */ + if ( (!bForward) && (mnFocusEntry == SC_OL_HEADERENTRY) ) + bRet |= ImplMoveFocusByLevel( bForward ); + // move to next/previous entry + bool bWrapInLevel = ImplMoveFocusByEntry( bForward, false ); + bRet |= bWrapInLevel; + /* one level up, if wrapped backward to right header, + or one level down, if wrapped forward to right header */ + if ( bForward && bWrapInLevel ) + bRet |= ImplMoveFocusByLevel( bForward ); + } + while ( !IsFocusButtonVisible() && ((nOldLevel != mnFocusLevel) || (nOldEntry != mnFocusEntry)) ); + + return bRet; +} + +void ScOutlineWindow::ImplMoveFocusToVisible( bool bForward ) +{ + // first try to find an entry in the same level + if ( !IsFocusButtonVisible() ) + ImplMoveFocusByEntry( bForward, true ); + // then try to find any other entry + if ( !IsFocusButtonVisible() ) + ImplMoveFocusByTabOrder( bForward ); +} + +void ScOutlineWindow::MoveFocusByEntry( bool bForward ) +{ + HideFocus(); + ImplMoveFocusByEntry( bForward, true ); + ShowFocus(); +} + +void ScOutlineWindow::MoveFocusByLevel( bool bForward ) +{ + HideFocus(); + ImplMoveFocusByLevel( bForward ); + ShowFocus(); +} + +void ScOutlineWindow::MoveFocusByTabOrder( bool bForward ) +{ + HideFocus(); + ImplMoveFocusByTabOrder( bForward ); + ShowFocus(); +} + +void ScOutlineWindow::GetFocus() +{ + Window::GetFocus(); + ShowFocus(); +} + +void ScOutlineWindow::LoseFocus() +{ + HideFocus(); + Window::LoseFocus(); +} + +// mouse ---------------------------------------------------------------------- + +void ScOutlineWindow::StartMouseTracking( size_t nLevel, size_t nEntry ) +{ + mbMTActive = true; + mnMTLevel = nLevel; + mnMTEntry = nEntry; + DrawBorderRel( nLevel, nEntry, true ); +} + +void ScOutlineWindow::EndMouseTracking() +{ + if ( mbMTPressed ) + DrawBorderRel( mnMTLevel, mnMTEntry, false ); + mbMTActive = false; +} + +void ScOutlineWindow::MouseMove( const MouseEvent& rMEvt ) +{ + if ( IsMouseTracking() ) + { + size_t nLevel, nEntry; + bool bHit = false; + + if ( ButtonHit( rMEvt.GetPosPixel(), nLevel, nEntry ) ) + bHit = (nLevel == mnMTLevel) && (nEntry == mnMTEntry); + + if ( bHit != mbMTPressed ) + DrawBorderRel( mnMTLevel, mnMTEntry, bHit ); + } +} + +void ScOutlineWindow::MouseButtonUp( const MouseEvent& rMEvt ) +{ + if ( IsMouseTracking() ) + { + EndMouseTracking(); + + size_t nLevel, nEntry; + if ( ButtonHit( rMEvt.GetPosPixel(), nLevel, nEntry ) ) + if ( (nLevel == mnMTLevel) && (nEntry == mnMTEntry) ) + DoFunction( nLevel, nEntry ); + } +} + +void ScOutlineWindow::MouseButtonDown( const MouseEvent& rMEvt ) +{ + size_t nLevel, nEntry; + bool bHit = ButtonHit( rMEvt.GetPosPixel(), nLevel, nEntry ); + if ( bHit ) + StartMouseTracking( nLevel, nEntry ); + else if ( rMEvt.GetClicks() == 2 ) + { + bHit = LineHit( rMEvt.GetPosPixel(), nLevel, nEntry ); + if ( bHit ) + DoFunction( nLevel, nEntry ); + } + + // if an item has been hit and window is focused, move focus to this item + if ( bHit && HasFocus() ) + { + HideFocus(); + mnFocusLevel = nLevel; + mnFocusEntry = nEntry; + ShowFocus(); + } +} + +// keyboard ------------------------------------------------------------------- + +void ScOutlineWindow::KeyInput( const KeyEvent& rKEvt ) +{ + const vcl::KeyCode& rKCode = rKEvt.GetKeyCode(); + bool bNoMod = !rKCode.GetModifier(); + bool bShift = (rKCode.GetModifier() == KEY_SHIFT); + bool bCtrl = (rKCode.GetModifier() == KEY_MOD1); + + sal_uInt16 nCode = rKCode.GetCode(); + bool bUpDownKey = (nCode == KEY_UP) || (nCode == KEY_DOWN); + bool bLeftRightKey = (nCode == KEY_LEFT) || (nCode == KEY_RIGHT); + + // TAB key + if ( (nCode == KEY_TAB) && (bNoMod || bShift) ) + // move forward without SHIFT key + MoveFocusByTabOrder( bNoMod ); // TAB uses logical order, regardless of mirroring + + // LEFT/RIGHT/UP/DOWN keys + else if ( bNoMod && (bUpDownKey || bLeftRightKey) ) + { + bool bForward = (nCode == KEY_DOWN) || (nCode == KEY_RIGHT); + if ( mbHoriz == bLeftRightKey ) + // move inside level with LEFT/RIGHT in horizontal and with UP/DOWN in vertical + MoveFocusByEntry( bForward != mbMirrorEntries ); + else + // move to next/prev level with LEFT/RIGHT in vertical and with UP/DOWN in horizontal + MoveFocusByLevel( bForward != mbMirrorLevels ); + } + + // CTRL + number + else if ( bCtrl && (nCode >= KEY_1) && (nCode <= KEY_9) ) + { + size_t nLevel = static_cast< size_t >( nCode - KEY_1 ); + if ( nLevel < GetLevelCount() ) + DoFunction( nLevel, SC_OL_HEADERENTRY ); + } + + // other key codes + else switch ( rKCode.GetFullCode() ) + { + case KEY_ADD: DoExpand( mnFocusLevel, mnFocusEntry ); break; + case KEY_SUBTRACT: DoCollapse( mnFocusLevel, mnFocusEntry ); break; + case KEY_SPACE: + case KEY_RETURN: DoFunction( mnFocusLevel, mnFocusEntry ); break; + default: Window::KeyInput( rKEvt ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |