/* -*- 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 #include #include #include #include #include #include #include #include #include #include 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(nLevel), sal::static_int_cast(nEntry) ) : nullptr; } bool ScOutlineWindow::IsHidden( SCCOLROW nColRowIndex ) const { return mbHoriz ? GetDoc().ColHidden(static_cast(nColRowIndex), GetTab()) : GetDoc().RowHidden(static_cast(nColRowIndex), GetTab()); } bool ScOutlineWindow::IsFiltered( SCCOLROW nColRowIndex ) const { // columns cannot be filtered return !mbHoriz && GetDoc().RowFiltered( static_cast(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(nColRowIndex), 0, meWhich, true ).X() : mrViewData.GetScrPos( 0, static_cast(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(nLevel) ); while ( nEntry ) { --nEntry; const ScOutlineEntry* pEntry = pArray->GetEntry( sal::static_int_cast(nLevel), sal::static_int_cast(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(nLevel) ); else { const ScOutlineEntry* pEntry = GetOutlineEntry( nLevel, nEntry ); if ( pEntry ) { if ( pEntry->IsHidden() ) rFunc.ShowOutline( mbHoriz, sal::static_int_cast(nLevel), sal::static_int_cast(nEntry) ); else rFunc.HideOutline( mbHoriz, sal::static_int_cast(nLevel), sal::static_int_cast(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(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(nLevel), sal::static_int_cast(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(nLevel), sal::static_int_cast(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(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: */