diff options
Diffstat (limited to 'vcl/source/treelist/iconviewimpl.cxx')
-rw-r--r-- | vcl/source/treelist/iconviewimpl.cxx | 738 |
1 files changed, 738 insertions, 0 deletions
diff --git a/vcl/source/treelist/iconviewimpl.cxx b/vcl/source/treelist/iconviewimpl.cxx new file mode 100644 index 0000000000..048e193d4f --- /dev/null +++ b/vcl/source/treelist/iconviewimpl.cxx @@ -0,0 +1,738 @@ +/* -*- 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 <vcl/svapp.hxx> +#include <vcl/toolkit/treelistentry.hxx> +#include <tools/debug.hxx> +#include <iconview.hxx> +#include "iconviewimpl.hxx" + +IconViewImpl::IconViewImpl( SvTreeListBox* pTreeListBox, SvTreeList* pTreeList, WinBits nWinStyle ) +: SvImpLBox( pTreeListBox, pTreeList, nWinStyle ) +{ +} + +static bool IsSeparator(const SvTreeListEntry* entry) +{ + return entry && entry->GetFlags() & SvTLEntryFlags::IS_SEPARATOR; +} + +Size IconViewImpl::GetEntrySize(const SvTreeListEntry& entry) const +{ + return static_cast<const IconView*>(m_pView.get())->GetEntrySize(entry); +} + +void IconViewImpl::IterateVisibleEntryAreas(const IterateEntriesFunc& f, bool fromStartEntry) const +{ + tools::Long x = 0, y = 0; + short column = 0; + const tools::Long rowWidth = m_pView->GetEntryWidth() * m_pView->GetColumnsCount(); + tools::Long nPrevHeight = 0; + for (auto entry = fromStartEntry ? m_pStartEntry : m_pView->FirstVisible(); entry; + entry = m_pView->NextVisible(entry)) + { + const Size s = GetEntrySize(*entry); + if (x >= rowWidth || IsSeparator(entry)) + { + column = 0; + x = 0; + y += nPrevHeight; + } + EntryAreaInfo info{ entry, column, tools::Rectangle{ Point{ x, y }, s } }; + const auto result = f(info); + if (result == CallbackResult::Stop) + return; + ++column; + x += s.Width(); + nPrevHeight = s.Height(); + } +} + +tools::Long IconViewImpl::GetEntryRow(const SvTreeListEntry* entry) const +{ + tools::Long nEntryRow = -1; + auto GetRow = [entry, &nEntryRow, row = -1](const EntryAreaInfo& info) mutable + { + if (info.column == 0 && !IsSeparator(info.entry)) + ++row; + if (info.entry != entry) + return CallbackResult::Continue; + nEntryRow = row; + return CallbackResult::Stop; + }; + IterateVisibleEntryAreas(GetRow); + return nEntryRow; +} + +void IconViewImpl::SetStartEntry(SvTreeListEntry* entry) +{ + const tools::Long max = m_aVerSBar->GetRangeMax() - m_aVerSBar->GetVisibleSize(); + tools::Long row = -1; + auto GetEntryAndRow = [&entry, &row, max, found = entry](const EntryAreaInfo& info) mutable + { + if (info.column == 0 && !IsSeparator(info.entry)) + { + found = info.entry; + ++row; + } + if (row >= max || info.entry == entry) + { + entry = found; + return CallbackResult::Stop; + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(GetEntryAndRow); + + m_pStartEntry = entry; + m_aVerSBar->SetThumbPos(row); + m_pView->Invalidate(GetVisibleArea()); +} + +void IconViewImpl::ScrollTo(SvTreeListEntry* entry) +{ + if (!m_aVerSBar->IsVisible()) + return; + const tools::Long entryRow = GetEntryRow(entry); + const tools::Long oldStartRow = m_aVerSBar->GetThumbPos(); + if (entryRow < oldStartRow) + IconViewImpl::SetStartEntry(entry); + const tools::Long visibleRows = m_aVerSBar->GetVisibleSize(); + const tools::Long posRelativeToBottom = entryRow - (oldStartRow + visibleRows) + 1; + if (posRelativeToBottom > 0) + IconViewImpl::SetStartEntry(GoToNextRow(m_pStartEntry, posRelativeToBottom)); +} + +SvTreeListEntry* IconViewImpl::GoToPrevRow(SvTreeListEntry* pEntry, int nRows) const +{ + SvTreeListEntry* pPrev = pEntry; + auto FindPrev = [this, pEntry, nRows, &pPrev, + prevs = std::vector<SvTreeListEntry*>()](const EntryAreaInfo& info) mutable + { + if (info.column == 0 && !IsSeparator(info.entry)) + prevs.push_back(info.entry); + if (pEntry == info.entry) + { + if (prevs.size() > 1) + { + int i = std::max(0, static_cast<int>(prevs.size()) - nRows - 1); + pPrev = prevs[i]; + for (short column = info.column; column; --column) + { + SvTreeListEntry* pNext = m_pView->NextVisible(pPrev); + if (!pNext || IsSeparator(pNext)) + break; + pPrev = pNext; + } + } + return CallbackResult::Stop; + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(FindPrev); + + return pPrev; +} + +SvTreeListEntry* IconViewImpl::GoToNextRow(SvTreeListEntry* pEntry, int nRows) const +{ + SvTreeListEntry* pNext = pEntry; + auto FindNext + = [pEntry, nRows, &pNext, column = -1](const EntryAreaInfo& info) mutable + { + if (info.column <= column && !IsSeparator(info.entry)) + { + if (info.column == 0 && --nRows < 0) + return CallbackResult::Stop; + pNext = info.entry; + if (info.column == column && nRows == 0) + return CallbackResult::Stop; + } + else if (pEntry == info.entry) + { + column = info.column; + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(FindNext); + + return pNext; +} + +void IconViewImpl::CursorUp() +{ + if (!m_pStartEntry) + return; + + SvTreeListEntry* pPrevFirstToDraw = GoToPrevRow(m_pStartEntry, 1); + + m_nFlags &= ~LBoxFlags::Filling; + ShowCursor( false ); + SetStartEntry(pPrevFirstToDraw); + ShowCursor( true ); + m_pView->NotifyScrolled(); +} + +void IconViewImpl::CursorDown() +{ + if (!m_pStartEntry) + return; + + SvTreeListEntry* pNextFirstToDraw = GoToNextRow(m_pStartEntry, 1); + + m_nFlags &= ~LBoxFlags::Filling; + ShowCursor( false ); + SetStartEntry(pNextFirstToDraw); + ShowCursor( true ); + m_pView->NotifyScrolled(); +} + +void IconViewImpl::PageDown( sal_uInt16 nDelta ) +{ + if( !nDelta ) + return; + + if (!m_pStartEntry) + return; + + SvTreeListEntry* pNext = GoToNextRow(m_pStartEntry, nDelta); + + ShowCursor( false ); + + m_nFlags &= ~LBoxFlags::Filling; + SetStartEntry(pNext); + + ShowCursor( true ); +} + +void IconViewImpl::PageUp( sal_uInt16 nDelta ) +{ + if( !nDelta ) + return; + + if (!m_pStartEntry) + return; + + SvTreeListEntry* pPrev = GoToPrevRow(m_pStartEntry, nDelta); + + m_nFlags &= ~LBoxFlags::Filling; + ShowCursor( false ); + + SetStartEntry(pPrev); + + ShowCursor( true ); +} + +void IconViewImpl::KeyDown( bool bPageDown ) +{ + if( !m_aVerSBar->IsVisible() ) + return; + + tools::Long nDelta; + if( bPageDown ) + nDelta = m_aVerSBar->GetPageSize(); + else + nDelta = 1; + + if( nDelta <= 0 ) + return; + + m_nFlags &= ~LBoxFlags::Filling; + + if( bPageDown ) + PageDown( static_cast<short>(nDelta) ); + else + CursorDown(); +} + +void IconViewImpl::KeyUp( bool bPageUp ) +{ + if( !m_aVerSBar->IsVisible() ) + return; + + tools::Long nDelta; + if( bPageUp ) + nDelta = m_aVerSBar->GetPageSize(); + else + nDelta = 1; + + m_nFlags &= ~LBoxFlags::Filling; + + if( bPageUp ) + PageUp( static_cast<short>(nDelta) ); + else + CursorUp(); +} + +tools::Long IconViewImpl::GetEntryLine(const SvTreeListEntry* pEntry) const +{ + if(!m_pStartEntry ) + return -1; // invisible position + + return IconViewImpl::GetEntryPosition(pEntry).Y(); +} + +Point IconViewImpl::GetEntryPosition(const SvTreeListEntry* pEntry) const +{ + Point result{ -m_pView->GetEntryWidth(), -m_pView->GetEntryHeight() }; // invisible + auto FindEntryPos = [pEntry, &result](const EntryAreaInfo& info) + { + if (pEntry == info.entry) + { + result = info.area.TopLeft(); + return CallbackResult::Stop; + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(FindEntryPos, true); + + return result; +} + +// Returns the last entry (in respective row) if position is just past the last entry +SvTreeListEntry* IconViewImpl::GetClickedEntry( const Point& rPoint ) const +{ + DBG_ASSERT( m_pView->GetModel(), "IconViewImpl::GetClickedEntry: how can this ever happen?" ); + if ( !m_pView->GetModel() ) + return nullptr; + if( m_pView->GetEntryCount() == 0 || !m_pStartEntry || !m_pView->GetEntryHeight() || !m_pView->GetEntryWidth()) + return nullptr; + + SvTreeListEntry* pEntry = nullptr; + auto FindEntryByPos = [&pEntry, &rPoint](const EntryAreaInfo& info) + { + if (info.area.Contains(rPoint)) + { + pEntry = info.entry; + return CallbackResult::Stop; + } + else if (info.area.Top() > rPoint.Y()) + { + return CallbackResult::Stop; // we are already below the clicked row + } + else if (info.area.Bottom() > rPoint.Y()) + { + pEntry = info.entry; // Same row; store the entry in case the click is past all entries + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(FindEntryByPos, true); + + return pEntry; +} + +bool IconViewImpl::IsEntryInView( SvTreeListEntry* pEntry ) const +{ + // parent collapsed + if( !m_pView->IsEntryVisible(pEntry) ) + return false; + + tools::Long nY = GetEntryLine( pEntry ); + if( nY < 0 ) + return false; + + tools::Long height = GetEntrySize(*pEntry).Height(); + if (nY + height > m_aOutputSize.Height()) + return false; + + return true; +} + +void IconViewImpl::AdjustScrollBars( Size& rSize ) +{ + tools::Long nEntryHeight = m_pView->GetEntryHeight(); + if( !nEntryHeight ) + return; + + sal_uInt16 nResult = 0; + + Size aOSize( m_pView->Control::GetOutputSizePixel() ); + + const WinBits nWindowStyle = m_pView->GetStyle(); + bool bVerSBar = ( nWindowStyle & WB_VSCROLL ) != 0; + + // number of entries visible within the view + const tools::Long nVisibleRows = aOSize.Height() / nEntryHeight; + m_nVisibleCount = nVisibleRows * m_pView->GetColumnsCount(); + + tools::Long nTotalRows = 0; + tools::Long totalHeight = 0; + auto CountRowsAndHeight = [&nTotalRows, &totalHeight](const EntryAreaInfo& info) + { + totalHeight = std::max(totalHeight, info.area.Bottom()); + if (info.column == 0 && !IsSeparator(info.entry)) + ++nTotalRows; + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(CountRowsAndHeight); + + // do we need a vertical scrollbar? + if( bVerSBar || totalHeight > aOSize.Height()) + { + nResult = 1; + } + + // do we need a Horizontal scrollbar? + bool bHorSBar = (nWindowStyle & WB_HSCROLL) != 0; + if (bHorSBar || m_pView->GetEntryWidth() > aOSize.Width()) + { + nResult += 2; + m_aHorSBar->SetRange(Range(0, m_pView->GetEntryWidth())); + m_aHorSBar->SetVisibleSize(aOSize.Width()); + } + + PositionScrollBars( aOSize, nResult ); + + // adapt Range, VisibleRange etc. + + // refresh output size, in case we have to scroll + tools::Rectangle aRect; + aRect.SetSize( aOSize ); + m_aSelEng.SetVisibleArea( aRect ); + + // vertical scrollbar + if( !m_bInVScrollHdl ) + { + m_aVerSBar->SetRange(Range(0, nTotalRows)); + m_aVerSBar->SetPageSize(nVisibleRows); + m_aVerSBar->SetVisibleSize(nVisibleRows); + } + else + { + m_nFlags |= LBoxFlags::EndScrollSetVisSize; + } + + if( nResult & 0x0001 ) + m_aVerSBar->Show(); + else + m_aVerSBar->Hide(); + + if (nResult & 0x0002) + m_aHorSBar->Show(); + else + m_aHorSBar->Hide(); + + rSize = aOSize; +} + +// returns 0 if position is just past the last entry +SvTreeListEntry* IconViewImpl::GetEntry( const Point& rPoint ) const +{ + if( (m_pView->GetEntryCount() == 0) || !m_pStartEntry || + (rPoint.Y() > m_aOutputSize.Height()) + || !m_pView->GetEntryHeight() + || !m_pView->GetEntryWidth()) + return nullptr; + + SvTreeListEntry* pEntry = nullptr; + auto FindEntryByPos = [&pEntry, &rPoint](const EntryAreaInfo& info) + { + if (info.area.Contains(rPoint)) + { + pEntry = info.entry; + return CallbackResult::Stop; + } + else if (info.area.Top() > rPoint.Y()) + { + return CallbackResult::Stop; // we are already below the clicked row + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(FindEntryByPos, true); + + return pEntry; +} + +void IconViewImpl::SyncVerThumb() +{ + m_aVerSBar->SetThumbPos(GetEntryRow(m_pStartEntry)); +} + +void IconViewImpl::UpdateAll() +{ + FindMostRight(); + SyncVerThumb(); + FillView(); + ShowVerSBar(); + if( m_bSimpleTravel && m_pCursor && m_pView->HasFocus() ) + m_pView->Select( m_pCursor ); + ShowCursor( true ); + m_pView->Invalidate( GetVisibleArea() ); +} + +void IconViewImpl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + if (!m_pView->GetVisibleCount()) + return; + + m_nFlags |= LBoxFlags::InPaint; + + if (m_nFlags & LBoxFlags::Filling) + { + SvTreeListEntry* pFirst = m_pView->First(); + if (pFirst != m_pStartEntry) + { + ShowCursor(false); + m_pStartEntry = m_pView->First(); + m_aVerSBar->SetThumbPos( 0 ); + StopUserEvent(); + ShowCursor(true); + m_nCurUserEvent = Application::PostUserEvent(LINK(this, SvImpLBox, MyUserEvent), + reinterpret_cast<void*>(1)); + return; + } + } + + if (!m_pStartEntry) + { + m_pStartEntry = m_pView->First(); + } + + if (!m_pCursor && !mbNoAutoCurEntry) + { + // do not select if multiselection or explicit set + bool bNotSelect = (m_aSelEng.GetSelectionMode() == SelectionMode::Multiple ) || ((m_nStyle & WB_NOINITIALSELECTION) == WB_NOINITIALSELECTION); + SetCursor(m_pStartEntry, bNotSelect); + } + + auto PaintEntry = [iconView = static_cast<IconView*>(m_pView.get()), &rRect, + &rRenderContext](const EntryAreaInfo& info) + { + if (!info.area.GetIntersection(rRect).IsEmpty()) + { + iconView->PaintEntry(*info.entry, info.area.Left(), info.area.Top(), rRenderContext); + } + else if (info.area.Top() > rRect.Bottom()) + { + return CallbackResult::Stop; // we are already below the last visible row + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(PaintEntry, true); + + m_nFlags &= ~LBoxFlags::DeselectAll; + rRenderContext.SetClipRegion(); + m_nFlags &= ~LBoxFlags::InPaint; +} + +void IconViewImpl::InvalidateEntry( tools::Long nId ) const +{ + if( m_nFlags & LBoxFlags::InPaint ) + return; + if (nId < 0) + return; + + // nId is a Y coordinate of the top of the element, coming from GetEntryLine + tools::Rectangle aRect( GetVisibleArea() ); + if (nId > aRect.Bottom()) + return; + aRect.SetTop(nId); // Invalidate everything below + m_pView->Invalidate( aRect ); +} + +bool IconViewImpl::KeyInput( const KeyEvent& rKEvt ) +{ + const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); + + if( rKeyCode.IsMod2() ) + return false; // don't evaluate Alt key + + m_nFlags &= ~LBoxFlags::Filling; + + if( !m_pCursor ) + m_pCursor = m_pStartEntry; + if( !m_pCursor ) + return false; + + sal_uInt16 aCode = rKeyCode.GetCode(); + + bool bShift = rKeyCode.IsShift(); + bool bMod1 = rKeyCode.IsMod1(); + + SvTreeListEntry* pNewCursor; + + bool bHandled = true; + + switch( aCode ) + { + case KEY_LEFT: + if( !IsEntryInView( m_pCursor ) ) + MakeVisible( m_pCursor ); + + pNewCursor = m_pCursor; + do + { + pNewCursor = m_pView->PrevVisible(pNewCursor); + } while( pNewCursor && !IsSelectable(pNewCursor) ); + + // if there is no next entry, take the current one + // this ensures that in case of _one_ entry in the list, this entry is selected when pressing + // the cursor key + if (!pNewCursor) + pNewCursor = m_pCursor; + + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on + if( !IsEntryInView( pNewCursor ) ) + KeyUp( false ); + break; + + case KEY_RIGHT: + if( !IsEntryInView( m_pCursor ) ) + MakeVisible( m_pCursor ); + + pNewCursor = m_pCursor; + do + { + pNewCursor = m_pView->NextVisible(pNewCursor); + } while( pNewCursor && !IsSelectable(pNewCursor) ); + + // if there is no next entry, take the current one + // this ensures that in case of _one_ entry in the list, this entry is selected when pressing + // the cursor key + if ( !pNewCursor && m_pCursor ) + pNewCursor = m_pCursor; + + if( pNewCursor ) + { + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + if( IsEntryInView( pNewCursor ) ) + SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on + else + { + if( m_pCursor ) + m_pView->Select( m_pCursor, false ); + KeyDown( false ); + SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on + } + } + else + KeyDown( false ); // because scrollbar range might still + // allow scrolling + break; + + case KEY_UP: + { + pNewCursor = GoToPrevRow(m_pCursor, 1); + + if( pNewCursor ) + { + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on + ScrollTo(pNewCursor); + } + break; + } + + case KEY_DOWN: + { + pNewCursor = GoToNextRow(m_pCursor, 1); + + if( pNewCursor ) + { + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + ScrollTo(pNewCursor); + SetCursor(pNewCursor, bMod1); // no selection, when Ctrl is on + } + else + KeyDown( false ); // because scrollbar range might still + // allow scrolling + break; + } + + case KEY_PAGEUP: + if (!bMod1) + { + const sal_uInt16 nDelta = m_aVerSBar->GetPageSize(); + pNewCursor = GoToPrevRow(m_pCursor, nDelta); + + if (pNewCursor) + { + m_aSelEng.CursorPosChanging(bShift, bMod1); + ScrollTo(pNewCursor); + SetCursor(pNewCursor); + } + } + else + bHandled = false; + break; + + case KEY_PAGEDOWN: + if (!bMod1) + { + const sal_uInt16 nDelta = m_aVerSBar->GetPageSize(); + pNewCursor = GoToNextRow(m_pCursor, nDelta); + + if (pNewCursor) + { + m_aSelEng.CursorPosChanging(bShift, bMod1); + ScrollTo(pNewCursor); + SetCursor(pNewCursor); + } + else + KeyDown(false); + } + else + bHandled = false; + break; + + case KEY_RETURN: + case KEY_SPACE: + { + bHandled = !m_pView->aDoubleClickHdl.Call(m_pView); + break; + } + + case KEY_END: + { + pNewCursor = m_pView->GetModel()->Last(); + + while( pNewCursor && !IsSelectable(pNewCursor) ) + { + pNewCursor = m_pView->PrevVisible(pNewCursor); + } + + SetStartEntry(pNewCursor); + + if( pNewCursor && pNewCursor != m_pCursor) + { +// SelAllDestrAnch( false ); + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + SetCursor( pNewCursor ); + } + + bHandled = true; + + break; + } + + default: + { + bHandled = false; + break; + } + } + + if(!bHandled) + return SvImpLBox::KeyInput( rKEvt ); + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |