diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/source/control/button.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/control/button.cxx')
-rw-r--r-- | vcl/source/control/button.cxx | 3826 |
1 files changed, 3826 insertions, 0 deletions
diff --git a/vcl/source/control/button.cxx b/vcl/source/control/button.cxx new file mode 100644 index 000000000..210d2b629 --- /dev/null +++ b/vcl/source/control/button.cxx @@ -0,0 +1,3826 @@ +/* -*- 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 <tools/poly.hxx> + +#include <vcl/builder.hxx> +#include <vcl/cvtgrf.hxx> +#include <vcl/image.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/decoview.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/toolkit/dialog.hxx> +#include <vcl/toolkit/fixed.hxx> +#include <vcl/toolkit/button.hxx> +#include <vcl/salnativewidgets.hxx> +#include <vcl/toolkit/edit.hxx> +#include <vcl/layout.hxx> +#include <vcl/stdtext.hxx> +#include <vcl/uitest/uiobject.hxx> + +#include <bitmaps.hlst> +#include <svdata.hxx> +#include <window.h> +#include <vclstatuslistener.hxx> +#include <osl/diagnose.h> + +#include <comphelper/base64.hxx> +#include <comphelper/dispatchcommand.hxx> +#include <comphelper/lok.hxx> +#include <officecfg/Office/Common.hxx> +#include <boost/property_tree/ptree.hpp> +#include <tools/json_writer.hxx> +#include <tools/stream.hxx> + + +using namespace css; + +constexpr auto PUSHBUTTON_VIEW_STYLE = WB_3DLOOK | + WB_LEFT | WB_CENTER | WB_RIGHT | + WB_TOP | WB_VCENTER | WB_BOTTOM | + WB_WORDBREAK | WB_NOLABEL | + WB_DEFBUTTON | WB_NOLIGHTBORDER | + WB_RECTSTYLE | WB_SMALLSTYLE | + WB_TOGGLE; +constexpr auto RADIOBUTTON_VIEW_STYLE = WB_3DLOOK | + WB_LEFT | WB_CENTER | WB_RIGHT | + WB_TOP | WB_VCENTER | WB_BOTTOM | + WB_WORDBREAK | WB_NOLABEL; +constexpr auto CHECKBOX_VIEW_STYLE = WB_3DLOOK | + WB_LEFT | WB_CENTER | WB_RIGHT | + WB_TOP | WB_VCENTER | WB_BOTTOM | + WB_WORDBREAK | WB_NOLABEL; + +#define STYLE_RADIOBUTTON_MONO (sal_uInt16(0x0001)) // legacy +#define STYLE_CHECKBOX_MONO (sal_uInt16(0x0001)) // legacy + +class ImplCommonButtonData +{ +public: + ImplCommonButtonData(); + + tools::Rectangle maFocusRect; + tools::Long mnSeparatorX; + DrawButtonFlags mnButtonState; + bool mbSmallSymbol; + bool mbGeneratedTooltip; + + Image maImage; + ImageAlign meImageAlign; + SymbolAlign meSymbolAlign; + + Image maCustomContentImage; + + /** StatusListener. Updates the button as the slot state changes */ + rtl::Reference<VclStatusListener<Button>> mpStatusListener; +}; + +ImplCommonButtonData::ImplCommonButtonData() : mnSeparatorX(0), mnButtonState(DrawButtonFlags::NONE), +mbSmallSymbol(false), mbGeneratedTooltip(false), meImageAlign(ImageAlign::Top), meSymbolAlign(SymbolAlign::LEFT) +{ +} + +Button::Button( WindowType nType ) : + Control( nType ), + mpButtonData( std::make_unique<ImplCommonButtonData>() ) +{ +} + +Button::~Button() +{ + disposeOnce(); +} + +void Button::dispose() +{ + if (mpButtonData->mpStatusListener.is()) + mpButtonData->mpStatusListener->dispose(); + Control::dispose(); +} + +void Button::SetCommandHandler(const OUString& aCommand) +{ + maCommand = aCommand; + SetClickHdl( LINK( this, Button, dispatchCommandHandler) ); + + mpButtonData->mpStatusListener = new VclStatusListener<Button>(this, aCommand); + mpButtonData->mpStatusListener->startListening(); +} + +void Button::Click() +{ + ImplCallEventListenersAndHandler( VclEventId::ButtonClick, [this] () { maClickHdl.Call(this); } ); +} + +void Button::SetModeImage( const Image& rImage ) +{ + if ( rImage != mpButtonData->maImage ) + { + mpButtonData->maImage = rImage; + StateChanged( StateChangedType::Data ); + queue_resize(); + } +} + +Image const & Button::GetModeImage( ) const +{ + return mpButtonData->maImage; +} + +bool Button::HasImage() const +{ + return !!(mpButtonData->maImage); +} + +void Button::SetImageAlign( ImageAlign eAlign ) +{ + if ( mpButtonData->meImageAlign != eAlign ) + { + mpButtonData->meImageAlign = eAlign; + StateChanged( StateChangedType::Data ); + } +} + +ImageAlign Button::GetImageAlign() const +{ + return mpButtonData->meImageAlign; +} + +void Button::SetCustomButtonImage(const Image& rImage) +{ + if (rImage != mpButtonData->maCustomContentImage) + { + mpButtonData->maCustomContentImage = rImage; + StateChanged( StateChangedType::Data ); + } +} + +Image const & Button::GetCustomButtonImage() const +{ + return mpButtonData->maCustomContentImage; +} + +tools::Long Button::ImplGetSeparatorX() const +{ + return mpButtonData->mnSeparatorX; +} + +void Button::ImplSetSeparatorX( tools::Long nX ) +{ + mpButtonData->mnSeparatorX = nX; +} + +DrawTextFlags Button::ImplGetTextStyle( WinBits nWinStyle, SystemTextColorFlags nSystemTextColorFlags ) const +{ + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + DrawTextFlags nTextStyle = FixedText::ImplGetTextStyle(nWinStyle & ~WB_DEFBUTTON); + + if (!IsEnabled()) + nTextStyle |= DrawTextFlags::Disable; + + if ((nSystemTextColorFlags & SystemTextColorFlags::Mono) || + (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono)) + { + nTextStyle |= DrawTextFlags::Mono; + } + + return nTextStyle; +} + +void Button::ImplDrawAlignedImage(OutputDevice* pDev, Point& rPos, + Size& rSize, + sal_Int32 nImageSep, + DrawTextFlags nTextStyle, tools::Rectangle *pSymbolRect, + bool bAddImageSep) +{ + OUString aText(GetText()); + bool bDrawImage = HasImage(); + bool bDrawText = !aText.isEmpty(); + bool bHasSymbol = pSymbolRect != nullptr; + + // No text and no image => nothing to do => return + if (!bDrawImage && !bDrawText && !bHasSymbol) + return; + + WinBits nWinStyle = GetStyle(); + tools::Rectangle aOutRect( rPos, rSize ); + ImageAlign eImageAlign = mpButtonData->meImageAlign; + Size aImageSize = mpButtonData->maImage.GetSizePixel(); + + aImageSize.setWidth( CalcZoom( aImageSize.Width() ) ); + aImageSize.setHeight( CalcZoom( aImageSize.Height() ) ); + + // Drawing text or symbol only is simple, use style and output rectangle + if (bHasSymbol && !bDrawImage && !bDrawText) + { + *pSymbolRect = aOutRect; + return; + } + else if (bDrawText && !bDrawImage && !bHasSymbol) + { + aOutRect = DrawControlText(*pDev, aOutRect, aText, nTextStyle, nullptr, nullptr); + tools::Rectangle textRect = GetTextRect( + tools::Rectangle(Point(), Size(0x7fffffff, 0x7fffffff)), aText, nTextStyle); + // If the button text doesn't fit into it, put it into a tooltip (might happen in sidebar) + if (GetQuickHelpText()!= aText && mpButtonData->mbGeneratedTooltip) + SetQuickHelpText(""); + if (GetQuickHelpText().isEmpty() && textRect.getWidth() > rSize.getWidth()) + { + SetQuickHelpText(aText); + mpButtonData->mbGeneratedTooltip = true; + } + + ImplSetFocusRect(aOutRect); + rSize = aOutRect.GetSize(); + rPos = aOutRect.TopLeft(); + + return; + } + + // check for HC mode ( image only! ) + Image* pImage = &(mpButtonData->maImage); + + Size aTextSize; + Size aSymbolSize; + Size aDeviceTextSize; + Point aImagePos = rPos; + Point aTextPos = rPos; + tools::Rectangle aUnion(aImagePos, aImageSize); + tools::Long nSymbolHeight = 0; + + if (bDrawText || bHasSymbol) + { + // Get the size of the text output area ( the symbol will be drawn in + // this area as well, so the symbol rectangle will be calculated here, too ) + + tools::Rectangle aRect(Point(), rSize); + Size aTSSize; + + if (bHasSymbol) + { + tools::Rectangle aSymbol; + if (bDrawText) + { + nSymbolHeight = pDev->GetTextHeight(); + if (mpButtonData->mbSmallSymbol) + nSymbolHeight = nSymbolHeight * 3 / 4; + + aSymbol = tools::Rectangle(Point(), Size(nSymbolHeight, nSymbolHeight)); + ImplCalcSymbolRect(aSymbol); + aRect.AdjustLeft(3 * nSymbolHeight / 2 ); + aTSSize.setWidth( 3 * nSymbolHeight / 2 ); + } + else + { + aSymbol = tools::Rectangle(Point(), rSize); + ImplCalcSymbolRect(aSymbol); + aTSSize.setWidth( aSymbol.GetWidth() ); + } + aTSSize.setHeight( aSymbol.GetHeight() ); + aSymbolSize = aSymbol.GetSize(); + } + + if (bDrawText) + { + if ((eImageAlign == ImageAlign::LeftTop) || + (eImageAlign == ImageAlign::Left ) || + (eImageAlign == ImageAlign::LeftBottom) || + (eImageAlign == ImageAlign::RightTop) || + (eImageAlign == ImageAlign::Right) || + (eImageAlign == ImageAlign::RightBottom)) + { + aRect.AdjustRight( -sal_Int32(aImageSize.Width() + nImageSep) ); + } + else if ((eImageAlign == ImageAlign::TopLeft) || + (eImageAlign == ImageAlign::Top) || + (eImageAlign == ImageAlign::TopRight) || + (eImageAlign == ImageAlign::BottomLeft) || + (eImageAlign == ImageAlign::Bottom) || + (eImageAlign == ImageAlign::BottomRight)) + { + aRect.AdjustBottom( -sal_Int32(aImageSize.Height() + nImageSep) ); + } + + aRect = GetControlTextRect(*pDev, aRect, aText, nTextStyle, &aDeviceTextSize); + aTextSize = aRect.GetSize(); + + aTSSize.AdjustWidth(aTextSize.Width() ); + + if (aTSSize.Height() < aTextSize.Height()) + aTSSize.setHeight( aTextSize.Height() ); + + if (bAddImageSep && bDrawImage) + { + tools::Long nDiff = (aImageSize.Height() - aTextSize.Height()) / 3; + if (nDiff > 0) + nImageSep += nDiff; + } + } + + Size aMax; + aMax.setWidth( std::max(aTSSize.Width(), aImageSize.Width()) ); + aMax.setHeight( std::max(aTSSize.Height(), aImageSize.Height()) ); + + // Now calculate the output area for the image and the text according to the image align flags + + if ((eImageAlign == ImageAlign::Left) || + (eImageAlign == ImageAlign::Right)) + { + aImagePos.setY( rPos.Y() + (aMax.Height() - aImageSize.Height()) / 2 ); + aTextPos.setY( rPos.Y() + (aMax.Height() - aTSSize.Height()) / 2 ); + } + else if ((eImageAlign == ImageAlign::LeftBottom) || + (eImageAlign == ImageAlign::RightBottom)) + { + aImagePos.setY( rPos.Y() + aMax.Height() - aImageSize.Height() ); + aTextPos.setY( rPos.Y() + aMax.Height() - aTSSize.Height() ); + } + else if ((eImageAlign == ImageAlign::Top) || + (eImageAlign == ImageAlign::Bottom)) + { + aImagePos.setX( rPos.X() + (aMax.Width() - aImageSize.Width()) / 2 ); + aTextPos.setX( rPos.X() + (aMax.Width() - aTSSize.Width()) / 2 ); + } + else if ((eImageAlign == ImageAlign::TopRight) || + (eImageAlign == ImageAlign::BottomRight)) + { + aImagePos.setX( rPos.X() + aMax.Width() - aImageSize.Width() ); + aTextPos.setX( rPos.X() + aMax.Width() - aTSSize.Width() ); + } + + if ((eImageAlign == ImageAlign::LeftTop) || + (eImageAlign == ImageAlign::Left) || + (eImageAlign == ImageAlign::LeftBottom)) + { + aTextPos.setX( rPos.X() + aImageSize.Width() + nImageSep ); + } + else if ((eImageAlign == ImageAlign::RightTop) || + (eImageAlign == ImageAlign::Right) || + (eImageAlign == ImageAlign::RightBottom)) + { + aImagePos.setX( rPos.X() + aTSSize.Width() + nImageSep ); + } + else if ((eImageAlign == ImageAlign::TopLeft) || + (eImageAlign == ImageAlign::Top) || + (eImageAlign == ImageAlign::TopRight)) + { + aTextPos.setY( rPos.Y() + aImageSize.Height() + nImageSep ); + } + else if ((eImageAlign == ImageAlign::BottomLeft) || + (eImageAlign == ImageAlign::Bottom) || + (eImageAlign == ImageAlign::BottomRight)) + { + aImagePos.setY( rPos.Y() + aTSSize.Height() + nImageSep ); + } + else if (eImageAlign == ImageAlign::Center) + { + aImagePos.setX( rPos.X() + (aMax.Width() - aImageSize.Width()) / 2 ); + aImagePos.setY( rPos.Y() + (aMax.Height() - aImageSize.Height()) / 2 ); + aTextPos.setX( rPos.X() + (aMax.Width() - aTSSize.Width()) / 2 ); + aTextPos.setY( rPos.Y() + (aMax.Height() - aTSSize.Height()) / 2 ); + } + aUnion = tools::Rectangle(aImagePos, aImageSize); + aUnion.Union(tools::Rectangle(aTextPos, aTSSize)); + } + + // Now place the combination of text and image in the output area of the button + // according to the window style (WinBits) + tools::Long nXOffset = 0; + tools::Long nYOffset = 0; + + if (nWinStyle & WB_CENTER) + { + nXOffset = (rSize.Width() - aUnion.GetWidth()) / 2; + } + else if (nWinStyle & WB_RIGHT) + { + nXOffset = rSize.Width() - aUnion.GetWidth(); + } + + if (nWinStyle & WB_VCENTER) + { + nYOffset = (rSize.Height() - aUnion.GetHeight()) / 2; + } + else if (nWinStyle & WB_BOTTOM) + { + nYOffset = rSize.Height() - aUnion.GetHeight(); + } + + // the top left corner should always be visible, so we don't allow negative offsets + if (nXOffset < 0) nXOffset = 0; + if (nYOffset < 0) nYOffset = 0; + + aImagePos.AdjustX(nXOffset ); + aImagePos.AdjustY(nYOffset ); + aTextPos.AdjustX(nXOffset ); + aTextPos.AdjustY(nYOffset ); + + // set rPos and rSize to the union + rSize = aUnion.GetSize(); + rPos.AdjustX(nXOffset ); + rPos.AdjustY(nYOffset ); + + if (bHasSymbol) + { + if (mpButtonData->meSymbolAlign == SymbolAlign::RIGHT) + { + Point aRightPos(aTextPos.X() + aTextSize.Width() + aSymbolSize.Width() / 2, aTextPos.Y()); + *pSymbolRect = tools::Rectangle(aRightPos, aSymbolSize); + } + else + { + *pSymbolRect = tools::Rectangle(aTextPos, aSymbolSize); + aTextPos.AdjustX(3 * nSymbolHeight / 2 ); + } + if (mpButtonData->mbSmallSymbol) + { + nYOffset = (aUnion.GetHeight() - aSymbolSize.Height()) / 2; + pSymbolRect->SetPosY(aTextPos.Y() + nYOffset); + } + } + + DrawImageFlags nStyle = DrawImageFlags::NONE; + + if (!IsEnabled()) + { + nStyle |= DrawImageFlags::Disable; + } + + if (IsZoom()) + pDev->DrawImage(aImagePos, aImageSize, *pImage, nStyle); + else + pDev->DrawImage(aImagePos, *pImage, nStyle); + + if (bDrawText) + { + const tools::Rectangle aTOutRect(aTextPos, aTextSize); + ImplSetFocusRect(aTOutRect); + DrawControlText(*pDev, aTOutRect, aText, nTextStyle, nullptr, nullptr, &aDeviceTextSize); + } + else + { + ImplSetFocusRect(tools::Rectangle(aImagePos, aImageSize)); + } +} + +void Button::ImplSetFocusRect(const tools::Rectangle &rFocusRect) +{ + tools::Rectangle aFocusRect = rFocusRect; + tools::Rectangle aOutputRect(Point(), GetOutputSizePixel()); + + if (!aFocusRect.IsEmpty()) + { + aFocusRect.AdjustLeft( -1 ); + aFocusRect.AdjustTop( -1 ); + aFocusRect.AdjustRight( 1 ); + aFocusRect.AdjustBottom( 1 ); + } + + if (aFocusRect.Left() < aOutputRect.Left()) + aFocusRect.SetLeft( aOutputRect.Left() ); + if (aFocusRect.Top() < aOutputRect.Top()) + aFocusRect.SetTop( aOutputRect.Top() ); + if (aFocusRect.Right() > aOutputRect.Right()) + aFocusRect.SetRight( aOutputRect.Right() ); + if (aFocusRect.Bottom() > aOutputRect.Bottom()) + aFocusRect.SetBottom( aOutputRect.Bottom() ); + + mpButtonData->maFocusRect = aFocusRect; +} + +const tools::Rectangle& Button::ImplGetFocusRect() const +{ + return mpButtonData->maFocusRect; +} + +DrawButtonFlags& Button::GetButtonState() +{ + return mpButtonData->mnButtonState; +} + +DrawButtonFlags Button::GetButtonState() const +{ + return mpButtonData->mnButtonState; +} + +void Button::ImplSetSymbolAlign( SymbolAlign eAlign ) +{ + if ( mpButtonData->meSymbolAlign != eAlign ) + { + mpButtonData->meSymbolAlign = eAlign; + StateChanged( StateChangedType::Data ); + } +} + +void Button::SetSmallSymbol() +{ + mpButtonData->mbSmallSymbol = true; +} + +bool Button::IsSmallSymbol () const +{ + return mpButtonData->mbSmallSymbol; +} + +bool Button::set_property(const OString &rKey, const OUString &rValue) +{ + if (rKey == "image-position") + { + ImageAlign eAlign = ImageAlign::Left; + if (rValue == "left") + eAlign = ImageAlign::Left; + else if (rValue == "right") + eAlign = ImageAlign::Right; + else if (rValue == "top") + eAlign = ImageAlign::Top; + else if (rValue == "bottom") + eAlign = ImageAlign::Bottom; + SetImageAlign(eAlign); + } + else if (rKey == "focus-on-click") + { + WinBits nBits = GetStyle(); + nBits &= ~WB_NOPOINTERFOCUS; + if (!toBool(rValue)) + nBits |= WB_NOPOINTERFOCUS; + SetStyle(nBits); + } + else + return Control::set_property(rKey, rValue); + return true; +} + +void Button::statusChanged(const css::frame::FeatureStateEvent& rEvent) +{ + Enable(rEvent.IsEnabled); +} + +FactoryFunction Button::GetUITestFactory() const +{ + return ButtonUIObject::create; +} + +namespace +{ + +const char* symbolTypeName(SymbolType eSymbolType) +{ + switch (eSymbolType) + { + case SymbolType::DONTKNOW: return "DONTKNOW"; + case SymbolType::IMAGE: return "IMAGE"; + case SymbolType::ARROW_UP: return "ARROW_UP"; + case SymbolType::ARROW_DOWN: return "ARROW_DOWN"; + case SymbolType::ARROW_LEFT: return "ARROW_LEFT"; + case SymbolType::ARROW_RIGHT: return "ARROW_RIGHT"; + case SymbolType::SPIN_UP: return "SPIN_UP"; + case SymbolType::SPIN_DOWN: return "SPIN_DOWN"; + case SymbolType::SPIN_LEFT: return "SPIN_LEFT"; + case SymbolType::SPIN_RIGHT: return "SPIN_RIGHT"; + case SymbolType::FIRST: return "FIRST"; + case SymbolType::LAST: return "LAST"; + case SymbolType::PREV: return "PREV"; + case SymbolType::NEXT: return "NEXT"; + case SymbolType::PAGEUP: return "PAGEUP"; + case SymbolType::PAGEDOWN: return "PAGEDOWN"; + case SymbolType::PLAY: return "PLAY"; + case SymbolType::STOP: return "STOP"; + case SymbolType::CLOSE: return "CLOSE"; + case SymbolType::CHECKMARK: return "CHECKMARK"; + case SymbolType::RADIOCHECKMARK: return "RADIOCHECKMARK"; + case SymbolType::FLOAT: return "FLOAT"; + case SymbolType::DOCK: return "DOCK"; + case SymbolType::HIDE: return "HIDE"; + case SymbolType::HELP: return "HELP"; + case SymbolType::PLUS: return "PLUS"; + } + + return "UNKNOWN"; +} + +} + +void Button::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + Control::DumpAsPropertyTree(rJsonWriter); + rJsonWriter.put("text", GetText()); + if (HasImage()) + { + SvMemoryStream aOStm(6535, 6535); + if(GraphicConverter::Export(aOStm, GetModeImage().GetBitmapEx(), ConvertDataFormat::PNG) == ERRCODE_NONE) + { + css::uno::Sequence<sal_Int8> aSeq( static_cast<sal_Int8 const *>(aOStm.GetData()), aOStm.Tell()); + OUStringBuffer aBuffer("data:image/png;base64,"); + ::comphelper::Base64::encode(aBuffer, aSeq); + rJsonWriter.put("image", aBuffer.makeStringAndClear()); + } + } + + if (GetStyle() & WB_DEFBUTTON) + rJsonWriter.put("has_default", true); +} + +void PushButton::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + Button::DumpAsPropertyTree(rJsonWriter); + if (GetSymbol() != SymbolType::DONTKNOW) + rJsonWriter.put("symbol", symbolTypeName(GetSymbol())); +} + +IMPL_STATIC_LINK( Button, dispatchCommandHandler, Button*, pButton, void ) +{ + if (pButton == nullptr) + return; + + comphelper::dispatchCommand(pButton->maCommand, uno::Sequence<beans::PropertyValue>()); +} + +void PushButton::ImplInitPushButtonData() +{ + mpWindowImpl->mbPushButton = true; + + meSymbol = SymbolType::DONTKNOW; + meState = TRISTATE_FALSE; + mnDDStyle = PushButtonDropdownStyle::NONE; + mbIsActive = false; + mbPressed = false; + mbIsAction = false; +} + +namespace +{ + vcl::Window* getPreviousSibling(vcl::Window const *pParent) + { + return pParent ? pParent->GetWindow(GetWindowType::LastChild) : nullptr; + } +} + +void PushButton::ImplInit( vcl::Window* pParent, WinBits nStyle ) +{ + nStyle = ImplInitStyle(getPreviousSibling(pParent), nStyle); + Button::ImplInit( pParent, nStyle, nullptr ); + + if ( nStyle & WB_NOLIGHTBORDER ) + GetButtonState() |= DrawButtonFlags::NoLightBorder; + + ImplInitSettings( true ); +} + +WinBits PushButton::ImplInitStyle( const vcl::Window* pPrevWindow, WinBits nStyle ) +{ + if ( !(nStyle & WB_NOTABSTOP) ) + nStyle |= WB_TABSTOP; + + // if no alignment is given, default to "vertically centered". This is because since + // #i26046#, we respect the vertical alignment flags (previously we didn't completely), + // but we of course want to look as before when no vertical alignment is specified + if ( ( nStyle & ( WB_TOP | WB_VCENTER | WB_BOTTOM ) ) == 0 ) + nStyle |= WB_VCENTER; + + if ( !(nStyle & WB_NOGROUP) && + (!pPrevWindow || + ((pPrevWindow->GetType() != WindowType::PUSHBUTTON ) && + (pPrevWindow->GetType() != WindowType::OKBUTTON ) && + (pPrevWindow->GetType() != WindowType::CANCELBUTTON) && + (pPrevWindow->GetType() != WindowType::HELPBUTTON )) ) ) + nStyle |= WB_GROUP; + return nStyle; +} + +const vcl::Font& PushButton::GetCanonicalFont( const StyleSettings& _rStyle ) const +{ + return _rStyle.GetPushButtonFont(); +} + +const Color& PushButton::GetCanonicalTextColor( const StyleSettings& _rStyle ) const +{ + return _rStyle.GetButtonTextColor(); +} + +void PushButton::ImplInitSettings( bool bBackground ) +{ + Button::ImplInitSettings(); + + if ( !bBackground ) + return; + + SetBackground(); + // #i38498#: do not check for GetParent()->IsChildTransparentModeEnabled() + // otherwise the formcontrol button will be overdrawn due to ParentClipMode::NoClip + // for radio and checkbox this is ok as they should appear transparent in documents + if ( IsNativeControlSupported( ControlType::Pushbutton, ControlPart::Entire ) || + (GetStyle() & WB_FLATBUTTON) != 0 ) + { + EnableChildTransparentMode(); + SetParentClipMode( ParentClipMode::NoClip ); + SetPaintTransparent( true ); + + if ((GetStyle() & WB_FLATBUTTON) == 0) + mpWindowImpl->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects; + else + mpWindowImpl->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRectsForFlatButtons; + } + else + { + EnableChildTransparentMode( false ); + SetParentClipMode(); + SetPaintTransparent( false ); + } +} + +void PushButton::ImplDrawPushButtonFrame(vcl::RenderContext& rRenderContext, + tools::Rectangle& rRect, DrawButtonFlags nStyle) +{ + if (!(GetStyle() & (WB_RECTSTYLE | WB_SMALLSTYLE))) + { + StyleSettings aStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + if (IsControlBackground()) + aStyleSettings.Set3DColors(GetControlBackground()); + } + + DecorationView aDecoView(&rRenderContext); + if (IsControlBackground()) + { + AllSettings aSettings = rRenderContext.GetSettings(); + AllSettings aOldSettings = aSettings; + StyleSettings aStyleSettings = aSettings.GetStyleSettings(); + if (nStyle & DrawButtonFlags::Highlight) + { + // with the custom background, native highlight do nothing, so code below mimic + // native highlight by changing luminance + Color controlBackgroundColorHighlighted = GetControlBackground(); + sal_uInt8 colorLuminance = controlBackgroundColorHighlighted.GetLuminance(); + if (colorLuminance < 205) + controlBackgroundColorHighlighted.IncreaseLuminance(50); + else + controlBackgroundColorHighlighted.DecreaseLuminance(50); + aStyleSettings.Set3DColors(controlBackgroundColorHighlighted); + } + else + aStyleSettings.Set3DColors(GetControlBackground()); + aSettings.SetStyleSettings(aStyleSettings); + + // Call OutputDevice::SetSettings() explicitly, as rRenderContext may + // be a vcl::Window in fact, and vcl::Window::SetSettings() will call + // Invalidate(), which is a problem, since we're in Paint(). + rRenderContext.OutputDevice::SetSettings(aSettings); + rRect = aDecoView.DrawButton(rRect, nStyle); + rRenderContext.OutputDevice::SetSettings(aOldSettings); + } + else + rRect = aDecoView.DrawButton(rRect, nStyle); +} + +bool PushButton::ImplHitTestPushButton( vcl::Window const * pDev, + const Point& rPos ) +{ + tools::Rectangle aTestRect( Point(), pDev->GetOutputSizePixel() ); + + return aTestRect.Contains( rPos ); +} + +DrawTextFlags PushButton::ImplGetTextStyle( SystemTextColorFlags nSystemTextColorFlags ) const +{ + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + + DrawTextFlags nTextStyle = DrawTextFlags::Mnemonic | DrawTextFlags::MultiLine | DrawTextFlags::EndEllipsis; + + if ( ( rStyleSettings.GetOptions() & StyleSettingsOptions::Mono ) || + ( nSystemTextColorFlags & SystemTextColorFlags::Mono ) ) + nTextStyle |= DrawTextFlags::Mono; + + if ( GetStyle() & WB_WORDBREAK ) + nTextStyle |= DrawTextFlags::WordBreak; + if ( GetStyle() & WB_NOLABEL ) + nTextStyle &= ~DrawTextFlags::Mnemonic; + + if ( GetStyle() & WB_LEFT ) + nTextStyle |= DrawTextFlags::Left; + else if ( GetStyle() & WB_RIGHT ) + nTextStyle |= DrawTextFlags::Right; + else + nTextStyle |= DrawTextFlags::Center; + + if ( GetStyle() & WB_TOP ) + nTextStyle |= DrawTextFlags::Top; + else if ( GetStyle() & WB_BOTTOM ) + nTextStyle |= DrawTextFlags::Bottom; + else + nTextStyle |= DrawTextFlags::VCenter; + + if ( !IsEnabled() ) + nTextStyle |= DrawTextFlags::Disable; + + return nTextStyle; +} + +void PushButton::ImplDrawPushButtonContent(OutputDevice *pDev, SystemTextColorFlags nSystemTextColorFlags, + const tools::Rectangle &rRect, bool bMenuBtnSep, + DrawButtonFlags nButtonFlags) +{ + const StyleSettings &rStyleSettings = GetSettings().GetStyleSettings(); + tools::Rectangle aInRect = rRect; + Color aColor; + DrawTextFlags nTextStyle = ImplGetTextStyle(nSystemTextColorFlags); + DrawSymbolFlags nStyle; + + if (aInRect.Right() < aInRect.Left() || aInRect.Bottom() < aInRect.Top()) + return; + + pDev->Push(vcl::PushFlags::CLIPREGION); + pDev->IntersectClipRegion(aInRect); + + if (nSystemTextColorFlags & SystemTextColorFlags::Mono) + aColor = COL_BLACK; + + else if (IsControlForeground()) + aColor = GetControlForeground(); + + // Button types with possibly different text coloring are flat buttons and regular buttons. Regular buttons may be action + // buttons and may have an additional default status. Moreover all buttons may have an additional pressed and rollover + // (highlight) status. Pressed buttons are always in rollover status. + + else if (GetStyle() & WB_FLATBUTTON) + if (nButtonFlags & DrawButtonFlags::Pressed) + aColor = rStyleSettings.GetFlatButtonPressedRolloverTextColor(); + else if (nButtonFlags & DrawButtonFlags::Highlight) + aColor = rStyleSettings.GetFlatButtonRolloverTextColor(); + else + aColor = rStyleSettings.GetFlatButtonTextColor(); + else + if (isAction() && (nButtonFlags & DrawButtonFlags::Default)) + if (nButtonFlags & DrawButtonFlags::Pressed) + aColor = rStyleSettings.GetDefaultActionButtonPressedRolloverTextColor(); + else if (nButtonFlags & DrawButtonFlags::Highlight) + aColor = rStyleSettings.GetDefaultActionButtonRolloverTextColor(); + else + aColor = rStyleSettings.GetDefaultActionButtonTextColor(); + else if (isAction()) + if (nButtonFlags & DrawButtonFlags::Pressed) + aColor = rStyleSettings.GetActionButtonPressedRolloverTextColor(); + else if (nButtonFlags & DrawButtonFlags::Highlight) + aColor = rStyleSettings.GetActionButtonRolloverTextColor(); + else + aColor = rStyleSettings.GetActionButtonTextColor(); + else if (nButtonFlags & DrawButtonFlags::Default) + if (nButtonFlags & DrawButtonFlags::Pressed) + aColor = rStyleSettings.GetDefaultButtonPressedRolloverTextColor(); + else if (nButtonFlags & DrawButtonFlags::Highlight) + aColor = rStyleSettings.GetDefaultButtonRolloverTextColor(); + else + aColor = rStyleSettings.GetDefaultButtonTextColor(); + else + if (nButtonFlags & DrawButtonFlags::Pressed) + aColor = rStyleSettings.GetButtonPressedRolloverTextColor(); + else if (nButtonFlags & DrawButtonFlags::Highlight) + aColor = rStyleSettings.GetButtonRolloverTextColor(); + else + aColor = rStyleSettings.GetButtonTextColor(); + + pDev->SetTextColor(aColor); + + if ( IsEnabled() ) + nStyle = DrawSymbolFlags::NONE; + else + nStyle = DrawSymbolFlags::Disable; + + Size aSize = rRect.GetSize(); + Point aPos = rRect.TopLeft(); + + sal_Int32 nImageSep = 1 + (pDev->GetTextHeight()-10)/2; + if( nImageSep < 1 ) + nImageSep = 1; + if ( mnDDStyle == PushButtonDropdownStyle::MenuButton || + mnDDStyle == PushButtonDropdownStyle::SplitMenuButton ) + { + tools::Long nSeparatorX = 0; + tools::Rectangle aSymbolRect = aInRect; + + // calculate symbol size + tools::Long nSymbolSize = pDev->GetTextHeight() / 2 + 1; + if (nSymbolSize > aSize.Width() / 2) + nSymbolSize = aSize.Width() / 2; + + nSeparatorX = aInRect.Right() - 2*nSymbolSize; + + // tdf#141761 Minimum width should be (1) Pixel, see comment + // with same task number above for more info + const tools::Long nWidthAdjust(2*nSymbolSize); + aSize.setWidth(std::max(static_cast<tools::Long>(1), aSize.getWidth() - nWidthAdjust)); + + // center symbol rectangle in the separated area + aSymbolRect.AdjustRight( -(nSymbolSize/2) ); + aSymbolRect.SetLeft( aSymbolRect.Right() - nSymbolSize ); + + ImplDrawAlignedImage( pDev, aPos, aSize, nImageSep, + nTextStyle, nullptr, true ); + + tools::Long nDistance = (aSymbolRect.GetHeight() > 10) ? 2 : 1; + DecorationView aDecoView( pDev ); + if( bMenuBtnSep && nSeparatorX > 0 ) + { + Point aStartPt( nSeparatorX, aSymbolRect.Top()+nDistance ); + Point aEndPt( nSeparatorX, aSymbolRect.Bottom()-nDistance ); + aDecoView.DrawSeparator( aStartPt, aEndPt ); + } + ImplSetSeparatorX( nSeparatorX ); + + aDecoView.DrawSymbol( aSymbolRect, SymbolType::SPIN_DOWN, aColor, nStyle ); + + } + else + { + tools::Rectangle aSymbolRect; + ImplDrawAlignedImage( pDev, aPos, aSize, nImageSep, + nTextStyle, IsSymbol() ? &aSymbolRect : nullptr, true ); + + if ( IsSymbol() ) + { + DecorationView aDecoView( pDev ); + aDecoView.DrawSymbol( aSymbolRect, meSymbol, aColor, nStyle ); + } + } + + pDev->Pop(); // restore clipregion +} + +void PushButton::ImplDrawPushButton(vcl::RenderContext& rRenderContext) +{ + HideFocus(); + + DrawButtonFlags nButtonStyle = GetButtonState(); + Size aOutSz(GetOutputSizePixel()); + tools::Rectangle aRect(Point(), aOutSz); + tools::Rectangle aInRect = aRect; + bool bNativeOK = false; + + // adjust style if button should be rendered 'pressed' + if (mbPressed || mbIsActive) + nButtonStyle |= DrawButtonFlags::Pressed; + + // TODO: move this to Window class or make it a member !!! + ControlType aCtrlType = ControlType::Generic; + switch(GetParent()->GetType()) + { + case WindowType::LISTBOX: + case WindowType::MULTILISTBOX: + case WindowType::TREELISTBOX: + aCtrlType = ControlType::Listbox; + break; + + case WindowType::COMBOBOX: + case WindowType::PATTERNBOX: + case WindowType::NUMERICBOX: + case WindowType::METRICBOX: + case WindowType::CURRENCYBOX: + case WindowType::DATEBOX: + case WindowType::TIMEBOX: + case WindowType::LONGCURRENCYBOX: + aCtrlType = ControlType::Combobox; + break; + default: + break; + } + + bool bDropDown = (IsSymbol() && (GetSymbol() == SymbolType::SPIN_DOWN) && GetText().isEmpty()); + + if( bDropDown && (aCtrlType == ControlType::Combobox || aCtrlType == ControlType::Listbox)) + { + if (GetParent()->IsNativeControlSupported(aCtrlType, ControlPart::Entire)) + { + // skip painting if the button was already drawn by the theme + if (aCtrlType == ControlType::Combobox) + { + Edit* pEdit = static_cast<Edit*>(GetParent()); + if (pEdit->ImplUseNativeBorder(rRenderContext, pEdit->GetStyle())) + bNativeOK = true; + } + else if (GetParent()->IsNativeControlSupported(aCtrlType, ControlPart::HasBackgroundTexture)) + { + bNativeOK = true; + } + + if (!bNativeOK && GetParent()->IsNativeControlSupported(aCtrlType, ControlPart::ButtonDown)) + { + // let the theme draw it, note we then need support + // for ControlType::Listbox/ControlPart::ButtonDown and ControlType::Combobox/ControlPart::ButtonDown + + ImplControlValue aControlValue; + ControlState nState = ControlState::NONE; + + if (mbPressed || mbIsActive) + nState |= ControlState::PRESSED; + if (GetButtonState() & DrawButtonFlags::Pressed) + nState |= ControlState::PRESSED; + if (HasFocus()) + nState |= ControlState::FOCUSED; + if (GetButtonState() & DrawButtonFlags::Default) + nState |= ControlState::DEFAULT; + if (Window::IsEnabled()) + nState |= ControlState::ENABLED; + + if (IsMouseOver() && aInRect.Contains(GetPointerPosPixel())) + nState |= ControlState::ROLLOVER; + + if ( IsMouseOver() && aInRect.Contains(GetPointerPosPixel()) && mbIsActive) + { + nState |= ControlState::ROLLOVER; + nButtonStyle &= ~DrawButtonFlags::Pressed; + } + + bNativeOK = rRenderContext.DrawNativeControl(aCtrlType, ControlPart::ButtonDown, aInRect, nState, + aControlValue, OUString()); + } + } + } + + if (bNativeOK) + return; + + bool bRollOver = (IsMouseOver() && aInRect.Contains(GetPointerPosPixel())); + if (bRollOver) + nButtonStyle |= DrawButtonFlags::Highlight; + bool bDrawMenuSep = mnDDStyle == PushButtonDropdownStyle::SplitMenuButton; + if (GetStyle() & WB_FLATBUTTON) + { + if (!bRollOver && !HasFocus()) + bDrawMenuSep = false; + } + // tdf#123175 if there is a custom control bg set, draw the button without outsourcing to the NWF + bNativeOK = !IsControlBackground() && rRenderContext.IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Entire); + if (bNativeOK) + { + PushButtonValue aControlValue; + aControlValue.mbIsAction = isAction(); + + tools::Rectangle aCtrlRegion(aInRect); + ControlState nState = ControlState::NONE; + + if (mbPressed || IsChecked() || mbIsActive) + { + nState |= ControlState::PRESSED; + nButtonStyle |= DrawButtonFlags::Pressed; + } + if (GetButtonState() & DrawButtonFlags::Pressed) + nState |= ControlState::PRESSED; + if (HasFocus()) + nState |= ControlState::FOCUSED; + if (GetButtonState() & DrawButtonFlags::Default) + nState |= ControlState::DEFAULT; + if (Window::IsEnabled()) + nState |= ControlState::ENABLED; + + if (bRollOver || mbIsActive) + { + nButtonStyle |= DrawButtonFlags::Highlight; + nState |= ControlState::ROLLOVER; + } + + if (mbIsActive && bRollOver) + { + nState &= ~ControlState::PRESSED; + nButtonStyle &= ~DrawButtonFlags::Pressed; + } + + if (GetStyle() & WB_FLATBUTTON) + aControlValue.m_bFlatButton = true; + if (GetStyle() & WB_BEVELBUTTON) + aControlValue.mbBevelButton = true; + + // draw frame into invisible window to have aInRect modified correctly + // but do not shift the inner rect for pressed buttons (ie remove DrawButtonFlags::Pressed) + // this assumes the theme has enough visual cues to signalize the button was pressed + //Window aWin( this ); + //ImplDrawPushButtonFrame( &aWin, aInRect, nButtonStyle & ~DrawButtonFlags::Pressed ); + + // looks better this way as symbols were displaced slightly using the above approach + aInRect.AdjustTop(4 ); + aInRect.AdjustBottom( -4 ); + aInRect.AdjustLeft(4 ); + aInRect.AdjustRight( -4 ); + + // prepare single line hint (needed on mac to decide between normal push button and + // rectangular bevel button look) + Size aFontSize(Application::GetSettings().GetStyleSettings().GetPushButtonFont().GetFontSize()); + aFontSize = rRenderContext.LogicToPixel(aFontSize, MapMode(MapUnit::MapPoint)); + Size aInRectSize(rRenderContext.LogicToPixel(Size(aInRect.GetWidth(), aInRect.GetHeight()))); + aControlValue.mbSingleLine = (aInRectSize.Height() < 2 * aFontSize.Height()); + + if ((nState & ControlState::ROLLOVER) || !(GetStyle() & WB_FLATBUTTON) + || (HasFocus() && mpWindowImpl->mbUseNativeFocus + && !IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Focus))) + { + bNativeOK = rRenderContext.DrawNativeControl(ControlType::Pushbutton, ControlPart::Entire, aCtrlRegion, nState, + aControlValue, OUString() /*PushButton::GetText()*/); + } + else + { + bNativeOK = true; + } + + // draw content using the same aInRect as non-native VCL would do + ImplDrawPushButtonContent(&rRenderContext, SystemTextColorFlags::NONE, + aInRect, bDrawMenuSep, nButtonStyle); + + if (HasFocus()) + ShowFocus(ImplGetFocusRect()); + } + + if (bNativeOK) + return; + + // draw PushButtonFrame, aInRect has content size afterwards + if (GetStyle() & WB_FLATBUTTON) + { + tools::Rectangle aTempRect(aInRect); + if (bRollOver) + ImplDrawPushButtonFrame(rRenderContext, aTempRect, nButtonStyle); + aInRect.AdjustLeft(2 ); + aInRect.AdjustTop(2 ); + aInRect.AdjustRight( -2 ); + aInRect.AdjustBottom( -2 ); + } + else + { + ImplDrawPushButtonFrame(rRenderContext, aInRect, nButtonStyle); + } + + // draw content + ImplDrawPushButtonContent(&rRenderContext, SystemTextColorFlags::NONE, aInRect, bDrawMenuSep, nButtonStyle); + + if (HasFocus()) + { + ShowFocus(ImplGetFocusRect()); + } +} + +void PushButton::ImplSetDefButton( bool bSet ) +{ + Size aSize( GetSizePixel() ); + Point aPos( GetPosPixel() ); + int dLeft(0), dRight(0), dTop(0), dBottom(0); + bool bSetPos = false; + + if ( IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Entire) ) + { + tools::Rectangle aBound, aCont; + tools::Rectangle aCtrlRegion( 0, 0, 80, 20 ); // use a constant size to avoid accumulating + // will not work if the theme has dynamic adornment sizes + ImplControlValue aControlValue; + + // get native size of a 'default' button + // and adjust the VCL button if more space for adornment is required + if( GetNativeControlRegion( ControlType::Pushbutton, ControlPart::Entire, aCtrlRegion, + ControlState::DEFAULT|ControlState::ENABLED, + aControlValue, + aBound, aCont ) ) + { + dLeft = aCont.Left() - aBound.Left(); + dTop = aCont.Top() - aBound.Top(); + dRight = aBound.Right() - aCont.Right(); + dBottom = aBound.Bottom() - aCont.Bottom(); + bSetPos = dLeft || dTop || dRight || dBottom; + } + } + + if ( bSet ) + { + if( !(GetButtonState() & DrawButtonFlags::Default) && bSetPos ) + { + // adjust pos/size when toggling from non-default to default + aPos.Move(-dLeft, -dTop); + aSize.AdjustWidth(dLeft + dRight ); + aSize.AdjustHeight(dTop + dBottom ); + } + GetButtonState() |= DrawButtonFlags::Default; + } + else + { + if( (GetButtonState() & DrawButtonFlags::Default) && bSetPos ) + { + // adjust pos/size when toggling from default to non-default + aPos.Move(dLeft, dTop); + aSize.AdjustWidth( -(dLeft + dRight) ); + aSize.AdjustHeight( -(dTop + dBottom) ); + } + GetButtonState() &= ~DrawButtonFlags::Default; + } + if( bSetPos ) + setPosSizePixel( aPos.X(), aPos.Y(), aSize.Width(), aSize.Height() ); + + Invalidate(); +} + +bool PushButton::ImplIsDefButton() const +{ + return bool(GetButtonState() & DrawButtonFlags::Default); +} + +PushButton::PushButton( WindowType nType ) : + Button( nType ) +{ + ImplInitPushButtonData(); +} + +PushButton::PushButton( vcl::Window* pParent, WinBits nStyle ) : + Button( WindowType::PUSHBUTTON ) +{ + ImplInitPushButtonData(); + ImplInit( pParent, nStyle ); +} + +void PushButton::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( !(rMEvt.IsLeft() && + ImplHitTestPushButton( this, rMEvt.GetPosPixel() )) ) + return; + + StartTrackingFlags nTrackFlags = StartTrackingFlags::NONE; + + if ( ( GetStyle() & WB_REPEAT ) && + ! ( GetStyle() & WB_TOGGLE ) ) + nTrackFlags |= StartTrackingFlags::ButtonRepeat; + + GetButtonState() |= DrawButtonFlags::Pressed; + Invalidate(); + StartTracking( nTrackFlags ); + + if ( nTrackFlags & StartTrackingFlags::ButtonRepeat ) + Click(); +} + +void PushButton::Tracking( const TrackingEvent& rTEvt ) +{ + if ( rTEvt.IsTrackingEnded() ) + { + if ( GetButtonState() & DrawButtonFlags::Pressed ) + { + if ( !(GetStyle() & WB_NOPOINTERFOCUS) && !rTEvt.IsTrackingCanceled() ) + GrabFocus(); + + if ( GetStyle() & WB_TOGGLE ) + { + // Don't toggle, when aborted + if ( !rTEvt.IsTrackingCanceled() ) + { + if ( IsChecked() ) + { + Check( false ); + GetButtonState() &= ~DrawButtonFlags::Pressed; + } + else + Check(); + } + } + else + GetButtonState() &= ~DrawButtonFlags::Pressed; + + Invalidate(); + + // do not call Click handler if aborted + if ( !rTEvt.IsTrackingCanceled() ) + { + if ( ! ( GetStyle() & WB_REPEAT ) || ( GetStyle() & WB_TOGGLE ) ) + Click(); + } + } + } + else + { + if ( ImplHitTestPushButton( this, rTEvt.GetMouseEvent().GetPosPixel() ) ) + { + if ( GetButtonState() & DrawButtonFlags::Pressed ) + { + if ( rTEvt.IsTrackingRepeat() && (GetStyle() & WB_REPEAT) && + ! ( GetStyle() & WB_TOGGLE ) ) + Click(); + } + else + { + GetButtonState() |= DrawButtonFlags::Pressed; + Invalidate(); + } + } + else + { + if ( GetButtonState() & DrawButtonFlags::Pressed ) + { + GetButtonState() &= ~DrawButtonFlags::Pressed; + Invalidate(); + } + } + } +} + +void PushButton::KeyInput( const KeyEvent& rKEvt ) +{ + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + + if ( !aKeyCode.GetModifier() && + ((aKeyCode.GetCode() == KEY_RETURN) || (aKeyCode.GetCode() == KEY_SPACE)) ) + { + if ( !(GetButtonState() & DrawButtonFlags::Pressed) ) + { + GetButtonState() |= DrawButtonFlags::Pressed; + Invalidate(); + } + + if ( ( GetStyle() & WB_REPEAT ) && + ! ( GetStyle() & WB_TOGGLE ) ) + Click(); + } + else if ( (GetButtonState() & DrawButtonFlags::Pressed) && (aKeyCode.GetCode() == KEY_ESCAPE) ) + { + GetButtonState() &= ~DrawButtonFlags::Pressed; + Invalidate(); + } + else + Button::KeyInput( rKEvt ); +} + +void PushButton::KeyUp( const KeyEvent& rKEvt ) +{ + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + + if ( (GetButtonState() & DrawButtonFlags::Pressed) && + ((aKeyCode.GetCode() == KEY_RETURN) || (aKeyCode.GetCode() == KEY_SPACE)) ) + { + if ( GetStyle() & WB_TOGGLE ) + { + if ( IsChecked() ) + { + Check( false ); + GetButtonState() &= ~DrawButtonFlags::Pressed; + } + else + Check(); + + Toggle(); + } + else + GetButtonState() &= ~DrawButtonFlags::Pressed; + + Invalidate(); + + if ( !( GetStyle() & WB_REPEAT ) || ( GetStyle() & WB_TOGGLE ) ) + Click(); + } + else + Button::KeyUp( rKEvt ); +} + +void PushButton::FillLayoutData() const +{ + mxLayoutData.emplace(); + const_cast<PushButton*>(this)->Invalidate(); +} + +void PushButton::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + const Image& rCustomButtonImage = GetCustomButtonImage(); + if (!!rCustomButtonImage) + { + rRenderContext.DrawImage(Point(0, 0), rCustomButtonImage); + return; + } + ImplDrawPushButton(rRenderContext); +} + +void PushButton::Draw( OutputDevice* pDev, const Point& rPos, + SystemTextColorFlags nFlags ) +{ + Point aPos = pDev->LogicToPixel( rPos ); + Size aSize = GetSizePixel(); + tools::Rectangle aRect( aPos, aSize ); + vcl::Font aFont = GetDrawPixelFont( pDev ); + + pDev->Push(); + pDev->SetMapMode(); + pDev->SetFont( aFont ); + + std::optional<StyleSettings> oOrigDevStyleSettings; + + if ( nFlags & SystemTextColorFlags::Mono ) + { + pDev->SetTextColor( COL_BLACK ); + } + else + { + pDev->SetTextColor( GetTextColor() ); + // DecoView uses the FaceColor... + AllSettings aSettings = pDev->GetSettings(); + StyleSettings aStyleSettings = aSettings.GetStyleSettings(); + oOrigDevStyleSettings = aStyleSettings; + if ( IsControlBackground() ) + aStyleSettings.SetFaceColor( GetControlBackground() ); + else + aStyleSettings.SetFaceColor( GetSettings().GetStyleSettings().GetFaceColor() ); + aSettings.SetStyleSettings( aStyleSettings ); + pDev->OutputDevice::SetSettings( aSettings ); + } + pDev->SetTextFillColor(); + + DecorationView aDecoView( pDev ); + DrawButtonFlags nButtonStyle = DrawButtonFlags::NONE; + if ( nFlags & SystemTextColorFlags::Mono ) + nButtonStyle |= DrawButtonFlags::Mono; + if ( IsChecked() ) + nButtonStyle |= DrawButtonFlags::Checked; + aRect = aDecoView.DrawButton( aRect, nButtonStyle ); + + ImplDrawPushButtonContent( pDev, nFlags, aRect, true, nButtonStyle ); + + // restore original settings (which are not affected by Push/Pop) after + // finished drawing + if (oOrigDevStyleSettings) + { + AllSettings aSettings = pDev->GetSettings(); + aSettings.SetStyleSettings(*oOrigDevStyleSettings); + pDev->OutputDevice::SetSettings( aSettings ); + } + + pDev->Pop(); +} + +void PushButton::Resize() +{ + Control::Resize(); + Invalidate(); +} + +void PushButton::GetFocus() +{ + ShowFocus( ImplGetFocusRect() ); + SetInputContext( InputContext( GetFont() ) ); + Button::GetFocus(); +} + +void PushButton::LoseFocus() +{ + EndSelection(); + HideFocus(); + Button::LoseFocus(); +} + +void PushButton::StateChanged( StateChangedType nType ) +{ + Button::StateChanged( nType ); + + if ( (nType == StateChangedType::Enable) || + (nType == StateChangedType::Text) || + (nType == StateChangedType::Data) || + (nType == StateChangedType::State) || + (nType == StateChangedType::UpdateMode) ) + { + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate(); + } + else if ( nType == StateChangedType::Style ) + { + SetStyle( ImplInitStyle( GetWindow( GetWindowType::Prev ), GetStyle() ) ); + + bool bIsDefButton = ( GetStyle() & WB_DEFBUTTON ) != 0; + bool bWasDefButton = ( GetPrevStyle() & WB_DEFBUTTON ) != 0; + if ( bIsDefButton != bWasDefButton ) + ImplSetDefButton( bIsDefButton ); + + if ( IsReallyVisible() && IsUpdateMode() ) + { + if ( (GetPrevStyle() & PUSHBUTTON_VIEW_STYLE) != + (GetStyle() & PUSHBUTTON_VIEW_STYLE) ) + Invalidate(); + } + } + else if ( (nType == StateChangedType::Zoom) || + (nType == StateChangedType::ControlFont) ) + { + ImplInitSettings( false ); + Invalidate(); + } + else if ( nType == StateChangedType::ControlForeground ) + { + ImplInitSettings( false ); + Invalidate(); + } + else if ( nType == StateChangedType::ControlBackground ) + { + ImplInitSettings( true ); + Invalidate(); + } +} + +void PushButton::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Button::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) || + (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) || + ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) ) + { + ImplInitSettings( true ); + Invalidate(); + } +} + +bool PushButton::PreNotify( NotifyEvent& rNEvt ) +{ + if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE ) + { + const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent(); + if( pMouseEvt && (pMouseEvt->IsEnterWindow() || pMouseEvt->IsLeaveWindow()) ) + { + // trigger redraw as mouse over state has changed + + // TODO: move this to Window class or make it a member !!! + ControlType aCtrlType = ControlType::Generic; + switch( GetParent()->GetType() ) + { + case WindowType::LISTBOX: + case WindowType::MULTILISTBOX: + case WindowType::TREELISTBOX: + aCtrlType = ControlType::Listbox; + break; + + case WindowType::COMBOBOX: + case WindowType::PATTERNBOX: + case WindowType::NUMERICBOX: + case WindowType::METRICBOX: + case WindowType::CURRENCYBOX: + case WindowType::DATEBOX: + case WindowType::TIMEBOX: + case WindowType::LONGCURRENCYBOX: + aCtrlType = ControlType::Combobox; + break; + default: + break; + } + + bool bDropDown = ( IsSymbol() && (GetSymbol()==SymbolType::SPIN_DOWN) && GetText().isEmpty() ); + + if( bDropDown && GetParent()->IsNativeControlSupported( aCtrlType, ControlPart::Entire) && + !GetParent()->IsNativeControlSupported( aCtrlType, ControlPart::ButtonDown) ) + { + vcl::Window *pBorder = GetParent()->GetWindow( GetWindowType::Border ); + if(aCtrlType == ControlType::Combobox) + { + // only paint the button part to avoid flickering of the combobox text + tools::Rectangle aClipRect( Point(), GetOutputSizePixel() ); + aClipRect.SetPos(pBorder->ScreenToOutputPixel(OutputToScreenPixel(aClipRect.TopLeft()))); + pBorder->Invalidate( aClipRect ); + } + else + { + pBorder->Invalidate( InvalidateFlags::NoErase ); + } + } + else if( (GetStyle() & WB_FLATBUTTON) || + IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Entire) ) + { + Invalidate(); + } + } + } + + return Button::PreNotify(rNEvt); +} + +void PushButton::Toggle() +{ + ImplCallEventListenersAndHandler( VclEventId::PushbuttonToggle, nullptr ); +} + +void PushButton::SetSymbol( SymbolType eSymbol ) +{ + if ( meSymbol != eSymbol ) + { + meSymbol = eSymbol; + CompatStateChanged( StateChangedType::Data ); + } +} + +void PushButton::SetSymbolAlign( SymbolAlign eAlign ) +{ + ImplSetSymbolAlign( eAlign ); +} + +void PushButton::SetDropDown( PushButtonDropdownStyle nStyle ) +{ + if ( mnDDStyle != nStyle ) + { + mnDDStyle = nStyle; + CompatStateChanged( StateChangedType::Data ); + } +} + +void PushButton::SetState( TriState eState ) +{ + if ( meState == eState ) + return; + + meState = eState; + if ( meState == TRISTATE_FALSE ) + GetButtonState() &= ~DrawButtonFlags(DrawButtonFlags::Checked | DrawButtonFlags::DontKnow); + else if ( meState == TRISTATE_TRUE ) + { + GetButtonState() &= ~DrawButtonFlags::DontKnow; + GetButtonState() |= DrawButtonFlags::Checked; + } + else // TRISTATE_INDET + { + GetButtonState() &= ~DrawButtonFlags::Checked; + GetButtonState() |= DrawButtonFlags::DontKnow; + } + + CompatStateChanged( StateChangedType::State ); + Toggle(); +} + +void PushButton::statusChanged(const css::frame::FeatureStateEvent& rEvent) +{ + Button::statusChanged(rEvent); + if (rEvent.State.has<bool>()) + SetPressed(rEvent.State.get<bool>()); +} + +void PushButton::SetPressed( bool bPressed ) +{ + if ( mbPressed != bPressed ) + { + mbPressed = bPressed; + CompatStateChanged( StateChangedType::Data ); + } +} + +void PushButton::EndSelection() +{ + EndTracking( TrackingEventFlags::Cancel ); + if ( !isDisposed() && + GetButtonState() & DrawButtonFlags::Pressed ) + { + GetButtonState() &= ~DrawButtonFlags::Pressed; + if ( !mbPressed ) + Invalidate(); + } +} + +Size PushButton::CalcMinimumSize() const +{ + Size aSize; + + if ( IsSymbol() ) + { + if ( IsSmallSymbol ()) + aSize = Size( 16, 12 ); + else + aSize = Size( 26, 24 ); + } + else if ( Button::HasImage() ) + aSize = GetModeImage().GetSizePixel(); + if( mnDDStyle == PushButtonDropdownStyle::MenuButton || + mnDDStyle == PushButtonDropdownStyle::SplitMenuButton ) + { + tools::Long nSymbolSize = GetTextHeight() / 2 + 1; + aSize.AdjustWidth(2*nSymbolSize ); + } + if (!PushButton::GetText().isEmpty()) + { + Size textSize = GetTextRect( tools::Rectangle( Point(), Size( 0x7fffffff, 0x7fffffff ) ), + PushButton::GetText(), ImplGetTextStyle( SystemTextColorFlags::NONE ) ).GetSize(); + + tools::Long nTextHeight = textSize.Height() * 1.15; + + ImageAlign eImageAlign = GetImageAlign(); + // tdf#142337 only considering the simple top/bottom/left/right possibilities + if (eImageAlign == ImageAlign::Top || eImageAlign == ImageAlign::Bottom) + { + aSize.AdjustHeight(nTextHeight); + aSize.setWidth(std::max(aSize.Width(), textSize.Width())); + } + else + { + aSize.AdjustWidth(textSize.Width()); + aSize.setHeight(std::max(aSize.Height(), nTextHeight)); + } + } + + // cf. ImplDrawPushButton ... + if( (GetStyle() & WB_SMALLSTYLE) == 0 ) + { + aSize.AdjustWidth(24 ); + aSize.AdjustHeight(12 ); + } + + return CalcWindowSize( aSize ); +} + +Size PushButton::GetOptimalSize() const +{ + return CalcMinimumSize(); +} + +bool PushButton::set_property(const OString &rKey, const OUString &rValue) +{ + if (rKey == "has-default") + { + WinBits nBits = GetStyle(); + nBits &= ~WB_DEFBUTTON; + if (toBool(rValue)) + nBits |= WB_DEFBUTTON; + SetStyle(nBits); + } + else + return Button::set_property(rKey, rValue); + return true; +} + +void PushButton::ShowFocus(const tools::Rectangle& rRect) +{ + if (IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Focus)) + { + PushButtonValue aControlValue; + aControlValue.mbIsAction = isAction(); + tools::Rectangle aInRect(Point(), GetOutputSizePixel()); + GetOutDev()->DrawNativeControl(ControlType::Pushbutton, ControlPart::Focus, aInRect, + ControlState::FOCUSED, aControlValue, OUString()); + } + Button::ShowFocus(rRect); +} + +void OKButton::ImplInit( vcl::Window* pParent, WinBits nStyle ) +{ + set_id("ok"); + PushButton::ImplInit( pParent, nStyle ); + + SetText( GetStandardText( StandardButtonType::OK ) ); +} + +OKButton::OKButton( vcl::Window* pParent, WinBits nStyle ) : + PushButton( WindowType::OKBUTTON ) +{ + ImplInit( pParent, nStyle ); +} + +void OKButton::Click() +{ + // close parent if no link set + if ( !GetClickHdl() ) + { + vcl::Window* pParent = getNonLayoutParent(this); + if ( pParent->IsSystemWindow() ) + { + if ( pParent->IsDialog() ) + { + VclPtr<Dialog> xParent( static_cast<Dialog*>(pParent) ); + if ( xParent->IsInExecute() ) + xParent->EndDialog( RET_OK ); + // prevent recursive calls + else if ( !xParent->IsInClose() ) + { + if ( pParent->GetStyle() & WB_CLOSEABLE ) + xParent->Close(); + } + } + else + { + if ( pParent->GetStyle() & WB_CLOSEABLE ) + static_cast<SystemWindow*>(pParent)->Close(); + } + } + } + else + { + PushButton::Click(); + } +} + +void CancelButton::ImplInit( vcl::Window* pParent, WinBits nStyle ) +{ + set_id("cancel"); + PushButton::ImplInit( pParent, nStyle ); + + SetText( GetStandardText( StandardButtonType::Cancel ) ); +} + +CancelButton::CancelButton( vcl::Window* pParent, WinBits nStyle ) : + PushButton( WindowType::CANCELBUTTON ) +{ + ImplInit( pParent, nStyle ); +} + +void CancelButton::Click() +{ + // close parent if link not set + if ( !GetClickHdl() ) + { + vcl::Window* pParent = getNonLayoutParent(this); + if ( pParent->IsSystemWindow() ) + { + if ( pParent->IsDialog() ) + { + if ( static_cast<Dialog*>(pParent)->IsInExecute() ) + static_cast<Dialog*>(pParent)->EndDialog(); + // prevent recursive calls + else if ( !static_cast<Dialog*>(pParent)->IsInClose() ) + { + if ( pParent->GetStyle() & WB_CLOSEABLE ) + static_cast<Dialog*>(pParent)->Close(); + } + } + else + { + if ( pParent->GetStyle() & WB_CLOSEABLE ) + static_cast<SystemWindow*>(pParent)->Close(); + } + } + } + else + { + PushButton::Click(); + } +} + +CloseButton::CloseButton( vcl::Window* pParent ) + : CancelButton(pParent, 0) +{ + SetText( GetStandardText( StandardButtonType::Close ) ); +} + +void HelpButton::ImplInit( vcl::Window* pParent, WinBits nStyle ) +{ + set_id("help"); + PushButton::ImplInit( pParent, nStyle | WB_NOPOINTERFOCUS ); + + SetText( GetStandardText( StandardButtonType::Help ) ); +} + +HelpButton::HelpButton( vcl::Window* pParent, WinBits nStyle ) : + PushButton( WindowType::HELPBUTTON ) +{ + ImplInit( pParent, nStyle ); +} + +void HelpButton::Click() +{ + // trigger help if no link set + if ( !GetClickHdl() ) + { + vcl::Window* pFocusWin = Application::GetFocusWindow(); + if ( !pFocusWin || comphelper::LibreOfficeKit::isActive() ) + pFocusWin = this; + + HelpEvent aEvt( pFocusWin->GetPointerPosPixel(), HelpEventMode::CONTEXT ); + pFocusWin->RequestHelp( aEvt ); + } + PushButton::Click(); +} + +void HelpButton::StateChanged( StateChangedType nStateChange ) +{ + // Hide when we have no help URL. + if (comphelper::LibreOfficeKit::isActive() && + officecfg::Office::Common::Help::HelpRootURL::get().isEmpty()) + Hide(); + else + PushButton::StateChanged(nStateChange); +} + +void RadioButton::ImplInitRadioButtonData() +{ + mbChecked = false; + mbRadioCheck = true; + mbStateChanged = false; +} + +void RadioButton::ImplInit( vcl::Window* pParent, WinBits nStyle ) +{ + nStyle = ImplInitStyle(getPreviousSibling(pParent), nStyle); + Button::ImplInit( pParent, nStyle, nullptr ); + + ImplInitSettings( true ); +} + +WinBits RadioButton::ImplInitStyle( const vcl::Window* pPrevWindow, WinBits nStyle ) const +{ + if ( !(nStyle & WB_NOGROUP) && + (!pPrevWindow || (pPrevWindow->GetType() != WindowType::RADIOBUTTON)) ) + nStyle |= WB_GROUP; + if ( !(nStyle & WB_NOTABSTOP) ) + { + if ( IsChecked() ) + nStyle |= WB_TABSTOP; + else + nStyle &= ~WB_TABSTOP; + } + + return nStyle; +} + +const vcl::Font& RadioButton::GetCanonicalFont( const StyleSettings& _rStyle ) const +{ + return _rStyle.GetRadioCheckFont(); +} + +const Color& RadioButton::GetCanonicalTextColor( const StyleSettings& _rStyle ) const +{ + return _rStyle.GetRadioCheckTextColor(); +} + +void RadioButton::ImplInitSettings( bool bBackground ) +{ + Button::ImplInitSettings(); + + if ( !bBackground ) + return; + + vcl::Window* pParent = GetParent(); + if ( !IsControlBackground() && + (pParent->IsChildTransparentModeEnabled() || IsNativeControlSupported( ControlType::Radiobutton, ControlPart::Entire ) ) ) + { + EnableChildTransparentMode(); + SetParentClipMode( ParentClipMode::NoClip ); + SetPaintTransparent( true ); + SetBackground(); + if( IsNativeControlSupported( ControlType::Radiobutton, ControlPart::Entire ) ) + mpWindowImpl->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects; + } + else + { + EnableChildTransparentMode( false ); + SetParentClipMode(); + SetPaintTransparent( false ); + + if ( IsControlBackground() ) + SetBackground( GetControlBackground() ); + else + SetBackground( pParent->GetBackground() ); + } +} + +void RadioButton::ImplDrawRadioButtonState(vcl::RenderContext& rRenderContext) +{ + bool bNativeOK = false; + + // no native drawing for image radio buttons + if (!maImage && rRenderContext.IsNativeControlSupported(ControlType::Radiobutton, ControlPart::Entire)) + { + ImplControlValue aControlValue( mbChecked ? ButtonValue::On : ButtonValue::Off ); + tools::Rectangle aCtrlRect(maStateRect.TopLeft(), maStateRect.GetSize()); + ControlState nState = ControlState::NONE; + + if (GetButtonState() & DrawButtonFlags::Pressed) + nState |= ControlState::PRESSED; + if (HasFocus()) + nState |= ControlState::FOCUSED; + if (GetButtonState() & DrawButtonFlags::Default) + nState |= ControlState::DEFAULT; + if (IsEnabled()) + nState |= ControlState::ENABLED; + + if (IsMouseOver() && maMouseRect.Contains(GetPointerPosPixel())) + nState |= ControlState::ROLLOVER; + + bNativeOK = rRenderContext.DrawNativeControl(ControlType::Radiobutton, ControlPart::Entire, aCtrlRect, + nState, aControlValue, OUString()); + } + + if (bNativeOK) + return; + + if (!maImage) + { + DrawButtonFlags nStyle = GetButtonState(); + if (!IsEnabled()) + nStyle |= DrawButtonFlags::Disabled; + if (mbChecked) + nStyle |= DrawButtonFlags::Checked; + Image aImage = GetRadioImage(rRenderContext.GetSettings(), nStyle); + if (IsZoom()) + rRenderContext.DrawImage(maStateRect.TopLeft(), maStateRect.GetSize(), aImage); + else + rRenderContext.DrawImage(maStateRect.TopLeft(), aImage); + } + else + { + HideFocus(); + + DecorationView aDecoView(&rRenderContext); + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + tools::Rectangle aImageRect = maStateRect; + Size aImageSize = maImage.GetSizePixel(); + bool bEnabled = IsEnabled(); + + aImageSize.setWidth( CalcZoom(aImageSize.Width()) ); + aImageSize.setHeight( CalcZoom(aImageSize.Height()) ); + + aImageRect.AdjustLeft( 1 ); + aImageRect.AdjustTop( 1 ); + aImageRect.AdjustRight( -1 ); + aImageRect.AdjustBottom( -1 ); + + // display border and selection status + aImageRect = aDecoView.DrawFrame(aImageRect, DrawFrameStyle::DoubleIn); + if ((GetButtonState() & DrawButtonFlags::Pressed) || !bEnabled) + rRenderContext.SetFillColor( rStyleSettings.GetFaceColor()); + else + rRenderContext.SetFillColor(rStyleSettings.GetFieldColor()); + rRenderContext.SetLineColor(); + rRenderContext.DrawRect(aImageRect); + + // display image + DrawImageFlags nImageStyle = DrawImageFlags::NONE; + if (!bEnabled) + nImageStyle |= DrawImageFlags::Disable; + + Image* pImage = &maImage; + + Point aImagePos(aImageRect.TopLeft()); + aImagePos.AdjustX((aImageRect.GetWidth() - aImageSize.Width()) / 2 ); + aImagePos.AdjustY((aImageRect.GetHeight() - aImageSize.Height()) / 2 ); + if (IsZoom()) + rRenderContext.DrawImage(aImagePos, aImageSize, *pImage, nImageStyle); + else + rRenderContext.DrawImage(aImagePos, *pImage, nImageStyle); + + aImageRect.AdjustLeft( 1 ); + aImageRect.AdjustTop( 1 ); + aImageRect.AdjustRight( -1 ); + aImageRect.AdjustBottom( -1 ); + + ImplSetFocusRect(aImageRect); + + if (mbChecked) + { + rRenderContext.SetLineColor(rStyleSettings.GetHighlightColor()); + rRenderContext.SetFillColor(); + if ((aImageSize.Width() >= 20) || (aImageSize.Height() >= 20)) + { + aImageRect.AdjustLeft( 1 ); + aImageRect.AdjustTop( 1 ); + aImageRect.AdjustRight( -1 ); + aImageRect.AdjustBottom( -1 ); + } + rRenderContext.DrawRect(aImageRect); + aImageRect.AdjustLeft( 1 ); + aImageRect.AdjustTop( 1 ); + aImageRect.AdjustRight( -1 ); + aImageRect.AdjustBottom( -1 ); + rRenderContext.DrawRect(aImageRect); + } + + if (HasFocus()) + ShowFocus(ImplGetFocusRect()); + } +} + +// for drawing RadioButton or CheckButton that has Text and/or Image +void Button::ImplDrawRadioCheck(OutputDevice* pDev, WinBits nWinStyle, SystemTextColorFlags nSystemTextColorFlags, + const Point& rPos, const Size& rSize, + const Size& rImageSize, tools::Rectangle& rStateRect, + tools::Rectangle& rMouseRect) +{ + DrawTextFlags nTextStyle = Button::ImplGetTextStyle( nWinStyle, nSystemTextColorFlags ); + + const tools::Long nImageSep = GetDrawPixel( pDev, ImplGetImageToTextDistance() ); + Size aSize( rSize ); + Point aPos( rPos ); + aPos.AdjustX(rImageSize.Width() + nImageSep ); + + // tdf#141761 Old (convenience?) adjustment of width may lead to empty + // or negative(!) Size, that needs to be avoided. The coordinate context + // is pixel-oriented (all Paints of Controls are, historically), so + // the minimum width should be '1' Pixel. + // Hint: nImageSep is based on Zoom (using Window::CalcZoom) and + // MapModes (using Window::GetDrawPixel) - so potentially a wide range + // of unpredictable values is possible + const tools::Long nWidthAdjust(rImageSize.Width() + nImageSep); + aSize.setWidth(std::max(static_cast<tools::Long>(1), aSize.getWidth() - nWidthAdjust)); + + // if the text rect height is smaller than the height of the image + // then for single lines the default should be centered text + if( (nWinStyle & (WB_TOP|WB_VCENTER|WB_BOTTOM)) == 0 && + (rImageSize.Height() > rSize.Height() || ! (nWinStyle & WB_WORDBREAK) ) ) + { + nTextStyle &= ~DrawTextFlags(DrawTextFlags::Top|DrawTextFlags::Bottom); + nTextStyle |= DrawTextFlags::VCenter; + aSize.setHeight( rImageSize.Height() ); + } + + ImplDrawAlignedImage( pDev, aPos, aSize, 1, nTextStyle ); + + rMouseRect = tools::Rectangle( aPos, aSize ); + rMouseRect.SetLeft( rPos.X() ); + + rStateRect.SetLeft( rPos.X() ); + rStateRect.SetTop( rMouseRect.Top() ); + + if ( aSize.Height() > rImageSize.Height() ) + rStateRect.AdjustTop(( aSize.Height() - rImageSize.Height() ) / 2 ); + else + { + rStateRect.AdjustTop( -(( rImageSize.Height() - aSize.Height() ) / 2) ); + if( rStateRect.Top() < 0 ) + rStateRect.SetTop( 0 ); + } + + rStateRect.SetRight( rStateRect.Left()+rImageSize.Width()-1 ); + rStateRect.SetBottom( rStateRect.Top()+rImageSize.Height()-1 ); + + if ( rStateRect.Bottom() > rMouseRect.Bottom() ) + rMouseRect.SetBottom( rStateRect.Bottom() ); +} + +void RadioButton::ImplDraw( OutputDevice* pDev, SystemTextColorFlags nSystemTextColorFlags, + const Point& rPos, const Size& rSize, + const Size& rImageSize, tools::Rectangle& rStateRect, + tools::Rectangle& rMouseRect ) +{ + WinBits nWinStyle = GetStyle(); + OUString aText( GetText() ); + + pDev->Push( vcl::PushFlags::CLIPREGION ); + pDev->IntersectClipRegion( tools::Rectangle( rPos, rSize ) ); + + // no image radio button + if ( !maImage ) + { + if (!aText.isEmpty() || HasImage()) + { + Button::ImplDrawRadioCheck(pDev, nWinStyle, nSystemTextColorFlags, + rPos, rSize, rImageSize, + rStateRect, rMouseRect); + } + else + { + rStateRect.SetLeft( rPos.X() ); + if ( nWinStyle & WB_VCENTER ) + rStateRect.SetTop( rPos.Y()+((rSize.Height()-rImageSize.Height())/2) ); + else if ( nWinStyle & WB_BOTTOM ) + rStateRect.SetTop( rPos.Y()+rSize.Height()-rImageSize.Height() ); //-1; + else + rStateRect.SetTop( rPos.Y() ); + rStateRect.SetRight( rStateRect.Left()+rImageSize.Width()-1 ); + rStateRect.SetBottom( rStateRect.Top()+rImageSize.Height()-1 ); + rMouseRect = rStateRect; + + ImplSetFocusRect( rStateRect ); + } + } + else + { + bool bTopImage = (nWinStyle & WB_TOP) != 0; + Size aImageSize = maImage.GetSizePixel(); + tools::Rectangle aImageRect( rPos, rSize ); + tools::Long nTextHeight = pDev->GetTextHeight(); + tools::Long nTextWidth = pDev->GetCtrlTextWidth( aText ); + + // calculate position and sizes + if (!aText.isEmpty()) + { + Size aTmpSize( (aImageSize.Width()+8), (aImageSize.Height()+8) ); + if ( bTopImage ) + { + aImageRect.SetLeft( (rSize.Width()-aTmpSize.Width())/2 ); + aImageRect.SetTop( (rSize.Height()-(aTmpSize.Height()+nTextHeight+6))/2 ); + } + else + aImageRect.SetTop( (rSize.Height()-aTmpSize.Height())/2 ); + + aImageRect.SetRight( aImageRect.Left()+aTmpSize.Width() ); + aImageRect.SetBottom( aImageRect.Top()+aTmpSize.Height() ); + + // display text + Point aTxtPos = rPos; + if ( bTopImage ) + { + aTxtPos.AdjustX((rSize.Width()-nTextWidth)/2 ); + aTxtPos.AdjustY(aImageRect.Bottom()+6 ); + } + else + { + aTxtPos.AdjustX(aImageRect.Right()+8 ); + aTxtPos.AdjustY((rSize.Height()-nTextHeight)/2 ); + } + pDev->DrawCtrlText( aTxtPos, aText, 0, aText.getLength() ); + } + + rMouseRect = aImageRect; + rStateRect = aImageRect; + } + + pDev->Pop(); +} + +void RadioButton::ImplDrawRadioButton(vcl::RenderContext& rRenderContext) +{ + HideFocus(); + + Size aImageSize; + if (!maImage) + aImageSize = ImplGetRadioImageSize(); + else + aImageSize = maImage.GetSizePixel(); + + aImageSize.setWidth( CalcZoom(aImageSize.Width()) ); + aImageSize.setHeight( CalcZoom(aImageSize.Height()) ); + + // Draw control text + ImplDraw(&rRenderContext, SystemTextColorFlags::NONE, Point(), GetOutputSizePixel(), + aImageSize, maStateRect, maMouseRect); + + if (!maImage && HasFocus()) + ShowFocus(ImplGetFocusRect()); + + ImplDrawRadioButtonState(rRenderContext); +} + +void RadioButton::group(RadioButton &rOther) +{ + if (&rOther == this) + return; + + if (!m_xGroup) + { + m_xGroup = std::make_shared<std::vector<VclPtr<RadioButton> >>(); + m_xGroup->push_back(this); + } + + auto aFind = std::find(m_xGroup->begin(), m_xGroup->end(), VclPtr<RadioButton>(&rOther)); + if (aFind == m_xGroup->end()) + { + m_xGroup->push_back(&rOther); + + if (rOther.m_xGroup) + { + std::vector< VclPtr<RadioButton> > aOthers(rOther.GetRadioButtonGroup(false)); + //make all members of the group share the same button group + for (auto const& elem : aOthers) + { + aFind = std::find(m_xGroup->begin(), m_xGroup->end(), elem); + if (aFind == m_xGroup->end()) + m_xGroup->push_back(elem); + } + } + + //make all members of the group share the same button group + for (VclPtr<RadioButton> const & pButton : *m_xGroup) + { + pButton->m_xGroup = m_xGroup; + } + } + + //if this one is checked, uncheck all the others + if (mbChecked) + ImplUncheckAllOther(); +} + +std::vector< VclPtr<RadioButton> > RadioButton::GetRadioButtonGroup(bool bIncludeThis) const +{ + if (m_xGroup) + { + if (bIncludeThis) + return *m_xGroup; + std::vector< VclPtr<RadioButton> > aGroup; + for (VclPtr<RadioButton> const & pRadioButton : *m_xGroup) + { + if (pRadioButton == this) + continue; + aGroup.push_back(pRadioButton); + } + return aGroup; + } + + std::vector<VclPtr<RadioButton>> aGroup; + if (mbUsesExplicitGroup) + return aGroup; + + //old-school + + // go back to first in group; + vcl::Window* pFirst = const_cast<RadioButton*>(this); + while( ( pFirst->GetStyle() & WB_GROUP ) == 0 ) + { + vcl::Window* pWindow = pFirst->GetWindow( GetWindowType::Prev ); + if( pWindow ) + pFirst = pWindow; + else + break; + } + // insert radiobuttons up to next group + do + { + if( pFirst->GetType() == WindowType::RADIOBUTTON ) + { + if( pFirst != this || bIncludeThis ) + aGroup.emplace_back(static_cast<RadioButton*>(pFirst) ); + } + pFirst = pFirst->GetWindow( GetWindowType::Next ); + } while( pFirst && ( ( pFirst->GetStyle() & WB_GROUP ) == 0 ) ); + + return aGroup; +} + +void RadioButton::ImplUncheckAllOther() +{ + mpWindowImpl->mnStyle |= WB_TABSTOP; + + std::vector<VclPtr<RadioButton> > aGroup(GetRadioButtonGroup(false)); + // iterate over radio button group and checked buttons + for (VclPtr<RadioButton>& pWindow : aGroup) + { + if ( pWindow->IsChecked() ) + { + pWindow->SetState( false ); + if ( pWindow->isDisposed() ) + return; + } + + // not inside if clause to always remove wrongly set WB_TABSTOPS + pWindow->mpWindowImpl->mnStyle &= ~WB_TABSTOP; + } +} + +void RadioButton::ImplCallClick( bool bGrabFocus, GetFocusFlags nFocusFlags ) +{ + mbStateChanged = !mbChecked; + mbChecked = true; + mpWindowImpl->mnStyle |= WB_TABSTOP; + Invalidate(); + VclPtr<vcl::Window> xWindow = this; + if ( mbRadioCheck ) + ImplUncheckAllOther(); + if ( xWindow->isDisposed() ) + return; + if ( bGrabFocus ) + ImplGrabFocus( nFocusFlags ); + if ( xWindow->isDisposed() ) + return; + if ( mbStateChanged ) + Toggle(); + if ( xWindow->isDisposed() ) + return; + Click(); + if ( xWindow->isDisposed() ) + return; + mbStateChanged = false; +} + +RadioButton::RadioButton(vcl::Window* pParent, bool bUsesExplicitGroup, WinBits nStyle) + : Button(WindowType::RADIOBUTTON) + , mbUsesExplicitGroup(bUsesExplicitGroup) +{ + ImplInitRadioButtonData(); + ImplInit( pParent, nStyle ); +} + +RadioButton::~RadioButton() +{ + disposeOnce(); +} + +void RadioButton::dispose() +{ + if (m_xGroup) + { + m_xGroup->erase(std::remove(m_xGroup->begin(), m_xGroup->end(), VclPtr<RadioButton>(this)), + m_xGroup->end()); + m_xGroup.reset(); + } + Button::dispose(); +} + +void RadioButton::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( rMEvt.IsLeft() && maMouseRect.Contains( rMEvt.GetPosPixel() ) ) + { + GetButtonState() |= DrawButtonFlags::Pressed; + Invalidate(); + StartTracking(); + return; + } + + Button::MouseButtonDown( rMEvt ); +} + +void RadioButton::Tracking( const TrackingEvent& rTEvt ) +{ + if ( rTEvt.IsTrackingEnded() ) + { + if ( GetButtonState() & DrawButtonFlags::Pressed ) + { + if ( !(GetStyle() & WB_NOPOINTERFOCUS) && !rTEvt.IsTrackingCanceled() ) + GrabFocus(); + + GetButtonState() &= ~DrawButtonFlags::Pressed; + + // do not call click handler if aborted + if ( !rTEvt.IsTrackingCanceled() ) + ImplCallClick(); + else + { + Invalidate(); + } + } + } + else + { + if ( maMouseRect.Contains( rTEvt.GetMouseEvent().GetPosPixel() ) ) + { + if ( !(GetButtonState() & DrawButtonFlags::Pressed) ) + { + GetButtonState() |= DrawButtonFlags::Pressed; + Invalidate(); + } + } + else + { + if ( GetButtonState() & DrawButtonFlags::Pressed ) + { + GetButtonState() &= ~DrawButtonFlags::Pressed; + Invalidate(); + } + } + } +} + +void RadioButton::KeyInput( const KeyEvent& rKEvt ) +{ + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + + if ( !aKeyCode.GetModifier() && (aKeyCode.GetCode() == KEY_SPACE) ) + { + if ( !(GetButtonState() & DrawButtonFlags::Pressed) ) + { + GetButtonState() |= DrawButtonFlags::Pressed; + Invalidate(); + } + } + else if ( (GetButtonState() & DrawButtonFlags::Pressed) && (aKeyCode.GetCode() == KEY_ESCAPE) ) + { + GetButtonState() &= ~DrawButtonFlags::Pressed; + Invalidate(); + } + else + Button::KeyInput( rKEvt ); +} + +void RadioButton::KeyUp( const KeyEvent& rKEvt ) +{ + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + + if ( (GetButtonState() & DrawButtonFlags::Pressed) && (aKeyCode.GetCode() == KEY_SPACE) ) + { + GetButtonState() &= ~DrawButtonFlags::Pressed; + ImplCallClick(); + } + else + Button::KeyUp( rKEvt ); +} + +void RadioButton::FillLayoutData() const +{ + mxLayoutData.emplace(); + const_cast<RadioButton*>(this)->Invalidate(); +} + +void RadioButton::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& ) +{ + ImplDrawRadioButton(rRenderContext); +} + +void RadioButton::Draw( OutputDevice* pDev, const Point& rPos, + SystemTextColorFlags nFlags ) +{ + if ( !maImage ) + { + MapMode aResMapMode( MapUnit::Map100thMM ); + Point aPos = pDev->LogicToPixel( rPos ); + Size aSize = GetSizePixel(); + Size aImageSize = pDev->LogicToPixel( Size( 300, 300 ), aResMapMode ); + Size aBrd1Size = pDev->LogicToPixel( Size( 20, 20 ), aResMapMode ); + Size aBrd2Size = pDev->LogicToPixel( Size( 60, 60 ), aResMapMode ); + vcl::Font aFont = GetDrawPixelFont( pDev ); + tools::Rectangle aStateRect; + tools::Rectangle aMouseRect; + + aImageSize.setWidth( CalcZoom( aImageSize.Width() ) ); + aImageSize.setHeight( CalcZoom( aImageSize.Height() ) ); + aBrd1Size.setWidth( CalcZoom( aBrd1Size.Width() ) ); + aBrd1Size.setHeight( CalcZoom( aBrd1Size.Height() ) ); + aBrd2Size.setWidth( CalcZoom( aBrd2Size.Width() ) ); + aBrd2Size.setHeight( CalcZoom( aBrd2Size.Height() ) ); + + if ( !aBrd1Size.Width() ) + aBrd1Size.setWidth( 1 ); + if ( !aBrd1Size.Height() ) + aBrd1Size.setHeight( 1 ); + if ( !aBrd2Size.Width() ) + aBrd2Size.setWidth( 1 ); + if ( !aBrd2Size.Height() ) + aBrd2Size.setHeight( 1 ); + + pDev->Push(); + pDev->SetMapMode(); + pDev->SetFont( aFont ); + if ( nFlags & SystemTextColorFlags::Mono ) + pDev->SetTextColor( COL_BLACK ); + else + pDev->SetTextColor( GetTextColor() ); + pDev->SetTextFillColor(); + + ImplDraw( pDev, nFlags, aPos, aSize, + aImageSize, aStateRect, aMouseRect ); + + Point aCenterPos = aStateRect.Center(); + tools::Long nRadX = aImageSize.Width()/2; + tools::Long nRadY = aImageSize.Height()/2; + + pDev->SetLineColor(); + pDev->SetFillColor( COL_BLACK ); + pDev->DrawPolygon( tools::Polygon( aCenterPos, nRadX, nRadY ) ); + nRadX -= aBrd1Size.Width(); + nRadY -= aBrd1Size.Height(); + pDev->SetFillColor( COL_WHITE ); + pDev->DrawPolygon( tools::Polygon( aCenterPos, nRadX, nRadY ) ); + if ( mbChecked ) + { + nRadX -= aBrd1Size.Width(); + nRadY -= aBrd1Size.Height(); + if ( !nRadX ) + nRadX = 1; + if ( !nRadY ) + nRadY = 1; + pDev->SetFillColor( COL_BLACK ); + pDev->DrawPolygon( tools::Polygon( aCenterPos, nRadX, nRadY ) ); + } + + pDev->Pop(); + } + else + { + OSL_FAIL( "RadioButton::Draw() - not implemented for RadioButton with Image" ); + } +} + +void RadioButton::Resize() +{ + Control::Resize(); + Invalidate(); +} + +void RadioButton::GetFocus() +{ + ShowFocus( ImplGetFocusRect() ); + SetInputContext( InputContext( GetFont() ) ); + Button::GetFocus(); +} + +void RadioButton::LoseFocus() +{ + if ( GetButtonState() & DrawButtonFlags::Pressed ) + { + GetButtonState() &= ~DrawButtonFlags::Pressed; + Invalidate(); + } + + HideFocus(); + Button::LoseFocus(); +} + +void RadioButton::StateChanged( StateChangedType nType ) +{ + Button::StateChanged( nType ); + + if ( nType == StateChangedType::State ) + { + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate( maStateRect ); + } + else if ( (nType == StateChangedType::Enable) || + (nType == StateChangedType::Text) || + (nType == StateChangedType::Data) || + (nType == StateChangedType::UpdateMode) ) + { + if ( IsUpdateMode() ) + Invalidate(); + } + else if ( nType == StateChangedType::Style ) + { + SetStyle( ImplInitStyle( GetWindow( GetWindowType::Prev ), GetStyle() ) ); + + if ( (GetPrevStyle() & RADIOBUTTON_VIEW_STYLE) != + (GetStyle() & RADIOBUTTON_VIEW_STYLE) ) + { + if ( IsUpdateMode() ) + Invalidate(); + } + } + else if ( (nType == StateChangedType::Zoom) || + (nType == StateChangedType::ControlFont) ) + { + ImplInitSettings( false ); + Invalidate(); + } + else if ( nType == StateChangedType::ControlForeground ) + { + ImplInitSettings( false ); + Invalidate(); + } + else if ( nType == StateChangedType::ControlBackground ) + { + ImplInitSettings( true ); + Invalidate(); + } +} + +void RadioButton::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Button::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) || + (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) || + ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) ) + { + ImplInitSettings( true ); + Invalidate(); + } +} + +bool RadioButton::PreNotify( NotifyEvent& rNEvt ) +{ + if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE ) + { + const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent(); + if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() ) + { + // trigger redraw if mouse over state has changed + if( IsNativeControlSupported(ControlType::Radiobutton, ControlPart::Entire) ) + { + if (maMouseRect.Contains(GetPointerPosPixel()) != maMouseRect.Contains(GetLastPointerPosPixel()) || + pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow()) + { + Invalidate( maStateRect ); + } + } + } + } + + return Button::PreNotify(rNEvt); +} + +void RadioButton::Toggle() +{ + ImplCallEventListenersAndHandler( VclEventId::RadiobuttonToggle, [this] () { maToggleHdl.Call(*this); } ); +} + +void RadioButton::SetModeRadioImage( const Image& rImage ) +{ + if ( rImage != maImage ) + { + maImage = rImage; + CompatStateChanged( StateChangedType::Data ); + queue_resize(); + } +} + + +void RadioButton::SetState( bool bCheck ) +{ + // carry the TabStop flag along correctly + if ( bCheck ) + mpWindowImpl->mnStyle |= WB_TABSTOP; + else + mpWindowImpl->mnStyle &= ~WB_TABSTOP; + + if ( mbChecked != bCheck ) + { + mbChecked = bCheck; + CompatStateChanged( StateChangedType::State ); + Toggle(); + } +} + +bool RadioButton::set_property(const OString &rKey, const OUString &rValue) +{ + if (rKey == "active") + SetState(toBool(rValue)); + else if (rKey == "image-position") + { + WinBits nBits = GetStyle(); + if (rValue == "left") + { + nBits &= ~(WB_CENTER | WB_RIGHT); + nBits |= WB_LEFT; + } + else if (rValue == "right") + { + nBits &= ~(WB_CENTER | WB_LEFT); + nBits |= WB_RIGHT; + } + else if (rValue == "top") + { + nBits &= ~(WB_VCENTER | WB_BOTTOM); + nBits |= WB_TOP; + } + else if (rValue == "bottom") + { + nBits &= ~(WB_VCENTER | WB_TOP); + nBits |= WB_BOTTOM; + } + //It's rather mad to have to set these bits when there is the other + //image align. Looks like e.g. the radiobuttons etc weren't converted + //over to image align fully. + SetStyle(nBits); + //Deliberate to set the sane ImageAlign property + return Button::set_property(rKey, rValue); + } + else + return Button::set_property(rKey, rValue); + return true; +} + +void RadioButton::Check( bool bCheck ) +{ + // TabStop-Flag richtig mitfuehren + if ( bCheck ) + mpWindowImpl->mnStyle |= WB_TABSTOP; + else + mpWindowImpl->mnStyle &= ~WB_TABSTOP; + + if ( mbChecked == bCheck ) + return; + + mbChecked = bCheck; + VclPtr<vcl::Window> xWindow = this; + CompatStateChanged( StateChangedType::State ); + if ( xWindow->isDisposed() ) + return; + if ( bCheck && mbRadioCheck ) + ImplUncheckAllOther(); + if ( xWindow->isDisposed() ) + return; + Toggle(); +} + +tools::Long Button::ImplGetImageToTextDistance() const +{ + // 4 pixels, but take zoom into account, so the text doesn't "jump" relative to surrounding elements, + // which might have been aligned with the text of the check box + return CalcZoom( 4 ); +} + +Size RadioButton::ImplGetRadioImageSize() const +{ + Size aSize; + bool bDefaultSize = true; + if( IsNativeControlSupported( ControlType::Radiobutton, ControlPart::Entire ) ) + { + ImplControlValue aControlValue; + tools::Rectangle aCtrlRegion( Point( 0, 0 ), GetSizePixel() ); + tools::Rectangle aBoundingRgn, aContentRgn; + + // get native size of a radio button + if( GetNativeControlRegion( ControlType::Radiobutton, ControlPart::Entire, aCtrlRegion, + ControlState::DEFAULT|ControlState::ENABLED, + aControlValue, + aBoundingRgn, aContentRgn ) ) + { + aSize = aContentRgn.GetSize(); + bDefaultSize = false; + } + } + if( bDefaultSize ) + aSize = GetRadioImage( GetSettings(), DrawButtonFlags::NONE ).GetSizePixel(); + return aSize; +} + +static void LoadThemedImageList(const StyleSettings &rStyleSettings, + std::vector<Image>& rList, const std::vector<OUString> &rResources) +{ + Color aColorAry1[6]; + Color aColorAry2[6]; + aColorAry1[0] = Color( 0xC0, 0xC0, 0xC0 ); + aColorAry1[1] = Color( 0xFF, 0xFF, 0x00 ); + aColorAry1[2] = Color( 0xFF, 0xFF, 0xFF ); + aColorAry1[3] = Color( 0x80, 0x80, 0x80 ); + aColorAry1[4] = Color( 0x00, 0x00, 0x00 ); + aColorAry1[5] = Color( 0x00, 0xFF, 0x00 ); + aColorAry2[0] = rStyleSettings.GetFaceColor(); + aColorAry2[1] = rStyleSettings.GetWindowColor(); + aColorAry2[2] = rStyleSettings.GetLightColor(); + aColorAry2[3] = rStyleSettings.GetShadowColor(); + aColorAry2[4] = rStyleSettings.GetDarkShadowColor(); + aColorAry2[5] = rStyleSettings.GetWindowTextColor(); + + static_assert( sizeof(aColorAry1) == sizeof(aColorAry2), "aColorAry1 must match aColorAry2" ); + + for (const auto &a : rResources) + { + BitmapEx aBmpEx(a); + aBmpEx.Replace(aColorAry1, aColorAry2, SAL_N_ELEMENTS(aColorAry1)); + rList.emplace_back(aBmpEx); + } +} + +Image RadioButton::GetRadioImage( const AllSettings& rSettings, DrawButtonFlags nFlags ) +{ + ImplSVData* pSVData = ImplGetSVData(); + const StyleSettings& rStyleSettings = rSettings.GetStyleSettings(); + sal_uInt16 nStyle = 0; + + if ( rStyleSettings.GetOptions() & StyleSettingsOptions::Mono ) + nStyle = STYLE_RADIOBUTTON_MONO; + + if ( pSVData->maCtrlData.maRadioImgList.empty() || + (pSVData->maCtrlData.mnRadioStyle != nStyle) || + (pSVData->maCtrlData.mnLastRadioFColor != rStyleSettings.GetFaceColor()) || + (pSVData->maCtrlData.mnLastRadioWColor != rStyleSettings.GetWindowColor()) || + (pSVData->maCtrlData.mnLastRadioLColor != rStyleSettings.GetLightColor()) ) + { + pSVData->maCtrlData.maRadioImgList.clear(); + + pSVData->maCtrlData.mnLastRadioFColor = rStyleSettings.GetFaceColor(); + pSVData->maCtrlData.mnLastRadioWColor = rStyleSettings.GetWindowColor(); + pSVData->maCtrlData.mnLastRadioLColor = rStyleSettings.GetLightColor(); + + std::vector<OUString> aResources; + if (nStyle) + { + aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO1); + aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO2); + aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO3); + aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO4); + aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO5); + aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO6); + } + else + { + aResources.emplace_back(SV_RESID_BITMAP_RADIO1); + aResources.emplace_back(SV_RESID_BITMAP_RADIO2); + aResources.emplace_back(SV_RESID_BITMAP_RADIO3); + aResources.emplace_back(SV_RESID_BITMAP_RADIO4); + aResources.emplace_back(SV_RESID_BITMAP_RADIO5); + aResources.emplace_back(SV_RESID_BITMAP_RADIO6); + } + LoadThemedImageList( rStyleSettings, pSVData->maCtrlData.maRadioImgList, aResources); + pSVData->maCtrlData.mnRadioStyle = nStyle; + } + + sal_uInt16 nIndex; + if ( nFlags & DrawButtonFlags::Disabled ) + { + if ( nFlags & DrawButtonFlags::Checked ) + nIndex = 5; + else + nIndex = 4; + } + else if ( nFlags & DrawButtonFlags::Pressed ) + { + if ( nFlags & DrawButtonFlags::Checked ) + nIndex = 3; + else + nIndex = 2; + } + else + { + if ( nFlags & DrawButtonFlags::Checked ) + nIndex = 1; + else + nIndex = 0; + } + return pSVData->maCtrlData.maRadioImgList[nIndex]; +} + +void RadioButton::ImplAdjustNWFSizes() +{ + GetOutDev()->Push( vcl::PushFlags::MAPMODE ); + SetMapMode(MapMode(MapUnit::MapPixel)); + + ImplControlValue aControlValue; + Size aCurSize( GetSizePixel() ); + tools::Rectangle aCtrlRegion( Point( 0, 0 ), aCurSize ); + tools::Rectangle aBoundingRgn, aContentRgn; + + // get native size of a radiobutton + if( GetNativeControlRegion( ControlType::Radiobutton, ControlPart::Entire, aCtrlRegion, + ControlState::DEFAULT|ControlState::ENABLED, aControlValue, + aBoundingRgn, aContentRgn ) ) + { + Size aSize = aContentRgn.GetSize(); + + if( aSize.Height() > aCurSize.Height() ) + { + aCurSize.setHeight( aSize.Height() ); + SetSizePixel( aCurSize ); + } + } + + GetOutDev()->Pop(); +} + +Size RadioButton::CalcMinimumSize(tools::Long nMaxWidth) const +{ + Size aSize; + if ( !maImage ) + aSize = ImplGetRadioImageSize(); + else + { + aSize = maImage.GetSizePixel(); + aSize.AdjustWidth(8); + aSize.AdjustHeight(8); + } + + if (Button::HasImage()) + { + Size aImgSize = GetModeImage().GetSizePixel(); + aSize = Size(std::max(aImgSize.Width(), aSize.Width()), + std::max(aImgSize.Height(), aSize.Height())); + } + + OUString aText = GetText(); + if (!aText.isEmpty()) + { + bool bTopImage = (GetStyle() & WB_TOP) != 0; + + Size aTextSize = GetTextRect( tools::Rectangle( Point(), Size( nMaxWidth > 0 ? nMaxWidth : 0x7fffffff, 0x7fffffff ) ), + aText, FixedText::ImplGetTextStyle( GetStyle() ) ).GetSize(); + + aSize.AdjustWidth(2 ); // for focus rect + + if (!bTopImage) + { + aSize.AdjustWidth(ImplGetImageToTextDistance() ); + aSize.AdjustWidth(aTextSize.Width() ); + if ( aSize.Height() < aTextSize.Height() ) + aSize.setHeight( aTextSize.Height() ); + } + else + { + aSize.AdjustHeight(6 ); + aSize.AdjustHeight(GetTextHeight() ); + if ( aSize.Width() < aTextSize.Width() ) + aSize.setWidth( aTextSize.Width() ); + } + } + + return CalcWindowSize( aSize ); +} + +Size RadioButton::GetOptimalSize() const +{ + return CalcMinimumSize(); +} + +void RadioButton::ShowFocus(const tools::Rectangle& rRect) +{ + if (IsNativeControlSupported(ControlType::Radiobutton, ControlPart::Focus)) + { + ImplControlValue aControlValue; + tools::Rectangle aInRect(Point(0, 0), GetSizePixel()); + + aInRect.SetLeft( rRect.Left() ); // exclude the radio element itself from the focusrect + + GetOutDev()->DrawNativeControl(ControlType::Radiobutton, ControlPart::Focus, aInRect, + ControlState::FOCUSED, aControlValue, OUString()); + } + Button::ShowFocus(rRect); +} + +void RadioButton::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + Button::DumpAsPropertyTree(rJsonWriter); + rJsonWriter.put("checked", IsChecked()); + + OUString sGroupId; + std::vector<VclPtr<RadioButton>> aGroup = GetRadioButtonGroup(); + for(const auto& pButton : aGroup) + sGroupId += pButton->get_id(); + + if (!sGroupId.isEmpty()) + rJsonWriter.put("group", sGroupId); + + if (!!maImage) + { + SvMemoryStream aOStm(6535, 6535); + if(GraphicConverter::Export(aOStm, maImage.GetBitmapEx(), ConvertDataFormat::PNG) == ERRCODE_NONE) + { + css::uno::Sequence<sal_Int8> aSeq( static_cast<sal_Int8 const *>(aOStm.GetData()), aOStm.Tell()); + OUStringBuffer aBuffer("data:image/png;base64,"); + ::comphelper::Base64::encode(aBuffer, aSeq); + rJsonWriter.put("image", aBuffer.makeStringAndClear()); + } + } +} + +FactoryFunction RadioButton::GetUITestFactory() const +{ + return RadioButtonUIObject::create; +} + +void CheckBox::ImplInitCheckBoxData() +{ + meState = TRISTATE_FALSE; + mbTriState = false; +} + +void CheckBox::ImplInit( vcl::Window* pParent, WinBits nStyle ) +{ + nStyle = ImplInitStyle(getPreviousSibling(pParent), nStyle); + Button::ImplInit( pParent, nStyle, nullptr ); + + ImplInitSettings( true ); +} + +WinBits CheckBox::ImplInitStyle( const vcl::Window* pPrevWindow, WinBits nStyle ) +{ + if ( !(nStyle & WB_NOTABSTOP) ) + nStyle |= WB_TABSTOP; + if ( !(nStyle & WB_NOGROUP) && + (!pPrevWindow || (pPrevWindow->GetType() != WindowType::CHECKBOX)) ) + nStyle |= WB_GROUP; + return nStyle; +} + +const vcl::Font& CheckBox::GetCanonicalFont( const StyleSettings& _rStyle ) const +{ + return _rStyle.GetRadioCheckFont(); +} + +const Color& CheckBox::GetCanonicalTextColor( const StyleSettings& _rStyle ) const +{ + return _rStyle.GetRadioCheckTextColor(); +} + +void CheckBox::ImplInitSettings( bool bBackground ) +{ + Button::ImplInitSettings(); + + if ( !bBackground ) + return; + + vcl::Window* pParent = GetParent(); + if ( !IsControlBackground() && + (pParent->IsChildTransparentModeEnabled() || IsNativeControlSupported( ControlType::Checkbox, ControlPart::Entire ) ) ) + { + EnableChildTransparentMode(); + SetParentClipMode( ParentClipMode::NoClip ); + SetPaintTransparent( true ); + SetBackground(); + if( IsNativeControlSupported( ControlType::Checkbox, ControlPart::Entire ) ) + ImplGetWindowImpl()->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects; + } + else + { + EnableChildTransparentMode( false ); + SetParentClipMode(); + SetPaintTransparent( false ); + + if ( IsControlBackground() ) + SetBackground( GetControlBackground() ); + else + SetBackground( pParent->GetBackground() ); + } +} + +void CheckBox::ImplDrawCheckBoxState(vcl::RenderContext& rRenderContext) +{ + bool bNativeOK = rRenderContext.IsNativeControlSupported(ControlType::Checkbox, ControlPart::Entire); + if (bNativeOK) + { + ImplControlValue aControlValue(meState == TRISTATE_TRUE ? ButtonValue::On : ButtonValue::Off); + tools::Rectangle aCtrlRegion(maStateRect); + ControlState nState = ControlState::NONE; + + if (HasFocus()) + nState |= ControlState::FOCUSED; + if (GetButtonState() & DrawButtonFlags::Default) + nState |= ControlState::DEFAULT; + if (GetButtonState() & DrawButtonFlags::Pressed) + nState |= ControlState::PRESSED; + if (IsEnabled()) + nState |= ControlState::ENABLED; + + if (meState == TRISTATE_TRUE) + aControlValue.setTristateVal(ButtonValue::On); + else if (meState == TRISTATE_INDET) + aControlValue.setTristateVal(ButtonValue::Mixed); + + if (IsMouseOver() && maMouseRect.Contains(GetPointerPosPixel())) + nState |= ControlState::ROLLOVER; + + bNativeOK = rRenderContext.DrawNativeControl(ControlType::Checkbox, ControlPart::Entire, aCtrlRegion, + nState, aControlValue, OUString()); + } + + if (bNativeOK) + return; + + DrawButtonFlags nStyle = GetButtonState(); + if (!IsEnabled()) + nStyle |= DrawButtonFlags::Disabled; + if (meState == TRISTATE_INDET) + nStyle |= DrawButtonFlags::DontKnow; + else if (meState == TRISTATE_TRUE) + nStyle |= DrawButtonFlags::Checked; + Image aImage = GetCheckImage(GetSettings(), nStyle); + if (IsZoom()) + rRenderContext.DrawImage(maStateRect.TopLeft(), maStateRect.GetSize(), aImage); + else + rRenderContext.DrawImage(maStateRect.TopLeft(), aImage); +} + +void CheckBox::ImplDraw( OutputDevice* pDev, SystemTextColorFlags nSystemTextColorFlags, + const Point& rPos, const Size& rSize, + const Size& rImageSize, tools::Rectangle& rStateRect, + tools::Rectangle& rMouseRect ) +{ + WinBits nWinStyle = GetStyle(); + OUString aText( GetText() ); + + pDev->Push( vcl::PushFlags::CLIPREGION | vcl::PushFlags::LINECOLOR ); + pDev->IntersectClipRegion( tools::Rectangle( rPos, rSize ) ); + + if (!aText.isEmpty() || HasImage()) + { + Button::ImplDrawRadioCheck(pDev, nWinStyle, nSystemTextColorFlags, + rPos, rSize, rImageSize, + rStateRect, rMouseRect); + } + else + { + rStateRect.SetLeft( rPos.X() ); + if ( nWinStyle & WB_VCENTER ) + rStateRect.SetTop( rPos.Y()+((rSize.Height()-rImageSize.Height())/2) ); + else if ( nWinStyle & WB_BOTTOM ) + rStateRect.SetTop( rPos.Y()+rSize.Height()-rImageSize.Height() ); + else + rStateRect.SetTop( rPos.Y() ); + rStateRect.SetRight( rStateRect.Left()+rImageSize.Width()-1 ); + rStateRect.SetBottom( rStateRect.Top()+rImageSize.Height()-1 ); + // provide space for focusrect + // note: this assumes that the control's size was adjusted + // accordingly in Get/LoseFocus, so the onscreen position won't change + if( HasFocus() ) + rStateRect.Move( 1, 1 ); + rMouseRect = rStateRect; + + ImplSetFocusRect( rStateRect ); + } + + pDev->Pop(); +} + +void CheckBox::ImplDrawCheckBox(vcl::RenderContext& rRenderContext) +{ + Size aImageSize = ImplGetCheckImageSize(); + aImageSize.setWidth( CalcZoom( aImageSize.Width() ) ); + aImageSize.setHeight( CalcZoom( aImageSize.Height() ) ); + + HideFocus(); + + ImplDraw(&rRenderContext, SystemTextColorFlags::NONE, Point(), GetOutputSizePixel(), + aImageSize, maStateRect, maMouseRect); + + ImplDrawCheckBoxState(rRenderContext); + if (HasFocus()) + ShowFocus(ImplGetFocusRect()); +} + +void CheckBox::ImplCheck() +{ + TriState eNewState; + if ( meState == TRISTATE_FALSE ) + eNewState = TRISTATE_TRUE; + else if ( !mbTriState ) + eNewState = TRISTATE_FALSE; + else if ( meState == TRISTATE_TRUE ) + eNewState = TRISTATE_INDET; + else + eNewState = TRISTATE_FALSE; + meState = eNewState; + + VclPtr<vcl::Window> xWindow = this; + Invalidate(); + Toggle(); + if ( xWindow->isDisposed() ) + return; + Click(); +} + +CheckBox::CheckBox( vcl::Window* pParent, WinBits nStyle ) : + Button( WindowType::CHECKBOX ) +{ + ImplInitCheckBoxData(); + ImplInit( pParent, nStyle ); +} + +void CheckBox::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( rMEvt.IsLeft() && maMouseRect.Contains( rMEvt.GetPosPixel() ) ) + { + GetButtonState() |= DrawButtonFlags::Pressed; + Invalidate(); + StartTracking(); + return; + } + + Button::MouseButtonDown( rMEvt ); +} + +void CheckBox::Tracking( const TrackingEvent& rTEvt ) +{ + if ( rTEvt.IsTrackingEnded() ) + { + if ( GetButtonState() & DrawButtonFlags::Pressed ) + { + if ( !(GetStyle() & WB_NOPOINTERFOCUS) && !rTEvt.IsTrackingCanceled() ) + GrabFocus(); + + GetButtonState() &= ~DrawButtonFlags::Pressed; + + // do not call click handler if aborted + if ( !rTEvt.IsTrackingCanceled() ) + ImplCheck(); + else + { + Invalidate(); + } + } + } + else + { + if ( maMouseRect.Contains( rTEvt.GetMouseEvent().GetPosPixel() ) ) + { + if ( !(GetButtonState() & DrawButtonFlags::Pressed) ) + { + GetButtonState() |= DrawButtonFlags::Pressed; + Invalidate(); + } + } + else + { + if ( GetButtonState() & DrawButtonFlags::Pressed ) + { + GetButtonState() &= ~DrawButtonFlags::Pressed; + Invalidate(); + } + } + } +} + +void CheckBox::KeyInput( const KeyEvent& rKEvt ) +{ + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + + if ( !aKeyCode.GetModifier() && (aKeyCode.GetCode() == KEY_SPACE) ) + { + if ( !(GetButtonState() & DrawButtonFlags::Pressed) ) + { + GetButtonState() |= DrawButtonFlags::Pressed; + Invalidate(); + } + } + else if ( (GetButtonState() & DrawButtonFlags::Pressed) && (aKeyCode.GetCode() == KEY_ESCAPE) ) + { + GetButtonState() &= ~DrawButtonFlags::Pressed; + Invalidate(); + } + else + Button::KeyInput( rKEvt ); +} + +void CheckBox::KeyUp( const KeyEvent& rKEvt ) +{ + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + + if ( (GetButtonState() & DrawButtonFlags::Pressed) && (aKeyCode.GetCode() == KEY_SPACE) ) + { + GetButtonState() &= ~DrawButtonFlags::Pressed; + ImplCheck(); + } + else + Button::KeyUp( rKEvt ); +} + +void CheckBox::FillLayoutData() const +{ + mxLayoutData.emplace(); + const_cast<CheckBox*>(this)->Invalidate(); +} + +void CheckBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + ImplDrawCheckBox(rRenderContext); +} + +void CheckBox::Draw( OutputDevice* pDev, const Point& rPos, + SystemTextColorFlags nFlags ) +{ + MapMode aResMapMode( MapUnit::Map100thMM ); + Point aPos = pDev->LogicToPixel( rPos ); + Size aSize = GetSizePixel(); + Size aImageSize = pDev->LogicToPixel( Size( 300, 300 ), aResMapMode ); + Size aBrd1Size = pDev->LogicToPixel( Size( 20, 20 ), aResMapMode ); + Size aBrd2Size = pDev->LogicToPixel( Size( 30, 30 ), aResMapMode ); + tools::Long nCheckWidth = pDev->LogicToPixel( Size( 20, 20 ), aResMapMode ).Width(); + vcl::Font aFont = GetDrawPixelFont( pDev ); + tools::Rectangle aStateRect; + tools::Rectangle aMouseRect; + + aImageSize.setWidth( CalcZoom( aImageSize.Width() ) ); + aImageSize.setHeight( CalcZoom( aImageSize.Height() ) ); + aBrd1Size.setWidth( CalcZoom( aBrd1Size.Width() ) ); + aBrd1Size.setHeight( CalcZoom( aBrd1Size.Height() ) ); + aBrd2Size.setWidth( CalcZoom( aBrd2Size.Width() ) ); + aBrd2Size.setHeight( CalcZoom( aBrd2Size.Height() ) ); + + if ( !aBrd1Size.Width() ) + aBrd1Size.setWidth( 1 ); + if ( !aBrd1Size.Height() ) + aBrd1Size.setHeight( 1 ); + if ( !aBrd2Size.Width() ) + aBrd2Size.setWidth( 1 ); + if ( !aBrd2Size.Height() ) + aBrd2Size.setHeight( 1 ); + if ( !nCheckWidth ) + nCheckWidth = 1; + + pDev->Push(); + pDev->SetMapMode(); + pDev->SetFont( aFont ); + if ( nFlags & SystemTextColorFlags::Mono ) + pDev->SetTextColor( COL_BLACK ); + else + pDev->SetTextColor( GetTextColor() ); + pDev->SetTextFillColor(); + + ImplDraw( pDev, nFlags, aPos, aSize, + aImageSize, aStateRect, aMouseRect ); + + pDev->SetLineColor(); + pDev->SetFillColor( COL_BLACK ); + pDev->DrawRect( aStateRect ); + aStateRect.AdjustLeft(aBrd1Size.Width() ); + aStateRect.AdjustTop(aBrd1Size.Height() ); + aStateRect.AdjustRight( -(aBrd1Size.Width()) ); + aStateRect.AdjustBottom( -(aBrd1Size.Height()) ); + if ( meState == TRISTATE_INDET ) + pDev->SetFillColor( COL_LIGHTGRAY ); + else + pDev->SetFillColor( COL_WHITE ); + pDev->DrawRect( aStateRect ); + + if ( meState == TRISTATE_TRUE ) + { + aStateRect.AdjustLeft(aBrd2Size.Width() ); + aStateRect.AdjustTop(aBrd2Size.Height() ); + aStateRect.AdjustRight( -(aBrd2Size.Width()) ); + aStateRect.AdjustBottom( -(aBrd2Size.Height()) ); + Point aPos11( aStateRect.TopLeft() ); + Point aPos12( aStateRect.BottomRight() ); + Point aPos21( aStateRect.TopRight() ); + Point aPos22( aStateRect.BottomLeft() ); + Point aTempPos11( aPos11 ); + Point aTempPos12( aPos12 ); + Point aTempPos21( aPos21 ); + Point aTempPos22( aPos22 ); + pDev->SetLineColor( COL_BLACK ); + tools::Long nDX = 0; + for ( tools::Long i = 0; i < nCheckWidth; i++ ) + { + if ( !(i % 2) ) + { + aTempPos11.setX( aPos11.X()+nDX ); + aTempPos12.setX( aPos12.X()+nDX ); + aTempPos21.setX( aPos21.X()+nDX ); + aTempPos22.setX( aPos22.X()+nDX ); + } + else + { + nDX++; + aTempPos11.setX( aPos11.X()-nDX ); + aTempPos12.setX( aPos12.X()-nDX ); + aTempPos21.setX( aPos21.X()-nDX ); + aTempPos22.setX( aPos22.X()-nDX ); + } + pDev->DrawLine( aTempPos11, aTempPos12 ); + pDev->DrawLine( aTempPos21, aTempPos22 ); + } + } + + pDev->Pop(); +} + +void CheckBox::Resize() +{ + Control::Resize(); + Invalidate(); +} + +void CheckBox::GetFocus() +{ + if (GetText().isEmpty()) + { + // increase button size to have space for focus rect + // checkboxes without text will draw focusrect around the check + // See CheckBox::ImplDraw() + Point aPos( GetPosPixel() ); + Size aSize( GetSizePixel() ); + aPos.Move(-1,-1); + aSize.AdjustHeight(2 ); + aSize.AdjustWidth(2 ); + setPosSizePixel( aPos.X(), aPos.Y(), aSize.Width(), aSize.Height() ); + Invalidate(); + // Trigger drawing to initialize the mouse rectangle, otherwise the mouse button down + // handler would ignore the mouse event. + PaintImmediately(); + } + else + ShowFocus( ImplGetFocusRect() ); + + SetInputContext( InputContext( GetFont() ) ); + Button::GetFocus(); +} + +void CheckBox::LoseFocus() +{ + if ( GetButtonState() & DrawButtonFlags::Pressed ) + { + GetButtonState() &= ~DrawButtonFlags::Pressed; + Invalidate(); + } + + HideFocus(); + Button::LoseFocus(); + + if (GetText().isEmpty()) + { + // decrease button size again (see GetFocus()) + // checkboxes without text will draw focusrect around the check + Point aPos( GetPosPixel() ); + Size aSize( GetSizePixel() ); + aPos.Move(1,1); + aSize.AdjustHeight( -2 ); + aSize.AdjustWidth( -2 ); + setPosSizePixel( aPos.X(), aPos.Y(), aSize.Width(), aSize.Height() ); + Invalidate(); + } +} + +void CheckBox::StateChanged( StateChangedType nType ) +{ + Button::StateChanged( nType ); + + if ( nType == StateChangedType::State ) + { + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate( maStateRect ); + } + else if ( (nType == StateChangedType::Enable) || + (nType == StateChangedType::Text) || + (nType == StateChangedType::Data) || + (nType == StateChangedType::UpdateMode) ) + { + if ( IsUpdateMode() ) + Invalidate(); + } + else if ( nType == StateChangedType::Style ) + { + SetStyle( ImplInitStyle( GetWindow( GetWindowType::Prev ), GetStyle() ) ); + + if ( (GetPrevStyle() & CHECKBOX_VIEW_STYLE) != + (GetStyle() & CHECKBOX_VIEW_STYLE) ) + { + if ( IsUpdateMode() ) + Invalidate(); + } + } + else if ( (nType == StateChangedType::Zoom) || + (nType == StateChangedType::ControlFont) ) + { + ImplInitSettings( false ); + Invalidate(); + } + else if ( nType == StateChangedType::ControlForeground ) + { + ImplInitSettings( false ); + Invalidate(); + } + else if ( nType == StateChangedType::ControlBackground ) + { + ImplInitSettings( true ); + Invalidate(); + } +} + +void CheckBox::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Button::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) || + (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) || + ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) ) + { + ImplInitSettings( true ); + Invalidate(); + } +} + +bool CheckBox::PreNotify( NotifyEvent& rNEvt ) +{ + if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE ) + { + const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent(); + if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() ) + { + // trigger redraw if mouse over state has changed + if( IsNativeControlSupported(ControlType::Checkbox, ControlPart::Entire) ) + { + if (maMouseRect.Contains(GetPointerPosPixel()) != maMouseRect.Contains(GetLastPointerPosPixel()) || + pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow()) + { + Invalidate( maStateRect ); + } + } + } + } + + return Button::PreNotify(rNEvt); +} + +void CheckBox::Toggle() +{ + ImplCallEventListenersAndHandler( VclEventId::CheckboxToggle, [this] () { maToggleHdl.Call(*this); } ); +} + +void CheckBox::SetState( TriState eState ) +{ + if ( !mbTriState && (eState == TRISTATE_INDET) ) + eState = TRISTATE_FALSE; + + if ( meState != eState ) + { + meState = eState; + StateChanged( StateChangedType::State ); + Toggle(); + } +} + +bool CheckBox::set_property(const OString &rKey, const OUString &rValue) +{ + if (rKey == "active") + SetState(toBool(rValue) ? TRISTATE_TRUE : TRISTATE_FALSE); + else + return Button::set_property(rKey, rValue); + return true; +} + +void CheckBox::EnableTriState( bool bTriState ) +{ + if ( mbTriState != bTriState ) + { + mbTriState = bTriState; + + if ( !bTriState && (meState == TRISTATE_INDET) ) + SetState( TRISTATE_FALSE ); + } +} + +Size CheckBox::ImplGetCheckImageSize() const +{ + Size aSize; + bool bDefaultSize = true; + if( IsNativeControlSupported( ControlType::Checkbox, ControlPart::Entire ) ) + { + ImplControlValue aControlValue; + tools::Rectangle aCtrlRegion( Point( 0, 0 ), GetSizePixel() ); + tools::Rectangle aBoundingRgn, aContentRgn; + + // get native size of a check box + if( GetNativeControlRegion( ControlType::Checkbox, ControlPart::Entire, aCtrlRegion, + ControlState::DEFAULT|ControlState::ENABLED, + aControlValue, + aBoundingRgn, aContentRgn ) ) + { + aSize = aContentRgn.GetSize(); + bDefaultSize = false; + } + } + if( bDefaultSize ) + aSize = GetCheckImage( GetSettings(), DrawButtonFlags::NONE ).GetSizePixel(); + return aSize; +} + +Image CheckBox::GetCheckImage( const AllSettings& rSettings, DrawButtonFlags nFlags ) +{ + ImplSVData* pSVData = ImplGetSVData(); + const StyleSettings& rStyleSettings = rSettings.GetStyleSettings(); + sal_uInt16 nStyle = 0; + + if ( rStyleSettings.GetOptions() & StyleSettingsOptions::Mono ) + nStyle = STYLE_CHECKBOX_MONO; + + if ( pSVData->maCtrlData.maCheckImgList.empty() || + (pSVData->maCtrlData.mnCheckStyle != nStyle) || + (pSVData->maCtrlData.mnLastCheckFColor != rStyleSettings.GetFaceColor()) || + (pSVData->maCtrlData.mnLastCheckWColor != rStyleSettings.GetWindowColor()) || + (pSVData->maCtrlData.mnLastCheckLColor != rStyleSettings.GetLightColor()) ) + { + pSVData->maCtrlData.maCheckImgList.clear(); + + pSVData->maCtrlData.mnLastCheckFColor = rStyleSettings.GetFaceColor(); + pSVData->maCtrlData.mnLastCheckWColor = rStyleSettings.GetWindowColor(); + pSVData->maCtrlData.mnLastCheckLColor = rStyleSettings.GetLightColor(); + + std::vector<OUString> aResources; + if (nStyle) + { + aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO1); + aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO2); + aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO3); + aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO4); + aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO5); + aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO6); + aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO7); + aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO8); + aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO9); + } + else + { + aResources.emplace_back(SV_RESID_BITMAP_CHECK1); + aResources.emplace_back(SV_RESID_BITMAP_CHECK2); + aResources.emplace_back(SV_RESID_BITMAP_CHECK3); + aResources.emplace_back(SV_RESID_BITMAP_CHECK4); + aResources.emplace_back(SV_RESID_BITMAP_CHECK5); + aResources.emplace_back(SV_RESID_BITMAP_CHECK6); + aResources.emplace_back(SV_RESID_BITMAP_CHECK7); + aResources.emplace_back(SV_RESID_BITMAP_CHECK8); + aResources.emplace_back(SV_RESID_BITMAP_CHECK9); + } + LoadThemedImageList(rStyleSettings, pSVData->maCtrlData.maCheckImgList, aResources); + pSVData->maCtrlData.mnCheckStyle = nStyle; + } + + sal_uInt16 nIndex; + if ( nFlags & DrawButtonFlags::Disabled ) + { + if ( nFlags & DrawButtonFlags::DontKnow ) + nIndex = 8; + else if ( nFlags & DrawButtonFlags::Checked ) + nIndex = 5; + else + nIndex = 4; + } + else if ( nFlags & DrawButtonFlags::Pressed ) + { + if ( nFlags & DrawButtonFlags::DontKnow ) + nIndex = 7; + else if ( nFlags & DrawButtonFlags::Checked ) + nIndex = 3; + else + nIndex = 2; + } + else + { + if ( nFlags & DrawButtonFlags::DontKnow ) + nIndex = 6; + else if ( nFlags & DrawButtonFlags::Checked ) + nIndex = 1; + else + nIndex = 0; + } + return pSVData->maCtrlData.maCheckImgList[nIndex]; +} + +void CheckBox::ImplAdjustNWFSizes() +{ + GetOutDev()->Push( vcl::PushFlags::MAPMODE ); + SetMapMode(MapMode(MapUnit::MapPixel)); + + ImplControlValue aControlValue; + Size aCurSize( GetSizePixel() ); + tools::Rectangle aCtrlRegion( Point( 0, 0 ), aCurSize ); + tools::Rectangle aBoundingRgn, aContentRgn; + + // get native size of a radiobutton + if( GetNativeControlRegion( ControlType::Checkbox, ControlPart::Entire, aCtrlRegion, + ControlState::DEFAULT|ControlState::ENABLED, aControlValue, + aBoundingRgn, aContentRgn ) ) + { + Size aSize = aContentRgn.GetSize(); + + if( aSize.Height() > aCurSize.Height() ) + { + aCurSize.setHeight( aSize.Height() ); + SetSizePixel( aCurSize ); + } + } + + GetOutDev()->Pop(); +} + +Size CheckBox::CalcMinimumSize( tools::Long nMaxWidth ) const +{ + Size aSize = ImplGetCheckImageSize(); + nMaxWidth -= aSize.Width(); + + OUString aText = GetText(); + if (!aText.isEmpty()) + { + // subtract what will be added later + nMaxWidth-=2; + nMaxWidth -= ImplGetImageToTextDistance(); + + Size aTextSize = GetTextRect( tools::Rectangle( Point(), Size( nMaxWidth > 0 ? nMaxWidth : 0x7fffffff, 0x7fffffff ) ), + aText, FixedText::ImplGetTextStyle( GetStyle() ) ).GetSize(); + aSize.AdjustWidth(2 ); // for focus rect + aSize.AdjustWidth(ImplGetImageToTextDistance() ); + aSize.AdjustWidth(aTextSize.Width() ); + if ( aSize.Height() < aTextSize.Height() ) + aSize.setHeight( aTextSize.Height() ); + } + else + { + // is this still correct ? since the checkbox now + // shows a focus rect it should be 2 pixels wider and longer +/* since otherwise the controls in the Writer hang too far up + aSize.Width() += 2; + aSize.Height() += 2; +*/ + } + + return CalcWindowSize( aSize ); +} + +Size CheckBox::GetOptimalSize() const +{ + int nWidthRequest(get_width_request()); + return CalcMinimumSize(nWidthRequest != -1 ? nWidthRequest : 0); +} + +void CheckBox::ShowFocus(const tools::Rectangle& rRect) +{ + if (IsNativeControlSupported(ControlType::Checkbox, ControlPart::Focus)) + { + ImplControlValue aControlValue; + tools::Rectangle aInRect(Point(0, 0), GetSizePixel()); + + aInRect.SetLeft( rRect.Left() ); // exclude the checkbox itself from the focusrect + + GetOutDev()->DrawNativeControl(ControlType::Checkbox, ControlPart::Focus, aInRect, + ControlState::FOCUSED, aControlValue, OUString()); + } + Button::ShowFocus(rRect); +} + +void CheckBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + Button::DumpAsPropertyTree(rJsonWriter); + rJsonWriter.put("checked", IsChecked()); +} + +FactoryFunction CheckBox::GetUITestFactory() const +{ + return CheckBoxUIObject::create; +} + +ImageButton::ImageButton( vcl::Window* pParent, WinBits nStyle ) : + PushButton( pParent, nStyle ) +{ + ImplInitStyle(); +} + +void ImageButton::ImplInitStyle() +{ + WinBits nStyle = GetStyle(); + + if ( ! ( nStyle & ( WB_RIGHT | WB_LEFT ) ) ) + nStyle |= WB_CENTER; + + if ( ! ( nStyle & ( WB_TOP | WB_BOTTOM ) ) ) + nStyle |= WB_VCENTER; + + SetStyle( nStyle ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |