summaryrefslogtreecommitdiffstats
path: root/vcl/source/control/combobox.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/source/control/combobox.cxx
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/control/combobox.cxx')
-rw-r--r--vcl/source/control/combobox.cxx1572
1 files changed, 1572 insertions, 0 deletions
diff --git a/vcl/source/control/combobox.cxx b/vcl/source/control/combobox.cxx
new file mode 100644
index 000000000..15e088d7f
--- /dev/null
+++ b/vcl/source/control/combobox.cxx
@@ -0,0 +1,1572 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/toolkit/combobox.hxx>
+
+#include <set>
+
+#include <comphelper/string.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/vclevent.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <sal/log.hxx>
+
+#include <listbox.hxx>
+#include <comphelper/lok.hxx>
+#include <tools/json_writer.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace {
+
+struct ComboBoxBounds
+{
+ Point aSubEditPos;
+ Size aSubEditSize;
+
+ Point aButtonPos;
+ Size aButtonSize;
+};
+
+}
+
+struct ComboBox::Impl
+{
+ ComboBox & m_rThis;
+ VclPtr<Edit> m_pSubEdit;
+ VclPtr<ImplListBox> m_pImplLB;
+ VclPtr<ImplBtn> m_pBtn;
+ VclPtr<ImplListBoxFloatingWindow> m_pFloatWin;
+ sal_uInt16 m_nDDHeight;
+ sal_Unicode m_cMultiSep;
+ bool m_isDDAutoSize : 1;
+ bool m_isSyntheticModify : 1;
+ bool m_isKeyBoardModify : 1;
+ bool m_isMatchCase : 1;
+ sal_Int32 m_nMaxWidthChars;
+ sal_Int32 m_nWidthInChars;
+ Link<ComboBox&,void> m_SelectHdl;
+
+ explicit Impl(ComboBox & rThis)
+ : m_rThis(rThis)
+ , m_nDDHeight(0)
+ , m_cMultiSep(0)
+ , m_isDDAutoSize(false)
+ , m_isSyntheticModify(false)
+ , m_isKeyBoardModify(false)
+ , m_isMatchCase(false)
+ , m_nMaxWidthChars(0)
+ , m_nWidthInChars(-1)
+ {
+ }
+
+ void ImplInitComboBoxData();
+ void ImplUpdateFloatSelection();
+ ComboBoxBounds calcComboBoxDropDownComponentBounds(
+ const Size &rOutSize, const Size &rBorderOutSize) const;
+
+ DECL_LINK( ImplSelectHdl, LinkParamNone*, void );
+ DECL_LINK( ImplCancelHdl, LinkParamNone*, void );
+ DECL_LINK( ImplDoubleClickHdl, ImplListBoxWindow*, void );
+ DECL_LINK( ImplClickBtnHdl, void*, void );
+ DECL_LINK( ImplPopupModeEndHdl, FloatingWindow*, void );
+ DECL_LINK( ImplSelectionChangedHdl, sal_Int32, void );
+ DECL_LINK( ImplAutocompleteHdl, Edit&, void );
+ DECL_LINK( ImplListItemSelectHdl , LinkParamNone*, void );
+};
+
+
+static void lcl_GetSelectedEntries( ::std::set< sal_Int32 >& rSelectedPos, std::u16string_view rText, sal_Unicode cTokenSep, const ImplEntryList& rEntryList )
+{
+ if (rText.empty())
+ return;
+
+ sal_Int32 nIdx{0};
+ do {
+ const sal_Int32 nPos = rEntryList.FindEntry(comphelper::string::strip(o3tl::getToken(rText, 0, cTokenSep, nIdx), ' '));
+ if ( nPos != LISTBOX_ENTRY_NOTFOUND )
+ rSelectedPos.insert( nPos );
+ } while (nIdx>=0);
+}
+
+ComboBox::ComboBox(vcl::Window *const pParent, WinBits const nStyle)
+ : Edit( WindowType::COMBOBOX )
+ , m_pImpl(new Impl(*this))
+{
+ m_pImpl->ImplInitComboBoxData();
+ ImplInit( pParent, nStyle );
+ SetWidthInChars(-1);
+}
+
+ComboBox::~ComboBox()
+{
+ disposeOnce();
+}
+
+void ComboBox::dispose()
+{
+ m_pImpl->m_pSubEdit.disposeAndClear();
+
+ VclPtr< ImplListBox > pImplLB = m_pImpl->m_pImplLB;
+ m_pImpl->m_pImplLB.clear();
+ pImplLB.disposeAndClear();
+
+ m_pImpl->m_pFloatWin.disposeAndClear();
+ m_pImpl->m_pBtn.disposeAndClear();
+ Edit::dispose();
+}
+
+void ComboBox::Impl::ImplInitComboBoxData()
+{
+ m_pSubEdit.disposeAndClear();
+ m_pBtn = nullptr;
+ m_pImplLB = nullptr;
+ m_pFloatWin = nullptr;
+
+ m_nDDHeight = 0;
+ m_isDDAutoSize = true;
+ m_isSyntheticModify = false;
+ m_isKeyBoardModify = false;
+ m_isMatchCase = false;
+ m_cMultiSep = ';';
+ m_nMaxWidthChars = -1;
+ m_nWidthInChars = -1;
+}
+
+void ComboBox::ImplCalcEditHeight()
+{
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ GetBorder( nLeft, nTop, nRight, nBottom );
+ m_pImpl->m_nDDHeight = static_cast<sal_uInt16>(m_pImpl->m_pSubEdit->GetTextHeight() + nTop + nBottom + 4);
+ if ( !IsDropDownBox() )
+ m_pImpl->m_nDDHeight += 4;
+
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), Size( 10, 10 ) );
+ tools::Rectangle aBoundRegion, aContentRegion;
+ ImplControlValue aControlValue;
+ ControlType aType = IsDropDownBox() ? ControlType::Combobox : ControlType::Editbox;
+ if( GetNativeControlRegion( aType, ControlPart::Entire,
+ aCtrlRegion,
+ ControlState::ENABLED,
+ aControlValue,
+ aBoundRegion, aContentRegion ) )
+ {
+ const tools::Long nNCHeight = aBoundRegion.GetHeight();
+ if (m_pImpl->m_nDDHeight < nNCHeight)
+ m_pImpl->m_nDDHeight = sal::static_int_cast<sal_uInt16>(nNCHeight);
+ }
+}
+
+void ComboBox::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ bool bNoBorder = ( nStyle & WB_NOBORDER ) != 0;
+ if ( !(nStyle & WB_DROPDOWN) )
+ {
+ nStyle &= ~WB_BORDER;
+ nStyle |= WB_NOBORDER;
+ }
+ else
+ {
+ if ( !bNoBorder )
+ nStyle |= WB_BORDER;
+ }
+
+ Edit::ImplInit( pParent, nStyle );
+ SetBackground();
+
+ // DropDown ?
+ WinBits nEditStyle = nStyle & ( WB_LEFT | WB_RIGHT | WB_CENTER );
+ WinBits nListStyle = nStyle;
+ if( nStyle & WB_DROPDOWN )
+ {
+ m_pImpl->m_pFloatWin = VclPtr<ImplListBoxFloatingWindow>::Create( this );
+ if (!IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Focus))
+ m_pImpl->m_pFloatWin->RequestDoubleBuffering(true);
+ m_pImpl->m_pFloatWin->SetAutoWidth( true );
+ m_pImpl->m_pFloatWin->SetPopupModeEndHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplPopupModeEndHdl) );
+
+ m_pImpl->m_pBtn = VclPtr<ImplBtn>::Create( this, WB_NOLIGHTBORDER | WB_RECTSTYLE );
+ ImplInitDropDownButton( m_pImpl->m_pBtn );
+ m_pImpl->m_pBtn->SetMBDownHdl( LINK( m_pImpl.get(), ComboBox::Impl, ImplClickBtnHdl ) );
+ m_pImpl->m_pBtn->Show();
+
+ nEditStyle |= WB_NOBORDER;
+ nListStyle &= ~WB_BORDER;
+ nListStyle |= WB_NOBORDER;
+ }
+ else
+ {
+ if ( !bNoBorder )
+ {
+ nEditStyle |= WB_BORDER;
+ nListStyle &= ~WB_NOBORDER;
+ nListStyle |= WB_BORDER;
+ }
+ }
+
+ m_pImpl->m_pSubEdit.set( VclPtr<Edit>::Create( this, nEditStyle ) );
+ m_pImpl->m_pSubEdit->EnableRTL( false );
+ SetSubEdit( m_pImpl->m_pSubEdit );
+ m_pImpl->m_pSubEdit->SetPosPixel( Point() );
+ EnableAutocomplete( true );
+ m_pImpl->m_pSubEdit->Show();
+
+ vcl::Window* pLBParent = this;
+ if (m_pImpl->m_pFloatWin)
+ pLBParent = m_pImpl->m_pFloatWin;
+ m_pImpl->m_pImplLB = VclPtr<ImplListBox>::Create( pLBParent, nListStyle|WB_SIMPLEMODE|WB_AUTOHSCROLL );
+ m_pImpl->m_pImplLB->SetPosPixel( Point() );
+ m_pImpl->m_pImplLB->SetSelectHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplSelectHdl) );
+ m_pImpl->m_pImplLB->SetCancelHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplCancelHdl) );
+ m_pImpl->m_pImplLB->SetDoubleClickHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplDoubleClickHdl) );
+ m_pImpl->m_pImplLB->SetSelectionChangedHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplSelectionChangedHdl) );
+ m_pImpl->m_pImplLB->SetListItemSelectHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplListItemSelectHdl) );
+ m_pImpl->m_pImplLB->Show();
+
+ if (m_pImpl->m_pFloatWin)
+ m_pImpl->m_pFloatWin->SetImplListBox( m_pImpl->m_pImplLB );
+ else
+ GetMainWindow()->AllowGrabFocus( true );
+
+ ImplCalcEditHeight();
+
+ SetCompoundControl( true );
+}
+
+WinBits ComboBox::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+void ComboBox::EnableAutocomplete( bool bEnable, bool bMatchCase )
+{
+ m_pImpl->m_isMatchCase = bMatchCase;
+
+ if ( bEnable )
+ m_pImpl->m_pSubEdit->SetAutocompleteHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplAutocompleteHdl) );
+ else
+ m_pImpl->m_pSubEdit->SetAutocompleteHdl( Link<Edit&,void>() );
+}
+
+bool ComboBox::IsAutocompleteEnabled() const
+{
+ return m_pImpl->m_pSubEdit->GetAutocompleteHdl().IsSet();
+}
+
+IMPL_LINK_NOARG(ComboBox::Impl, ImplClickBtnHdl, void*, void)
+{
+ m_rThis.CallEventListeners( VclEventId::DropdownPreOpen );
+ m_pSubEdit->GrabFocus();
+ if (!m_pImplLB->GetEntryList().GetMRUCount())
+ ImplUpdateFloatSelection();
+ else
+ m_pImplLB->SelectEntry( 0 , true );
+ m_pBtn->SetPressed( true );
+ m_rThis.SetSelection( Selection( 0, SELECTION_MAX ) );
+ m_pFloatWin->StartFloat( true );
+ m_rThis.CallEventListeners( VclEventId::DropdownOpen );
+
+ m_rThis.ImplClearLayoutData();
+ if (m_pImplLB)
+ m_pImplLB->GetMainWindow()->ImplClearLayoutData();
+}
+
+IMPL_LINK_NOARG(ComboBox::Impl, ImplPopupModeEndHdl, FloatingWindow*, void)
+{
+ if (m_pFloatWin->IsPopupModeCanceled())
+ {
+ if (!m_pImplLB->GetEntryList().IsEntryPosSelected(
+ m_pFloatWin->GetPopupModeStartSaveSelection()))
+ {
+ m_pImplLB->SelectEntry(m_pFloatWin->GetPopupModeStartSaveSelection(), true);
+ bool bTravelSelect = m_pImplLB->IsTravelSelect();
+ m_pImplLB->SetTravelSelect( true );
+ m_rThis.Select();
+ m_pImplLB->SetTravelSelect( bTravelSelect );
+ }
+ }
+
+ m_rThis.ImplClearLayoutData();
+ if (m_pImplLB)
+ m_pImplLB->GetMainWindow()->ImplClearLayoutData();
+
+ m_pBtn->SetPressed( false );
+ m_rThis.CallEventListeners( VclEventId::DropdownClose );
+}
+
+IMPL_LINK(ComboBox::Impl, ImplAutocompleteHdl, Edit&, rEdit, void)
+{
+ Selection aSel = rEdit.GetSelection();
+
+ {
+ OUString aFullText = rEdit.GetText();
+ OUString aStartText = aFullText.copy( 0, static_cast<sal_Int32>(aSel.Max()) );
+ sal_Int32 nStart = m_pImplLB->GetCurrentPos();
+
+ if ( nStart == LISTBOX_ENTRY_NOTFOUND )
+ nStart = 0;
+
+ sal_Int32 nPos = LISTBOX_ENTRY_NOTFOUND;
+ if (!m_isMatchCase)
+ {
+ // Try match case insensitive from current position
+ nPos = m_pImplLB->GetEntryList().FindMatchingEntry(aStartText, nStart, true);
+ if ( nPos == LISTBOX_ENTRY_NOTFOUND )
+ // Try match case insensitive, but from start
+ nPos = m_pImplLB->GetEntryList().FindMatchingEntry(aStartText, 0, true);
+ }
+
+ if ( nPos == LISTBOX_ENTRY_NOTFOUND )
+ // Try match full from current position
+ nPos = m_pImplLB->GetEntryList().FindMatchingEntry(aStartText, nStart, false);
+ if ( nPos == LISTBOX_ENTRY_NOTFOUND )
+ // Match full, but from start
+ nPos = m_pImplLB->GetEntryList().FindMatchingEntry(aStartText, 0, false);
+
+ if ( nPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ OUString aText = m_pImplLB->GetEntryList().GetEntryText( nPos );
+ Selection aSelection( aText.getLength(), aStartText.getLength() );
+ rEdit.SetText( aText, aSelection );
+ }
+ }
+}
+
+IMPL_LINK_NOARG(ComboBox::Impl, ImplSelectHdl, LinkParamNone*, void)
+{
+ bool bPopup = m_rThis.IsInDropDown();
+ bool bCallSelect = false;
+ if (m_pImplLB->IsSelectionChanged() || bPopup)
+ {
+ OUString aText;
+ if (m_rThis.IsMultiSelectionEnabled())
+ {
+ aText = m_pSubEdit->GetText();
+
+ // remove all entries to which there is an entry, but which is not selected
+ sal_Int32 nIndex = 0;
+ while ( nIndex >= 0 )
+ {
+ sal_Int32 nPrevIndex = nIndex;
+ std::u16string_view aToken = o3tl::getToken(aText, 0, m_cMultiSep, nIndex );
+ sal_Int32 nTokenLen = aToken.size();
+ aToken = comphelper::string::strip(aToken, ' ');
+ sal_Int32 nP = m_pImplLB->GetEntryList().FindEntry( aToken );
+ if ((nP != LISTBOX_ENTRY_NOTFOUND) && (!m_pImplLB->GetEntryList().IsEntryPosSelected(nP)))
+ {
+ aText = aText.replaceAt( nPrevIndex, nTokenLen, u"" );
+ nIndex = nIndex - nTokenLen;
+ sal_Int32 nSepCount=0;
+ if ((nPrevIndex+nSepCount < aText.getLength()) && (aText[nPrevIndex+nSepCount] == m_cMultiSep))
+ {
+ nIndex--;
+ ++nSepCount;
+ }
+ aText = aText.replaceAt( nPrevIndex, nSepCount, u"" );
+ }
+ aText = comphelper::string::strip(aText, ' ');
+ }
+
+ // attach missing entries
+ ::std::set< sal_Int32 > aSelInText;
+ lcl_GetSelectedEntries( aSelInText, aText, m_cMultiSep, m_pImplLB->GetEntryList() );
+ sal_Int32 nSelectedEntries = m_pImplLB->GetEntryList().GetSelectedEntryCount();
+ for ( sal_Int32 n = 0; n < nSelectedEntries; n++ )
+ {
+ sal_Int32 nP = m_pImplLB->GetEntryList().GetSelectedEntryPos( n );
+ if ( !aSelInText.count( nP ) )
+ {
+ if (!aText.isEmpty() && (aText[aText.getLength()-1] != m_cMultiSep))
+ aText += OUStringChar(m_cMultiSep);
+ if ( !aText.isEmpty() )
+ aText += " "; // slightly loosen
+ aText += m_pImplLB->GetEntryList().GetEntryText( nP ) +
+ OUStringChar(m_cMultiSep);
+ }
+ }
+ aText = comphelper::string::stripEnd( aText, m_cMultiSep );
+ }
+ else
+ {
+ aText = m_pImplLB->GetEntryList().GetSelectedEntry( 0 );
+ }
+
+ m_pSubEdit->SetText( aText );
+
+ Selection aNewSelection( 0, aText.getLength() );
+ if (m_rThis.IsMultiSelectionEnabled())
+ aNewSelection.Min() = aText.getLength();
+ m_pSubEdit->SetSelection( aNewSelection );
+
+ bCallSelect = true;
+ }
+
+ // #84652# Call GrabFocus and EndPopupMode before calling Select/Modify, but after changing the text
+ bool bMenuSelect = bPopup && !m_pImplLB->IsTravelSelect() && (!m_rThis.IsMultiSelectionEnabled() || !m_pImplLB->GetSelectModifier());
+ if (bMenuSelect)
+ {
+ m_pFloatWin->EndPopupMode();
+ m_rThis.GrabFocus();
+ }
+
+ if ( bCallSelect )
+ {
+ m_isKeyBoardModify = !bMenuSelect;
+ m_pSubEdit->SetModifyFlag();
+ m_isSyntheticModify = true;
+ m_rThis.Modify();
+ m_isSyntheticModify = false;
+ m_rThis.Select();
+ m_isKeyBoardModify = false;
+ }
+}
+
+bool ComboBox::IsSyntheticModify() const
+{
+ return m_pImpl->m_isSyntheticModify;
+}
+
+bool ComboBox::IsModifyByKeyboard() const
+{
+ return m_pImpl->m_isKeyBoardModify;
+}
+
+IMPL_LINK_NOARG( ComboBox::Impl, ImplListItemSelectHdl, LinkParamNone*, void )
+{
+ m_rThis.CallEventListeners( VclEventId::DropdownSelect );
+}
+
+IMPL_LINK_NOARG(ComboBox::Impl, ImplCancelHdl, LinkParamNone*, void)
+{
+ if (m_rThis.IsInDropDown())
+ m_pFloatWin->EndPopupMode();
+}
+
+IMPL_LINK( ComboBox::Impl, ImplSelectionChangedHdl, sal_Int32, nChanged, void )
+{
+ if (!m_pImplLB->IsTrackingSelect())
+ {
+ if (!m_pSubEdit->IsReadOnly() && m_pImplLB->GetEntryList().IsEntryPosSelected(nChanged))
+ m_pSubEdit->SetText(m_pImplLB->GetEntryList().GetEntryText(nChanged));
+ }
+}
+
+IMPL_LINK_NOARG(ComboBox::Impl, ImplDoubleClickHdl, ImplListBoxWindow*, void)
+{
+ m_rThis.DoubleClick();
+}
+
+void ComboBox::ToggleDropDown()
+{
+ if( !IsDropDownBox() )
+ return;
+
+ if (m_pImpl->m_pFloatWin->IsInPopupMode())
+ m_pImpl->m_pFloatWin->EndPopupMode();
+ else
+ {
+ m_pImpl->m_pSubEdit->GrabFocus();
+ if (!m_pImpl->m_pImplLB->GetEntryList().GetMRUCount())
+ m_pImpl->ImplUpdateFloatSelection();
+ else
+ m_pImpl->m_pImplLB->SelectEntry( 0 , true );
+ CallEventListeners( VclEventId::DropdownPreOpen );
+ m_pImpl->m_pBtn->SetPressed( true );
+ SetSelection( Selection( 0, SELECTION_MAX ) );
+ m_pImpl->m_pFloatWin->StartFloat( true );
+ CallEventListeners( VclEventId::DropdownOpen );
+ }
+}
+
+void ComboBox::Select()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ComboboxSelect, [this] () { m_pImpl->m_SelectHdl.Call(*this); } );
+}
+
+void ComboBox::DoubleClick()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ComboboxDoubleClick, [] () {} );
+}
+
+bool ComboBox::IsAutoSizeEnabled() const { return m_pImpl->m_isDDAutoSize; }
+
+void ComboBox::EnableAutoSize( bool bAuto )
+{
+ m_pImpl->m_isDDAutoSize = bAuto;
+ if (m_pImpl->m_pFloatWin)
+ {
+ if (bAuto && !m_pImpl->m_pFloatWin->GetDropDownLineCount())
+ {
+ // Adapt to GetListBoxMaximumLineCount here; was on fixed number of five before
+ AdaptDropDownLineCountToMaximum();
+ }
+ else if ( !bAuto )
+ {
+ m_pImpl->m_pFloatWin->SetDropDownLineCount( 0 );
+ }
+ }
+}
+
+void ComboBox::SetDropDownLineCount( sal_uInt16 nLines )
+{
+ if (m_pImpl->m_pFloatWin)
+ m_pImpl->m_pFloatWin->SetDropDownLineCount( nLines );
+}
+
+void ComboBox::AdaptDropDownLineCountToMaximum()
+{
+ // Adapt to maximum allowed number.
+ // Limit for LOK as we can't render outside of the dialog canvas.
+ if (comphelper::LibreOfficeKit::isActive())
+ SetDropDownLineCount(11);
+ else
+ SetDropDownLineCount(GetSettings().GetStyleSettings().GetListBoxMaximumLineCount());
+}
+
+sal_uInt16 ComboBox::GetDropDownLineCount() const
+{
+ sal_uInt16 nLines = 0;
+ if (m_pImpl->m_pFloatWin)
+ nLines = m_pImpl->m_pFloatWin->GetDropDownLineCount();
+ return nLines;
+}
+
+void ComboBox::setPosSizePixel( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ PosSizeFlags nFlags )
+{
+ if( IsDropDownBox() && ( nFlags & PosSizeFlags::Size ) )
+ {
+ Size aPrefSz = m_pImpl->m_pFloatWin->GetPrefSize();
+ if ((nFlags & PosSizeFlags::Height) && (nHeight >= 2*m_pImpl->m_nDDHeight))
+ aPrefSz.setHeight( nHeight-m_pImpl->m_nDDHeight );
+ if ( nFlags & PosSizeFlags::Width )
+ aPrefSz.setWidth( nWidth );
+ m_pImpl->m_pFloatWin->SetPrefSize( aPrefSz );
+
+ if (IsAutoSizeEnabled())
+ nHeight = m_pImpl->m_nDDHeight;
+ }
+
+ Edit::setPosSizePixel( nX, nY, nWidth, nHeight, nFlags );
+}
+
+void ComboBox::Resize()
+{
+ Control::Resize();
+
+ if (m_pImpl->m_pSubEdit)
+ {
+ Size aOutSz = GetOutputSizePixel();
+ if( IsDropDownBox() )
+ {
+ ComboBoxBounds aBounds(m_pImpl->calcComboBoxDropDownComponentBounds(aOutSz,
+ GetWindow(GetWindowType::Border)->GetOutputSizePixel()));
+ m_pImpl->m_pSubEdit->SetPosSizePixel(aBounds.aSubEditPos, aBounds.aSubEditSize);
+ m_pImpl->m_pBtn->SetPosSizePixel(aBounds.aButtonPos, aBounds.aButtonSize);
+ }
+ else
+ {
+ m_pImpl->m_pSubEdit->SetSizePixel(Size(aOutSz.Width(), m_pImpl->m_nDDHeight));
+ m_pImpl->m_pImplLB->setPosSizePixel(0, m_pImpl->m_nDDHeight,
+ aOutSz.Width(), aOutSz.Height() - m_pImpl->m_nDDHeight);
+ if ( !GetText().isEmpty() )
+ m_pImpl->ImplUpdateFloatSelection();
+ }
+ }
+
+ // adjust the size of the FloatingWindow even when invisible
+ // as KEY_PGUP/DOWN is being processed...
+ if (m_pImpl->m_pFloatWin)
+ m_pImpl->m_pFloatWin->SetSizePixel(m_pImpl->m_pFloatWin->CalcFloatSize());
+}
+
+bool ComboBox::IsDropDownBox() const { return m_pImpl->m_pFloatWin != nullptr; }
+
+void ComboBox::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ AppendLayoutData( *m_pImpl->m_pSubEdit );
+ m_pImpl->m_pSubEdit->SetLayoutDataParent( this );
+ ImplListBoxWindow* rMainWindow = GetMainWindow();
+ if (m_pImpl->m_pFloatWin)
+ {
+ // dropdown mode
+ if (m_pImpl->m_pFloatWin->IsReallyVisible())
+ {
+ AppendLayoutData( *rMainWindow );
+ rMainWindow->SetLayoutDataParent( this );
+ }
+ }
+ else
+ {
+ AppendLayoutData( *rMainWindow );
+ rMainWindow->SetLayoutDataParent( this );
+ }
+}
+
+void ComboBox::StateChanged( StateChangedType nType )
+{
+ Edit::StateChanged( nType );
+
+ if ( nType == StateChangedType::ReadOnly )
+ {
+ m_pImpl->m_pImplLB->SetReadOnly( IsReadOnly() );
+ if (m_pImpl->m_pBtn)
+ m_pImpl->m_pBtn->Enable( IsEnabled() && !IsReadOnly() );
+ }
+ else if ( nType == StateChangedType::Enable )
+ {
+ m_pImpl->m_pSubEdit->Enable( IsEnabled() );
+ m_pImpl->m_pImplLB->Enable( IsEnabled() && !IsReadOnly() );
+ if (m_pImpl->m_pBtn)
+ m_pImpl->m_pBtn->Enable( IsEnabled() && !IsReadOnly() );
+ Invalidate();
+ }
+ else if( nType == StateChangedType::UpdateMode )
+ {
+ m_pImpl->m_pImplLB->SetUpdateMode( IsUpdateMode() );
+ }
+ else if ( nType == StateChangedType::Zoom )
+ {
+ m_pImpl->m_pImplLB->SetZoom( GetZoom() );
+ m_pImpl->m_pSubEdit->SetZoom( GetZoom() );
+ ImplCalcEditHeight();
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlFont )
+ {
+ m_pImpl->m_pImplLB->SetControlFont( GetControlFont() );
+ m_pImpl->m_pSubEdit->SetControlFont( GetControlFont() );
+ ImplCalcEditHeight();
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ m_pImpl->m_pImplLB->SetControlForeground( GetControlForeground() );
+ m_pImpl->m_pSubEdit->SetControlForeground( GetControlForeground() );
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ m_pImpl->m_pImplLB->SetControlBackground( GetControlBackground() );
+ m_pImpl->m_pSubEdit->SetControlBackground( GetControlBackground() );
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ GetMainWindow()->EnableSort( ( GetStyle() & WB_SORT ) != 0 );
+ }
+ else if( nType == StateChangedType::Mirroring )
+ {
+ if (m_pImpl->m_pBtn)
+ {
+ m_pImpl->m_pBtn->EnableRTL( IsRTLEnabled() );
+ ImplInitDropDownButton( m_pImpl->m_pBtn );
+ }
+ m_pImpl->m_pSubEdit->CompatStateChanged( StateChangedType::Mirroring );
+ m_pImpl->m_pImplLB->EnableRTL( IsRTLEnabled() );
+ Resize();
+ }
+}
+
+void ComboBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( !((rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))) )
+ return;
+
+ if (m_pImpl->m_pBtn)
+ {
+ m_pImpl->m_pBtn->GetOutDev()->SetSettings( GetSettings() );
+ ImplInitDropDownButton( m_pImpl->m_pBtn );
+ }
+ Resize();
+ m_pImpl->m_pImplLB->Resize(); // not called by ComboBox::Resize() if ImplLB is unchanged
+
+ SetBackground(); // due to a hack in Window::UpdateSettings the background must be reset
+ // otherwise it will overpaint NWF drawn comboboxes
+}
+
+bool ComboBox::EventNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+ if ((rNEvt.GetType() == MouseNotifyEvent::KEYINPUT)
+ && (rNEvt.GetWindow() == m_pImpl->m_pSubEdit)
+ && !IsReadOnly())
+ {
+ KeyEvent aKeyEvt = *rNEvt.GetKeyEvent();
+ sal_uInt16 nKeyCode = aKeyEvt.GetKeyCode().GetCode();
+ switch( nKeyCode )
+ {
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN:
+ {
+ m_pImpl->ImplUpdateFloatSelection();
+ if ((nKeyCode == KEY_DOWN) && m_pImpl->m_pFloatWin
+ && !m_pImpl->m_pFloatWin->IsInPopupMode()
+ && aKeyEvt.GetKeyCode().IsMod2())
+ {
+ CallEventListeners( VclEventId::DropdownPreOpen );
+ m_pImpl->m_pBtn->SetPressed( true );
+ if (m_pImpl->m_pImplLB->GetEntryList().GetMRUCount())
+ m_pImpl->m_pImplLB->SelectEntry( 0 , true );
+ SetSelection( Selection( 0, SELECTION_MAX ) );
+ m_pImpl->m_pFloatWin->StartFloat( false );
+ CallEventListeners( VclEventId::DropdownOpen );
+ bDone = true;
+ }
+ else if ((nKeyCode == KEY_UP) && m_pImpl->m_pFloatWin
+ && m_pImpl->m_pFloatWin->IsInPopupMode()
+ && aKeyEvt.GetKeyCode().IsMod2())
+ {
+ m_pImpl->m_pFloatWin->EndPopupMode();
+ bDone = true;
+ }
+ else
+ {
+ bDone = m_pImpl->m_pImplLB->ProcessKeyInput( aKeyEvt );
+ }
+ }
+ break;
+
+ case KEY_RETURN:
+ {
+ if ((rNEvt.GetWindow() == m_pImpl->m_pSubEdit) && IsInDropDown())
+ {
+ m_pImpl->m_pImplLB->ProcessKeyInput( aKeyEvt );
+ bDone = true;
+ }
+ }
+ break;
+ }
+ }
+ else if ((rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS) && m_pImpl->m_pFloatWin)
+ {
+ if (m_pImpl->m_pFloatWin->HasChildPathFocus())
+ m_pImpl->m_pSubEdit->GrabFocus();
+ else if (m_pImpl->m_pFloatWin->IsInPopupMode() && !HasChildPathFocus(true))
+ m_pImpl->m_pFloatWin->EndPopupMode();
+ }
+ else if( (rNEvt.GetType() == MouseNotifyEvent::COMMAND) &&
+ (rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) &&
+ (rNEvt.GetWindow() == m_pImpl->m_pSubEdit) )
+ {
+ MouseWheelBehaviour nWheelBehavior( GetSettings().GetMouseSettings().GetWheelBehavior() );
+ if ( ( nWheelBehavior == MouseWheelBehaviour::ALWAYS )
+ || ( ( nWheelBehavior == MouseWheelBehaviour::FocusOnly )
+ && HasChildPathFocus()
+ )
+ )
+ {
+ bDone = m_pImpl->m_pImplLB->HandleWheelAsCursorTravel(*rNEvt.GetCommandEvent(), *this);
+ }
+ else
+ {
+ bDone = false; // don't eat this event, let the default handling happen (i.e. scroll the context)
+ }
+ }
+ else if ((rNEvt.GetType() == MouseNotifyEvent::MOUSEBUTTONDOWN)
+ && (rNEvt.GetWindow() == GetMainWindow()))
+ {
+ m_pImpl->m_pSubEdit->GrabFocus();
+ }
+
+ return bDone || Edit::EventNotify( rNEvt );
+}
+
+void ComboBox::SetText( const OUString& rStr )
+{
+ CallEventListeners( VclEventId::ComboboxSetText );
+
+ Edit::SetText( rStr );
+ m_pImpl->ImplUpdateFloatSelection();
+}
+
+void ComboBox::SetText( const OUString& rStr, const Selection& rNewSelection )
+{
+ CallEventListeners( VclEventId::ComboboxSetText );
+
+ Edit::SetText( rStr, rNewSelection );
+ m_pImpl->ImplUpdateFloatSelection();
+}
+
+void ComboBox::Modify()
+{
+ if (!m_pImpl->m_isSyntheticModify)
+ m_pImpl->ImplUpdateFloatSelection();
+
+ Edit::Modify();
+}
+
+void ComboBox::Impl::ImplUpdateFloatSelection()
+{
+ if (!m_pImplLB || !m_pSubEdit)
+ return;
+
+ // move text in the ListBox into the visible region
+ m_pImplLB->SetCallSelectionChangedHdl( false );
+ if (!m_rThis.IsMultiSelectionEnabled())
+ {
+ OUString aSearchStr( m_pSubEdit->GetText() );
+ sal_Int32 nSelect = LISTBOX_ENTRY_NOTFOUND;
+ bool bSelect = true;
+
+ if (m_pImplLB->GetCurrentPos() != LISTBOX_ENTRY_NOTFOUND)
+ {
+ OUString aCurrent = m_pImplLB->GetEntryList().GetEntryText(
+ m_pImplLB->GetCurrentPos());
+ if ( aCurrent == aSearchStr )
+ nSelect = m_pImplLB->GetCurrentPos();
+ }
+
+ if ( nSelect == LISTBOX_ENTRY_NOTFOUND )
+ nSelect = m_pImplLB->GetEntryList().FindEntry( aSearchStr );
+ if ( nSelect == LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = m_pImplLB->GetEntryList().FindMatchingEntry( aSearchStr, 0, true );
+ bSelect = false;
+ }
+
+ if( nSelect != LISTBOX_ENTRY_NOTFOUND )
+ {
+ if (!m_pImplLB->IsVisible(nSelect))
+ m_pImplLB->ShowProminentEntry( nSelect );
+ m_pImplLB->SelectEntry( nSelect, bSelect );
+ }
+ else
+ {
+ nSelect = m_pImplLB->GetEntryList().GetSelectedEntryPos( 0 );
+ if( nSelect != LISTBOX_ENTRY_NOTFOUND )
+ m_pImplLB->SelectEntry( nSelect, false );
+ m_pImplLB->ResetCurrentPos();
+ }
+ }
+ else
+ {
+ ::std::set< sal_Int32 > aSelInText;
+ lcl_GetSelectedEntries(aSelInText, m_pSubEdit->GetText(), m_cMultiSep, m_pImplLB->GetEntryList());
+ for (sal_Int32 n = 0; n < m_pImplLB->GetEntryList().GetEntryCount(); n++)
+ m_pImplLB->SelectEntry( n, aSelInText.count( n ) != 0 );
+ }
+ m_pImplLB->SetCallSelectionChangedHdl( true );
+}
+
+sal_Int32 ComboBox::InsertEntry(const OUString& rStr, sal_Int32 const nPos)
+{
+ assert(nPos >= 0 && COMBOBOX_MAX_ENTRIES > m_pImpl->m_pImplLB->GetEntryList().GetEntryCount());
+
+ sal_Int32 nRealPos;
+ if (nPos == COMBOBOX_APPEND)
+ nRealPos = nPos;
+ else
+ {
+ const sal_Int32 nMRUCount = m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ assert(nPos <= COMBOBOX_MAX_ENTRIES - nMRUCount);
+ nRealPos = nPos + nMRUCount;
+ }
+
+ nRealPos = m_pImpl->m_pImplLB->InsertEntry( nRealPos, rStr );
+ nRealPos -= m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ CallEventListeners( VclEventId::ComboboxItemAdded, reinterpret_cast<void*>(nRealPos) );
+ return nRealPos;
+}
+
+sal_Int32 ComboBox::InsertEntryWithImage(
+ const OUString& rStr, const Image& rImage, sal_Int32 const nPos)
+{
+ assert(nPos >= 0 && COMBOBOX_MAX_ENTRIES > m_pImpl->m_pImplLB->GetEntryList().GetEntryCount());
+
+ sal_Int32 nRealPos;
+ if (nPos == COMBOBOX_APPEND)
+ nRealPos = nPos;
+ else
+ {
+ const sal_Int32 nMRUCount = m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ assert(nPos <= COMBOBOX_MAX_ENTRIES - nMRUCount);
+ nRealPos = nPos + nMRUCount;
+ }
+
+ nRealPos = m_pImpl->m_pImplLB->InsertEntry( nRealPos, rStr, rImage );
+ nRealPos -= m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ CallEventListeners( VclEventId::ComboboxItemAdded, reinterpret_cast<void*>(nRealPos) );
+ return nRealPos;
+}
+
+void ComboBox::RemoveEntryAt(sal_Int32 const nPos)
+{
+ const sal_Int32 nMRUCount = m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ assert(nPos >= 0 && nPos <= COMBOBOX_MAX_ENTRIES - nMRUCount);
+ m_pImpl->m_pImplLB->RemoveEntry( nPos + nMRUCount );
+ CallEventListeners( VclEventId::ComboboxItemRemoved, reinterpret_cast<void*>(nPos) );
+}
+
+void ComboBox::Clear()
+{
+ if (!m_pImpl->m_pImplLB)
+ return;
+ m_pImpl->m_pImplLB->Clear();
+ CallEventListeners( VclEventId::ComboboxItemRemoved, reinterpret_cast<void*>(-1) );
+}
+
+Image ComboBox::GetEntryImage( sal_Int32 nPos ) const
+{
+ if (m_pImpl->m_pImplLB->GetEntryList().HasEntryImage(nPos))
+ return m_pImpl->m_pImplLB->GetEntryList().GetEntryImage( nPos );
+ return Image();
+}
+
+sal_Int32 ComboBox::GetEntryPos( std::u16string_view rStr ) const
+{
+ sal_Int32 nPos = m_pImpl->m_pImplLB->GetEntryList().FindEntry( rStr );
+ if ( nPos != LISTBOX_ENTRY_NOTFOUND )
+ nPos -= m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ return nPos;
+}
+
+OUString ComboBox::GetEntry( sal_Int32 nPos ) const
+{
+ const sal_Int32 nMRUCount = m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ if (nPos < 0 || nPos > COMBOBOX_MAX_ENTRIES - nMRUCount)
+ return OUString();
+
+ return m_pImpl->m_pImplLB->GetEntryList().GetEntryText( nPos + nMRUCount );
+}
+
+sal_Int32 ComboBox::GetEntryCount() const
+{
+ if (!m_pImpl->m_pImplLB)
+ return 0;
+ return m_pImpl->m_pImplLB->GetEntryList().GetEntryCount() - m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+}
+
+bool ComboBox::IsTravelSelect() const
+{
+ return m_pImpl->m_pImplLB->IsTravelSelect();
+}
+
+bool ComboBox::IsInDropDown() const
+{
+ // when the dropdown is dismissed, first mbInPopupMode is set to false, and on the next event iteration then
+ // mbPopupMode is set to false
+ return m_pImpl->m_pFloatWin && m_pImpl->m_pFloatWin->IsInPopupMode() && m_pImpl->m_pFloatWin->ImplIsInPrivatePopupMode();
+}
+
+bool ComboBox::IsMultiSelectionEnabled() const
+{
+ return m_pImpl->m_pImplLB->IsMultiSelectionEnabled();
+}
+
+void ComboBox::SetSelectHdl(const Link<ComboBox&,void>& rLink) { m_pImpl->m_SelectHdl = rLink; }
+
+void ComboBox::SetEntryActivateHdl(const Link<Edit&,bool>& rLink)
+{
+ if (!m_pImpl->m_pSubEdit)
+ return;
+ m_pImpl->m_pSubEdit->SetActivateHdl(rLink);
+}
+
+Size ComboBox::GetOptimalSize() const
+{
+ return CalcMinimumSize();
+}
+
+tools::Long ComboBox::getMaxWidthScrollBarAndDownButton() const
+{
+ tools::Long nButtonDownWidth = 0;
+
+ vcl::Window *pBorder = GetWindow( GetWindowType::Border );
+ ImplControlValue aControlValue;
+ tools::Rectangle aContent, aBound;
+
+ // use the full extent of the control
+ tools::Rectangle aArea( Point(), pBorder->GetOutputSizePixel() );
+
+ if ( GetNativeControlRegion(ControlType::Combobox, ControlPart::ButtonDown,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ nButtonDownWidth = aContent.getWidth();
+ }
+
+ tools::Long nScrollBarWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
+
+ return std::max(nScrollBarWidth, nButtonDownWidth);
+}
+
+Size ComboBox::CalcMinimumSize() const
+{
+ Size aSz;
+
+ if (!m_pImpl->m_pImplLB)
+ return aSz;
+
+ if (!IsDropDownBox())
+ {
+ aSz = m_pImpl->m_pImplLB->CalcSize( m_pImpl->m_pImplLB->GetEntryList().GetEntryCount() );
+ aSz.AdjustHeight(m_pImpl->m_nDDHeight );
+ }
+ else
+ {
+ aSz.setHeight( Edit::CalcMinimumSizeForText(GetText()).Height() );
+
+ if (m_pImpl->m_nWidthInChars!= -1)
+ aSz.setWidth(m_pImpl->m_nWidthInChars * approximate_digit_width());
+ else
+ aSz.setWidth(m_pImpl->m_pImplLB->GetMaxEntryWidth());
+ }
+
+ if (m_pImpl->m_nMaxWidthChars != -1)
+ {
+ tools::Long nMaxWidth = m_pImpl->m_nMaxWidthChars * approximate_char_width();
+ aSz.setWidth( std::min(aSz.Width(), nMaxWidth) );
+ }
+
+ if (IsDropDownBox())
+ aSz.AdjustWidth(getMaxWidthScrollBarAndDownButton() );
+
+ ComboBoxBounds aBounds(m_pImpl->calcComboBoxDropDownComponentBounds(
+ Size(0xFFFF, 0xFFFF), Size(0xFFFF, 0xFFFF)));
+ aSz.AdjustWidth(aBounds.aSubEditPos.X()*2 );
+
+ aSz.AdjustWidth(ImplGetExtraXOffset() * 2 );
+
+ aSz = CalcWindowSize( aSz );
+ return aSz;
+}
+
+Size ComboBox::CalcAdjustedSize( const Size& rPrefSize ) const
+{
+ Size aSz = rPrefSize;
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ static_cast<vcl::Window*>(const_cast<ComboBox *>(this))->GetBorder( nLeft, nTop, nRight, nBottom );
+ aSz.AdjustHeight( -(nTop+nBottom) );
+ if ( !IsDropDownBox() )
+ {
+ tools::Long nEntryHeight = CalcBlockSize( 1, 1 ).Height();
+ tools::Long nLines = aSz.Height() / nEntryHeight;
+ if ( nLines < 1 )
+ nLines = 1;
+ aSz.setHeight( nLines * nEntryHeight );
+ aSz.AdjustHeight(m_pImpl->m_nDDHeight );
+ }
+ else
+ {
+ aSz.setHeight( m_pImpl->m_nDDHeight );
+ }
+ aSz.AdjustHeight(nTop+nBottom );
+
+ aSz = CalcWindowSize( aSz );
+ return aSz;
+}
+
+Size ComboBox::CalcBlockSize( sal_uInt16 nColumns, sal_uInt16 nLines ) const
+{
+ // show ScrollBars where appropriate
+ Size aMinSz = CalcMinimumSize();
+ Size aSz;
+
+ // height
+ if ( nLines )
+ {
+ if ( !IsDropDownBox() )
+ aSz.setHeight( m_pImpl->m_pImplLB->CalcSize( nLines ).Height() + m_pImpl->m_nDDHeight );
+ else
+ aSz.setHeight( m_pImpl->m_nDDHeight );
+ }
+ else
+ aSz.setHeight( aMinSz.Height() );
+
+ // width
+ if ( nColumns )
+ aSz.setWidth( nColumns * approximate_char_width() );
+ else
+ aSz.setWidth( aMinSz.Width() );
+
+ if ( IsDropDownBox() )
+ aSz.AdjustWidth(getMaxWidthScrollBarAndDownButton() );
+
+ if ( !IsDropDownBox() )
+ {
+ if ( aSz.Width() < aMinSz.Width() )
+ aSz.AdjustHeight(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ if ( aSz.Height() < aMinSz.Height() )
+ aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ }
+
+ aSz.AdjustWidth(ImplGetExtraXOffset() * 2 );
+
+ aSz = CalcWindowSize( aSz );
+ return aSz;
+}
+
+tools::Long ComboBox::GetDropDownEntryHeight() const
+{
+ return m_pImpl->m_pImplLB->GetEntryHeight();
+}
+
+void ComboBox::GetMaxVisColumnsAndLines( sal_uInt16& rnCols, sal_uInt16& rnLines ) const
+{
+ tools::Long nCharWidth = GetTextWidth(OUString(u'x'));
+ if ( !IsDropDownBox() )
+ {
+ Size aOutSz = GetMainWindow()->GetOutputSizePixel();
+ rnCols = (nCharWidth > 0) ? static_cast<sal_uInt16>(aOutSz.Width()/nCharWidth) : 1;
+ rnLines = static_cast<sal_uInt16>(aOutSz.Height()/GetDropDownEntryHeight());
+ }
+ else
+ {
+ Size aOutSz = m_pImpl->m_pSubEdit->GetOutputSizePixel();
+ rnCols = (nCharWidth > 0) ? static_cast<sal_uInt16>(aOutSz.Width()/nCharWidth) : 1;
+ rnLines = 1;
+ }
+}
+
+void ComboBox::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags )
+{
+ GetMainWindow()->ApplySettings(*pDev);
+
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ vcl::Font aFont = GetMainWindow()->GetDrawPixelFont( pDev );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ pDev->SetTextFillColor();
+
+ // Border/Background
+ pDev->SetLineColor();
+ pDev->SetFillColor();
+ bool bBorder = (GetStyle() & WB_BORDER);
+ bool bBackground = IsControlBackground();
+ if ( bBorder || bBackground )
+ {
+ tools::Rectangle aRect( aPos, aSize );
+ // aRect.Top() += nEditHeight;
+ if ( bBorder )
+ {
+ ImplDrawFrame( pDev, aRect );
+ }
+ if ( bBackground )
+ {
+ pDev->SetFillColor( GetControlBackground() );
+ pDev->DrawRect( aRect );
+ }
+ }
+
+ // contents
+ if ( !IsDropDownBox() )
+ {
+ tools::Long nOnePixel = GetDrawPixel( pDev, 1 );
+ tools::Long nTextHeight = pDev->GetTextHeight();
+ tools::Long nEditHeight = nTextHeight + 6*nOnePixel;
+ DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
+
+ // First, draw the edit part
+ Size aOrigSize(m_pImpl->m_pSubEdit->GetSizePixel());
+ m_pImpl->m_pSubEdit->SetSizePixel(Size(aSize.Width(), nEditHeight));
+ m_pImpl->m_pSubEdit->Draw( pDev, aPos, nFlags );
+ m_pImpl->m_pSubEdit->SetSizePixel(aOrigSize);
+
+ // Second, draw the listbox
+ if ( GetStyle() & WB_CENTER )
+ nTextStyle |= DrawTextFlags::Center;
+ else if ( GetStyle() & WB_RIGHT )
+ nTextStyle |= DrawTextFlags::Right;
+ else
+ nTextStyle |= DrawTextFlags::Left;
+
+ if ( nFlags & SystemTextColorFlags::Mono )
+ {
+ pDev->SetTextColor( COL_BLACK );
+ }
+ else
+ {
+ if ( !IsEnabled() )
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ pDev->SetTextColor( rStyleSettings.GetDisableColor() );
+ }
+ else
+ {
+ pDev->SetTextColor( GetTextColor() );
+ }
+ }
+
+ tools::Rectangle aClip( aPos, aSize );
+ pDev->IntersectClipRegion( aClip );
+ sal_Int32 nLines = static_cast<sal_Int32>( nTextHeight > 0 ? (aSize.Height()-nEditHeight)/nTextHeight : 1 );
+ if ( !nLines )
+ nLines = 1;
+ const sal_Int32 nTEntry = IsReallyVisible() ? m_pImpl->m_pImplLB->GetTopEntry() : 0;
+
+ tools::Rectangle aTextRect( aPos, aSize );
+
+ aTextRect.AdjustLeft(3*nOnePixel );
+ aTextRect.AdjustRight( -(3*nOnePixel) );
+ aTextRect.AdjustTop(nEditHeight + nOnePixel );
+ aTextRect.SetBottom( aTextRect.Top() + nTextHeight );
+
+ // the drawing starts here
+ for ( sal_Int32 n = 0; n < nLines; ++n )
+ {
+ pDev->DrawText( aTextRect, m_pImpl->m_pImplLB->GetEntryList().GetEntryText( n+nTEntry ), nTextStyle );
+ aTextRect.AdjustTop(nTextHeight );
+ aTextRect.AdjustBottom(nTextHeight );
+ }
+ }
+
+ pDev->Pop();
+
+ // Call Edit::Draw after restoring the MapMode...
+ if ( IsDropDownBox() )
+ {
+ Size aOrigSize(m_pImpl->m_pSubEdit->GetSizePixel());
+ m_pImpl->m_pSubEdit->SetSizePixel(GetSizePixel());
+ m_pImpl->m_pSubEdit->Draw( pDev, rPos, nFlags );
+ m_pImpl->m_pSubEdit->SetSizePixel(aOrigSize);
+ // DD-Button ?
+ }
+}
+
+void ComboBox::SetUserDrawHdl(const Link<UserDrawEvent*, void>& rLink)
+{
+ m_pImpl->m_pImplLB->SetUserDrawHdl(rLink);
+}
+
+void ComboBox::SetUserItemSize( const Size& rSz )
+{
+ GetMainWindow()->SetUserItemSize( rSz );
+}
+
+void ComboBox::EnableUserDraw( bool bUserDraw )
+{
+ GetMainWindow()->EnableUserDraw( bUserDraw );
+}
+
+bool ComboBox::IsUserDrawEnabled() const
+{
+ return GetMainWindow()->IsUserDrawEnabled();
+}
+
+void ComboBox::DrawEntry(const UserDrawEvent& rEvt)
+{
+ GetMainWindow()->DrawEntry(*rEvt.GetRenderContext(), rEvt.GetItemId(), /*bDrawImage*/false, /*bDrawText*/false);
+}
+
+void ComboBox::AddSeparator( sal_Int32 n )
+{
+ m_pImpl->m_pImplLB->AddSeparator( n );
+}
+
+void ComboBox::SetMRUEntries( std::u16string_view rEntries )
+{
+ m_pImpl->m_pImplLB->SetMRUEntries( rEntries, ';' );
+}
+
+OUString ComboBox::GetMRUEntries() const
+{
+ return m_pImpl->m_pImplLB ? m_pImpl->m_pImplLB->GetMRUEntries( ';' ) : OUString();
+}
+
+void ComboBox::SetMaxMRUCount( sal_Int32 n )
+{
+ m_pImpl->m_pImplLB->SetMaxMRUCount( n );
+}
+
+sal_Int32 ComboBox::GetMaxMRUCount() const
+{
+ return m_pImpl->m_pImplLB ? m_pImpl->m_pImplLB->GetMaxMRUCount() : 0;
+}
+
+sal_uInt16 ComboBox::GetDisplayLineCount() const
+{
+ return m_pImpl->m_pImplLB ? m_pImpl->m_pImplLB->GetDisplayLineCount() : 0;
+}
+
+void ComboBox::SetEntryData( sal_Int32 nPos, void* pNewData )
+{
+ m_pImpl->m_pImplLB->SetEntryData( nPos + m_pImpl->m_pImplLB->GetEntryList().GetMRUCount(), pNewData );
+}
+
+void* ComboBox::GetEntryData( sal_Int32 nPos ) const
+{
+ return m_pImpl->m_pImplLB->GetEntryList().GetEntryData(
+ nPos + m_pImpl->m_pImplLB->GetEntryList().GetMRUCount() );
+}
+
+sal_Int32 ComboBox::GetTopEntry() const
+{
+ sal_Int32 nPos = GetEntryCount() ? m_pImpl->m_pImplLB->GetTopEntry() : LISTBOX_ENTRY_NOTFOUND;
+ if (nPos < m_pImpl->m_pImplLB->GetEntryList().GetMRUCount())
+ nPos = 0;
+ return nPos;
+}
+
+tools::Rectangle ComboBox::GetDropDownPosSizePixel() const
+{
+ return m_pImpl->m_pFloatWin
+ ? m_pImpl->m_pFloatWin->GetWindowExtentsRelative(this)
+ : tools::Rectangle();
+}
+
+const Wallpaper& ComboBox::GetDisplayBackground() const
+{
+ if (!m_pImpl->m_pSubEdit->IsBackground())
+ return Control::GetDisplayBackground();
+
+ const Wallpaper& rBack = m_pImpl->m_pSubEdit->GetBackground();
+ if( ! rBack.IsBitmap() &&
+ ! rBack.IsGradient() &&
+ rBack == Wallpaper(COL_TRANSPARENT)
+ )
+ return Control::GetDisplayBackground();
+ return rBack;
+}
+
+sal_Int32 ComboBox::GetSelectedEntryCount() const
+{
+ return m_pImpl->m_pImplLB->GetEntryList().GetSelectedEntryCount();
+}
+
+sal_Int32 ComboBox::GetSelectedEntryPos( sal_Int32 nIndex ) const
+{
+ sal_Int32 nPos = m_pImpl->m_pImplLB->GetEntryList().GetSelectedEntryPos( nIndex );
+ if ( nPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ if (nPos < m_pImpl->m_pImplLB->GetEntryList().GetMRUCount())
+ nPos = m_pImpl->m_pImplLB->GetEntryList().FindEntry(m_pImpl->m_pImplLB->GetEntryList().GetEntryText(nPos));
+ nPos = sal::static_int_cast<sal_Int32>(nPos - m_pImpl->m_pImplLB->GetEntryList().GetMRUCount());
+ }
+ return nPos;
+}
+
+bool ComboBox::IsEntryPosSelected( sal_Int32 nPos ) const
+{
+ return m_pImpl->m_pImplLB->GetEntryList().IsEntryPosSelected(
+ nPos + m_pImpl->m_pImplLB->GetEntryList().GetMRUCount() );
+}
+
+void ComboBox::SelectEntryPos( sal_Int32 nPos, bool bSelect)
+{
+ if (nPos < m_pImpl->m_pImplLB->GetEntryList().GetEntryCount())
+ m_pImpl->m_pImplLB->SelectEntry(
+ nPos + m_pImpl->m_pImplLB->GetEntryList().GetMRUCount(), bSelect);
+}
+
+void ComboBox::SetNoSelection()
+{
+ m_pImpl->m_pImplLB->SetNoSelection();
+ m_pImpl->m_pSubEdit->SetText( OUString() );
+}
+
+tools::Rectangle ComboBox::GetBoundingRectangle( sal_Int32 nItem ) const
+{
+ tools::Rectangle aRect = GetMainWindow()->GetBoundingRectangle( nItem );
+ tools::Rectangle aOffset = GetMainWindow()->GetWindowExtentsRelative( static_cast<vcl::Window*>(const_cast<ComboBox *>(this)) );
+ aRect.Move( aOffset.Left(), aOffset.Top() );
+ return aRect;
+}
+
+void ComboBox::SetBorderStyle( WindowBorderStyle nBorderStyle )
+{
+ Window::SetBorderStyle( nBorderStyle );
+ if ( !IsDropDownBox() )
+ {
+ m_pImpl->m_pSubEdit->SetBorderStyle( nBorderStyle );
+ m_pImpl->m_pImplLB->SetBorderStyle( nBorderStyle );
+ }
+}
+
+ImplListBoxWindow* ComboBox::GetMainWindow() const
+{
+ return m_pImpl->m_pImplLB->GetMainWindow();
+}
+
+tools::Long ComboBox::GetIndexForPoint( const Point& rPoint, sal_Int32& rPos ) const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+
+ // check whether rPoint fits at all
+ tools::Long nIndex = Control::GetIndexForPoint( rPoint );
+ if( nIndex != -1 )
+ {
+ // point must be either in main list window
+ // or in impl window (dropdown case)
+ ImplListBoxWindow* rMain = GetMainWindow();
+
+ // convert coordinates to ImplListBoxWindow pixel coordinate space
+ Point aConvPoint = LogicToPixel( rPoint );
+ aConvPoint = OutputToAbsoluteScreenPixel( aConvPoint );
+ aConvPoint = rMain->AbsoluteScreenToOutputPixel( aConvPoint );
+ aConvPoint = rMain->PixelToLogic( aConvPoint );
+
+ // try to find entry
+ sal_Int32 nEntry = rMain->GetEntryPosForPoint( aConvPoint );
+ if( nEntry == LISTBOX_ENTRY_NOTFOUND )
+ nIndex = -1;
+ else
+ rPos = nEntry;
+ }
+
+ // get line relative index
+ if( nIndex != -1 )
+ nIndex = ToRelativeLineIndex( nIndex );
+
+ return nIndex;
+}
+
+ComboBoxBounds ComboBox::Impl::calcComboBoxDropDownComponentBounds(
+ const Size &rOutSz, const Size &rBorderOutSz) const
+{
+ ComboBoxBounds aBounds;
+
+ tools::Long nTop = 0;
+ tools::Long nBottom = rOutSz.Height();
+
+ vcl::Window *pBorder = m_rThis.GetWindow( GetWindowType::Border );
+ ImplControlValue aControlValue;
+ Point aPoint;
+ tools::Rectangle aContent, aBound;
+
+ // use the full extent of the control
+ tools::Rectangle aArea( aPoint, rBorderOutSz );
+
+ if (m_rThis.GetNativeControlRegion(ControlType::Combobox, ControlPart::ButtonDown,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ // convert back from border space to local coordinates
+ aPoint = pBorder->ScreenToOutputPixel(m_rThis.OutputToScreenPixel(aPoint));
+ aContent.Move(-aPoint.X(), -aPoint.Y());
+
+ aBounds.aButtonPos = Point(aContent.Left(), nTop);
+ aBounds.aButtonSize = Size(aContent.getWidth(), (nBottom-nTop));
+
+ // adjust the size of the edit field
+ if (m_rThis.GetNativeControlRegion(ControlType::Combobox, ControlPart::SubEdit,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ // convert back from border space to local coordinates
+ aContent.Move(-aPoint.X(), -aPoint.Y());
+
+ // use the themes drop down size
+ aBounds.aSubEditPos = aContent.TopLeft();
+ aBounds.aSubEditSize = aContent.GetSize();
+ }
+ else
+ {
+ // use the themes drop down size for the button
+ aBounds.aSubEditSize = Size(rOutSz.Width() - aContent.getWidth(), rOutSz.Height());
+ }
+ }
+ else
+ {
+ tools::Long nSBWidth = m_rThis.GetSettings().GetStyleSettings().GetScrollBarSize();
+ nSBWidth = m_rThis.CalcZoom( nSBWidth );
+ aBounds.aSubEditSize = Size(rOutSz.Width() - nSBWidth, rOutSz.Height());
+ aBounds.aButtonPos = Point(rOutSz.Width() - nSBWidth, nTop);
+ aBounds.aButtonSize = Size(nSBWidth, (nBottom-nTop));
+ }
+ return aBounds;
+}
+
+void ComboBox::SetWidthInChars(sal_Int32 nWidthInChars)
+{
+ if (nWidthInChars != m_pImpl->m_nWidthInChars)
+ {
+ m_pImpl->m_nWidthInChars = nWidthInChars;
+ queue_resize();
+ }
+}
+
+void ComboBox::setMaxWidthChars(sal_Int32 nWidth)
+{
+ if (nWidth != m_pImpl->m_nMaxWidthChars)
+ {
+ m_pImpl->m_nMaxWidthChars = nWidth;
+ queue_resize();
+ }
+}
+
+bool ComboBox::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "width-chars")
+ SetWidthInChars(rValue.toInt32());
+ else if (rKey == "max-width-chars")
+ setMaxWidthChars(rValue.toInt32());
+ else if (rKey == "can-focus")
+ {
+ // as far as I can see in Gtk, setting a ComboBox as can.focus means
+ // the focus gets stuck in it, so try here to behave like gtk does
+ // with the settings that work, i.e. can.focus of false doesn't
+ // set the hard WB_NOTABSTOP
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_TABSTOP|WB_NOTABSTOP);
+ if (toBool(rValue))
+ nBits |= WB_TABSTOP;
+ SetStyle(nBits);
+ }
+ else if (rKey == "placeholder-text")
+ SetPlaceholderText(rValue);
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+FactoryFunction ComboBox::GetUITestFactory() const
+{
+ return ComboBoxUIObject::create;
+}
+
+void ComboBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Control::DumpAsPropertyTree(rJsonWriter);
+
+ {
+ auto entriesNode = rJsonWriter.startArray("entries");
+ for (int i = 0; i < GetEntryCount(); ++i)
+ {
+ rJsonWriter.putSimpleValue(GetEntry(i));
+ }
+ }
+
+ {
+ auto selectedNode = rJsonWriter.startArray("selectedEntries");
+ for (int i = 0; i < GetSelectedEntryCount(); ++i)
+ {
+ rJsonWriter.putSimpleValue(OUString::number(GetSelectedEntryPos(i)));
+ }
+ }
+
+ rJsonWriter.put("selectedCount", GetSelectedEntryCount());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */