1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <vcl/toolkit/combobox.hxx>
21 
22 #include <set>
23 
24 #include <comphelper/string.hxx>
25 #include <vcl/toolkit/lstbox.hxx>
26 #include <vcl/builder.hxx>
27 #include <vcl/commandevent.hxx>
28 #include <vcl/event.hxx>
29 #include <vcl/settings.hxx>
30 #include <vcl/vclevent.hxx>
31 #include <vcl/uitest/uiobject.hxx>
32 #include <sal/log.hxx>
33 
34 #include <listbox.hxx>
35 #include <controldata.hxx>
36 #include <comphelper/lok.hxx>
37 #include <tools/json_writer.hxx>
38 
39 namespace {
40 
41 struct ComboBoxBounds
42 {
43     Point aSubEditPos;
44     Size aSubEditSize;
45 
46     Point aButtonPos;
47     Size aButtonSize;
48 };
49 
50 }
51 
52 struct ComboBox::Impl
53 {
54     ComboBox &          m_rThis;
55     VclPtr<Edit>        m_pSubEdit;
56     VclPtr<ImplListBox> m_pImplLB;
57     VclPtr<ImplBtn>     m_pBtn;
58     VclPtr<ImplListBoxFloatingWindow>  m_pFloatWin;
59     sal_uInt16          m_nDDHeight;
60     sal_Unicode         m_cMultiSep;
61     bool                m_isDDAutoSize        : 1;
62     bool                m_isSyntheticModify   : 1;
63     bool                m_isKeyBoardModify    : 1;
64     bool                m_isMatchCase         : 1;
65     sal_Int32           m_nMaxWidthChars;
66     sal_Int32           m_nWidthInChars;
67     Link<ComboBox&,void>               m_SelectHdl;
68 
ImplComboBox::Impl69     explicit Impl(ComboBox & rThis)
70         : m_rThis(rThis)
71         , m_nDDHeight(0)
72         , m_cMultiSep(0)
73         , m_isDDAutoSize(false)
74         , m_isSyntheticModify(false)
75         , m_isKeyBoardModify(false)
76         , m_isMatchCase(false)
77         , m_nMaxWidthChars(0)
78         , m_nWidthInChars(-1)
79     {
80     }
81 
82     void ImplInitComboBoxData();
83     void ImplUpdateFloatSelection();
84     ComboBoxBounds calcComboBoxDropDownComponentBounds(
85         const Size &rOutSize, const Size &rBorderOutSize) const;
86 
87     DECL_LINK( ImplSelectHdl, LinkParamNone*, void );
88     DECL_LINK( ImplCancelHdl, LinkParamNone*, void );
89     DECL_LINK( ImplDoubleClickHdl, ImplListBoxWindow*, void );
90     DECL_LINK( ImplClickBtnHdl, void*, void );
91     DECL_LINK( ImplPopupModeEndHdl, FloatingWindow*, void );
92     DECL_LINK( ImplSelectionChangedHdl, sal_Int32, void );
93     DECL_LINK( ImplAutocompleteHdl, Edit&, void );
94     DECL_LINK( ImplListItemSelectHdl , LinkParamNone*, void );
95 };
96 
97 
lcl_GetSelectedEntries(::std::set<sal_Int32> & rSelectedPos,const OUString & rText,sal_Unicode cTokenSep,const ImplEntryList * pEntryList)98 static void lcl_GetSelectedEntries( ::std::set< sal_Int32 >& rSelectedPos, const OUString& rText, sal_Unicode cTokenSep, const ImplEntryList* pEntryList )
99 {
100     if (rText.isEmpty())
101         return;
102 
103     sal_Int32 nIdx{0};
104     do {
105         const sal_Int32 nPos = pEntryList->FindEntry(comphelper::string::strip(rText.getToken(0, cTokenSep, nIdx), ' '));
106         if ( nPos != LISTBOX_ENTRY_NOTFOUND )
107             rSelectedPos.insert( nPos );
108     } while (nIdx>=0);
109 }
110 
ComboBox(vcl::Window * const pParent,WinBits const nStyle)111 ComboBox::ComboBox(vcl::Window *const pParent, WinBits const nStyle)
112     : Edit( WindowType::COMBOBOX )
113     , m_pImpl(new Impl(*this))
114 {
115     m_pImpl->ImplInitComboBoxData();
116     ImplInit( pParent, nStyle );
117     SetWidthInChars(-1);
118 }
119 
~ComboBox()120 ComboBox::~ComboBox()
121 {
122     disposeOnce();
123 }
124 
dispose()125 void ComboBox::dispose()
126 {
127     m_pImpl->m_pSubEdit.disposeAndClear();
128 
129     VclPtr< ImplListBox > pImplLB = m_pImpl->m_pImplLB;
130     m_pImpl->m_pImplLB.clear();
131     pImplLB.disposeAndClear();
132 
133     m_pImpl->m_pFloatWin.disposeAndClear();
134     m_pImpl->m_pBtn.disposeAndClear();
135     Edit::dispose();
136 }
137 
ImplInitComboBoxData()138 void ComboBox::Impl::ImplInitComboBoxData()
139 {
140     m_pSubEdit.disposeAndClear();
141     m_pBtn              = nullptr;
142     m_pImplLB           = nullptr;
143     m_pFloatWin         = nullptr;
144 
145     m_nDDHeight         = 0;
146     m_isDDAutoSize      = true;
147     m_isSyntheticModify = false;
148     m_isKeyBoardModify  = false;
149     m_isMatchCase       = false;
150     m_cMultiSep         = ';';
151     m_nMaxWidthChars    = -1;
152     m_nWidthInChars     = -1;
153 }
154 
ImplCalcEditHeight()155 void ComboBox::ImplCalcEditHeight()
156 {
157     sal_Int32 nLeft, nTop, nRight, nBottom;
158     GetBorder( nLeft, nTop, nRight, nBottom );
159     m_pImpl->m_nDDHeight = static_cast<sal_uInt16>(m_pImpl->m_pSubEdit->GetTextHeight() + nTop + nBottom + 4);
160     if ( !IsDropDownBox() )
161         m_pImpl->m_nDDHeight += 4;
162 
163     tools::Rectangle aCtrlRegion( Point( 0, 0 ), Size( 10, 10 ) );
164     tools::Rectangle aBoundRegion, aContentRegion;
165     ImplControlValue aControlValue;
166     ControlType aType = IsDropDownBox() ? ControlType::Combobox : ControlType::Editbox;
167     if( GetNativeControlRegion( aType, ControlPart::Entire,
168                                 aCtrlRegion,
169                                 ControlState::ENABLED,
170                                 aControlValue,
171                                 aBoundRegion, aContentRegion ) )
172     {
173         const tools::Long nNCHeight = aBoundRegion.GetHeight();
174         if (m_pImpl->m_nDDHeight < nNCHeight)
175             m_pImpl->m_nDDHeight = sal::static_int_cast<sal_uInt16>(nNCHeight);
176     }
177 }
178 
ImplInit(vcl::Window * pParent,WinBits nStyle)179 void ComboBox::ImplInit( vcl::Window* pParent, WinBits nStyle )
180 {
181     bool bNoBorder = ( nStyle & WB_NOBORDER ) != 0;
182     if ( !(nStyle & WB_DROPDOWN) )
183     {
184         nStyle &= ~WB_BORDER;
185         nStyle |= WB_NOBORDER;
186     }
187     else
188     {
189         if ( !bNoBorder )
190             nStyle |= WB_BORDER;
191     }
192 
193     Edit::ImplInit( pParent, nStyle );
194     SetBackground();
195 
196     // DropDown ?
197     WinBits nEditStyle = nStyle & ( WB_LEFT | WB_RIGHT | WB_CENTER );
198     WinBits nListStyle = nStyle;
199     if( nStyle & WB_DROPDOWN )
200     {
201         m_pImpl->m_pFloatWin = VclPtr<ImplListBoxFloatingWindow>::Create( this );
202         if (!IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Focus))
203             m_pImpl->m_pFloatWin->RequestDoubleBuffering(true);
204         m_pImpl->m_pFloatWin->SetAutoWidth( true );
205         m_pImpl->m_pFloatWin->SetPopupModeEndHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplPopupModeEndHdl) );
206 
207         m_pImpl->m_pBtn = VclPtr<ImplBtn>::Create( this, WB_NOLIGHTBORDER | WB_RECTSTYLE );
208         ImplInitDropDownButton( m_pImpl->m_pBtn );
209         m_pImpl->m_pBtn->SetMBDownHdl( LINK( m_pImpl.get(), ComboBox::Impl, ImplClickBtnHdl ) );
210         m_pImpl->m_pBtn->Show();
211 
212         nEditStyle |= WB_NOBORDER;
213         nListStyle &= ~WB_BORDER;
214         nListStyle |= WB_NOBORDER;
215     }
216     else
217     {
218         if ( !bNoBorder )
219         {
220             nEditStyle |= WB_BORDER;
221             nListStyle &= ~WB_NOBORDER;
222             nListStyle |= WB_BORDER;
223         }
224     }
225 
226     m_pImpl->m_pSubEdit.set( VclPtr<Edit>::Create( this, nEditStyle ) );
227     m_pImpl->m_pSubEdit->EnableRTL( false );
228     SetSubEdit( m_pImpl->m_pSubEdit );
229     m_pImpl->m_pSubEdit->SetPosPixel( Point() );
230     EnableAutocomplete( true );
231     m_pImpl->m_pSubEdit->Show();
232 
233     vcl::Window* pLBParent = this;
234     if (m_pImpl->m_pFloatWin)
235         pLBParent = m_pImpl->m_pFloatWin;
236     m_pImpl->m_pImplLB = VclPtr<ImplListBox>::Create( pLBParent, nListStyle|WB_SIMPLEMODE|WB_AUTOHSCROLL );
237     m_pImpl->m_pImplLB->SetPosPixel( Point() );
238     m_pImpl->m_pImplLB->SetSelectHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplSelectHdl) );
239     m_pImpl->m_pImplLB->SetCancelHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplCancelHdl) );
240     m_pImpl->m_pImplLB->SetDoubleClickHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplDoubleClickHdl) );
241     m_pImpl->m_pImplLB->SetSelectionChangedHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplSelectionChangedHdl) );
242     m_pImpl->m_pImplLB->SetListItemSelectHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplListItemSelectHdl) );
243     m_pImpl->m_pImplLB->Show();
244 
245     if (m_pImpl->m_pFloatWin)
246         m_pImpl->m_pFloatWin->SetImplListBox( m_pImpl->m_pImplLB );
247     else
248         GetMainWindow()->AllowGrabFocus( true );
249 
250     ImplCalcEditHeight();
251 
252     SetCompoundControl( true );
253 }
254 
ImplInitStyle(WinBits nStyle)255 WinBits ComboBox::ImplInitStyle( WinBits nStyle )
256 {
257     if ( !(nStyle & WB_NOTABSTOP) )
258         nStyle |= WB_TABSTOP;
259     if ( !(nStyle & WB_NOGROUP) )
260         nStyle |= WB_GROUP;
261     return nStyle;
262 }
263 
EnableAutocomplete(bool bEnable,bool bMatchCase)264 void ComboBox::EnableAutocomplete( bool bEnable, bool bMatchCase )
265 {
266     m_pImpl->m_isMatchCase = bMatchCase;
267 
268     if ( bEnable )
269         m_pImpl->m_pSubEdit->SetAutocompleteHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplAutocompleteHdl) );
270     else
271         m_pImpl->m_pSubEdit->SetAutocompleteHdl( Link<Edit&,void>() );
272 }
273 
IsAutocompleteEnabled() const274 bool ComboBox::IsAutocompleteEnabled() const
275 {
276     return m_pImpl->m_pSubEdit->GetAutocompleteHdl().IsSet();
277 }
278 
IMPL_LINK_NOARG(ComboBox::Impl,ImplClickBtnHdl,void *,void)279 IMPL_LINK_NOARG(ComboBox::Impl, ImplClickBtnHdl, void*, void)
280 {
281     m_rThis.CallEventListeners( VclEventId::DropdownPreOpen );
282     m_pSubEdit->GrabFocus();
283     if (!m_pImplLB->GetEntryList()->GetMRUCount())
284         ImplUpdateFloatSelection();
285     else
286         m_pImplLB->SelectEntry( 0 , true );
287     m_pBtn->SetPressed( true );
288     m_rThis.SetSelection( Selection( 0, SELECTION_MAX ) );
289     m_pFloatWin->StartFloat( true );
290     m_rThis.CallEventListeners( VclEventId::DropdownOpen );
291 
292     m_rThis.ImplClearLayoutData();
293     if (m_pImplLB)
294         m_pImplLB->GetMainWindow()->ImplClearLayoutData();
295 }
296 
IMPL_LINK_NOARG(ComboBox::Impl,ImplPopupModeEndHdl,FloatingWindow *,void)297 IMPL_LINK_NOARG(ComboBox::Impl, ImplPopupModeEndHdl, FloatingWindow*, void)
298 {
299     if (m_pFloatWin->IsPopupModeCanceled())
300     {
301         if (!m_pImplLB->GetEntryList()->IsEntryPosSelected(
302                     m_pFloatWin->GetPopupModeStartSaveSelection()))
303         {
304             m_pImplLB->SelectEntry(m_pFloatWin->GetPopupModeStartSaveSelection(), true);
305             bool bTravelSelect = m_pImplLB->IsTravelSelect();
306             m_pImplLB->SetTravelSelect( true );
307             m_rThis.Select();
308             m_pImplLB->SetTravelSelect( bTravelSelect );
309         }
310     }
311 
312     m_rThis.ImplClearLayoutData();
313     if (m_pImplLB)
314         m_pImplLB->GetMainWindow()->ImplClearLayoutData();
315 
316     m_pBtn->SetPressed( false );
317     m_rThis.CallEventListeners( VclEventId::DropdownClose );
318 }
319 
IMPL_LINK(ComboBox::Impl,ImplAutocompleteHdl,Edit &,rEdit,void)320 IMPL_LINK(ComboBox::Impl, ImplAutocompleteHdl, Edit&, rEdit, void)
321 {
322     Selection           aSel = rEdit.GetSelection();
323 
324     {
325         OUString    aFullText = rEdit.GetText();
326         OUString    aStartText = aFullText.copy( 0, static_cast<sal_Int32>(aSel.Max()) );
327         sal_Int32   nStart = m_pImplLB->GetCurrentPos();
328 
329         if ( nStart == LISTBOX_ENTRY_NOTFOUND )
330             nStart = 0;
331 
332         sal_Int32 nPos = LISTBOX_ENTRY_NOTFOUND;
333         if (!m_isMatchCase)
334         {
335             // Try match case insensitive from current position
336             nPos = m_pImplLB->GetEntryList()->FindMatchingEntry(aStartText, nStart, true);
337             if ( nPos == LISTBOX_ENTRY_NOTFOUND )
338                 // Try match case insensitive, but from start
339                 nPos = m_pImplLB->GetEntryList()->FindMatchingEntry(aStartText, 0, true);
340         }
341 
342         if ( nPos == LISTBOX_ENTRY_NOTFOUND )
343             // Try match full from current position
344             nPos = m_pImplLB->GetEntryList()->FindMatchingEntry(aStartText, nStart, false);
345         if ( nPos == LISTBOX_ENTRY_NOTFOUND )
346             //  Match full, but from start
347             nPos = m_pImplLB->GetEntryList()->FindMatchingEntry(aStartText, 0, false);
348 
349         if ( nPos != LISTBOX_ENTRY_NOTFOUND )
350         {
351             OUString aText = m_pImplLB->GetEntryList()->GetEntryText( nPos );
352             Selection aSelection( aText.getLength(), aStartText.getLength() );
353             rEdit.SetText( aText, aSelection );
354         }
355     }
356 }
357 
IMPL_LINK_NOARG(ComboBox::Impl,ImplSelectHdl,LinkParamNone *,void)358 IMPL_LINK_NOARG(ComboBox::Impl, ImplSelectHdl, LinkParamNone*, void)
359 {
360     bool bPopup = m_rThis.IsInDropDown();
361     bool bCallSelect = false;
362     if (m_pImplLB->IsSelectionChanged() || bPopup)
363     {
364         OUString aText;
365         if (m_rThis.IsMultiSelectionEnabled())
366         {
367             aText = m_pSubEdit->GetText();
368 
369             // remove all entries to which there is an entry, but which is not selected
370             sal_Int32 nIndex = 0;
371             while ( nIndex >= 0 )
372             {
373                 sal_Int32  nPrevIndex = nIndex;
374                 OUString   aToken = aText.getToken( 0, m_cMultiSep, nIndex );
375                 sal_Int32  nTokenLen = aToken.getLength();
376                 aToken = comphelper::string::strip(aToken, ' ');
377                 sal_Int32 nP = m_pImplLB->GetEntryList()->FindEntry( aToken );
378                 if ((nP != LISTBOX_ENTRY_NOTFOUND) && (!m_pImplLB->GetEntryList()->IsEntryPosSelected(nP)))
379                 {
380                     aText = aText.replaceAt( nPrevIndex, nTokenLen, "" );
381                     nIndex = nIndex - nTokenLen;
382                     sal_Int32 nSepCount=0;
383                     if ((nPrevIndex+nSepCount < aText.getLength()) && (aText[nPrevIndex+nSepCount] == m_cMultiSep))
384                     {
385                         nIndex--;
386                         ++nSepCount;
387                     }
388                     aText = aText.replaceAt( nPrevIndex, nSepCount, "" );
389                 }
390                 aText = comphelper::string::strip(aText, ' ');
391             }
392 
393             // attach missing entries
394             ::std::set< sal_Int32 > aSelInText;
395             lcl_GetSelectedEntries( aSelInText, aText, m_cMultiSep, m_pImplLB->GetEntryList() );
396             sal_Int32 nSelectedEntries = m_pImplLB->GetEntryList()->GetSelectedEntryCount();
397             for ( sal_Int32 n = 0; n < nSelectedEntries; n++ )
398             {
399                 sal_Int32 nP = m_pImplLB->GetEntryList()->GetSelectedEntryPos( n );
400                 if ( !aSelInText.count( nP ) )
401                 {
402                     if (!aText.isEmpty() && (aText[aText.getLength()-1] != m_cMultiSep))
403                         aText += OUStringChar(m_cMultiSep);
404                     if ( !aText.isEmpty() )
405                         aText += " ";   // slightly loosen
406                     aText += m_pImplLB->GetEntryList()->GetEntryText( nP ) +
407                         OUStringChar(m_cMultiSep);
408                 }
409             }
410             aText = comphelper::string::stripEnd( aText, m_cMultiSep );
411         }
412         else
413         {
414             aText = m_pImplLB->GetEntryList()->GetSelectedEntry( 0 );
415         }
416 
417         m_pSubEdit->SetText( aText );
418 
419         Selection aNewSelection( 0, aText.getLength() );
420         if (m_rThis.IsMultiSelectionEnabled())
421             aNewSelection.Min() = aText.getLength();
422         m_pSubEdit->SetSelection( aNewSelection );
423 
424         bCallSelect = true;
425     }
426 
427     // #84652# Call GrabFocus and EndPopupMode before calling Select/Modify, but after changing the text
428     bool bMenuSelect = bPopup && !m_pImplLB->IsTravelSelect() && (!m_rThis.IsMultiSelectionEnabled() || !m_pImplLB->GetSelectModifier());
429     if (bMenuSelect)
430     {
431         m_pFloatWin->EndPopupMode();
432         m_rThis.GrabFocus();
433     }
434 
435     if ( bCallSelect )
436     {
437         m_isKeyBoardModify = !bMenuSelect;
438         m_pSubEdit->SetModifyFlag();
439         m_isSyntheticModify = true;
440         m_rThis.Modify();
441         m_isSyntheticModify = false;
442         m_rThis.Select();
443         m_isKeyBoardModify = false;
444     }
445 }
446 
IsSyntheticModify() const447 bool ComboBox::IsSyntheticModify() const
448 {
449     return m_pImpl->m_isSyntheticModify;
450 }
451 
IsModifyByKeyboard() const452 bool ComboBox::IsModifyByKeyboard() const
453 {
454     return m_pImpl->m_isKeyBoardModify;
455 }
456 
IMPL_LINK_NOARG(ComboBox::Impl,ImplListItemSelectHdl,LinkParamNone *,void)457 IMPL_LINK_NOARG( ComboBox::Impl, ImplListItemSelectHdl, LinkParamNone*, void )
458 {
459     m_rThis.CallEventListeners( VclEventId::DropdownSelect );
460 }
461 
IMPL_LINK_NOARG(ComboBox::Impl,ImplCancelHdl,LinkParamNone *,void)462 IMPL_LINK_NOARG(ComboBox::Impl, ImplCancelHdl, LinkParamNone*, void)
463 {
464     if (m_rThis.IsInDropDown())
465         m_pFloatWin->EndPopupMode();
466 }
467 
IMPL_LINK(ComboBox::Impl,ImplSelectionChangedHdl,sal_Int32,nChanged,void)468 IMPL_LINK( ComboBox::Impl, ImplSelectionChangedHdl, sal_Int32, nChanged, void )
469 {
470     if (!m_pImplLB->IsTrackingSelect())
471     {
472         if (!m_pSubEdit->IsReadOnly() && m_pImplLB->GetEntryList()->IsEntryPosSelected(nChanged))
473             m_pSubEdit->SetText(m_pImplLB->GetEntryList()->GetEntryText(nChanged));
474     }
475 }
476 
IMPL_LINK_NOARG(ComboBox::Impl,ImplDoubleClickHdl,ImplListBoxWindow *,void)477 IMPL_LINK_NOARG(ComboBox::Impl, ImplDoubleClickHdl, ImplListBoxWindow*, void)
478 {
479     m_rThis.DoubleClick();
480 }
481 
ToggleDropDown()482 void ComboBox::ToggleDropDown()
483 {
484     if( !IsDropDownBox() )
485         return;
486 
487     if (m_pImpl->m_pFloatWin->IsInPopupMode())
488         m_pImpl->m_pFloatWin->EndPopupMode();
489     else
490     {
491         m_pImpl->m_pSubEdit->GrabFocus();
492         if (!m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount())
493             m_pImpl->ImplUpdateFloatSelection();
494         else
495             m_pImpl->m_pImplLB->SelectEntry( 0 , true );
496         CallEventListeners( VclEventId::DropdownPreOpen );
497         m_pImpl->m_pBtn->SetPressed( true );
498         SetSelection( Selection( 0, SELECTION_MAX ) );
499         m_pImpl->m_pFloatWin->StartFloat( true );
500         CallEventListeners( VclEventId::DropdownOpen );
501     }
502 }
503 
Select()504 void ComboBox::Select()
505 {
506     ImplCallEventListenersAndHandler( VclEventId::ComboboxSelect, [this] () { m_pImpl->m_SelectHdl.Call(*this); } );
507 }
508 
DoubleClick()509 void ComboBox::DoubleClick()
510 {
511     ImplCallEventListenersAndHandler( VclEventId::ComboboxDoubleClick, [] () {} );
512 }
513 
IsAutoSizeEnabled() const514 bool ComboBox::IsAutoSizeEnabled() const { return m_pImpl->m_isDDAutoSize; }
515 
EnableAutoSize(bool bAuto)516 void ComboBox::EnableAutoSize( bool bAuto )
517 {
518     m_pImpl->m_isDDAutoSize = bAuto;
519     if (m_pImpl->m_pFloatWin)
520     {
521         if (bAuto && !m_pImpl->m_pFloatWin->GetDropDownLineCount())
522         {
523             // Adapt to GetListBoxMaximumLineCount here; was on fixed number of five before
524             AdaptDropDownLineCountToMaximum();
525         }
526         else if ( !bAuto )
527         {
528             m_pImpl->m_pFloatWin->SetDropDownLineCount( 0 );
529         }
530     }
531 }
532 
SetDropDownLineCount(sal_uInt16 nLines)533 void ComboBox::SetDropDownLineCount( sal_uInt16 nLines )
534 {
535     if (m_pImpl->m_pFloatWin)
536         m_pImpl->m_pFloatWin->SetDropDownLineCount( nLines );
537 }
538 
AdaptDropDownLineCountToMaximum()539 void ComboBox::AdaptDropDownLineCountToMaximum()
540 {
541     // Adapt to maximum allowed number.
542     // Limit for LOK as we can't render outside of the dialog canvas.
543     if (comphelper::LibreOfficeKit::isActive())
544         SetDropDownLineCount(11);
545     else
546         SetDropDownLineCount(GetSettings().GetStyleSettings().GetListBoxMaximumLineCount());
547 }
548 
GetDropDownLineCount() const549 sal_uInt16 ComboBox::GetDropDownLineCount() const
550 {
551     sal_uInt16 nLines = 0;
552     if (m_pImpl->m_pFloatWin)
553         nLines = m_pImpl->m_pFloatWin->GetDropDownLineCount();
554     return nLines;
555 }
556 
setPosSizePixel(tools::Long nX,tools::Long nY,tools::Long nWidth,tools::Long nHeight,PosSizeFlags nFlags)557 void ComboBox::setPosSizePixel( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
558                                 PosSizeFlags nFlags )
559 {
560     if( IsDropDownBox() && ( nFlags & PosSizeFlags::Size ) )
561     {
562         Size aPrefSz = m_pImpl->m_pFloatWin->GetPrefSize();
563         if ((nFlags & PosSizeFlags::Height) && (nHeight >= 2*m_pImpl->m_nDDHeight))
564             aPrefSz.setHeight( nHeight-m_pImpl->m_nDDHeight );
565         if ( nFlags & PosSizeFlags::Width )
566             aPrefSz.setWidth( nWidth );
567         m_pImpl->m_pFloatWin->SetPrefSize( aPrefSz );
568 
569         if (IsAutoSizeEnabled())
570             nHeight = m_pImpl->m_nDDHeight;
571     }
572 
573     Edit::setPosSizePixel( nX, nY, nWidth, nHeight, nFlags );
574 }
575 
Resize()576 void ComboBox::Resize()
577 {
578     Control::Resize();
579 
580     if (m_pImpl->m_pSubEdit)
581     {
582         Size aOutSz = GetOutputSizePixel();
583         if( IsDropDownBox() )
584         {
585             ComboBoxBounds aBounds(m_pImpl->calcComboBoxDropDownComponentBounds(aOutSz,
586                 GetWindow(GetWindowType::Border)->GetOutputSizePixel()));
587             m_pImpl->m_pSubEdit->SetPosSizePixel(aBounds.aSubEditPos, aBounds.aSubEditSize);
588             m_pImpl->m_pBtn->SetPosSizePixel(aBounds.aButtonPos, aBounds.aButtonSize);
589         }
590         else
591         {
592             m_pImpl->m_pSubEdit->SetSizePixel(Size(aOutSz.Width(), m_pImpl->m_nDDHeight));
593             m_pImpl->m_pImplLB->setPosSizePixel(0, m_pImpl->m_nDDHeight,
594                     aOutSz.Width(), aOutSz.Height() - m_pImpl->m_nDDHeight);
595             if ( !GetText().isEmpty() )
596                 m_pImpl->ImplUpdateFloatSelection();
597         }
598     }
599 
600     // adjust the size of the FloatingWindow even when invisible
601     // as KEY_PGUP/DOWN is being processed...
602     if (m_pImpl->m_pFloatWin)
603         m_pImpl->m_pFloatWin->SetSizePixel(m_pImpl->m_pFloatWin->CalcFloatSize());
604 }
605 
IsDropDownBox() const606 bool ComboBox::IsDropDownBox() const { return m_pImpl->m_pFloatWin != nullptr; }
607 
FillLayoutData() const608 void ComboBox::FillLayoutData() const
609 {
610     mpControlData->mpLayoutData.reset( new vcl::ControlLayoutData );
611     AppendLayoutData( *m_pImpl->m_pSubEdit );
612     m_pImpl->m_pSubEdit->SetLayoutDataParent( this );
613     ImplListBoxWindow* rMainWindow = GetMainWindow();
614     if (m_pImpl->m_pFloatWin)
615     {
616         // dropdown mode
617         if (m_pImpl->m_pFloatWin->IsReallyVisible())
618         {
619             AppendLayoutData( *rMainWindow );
620             rMainWindow->SetLayoutDataParent( this );
621         }
622     }
623     else
624     {
625         AppendLayoutData( *rMainWindow );
626         rMainWindow->SetLayoutDataParent( this );
627     }
628 }
629 
StateChanged(StateChangedType nType)630 void ComboBox::StateChanged( StateChangedType nType )
631 {
632     Edit::StateChanged( nType );
633 
634     if ( nType == StateChangedType::ReadOnly )
635     {
636         m_pImpl->m_pImplLB->SetReadOnly( IsReadOnly() );
637         if (m_pImpl->m_pBtn)
638             m_pImpl->m_pBtn->Enable( IsEnabled() && !IsReadOnly() );
639     }
640     else if ( nType == StateChangedType::Enable )
641     {
642         m_pImpl->m_pSubEdit->Enable( IsEnabled() );
643         m_pImpl->m_pImplLB->Enable( IsEnabled() && !IsReadOnly() );
644         if (m_pImpl->m_pBtn)
645             m_pImpl->m_pBtn->Enable( IsEnabled() && !IsReadOnly() );
646         Invalidate();
647     }
648     else if( nType == StateChangedType::UpdateMode )
649     {
650         m_pImpl->m_pImplLB->SetUpdateMode( IsUpdateMode() );
651     }
652     else if ( nType == StateChangedType::Zoom )
653     {
654         m_pImpl->m_pImplLB->SetZoom( GetZoom() );
655         m_pImpl->m_pSubEdit->SetZoom( GetZoom() );
656         ImplCalcEditHeight();
657         Resize();
658     }
659     else if ( nType == StateChangedType::ControlFont )
660     {
661         m_pImpl->m_pImplLB->SetControlFont( GetControlFont() );
662         m_pImpl->m_pSubEdit->SetControlFont( GetControlFont() );
663         ImplCalcEditHeight();
664         Resize();
665     }
666     else if ( nType == StateChangedType::ControlForeground )
667     {
668         m_pImpl->m_pImplLB->SetControlForeground( GetControlForeground() );
669         m_pImpl->m_pSubEdit->SetControlForeground( GetControlForeground() );
670     }
671     else if ( nType == StateChangedType::ControlBackground )
672     {
673         m_pImpl->m_pImplLB->SetControlBackground( GetControlBackground() );
674         m_pImpl->m_pSubEdit->SetControlBackground( GetControlBackground() );
675     }
676     else if ( nType == StateChangedType::Style )
677     {
678         SetStyle( ImplInitStyle( GetStyle() ) );
679         GetMainWindow()->EnableSort( ( GetStyle() & WB_SORT ) != 0 );
680     }
681     else if( nType == StateChangedType::Mirroring )
682     {
683         if (m_pImpl->m_pBtn)
684         {
685             m_pImpl->m_pBtn->EnableRTL( IsRTLEnabled() );
686             ImplInitDropDownButton( m_pImpl->m_pBtn );
687         }
688         m_pImpl->m_pSubEdit->CompatStateChanged( StateChangedType::Mirroring );
689         m_pImpl->m_pImplLB->EnableRTL( IsRTLEnabled() );
690         Resize();
691     }
692 }
693 
DataChanged(const DataChangedEvent & rDCEvt)694 void ComboBox::DataChanged( const DataChangedEvent& rDCEvt )
695 {
696     Control::DataChanged( rDCEvt );
697 
698     if ( !((rDCEvt.GetType() == DataChangedEventType::FONTS) ||
699          (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
700          ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
701           (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))) )
702         return;
703 
704     if (m_pImpl->m_pBtn)
705     {
706         m_pImpl->m_pBtn->GetOutDev()->SetSettings( GetSettings() );
707         ImplInitDropDownButton( m_pImpl->m_pBtn );
708     }
709     Resize();
710     m_pImpl->m_pImplLB->Resize(); // not called by ComboBox::Resize() if ImplLB is unchanged
711 
712     SetBackground();    // due to a hack in Window::UpdateSettings the background must be reset
713                         // otherwise it will overpaint NWF drawn comboboxes
714 }
715 
EventNotify(NotifyEvent & rNEvt)716 bool ComboBox::EventNotify( NotifyEvent& rNEvt )
717 {
718     bool bDone = false;
719     if ((rNEvt.GetType() == MouseNotifyEvent::KEYINPUT)
720         && (rNEvt.GetWindow() == m_pImpl->m_pSubEdit)
721         && !IsReadOnly())
722     {
723         KeyEvent aKeyEvt = *rNEvt.GetKeyEvent();
724         sal_uInt16   nKeyCode = aKeyEvt.GetKeyCode().GetCode();
725         switch( nKeyCode )
726         {
727             case KEY_UP:
728             case KEY_DOWN:
729             case KEY_PAGEUP:
730             case KEY_PAGEDOWN:
731             {
732                 m_pImpl->ImplUpdateFloatSelection();
733                 if ((nKeyCode == KEY_DOWN) && m_pImpl->m_pFloatWin
734                     && !m_pImpl->m_pFloatWin->IsInPopupMode()
735                     && aKeyEvt.GetKeyCode().IsMod2())
736                 {
737                     CallEventListeners( VclEventId::DropdownPreOpen );
738                     m_pImpl->m_pBtn->SetPressed( true );
739                     if (m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount())
740                         m_pImpl->m_pImplLB->SelectEntry( 0 , true );
741                     SetSelection( Selection( 0, SELECTION_MAX ) );
742                     m_pImpl->m_pFloatWin->StartFloat( false );
743                     CallEventListeners( VclEventId::DropdownOpen );
744                     bDone = true;
745                 }
746                 else if ((nKeyCode == KEY_UP) && m_pImpl->m_pFloatWin
747                         && m_pImpl->m_pFloatWin->IsInPopupMode()
748                         && aKeyEvt.GetKeyCode().IsMod2())
749                 {
750                     m_pImpl->m_pFloatWin->EndPopupMode();
751                     bDone = true;
752                 }
753                 else
754                 {
755                     bDone = m_pImpl->m_pImplLB->ProcessKeyInput( aKeyEvt );
756                 }
757             }
758             break;
759 
760             case KEY_RETURN:
761             {
762                 if ((rNEvt.GetWindow() == m_pImpl->m_pSubEdit) && IsInDropDown())
763                 {
764                     m_pImpl->m_pImplLB->ProcessKeyInput( aKeyEvt );
765                     bDone = true;
766                 }
767             }
768             break;
769         }
770     }
771     else if ((rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS) && m_pImpl->m_pFloatWin)
772     {
773         if (m_pImpl->m_pFloatWin->HasChildPathFocus())
774             m_pImpl->m_pSubEdit->GrabFocus();
775         else if (m_pImpl->m_pFloatWin->IsInPopupMode() && !HasChildPathFocus(true))
776             m_pImpl->m_pFloatWin->EndPopupMode();
777     }
778     else if( (rNEvt.GetType() == MouseNotifyEvent::COMMAND) &&
779              (rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) &&
780              (rNEvt.GetWindow() == m_pImpl->m_pSubEdit) )
781     {
782         MouseWheelBehaviour nWheelBehavior( GetSettings().GetMouseSettings().GetWheelBehavior() );
783         if  (   ( nWheelBehavior == MouseWheelBehaviour::ALWAYS )
784             ||  (   ( nWheelBehavior == MouseWheelBehaviour::FocusOnly )
785                 &&  HasChildPathFocus()
786                 )
787             )
788         {
789             bDone = m_pImpl->m_pImplLB->HandleWheelAsCursorTravel(*rNEvt.GetCommandEvent(), *this);
790         }
791         else
792         {
793             bDone = false;  // don't eat this event, let the default handling happen (i.e. scroll the context)
794         }
795     }
796     else if ((rNEvt.GetType() == MouseNotifyEvent::MOUSEBUTTONDOWN)
797             && (rNEvt.GetWindow() == GetMainWindow()))
798     {
799         m_pImpl->m_pSubEdit->GrabFocus();
800     }
801 
802     return bDone || Edit::EventNotify( rNEvt );
803 }
804 
SetText(const OUString & rStr)805 void ComboBox::SetText( const OUString& rStr )
806 {
807     CallEventListeners( VclEventId::ComboboxSetText );
808 
809     Edit::SetText( rStr );
810     m_pImpl->ImplUpdateFloatSelection();
811 }
812 
SetText(const OUString & rStr,const Selection & rNewSelection)813 void ComboBox::SetText( const OUString& rStr, const Selection& rNewSelection )
814 {
815     CallEventListeners( VclEventId::ComboboxSetText );
816 
817     Edit::SetText( rStr, rNewSelection );
818     m_pImpl->ImplUpdateFloatSelection();
819 }
820 
Modify()821 void ComboBox::Modify()
822 {
823     if (!m_pImpl->m_isSyntheticModify)
824         m_pImpl->ImplUpdateFloatSelection();
825 
826     Edit::Modify();
827 }
828 
ImplUpdateFloatSelection()829 void ComboBox::Impl::ImplUpdateFloatSelection()
830 {
831     if (!m_pImplLB || !m_pSubEdit)
832         return;
833 
834     // move text in the ListBox into the visible region
835     m_pImplLB->SetCallSelectionChangedHdl( false );
836     if (!m_rThis.IsMultiSelectionEnabled())
837     {
838         OUString        aSearchStr( m_pSubEdit->GetText() );
839         sal_Int32       nSelect = LISTBOX_ENTRY_NOTFOUND;
840         bool        bSelect = true;
841 
842         if (m_pImplLB->GetCurrentPos() != LISTBOX_ENTRY_NOTFOUND)
843         {
844             OUString aCurrent = m_pImplLB->GetEntryList()->GetEntryText(
845                                     m_pImplLB->GetCurrentPos());
846             if ( aCurrent == aSearchStr )
847                 nSelect = m_pImplLB->GetCurrentPos();
848         }
849 
850         if ( nSelect == LISTBOX_ENTRY_NOTFOUND )
851             nSelect = m_pImplLB->GetEntryList()->FindEntry( aSearchStr );
852         if ( nSelect == LISTBOX_ENTRY_NOTFOUND )
853         {
854             nSelect = m_pImplLB->GetEntryList()->FindMatchingEntry( aSearchStr, 0, true );
855             bSelect = false;
856         }
857 
858         if( nSelect != LISTBOX_ENTRY_NOTFOUND )
859         {
860             if (!m_pImplLB->IsVisible(nSelect))
861                 m_pImplLB->ShowProminentEntry( nSelect );
862             m_pImplLB->SelectEntry( nSelect, bSelect );
863         }
864         else
865         {
866             nSelect = m_pImplLB->GetEntryList()->GetSelectedEntryPos( 0 );
867             if( nSelect != LISTBOX_ENTRY_NOTFOUND )
868                 m_pImplLB->SelectEntry( nSelect, false );
869             m_pImplLB->ResetCurrentPos();
870         }
871     }
872     else
873     {
874         ::std::set< sal_Int32 > aSelInText;
875         lcl_GetSelectedEntries(aSelInText, m_pSubEdit->GetText(), m_cMultiSep, m_pImplLB->GetEntryList());
876         for (sal_Int32 n = 0; n < m_pImplLB->GetEntryList()->GetEntryCount(); n++)
877             m_pImplLB->SelectEntry( n, aSelInText.count( n ) != 0 );
878     }
879     m_pImplLB->SetCallSelectionChangedHdl( true );
880 }
881 
InsertEntry(const OUString & rStr,sal_Int32 const nPos)882 sal_Int32 ComboBox::InsertEntry(const OUString& rStr, sal_Int32 const nPos)
883 {
884     assert(nPos >= 0 && COMBOBOX_MAX_ENTRIES > m_pImpl->m_pImplLB->GetEntryList()->GetEntryCount());
885 
886     sal_Int32 nRealPos;
887     if (nPos == COMBOBOX_APPEND)
888         nRealPos = nPos;
889     else
890     {
891         const sal_Int32 nMRUCount = m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount();
892         assert(nPos <= COMBOBOX_MAX_ENTRIES - nMRUCount);
893         nRealPos = nPos + nMRUCount;
894     }
895 
896     nRealPos = m_pImpl->m_pImplLB->InsertEntry( nRealPos, rStr );
897     nRealPos -= m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount();
898     CallEventListeners( VclEventId::ComboboxItemAdded, reinterpret_cast<void*>(nRealPos) );
899     return nRealPos;
900 }
901 
InsertEntryWithImage(const OUString & rStr,const Image & rImage,sal_Int32 const nPos)902 sal_Int32 ComboBox::InsertEntryWithImage(
903         const OUString& rStr, const Image& rImage, sal_Int32 const nPos)
904 {
905     assert(nPos >= 0 && COMBOBOX_MAX_ENTRIES > m_pImpl->m_pImplLB->GetEntryList()->GetEntryCount());
906 
907     sal_Int32 nRealPos;
908     if (nPos == COMBOBOX_APPEND)
909         nRealPos = nPos;
910     else
911     {
912         const sal_Int32 nMRUCount = m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount();
913         assert(nPos <= COMBOBOX_MAX_ENTRIES - nMRUCount);
914         nRealPos = nPos + nMRUCount;
915     }
916 
917     nRealPos = m_pImpl->m_pImplLB->InsertEntry( nRealPos, rStr, rImage );
918     nRealPos -= m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount();
919     CallEventListeners( VclEventId::ComboboxItemAdded, reinterpret_cast<void*>(nRealPos) );
920     return nRealPos;
921 }
922 
RemoveEntryAt(sal_Int32 const nPos)923 void ComboBox::RemoveEntryAt(sal_Int32 const nPos)
924 {
925     const sal_Int32 nMRUCount = m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount();
926     assert(nPos >= 0 && nPos <= COMBOBOX_MAX_ENTRIES - nMRUCount);
927     m_pImpl->m_pImplLB->RemoveEntry( nPos + nMRUCount );
928     CallEventListeners( VclEventId::ComboboxItemRemoved, reinterpret_cast<void*>(nPos) );
929 }
930 
Clear()931 void ComboBox::Clear()
932 {
933     if (!m_pImpl->m_pImplLB)
934         return;
935     m_pImpl->m_pImplLB->Clear();
936     CallEventListeners( VclEventId::ComboboxItemRemoved, reinterpret_cast<void*>(-1) );
937 }
938 
GetEntryImage(sal_Int32 nPos) const939 Image ComboBox::GetEntryImage( sal_Int32 nPos ) const
940 {
941     if (m_pImpl->m_pImplLB->GetEntryList()->HasEntryImage(nPos))
942         return m_pImpl->m_pImplLB->GetEntryList()->GetEntryImage( nPos );
943     return Image();
944 }
945 
GetEntryPos(std::u16string_view rStr) const946 sal_Int32 ComboBox::GetEntryPos( std::u16string_view rStr ) const
947 {
948     sal_Int32 nPos = m_pImpl->m_pImplLB->GetEntryList()->FindEntry( rStr );
949     if ( nPos != LISTBOX_ENTRY_NOTFOUND )
950         nPos -= m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount();
951     return nPos;
952 }
953 
GetEntry(sal_Int32 nPos) const954 OUString ComboBox::GetEntry( sal_Int32 nPos ) const
955 {
956     const sal_Int32 nMRUCount = m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount();
957     if (nPos < 0 || nPos > COMBOBOX_MAX_ENTRIES - nMRUCount)
958         return OUString();
959 
960     return m_pImpl->m_pImplLB->GetEntryList()->GetEntryText( nPos + nMRUCount );
961 }
962 
GetEntryCount() const963 sal_Int32 ComboBox::GetEntryCount() const
964 {
965     if (!m_pImpl->m_pImplLB)
966         return 0;
967     return m_pImpl->m_pImplLB->GetEntryList()->GetEntryCount() - m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount();
968 }
969 
IsTravelSelect() const970 bool ComboBox::IsTravelSelect() const
971 {
972     return m_pImpl->m_pImplLB->IsTravelSelect();
973 }
974 
IsInDropDown() const975 bool ComboBox::IsInDropDown() const
976 {
977     // when the dropdown is dismissed, first mbInPopupMode is set to false, and on the next event iteration then
978     // mbPopupMode is set to false
979     return m_pImpl->m_pFloatWin && m_pImpl->m_pFloatWin->IsInPopupMode() && m_pImpl->m_pFloatWin->ImplIsInPrivatePopupMode();
980 }
981 
IsMultiSelectionEnabled() const982 bool ComboBox::IsMultiSelectionEnabled() const
983 {
984     return m_pImpl->m_pImplLB->IsMultiSelectionEnabled();
985 }
986 
SetSelectHdl(const Link<ComboBox &,void> & rLink)987 void ComboBox::SetSelectHdl(const Link<ComboBox&,void>& rLink) { m_pImpl->m_SelectHdl = rLink; }
988 
SetEntryActivateHdl(const Link<Edit &,bool> & rLink)989 void ComboBox::SetEntryActivateHdl(const Link<Edit&,bool>& rLink)
990 {
991     if (!m_pImpl->m_pSubEdit)
992         return;
993     m_pImpl->m_pSubEdit->SetActivateHdl(rLink);
994 }
995 
GetOptimalSize() const996 Size ComboBox::GetOptimalSize() const
997 {
998     return CalcMinimumSize();
999 }
1000 
getMaxWidthScrollBarAndDownButton() const1001 tools::Long ComboBox::getMaxWidthScrollBarAndDownButton() const
1002 {
1003     tools::Long nButtonDownWidth = 0;
1004 
1005     vcl::Window *pBorder = GetWindow( GetWindowType::Border );
1006     ImplControlValue aControlValue;
1007     tools::Rectangle aContent, aBound;
1008 
1009     // use the full extent of the control
1010     tools::Rectangle aArea( Point(), pBorder->GetOutputSizePixel() );
1011 
1012     if ( GetNativeControlRegion(ControlType::Combobox, ControlPart::ButtonDown,
1013         aArea, ControlState::NONE, aControlValue, aBound, aContent) )
1014     {
1015         nButtonDownWidth = aContent.getWidth();
1016     }
1017 
1018     tools::Long nScrollBarWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
1019 
1020     return std::max(nScrollBarWidth, nButtonDownWidth);
1021 }
1022 
CalcMinimumSize() const1023 Size ComboBox::CalcMinimumSize() const
1024 {
1025     Size aSz;
1026 
1027     if (!m_pImpl->m_pImplLB)
1028         return aSz;
1029 
1030     if (!IsDropDownBox())
1031     {
1032         aSz = m_pImpl->m_pImplLB->CalcSize( m_pImpl->m_pImplLB->GetEntryList()->GetEntryCount() );
1033         aSz.AdjustHeight(m_pImpl->m_nDDHeight );
1034     }
1035     else
1036     {
1037         aSz.setHeight( Edit::CalcMinimumSizeForText(GetText()).Height() );
1038 
1039         if (m_pImpl->m_nWidthInChars!= -1)
1040             aSz.setWidth(m_pImpl->m_nWidthInChars * approximate_digit_width());
1041         else
1042             aSz.setWidth(m_pImpl->m_pImplLB->GetMaxEntryWidth());
1043     }
1044 
1045     if (m_pImpl->m_nMaxWidthChars != -1)
1046     {
1047         tools::Long nMaxWidth = m_pImpl->m_nMaxWidthChars * approximate_char_width();
1048         aSz.setWidth( std::min(aSz.Width(), nMaxWidth) );
1049     }
1050 
1051     if (IsDropDownBox())
1052         aSz.AdjustWidth(getMaxWidthScrollBarAndDownButton() );
1053 
1054     ComboBoxBounds aBounds(m_pImpl->calcComboBoxDropDownComponentBounds(
1055         Size(0xFFFF, 0xFFFF), Size(0xFFFF, 0xFFFF)));
1056     aSz.AdjustWidth(aBounds.aSubEditPos.X()*2 );
1057 
1058     aSz.AdjustWidth(ImplGetExtraXOffset() * 2 );
1059 
1060     aSz = CalcWindowSize( aSz );
1061     return aSz;
1062 }
1063 
CalcAdjustedSize(const Size & rPrefSize) const1064 Size ComboBox::CalcAdjustedSize( const Size& rPrefSize ) const
1065 {
1066     Size aSz = rPrefSize;
1067     sal_Int32 nLeft, nTop, nRight, nBottom;
1068     static_cast<vcl::Window*>(const_cast<ComboBox *>(this))->GetBorder( nLeft, nTop, nRight, nBottom );
1069     aSz.AdjustHeight( -(nTop+nBottom) );
1070     if ( !IsDropDownBox() )
1071     {
1072         tools::Long nEntryHeight = CalcBlockSize( 1, 1 ).Height();
1073         tools::Long nLines = aSz.Height() / nEntryHeight;
1074         if ( nLines < 1 )
1075             nLines = 1;
1076         aSz.setHeight( nLines * nEntryHeight );
1077         aSz.AdjustHeight(m_pImpl->m_nDDHeight );
1078     }
1079     else
1080     {
1081         aSz.setHeight( m_pImpl->m_nDDHeight );
1082     }
1083     aSz.AdjustHeight(nTop+nBottom );
1084 
1085     aSz = CalcWindowSize( aSz );
1086     return aSz;
1087 }
1088 
CalcBlockSize(sal_uInt16 nColumns,sal_uInt16 nLines) const1089 Size ComboBox::CalcBlockSize( sal_uInt16 nColumns, sal_uInt16 nLines ) const
1090 {
1091     // show ScrollBars where appropriate
1092     Size aMinSz = CalcMinimumSize();
1093     Size aSz;
1094 
1095     // height
1096     if ( nLines )
1097     {
1098         if ( !IsDropDownBox() )
1099             aSz.setHeight( m_pImpl->m_pImplLB->CalcSize( nLines ).Height() + m_pImpl->m_nDDHeight );
1100         else
1101             aSz.setHeight( m_pImpl->m_nDDHeight );
1102     }
1103     else
1104         aSz.setHeight( aMinSz.Height() );
1105 
1106     // width
1107     if ( nColumns )
1108         aSz.setWidth( nColumns * approximate_char_width() );
1109     else
1110         aSz.setWidth( aMinSz.Width() );
1111 
1112     if ( IsDropDownBox() )
1113         aSz.AdjustWidth(getMaxWidthScrollBarAndDownButton() );
1114 
1115     if ( !IsDropDownBox() )
1116     {
1117         if ( aSz.Width() < aMinSz.Width() )
1118             aSz.AdjustHeight(GetSettings().GetStyleSettings().GetScrollBarSize() );
1119         if ( aSz.Height() < aMinSz.Height() )
1120             aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
1121     }
1122 
1123     aSz.AdjustWidth(ImplGetExtraXOffset() * 2 );
1124 
1125     aSz = CalcWindowSize( aSz );
1126     return aSz;
1127 }
1128 
GetDropDownEntryHeight() const1129 tools::Long ComboBox::GetDropDownEntryHeight() const
1130 {
1131     return m_pImpl->m_pImplLB->GetEntryHeight();
1132 }
1133 
GetMaxVisColumnsAndLines(sal_uInt16 & rnCols,sal_uInt16 & rnLines) const1134 void ComboBox::GetMaxVisColumnsAndLines( sal_uInt16& rnCols, sal_uInt16& rnLines ) const
1135 {
1136     tools::Long nCharWidth = GetTextWidth(OUString(u'x'));
1137     if ( !IsDropDownBox() )
1138     {
1139         Size aOutSz = GetMainWindow()->GetOutputSizePixel();
1140         rnCols = (nCharWidth > 0) ? static_cast<sal_uInt16>(aOutSz.Width()/nCharWidth) : 1;
1141         rnLines = static_cast<sal_uInt16>(aOutSz.Height()/GetDropDownEntryHeight());
1142     }
1143     else
1144     {
1145         Size aOutSz = m_pImpl->m_pSubEdit->GetOutputSizePixel();
1146         rnCols = (nCharWidth > 0) ? static_cast<sal_uInt16>(aOutSz.Width()/nCharWidth) : 1;
1147         rnLines = 1;
1148     }
1149 }
1150 
Draw(OutputDevice * pDev,const Point & rPos,DrawFlags nFlags)1151 void ComboBox::Draw( OutputDevice* pDev, const Point& rPos, DrawFlags nFlags )
1152 {
1153     GetMainWindow()->ApplySettings(*pDev);
1154 
1155     Point aPos = pDev->LogicToPixel( rPos );
1156     Size aSize = GetSizePixel();
1157     vcl::Font aFont = GetMainWindow()->GetDrawPixelFont( pDev );
1158 
1159     pDev->Push();
1160     pDev->SetMapMode();
1161     pDev->SetFont( aFont );
1162     pDev->SetTextFillColor();
1163 
1164     // Border/Background
1165     pDev->SetLineColor();
1166     pDev->SetFillColor();
1167     bool bBorder = (GetStyle() & WB_BORDER);
1168     bool bBackground = IsControlBackground();
1169     if ( bBorder || bBackground )
1170     {
1171         tools::Rectangle aRect( aPos, aSize );
1172         // aRect.Top() += nEditHeight;
1173         if ( bBorder )
1174         {
1175             ImplDrawFrame( pDev, aRect );
1176         }
1177         if ( bBackground )
1178         {
1179             pDev->SetFillColor( GetControlBackground() );
1180             pDev->DrawRect( aRect );
1181         }
1182     }
1183 
1184     // contents
1185     if ( !IsDropDownBox() )
1186     {
1187         tools::Long        nOnePixel = GetDrawPixel( pDev, 1 );
1188         tools::Long        nTextHeight = pDev->GetTextHeight();
1189         tools::Long        nEditHeight = nTextHeight + 6*nOnePixel;
1190         DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
1191 
1192         // First, draw the edit part
1193         Size aOrigSize(m_pImpl->m_pSubEdit->GetSizePixel());
1194         m_pImpl->m_pSubEdit->SetSizePixel(Size(aSize.Width(), nEditHeight));
1195         m_pImpl->m_pSubEdit->Draw( pDev, aPos, nFlags );
1196         m_pImpl->m_pSubEdit->SetSizePixel(aOrigSize);
1197 
1198         // Second, draw the listbox
1199         if ( GetStyle() & WB_CENTER )
1200             nTextStyle |= DrawTextFlags::Center;
1201         else if ( GetStyle() & WB_RIGHT )
1202             nTextStyle |= DrawTextFlags::Right;
1203         else
1204             nTextStyle |= DrawTextFlags::Left;
1205 
1206         if ( nFlags & DrawFlags::Mono )
1207         {
1208             pDev->SetTextColor( COL_BLACK );
1209         }
1210         else
1211         {
1212             if ( !IsEnabled() )
1213             {
1214                 const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
1215                 pDev->SetTextColor( rStyleSettings.GetDisableColor() );
1216             }
1217             else
1218             {
1219                 pDev->SetTextColor( GetTextColor() );
1220             }
1221         }
1222 
1223         tools::Rectangle aClip( aPos, aSize );
1224         pDev->IntersectClipRegion( aClip );
1225         sal_Int32 nLines = static_cast<sal_Int32>( nTextHeight > 0 ? (aSize.Height()-nEditHeight)/nTextHeight : 1 );
1226         if ( !nLines )
1227             nLines = 1;
1228         const sal_Int32 nTEntry = IsReallyVisible() ? m_pImpl->m_pImplLB->GetTopEntry() : 0;
1229 
1230         tools::Rectangle aTextRect( aPos, aSize );
1231 
1232         aTextRect.AdjustLeft(3*nOnePixel );
1233         aTextRect.AdjustRight( -(3*nOnePixel) );
1234         aTextRect.AdjustTop(nEditHeight + nOnePixel );
1235         aTextRect.SetBottom( aTextRect.Top() + nTextHeight );
1236 
1237         // the drawing starts here
1238         for ( sal_Int32 n = 0; n < nLines; ++n )
1239         {
1240             pDev->DrawText( aTextRect, m_pImpl->m_pImplLB->GetEntryList()->GetEntryText( n+nTEntry ), nTextStyle );
1241             aTextRect.AdjustTop(nTextHeight );
1242             aTextRect.AdjustBottom(nTextHeight );
1243         }
1244     }
1245 
1246     pDev->Pop();
1247 
1248     // Call Edit::Draw after restoring the MapMode...
1249     if ( IsDropDownBox() )
1250     {
1251         Size aOrigSize(m_pImpl->m_pSubEdit->GetSizePixel());
1252         m_pImpl->m_pSubEdit->SetSizePixel(GetSizePixel());
1253         m_pImpl->m_pSubEdit->Draw( pDev, rPos, nFlags );
1254         m_pImpl->m_pSubEdit->SetSizePixel(aOrigSize);
1255         // DD-Button ?
1256     }
1257 }
1258 
SetUserDrawHdl(const Link<UserDrawEvent *,void> & rLink)1259 void ComboBox::SetUserDrawHdl(const Link<UserDrawEvent*, void>& rLink)
1260 {
1261     m_pImpl->m_pImplLB->SetUserDrawHdl(rLink);
1262 }
1263 
SetUserItemSize(const Size & rSz)1264 void ComboBox::SetUserItemSize( const Size& rSz )
1265 {
1266     GetMainWindow()->SetUserItemSize( rSz );
1267 }
1268 
EnableUserDraw(bool bUserDraw)1269 void ComboBox::EnableUserDraw( bool bUserDraw )
1270 {
1271     GetMainWindow()->EnableUserDraw( bUserDraw );
1272 }
1273 
IsUserDrawEnabled() const1274 bool ComboBox::IsUserDrawEnabled() const
1275 {
1276     return GetMainWindow()->IsUserDrawEnabled();
1277 }
1278 
DrawEntry(const UserDrawEvent & rEvt)1279 void ComboBox::DrawEntry(const UserDrawEvent& rEvt)
1280 {
1281     GetMainWindow()->DrawEntry(*rEvt.GetRenderContext(), rEvt.GetItemId(), /*bDrawImage*/false, /*bDrawText*/false);
1282 }
1283 
AddSeparator(sal_Int32 n)1284 void ComboBox::AddSeparator( sal_Int32 n )
1285 {
1286     m_pImpl->m_pImplLB->AddSeparator( n );
1287 }
1288 
SetMRUEntries(const OUString & rEntries)1289 void ComboBox::SetMRUEntries( const OUString& rEntries )
1290 {
1291     m_pImpl->m_pImplLB->SetMRUEntries( rEntries, ';' );
1292 }
1293 
GetMRUEntries() const1294 OUString ComboBox::GetMRUEntries() const
1295 {
1296     return m_pImpl->m_pImplLB ? m_pImpl->m_pImplLB->GetMRUEntries( ';' ) : OUString();
1297 }
1298 
SetMaxMRUCount(sal_Int32 n)1299 void ComboBox::SetMaxMRUCount( sal_Int32 n )
1300 {
1301     m_pImpl->m_pImplLB->SetMaxMRUCount( n );
1302 }
1303 
GetMaxMRUCount() const1304 sal_Int32 ComboBox::GetMaxMRUCount() const
1305 {
1306     return m_pImpl->m_pImplLB ? m_pImpl->m_pImplLB->GetMaxMRUCount() : 0;
1307 }
1308 
GetDisplayLineCount() const1309 sal_uInt16 ComboBox::GetDisplayLineCount() const
1310 {
1311     return m_pImpl->m_pImplLB ? m_pImpl->m_pImplLB->GetDisplayLineCount() : 0;
1312 }
1313 
SetEntryData(sal_Int32 nPos,void * pNewData)1314 void ComboBox::SetEntryData( sal_Int32 nPos, void* pNewData )
1315 {
1316     m_pImpl->m_pImplLB->SetEntryData( nPos + m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount(), pNewData );
1317 }
1318 
GetEntryData(sal_Int32 nPos) const1319 void* ComboBox::GetEntryData( sal_Int32 nPos ) const
1320 {
1321     return m_pImpl->m_pImplLB->GetEntryList()->GetEntryData(
1322             nPos + m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount() );
1323 }
1324 
GetTopEntry() const1325 sal_Int32 ComboBox::GetTopEntry() const
1326 {
1327     sal_Int32 nPos = GetEntryCount() ? m_pImpl->m_pImplLB->GetTopEntry() : LISTBOX_ENTRY_NOTFOUND;
1328     if (nPos < m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount())
1329         nPos = 0;
1330     return nPos;
1331 }
1332 
GetDropDownPosSizePixel() const1333 tools::Rectangle ComboBox::GetDropDownPosSizePixel() const
1334 {
1335     return m_pImpl->m_pFloatWin
1336         ? m_pImpl->m_pFloatWin->GetWindowExtentsRelative(this)
1337         : tools::Rectangle();
1338 }
1339 
GetDisplayBackground() const1340 const Wallpaper& ComboBox::GetDisplayBackground() const
1341 {
1342     if (!m_pImpl->m_pSubEdit->IsBackground())
1343         return Control::GetDisplayBackground();
1344 
1345     const Wallpaper& rBack = m_pImpl->m_pSubEdit->GetBackground();
1346     if( ! rBack.IsBitmap() &&
1347         ! rBack.IsGradient() &&
1348         rBack == Wallpaper(COL_TRANSPARENT)
1349         )
1350         return Control::GetDisplayBackground();
1351     return rBack;
1352 }
1353 
GetSelectedEntryCount() const1354 sal_Int32 ComboBox::GetSelectedEntryCount() const
1355 {
1356     return m_pImpl->m_pImplLB->GetEntryList()->GetSelectedEntryCount();
1357 }
1358 
GetSelectedEntryPos(sal_Int32 nIndex) const1359 sal_Int32 ComboBox::GetSelectedEntryPos( sal_Int32 nIndex ) const
1360 {
1361     sal_Int32 nPos = m_pImpl->m_pImplLB->GetEntryList()->GetSelectedEntryPos( nIndex );
1362     if ( nPos != LISTBOX_ENTRY_NOTFOUND )
1363     {
1364         if (nPos < m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount())
1365             nPos = m_pImpl->m_pImplLB->GetEntryList()->FindEntry(m_pImpl->m_pImplLB->GetEntryList()->GetEntryText(nPos));
1366         nPos = sal::static_int_cast<sal_Int32>(nPos - m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount());
1367     }
1368     return nPos;
1369 }
1370 
IsEntryPosSelected(sal_Int32 nPos) const1371 bool ComboBox::IsEntryPosSelected( sal_Int32 nPos ) const
1372 {
1373     return m_pImpl->m_pImplLB->GetEntryList()->IsEntryPosSelected(
1374             nPos + m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount() );
1375 }
1376 
SelectEntryPos(sal_Int32 nPos,bool bSelect)1377 void ComboBox::SelectEntryPos( sal_Int32 nPos, bool bSelect)
1378 {
1379     if (nPos < m_pImpl->m_pImplLB->GetEntryList()->GetEntryCount())
1380         m_pImpl->m_pImplLB->SelectEntry(
1381             nPos + m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount(), bSelect);
1382 }
1383 
SetNoSelection()1384 void ComboBox::SetNoSelection()
1385 {
1386     m_pImpl->m_pImplLB->SetNoSelection();
1387     m_pImpl->m_pSubEdit->SetText( OUString() );
1388 }
1389 
GetBoundingRectangle(sal_Int32 nItem) const1390 tools::Rectangle ComboBox::GetBoundingRectangle( sal_Int32 nItem ) const
1391 {
1392     tools::Rectangle aRect = GetMainWindow()->GetBoundingRectangle( nItem );
1393     tools::Rectangle aOffset = GetMainWindow()->GetWindowExtentsRelative( static_cast<vcl::Window*>(const_cast<ComboBox *>(this)) );
1394     aRect.Move( aOffset.Left(), aOffset.Top() );
1395     return aRect;
1396 }
1397 
SetBorderStyle(WindowBorderStyle nBorderStyle)1398 void ComboBox::SetBorderStyle( WindowBorderStyle nBorderStyle )
1399 {
1400     Window::SetBorderStyle( nBorderStyle );
1401     if ( !IsDropDownBox() )
1402     {
1403         m_pImpl->m_pSubEdit->SetBorderStyle( nBorderStyle );
1404         m_pImpl->m_pImplLB->SetBorderStyle( nBorderStyle );
1405     }
1406 }
1407 
GetMainWindow() const1408 ImplListBoxWindow* ComboBox::GetMainWindow() const
1409 {
1410     return m_pImpl->m_pImplLB->GetMainWindow();
1411 }
1412 
GetIndexForPoint(const Point & rPoint,sal_Int32 & rPos) const1413 tools::Long ComboBox::GetIndexForPoint( const Point& rPoint, sal_Int32& rPos ) const
1414 {
1415     if( !HasLayoutData() )
1416         FillLayoutData();
1417 
1418     // check whether rPoint fits at all
1419     tools::Long nIndex = Control::GetIndexForPoint( rPoint );
1420     if( nIndex != -1 )
1421     {
1422         // point must be either in main list window
1423         // or in impl window (dropdown case)
1424         ImplListBoxWindow* rMain = GetMainWindow();
1425 
1426         // convert coordinates to ImplListBoxWindow pixel coordinate space
1427         Point aConvPoint = LogicToPixel( rPoint );
1428         aConvPoint = OutputToAbsoluteScreenPixel( aConvPoint );
1429         aConvPoint = rMain->AbsoluteScreenToOutputPixel( aConvPoint );
1430         aConvPoint = rMain->PixelToLogic( aConvPoint );
1431 
1432         // try to find entry
1433         sal_Int32 nEntry = rMain->GetEntryPosForPoint( aConvPoint );
1434         if( nEntry == LISTBOX_ENTRY_NOTFOUND )
1435             nIndex = -1;
1436         else
1437             rPos = nEntry;
1438     }
1439 
1440     // get line relative index
1441     if( nIndex != -1 )
1442         nIndex = ToRelativeLineIndex( nIndex );
1443 
1444     return nIndex;
1445 }
1446 
calcComboBoxDropDownComponentBounds(const Size & rOutSz,const Size & rBorderOutSz) const1447 ComboBoxBounds ComboBox::Impl::calcComboBoxDropDownComponentBounds(
1448     const Size &rOutSz, const Size &rBorderOutSz) const
1449 {
1450     ComboBoxBounds aBounds;
1451 
1452     tools::Long    nTop = 0;
1453     tools::Long    nBottom = rOutSz.Height();
1454 
1455     vcl::Window *pBorder = m_rThis.GetWindow( GetWindowType::Border );
1456     ImplControlValue aControlValue;
1457     Point aPoint;
1458     tools::Rectangle aContent, aBound;
1459 
1460     // use the full extent of the control
1461     tools::Rectangle aArea( aPoint, rBorderOutSz );
1462 
1463     if (m_rThis.GetNativeControlRegion(ControlType::Combobox, ControlPart::ButtonDown,
1464             aArea, ControlState::NONE, aControlValue, aBound, aContent) )
1465     {
1466         // convert back from border space to local coordinates
1467         aPoint = pBorder->ScreenToOutputPixel(m_rThis.OutputToScreenPixel(aPoint));
1468         aContent.Move(-aPoint.X(), -aPoint.Y());
1469 
1470         aBounds.aButtonPos = Point(aContent.Left(), nTop);
1471         aBounds.aButtonSize = Size(aContent.getWidth(), (nBottom-nTop));
1472 
1473         // adjust the size of the edit field
1474         if (m_rThis.GetNativeControlRegion(ControlType::Combobox, ControlPart::SubEdit,
1475                     aArea, ControlState::NONE, aControlValue, aBound, aContent) )
1476         {
1477             // convert back from border space to local coordinates
1478             aContent.Move(-aPoint.X(), -aPoint.Y());
1479 
1480             // use the themes drop down size
1481             aBounds.aSubEditPos = aContent.TopLeft();
1482             aBounds.aSubEditSize = aContent.GetSize();
1483         }
1484         else
1485         {
1486             // use the themes drop down size for the button
1487             aBounds.aSubEditSize = Size(rOutSz.Width() - aContent.getWidth(), rOutSz.Height());
1488         }
1489     }
1490     else
1491     {
1492         tools::Long nSBWidth = m_rThis.GetSettings().GetStyleSettings().GetScrollBarSize();
1493         nSBWidth = m_rThis.CalcZoom( nSBWidth );
1494         aBounds.aSubEditSize = Size(rOutSz.Width() - nSBWidth, rOutSz.Height());
1495         aBounds.aButtonPos = Point(rOutSz.Width() - nSBWidth, nTop);
1496         aBounds.aButtonSize = Size(nSBWidth, (nBottom-nTop));
1497     }
1498     return aBounds;
1499 }
1500 
SetWidthInChars(sal_Int32 nWidthInChars)1501 void ComboBox::SetWidthInChars(sal_Int32 nWidthInChars)
1502 {
1503     if (nWidthInChars != m_pImpl->m_nWidthInChars)
1504     {
1505         m_pImpl->m_nWidthInChars = nWidthInChars;
1506         queue_resize();
1507     }
1508 }
1509 
setMaxWidthChars(sal_Int32 nWidth)1510 void ComboBox::setMaxWidthChars(sal_Int32 nWidth)
1511 {
1512     if (nWidth != m_pImpl->m_nMaxWidthChars)
1513     {
1514         m_pImpl->m_nMaxWidthChars = nWidth;
1515         queue_resize();
1516     }
1517 }
1518 
set_property(const OString & rKey,const OUString & rValue)1519 bool ComboBox::set_property(const OString &rKey, const OUString &rValue)
1520 {
1521     if (rKey == "width-chars")
1522         SetWidthInChars(rValue.toInt32());
1523     else if (rKey == "max-width-chars")
1524         setMaxWidthChars(rValue.toInt32());
1525     else if (rKey == "can-focus")
1526     {
1527         // as far as I can see in Gtk, setting a ComboBox as can.focus means
1528         // the focus gets stuck in it, so try here to behave like gtk does
1529         // with the settings that work, i.e. can.focus of false doesn't
1530         // set the hard WB_NOTABSTOP
1531         WinBits nBits = GetStyle();
1532         nBits &= ~(WB_TABSTOP|WB_NOTABSTOP);
1533         if (toBool(rValue))
1534             nBits |= WB_TABSTOP;
1535         SetStyle(nBits);
1536     }
1537     else if (rKey == "placeholder-text")
1538         SetPlaceholderText(rValue);
1539     else
1540         return Control::set_property(rKey, rValue);
1541     return true;
1542 }
1543 
GetUITestFactory() const1544 FactoryFunction ComboBox::GetUITestFactory() const
1545 {
1546     return ComboBoxUIObject::create;
1547 }
1548 
DumpAsPropertyTree(tools::JsonWriter & rJsonWriter)1549 void ComboBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
1550 {
1551     Control::DumpAsPropertyTree(rJsonWriter);
1552 
1553     {
1554         auto entriesNode = rJsonWriter.startArray("entries");
1555         for (int i = 0; i < GetEntryCount(); ++i)
1556         {
1557             rJsonWriter.putSimpleValue(GetEntry(i));
1558         }
1559     }
1560 
1561     {
1562         auto selectedNode = rJsonWriter.startArray("selectedEntries");
1563         for (int i = 0; i < GetSelectedEntryCount(); ++i)
1564         {
1565             rJsonWriter.putSimpleValue(OUString::number(GetSelectedEntryPos(i)));
1566         }
1567     }
1568 
1569     rJsonWriter.put("selectedCount", GetSelectedEntryCount());
1570 }
1571 
1572 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1573