/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include ListBox::ListBox(WindowType nType) : Control(nType) , mpImplLB(nullptr) { ImplInitListBoxData(); } ListBox::ListBox( vcl::Window* pParent, WinBits nStyle ) : Control( WindowType::LISTBOX ) { ImplInitListBoxData(); ImplInit( pParent, nStyle ); } ListBox::~ListBox() { disposeOnce(); } void ListBox::dispose() { CallEventListeners( VclEventId::ObjectDying ); mpImplLB.disposeAndClear(); mpFloatWin.disposeAndClear(); mpImplWin.disposeAndClear(); mpBtn.disposeAndClear(); Control::dispose(); } void ListBox::ImplInitListBoxData() { mpFloatWin = nullptr; mpImplWin = nullptr; mpBtn = nullptr; mnDDHeight = 0; mnLineCount = 0; m_nMaxWidthChars = -1; mbDDAutoSize = true; } void ListBox::ImplInit( vcl::Window* pParent, WinBits nStyle ) { nStyle = ImplInitStyle( nStyle ); if ( !(nStyle & WB_NOBORDER) && ( nStyle & WB_DROPDOWN ) ) nStyle |= WB_BORDER; Control::ImplInit( pParent, nStyle, nullptr ); css::uno::Reference< css::datatransfer::dnd::XDropTargetListener> xDrop = new DNDEventDispatcher(this); if( nStyle & WB_DROPDOWN ) { sal_Int32 nLeft, nTop, nRight, nBottom; GetBorder( nLeft, nTop, nRight, nBottom ); mnDDHeight = static_cast(GetTextHeight() + nTop + nBottom + 4); if( IsNativeWidgetEnabled() && IsNativeControlSupported( ControlType::Listbox, ControlPart::Entire ) ) { ImplControlValue aControlValue; tools::Rectangle aCtrlRegion( Point( 0, 0 ), Size( 20, mnDDHeight ) ); tools::Rectangle aBoundingRgn( aCtrlRegion ); tools::Rectangle aContentRgn( aCtrlRegion ); if( GetNativeControlRegion( ControlType::Listbox, ControlPart::Entire, aCtrlRegion, ControlState::ENABLED, aControlValue, aBoundingRgn, aContentRgn ) ) { sal_Int32 nHeight = aBoundingRgn.GetHeight(); if( nHeight > mnDDHeight ) mnDDHeight = static_cast(nHeight); } } mpFloatWin = VclPtr::Create( this ); if (!IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Focus)) mpFloatWin->RequestDoubleBuffering(true); mpFloatWin->SetAutoWidth( true ); mpFloatWin->SetPopupModeEndHdl( LINK( this, ListBox, ImplPopupModeEndHdl ) ); mpFloatWin->GetDropTarget()->addDropTargetListener(xDrop); mpImplWin = VclPtr::Create( this, (nStyle & (WB_LEFT|WB_RIGHT|WB_CENTER))|WB_NOBORDER ); mpImplWin->SetMBDownHdl( LINK( this, ListBox, ImplClickBtnHdl ) ); mpImplWin->Show(); mpImplWin->GetDropTarget()->addDropTargetListener(xDrop); mpImplWin->SetEdgeBlending(false); mpBtn = VclPtr::Create( this, WB_NOLIGHTBORDER | WB_RECTSTYLE ); ImplInitDropDownButton( mpBtn ); mpBtn->SetMBDownHdl( LINK( this, ListBox, ImplClickBtnHdl ) ); mpBtn->Show(); mpBtn->GetDropTarget()->addDropTargetListener(xDrop); } vcl::Window* pLBParent = this; if ( mpFloatWin ) pLBParent = mpFloatWin; mpImplLB = VclPtr::Create( pLBParent, nStyle&(~WB_BORDER) ); mpImplLB->SetSelectHdl( LINK( this, ListBox, ImplSelectHdl ) ); mpImplLB->SetScrollHdl( LINK( this, ListBox, ImplScrollHdl ) ); mpImplLB->SetCancelHdl( LINK( this, ListBox, ImplCancelHdl ) ); mpImplLB->SetDoubleClickHdl( LINK( this, ListBox, ImplDoubleClickHdl ) ); mpImplLB->SetFocusHdl( LINK( this, ListBox, ImplFocusHdl ) ); mpImplLB->SetListItemSelectHdl( LINK( this, ListBox, ImplListItemSelectHdl ) ); mpImplLB->SetPosPixel( Point() ); mpImplLB->SetEdgeBlending(false); mpImplLB->Show(); mpImplLB->GetDropTarget()->addDropTargetListener(xDrop); if ( mpFloatWin ) { mpFloatWin->SetImplListBox( mpImplLB ); mpImplLB->SetSelectionChangedHdl( LINK( this, ListBox, ImplSelectionChangedHdl ) ); } else mpImplLB->GetMainWindow()->AllowGrabFocus( true ); SetCompoundControl( true ); } WinBits ListBox::ImplInitStyle( WinBits nStyle ) { if ( !(nStyle & WB_NOTABSTOP) ) nStyle |= WB_TABSTOP; if ( !(nStyle & WB_NOGROUP) ) nStyle |= WB_GROUP; return nStyle; } IMPL_LINK_NOARG(ListBox, ImplSelectHdl, LinkParamNone*, void) { bool bPopup = IsInDropDown(); if( IsDropDownBox() ) { if( !mpImplLB->IsTravelSelect() ) { mpFloatWin->EndPopupMode(); mpImplWin->GrabFocus(); } mpImplWin->SetItemPos( GetSelectedEntryPos() ); mpImplWin->SetString( GetSelectedEntry() ); if( mpImplLB->GetEntryList().HasImages() ) { Image aImage = mpImplLB->GetEntryList().GetEntryImage( GetSelectedEntryPos() ); mpImplWin->SetImage( aImage ); } mpImplWin->Invalidate(); } if ( ( !IsTravelSelect() || mpImplLB->IsSelectionChanged() ) || ( bPopup && !IsMultiSelectionEnabled() ) ) Select(); } IMPL_LINK( ListBox, ImplFocusHdl, sal_Int32, nPos, void ) { CallEventListeners( VclEventId::ListboxFocus, reinterpret_cast(nPos) ); } IMPL_LINK_NOARG( ListBox, ImplListItemSelectHdl, LinkParamNone*, void ) { CallEventListeners( VclEventId::DropdownSelect ); } IMPL_LINK_NOARG(ListBox, ImplScrollHdl, ImplListBox*, void) { CallEventListeners( VclEventId::ListboxScrolled ); } IMPL_LINK_NOARG(ListBox, ImplCancelHdl, LinkParamNone*, void) { if( IsInDropDown() ) mpFloatWin->EndPopupMode(); } IMPL_LINK( ListBox, ImplSelectionChangedHdl, sal_Int32, nChanged, void ) { if ( mpImplLB->IsTrackingSelect() ) return; const ImplEntryList& rEntryList = mpImplLB->GetEntryList(); if ( rEntryList.IsEntryPosSelected( nChanged ) ) { // FIXME? This should've been turned into an ImplPaintEntry some time ago... if ( nChanged < rEntryList.GetMRUCount() ) nChanged = rEntryList.FindEntry( rEntryList.GetEntryText( nChanged ) ); mpImplWin->SetItemPos( nChanged ); mpImplWin->SetString( rEntryList.GetEntryText( nChanged ) ); if( rEntryList.HasImages() ) { Image aImage = rEntryList.GetEntryImage( nChanged ); mpImplWin->SetImage( aImage ); } } else { mpImplWin->SetItemPos( LISTBOX_ENTRY_NOTFOUND ); mpImplWin->SetString( OUString() ); Image aImage; mpImplWin->SetImage( aImage ); } mpImplWin->Invalidate(); } IMPL_LINK_NOARG(ListBox, ImplDoubleClickHdl, ImplListBoxWindow*, void) { DoubleClick(); } IMPL_LINK_NOARG(ListBox, ImplClickBtnHdl, void*, void) { if( mpFloatWin->IsInPopupMode() ) return; CallEventListeners( VclEventId::DropdownPreOpen ); mpImplWin->GrabFocus(); mpBtn->SetPressed( true ); mpFloatWin->StartFloat( true ); CallEventListeners( VclEventId::DropdownOpen ); ImplClearLayoutData(); if( mpImplLB ) mpImplLB->GetMainWindow()->ImplClearLayoutData(); if( mpImplWin ) mpImplWin->ImplClearLayoutData(); } IMPL_LINK_NOARG(ListBox, ImplPopupModeEndHdl, FloatingWindow*, void) { if( mpFloatWin->IsPopupModeCanceled() ) { if ( ( mpFloatWin->GetPopupModeStartSaveSelection() != LISTBOX_ENTRY_NOTFOUND ) && !IsEntryPosSelected( mpFloatWin->GetPopupModeStartSaveSelection() ) ) { mpImplLB->SelectEntry( mpFloatWin->GetPopupModeStartSaveSelection(), true ); bool bTravelSelect = mpImplLB->IsTravelSelect(); mpImplLB->SetTravelSelect( true ); VclPtr xWindow = this; Select(); if ( xWindow->isDisposed() ) return; mpImplLB->SetTravelSelect( bTravelSelect ); } } ImplClearLayoutData(); if( mpImplLB ) mpImplLB->GetMainWindow()->ImplClearLayoutData(); if( mpImplWin ) mpImplWin->ImplClearLayoutData(); mpBtn->SetPressed( false ); CallEventListeners( VclEventId::DropdownClose ); } void ListBox::ToggleDropDown() { if( !IsDropDownBox() ) return; if( mpFloatWin->IsInPopupMode() ) mpFloatWin->EndPopupMode(); else { CallEventListeners( VclEventId::DropdownPreOpen ); mpImplWin->GrabFocus(); mpBtn->SetPressed( true ); mpFloatWin->StartFloat( true ); CallEventListeners( VclEventId::DropdownOpen ); } } void ListBox::ApplySettings(vcl::RenderContext& rRenderContext) { rRenderContext.SetBackground(); } void ListBox::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags ) { mpImplLB->GetMainWindow()->ApplySettings(*pDev); Point aPos = pDev->LogicToPixel( rPos ); Size aSize = GetSizePixel(); vcl::Font aFont = mpImplLB->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 ); if ( bBorder ) { ImplDrawFrame( pDev, aRect ); } if ( bBackground ) { pDev->SetFillColor( GetControlBackground() ); pDev->DrawRect( aRect ); } } // Content if ( nFlags & SystemTextColorFlags::Mono ) { pDev->SetTextColor( COL_BLACK ); } else { if ( !IsEnabled() ) { const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); pDev->SetTextColor( rStyleSettings.GetDisableColor() ); } else { pDev->SetTextColor( GetTextColor() ); } } const tools::Long nOnePixel = GetDrawPixel( pDev, 1 ); const tools::Long nOffX = 3*nOnePixel; DrawTextFlags nTextStyle = DrawTextFlags::VCenter; tools::Rectangle aTextRect( aPos, aSize ); if ( GetStyle() & WB_CENTER ) nTextStyle |= DrawTextFlags::Center; else if ( GetStyle() & WB_RIGHT ) nTextStyle |= DrawTextFlags::Right; else nTextStyle |= DrawTextFlags::Left; aTextRect.AdjustLeft(nOffX ); aTextRect.AdjustRight( -nOffX ); if ( IsDropDownBox() ) { OUString aText = GetSelectedEntry(); tools::Long nTextHeight = pDev->GetTextHeight(); tools::Long nTextWidth = pDev->GetTextWidth( aText ); tools::Long nOffY = (aSize.Height()-nTextHeight) / 2; // Clipping? if ( (nOffY < 0) || ((nOffY+nTextHeight) > aSize.Height()) || ((nOffX+nTextWidth) > aSize.Width()) ) { tools::Rectangle aClip( aPos, aSize ); if ( nTextHeight > aSize.Height() ) aClip.AdjustBottom(nTextHeight-aSize.Height()+1 ); // So that HP Printers don't optimize this away pDev->IntersectClipRegion( aClip ); } pDev->DrawText( aTextRect, aText, nTextStyle ); } else { tools::Long nTextHeight = pDev->GetTextHeight(); sal_uInt16 nLines = ( nTextHeight > 0 ) ? static_cast(aSize.Height() / nTextHeight) : 1; tools::Rectangle aClip( aPos, aSize ); pDev->IntersectClipRegion( aClip ); if ( !nLines ) nLines = 1; for ( sal_uInt16 n = 0; n < nLines; n++ ) { sal_Int32 nEntry = n+mpImplLB->GetTopEntry(); bool bSelected = mpImplLB->GetEntryList().IsEntryPosSelected( nEntry ); if ( bSelected ) { pDev->SetFillColor( COL_BLACK ); pDev->DrawRect( tools::Rectangle( Point( aPos.X(), aPos.Y() + n*nTextHeight ), Point( aPos.X() + aSize.Width(), aPos.Y() + (n+1)*nTextHeight + 2*nOnePixel ) ) ); pDev->SetFillColor(); pDev->SetTextColor( COL_WHITE ); } aTextRect.SetTop( aPos.Y() + n*nTextHeight ); aTextRect.SetBottom( aTextRect.Top() + nTextHeight ); pDev->DrawText( aTextRect, mpImplLB->GetEntryList().GetEntryText( nEntry ), nTextStyle ); if ( bSelected ) pDev->SetTextColor( COL_BLACK ); } } pDev->Pop(); } void ListBox::GetFocus() { if ( mpImplLB ) { if( IsDropDownBox() ) mpImplWin->GrabFocus(); else mpImplLB->GrabFocus(); } Control::GetFocus(); } void ListBox::LoseFocus() { if( IsDropDownBox() ) { if (mpImplWin) mpImplWin->HideFocus(); } else { if (mpImplLB) mpImplLB->HideFocus(); } Control::LoseFocus(); } void ListBox::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; SetBackground(); // Due to a hack in Window::UpdateSettings the background must be reset // otherwise it will overpaint NWF drawn listboxes Resize(); mpImplLB->Resize(); // Is not called by ListBox::Resize() if the ImplLB does not change if ( mpImplWin ) { mpImplWin->GetOutDev()->SetSettings( GetSettings() ); // If not yet set... mpImplWin->ApplySettings(*mpImplWin->GetOutDev()); mpBtn->GetOutDev()->SetSettings( GetSettings() ); ImplInitDropDownButton( mpBtn ); } if ( IsDropDownBox() ) Invalidate(); } void ListBox::EnableAutoSize( bool bAuto ) { mbDDAutoSize = bAuto; if ( mpFloatWin ) { if ( bAuto && !mpFloatWin->GetDropDownLineCount() ) { // use GetListBoxMaximumLineCount here; before, was on fixed number of five AdaptDropDownLineCountToMaximum(); } else if ( !bAuto ) { mpFloatWin->SetDropDownLineCount( 0 ); } } } void ListBox::SetDropDownLineCount( sal_uInt16 nLines ) { mnLineCount = nLines; if ( mpFloatWin ) mpFloatWin->SetDropDownLineCount( mnLineCount ); } void ListBox::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 ListBox::GetDropDownLineCount() const { if ( mpFloatWin ) return mpFloatWin->GetDropDownLineCount(); return mnLineCount; } void ListBox::setPosSizePixel( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, PosSizeFlags nFlags ) { if( IsDropDownBox() && ( nFlags & PosSizeFlags::Size ) ) { Size aPrefSz = mpFloatWin->GetPrefSize(); if ( ( nFlags & PosSizeFlags::Height ) && ( nHeight >= 2*mnDDHeight ) ) aPrefSz.setHeight( nHeight-mnDDHeight ); if ( nFlags & PosSizeFlags::Width ) aPrefSz.setWidth( nWidth ); mpFloatWin->SetPrefSize( aPrefSz ); if (IsAutoSizeEnabled()) nHeight = mnDDHeight; } Control::setPosSizePixel( nX, nY, nWidth, nHeight, nFlags ); } void ListBox::Resize() { Size aOutSz = GetOutputSizePixel(); if( IsDropDownBox() ) { // Initialize the dropdown button size with the standard scrollbar width tools::Long nSBWidth = GetSettings().GetStyleSettings().GetScrollBarSize(); tools::Long nBottom = aOutSz.Height(); // Note: in case of no border, pBorder will actually be this vcl::Window *pBorder = GetWindow( GetWindowType::Border ); ImplControlValue aControlValue; Point aPoint; tools::Rectangle aContent, aBound; // Use the full extent of the control tools::Rectangle aArea( aPoint, pBorder->GetOutputSizePixel() ); if ( GetNativeControlRegion( ControlType::Listbox, ControlPart::ButtonDown, aArea, ControlState::NONE, aControlValue, aBound, aContent) ) { // Convert back from border space to local coordinates aPoint = pBorder->ScreenToOutputPixel( OutputToScreenPixel( aPoint ) ); aContent.Move( -aPoint.X(), -aPoint.Y() ); // Use the themes drop down size for the button aOutSz.setWidth( aContent.Left() ); mpBtn->setPosSizePixel( aContent.Left(), 0, aContent.GetWidth(), nBottom ); // Adjust the size of the edit field if ( GetNativeControlRegion( ControlType::Listbox, 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 if( ! (GetStyle() & WB_BORDER) && ImplGetSVData()->maNWFData.mbNoFocusRects ) { // No border but focus ring behavior -> we have a problem; the // native rect relies on the border to draw the focus // let's do the best we can and center vertically, so it doesn't look // completely wrong. Size aSz( GetOutputSizePixel() ); tools::Long nDiff = aContent.Top() - (aSz.Height() - aContent.GetHeight())/2; aContent.AdjustTop( -nDiff ); aContent.AdjustBottom( -nDiff ); } mpImplWin->SetPosSizePixel( aContent.TopLeft(), aContent.GetSize() ); } else mpImplWin->SetSizePixel( aOutSz ); } else { nSBWidth = CalcZoom( nSBWidth ); mpImplWin->setPosSizePixel( 0, 0, aOutSz.Width() - nSBWidth, aOutSz.Height() ); mpBtn->setPosSizePixel( aOutSz.Width() - nSBWidth, 0, nSBWidth, aOutSz.Height() ); } } else { mpImplLB->SetSizePixel( aOutSz ); } // Retain FloatingWindow size even when it's invisible, as we still process KEY_PGUP/DOWN ... if ( mpFloatWin ) mpFloatWin->SetSizePixel( mpFloatWin->CalcFloatSize() ); Control::Resize(); } void ListBox::FillLayoutData() const { mxLayoutData.emplace(); const ImplListBoxWindow* rMainWin = mpImplLB->GetMainWindow(); if( mpFloatWin ) { // Dropdown mode AppendLayoutData( *mpImplWin ); mpImplWin->SetLayoutDataParent( this ); if( mpFloatWin->IsReallyVisible() ) { AppendLayoutData( *rMainWin ); rMainWin->SetLayoutDataParent( this ); } } else { AppendLayoutData( *rMainWin ); rMainWin->SetLayoutDataParent( this ); } } tools::Long ListBox::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 = mpImplLB->GetMainWindow(); // Convert coordinates to ImplListBoxWindow pixel coordinate space Point aConvPoint = LogicToPixel( rPoint ); AbsoluteScreenPixelPoint aConvPointAbs = OutputToAbsoluteScreenPixel( aConvPoint ); aConvPoint = rMain->AbsoluteScreenToOutputPixel( aConvPointAbs ); aConvPoint = rMain->PixelToLogic( aConvPoint ); // Try to find entry sal_Int32 nEntry = rMain->GetEntryPosForPoint( aConvPoint ); if( nEntry == LISTBOX_ENTRY_NOTFOUND ) { // Not found, maybe dropdown case if( mpImplWin && mpImplWin->IsReallyVisible() ) { // Convert to impl window pixel coordinates aConvPoint = LogicToPixel( rPoint ); aConvPointAbs = OutputToAbsoluteScreenPixel( aConvPoint ); aConvPoint = mpImplWin->AbsoluteScreenToOutputPixel( aConvPointAbs ); // Check whether converted point is inside impl window Size aImplWinSize = mpImplWin->GetOutputSizePixel(); if( aConvPoint.X() >= 0 && aConvPoint.Y() >= 0 && aConvPoint.X() < aImplWinSize.Width() && aConvPoint.Y() < aImplWinSize.Height() ) { // Inside the impl window, the position is the current item pos rPos = mpImplWin->GetItemPos(); } else nIndex = -1; } else nIndex = -1; } else rPos = nEntry; SAL_WARN_IF( nIndex == -1, "vcl", "found index for point, but relative index failed" ); } // Get line relative index if( nIndex != -1 ) nIndex = ToRelativeLineIndex( nIndex ); return nIndex; } void ListBox::StateChanged( StateChangedType nType ) { if( nType == StateChangedType::ReadOnly ) { if( mpImplWin ) mpImplWin->Enable( !IsReadOnly() ); if( mpBtn ) mpBtn->Enable( !IsReadOnly() ); } else if( nType == StateChangedType::Enable ) { mpImplLB->Enable( IsEnabled() ); if( mpImplWin ) { mpImplWin->Enable( IsEnabled() ); if ( IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire) && ! IsNativeControlSupported(ControlType::Listbox, ControlPart::ButtonDown) ) { GetWindow( GetWindowType::Border )->Invalidate( InvalidateFlags::NoErase ); } else mpImplWin->Invalidate(); } if( mpBtn ) mpBtn->Enable( IsEnabled() ); } else if( nType == StateChangedType::UpdateMode ) { mpImplLB->SetUpdateMode( IsUpdateMode() ); } else if ( nType == StateChangedType::Zoom ) { mpImplLB->SetZoom( GetZoom() ); if ( mpImplWin ) { mpImplWin->SetZoom( GetZoom() ); mpImplWin->SetFont( mpImplLB->GetMainWindow()->GetFont() ); mpImplWin->Invalidate(); } Resize(); } else if ( nType == StateChangedType::ControlFont ) { mpImplLB->SetControlFont( GetControlFont() ); if ( mpImplWin ) { mpImplWin->SetControlFont( GetControlFont() ); mpImplWin->SetFont( mpImplLB->GetMainWindow()->GetFont() ); mpImplWin->Invalidate(); } Resize(); } else if ( nType == StateChangedType::ControlForeground ) { mpImplLB->SetControlForeground( GetControlForeground() ); if ( mpImplWin ) { mpImplWin->SetControlForeground( GetControlForeground() ); mpImplWin->SetTextColor( GetControlForeground() ); mpImplWin->SetFont( mpImplLB->GetMainWindow()->GetFont() ); mpImplWin->Invalidate(); } } else if ( nType == StateChangedType::ControlBackground ) { mpImplLB->SetControlBackground( GetControlBackground() ); if ( mpImplWin ) { mpImplWin->SetBackground( GetControlBackground() ); mpImplWin->SetControlBackground( GetControlBackground() ); mpImplWin->SetFont( mpImplLB->GetMainWindow()->GetFont() ); mpImplWin->Invalidate(); } } else if ( nType == StateChangedType::Style ) { SetStyle( ImplInitStyle( GetStyle() ) ); mpImplLB->GetMainWindow()->EnableSort( ( GetStyle() & WB_SORT ) != 0 ); bool bSimpleMode = ( GetStyle() & WB_SIMPLEMODE ) != 0; mpImplLB->SetMultiSelectionSimpleMode( bSimpleMode ); } else if( nType == StateChangedType::Mirroring ) { if( mpBtn ) { mpBtn->EnableRTL( IsRTLEnabled() ); ImplInitDropDownButton( mpBtn ); } mpImplLB->EnableRTL( IsRTLEnabled() ); if( mpImplWin ) mpImplWin->EnableRTL( IsRTLEnabled() ); Resize(); } Control::StateChanged( nType ); } bool ListBox::PreNotify( NotifyEvent& rNEvt ) { bool bDone = false; if ( mpImplLB ) { if( ( rNEvt.GetType() == NotifyEventType::KEYINPUT ) && ( rNEvt.GetWindow() == mpImplWin ) ) { KeyEvent aKeyEvt = *rNEvt.GetKeyEvent(); switch( aKeyEvt.GetKeyCode().GetCode() ) { case KEY_DOWN: { if( mpFloatWin && !mpFloatWin->IsInPopupMode() && aKeyEvt.GetKeyCode().IsMod2() ) { CallEventListeners( VclEventId::DropdownPreOpen ); mpBtn->SetPressed( true ); mpFloatWin->StartFloat( false ); CallEventListeners( VclEventId::DropdownOpen ); bDone = true; } else { bDone = mpImplLB->ProcessKeyInput( aKeyEvt ); } } break; case KEY_UP: { if( mpFloatWin && mpFloatWin->IsInPopupMode() && aKeyEvt.GetKeyCode().IsMod2() ) { mpFloatWin->EndPopupMode(); bDone = true; } else { bDone = mpImplLB->ProcessKeyInput( aKeyEvt ); } } break; case KEY_RETURN: { if( IsInDropDown() ) { mpImplLB->ProcessKeyInput( aKeyEvt ); bDone = true; } } break; default: { bDone = mpImplLB->ProcessKeyInput( aKeyEvt ); } } } else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS ) { if ( IsInDropDown() && !HasChildPathFocus( true ) ) mpFloatWin->EndPopupMode(); } else if ( (rNEvt.GetType() == NotifyEventType::COMMAND) && (rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) && (rNEvt.GetWindow() == mpImplWin) ) { const Point& rMousePos = rNEvt.GetCommandEvent()->GetMousePosPixel(); const tools::Rectangle aWinRect(mpImplWin->GetPosPixel(), mpImplWin->GetSizePixel()); const bool bMousePositionedOverWin = aWinRect.Contains(rMousePos); MouseWheelBehaviour nWheelBehavior( GetSettings().GetMouseSettings().GetWheelBehavior() ); if (bMousePositionedOverWin && ((nWheelBehavior == MouseWheelBehaviour::ALWAYS) || ((nWheelBehavior == MouseWheelBehaviour::FocusOnly) && HasChildPathFocus()))) { bDone = mpImplLB->HandleWheelAsCursorTravel(*rNEvt.GetCommandEvent(), *this); } else { bDone = false; // Don't consume this event, let the default handling take it (i.e. scroll the context) } } } if (rNEvt.GetType() == NotifyEventType::MOUSEMOVE) { const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent(); if (pMouseEvt && (pMouseEvt->IsEnterWindow() || pMouseEvt->IsLeaveWindow())) { // trigger redraw as mouse over state has changed if (IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire) && !IsNativeControlSupported(ControlType::Listbox, ControlPart::ButtonDown)) { GetWindow(GetWindowType::Border)->Invalidate(InvalidateFlags::NoErase); } } } return bDone || Control::PreNotify( rNEvt ); } void ListBox::Select() { ImplCallEventListenersAndHandler( VclEventId::ListboxSelect, [this] () { maSelectHdl.Call(*this); } ); } void ListBox::DoubleClick() { ImplCallEventListenersAndHandler( VclEventId::ListboxDoubleClick, {} ); } void ListBox::Clear() { if (!mpImplLB) return; mpImplLB->Clear(); if( IsDropDownBox() ) { mpImplWin->SetItemPos( LISTBOX_ENTRY_NOTFOUND ); mpImplWin->SetString( OUString() ); Image aImage; mpImplWin->SetImage( aImage ); mpImplWin->Invalidate(); } CallEventListeners( VclEventId::ListboxItemRemoved, reinterpret_cast(-1) ); } void ListBox::SetNoSelection() { mpImplLB->SetNoSelection(); if( IsDropDownBox() ) { mpImplWin->SetItemPos( LISTBOX_ENTRY_NOTFOUND ); mpImplWin->SetString( OUString() ); Image aImage; mpImplWin->SetImage( aImage ); mpImplWin->Invalidate(); } } sal_Int32 ListBox::InsertEntry( const OUString& rStr, sal_Int32 nPos ) { sal_Int32 nRealPos = mpImplLB->InsertEntry( nPos + mpImplLB->GetEntryList().GetMRUCount(), rStr ); nRealPos = sal::static_int_cast(nRealPos - mpImplLB->GetEntryList().GetMRUCount()); CallEventListeners( VclEventId::ListboxItemAdded, reinterpret_cast(nRealPos) ); return nRealPos; } sal_Int32 ListBox::InsertEntry( const OUString& rStr, const Image& rImage, sal_Int32 nPos ) { sal_Int32 nRealPos = mpImplLB->InsertEntry( nPos + mpImplLB->GetEntryList().GetMRUCount(), rStr, rImage ); nRealPos = sal::static_int_cast(nRealPos - mpImplLB->GetEntryList().GetMRUCount()); CallEventListeners( VclEventId::ListboxItemAdded, reinterpret_cast(nRealPos) ); return nRealPos; } void ListBox::RemoveEntry( sal_Int32 nPos ) { mpImplLB->RemoveEntry( nPos + mpImplLB->GetEntryList().GetMRUCount() ); CallEventListeners( VclEventId::ListboxItemRemoved, reinterpret_cast(nPos) ); } Image ListBox::GetEntryImage( sal_Int32 nPos ) const { if ( mpImplLB && mpImplLB->GetEntryList().HasEntryImage( nPos ) ) return mpImplLB->GetEntryList().GetEntryImage( nPos ); return Image(); } sal_Int32 ListBox::GetEntryPos( std::u16string_view rStr ) const { if (!mpImplLB) return LISTBOX_ENTRY_NOTFOUND; sal_Int32 nPos = mpImplLB->GetEntryList().FindEntry( rStr ); if ( nPos != LISTBOX_ENTRY_NOTFOUND ) nPos = nPos - mpImplLB->GetEntryList().GetMRUCount(); return nPos; } OUString ListBox::GetEntry( sal_Int32 nPos ) const { if (!mpImplLB) return OUString(); return mpImplLB->GetEntryList().GetEntryText( nPos + mpImplLB->GetEntryList().GetMRUCount() ); } sal_Int32 ListBox::GetEntryCount() const { if (!mpImplLB) return 0; return mpImplLB->GetEntryList().GetEntryCount() - mpImplLB->GetEntryList().GetMRUCount(); } OUString ListBox::GetSelectedEntry(sal_Int32 nIndex) const { return GetEntry( GetSelectedEntryPos( nIndex ) ); } sal_Int32 ListBox::GetSelectedEntryCount() const { if (!mpImplLB) return 0; return mpImplLB->GetEntryList().GetSelectedEntryCount(); } sal_Int32 ListBox::GetSelectedEntryPos( sal_Int32 nIndex ) const { if (!mpImplLB) return LISTBOX_ENTRY_NOTFOUND; sal_Int32 nPos = mpImplLB->GetEntryList().GetSelectedEntryPos( nIndex ); if ( nPos != LISTBOX_ENTRY_NOTFOUND ) { if ( nPos < mpImplLB->GetEntryList().GetMRUCount() ) nPos = mpImplLB->GetEntryList().FindEntry( mpImplLB->GetEntryList().GetEntryText( nPos ) ); nPos = nPos - mpImplLB->GetEntryList().GetMRUCount(); } return nPos; } bool ListBox::IsEntryPosSelected( sal_Int32 nPos ) const { return mpImplLB->GetEntryList().IsEntryPosSelected( nPos + mpImplLB->GetEntryList().GetMRUCount() ); } void ListBox::SelectEntry( std::u16string_view rStr, bool bSelect ) { SelectEntryPos( GetEntryPos( rStr ), bSelect ); } void ListBox::SelectEntryPos( sal_Int32 nPos, bool bSelect ) { if (!mpImplLB) return; if ( 0 <= nPos && nPos < mpImplLB->GetEntryList().GetEntryCount() ) { sal_Int32 nCurrentPos = mpImplLB->GetCurrentPos(); mpImplLB->SelectEntry( nPos + mpImplLB->GetEntryList().GetMRUCount(), bSelect ); //Only when bSelect == true, send both Selection & Focus events if (nCurrentPos != nPos && bSelect) { CallEventListeners( VclEventId::ListboxSelect, reinterpret_cast(nPos)); if (HasFocus()) CallEventListeners( VclEventId::ListboxFocus, reinterpret_cast(nPos)); } } } void ListBox::SelectEntriesPos( const std::vector& rPositions, bool bSelect ) { if (!mpImplLB) return; bool bCallListeners = false; const sal_Int32 nCurrentPos = mpImplLB->GetCurrentPos(); const auto nEntryCount = mpImplLB->GetEntryList().GetEntryCount(); const auto nMRUCount = mpImplLB->GetEntryList().GetMRUCount(); for (auto nPos : rPositions) { if (0 <= nPos && nPos < nEntryCount) { mpImplLB->SelectEntry(nPos + nMRUCount, bSelect); if (nCurrentPos != nPos && bSelect) bCallListeners = true; } } //Only when bSelect == true, send both Selection & Focus events if (bCallListeners) { CallEventListeners(VclEventId::ListboxSelect); if (HasFocus()) CallEventListeners(VclEventId::ListboxFocus); } } void ListBox::SetEntryData( sal_Int32 nPos, void* pNewData ) { mpImplLB->SetEntryData( nPos + mpImplLB->GetEntryList().GetMRUCount(), pNewData ); } void* ListBox::GetEntryData( sal_Int32 nPos ) const { return mpImplLB->GetEntryList().GetEntryData( nPos + mpImplLB->GetEntryList().GetMRUCount() ); } void ListBox::SetEntryFlags( sal_Int32 nPos, ListBoxEntryFlags nFlags ) { mpImplLB->SetEntryFlags( nPos + mpImplLB->GetEntryList().GetMRUCount(), nFlags ); } void ListBox::SetTopEntry( sal_Int32 nPos ) { mpImplLB->SetTopEntry( nPos + mpImplLB->GetEntryList().GetMRUCount() ); } sal_Int32 ListBox::GetTopEntry() const { sal_Int32 nPos = GetEntryCount() ? mpImplLB->GetTopEntry() : LISTBOX_ENTRY_NOTFOUND; if ( nPos < mpImplLB->GetEntryList().GetMRUCount() ) nPos = 0; return nPos; } bool ListBox::IsTravelSelect() const { return mpImplLB->IsTravelSelect(); } bool ListBox::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 mpFloatWin && mpFloatWin->IsInPopupMode() && mpFloatWin->ImplIsInPrivatePopupMode(); } tools::Rectangle ListBox::GetBoundingRectangle( sal_Int32 nItem ) const { tools::Rectangle aRect = mpImplLB->GetMainWindow()->GetBoundingRectangle( nItem ); tools::Rectangle aOffset = mpImplLB->GetMainWindow()->GetWindowExtentsRelative( *static_cast(const_cast(this)) ); aRect.Move( aOffset.Left(), aOffset.Top() ); return aRect; } void ListBox::EnableMultiSelection( bool bMulti ) { mpImplLB->EnableMultiSelection( bMulti ); // WB_SIMPLEMODE: // The MultiListBox behaves just like a normal ListBox // MultiSelection is possible via corresponding additional keys bool bSimpleMode = ( GetStyle() & WB_SIMPLEMODE ) != 0; mpImplLB->SetMultiSelectionSimpleMode( bSimpleMode ); // In a MultiSelection, we can't see us travelling without focus if ( mpFloatWin ) mpImplLB->GetMainWindow()->AllowGrabFocus( bMulti ); } bool ListBox::IsMultiSelectionEnabled() const { return mpImplLB->IsMultiSelectionEnabled(); } void ListBox::SetHighlightColor(const Color& rColor) { AllSettings aSettings(GetSettings()); StyleSettings aStyle(aSettings.GetStyleSettings()); aStyle.SetHighlightColor(rColor); aSettings.SetStyleSettings(aStyle); SetSettings(aSettings); mpImplLB->SetHighlightColor(rColor); } void ListBox::SetHighlightTextColor(const Color& rColor) { AllSettings aSettings(GetSettings()); StyleSettings aStyle(aSettings.GetStyleSettings()); aStyle.SetHighlightTextColor(rColor); aSettings.SetStyleSettings(aStyle); SetSettings(aSettings); mpImplLB->SetHighlightTextColor(rColor); } Size ListBox::CalcMinimumSize() const { Size aSz; if (!mpImplLB) return aSz; aSz = CalcSubEditSize(); bool bAddScrollWidth = false; if (IsDropDownBox()) { aSz.AdjustHeight(4 ); // add a space between entry and border aSz.AdjustWidth(4 ); // add a little breathing space bAddScrollWidth = true; } else bAddScrollWidth = (GetStyle() & WB_VSCROLL) == WB_VSCROLL; if (bAddScrollWidth) { // Try native borders; scrollbar size may not be a good indicator // See how large the edit area inside is to estimate what is needed for the dropdown ImplControlValue aControlValue; tools::Rectangle aContent, aBound; Size aTestSize( 100, 20 ); tools::Rectangle aArea( Point(), aTestSize ); if( GetNativeControlRegion( ControlType::Listbox, ControlPart::SubEdit, aArea, ControlState::NONE, aControlValue, aBound, aContent) ) { // use the themes drop down size aSz.AdjustWidth(aTestSize.Width() - aContent.GetWidth() ); } else aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() ); } aSz = CalcWindowSize( aSz ); if (IsDropDownBox()) // Check minimum height of dropdown box { ImplControlValue aControlValue; tools::Rectangle aRect( Point( 0, 0 ), aSz ); tools::Rectangle aContent, aBound; if( GetNativeControlRegion( ControlType::Listbox, ControlPart::Entire, aRect, ControlState::NONE, aControlValue, aBound, aContent) ) { if( aBound.GetHeight() > aSz.Height() ) aSz.setHeight( aBound.GetHeight() ); } } return aSz; } Size ListBox::CalcSubEditSize() const { Size aSz; if (!mpImplLB) return aSz; if ( !IsDropDownBox() ) aSz = mpImplLB->CalcSize (mnLineCount ? mnLineCount : mpImplLB->GetEntryList().GetEntryCount()); else { aSz.setHeight( mpImplLB->GetEntryHeight() ); // Size to maximum entry width aSz.setWidth( mpImplLB->GetMaxEntryWidth() ); if (m_nMaxWidthChars != -1) { tools::Long nMaxWidth = m_nMaxWidthChars * approximate_char_width(); aSz.setWidth( std::min(aSz.Width(), nMaxWidth) ); } // Do not create ultrathin ListBoxes, it doesn't look good if( aSz.Width() < GetSettings().GetStyleSettings().GetScrollBarSize() ) aSz.setWidth( GetSettings().GetStyleSettings().GetScrollBarSize() ); } return aSz; } Size ListBox::GetOptimalSize() const { return CalcMinimumSize(); } Size ListBox::CalcAdjustedSize( const Size& rPrefSize ) const { Size aSz = rPrefSize; sal_Int32 nLeft, nTop, nRight, nBottom; static_cast(const_cast(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 ); } else { aSz.setHeight( mnDDHeight ); } aSz.AdjustHeight(nTop+nBottom ); aSz = CalcWindowSize( aSz ); return aSz; } Size ListBox::CalcBlockSize( sal_uInt16 nColumns, sal_uInt16 nLines ) const { // ScrollBars are shown if needed Size aMinSz = CalcMinimumSize(); // aMinSz = ImplCalcOutSz( aMinSz ); Size aSz; // Height if ( nLines ) { if ( !IsDropDownBox() ) aSz.setHeight( mpImplLB->CalcSize( nLines ).Height() ); else aSz.setHeight( mnDDHeight ); } else aSz.setHeight( aMinSz.Height() ); // Width if ( nColumns ) aSz.setWidth( nColumns * GetTextWidth( OUString('X') ) ); else aSz.setWidth( aMinSz.Width() ); if ( IsDropDownBox() ) aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() ); if ( !IsDropDownBox() ) { if ( aSz.Width() < aMinSz.Width() ) aSz.AdjustHeight(GetSettings().GetStyleSettings().GetScrollBarSize() ); if ( aSz.Height() < aMinSz.Height() ) aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() ); } aSz = CalcWindowSize( aSz ); return aSz; } void ListBox::GetMaxVisColumnsAndLines( sal_uInt16& rnCols, sal_uInt16& rnLines ) const { float nCharWidth = approximate_char_width(); if ( !IsDropDownBox() ) { Size aOutSz = mpImplLB->GetMainWindow()->GetOutputSizePixel(); rnCols = static_cast(aOutSz.Width()/nCharWidth); rnLines = static_cast(aOutSz.Height()/mpImplLB->GetEntryHeightWithMargin()); } else { Size aOutSz = mpImplWin->GetOutputSizePixel(); rnCols = static_cast(aOutSz.Width()/nCharWidth); rnLines = 1; } } void ListBox::SetReadOnly( bool bReadOnly ) { if ( mpImplLB->IsReadOnly() != bReadOnly ) { mpImplLB->SetReadOnly( bReadOnly ); CompatStateChanged( StateChangedType::ReadOnly ); } } bool ListBox::IsReadOnly() const { return mpImplLB->IsReadOnly(); } void ListBox::SetSeparatorPos( sal_Int32 n ) { mpImplLB->SetSeparatorPos( n ); } sal_Int32 ListBox::GetSeparatorPos() const { return mpImplLB->GetSeparatorPos(); } void ListBox::AddSeparator( sal_Int32 n ) { mpImplLB->AddSeparator( n ); } sal_uInt16 ListBox::GetDisplayLineCount() const { return mpImplLB->GetDisplayLineCount(); } tools::Rectangle ListBox::GetDropDownPosSizePixel() const { return mpFloatWin ? mpFloatWin->GetWindowExtentsRelative(*this) : tools::Rectangle(); } const Wallpaper& ListBox::GetDisplayBackground() const { // !!! Recursion does not occur because the ImplListBox is initialized by default // to a non-transparent color in Window::ImplInitData return mpImplLB->GetDisplayBackground(); } void ListBox::setMaxWidthChars(sal_Int32 nWidth) { if (nWidth != m_nMaxWidthChars) { m_nMaxWidthChars = nWidth; queue_resize(); } } bool ListBox::set_property(const OUString &rKey, const OUString &rValue) { if (rKey == "active") SelectEntryPos(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 return Control::set_property(rKey, rValue); return true; } FactoryFunction ListBox::GetUITestFactory() const { return ListBoxUIObject::create; } void ListBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) { Control::DumpAsPropertyTree(rJsonWriter); { auto entriesNode = rJsonWriter.startArray("entries"); for (int i = 0; i < GetEntryCount(); ++i) { rJsonWriter.putSimpleValue(GetEntry(i)); } } rJsonWriter.put("selectedCount", GetSelectedEntryCount()); { auto entriesNode = rJsonWriter.startArray("selectedEntries"); for (int i = 0; i < GetSelectedEntryCount(); ++i) { rJsonWriter.putSimpleValue(OUString::number(GetSelectedEntryPos(i))); } } } MultiListBox::MultiListBox( vcl::Window* pParent, WinBits nStyle ) : ListBox( WindowType::MULTILISTBOX ) { ImplInit( pParent, nStyle ); EnableMultiSelection( true ); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */