/* -*- 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 <memory>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <vcl/event.hxx>
#include <vcl/i18nhelp.hxx>
#include <vcl/naturalsort.hxx>
#include <vcl/vcllayout.hxx>
#include <vcl/toolkit/lstbox.hxx>
#include <vcl/toolkit/scrbar.hxx>
#include <listbox.hxx>
#include <svdata.hxx>
#include <window.h>
#include <com/sun/star/accessibility/AccessibleRole.hpp>
#include <sal/log.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/string_view.hxx>
#include <osl/diagnose.h>
#include <comphelper/string.hxx>
#include <comphelper/processfactory.hxx>
#include <limits>
#define MULTILINE_ENTRY_DRAW_FLAGS ( DrawTextFlags::WordBreak | DrawTextFlags::MultiL
ine | DrawTextFlags::VCenter )
using namespace ::com::sun::star;
constexpr tools::Long gnBorder = 1;
void ImplInitDropDownButton( PushButton* pButton )
{
pButton->SetSymbol( SymbolType::SPIN_DOWN );
if ( pButton->IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire)
&& ! pButton->IsNativeControlSupported(ControlType::Listbox, ControlPart::ButtonDown) )
pButton->SetBackground();
}
ImplEntryList::ImplEntryList( vcl::Window* pWindow )
{
mpWindow = pWindow;
mnLastSelected = LISTBOX_ENTRY_NOTFOUND;
mnSelectionAnchor = LISTBOX_ENTRY_NOTFOUND;
mnImages = 0;
mbCallSelectionChangedHdl = true ;
mnMRUCount = 0;
mnMaxMRUCount = 0;
}
ImplEntryList::~ImplEntryList()
{
Clear();
}
void ImplEntryList::Clear()
{
mnImages = 0;
maEntries.clear();
}
void ImplEntryList::dispose()
{
Clear();
mpWindow.reset();
}
void ImplEntryList::SelectEntry( sal_Int32 nPos, bool bSelect )
{
if (0 <= nPos && o3tl::make_unsigned(nPos) < maEntries.size())
{
std::vector<std::unique_ptr<ImplEntryType> >::iterator iter = maEntries.begin()+nPos;
if ( ( (*iter)->mbIsSelected != bSelect ) &&
( ( (*iter)->mnFlags & ListBoxEntryFlags::DisableSelection) == ListBoxEntryFlags::NONE ) )
{
(*iter)->mbIsSelected = bSelect;
if ( mbCallSelectionChangedHdl )
maSelectionChangedHdl.Call( nPos );
}
}
}
namespace
{
comphelper::string::NaturalStringSorter& GetSorter()
{
static comphelper::string::NaturalStringSorter gSorter(
::comphelper::getProcessComponentContext(),
Application::GetSettings().GetLanguageTag().getLocale());
return gSorter;
};
}
namespace vcl
{
sal_Int32 NaturalSortCompare(const OUString &rA, const OUString &rB)
{
const comphelper::string::NaturalStringSorter &rSorter = GetSorter();
return rSorter.compare(rA, rB);
}
}
sal_Int32 ImplEntryList::InsertEntry( sal_Int32 nPos, ImplEntryType* pNewEntry, bool bSort )
{
assert(nPos >= 0);
assert(maEntries.size() < LISTBOX_MAX_ENTRIES);
if ( !!pNewEntry->maImage )
mnImages++;
sal_Int32 insPos = 0;
const sal_Int32 nEntriesSize = static_cast <sal_Int32>(maEntries.size());
if ( !bSort || maEntries.empty())
{
if (0 <= nPos && nPos < nEntriesSize)
{
insPos = nPos;
maEntries.insert( maEntries.begin() + nPos, std::unique_ptr<ImplEntryType>(pNewEntry) );
}
else
{
insPos = nEntriesSize;
maEntries.push_back(std::unique_ptr<ImplEntryType>(pNewEntry));
}
}
else
{
const comphelper::string::NaturalStringSorter &rSorter = GetSorter();
const OUString& rStr = pNewEntry->maStr;
ImplEntryType* pTemp = GetEntry( nEntriesSize-1 );
try
{
sal_Int32 nComp = rSorter.compare(rStr, pTemp->maStr);
// fast insert for sorted data
if ( nComp >= 0 )
{
insPos = nEntriesSize;
maEntries.push_back(std::unique_ptr<ImplEntryType>(pNewEntry));
}
else
{
pTemp = GetEntry( mnMRUCount );
nComp = rSorter.compare(rStr, pTemp->maStr);
if ( nComp <= 0 )
{
insPos = 0;
maEntries.insert(maEntries.begin(), std::unique_ptr<ImplEntryType>(pNewEntry));
}
else
{
sal_uLong nLow = mnMRUCount;
sal_uLong nHigh = maEntries.size()-1;
sal_Int32 nMid;
// binary search
do
{
nMid = static_cast <sal_Int32>((nLow + nHigh) / 2);
pTemp = GetEntry( nMid );
nComp = rSorter.compare(rStr, pTemp->maStr);
if ( nComp < 0 )
nHigh = nMid-1;
else
{
if ( nComp > 0 )
nLow = nMid + 1;
else
break ;
}
}
while ( nLow <= nHigh );
if ( nComp >= 0 )
nMid++;
insPos = nMid;
maEntries.insert(maEntries.begin()+nMid, std::unique_ptr<ImplEntryType>(pNewEntry));
}
}
}
catch (uno::RuntimeException& )
{
// XXX this is arguable, if the exception occurred because pNewEntry is
// garbage you wouldn't insert it. If the exception occurred because the
// Collator implementation is garbage then give the user a chance to see
// his stuff
insPos = 0;
maEntries.insert(maEntries.begin(), std::unique_ptr<ImplEntryType>(pNewEntry));
}
}
return insPos;
}
void ImplEntryList::RemoveEntry( sal_Int32 nPos )
{
if (0 <= nPos && o3tl::make_unsigned(nPos) < maEntries.size())
{
std::vector<std::unique_ptr<ImplEntryType> >::iterator iter = maEntries.begin()+ nPos;
if ( !!(*iter)->maImage )
mnImages--;
maEntries.erase(iter);
}
}
sal_Int32 ImplEntryList::FindEntry( std::u16string_view rString, bool bSearchMRUArea ) const
{
const sal_Int32 nEntries = static_cast <sal_Int32>(maEntries.size());
for ( sal_Int32 n = bSearchMRUArea ? 0 : GetMRUCount(); n < nEntries; n++ )
{
OUString aComp( vcl::I18nHelper::filterFormattingChars( maEntries[n]->maStr ) );
if ( aComp == rString )
return n;
}
return LISTBOX_ENTRY_NOTFOUND;
}
sal_Int32 ImplEntryList::FindMatchingEntry( const OUString& rStr, sal_Int32 nStart, bool bLazy ) const
{
sal_Int32 nPos = LISTBOX_ENTRY_NOTFOUND;
sal_Int32 nEntryCount = GetEntryCount();
const vcl::I18nHelper& rI18nHelper = mpWindow->GetSettings().GetLocaleI18nHelper();
for ( sal_Int32 n = nStart; n < nEntryCount; )
{
ImplEntryType* pImplEntry = GetEntry( n );
bool bMatch;
if ( bLazy )
{
bMatch = rI18nHelper.MatchString( rStr, pImplEntry->maStr );
}
else
{
bMatch = pImplEntry->maStr.startsWith(rStr);
}
if ( bMatch )
{
nPos = n;
break ;
}
n++;
}
return nPos;
}
tools::Long ImplEntryList::GetAddedHeight( sal_Int32 i_nEndIndex, sal_Int32 i_nBeginIndex ) const
{
tools::Long nHeight = 0;
sal_Int32 nStart = std::min(i_nEndIndex, i_nBeginIndex);
sal_Int32 nStop = std::max(i_nEndIndex, i_nBeginIndex);
sal_Int32 nEntryCount = GetEntryCount();
if ( 0 <= nStop && nStop != LISTBOX_ENTRY_NOTFOUND && nEntryCount != 0 )
{
// sanity check
if ( nStop > nEntryCount-1 )
nStop = nEntryCount-1;
if (nStart < 0)
nStart = 0;
else if ( nStart > nEntryCount-1 )
nStart = nEntryCount-1;
sal_Int32 nIndex = nStart;
while ( nIndex != LISTBOX_ENTRY_NOTFOUND && nIndex < nStop )
{
tools::Long nPosHeight = GetEntryPtr( nIndex )->getHeightWithMargin();
if (nHeight > ::std::numeric_limits<tools::Long >::max() - nPosHeight)
{
SAL_WARN( "vcl" , "ImplEntryList::GetAddedHeight: truncated" );
break ;
}
nHeight += nPosHeight;
nIndex++;
}
}
else
nHeight = 0;
return i_nEndIndex > i_nBeginIndex ? nHeight : -nHeight;
}
tools::Long ImplEntryList::GetEntryHeight( sal_Int32 nPos ) const
{
ImplEntryType* pImplEntry = GetEntry( nPos );
return pImplEntry ? pImplEntry->getHeightWithMargin() : 0;
}
OUString ImplEntryList::GetEntryText( sal_Int32 nPos ) const
{
OUString aEntryText;
ImplEntryType* pImplEntry = GetEntry( nPos );
if ( pImplEntry )
aEntryText = pImplEntry->maStr;
return aEntryText;
}
bool ImplEntryList::HasEntryImage( sal_Int32 nPos ) const
{
bool bImage = false ;
ImplEntryType* pImplEntry = GetEntry( nPos );
if ( pImplEntry )
bImage = !!pImplEntry->maImage;
return bImage;
}
Image ImplEntryList::GetEntryImage( sal_Int32 nPos ) const
{
Image aImage;
ImplEntryType* pImplEntry = GetEntry( nPos );
if ( pImplEntry )
aImage = pImplEntry->maImage;
return aImage;
}
void ImplEntryList::SetEntryData( sal_Int32 nPos, void * pNewData )
{
ImplEntryType* pImplEntry = GetEntry( nPos );
if ( pImplEntry )
pImplEntry->mpUserData = pNewData;
}
void * ImplEntryList::GetEntryData( sal_Int32 nPos ) const
{
ImplEntryType* pImplEntry = GetEntry( nPos );
return pImplEntry ? pImplEntry->mpUserData : nullptr;
}
void ImplEntryList::SetEntryFlags( sal_Int32 nPos, ListBoxEntryFlags nFlags )
{
ImplEntryType* pImplEntry = GetEntry( nPos );
if ( pImplEntry )
pImplEntry->mnFlags = nFlags;
}
sal_Int32 ImplEntryList::GetSelectedEntryCount() const
{
sal_Int32 nSelCount = 0;
for ( sal_Int32 n = GetEntryCount(); n; )
{
ImplEntryType* pImplEntry = GetEntry( --n );
if ( pImplEntry->mbIsSelected )
nSelCount++;
}
return nSelCount;
}
OUString ImplEntryList::GetSelectedEntry( sal_Int32 nIndex ) const
{
return GetEntryText( GetSelectedEntryPos( nIndex ) );
}
sal_Int32 ImplEntryList::GetSelectedEntryPos( sal_Int32 nIndex ) const
{
sal_Int32 nSelEntryPos = LISTBOX_ENTRY_NOTFOUND;
sal_Int32 nSel = 0;
sal_Int32 nEntryCount = GetEntryCount();
for ( sal_Int32 n = 0; n < nEntryCount; n++ )
{
ImplEntryType* pImplEntry = GetEntry( n );
if ( pImplEntry->mbIsSelected )
{
if ( nSel == nIndex )
{
nSelEntryPos = n;
break ;
}
nSel++;
}
}
return nSelEntryPos;
}
bool ImplEntryList::IsEntryPosSelected( sal_Int32 nIndex ) const
{
ImplEntryType* pImplEntry = GetEntry( nIndex );
return pImplEntry && pImplEntry->mbIsSelected;
}
bool ImplEntryList::IsEntrySelectable( sal_Int32 nPos ) const
{
ImplEntryType* pImplEntry = GetEntry( nPos );
return pImplEntry == nullptr || ((pImplEntry->mnFlags & ListBoxEntryFlags::DisableSelection) == ListBoxEntryFlags::NONE);
}
sal_Int32 ImplEntryList::FindFirstSelectable( sal_Int32 nPos, bool bForward /* = true */ ) const
{
if ( IsEntrySelectable( nPos ) )
return nPos;
if ( bForward )
{
for ( nPos = nPos + 1; nPos < GetEntryCount(); nPos++ )
{
if ( IsEntrySelectable( nPos ) )
return nPos;
}
}
else
{
while ( nPos )
{
nPos--;
if ( IsEntrySelectable( nPos ) )
return nPos;
}
}
return LISTBOX_ENTRY_NOTFOUND;
}
ImplListBoxWindow::ImplListBoxWindow( vcl::Window* pParent, WinBits nWinStyle ) :
Control( pParent, 0 ),
maEntryList( this ),
maQuickSelectionEngine( *this )
{
mnTop = 0;
mnLeft = 0;
mnSelectModifier = 0;
mnUserDrawEntry = LISTBOX_ENTRY_NOTFOUND;
mbTrack = false ;
mbTravelSelect = false ;
mbTrackingSelect = false ;
mbSelectionChanged = false ;
mbMouseMoveSelect = false ;
mbMulti = false ;
mbGrabFocus = false ;
mbUserDrawEnabled = false ;
mbInUserDraw = false ;
mbReadOnly = false ;
mbHasFocusRect = false ;
mbRight = ( nWinStyle & WB_RIGHT );
mbCenter = ( nWinStyle & WB_CENTER );
mbSimpleMode = ( nWinStyle & WB_SIMPLEMODE );
mbSort = ( nWinStyle & WB_SORT );
mbIsDropdown = ( nWinStyle & WB_DROPDOWN );
mbEdgeBlending = false ;
mnCurrentPos = LISTBOX_ENTRY_NOTFOUND;
mnTrackingSaveSelection = LISTBOX_ENTRY_NOTFOUND;
GetOutDev()->SetLineColor();
SetTextFillColor();
SetBackground( Wallpaper( GetSettings().GetStyleSettings().GetFieldColor() ) );
ApplySettings(*GetOutDev());
ImplCalcMetrics();
}
ImplListBoxWindow::~ImplListBoxWindow()
{
disposeOnce();
}
void ImplListBoxWindow::dispose()
{
maEntryList.dispose();
Control::dispose();
}
void ImplListBoxWindow::ApplySettings(vcl::RenderContext& rRenderContext)
{
const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
ApplyControlFont(rRenderContext, rStyleSettings.GetFieldFont());
ApplyControlForeground(rRenderContext, rStyleSettings.GetListBoxWindowTextColor());
if (IsControlBackground())
rRenderContext.SetBackground(GetControlBackground());
else
rRenderContext.SetBackground(rStyleSettings.GetListBoxWindowBackgroundColor());
}
void ImplListBoxWindow::ImplCalcMetrics()
{
mnMaxWidth = 0;
mnMaxTxtWidth = 0;
mnMaxImgWidth = 0;
mnMaxImgTxtWidth= 0;
mnMaxImgHeight = 0;
mnTextHeight = static_cast <sal_uInt16>(GetTextHeight());
mnMaxTxtHeight = mnTextHeight + gnBorder;
mnMaxHeight = mnMaxTxtHeight;
if ( maUserItemSize.Height() > mnMaxHeight )
mnMaxHeight = static_cast <sal_uInt16>(maUserItemSize.Height());
if ( maUserItemSize.Width() > mnMaxWidth )
mnMaxWidth= static_cast <sal_uInt16>(maUserItemSize.Width());
for ( sal_Int32 n = maEntryList.GetEntryCount(); n; )
{
ImplEntryType* pEntry = maEntryList.GetMutableEntryPtr( --n );
ImplUpdateEntryMetrics( *pEntry );
}
if ( mnCurrentPos != LISTBOX_ENTRY_NOTFOUND )
{
Size aSz( GetOutputSizePixel().Width(), maEntryList.GetEntryPtr( mnCurrentPos )->getHeightWithMargin() );
maFocusRect.SetSize( aSz );
}
}
void ImplListBoxWindow::Clear()
{
maEntryList.Clear();
mnMaxHeight = mnMaxTxtHeight;
mnMaxWidth = 0;
mnMaxTxtWidth = 0;
mnMaxImgTxtWidth= 0;
mnMaxImgWidth = 0;
mnMaxImgHeight = 0;
mnTop = 0;
mnLeft = 0;
ImplClearLayoutData();
mnCurrentPos = LISTBOX_ENTRY_NOTFOUND;
maQuickSelectionEngine.Reset();
Invalidate();
}
void ImplListBoxWindow::SetUserItemSize( const Size& rSz )
{
ImplClearLayoutData();
maUserItemSize = rSz;
ImplCalcMetrics();
}
namespace {
struct ImplEntryMetrics
{
bool bText;
bool bImage;
tools::Long nEntryWidth;
tools::Long nEntryHeight;
tools::Long nTextWidth;
tools::Long nImgWidth;
tools::Long nImgHeight;
};
}
tools::Long ImplEntryType::getHeightWithMargin() const
{
return mnHeight + ImplGetSVData()->maNWFData.mnListBoxEntryMargin;
}
SalLayoutGlyphs* ImplEntryType::GetTextGlyphs(const OutputDevice* pOutputDevice)
{
if (maStrGlyphs.IsValid())
// Use pre-calculated result.
return &maStrGlyphs;
std::unique_ptr<SalLayout> pLayout = pOutputDevice->ImplLayout(
maStr, 0, maStr.getLength(), Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly);
if (!pLayout)
return nullptr;
// Remember the calculation result.
maStrGlyphs = pLayout->GetGlyphs();
return &maStrGlyphs;
}
void ImplListBoxWindow::ImplUpdateEntryMetrics( ImplEntryType& rEntry )
{
const bool bIsUserDrawEnabled = IsUserDrawEnabled();
ImplEntryMetrics aMetrics;
aMetrics.bText = !rEntry.maStr.isEmpty();
aMetrics.bImage = !!rEntry.maImage;
aMetrics.nEntryWidth = 0;
aMetrics.nEntryHeight = 0;
aMetrics.nTextWidth = 0;
aMetrics.nImgWidth = 0;
aMetrics.nImgHeight = 0;
if (aMetrics.bText && !bIsUserDrawEnabled)
{
if ( rEntry.mnFlags & ListBoxEntryFlags::MultiLine )
{
// multiline case
Size aCurSize( PixelToLogic( GetSizePixel() ) );
// set the current size to a large number
// GetTextRect should shrink it to the actual size
aCurSize.setHeight( 0x7fffff );
tools::Rectangle aTextRect( Point( 0, 0 ), aCurSize );
aTextRect = GetTextRect( aTextRect, rEntry.maStr, DrawTextFlags::WordBreak | DrawTextFlags::MultiLine );
aMetrics.nTextWidth = aTextRect.GetWidth();
if ( aMetrics.nTextWidth > mnMaxTxtWidth )
mnMaxTxtWidth = aMetrics.nTextWidth;
aMetrics.nEntryWidth = mnMaxTxtWidth;
aMetrics.nEntryHeight = aTextRect.GetHeight() + gnBorder;
}
else
{
// normal single line case
const SalLayoutGlyphs* pGlyphs = rEntry.GetTextGlyphs(GetOutDev());
aMetrics.nTextWidth
= static_cast <sal_uInt16>(GetTextWidth(rEntry.maStr, 0, -1, nullptr, pGlyphs));
if ( aMetrics.nTextWidth > mnMaxTxtWidth )
mnMaxTxtWidth = aMetrics.nTextWidth;
aMetrics.nEntryWidth = mnMaxTxtWidth;
aMetrics.nEntryHeight = mnTextHeight + gnBorder;
}
}
if ( aMetrics.bImage )
{
Size aImgSz = rEntry.maImage.GetSizePixel();
aMetrics.nImgWidth = static_cast <sal_uInt16>(CalcZoom( aImgSz.Width() ));
aMetrics.nImgHeight = static_cast <sal_uInt16>(CalcZoom( aImgSz.Height() ));
if ( aMetrics.nImgWidth > mnMaxImgWidth )
mnMaxImgWidth = aMetrics.nImgWidth;
if ( aMetrics.nImgHeight > mnMaxImgHeight )
mnMaxImgHeight = aMetrics.nImgHeight;
mnMaxImgTxtWidth = std::max( mnMaxImgTxtWidth, aMetrics.nTextWidth );
aMetrics.nEntryHeight = std::max( aMetrics.nImgHeight, aMetrics.nEntryHeight );
}
if (bIsUserDrawEnabled || aMetrics.bImage)
{
aMetrics.nEntryWidth = std::max( aMetrics.nImgWidth, maUserItemSize.Width() );
if (!bIsUserDrawEnabled && aMetrics.bText)
aMetrics.nEntryWidth += aMetrics.nTextWidth + IMG_TXT_DISTANCE;
aMetrics.nEntryHeight = std::max( std::max( mnMaxImgHeight, maUserItemSize.Height() ) + 2,
aMetrics.nEntryHeight );
}
if (!aMetrics.bText && !aMetrics.bImage && !bIsUserDrawEnabled)
{
// entries which have no (aka an empty) text, and no image,
// and are not user-drawn, should be shown nonetheless
aMetrics.nEntryHeight = mnTextHeight + gnBorder;
}
if ( aMetrics.nEntryWidth > mnMaxWidth )
mnMaxWidth = aMetrics.nEntryWidth;
if ( aMetrics.nEntryHeight > mnMaxHeight )
mnMaxHeight = aMetrics.nEntryHeight;
rEntry.mnHeight = aMetrics.nEntryHeight;
}
void ImplListBoxWindow::ImplCallSelect()
{
if ( !IsTravelSelect() && GetEntryList().GetMaxMRUCount() )
{
// Insert the selected entry as MRU, if not already first MRU
sal_Int32 nSelected = GetEntryList().GetSelectedEntryPos( 0 );
sal_Int32 nMRUCount = GetEntryList().GetMRUCount();
OUString aSelected = GetEntryList().GetEntryText( nSelected );
sal_Int32 nFirstMatchingEntryPos = GetEntryList().FindEntry( aSelected, true );
if ( nFirstMatchingEntryPos || !nMRUCount )
{
bool bSelectNewEntry = false ;
if ( nFirstMatchingEntryPos < nMRUCount )
{
RemoveEntry( nFirstMatchingEntryPos );
nMRUCount--;
if ( nFirstMatchingEntryPos == nSelected )
bSelectNewEntry = true ;
}
else if ( nMRUCount == GetEntryList().GetMaxMRUCount() )
{
RemoveEntry( nMRUCount - 1 );
nMRUCount--;
}
ImplClearLayoutData();
ImplEntryType* pNewEntry = new ImplEntryType( aSelected );
pNewEntry->mbIsSelected = bSelectNewEntry;
GetEntryList().InsertEntry( 0, pNewEntry, false );
ImplUpdateEntryMetrics( *pNewEntry );
GetEntryList().SetMRUCount( ++nMRUCount );
SetSeparatorPos( nMRUCount ? nMRUCount-1 : 0 );
maMRUChangedHdl.Call( nullptr );
}
}
maSelectHdl.Call( nullptr );
mbSelectionChanged = false ;
}
sal_Int32 ImplListBoxWindow::InsertEntry(sal_Int32 nPos, ImplEntryType* pNewEntry, bool bSort)
{
assert(nPos >= 0);
assert(maEntryList.GetEntryCount() < LISTBOX_MAX_ENTRIES);
ImplClearLayoutData();
sal_Int32 nNewPos = maEntryList.InsertEntry( nPos, pNewEntry, bSort );
if ( GetStyle() & WB_WORDBREAK )
pNewEntry->mnFlags |= ListBoxEntryFlags::MultiLine;
ImplUpdateEntryMetrics( *pNewEntry );
return nNewPos;
}
sal_Int32 ImplListBoxWindow::InsertEntry( sal_Int32 nPos, ImplEntryType* pNewEntry )
{
return InsertEntry(nPos, pNewEntry, mbSort);
}
void ImplListBoxWindow::RemoveEntry( sal_Int32 nPos )
{
ImplClearLayoutData();
maEntryList.RemoveEntry( nPos );
if ( mnCurrentPos >= maEntryList.GetEntryCount() )
mnCurrentPos = LISTBOX_ENTRY_NOTFOUND;
ImplCalcMetrics();
}
void ImplListBoxWindow::SetEntryFlags( sal_Int32 nPos, ListBoxEntryFlags nFlags )
{
maEntryList.SetEntryFlags( nPos, nFlags );
ImplEntryType* pEntry = maEntryList.GetMutableEntryPtr( nPos );
if ( pEntry )
ImplUpdateEntryMetrics( *pEntry );
}
void ImplListBoxWindow::ImplShowFocusRect()
{
if ( mbHasFocusRect )
HideFocus();
ShowFocus( maFocusRect );
mbHasFocusRect = true ;
}
void ImplListBoxWindow::ImplHideFocusRect()
{
if ( mbHasFocusRect )
{
HideFocus();
mbHasFocusRect = false ;
}
}
sal_Int32 ImplListBoxWindow::GetEntryPosForPoint( const Point& rPoint ) const
{
tools::Long nY = gnBorder;
sal_Int32 nSelect = mnTop;
const ImplEntryType* pEntry = maEntryList.GetEntryPtr( nSelect );
while (pEntry)
{
tools::Long nEntryHeight = pEntry->getHeightWithMargin();
if (rPoint.Y() <= nEntryHeight + nY)
break ;
nY += nEntryHeight;
pEntry = maEntryList.GetEntryPtr( ++nSelect );
}
if ( pEntry == nullptr )
nSelect = LISTBOX_ENTRY_NOTFOUND;
return nSelect;
}
bool ImplListBoxWindow::IsVisible( sal_Int32 i_nEntry ) const
{
bool bRet = false ;
if ( i_nEntry >= mnTop )
{
if ( maEntryList.GetAddedHeight( i_nEntry, mnTop ) <
PixelToLogic( GetSizePixel() ).Height() )
{
bRet = true ;
}
}
return bRet;
}
tools::Long ImplListBoxWindow::GetEntryHeightWithMargin() const
{
tools::Long nMargin = ImplGetSVData()->maNWFData.mnListBoxEntryMargin;
return mnMaxHeight + nMargin;
}
sal_Int32 ImplListBoxWindow::GetLastVisibleEntry() const
{
sal_Int32 nPos = mnTop;
tools::Long nWindowHeight = GetSizePixel().Height();
sal_Int32 nCount = maEntryList.GetEntryCount();
tools::Long nDiff;
for ( nDiff = 0; nDiff < nWindowHeight && nPos < nCount; nDiff = maEntryList.GetAddedHeight( nPos, mnTop ) )
nPos++;
if ( nDiff > nWindowHeight && nPos > mnTop )
nPos--;
if ( nPos >= nCount )
nPos = nCount-1;
return nPos;
}
void ImplListBoxWindow::MouseButtonDown( const MouseEvent& rMEvt )
{
mbMouseMoveSelect = false ; // only till the first MouseButtonDown
maQuickSelectionEngine.Reset();
if ( !IsReadOnly() )
{
if ( rMEvt.GetClicks() == 1 )
{
sal_Int32 nSelect = GetEntryPosForPoint( rMEvt.GetPosPixel() );
if ( nSelect != LISTBOX_ENTRY_NOTFOUND )
{
if ( !mbMulti && GetEntryList().GetSelectedEntryCount() )
mnTrackingSaveSelection = GetEntryList().GetSelectedEntryPos( 0 );
else
mnTrackingSaveSelection = LISTBOX_ENTRY_NOTFOUND;
mnCurrentPos = nSelect;
mbTrackingSelect = true ;
bool bCurPosChange = (mnCurrentPos != nSelect);
(void )SelectEntries( nSelect, LET_MBDOWN, rMEvt.IsShift(), rMEvt.IsMod1() ,bCurPosChange);
mbTrackingSelect = false ;
if ( mbGrabFocus )
GrabFocus();
StartTracking( StartTrackingFlags::ScrollRepeat );
}
}
if ( rMEvt.GetClicks() == 2 )
{
maDoubleClickHdl.Call( this );
}
}
else // if ( mbGrabFocus )
{
GrabFocus();
}
}
void ImplListBoxWindow::MouseMove( const MouseEvent& rMEvt )
{
if (rMEvt.IsLeaveWindow() || mbMulti || !IsMouseMoveSelect() || !maEntryList.GetEntryCount())
return ;
tools::Rectangle aRect( Point(), GetOutputSizePixel() );
if ( !aRect.Contains( rMEvt.GetPosPixel() ) )
return ;
// prevent initial mouse move event from selecting an entry when the listbox is started
if (mnLastPosPixel == Point())
{
mnLastPosPixel = rMEvt.GetPosPixel();
return ;
}
if (mnLastPosPixel == rMEvt.GetPosPixel())
return ;
if ( IsMouseMoveSelect() )
{
sal_Int32 nSelect = GetEntryPosForPoint( rMEvt.GetPosPixel() );
if ( nSelect == LISTBOX_ENTRY_NOTFOUND )
nSelect = maEntryList.GetEntryCount() - 1;
nSelect = std::min( nSelect, GetLastVisibleEntry() );
nSelect = std::min( nSelect, static_cast <sal_Int32>( maEntryList.GetEntryCount() - 1 ) );
// Select only visible Entries with MouseMove, otherwise Tracking...
if ( IsVisible( nSelect ) &&
maEntryList.IsEntrySelectable( nSelect ) &&
( ( nSelect != mnCurrentPos ) || !GetEntryList().GetSelectedEntryCount() || ( nSelect != GetEntryList().GetSelectedEntryPos( 0 ) ) ) )
{
mbTrackingSelect = true ;
if ( SelectEntries( nSelect, LET_TRACKING ) )
{
// When list box selection change by mouse move, notify
// VclEventId::ListboxSelect vcl event.
maListItemSelectHdl.Call(nullptr);
}
mbTrackingSelect = false ;
}
}
// if the DD button was pressed and someone moved into the ListBox
// with the mouse button pressed...
if ( rMEvt.IsLeft() && !rMEvt.IsSynthetic() )
{
if ( !mbMulti && GetEntryList().GetSelectedEntryCount() )
mnTrackingSaveSelection = GetEntryList().GetSelectedEntryPos( 0 );
else
mnTrackingSaveSelection = LISTBOX_ENTRY_NOTFOUND;
StartTracking( StartTrackingFlags::ScrollRepeat );
}
}
void ImplListBoxWindow::DeselectAll()
{
while ( GetEntryList().GetSelectedEntryCount() )
{
sal_Int32 nS = GetEntryList().GetSelectedEntryPos( 0 );
SelectEntry( nS, false );
}
}
void ImplListBoxWindow::SelectEntry( sal_Int32 nPos, bool bSelect )
{
if ( (maEntryList.IsEntryPosSelected( nPos ) == bSelect) || !maEntryList.IsEntrySelectable( nPos ) )
return ;
ImplHideFocusRect();
if ( bSelect )
{
if ( !mbMulti )
{
// deselect the selected entry
sal_Int32 nDeselect = GetEntryList().GetSelectedEntryPos( 0 );
if ( nDeselect != LISTBOX_ENTRY_NOTFOUND )
{
//SelectEntryPos( nDeselect, false );
GetEntryList().SelectEntry( nDeselect, false );
if (IsUpdateMode() && IsReallyVisible())
Invalidate();
}
}
maEntryList.SelectEntry( nPos, true );
mnCurrentPos = nPos;
if ( ( nPos != LISTBOX_ENTRY_NOTFOUND ) && IsUpdateMode() )
{
Invalidate();
if ( !IsVisible( nPos ) )
{
ImplClearLayoutData();
sal_Int32 nVisibleEntries = GetLastVisibleEntry()-mnTop;
if ( !nVisibleEntries || !IsReallyVisible() || ( nPos < GetTopEntry() ) )
{
Resize();
ShowProminentEntry( nPos );
}
else
{
ShowProminentEntry( nPos );
}
}
}
}
else
{
maEntryList.SelectEntry( nPos, false );
Invalidate();
}
mbSelectionChanged = true ;
}
bool ImplListBoxWindow::SelectEntries( sal_Int32 nSelect, LB_EVENT_TYPE eLET, bool bShift, bool bCtrl, bool bSelectPosChange /*=FALSE*/ )
{
bool bSelectionChanged = false ;
if ( IsEnabled() && maEntryList.IsEntrySelectable( nSelect ) )
{
bool bFocusChanged = false ;
// here (Single-ListBox) only one entry can be deselected
if ( !mbMulti )
{
sal_Int32 nDeselect = maEntryList.GetSelectedEntryPos( 0 );
if ( nSelect != nDeselect )
{
SelectEntry( nSelect, true );
maEntryList.SetLastSelected( nSelect );
bFocusChanged = true ;
bSelectionChanged = true ;
}
}
// MultiListBox without Modifier
else if ( mbSimpleMode && !bCtrl && !bShift )
{
sal_Int32 nEntryCount = maEntryList.GetEntryCount();
for ( sal_Int32 nPos = 0; nPos < nEntryCount; nPos++ )
{
bool bSelect = nPos == nSelect;
if ( maEntryList.IsEntryPosSelected( nPos ) != bSelect )
{
SelectEntry( nPos, bSelect );
bFocusChanged = true ;
bSelectionChanged = true ;
}
}
maEntryList.SetLastSelected( nSelect );
maEntryList.SetSelectionAnchor( nSelect );
}
// MultiListBox only with CTRL/SHIFT or not in SimpleMode
else if ( ( !mbSimpleMode /* && !bShift */ ) || ( mbSimpleMode && ( bCtrl || bShift ) ) )
{
// Space for selection change
if ( !bShift && ( ( eLET == LET_KEYSPACE ) || ( eLET == LET_MBDOWN ) ) )
{
bool bSelect = !maEntryList.IsEntryPosSelected( nSelect );
SelectEntry( nSelect, bSelect );
maEntryList.SetLastSelected( nSelect );
maEntryList.SetSelectionAnchor( nSelect );
if ( !maEntryList.IsEntryPosSelected( nSelect ) )
maEntryList.SetSelectionAnchor( LISTBOX_ENTRY_NOTFOUND );
bFocusChanged = true ;
bSelectionChanged = true ;
}
else if ( ( ( eLET == LET_TRACKING ) && ( nSelect != mnCurrentPos ) ) ||
( bShift && ( ( eLET == LET_KEYMOVE ) || ( eLET == LET_MBDOWN ) ) ) )
{
mnCurrentPos = nSelect;
bFocusChanged = true ;
sal_Int32 nAnchor = maEntryList.GetSelectionAnchor();
if ( ( nAnchor == LISTBOX_ENTRY_NOTFOUND ) && maEntryList.GetSelectedEntryCount() )
{
nAnchor = maEntryList.GetSelectedEntryPos( maEntryList.GetSelectedEntryCount() - 1 );
}
if ( nAnchor != LISTBOX_ENTRY_NOTFOUND )
{
// All entries from Anchor to nSelect have to be selected
sal_Int32 nStart = std::min( nSelect, nAnchor );
sal_Int32 nEnd = std::max( nSelect, nAnchor );
for ( sal_Int32 n = nStart; n <= nEnd; n++ )
{
if ( !maEntryList.IsEntryPosSelected( n ) )
{
SelectEntry( n, true );
bSelectionChanged = true ;
}
}
// if appropriate some more has to be deselected...
sal_Int32 nLast = maEntryList.GetLastSelected();
if ( nLast != LISTBOX_ENTRY_NOTFOUND )
{
if ( ( nLast > nSelect ) && ( nLast > nAnchor ) )
{
for ( sal_Int32 n = nSelect+1; n <= nLast; n++ )
{
if ( maEntryList.IsEntryPosSelected( n ) )
{
SelectEntry( n, false );
bSelectionChanged = true ;
}
}
}
else if ( ( nLast < nSelect ) && ( nLast < nAnchor ) )
{
for ( sal_Int32 n = nLast; n < nSelect; n++ )
{
if ( maEntryList.IsEntryPosSelected( n ) )
{
SelectEntry( n, false );
bSelectionChanged = true ;
}
}
}
}
maEntryList.SetLastSelected( nSelect );
}
}
else if ( eLET != LET_TRACKING )
{
ImplHideFocusRect();
Invalidate();
bFocusChanged = true ;
}
}
else if ( bShift )
{
bFocusChanged = true ;
}
if ( bSelectionChanged )
mbSelectionChanged = true ;
if ( bFocusChanged )
{
tools::Long nHeightDiff = maEntryList.GetAddedHeight( nSelect, mnTop );
maFocusRect.SetPos( Point( 0, nHeightDiff ) );
Size aSz( maFocusRect.GetWidth(),
maEntryList.GetEntryHeight( nSelect ) );
maFocusRect.SetSize( aSz );
if ( HasFocus() )
ImplShowFocusRect();
if (bSelectPosChange)
{
maFocusHdl.Call(nSelect);
}
}
ImplClearLayoutData();
}
return bSelectionChanged;
}
void ImplListBoxWindow::Tracking( const TrackingEvent& rTEvt )
{
tools::Rectangle aRect( Point(), GetOutputSizePixel() );
bool bInside = aRect.Contains( rTEvt.GetMouseEvent().GetPosPixel() );
if ( rTEvt.IsTrackingCanceled() || rTEvt.IsTrackingEnded() ) // MouseButtonUp
{
if ( bInside && !rTEvt.IsTrackingCanceled() )
{
mnSelectModifier = rTEvt.GetMouseEvent().GetModifier();
ImplCallSelect();
}
else
{
maCancelHdl.Call( nullptr );
if ( !mbMulti )
{
mbTrackingSelect = true ;
SelectEntry( mnTrackingSaveSelection, true );
mbTrackingSelect = false ;
if ( mnTrackingSaveSelection != LISTBOX_ENTRY_NOTFOUND )
{
tools::Long nHeightDiff = maEntryList.GetAddedHeight( mnCurrentPos, mnTop );
maFocusRect.SetPos( Point( 0, nHeightDiff ) );
Size aSz( maFocusRect.GetWidth(),
maEntryList.GetEntryHeight( mnCurrentPos ) );
maFocusRect.SetSize( aSz );
ImplShowFocusRect();
}
}
}
mbTrack = false ;
}
else
{
bool bTrackOrQuickClick = mbTrack;
if ( !mbTrack )
{
if ( bInside )
{
mbTrack = true ;
}
// this case only happens, if the mouse button is pressed very briefly
if ( rTEvt.IsTrackingEnded() && mbTrack )
{
bTrackOrQuickClick = true ;
mbTrack = false ;
}
}
if ( bTrackOrQuickClick )
{
MouseEvent aMEvt = rTEvt.GetMouseEvent();
Point aPt( aMEvt.GetPosPixel() );
bool bShift = aMEvt.IsShift();
bool bCtrl = aMEvt.IsMod1();
sal_Int32 nSelect = LISTBOX_ENTRY_NOTFOUND;
if ( aPt.Y() < 0 )
{
if ( mnCurrentPos != LISTBOX_ENTRY_NOTFOUND )
{
nSelect = mnCurrentPos ? ( mnCurrentPos - 1 ) : 0;
if ( nSelect < mnTop )
SetTopEntry( mnTop-1 );
}
}
else if ( aPt.Y() > GetOutputSizePixel().Height() )
{
if ( mnCurrentPos != LISTBOX_ENTRY_NOTFOUND )
{
nSelect = std::min( static_cast <sal_Int32>(mnCurrentPos+1), static_cast <sal_Int32>(maEntryList.GetEntryCount()-1) );
if ( nSelect >= GetLastVisibleEntry() )
SetTopEntry( mnTop+1 );
}
}
else
{
nSelect = static_cast <sal_Int32>( ( aPt.Y() + gnBorder ) / mnMaxHeight ) + mnTop;
nSelect = std::min( nSelect, GetLastVisibleEntry() );
nSelect = std::min( nSelect, static_cast <sal_Int32>( maEntryList.GetEntryCount() - 1 ) );
}
if ( bInside )
{
if ( ( nSelect != mnCurrentPos ) || !GetEntryList().GetSelectedEntryCount() )
{
mbTrackingSelect = true ;
SelectEntries(nSelect, LET_TRACKING, bShift, bCtrl);
mbTrackingSelect = false ;
}
}
else
{
if ( !mbMulti && GetEntryList().GetSelectedEntryCount() )
{
mbTrackingSelect = true ;
SelectEntry( GetEntryList().GetSelectedEntryPos( 0 ), false );
mbTrackingSelect = false ;
}
}
mnCurrentPos = nSelect;
if ( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
{
ImplHideFocusRect();
}
else
{
tools::Long nHeightDiff = maEntryList.GetAddedHeight( mnCurrentPos, mnTop );
maFocusRect.SetPos( Point( 0, nHeightDiff ) );
Size aSz( maFocusRect.GetWidth(), maEntryList.GetEntryHeight( mnCurrentPos ) );
maFocusRect.SetSize( aSz );
ImplShowFocusRect();
}
}
}
}
void ImplListBoxWindow::KeyInput( const KeyEvent& rKEvt )
{
if ( !ProcessKeyInput( rKEvt ) )
Control::KeyInput( rKEvt );
}
bool ImplListBoxWindow::ProcessKeyInput( const KeyEvent& rKEvt )
{
// entry to be selected
sal_Int32 nSelect = LISTBOX_ENTRY_NOTFOUND;
LB_EVENT_TYPE eLET = LET_KEYMOVE;
vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
bool bShift = aKeyCode.IsShift();
bool bCtrl = aKeyCode.IsMod1() || aKeyCode.IsMod3();
bool bMod2 = aKeyCode.IsMod2();
bool bDone = false ;
bool bHandleKey = false ;
switch ( aKeyCode.GetCode() )
{
case KEY_UP:
{
if ( IsReadOnly() )
{
if ( GetTopEntry() )
SetTopEntry( GetTopEntry()-1 );
}
else if ( !bMod2 )
{
if ( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
{
nSelect = maEntryList.FindFirstSelectable( 0 );
}
else if ( mnCurrentPos )
{
// search first selectable above the current position
nSelect = maEntryList.FindFirstSelectable( mnCurrentPos - 1, false );
}
if ( ( nSelect != LISTBOX_ENTRY_NOTFOUND ) && ( nSelect < mnTop ) )
SetTopEntry( mnTop-1 );
bDone = true ;
}
maQuickSelectionEngine.Reset();
}
break ;
case KEY_DOWN:
{
if ( IsReadOnly() )
{
SetTopEntry( GetTopEntry()+1 );
}
else if ( !bMod2 )
{
if ( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
{
nSelect = maEntryList.FindFirstSelectable( 0 );
}
else if ( (mnCurrentPos+1) < maEntryList.GetEntryCount() )
{
// search first selectable below the current position
nSelect = maEntryList.FindFirstSelectable( mnCurrentPos + 1 );
}
if ( ( nSelect != LISTBOX_ENTRY_NOTFOUND ) && ( nSelect >= GetLastVisibleEntry() ) )
SetTopEntry( mnTop+1 );
bDone = true ;
}
maQuickSelectionEngine.Reset();
}
break ;
case KEY_PAGEUP:
{
if ( IsReadOnly() )
{
sal_Int32 nCurVis = GetLastVisibleEntry() - mnTop +1;
SetTopEntry( ( mnTop > nCurVis ) ?
(mnTop-nCurVis) : 0 );
}
else if ( !bCtrl && !bMod2 )
{
if ( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
{
nSelect = maEntryList.FindFirstSelectable( 0 );
}
else if ( mnCurrentPos )
{
if ( mnCurrentPos == mnTop )
{
sal_Int32 nCurVis = GetLastVisibleEntry() - mnTop +1;
SetTopEntry( ( mnTop > nCurVis ) ? ( mnTop-nCurVis+1 ) : 0 );
}
// find first selectable starting from mnTop looking forward
nSelect = maEntryList.FindFirstSelectable( mnTop );
}
bDone = true ;
}
maQuickSelectionEngine.Reset();
}
break ;
case KEY_PAGEDOWN:
{
if ( IsReadOnly() )
{
SetTopEntry( GetLastVisibleEntry() );
}
else if ( !bCtrl && !bMod2 )
{
if ( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
{
nSelect = maEntryList.FindFirstSelectable( 0 );
}
else if ( (mnCurrentPos+1) < maEntryList.GetEntryCount() )
{
sal_Int32 nCount = maEntryList.GetEntryCount();
sal_Int32 nCurVis = GetLastVisibleEntry() - mnTop;
sal_Int32 nTmp = std::min( nCurVis, nCount );
nTmp += mnTop - 1;
if ( mnCurrentPos == nTmp && mnCurrentPos != nCount - 1 )
{
tools::Long nTmp2 = std::min( static_cast <tools::Long >(nCount-nCurVis), static_cast <tools::Long >(static_cast <tools::Long >(mnTop)+static_cast <tools::Long >(nCurVis)-1) );
nTmp2 = std::max( tools::Long (0) , nTmp2 );
nTmp = static_cast <sal_Int32>(nTmp2+(nCurVis-1) );
SetTopEntry( static_cast <sal_Int32>(nTmp2) );
}
// find first selectable starting from nTmp looking backwards
nSelect = maEntryList.FindFirstSelectable( nTmp, false );
}
bDone = true ;
}
maQuickSelectionEngine.Reset();
}
break ;
case KEY_HOME:
{
if ( IsReadOnly() )
{
SetTopEntry( 0 );
}
else if ( !bCtrl && !bMod2 && mnCurrentPos )
{
nSelect = maEntryList.FindFirstSelectable( maEntryList.GetEntryCount() ? 0 : LISTBOX_ENTRY_NOTFOUND );
if ( mnTop != 0 )
SetTopEntry( 0 );
bDone = true ;
}
maQuickSelectionEngine.Reset();
}
break ;
case KEY_END:
{
if ( IsReadOnly() )
{
SetTopEntry( 0xFFFF );
}
else if ( !bCtrl && !bMod2 )
{
if ( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
{
nSelect = maEntryList.FindFirstSelectable( 0 );
}
else if ( (mnCurrentPos+1) < maEntryList.GetEntryCount() )
{
sal_Int32 nCount = maEntryList.GetEntryCount();
nSelect = maEntryList.FindFirstSelectable( nCount - 1, false );
sal_Int32 nCurVis = GetLastVisibleEntry() - mnTop + 1;
if ( nCount > nCurVis )
SetTopEntry( nCount - nCurVis );
}
bDone = true ;
}
maQuickSelectionEngine.Reset();
}
break ;
case KEY_LEFT:
{
if ( !bCtrl && !bMod2 )
{
ScrollHorz( -HORZ_SCROLL );
bDone = true ;
}
maQuickSelectionEngine.Reset();
}
break ;
case KEY_RIGHT:
{
if ( !bCtrl && !bMod2 )
{
ScrollHorz( HORZ_SCROLL );
bDone = true ;
}
maQuickSelectionEngine.Reset();
}
break ;
case KEY_RETURN:
{
if ( !bMod2 && !IsReadOnly() )
{
mnSelectModifier = rKEvt.GetKeyCode().GetModifier();
ImplCallSelect();
bDone = false ; // do not catch RETURN
}
maQuickSelectionEngine.Reset();
}
break ;
case KEY_SPACE:
{
if ( !bMod2 && !IsReadOnly() )
{
if ( mbMulti && ( !mbSimpleMode || ( mbSimpleMode && bCtrl && !bShift ) ) )
{
nSelect = mnCurrentPos;
eLET = LET_KEYSPACE;
}
bDone = true ;
}
bHandleKey = true ;
}
break ;
case KEY_A:
{
if ( bCtrl && mbMulti )
{
// paint only once
bool bUpdates = IsUpdateMode();
SetUpdateMode( false );
sal_Int32 nEntryCount = maEntryList.GetEntryCount();
for ( sal_Int32 i = 0; i < nEntryCount; i++ )
SelectEntry( i, true );
// tdf#97066 - Update selected items
ImplCallSelect();
// restore update mode
SetUpdateMode( bUpdates );
Invalidate();
maQuickSelectionEngine.Reset();
bDone = true ;
}
else
{
bHandleKey = true ;
}
}
break ;
default :
bHandleKey = true ;
break ;
}
if (bHandleKey && !IsReadOnly())
{
bDone = maQuickSelectionEngine.HandleKeyEvent( rKEvt );
}
if ( ( nSelect != LISTBOX_ENTRY_NOTFOUND )
&& ( ( !maEntryList.IsEntryPosSelected( nSelect ) )
|| ( eLET == LET_KEYSPACE )
)
)
{
SAL_WARN_IF( maEntryList.IsEntryPosSelected( nSelect ) && !mbMulti, "vcl" , "ImplListBox: Selecting same Entry" );
sal_Int32 nCount = maEntryList.GetEntryCount();
if (nSelect >= nCount)
nSelect = nCount ? nCount-1 : LISTBOX_ENTRY_NOTFOUND;
bool bCurPosChange = (mnCurrentPos != nSelect);
mnCurrentPos = nSelect;
if (SelectEntries( nSelect, eLET, bShift, bCtrl, bCurPosChange))
{
// tdf#129043 Correctly deliver events when changing values with arrow keys in combobox
if (mbIsDropdown && IsReallyVisible())
mbTravelSelect = true ;
mnSelectModifier = rKEvt.GetKeyCode().GetModifier();
ImplCallSelect();
mbTravelSelect = false ;
}
}
return bDone;
}
namespace
{
vcl::StringEntryIdentifier lcl_getEntry( const ImplEntryList& _rList, sal_Int32 _nPos, OUString& _out_entryText )
{
OSL_PRECOND( ( _nPos != LISTBOX_ENTRY_NOTFOUND ), "lcl_getEntry: invalid position!" );
sal_Int32 nEntryCount( _rList.GetEntryCount() );
if ( _nPos >= nEntryCount )
_nPos = 0;
_out_entryText = _rList.GetEntryText( _nPos );
// vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based
// => normalize
return reinterpret_cast < vcl::StringEntryIdentifier >( _nPos + sal_IntPtr(1) );
}
sal_Int32 lcl_getEntryPos( vcl::StringEntryIdentifier _entry )
{
// our pos is 0-based, but StringEntryIdentifier does not allow for a NULL
return static_cast < sal_Int32 >( reinterpret_cast < sal_Int64 >( _entry ) ) - 1;
}
}
vcl::StringEntryIdentifier ImplListBoxWindow::CurrentEntry( OUString& _out_entryText ) const
{
return lcl_getEntry( GetEntryList(), ( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND ) ? 0 : mnCurrentPos, _out_entryText );
}
vcl::StringEntryIdentifier ImplListBoxWindow::NextEntry( vcl::StringEntryIdentifier _currentEntry, OUString& _out_entryText ) const
{
sal_Int32 nNextPos = lcl_getEntryPos( _currentEntry ) + 1;
return lcl_getEntry( GetEntryList(), nNextPos, _out_entryText );
}
void ImplListBoxWindow::SelectEntry( vcl::StringEntryIdentifier _entry )
{
sal_Int32 nSelect = lcl_getEntryPos( _entry );
if ( maEntryList.IsEntryPosSelected( nSelect ) )
{
// ignore that. This method is a callback from the QuickSelectionEngine, which means the user attempted
// to select the given entry by typing its starting letters. No need to act.
return ;
}
// normalize
OSL_ENSURE( nSelect < maEntryList.GetEntryCount(), "ImplListBoxWindow::SelectEntry: how that?" );
sal_Int32 nCount = maEntryList.GetEntryCount();
if (nSelect >= nCount)
nSelect = nCount ? nCount-1 : LISTBOX_ENTRY_NOTFOUND;
// make visible
ShowProminentEntry( nSelect );
// actually select
mnCurrentPos = nSelect;
if ( SelectEntries( nSelect, LET_KEYMOVE ) )
{
mbTravelSelect = true ;
mnSelectModifier = 0;
ImplCallSelect();
mbTravelSelect = false ;
}
}
void ImplListBoxWindow::ImplPaint(vcl::RenderContext& rRenderContext, sal_Int32 nPos)
{
const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
const ImplEntryType* pEntry = maEntryList.GetEntryPtr( nPos );
if (!pEntry)
return ;
tools::Long nWidth = GetOutputSizePixel().Width();
tools::Long nY = maEntryList.GetAddedHeight(nPos, mnTop);
tools::Rectangle aRect(Point(0, nY), Size(nWidth, pEntry->getHeightWithMargin()));
bool bSelected = maEntryList.IsEntryPosSelected(nPos);
if (bSelected)
{
rRenderContext.SetTextColor(!IsEnabled() ? rStyleSettings.GetDisableColor() : rStyleSettings.GetListBoxWindowHighlightTextColor());
rRenderContext.SetFillColor(rStyleSettings.GetListBoxWindowHighlightColor());
rRenderContext.SetLineColor();
rRenderContext.DrawRect(aRect);
}
else
{
ApplySettings(rRenderContext);
if (!IsEnabled())
rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
}
rRenderContext.SetTextFillColor();
if (IsUserDrawEnabled())
{
mbInUserDraw = true ;
mnUserDrawEntry = nPos;
aRect.AdjustLeft( -mnLeft );
if (nPos < GetEntryList().GetMRUCount())
nPos = GetEntryList().FindEntry(GetEntryList().GetEntryText(nPos));
nPos = nPos - GetEntryList().GetMRUCount();
UserDrawEvent aUDEvt(&rRenderContext, aRect, nPos, bSelected);
maUserDrawHdl.Call( &aUDEvt );
mbInUserDraw = false ;
}
else
{
DrawEntry(rRenderContext, nPos, true , true );
}
}
void ImplListBoxWindow::DrawEntry(vcl::RenderContext& rRenderContext, sal_Int32 nPos, bool bDrawImage, bool bDrawText)
{
const ImplEntryType* pEntry = maEntryList.GetEntryPtr(nPos);
if (!pEntry)
return ;
tools::Long nEntryHeight = pEntry->getHeightWithMargin();
// when changing this function don't forget to adjust ImplWin::DrawEntry()
if (mbInUserDraw)
nPos = mnUserDrawEntry; // real entry, not the matching entry from MRU
tools::Long nY = maEntryList.GetAddedHeight(nPos, mnTop);
if (bDrawImage && maEntryList.HasImages())
{
Image aImage = maEntryList.GetEntryImage(nPos);
if (!!aImage)
{
Size aImgSz = aImage.GetSizePixel();
Point aPtImg(gnBorder - mnLeft, nY + ((nEntryHeight - aImgSz.Height()) / 2));
if (!IsZoom())
{
rRenderContext.DrawImage(aPtImg, aImage);
}
else
{
aImgSz.setWidth( CalcZoom(aImgSz.Width()) );
aImgSz.setHeight( CalcZoom(aImgSz.Height()) );
rRenderContext.DrawImage(aPtImg, aImgSz, aImage);
}
const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
const sal_uInt16 nEdgeBlendingPercent(GetEdgeBlending() ? rStyleSettings.GetEdgeBlending() : 0);
if (nEdgeBlendingPercent && aImgSz.Width() && aImgSz.Height())
{
const Color& rTopLeft(rStyleSettings.GetEdgeBlendingTopLeftColor());
const Color& rBottomRight(rStyleSettings.GetEdgeBlendingBottomRightColor());
const sal_uInt8 nAlpha(255 - ((nEdgeBlendingPercent * 255) / 100));
const BitmapEx aBlendFrame(createAlphaBlendFrame(aImgSz, nAlpha, rTopLeft, rBottomRight));
if (!aBlendFrame.IsEmpty())
{
rRenderContext.DrawBitmapEx(aPtImg, aBlendFrame);
}
}
}
}
if (bDrawText)
{
OUString aStr(maEntryList.GetEntryText(nPos));
if (!aStr.isEmpty())
{
tools::Long nMaxWidth = std::max(mnMaxWidth, GetOutputSizePixel().Width() - 2 * gnBorder);
// a multiline entry should only be as wide as the window
if (pEntry->mnFlags & ListBoxEntryFlags::MultiLine)
nMaxWidth = GetOutputSizePixel().Width() - 2 * gnBorder;
tools::Rectangle aTextRect(Point(gnBorder - mnLeft, nY),
Size(nMaxWidth, nEntryHeight));
if (maEntryList.HasEntryImage(nPos) || IsUserDrawEnabled())
{
tools::Long nImageWidth = std::max(mnMaxImgWidth, maUserItemSize.Width());
aTextRect.AdjustLeft(nImageWidth + IMG_TXT_DISTANCE );
}
DrawTextFlags nDrawStyle = ImplGetTextStyle();
if (pEntry->mnFlags & ListBoxEntryFlags::MultiLine)
nDrawStyle |= MULTILINE_ENTRY_DRAW_FLAGS;
if (pEntry->mnFlags & ListBoxEntryFlags::DrawDisabled)
nDrawStyle |= DrawTextFlags::Disable;
rRenderContext.DrawText(aTextRect, aStr, nDrawStyle);
}
}
if ( !maSeparators.empty() && ( isSeparator(nPos) || isSeparator(nPos-1) ) )
{
Color aOldLineColor(rRenderContext.GetLineColor());
rRenderContext.SetLineColor((GetBackground() != COL_LIGHTGRAY) ? COL_LIGHTGRAY : COL_GRAY);
Point aStartPos(0, nY);
if (isSeparator(nPos))
aStartPos.AdjustY(pEntry->getHeightWithMargin() - 1 );
Point aEndPos(aStartPos);
aEndPos.setX( GetOutputSizePixel().Width() );
rRenderContext.DrawLine(aStartPos, aEndPos);
rRenderContext.SetLineColor(aOldLineColor);
}
}
void ImplListBoxWindow::FillLayoutData() const
{
mxLayoutData.emplace();
const_cast <ImplListBoxWindow*>(this )->Invalidate(tools::Rectangle(Point(0, 0), GetOutDev()->GetOutputSize()));
}
void ImplListBoxWindow::ImplDoPaint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
sal_Int32 nCount = maEntryList.GetEntryCount();
bool bShowFocusRect = mbHasFocusRect;
if (mbHasFocusRect)
ImplHideFocusRect();
tools::Long nY = 0; // + gnBorder;
tools::Long nHeight = GetOutputSizePixel().Height();// - mnMaxHeight + gnBorder;
for (sal_Int32 i = mnTop; i < nCount && nY < nHeight + mnMaxHeight; i++)
{
const ImplEntryType* pEntry = maEntryList.GetEntryPtr(i);
tools::Long nEntryHeight = pEntry->getHeightWithMargin();
if (nY + nEntryHeight >= rRect.Top() &&
nY <= rRect.Bottom() + mnMaxHeight)
{
ImplPaint(rRenderContext, i);
}
nY += nEntryHeight;
}
tools::Long nHeightDiff = maEntryList.GetAddedHeight(mnCurrentPos, mnTop);
maFocusRect.SetPos(Point(0, nHeightDiff));
Size aSz(maFocusRect.GetWidth(), maEntryList.GetEntryHeight(mnCurrentPos));
maFocusRect.SetSize(aSz);
if (HasFocus() && bShowFocusRect)
ImplShowFocusRect();
}
void ImplListBoxWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
if (SupportsDoubleBuffering())
{
// This widget is explicitly double-buffered, so avoid partial paints.
tools::Rectangle aRect(Point(0, 0), GetOutputSizePixel());
ImplDoPaint(rRenderContext, aRect);
}
else
ImplDoPaint(rRenderContext, rRect);
}
sal_uInt16 ImplListBoxWindow::GetDisplayLineCount() const
{
// FIXME: ListBoxEntryFlags::MultiLine
const sal_Int32 nCount = maEntryList.GetEntryCount()-mnTop;
tools::Long nHeight = GetOutputSizePixel().Height();// - mnMaxHeight + gnBorder;
sal_uInt16 nEntries = static_cast < sal_uInt16 >( ( nHeight + mnMaxHeight - 1 ) / mnMaxHeight );
if ( nEntries > nCount )
nEntries = static_cast <sal_uInt16>(nCount);
return nEntries;
}
void ImplListBoxWindow::Resize()
{
Control::Resize();
bool bShowFocusRect = mbHasFocusRect;
if ( bShowFocusRect )
ImplHideFocusRect();
if ( mnCurrentPos != LISTBOX_ENTRY_NOTFOUND )
{
Size aSz( GetOutputSizePixel().Width(), maEntryList.GetEntryHeight( mnCurrentPos ) );
maFocusRect.SetSize( aSz );
}
if ( bShowFocusRect )
ImplShowFocusRect();
ImplClearLayoutData();
}
void ImplListBoxWindow::GetFocus()
{
sal_Int32 nPos = mnCurrentPos;
if ( nPos == LISTBOX_ENTRY_NOTFOUND )
nPos = 0;
tools::Long nHeightDiff = maEntryList.GetAddedHeight( nPos, mnTop );
maFocusRect.SetPos( Point( 0, nHeightDiff ) );
Size aSz( maFocusRect.GetWidth(), maEntryList.GetEntryHeight( nPos ) );
maFocusRect.SetSize( aSz );
ImplShowFocusRect();
Control::GetFocus();
}
void ImplListBoxWindow::LoseFocus()
{
ImplHideFocusRect();
Control::LoseFocus();
}
void ImplListBoxWindow::SetTopEntry( sal_Int32 nTop )
{
if ( maEntryList.GetEntryCount() == 0 )
return ;
tools::Long nWHeight = PixelToLogic( GetSizePixel() ).Height();
sal_Int32 nLastEntry = maEntryList.GetEntryCount()-1;
if ( nTop > nLastEntry )
nTop = nLastEntry;
const ImplEntryType* pLast = maEntryList.GetEntryPtr( nLastEntry );
while ( nTop > 0 && maEntryList.GetAddedHeight( nLastEntry, nTop-1 ) + pLast->getHeightWithMargin() <= nWHeight )
nTop--;
if ( nTop == mnTop )
return ;
ImplClearLayoutData();
tools::Long nDiff = maEntryList.GetAddedHeight( mnTop, nTop );
PaintImmediately();
ImplHideFocusRect();
mnTop = nTop;
Scroll( 0, nDiff );
PaintImmediately();
if ( HasFocus() )
ImplShowFocusRect();
maScrollHdl.Call( this );
}
void ImplListBoxWindow::ShowProminentEntry( sal_Int32 nEntryPos )
{
sal_Int32 nPos = nEntryPos;
auto nWHeight = PixelToLogic( GetSizePixel() ).Height();
while ( nEntryPos > 0 && maEntryList.GetAddedHeight( nPos+1, nEntryPos ) < nWHeight/2 )
nEntryPos--;
SetTopEntry( nEntryPos );
}
void ImplListBoxWindow::SetLeftIndent( tools::Long n )
{
ScrollHorz( n - mnLeft );
}
void ImplListBoxWindow::ScrollHorz( tools::Long n )
{
tools::Long nDiff = 0;
if ( n > 0 )
{
tools::Long nWidth = GetOutputSizePixel().Width();
if ( ( mnMaxWidth - mnLeft + n ) > nWidth )
nDiff = n;
}
else if ( n < 0 )
{
if ( mnLeft )
{
tools::Long nAbs = -n;
nDiff = - std::min( mnLeft, nAbs );
}
}
if ( nDiff )
{
ImplClearLayoutData();
mnLeft = sal::static_int_cast<sal_uInt16>(mnLeft + nDiff);
PaintImmediately();
ImplHideFocusRect();
Scroll( -nDiff, 0 );
PaintImmediately();
if ( HasFocus() )
ImplShowFocusRect();
maScrollHdl.Call( this );
}
}
void ImplListBoxWindow::SetSeparatorPos( sal_Int32 n )
{
maSeparators.clear();
if ( n != LISTBOX_ENTRY_NOTFOUND )
{
maSeparators.insert( n );
}
}
sal_Int32 ImplListBoxWindow::GetSeparatorPos() const
{
if (!maSeparators.empty())
return *(maSeparators.begin());
else
return LISTBOX_ENTRY_NOTFOUND;
}
bool ImplListBoxWindow::isSeparator( const sal_Int32 &n) const
{
return maSeparators.find(n) != maSeparators.end();
}
Size ImplListBoxWindow::CalcSize(sal_Int32 nMaxLines) const
{
// FIXME: ListBoxEntryFlags::MultiLine
Size aSz;
aSz.setHeight(nMaxLines * GetEntryHeightWithMargin());
aSz.setWidth( mnMaxWidth + 2*gnBorder );
return aSz;
}
tools::Rectangle ImplListBoxWindow::GetBoundingRectangle( sal_Int32 nItem ) const
{
const ImplEntryType* pEntry = maEntryList.GetEntryPtr( nItem );
Size aSz( GetSizePixel().Width(), pEntry ? pEntry->getHeightWithMargin() : GetEntryHeightWithMargin() );
tools::Long nY = maEntryList.GetAddedHeight( nItem, GetTopEntry() ) + GetEntryList().GetMRUCount()*GetEntryHeightWithMargin();
tools::Rectangle aRect( Point( 0, nY ), aSz );
return aRect;
}
void ImplListBoxWindow::StateChanged( StateChangedType nType )
{
Control::StateChanged( nType );
if ( nType == StateChangedType::Zoom )
{
ApplySettings(*GetOutDev());
ImplCalcMetrics();
Invalidate();
}
else if ( nType == StateChangedType::UpdateMode )
{
if ( IsUpdateMode() && IsReallyVisible() )
Invalidate();
}
else if ( nType == StateChangedType::ControlFont )
{
ApplySettings(*GetOutDev());
ImplCalcMetrics();
Invalidate();
}
else if ( nType == StateChangedType::ControlForeground )
{
ApplySettings(*GetOutDev());
Invalidate();
}
else if ( nType == StateChangedType::ControlBackground )
{
ApplySettings(*GetOutDev());
Invalidate();
}
else if ( nType == StateChangedType::Enable )
{
Invalidate();
}
ImplClearLayoutData();
}
void ImplListBoxWindow::DataChanged( const DataChangedEvent& rDCEvt )
{
Control::DataChanged( rDCEvt );
if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
(rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
(rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
{
ImplClearLayoutData();
ApplySettings(*GetOutDev());
ImplCalcMetrics();
Invalidate();
}
}
DrawTextFlags ImplListBoxWindow::ImplGetTextStyle() const
{
DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
if (maEntryList.HasImages())
nTextStyle |= DrawTextFlags::Left;
else if (mbCenter)
nTextStyle |= DrawTextFlags::Center;
else if (mbRight)
nTextStyle |= DrawTextFlags::Right;
else
nTextStyle |= DrawTextFlags::Left;
return nTextStyle;
}
ImplListBox::ImplListBox( vcl::Window* pParent, WinBits nWinStyle ) :
Control( pParent, nWinStyle ),
maLBWindow(VclPtr<ImplListBoxWindow>::Create( this , nWinStyle&(~WB_BORDER) ))
{
// for native widget rendering we must be able to detect this window type
SetType( WindowType::LISTBOXWINDOW );
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 C=95 H=96 G=95
¤ Dauer der Verarbeitung: 0.22 Sekunden
¤
*© Formatika GbR, Deutschland