/* -*- 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 #include #include #include #include #include #include #include #include #include #define MULTILINE_ENTRY_DRAW_FLAGS ( DrawTextFlags::WordBreak | DrawTextFlags::MultiLine | 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.clear(); } void ImplEntryList::SelectEntry( sal_Int32 nPos, bool bSelect ) { if (0 <= nPos && o3tl::make_unsigned(nPos) < maEntries.size()) { std::vector >::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(maEntries.size()); if ( !bSort || maEntries.empty()) { if (0 <= nPos && nPos < nEntriesSize) { insPos = nPos; maEntries.insert( maEntries.begin() + nPos, std::unique_ptr(pNewEntry) ); } else { insPos = nEntriesSize; maEntries.push_back(std::unique_ptr(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(pNewEntry)); } else { pTemp = GetEntry( mnMRUCount ); nComp = rSorter.compare(rStr, pTemp->maStr); if ( nComp <= 0 ) { insPos = 0; maEntries.insert(maEntries.begin(), std::unique_ptr(pNewEntry)); } else { sal_uLong nLow = mnMRUCount; sal_uLong nHigh = maEntries.size()-1; sal_Int32 nMid; // binary search do { nMid = static_cast((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(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(pNewEntry)); } } return insPos; } void ImplEntryList::RemoveEntry( sal_Int32 nPos ) { if (0 <= nPos && o3tl::make_unsigned(nPos) < maEntries.size()) { std::vector >::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(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::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(GetTextHeight()); mnMaxTxtHeight = mnTextHeight + gnBorder; mnMaxHeight = mnMaxTxtHeight; if ( maUserItemSize.Height() > mnMaxHeight ) mnMaxHeight = static_cast(maUserItemSize.Height()); if ( maUserItemSize.Width() > mnMaxWidth ) mnMaxWidth= static_cast(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 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 ) { 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 ) { 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(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(CalcZoom( aImgSz.Width() )); aMetrics.nImgHeight = static_cast(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 ); } bool bIsUserDrawEnabled = IsUserDrawEnabled(); 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; 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( 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(mnCurrentPos+1), static_cast(maEntryList.GetEntryCount()-1) ); if( nSelect >= GetLastVisibleEntry() ) SetTopEntry( mnTop+1 ); } } else { nSelect = static_cast( ( aPt.Y() + gnBorder ) / mnMaxHeight ) + mnTop; nSelect = std::min( nSelect, GetLastVisibleEntry() ); nSelect = std::min( nSelect, static_cast( 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(nCount-nCurVis), static_cast(static_cast(mnTop)+static_cast(nCurVis)-1) ); nTmp2 = std::max( tools::Long(0) , nTmp2 ); nTmp = static_cast(nTmp2+(nCurVis-1) ); SetTopEntry( static_cast(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 + 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((nEdgeBlendingPercent * 255) / 100); const BitmapEx aBlendFrame(createBlendFrame(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(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(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(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::Create( this, nWinStyle&(~WB_BORDER) )) { // for native widget rendering we must be able to detect this window type SetType( WindowType::LISTBOXWINDOW ); mpVScrollBar = VclPtr::Create( this, WB_VSCROLL | WB_DRAG ); mpHScrollBar = VclPtr::Create( this, WB_HSCROLL | WB_DRAG ); mpScrollBarBox = VclPtr::Create( this ); Link aLink( LINK( this, ImplListBox, ScrollBarHdl ) ); mpVScrollBar->SetScrollHdl( aLink ); mpHScrollBar->SetScrollHdl( aLink ); mbVScroll = false; mbHScroll = false; mbAutoHScroll = ( nWinStyle & WB_AUTOHSCROLL ); mbEdgeBlending = false; maLBWindow->SetScrollHdl( LINK( this, ImplListBox, LBWindowScrolled ) ); maLBWindow->SetMRUChangedHdl( LINK( this, ImplListBox, MRUChanged ) ); maLBWindow->SetEdgeBlending(GetEdgeBlending()); maLBWindow->Show(); } ImplListBox::~ImplListBox() { disposeOnce(); } void ImplListBox::dispose() { mpHScrollBar.disposeAndClear(); mpVScrollBar.disposeAndClear(); mpScrollBarBox.disposeAndClear(); maLBWindow.disposeAndClear(); Control::dispose(); } void ImplListBox::Clear() { maLBWindow->Clear(); if ( GetEntryList().GetMRUCount() ) { maLBWindow->GetEntryList().SetMRUCount( 0 ); maLBWindow->SetSeparatorPos( LISTBOX_ENTRY_NOTFOUND ); } mpVScrollBar->SetThumbPos( 0 ); mpHScrollBar->SetThumbPos( 0 ); CompatStateChanged( StateChangedType::Data ); } sal_Int32 ImplListBox::InsertEntry( sal_Int32 nPos, const OUString& rStr ) { ImplEntryType* pNewEntry = new ImplEntryType( rStr ); sal_Int32 nNewPos = maLBWindow->InsertEntry( nPos, pNewEntry ); CompatStateChanged( StateChangedType::Data ); return nNewPos; } sal_Int32 ImplListBox::InsertEntry( sal_Int32 nPos, const OUString& rStr, const Image& rImage ) { ImplEntryType* pNewEntry = new ImplEntryType( rStr, rImage ); sal_Int32 nNewPos = maLBWindow->InsertEntry( nPos, pNewEntry ); CompatStateChanged( StateChangedType::Data ); return nNewPos; } void ImplListBox::RemoveEntry( sal_Int32 nPos ) { maLBWindow->RemoveEntry( nPos ); CompatStateChanged( StateChangedType::Data ); } void ImplListBox::SetEntryFlags( sal_Int32 nPos, ListBoxEntryFlags nFlags ) { maLBWindow->SetEntryFlags( nPos, nFlags ); } void ImplListBox::SelectEntry( sal_Int32 nPos, bool bSelect ) { maLBWindow->SelectEntry( nPos, bSelect ); } void ImplListBox::SetNoSelection() { maLBWindow->DeselectAll(); } void ImplListBox::GetFocus() { if (maLBWindow) maLBWindow->GrabFocus(); else Control::GetFocus(); } void ImplListBox::Resize() { Control::Resize(); ImplResizeControls(); ImplCheckScrollBars(); } IMPL_LINK_NOARG(ImplListBox, MRUChanged, LinkParamNone*, void) { CompatStateChanged( StateChangedType::Data ); } IMPL_LINK_NOARG(ImplListBox, LBWindowScrolled, ImplListBoxWindow*, void) { tools::Long nSet = GetTopEntry(); if( nSet > mpVScrollBar->GetRangeMax() ) mpVScrollBar->SetRangeMax( GetEntryList().GetEntryCount() ); mpVScrollBar->SetThumbPos( GetTopEntry() ); mpHScrollBar->SetThumbPos( GetLeftIndent() ); maScrollHdl.Call( this ); } IMPL_LINK( ImplListBox, ScrollBarHdl, ScrollBar*, pSB, void ) { sal_uInt16 nPos = static_cast(pSB->GetThumbPos()); if( pSB == mpVScrollBar ) SetTopEntry( nPos ); else if( pSB == mpHScrollBar ) SetLeftIndent( nPos ); if( GetParent() ) GetParent()->Invalidate( InvalidateFlags::Update ); } void ImplListBox::ImplCheckScrollBars() { bool bArrange = false; Size aOutSz = GetOutputSizePixel(); sal_Int32 nEntries = GetEntryList().GetEntryCount(); sal_uInt16 nMaxVisEntries = static_cast(aOutSz.Height() / GetEntryHeightWithMargin()); // vertical ScrollBar if( nEntries > nMaxVisEntries ) { if( !mbVScroll ) bArrange = true; mbVScroll = true; // check of the scrolled-out region if( GetEntryList().GetSelectedEntryCount() == 1 && GetEntryList().GetSelectedEntryPos( 0 ) != LISTBOX_ENTRY_NOTFOUND ) ShowProminentEntry( GetEntryList().GetSelectedEntryPos( 0 ) ); else SetTopEntry( GetTopEntry() ); // MaxTop is being checked... } else { if( mbVScroll ) bArrange = true; mbVScroll = false; SetTopEntry( 0 ); } // horizontal ScrollBar if( mbAutoHScroll ) { tools::Long nWidth = static_cast(aOutSz.Width()); if ( mbVScroll ) nWidth -= mpVScrollBar->GetSizePixel().Width(); tools::Long nMaxWidth = GetMaxEntryWidth(); if( nWidth < nMaxWidth ) { if( !mbHScroll ) bArrange = true; mbHScroll = true; if ( !mbVScroll ) // maybe we do need one now { nMaxVisEntries = static_cast( ( aOutSz.Height() - mpHScrollBar->GetSizePixel().Height() ) / GetEntryHeightWithMargin() ); if( nEntries > nMaxVisEntries ) { bArrange = true; mbVScroll = true; // check of the scrolled-out region if( GetEntryList().GetSelectedEntryCount() == 1 && GetEntryList().GetSelectedEntryPos( 0 ) != LISTBOX_ENTRY_NOTFOUND ) ShowProminentEntry( GetEntryList().GetSelectedEntryPos( 0 ) ); else SetTopEntry( GetTopEntry() ); // MaxTop is being checked... } } // check of the scrolled-out region sal_uInt16 nMaxLI = static_cast(nMaxWidth - nWidth); if ( nMaxLI < GetLeftIndent() ) SetLeftIndent( nMaxLI ); } else { if( mbHScroll ) bArrange = true; mbHScroll = false; SetLeftIndent( 0 ); } } if( bArrange ) ImplResizeControls(); ImplInitScrollBars(); } void ImplListBox::ImplInitScrollBars() { Size aOutSz = maLBWindow->GetOutputSizePixel(); if ( mbVScroll ) { sal_Int32 nEntries = GetEntryList().GetEntryCount(); sal_uInt16 nVisEntries = static_cast(aOutSz.Height() / GetEntryHeightWithMargin()); mpVScrollBar->SetRangeMax( nEntries ); mpVScrollBar->SetVisibleSize( nVisEntries ); mpVScrollBar->SetPageSize( nVisEntries - 1 ); } if ( mbHScroll ) { mpHScrollBar->SetRangeMax( GetMaxEntryWidth() + HORZ_SCROLL ); mpHScrollBar->SetVisibleSize( static_cast(aOutSz.Width()) ); mpHScrollBar->SetLineSize( HORZ_SCROLL ); mpHScrollBar->SetPageSize( aOutSz.Width() - HORZ_SCROLL ); } } void ImplListBox::ImplResizeControls() { // Here we only position the Controls; if the Scrollbars are to be // visible is already determined in ImplCheckScrollBars Size aOutSz = GetOutputSizePixel(); tools::Long nSBWidth = GetSettings().GetStyleSettings().GetScrollBarSize(); nSBWidth = CalcZoom( nSBWidth ); Size aInnerSz( aOutSz ); if ( mbVScroll ) aInnerSz.AdjustWidth( -nSBWidth ); if ( mbHScroll ) aInnerSz.AdjustHeight( -nSBWidth ); Point aWinPos( 0, 0 ); maLBWindow->SetPosSizePixel( aWinPos, aInnerSz ); // ScrollBarBox if( mbVScroll && mbHScroll ) { Point aBoxPos( aInnerSz.Width(), aInnerSz.Height() ); mpScrollBarBox->SetPosSizePixel( aBoxPos, Size( nSBWidth, nSBWidth ) ); mpScrollBarBox->Show(); } else { mpScrollBarBox->Hide(); } // vertical ScrollBar if( mbVScroll ) { // Scrollbar on left or right side? Point aVPos( aOutSz.Width() - nSBWidth, 0 ); mpVScrollBar->SetPosSizePixel( aVPos, Size( nSBWidth, aInnerSz.Height() ) ); mpVScrollBar->Show(); } else { mpVScrollBar->Hide(); // #107254# Don't reset top entry after resize, but check for max top entry SetTopEntry( GetTopEntry() ); } // horizontal ScrollBar if( mbHScroll ) { Point aHPos( 0, aOutSz.Height() - nSBWidth ); mpHScrollBar->SetPosSizePixel( aHPos, Size( aInnerSz.Width(), nSBWidth ) ); mpHScrollBar->Show(); } else { mpHScrollBar->Hide(); SetLeftIndent( 0 ); } } void ImplListBox::StateChanged( StateChangedType nType ) { if ( nType == StateChangedType::InitShow ) { ImplCheckScrollBars(); } else if ( ( nType == StateChangedType::UpdateMode ) || ( nType == StateChangedType::Data ) ) { bool bUpdate = IsUpdateMode(); maLBWindow->SetUpdateMode( bUpdate ); if ( bUpdate && IsReallyVisible() ) ImplCheckScrollBars(); } else if( nType == StateChangedType::Enable ) { mpHScrollBar->Enable( IsEnabled() ); mpVScrollBar->Enable( IsEnabled() ); mpScrollBarBox->Enable( IsEnabled() ); maLBWindow->Enable( IsEnabled() ); Invalidate(); } else if ( nType == StateChangedType::Zoom ) { maLBWindow->SetZoom( GetZoom() ); Resize(); } else if ( nType == StateChangedType::ControlFont ) { maLBWindow->SetControlFont( GetControlFont() ); } else if ( nType == StateChangedType::ControlForeground ) { maLBWindow->SetControlForeground( GetControlForeground() ); } else if ( nType == StateChangedType::ControlBackground ) { maLBWindow->SetControlBackground( GetControlBackground() ); } else if( nType == StateChangedType::Mirroring ) { maLBWindow->EnableRTL( IsRTLEnabled() ); mpHScrollBar->EnableRTL( IsRTLEnabled() ); mpVScrollBar->EnableRTL( IsRTLEnabled() ); ImplResizeControls(); } Control::StateChanged( nType ); } bool ImplListBox::EventNotify( NotifyEvent& rNEvt ) { bool bDone = false; if ( rNEvt.GetType() == NotifyEventType::COMMAND ) { const CommandEvent& rCEvt = *rNEvt.GetCommandEvent(); if ( rCEvt.GetCommand() == CommandEventId::Wheel ) { const CommandWheelData* pData = rCEvt.GetWheelData(); if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) ) { bDone = HandleScrollCommand( rCEvt, mpHScrollBar, mpVScrollBar ); } } else if (rCEvt.GetCommand() == CommandEventId::GesturePan) { bDone = HandleScrollCommand(rCEvt, mpHScrollBar, mpVScrollBar); } } return bDone || Window::EventNotify( rNEvt ); } const Wallpaper& ImplListBox::GetDisplayBackground() const { return maLBWindow->GetDisplayBackground(); } bool ImplListBox::HandleWheelAsCursorTravel(const CommandEvent& rCEvt, Control& rControl) { bool bDone = false; if ( rCEvt.GetCommand() == CommandEventId::Wheel ) { const CommandWheelData* pData = rCEvt.GetWheelData(); if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) ) { if (!rControl.HasChildPathFocus()) rControl.GrabFocus(); sal_uInt16 nKey = ( pData->GetDelta() < 0 ) ? KEY_DOWN : KEY_UP; KeyEvent aKeyEvent( 0, vcl::KeyCode( nKey ) ); bDone = ProcessKeyInput( aKeyEvent ); } } return bDone; } void ImplListBox::SetMRUEntries( std::u16string_view rEntries, sal_Unicode cSep ) { bool bChanges = GetEntryList().GetMRUCount() != 0; // Remove old MRU entries for ( sal_Int32 n = GetEntryList().GetMRUCount();n; ) maLBWindow->RemoveEntry( --n ); sal_Int32 nMRUCount = 0; sal_Int32 nIndex = 0; do { OUString aEntry( o3tl::getToken(rEntries, 0, cSep, nIndex ) ); // Accept only existing entries if ( GetEntryList().FindEntry( aEntry ) != LISTBOX_ENTRY_NOTFOUND ) { ImplEntryType* pNewEntry = new ImplEntryType( aEntry ); maLBWindow->InsertEntry(nMRUCount++, pNewEntry, false); bChanges = true; } } while ( nIndex >= 0 ); if ( bChanges ) { maLBWindow->GetEntryList().SetMRUCount( nMRUCount ); SetSeparatorPos( nMRUCount ? nMRUCount-1 : 0 ); CompatStateChanged( StateChangedType::Data ); } } OUString ImplListBox::GetMRUEntries( sal_Unicode cSep ) const { OUStringBuffer aEntries; for ( sal_Int32 n = 0; n < GetEntryList().GetMRUCount(); n++ ) { aEntries.append(GetEntryList().GetEntryText( n )); if( n < ( GetEntryList().GetMRUCount() - 1 ) ) aEntries.append(cSep); } return aEntries.makeStringAndClear(); } void ImplListBox::SetEdgeBlending(bool bNew) { if(mbEdgeBlending != bNew) { mbEdgeBlending = bNew; maLBWindow->SetEdgeBlending(GetEdgeBlending()); } } void ImplListBox::SetHighlightColor(const Color& rColor) { AllSettings aSettings(GetSettings()); StyleSettings aStyle(aSettings.GetStyleSettings()); aStyle.SetHighlightColor(rColor); aSettings.SetStyleSettings(aStyle); SetSettings(aSettings); AllSettings aSettingsLB(maLBWindow->GetSettings()); StyleSettings aStyleLB(aSettingsLB.GetStyleSettings()); aStyleLB.SetListBoxWindowHighlightColor(rColor); aSettingsLB.SetStyleSettings(aStyleLB); maLBWindow->SetSettings(aSettingsLB); } void ImplListBox::SetHighlightTextColor(const Color& rColor) { AllSettings aSettings(GetSettings()); StyleSettings aStyle(aSettings.GetStyleSettings()); aStyle.SetHighlightTextColor(rColor); aSettings.SetStyleSettings(aStyle); SetSettings(aSettings); AllSettings aSettingsLB(maLBWindow->GetSettings()); StyleSettings aStyleLB(aSettingsLB.GetStyleSettings()); aStyleLB.SetListBoxWindowHighlightTextColor(rColor); aSettingsLB.SetStyleSettings(aStyleLB); maLBWindow->SetSettings(aSettingsLB); } ImplWin::ImplWin( vcl::Window* pParent, WinBits nWinStyle ) : Control ( pParent, nWinStyle ) { if ( IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire) && ! IsNativeControlSupported(ControlType::Listbox, ControlPart::ButtonDown) ) SetBackground(); else SetBackground( Wallpaper( GetSettings().GetStyleSettings().GetFieldColor() ) ); ImplGetWindowImpl()->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects; mbEdgeBlending = false; mnItemPos = LISTBOX_ENTRY_NOTFOUND; } void ImplWin::MouseButtonDown( const MouseEvent& ) { if( IsEnabled() ) { maMBDownHdl.Call(this); } } void ImplWin::FillLayoutData() const { mxLayoutData.emplace(); ImplWin* pThis = const_cast(this); pThis->ImplDraw(*pThis->GetOutDev(), true); } void ImplWin::ImplDraw(vcl::RenderContext& rRenderContext, bool bLayout) { const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); if (!bLayout) { bool bNativeOK = false; bool bHasFocus = HasFocus(); bool bIsEnabled = IsEnabled(); ControlState nState = ControlState::ENABLED; if (rRenderContext.IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire) && rRenderContext.IsNativeControlSupported(ControlType::Listbox, ControlPart::HasBackgroundTexture) ) { // Repaint the (focused) area similarly to // ImplSmallBorderWindowView::DrawWindow() in // vcl/source/window/brdwin.cxx vcl::Window *pWin = GetParent(); ImplControlValue aControlValue; bIsEnabled &= pWin->IsEnabled(); if ( !bIsEnabled ) nState &= ~ControlState::ENABLED; bHasFocus |= pWin->HasFocus(); if ( bHasFocus ) nState |= ControlState::FOCUSED; // The listbox is painted over the entire control including the // border, but ImplWin does not contain the border => correction // needed. sal_Int32 nLeft, nTop, nRight, nBottom; pWin->GetBorder( nLeft, nTop, nRight, nBottom ); Point aPoint( -nLeft, -nTop ); tools::Rectangle aCtrlRegion( aPoint - GetPosPixel(), pWin->GetSizePixel() ); bool bMouseOver = pWin->IsMouseOver(); if (!bMouseOver) { vcl::Window *pChild = pWin->GetWindow( GetWindowType::FirstChild ); while( pChild ) { bMouseOver = pChild->IsMouseOver(); if (bMouseOver) break; pChild = pChild->GetWindow( GetWindowType::Next ); } } if( bMouseOver ) nState |= ControlState::ROLLOVER; Color aBackgroundColor = COL_AUTO; if (IsControlBackground()) aBackgroundColor = GetControlBackground(); // if parent has no border, then nobody has drawn the background // since no border window exists. so draw it here. WinBits nParentStyle = pWin->GetStyle(); if( ! (nParentStyle & WB_BORDER) || (nParentStyle & WB_NOBORDER) ) { tools::Rectangle aParentRect( Point( 0, 0 ), pWin->GetSizePixel() ); pWin->GetOutDev()->DrawNativeControl( ControlType::Listbox, ControlPart::Entire, aParentRect, nState, aControlValue, OUString(), aBackgroundColor); } bNativeOK = rRenderContext.DrawNativeControl(ControlType::Listbox, ControlPart::Entire, aCtrlRegion, nState, aControlValue, OUString(), aBackgroundColor); } if (bIsEnabled) { if (bHasFocus && !ImplGetSVData()->maNWFData.mbDDListBoxNoTextArea) { if ( !ImplGetSVData()->maNWFData.mbNoFocusRects ) { rRenderContext.SetFillColor( rStyleSettings.GetHighlightColor() ); rRenderContext.SetTextColor( rStyleSettings.GetHighlightTextColor() ); } else { rRenderContext.SetLineColor(); rRenderContext.SetFillColor(); rRenderContext.SetTextColor( rStyleSettings.GetFieldTextColor() ); } rRenderContext.DrawRect( maFocusRect ); } else { Color aColor; if (IsControlForeground()) aColor = GetControlForeground(); else if (ImplGetSVData()->maNWFData.mbDDListBoxNoTextArea) { if( bNativeOK && (nState & ControlState::ROLLOVER) ) aColor = rStyleSettings.GetButtonRolloverTextColor(); else aColor = rStyleSettings.GetButtonTextColor(); } else { if( bNativeOK && (nState & ControlState::ROLLOVER) ) aColor = rStyleSettings.GetFieldRolloverTextColor(); else aColor = rStyleSettings.GetFieldTextColor(); } rRenderContext.SetTextColor(aColor); if (!bNativeOK) rRenderContext.Erase(maFocusRect); } } else // Disabled { rRenderContext.SetTextColor(rStyleSettings.GetDisableColor()); if (!bNativeOK) rRenderContext.Erase(maFocusRect); } } DrawEntry(rRenderContext, bLayout); } void ImplWin::ApplySettings(vcl::RenderContext& rRenderContext) { const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); ApplyControlFont(rRenderContext, rStyleSettings.GetFieldFont()); ApplyControlForeground(rRenderContext, rStyleSettings.GetFieldTextColor()); if (IsControlBackground()) rRenderContext.SetBackground(GetControlBackground()); else rRenderContext.SetBackground(rStyleSettings.GetFieldColor()); } void ImplWin::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& ) { ImplDraw(rRenderContext); } void ImplWin::DrawEntry(vcl::RenderContext& rRenderContext, bool bLayout) { tools::Long nBorder = 1; Size aOutSz(GetOutputSizePixel()); bool bImage = !!maImage; if (bImage && !bLayout) { DrawImageFlags nStyle = DrawImageFlags::NONE; Size aImgSz = maImage.GetSizePixel(); Point aPtImg( nBorder, ( ( aOutSz.Height() - aImgSz.Height() ) / 2 ) ); const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); // check for HC mode Image *pImage = &maImage; if ( !IsZoom() ) { rRenderContext.DrawImage( aPtImg, *pImage, nStyle ); } else { aImgSz.setWidth( CalcZoom( aImgSz.Width() ) ); aImgSz.setHeight( CalcZoom( aImgSz.Height() ) ); rRenderContext.DrawImage( aPtImg, aImgSz, *pImage, nStyle ); } const sal_uInt16 nEdgeBlendingPercent(GetEdgeBlending() ? rStyleSettings.GetEdgeBlending() : 0); if(nEdgeBlendingPercent) { const Color& rTopLeft(rStyleSettings.GetEdgeBlendingTopLeftColor()); const Color& rBottomRight(rStyleSettings.GetEdgeBlendingBottomRightColor()); const sal_uInt8 nAlpha((nEdgeBlendingPercent * 255) / 100); const BitmapEx aBlendFrame(createBlendFrame(aImgSz, nAlpha, rTopLeft, rBottomRight)); if(!aBlendFrame.IsEmpty()) { rRenderContext.DrawBitmapEx(aPtImg, aBlendFrame); } } } if( !maString.isEmpty() ) { DrawTextFlags nTextStyle = DrawTextFlags::VCenter; if ( bImage && !bLayout ) nTextStyle |= DrawTextFlags::Left; else if ( GetStyle() & WB_CENTER ) nTextStyle |= DrawTextFlags::Center; else if ( GetStyle() & WB_RIGHT ) nTextStyle |= DrawTextFlags::Right; else nTextStyle |= DrawTextFlags::Left; tools::Rectangle aTextRect( Point( nBorder, 0 ), Size( aOutSz.Width()-2*nBorder, aOutSz.Height() ) ); if ( bImage ) { aTextRect.AdjustLeft(maImage.GetSizePixel().Width() + IMG_TXT_DISTANCE ); } std::vector< tools::Rectangle >* pVector = bLayout ? &mxLayoutData->m_aUnicodeBoundRects : nullptr; OUString* pDisplayText = bLayout ? &mxLayoutData->m_aDisplayText : nullptr; rRenderContext.DrawText( aTextRect, maString, nTextStyle, pVector, pDisplayText ); } if( HasFocus() && !bLayout ) ShowFocus( maFocusRect ); } void ImplWin::Resize() { Control::Resize(); maFocusRect.SetSize( GetOutputSizePixel() ); Invalidate(); } void ImplWin::GetFocus() { ShowFocus( maFocusRect ); if (IsNativeWidgetEnabled() && IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire)) { vcl::Window* pWin = GetParent()->GetWindow( GetWindowType::Border ); if( ! pWin ) pWin = GetParent(); pWin->Invalidate(); } else Invalidate(); Control::GetFocus(); } void ImplWin::LoseFocus() { HideFocus(); if (IsNativeWidgetEnabled() && IsNativeControlSupported( ControlType::Listbox, ControlPart::Entire)) { vcl::Window* pWin = GetParent()->GetWindow( GetWindowType::Border ); if( ! pWin ) pWin = GetParent(); pWin->Invalidate(); } else Invalidate(); Control::LoseFocus(); } void ImplWin::ShowFocus(const tools::Rectangle& rRect) { if (IsNativeControlSupported(ControlType::Listbox, ControlPart::Focus)) { ImplControlValue aControlValue; vcl::Window *pWin = GetParent(); tools::Rectangle aParentRect(Point(0, 0), pWin->GetSizePixel()); pWin->GetOutDev()->DrawNativeControl(ControlType::Listbox, ControlPart::Focus, aParentRect, ControlState::FOCUSED, aControlValue, OUString()); } Control::ShowFocus(rRect); } ImplBtn::ImplBtn( vcl::Window* pParent, WinBits nWinStyle ) : PushButton( pParent, nWinStyle ) { } void ImplBtn::MouseButtonDown( const MouseEvent& ) { if( IsEnabled() ) maMBDownHdl.Call(this); } ImplListBoxFloatingWindow::ImplListBoxFloatingWindow( vcl::Window* pParent ) : FloatingWindow( pParent, WB_BORDER | WB_SYSTEMWINDOW | WB_NOSHADOW ) // no drop shadow for list boxes { // for native widget rendering we must be able to detect this window type SetType( WindowType::LISTBOXWINDOW ); mpImplLB = nullptr; mnDDLineCount = 0; mbAutoWidth = false; mnPopupModeStartSaveSelection = LISTBOX_ENTRY_NOTFOUND; vcl::Window * pBorderWindow = ImplGetBorderWindow(); if( pBorderWindow ) { SetAccessibleRole(accessibility::AccessibleRole::PANEL); pBorderWindow->SetAccessibleRole(accessibility::AccessibleRole::WINDOW); } else { SetAccessibleRole(accessibility::AccessibleRole::WINDOW); } } ImplListBoxFloatingWindow::~ImplListBoxFloatingWindow() { disposeOnce(); } void ImplListBoxFloatingWindow::dispose() { mpImplLB.clear(); FloatingWindow::dispose(); } bool ImplListBoxFloatingWindow::PreNotify( NotifyEvent& rNEvt ) { if( rNEvt.GetType() == NotifyEventType::LOSEFOCUS ) { if( !GetParent()->HasChildPathFocus( true ) ) EndPopupMode(); } return FloatingWindow::PreNotify( rNEvt ); } void ImplListBoxFloatingWindow::setPosSizePixel( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, PosSizeFlags nFlags ) { FloatingWindow::setPosSizePixel( nX, nY, nWidth, nHeight, nFlags ); // Fix #60890# ( MBA ): to be able to resize the Listbox even in its open state // after a call to Resize(), we adjust its position if necessary if ( IsReallyVisible() && ( nFlags & PosSizeFlags::Height ) ) { Point aPos = GetParent()->GetPosPixel(); aPos = GetParent()->GetParent()->OutputToScreenPixel( aPos ); if ( nFlags & PosSizeFlags::X ) aPos.setX( nX ); if ( nFlags & PosSizeFlags::Y ) aPos.setY( nY ); sal_uInt16 nIndex; SetPosPixel( ImplCalcPos( this, tools::Rectangle( aPos, GetParent()->GetSizePixel() ), FloatWinPopupFlags::Down, nIndex ) ); } // if( !IsReallyVisible() ) { // The ImplListBox does not get a Resize() as not visible. // But the windows must get a Resize(), so that the number of // visible entries is correct for PgUp/PgDown. // The number also cannot be calculated by List/Combobox, as for // this the presence of the vertical Scrollbar has to be known. mpImplLB->SetSizePixel( GetOutputSizePixel() ); static_cast(mpImplLB)->Resize(); static_cast(mpImplLB->GetMainWindow())->Resize(); } } void ImplListBoxFloatingWindow::Resize() { mpImplLB->GetMainWindow()->ImplClearLayoutData(); FloatingWindow::Resize(); } Size ImplListBoxFloatingWindow::CalcFloatSize() const { Size aFloatSz( maPrefSz ); sal_Int32 nLeft, nTop, nRight, nBottom; GetBorder( nLeft, nTop, nRight, nBottom ); sal_Int32 nLines = mpImplLB->GetEntryList().GetEntryCount(); if ( mnDDLineCount && ( nLines > mnDDLineCount ) ) nLines = mnDDLineCount; Size aSz = mpImplLB->CalcSize( nLines ); tools::Long nMaxHeight = aSz.Height() + nTop + nBottom; if ( mnDDLineCount ) aFloatSz.setHeight( nMaxHeight ); if( mbAutoWidth ) { // AutoSize first only for width... aFloatSz.setWidth( aSz.Width() + nLeft + nRight ); aFloatSz.AdjustWidth(nRight ); // adding some space looks better... if ( ( aFloatSz.Height() < nMaxHeight ) || ( mnDDLineCount && ( mnDDLineCount < mpImplLB->GetEntryList().GetEntryCount() ) ) ) { // then we also need the vertical Scrollbar tools::Long nSBWidth = GetSettings().GetStyleSettings().GetScrollBarSize(); aFloatSz.AdjustWidth(nSBWidth ); } tools::Long nDesktopWidth = GetDesktopRectPixel().getOpenWidth(); if (aFloatSz.Width() > nDesktopWidth) // Don't exceed the desktop width. aFloatSz.setWidth( nDesktopWidth ); } if ( aFloatSz.Height() > nMaxHeight ) aFloatSz.setHeight( nMaxHeight ); // Minimal height, in case height is not set to Float height. // The parent of FloatWin must be DropDown-Combo/Listbox. Size aParentSz = GetParent()->GetSizePixel(); if( (!mnDDLineCount || !nLines) && ( aFloatSz.Height() < aParentSz.Height() ) ) aFloatSz.setHeight( aParentSz.Height() ); // do not get narrower than the parent... if( aFloatSz.Width() < aParentSz.Width() ) aFloatSz.setWidth( aParentSz.Width() ); // align height to entries... tools::Long nInnerHeight = aFloatSz.Height() - nTop - nBottom; tools::Long nEntryHeight = mpImplLB->GetEntryHeightWithMargin(); if ( nInnerHeight % nEntryHeight ) { nInnerHeight /= nEntryHeight; nInnerHeight++; nInnerHeight *= nEntryHeight; aFloatSz.setHeight( nInnerHeight + nTop + nBottom ); } if (aFloatSz.Width() < aSz.Width()) { // The max width of list box entries exceeds the window width. // Account for the scroll bar height. tools::Long nSBWidth = GetSettings().GetStyleSettings().GetScrollBarSize(); aFloatSz.AdjustHeight(nSBWidth ); } return aFloatSz; } void ImplListBoxFloatingWindow::StartFloat( bool bStartTracking ) { if( IsInPopupMode() ) return; Size aFloatSz = CalcFloatSize(); SetSizePixel( aFloatSz ); mpImplLB->SetSizePixel( GetOutputSizePixel() ); sal_Int32 nPos = mpImplLB->GetEntryList().GetSelectedEntryPos( 0 ); mnPopupModeStartSaveSelection = nPos; Size aSz = GetParent()->GetSizePixel(); Point aPos = GetParent()->GetPosPixel(); aPos = GetParent()->GetParent()->OutputToScreenPixel( aPos ); // FIXME: this ugly hack is for Mac/Aqua // should be replaced by a real mechanism to place the float rectangle if( ImplGetSVData()->maNWFData.mbNoFocusRects && GetParent()->IsNativeWidgetEnabled() ) { const sal_Int32 nLeft = 4, nTop = 4, nRight = 4, nBottom = 4; aPos.AdjustX(nLeft ); aPos.AdjustY(nTop ); aSz.AdjustWidth( -(nLeft + nRight) ); aSz.AdjustHeight( -(nTop + nBottom) ); } tools::Rectangle aRect( aPos, aSz ); // check if the control's parent is un-mirrored which is the case for form controls in a mirrored UI // where the document is unmirrored // because StartPopupMode() expects a rectangle in mirrored coordinates we have to re-mirror vcl::Window *pGrandparent = GetParent()->GetParent(); const OutputDevice *pGrandparentOutDev = pGrandparent->GetOutDev(); if( pGrandparent->GetOutDev()->ImplIsAntiparallel() ) pGrandparentOutDev->ReMirror( aRect ); // mouse-button right: close the List-Box-Float-win and don't stop the handling fdo#84795 StartPopupMode( aRect, LISTBOX_FLOATWINPOPUPFLAGS ); if( nPos != LISTBOX_ENTRY_NOTFOUND ) mpImplLB->ShowProminentEntry( nPos ); if( bStartTracking ) mpImplLB->GetMainWindow()->EnableMouseMoveSelect( true ); if ( mpImplLB->GetMainWindow()->IsGrabFocusAllowed() ) mpImplLB->GetMainWindow()->GrabFocus(); mpImplLB->GetMainWindow()->ImplClearLayoutData(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */