office-gobmx/vcl/qt5/QtGraphics_Controls.cxx
Michael Weghorn bf4ec3b2be related tdf#160176 qt: Create dummy Qt widget in main thread
Always run `QtGraphics_Controls::getNativeControlRegion`
in the main thread, as it may create a dummy `QLineEdit`
(see the `ControlType::MultilineEditbox` and
`ControlType::Editbox` cases) and creating Qt widgets
is only allowed in the main thread.

Without the following scenario runs into an assert
with a current Qt 6 dev debug build when using the
qt6 VCL plugin:

1) open sample document attachment 193089
   from tdf#160176
2) enable Form Design Toolbar
3) switch to Design mode
4) select label next to "Labelfield2" label
5) right-click, "Control Properties"
6) type anything for the help text and tab
   to the next UI element

Backtrace:

    ASSERT failure in QWidget: "Widgets must be created in the GUI thread.", file /home/michi/development/git/qt5/qtbase/src/widgets/kernel/qwidget.cpp, line 956

    Thread 45 "browserlistbox" received signal SIGABRT, Aborted.
    [Switching to Thread 0x7fff8d08d6c0 (LWP 203558)]
    __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at ./nptl/pthread_kill.c:44
    44      ./nptl/pthread_kill.c: No such file or directory.
    (gdb) bt
    #0  __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at ./nptl/pthread_kill.c:44
    #1  0x00007ffff78a81cf in __pthread_kill_internal (signo=6, threadid=<optimized out>) at ./nptl/pthread_kill.c:78
    #2  0x00007ffff785a472 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
    #3  0x00007ffff78444b2 in __GI_abort () at ./stdlib/abort.c:79
    #4  0x00007fffe32f8540 in qAbort() () at /home/michi/development/git/qt5/qtbase/src/corelib/global/qassert.cpp:49
    #5  0x00007fffe330c393 in qt_message_fatal<QString&>(QtMsgType, QMessageLogContext const&, QString&) (context=..., message=...) at /home/michi/development/git/qt5/qtbase/src/corelib/global/qlogging.cpp:2056
    #6  0x00007fffe3305344 in qt_message(QtMsgType, const QMessageLogContext &, const char *, typedef __va_list_tag __va_list_tag *)
        (msgType=QtFatalMsg, context=..., msg=0x7fffe3981990 "ASSERT failure in %s: \"%s\", file %s, line %d", ap=0x7fff8d086178) at /home/michi/development/git/qt5/qtbase/src/corelib/global/qlogging.cpp:374
    #7  0x00007fffe330740f in QMessageLogger::fatal(char const*, ...) const (this=0x7fff8d0863c0, msg=0x7fffe3981990 "ASSERT failure in %s: \"%s\", file %s, line %d")
        at /home/michi/development/git/qt5/qtbase/src/corelib/global/qlogging.cpp:874
    #8  0x00007fffe32f8613 in qt_assert_x(char const*, char const*, char const*, int)
        (where=0x7fffe1becdf3 "QWidget", what=0x7fffe1becdc8 "Widgets must be created in the GUI thread.", file=0x7fffe1becca0 "/home/michi/development/git/qt5/qtbase/src/widgets/kernel/qwidget.cpp", line=956)
        at /home/michi/development/git/qt5/qtbase/src/corelib/global/qassert.cpp:114
    #9  0x00007fffe144118c in QWidgetPrivate::init(QWidget*, QFlags<Qt::WindowType>) (this=0x7fff780054d0, parentWidget=0x0, f=...) at /home/michi/development/git/qt5/qtbase/src/widgets/kernel/qwidget.cpp:956
    #10 0x00007fffe1440ca1 in QWidget::QWidget(QWidgetPrivate&, QWidget*, QFlags<Qt::WindowType>) (this=0x7fff8d086d00, dd=..., parent=0x0, f=...) at /home/michi/development/git/qt5/qtbase/src/widgets/kernel/qwidget.cpp:872
    #11 0x00007fffe16a7d22 in QLineEdit::QLineEdit(QString const&, QWidget*) (this=0x7fff8d086d00, contents=..., parent=0x0) at /home/michi/development/git/qt5/qtbase/src/widgets/widgets/qlineedit.cpp:253
    #12 0x00007fffe16a7c98 in QLineEdit::QLineEdit(QWidget*) (this=0x7fff8d086d00, parent=0x0) at /home/michi/development/git/qt5/qtbase/src/widgets/widgets/qlineedit.cpp:239
    #13 0x00007fffe404aebc in QtGraphics_Controls::getNativeControlRegion(ControlType, ControlPart, tools::Rectangle const&, ControlState, ImplControlValue const&, rtl::OUString const&, tools::Rectangle&, tools::Rectangle&)
        (this=0x55555cbb13e0, type=ControlType::Editbox, part=ControlPart::Entire, controlRegion=..., controlState=(ControlState::ENABLED | ControlState::ROLLOVER), val=..., nativeBoundingRegion=..., nativeContentRegion=...)
        at vcl/qt6/../qt5/QtGraphics_Controls.cxx:784
    #14 0x00007fffee72945e in SalGraphics::GetNativeControlRegion(ControlType, ControlPart, tools::Rectangle const&, ControlState, ImplControlValue const&, tools::Rectangle&, tools::Rectangle&, OutputDevice const&)
        (this=0x55555eb28920, nType=ControlType::Editbox, nPart=ControlPart::Entire, rControlRegion=..., nState=(ControlState::ENABLED | ControlState::ROLLOVER), aValue=..., rNativeBoundingRegion=..., rNativeContentRegion=..., rOutDev=...)
        at /home/michi/development/git/libreoffice/vcl/source/gdi/salgdilayout.cxx:834
    #15 0x00007fffee3a7195 in OutputDevice::GetNativeControlRegion(ControlType, ControlPart, tools::Rectangle const&, ControlState, ImplControlValue const&, tools::Rectangle&, tools::Rectangle&) const
        (this=0x5555607cd870, nType=ControlType::Editbox, nPart=ControlPart::Entire, rControlRegion=..., nState=(ControlState::ENABLED | ControlState::ROLLOVER), aValue=..., rNativeBoundingRegion=..., rNativeContentRegion=...)
        at /home/michi/development/git/libreoffice/vcl/source/outdev/nativecontrols.cxx:313
    #16 0x00007fffede63b9d in ImplSmallBorderWindowView::DrawWindow(OutputDevice&, Point const*) (this=0x5555607c7c10, rRenderContext=...) at /home/michi/development/git/libreoffice/vcl/source/window/brdwin.cxx:699
    #17 0x00007fffede69900 in ImplBorderWindow::Paint(OutputDevice&, tools::Rectangle const&) (this=0x5555607c7ca0, rRenderContext=...) at /home/michi/development/git/libreoffice/vcl/source/window/brdwin.cxx:1628
    #18 0x00007fffede3e241 in PaintHelper::DoPaint(vcl::Region const*) (this=0x7fff8d088188, pRegion=0x7fff78006480) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:313
    #19 0x00007fffede3feff in vcl::Window::ImplCallPaint(vcl::Region const*, ImplPaintFlags)
        (this=0x5555607c7ca0, pRegion=0x7fff78006480, nPaintFlags=(ImplPaintFlags::PaintAllChildren | ImplPaintFlags::PaintChildren | ImplPaintFlags::Erase)) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:617
    #20 0x00007fffede3f791 in PaintHelper::~PaintHelper() (this=0x7fff8d0883c8) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:552
    #21 0x00007fffede3ff86 in vcl::Window::ImplCallPaint(vcl::Region const*, ImplPaintFlags)
        (this=0x55556002ead0, pRegion=0x7fff78005ec0, nPaintFlags=(ImplPaintFlags::PaintAllChildren | ImplPaintFlags::PaintChildren | ImplPaintFlags::Erase)) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:623
    #22 0x00007fffede3f791 in PaintHelper::~PaintHelper() (this=0x7fff8d088608) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:552
    #23 0x00007fffede3ff86 in vcl::Window::ImplCallPaint(vcl::Region const*, ImplPaintFlags)
        (this=0x55555edac0e0, pRegion=0x7fff78005dc0, nPaintFlags=(ImplPaintFlags::PaintAllChildren | ImplPaintFlags::PaintChildren | ImplPaintFlags::Erase)) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:623
    #24 0x00007fffede3f791 in PaintHelper::~PaintHelper() (this=0x7fff8d088848) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:552
    #25 0x00007fffede3ff86 in vcl::Window::ImplCallPaint(vcl::Region const*, ImplPaintFlags)
        (this=0x55555edab480, pRegion=0x7fff78005d80, nPaintFlags=(ImplPaintFlags::PaintAllChildren | ImplPaintFlags::PaintChildren | ImplPaintFlags::Erase)) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:623
    #26 0x00007fffede3f791 in PaintHelper::~PaintHelper() (this=0x7fff8d088a88) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:552
    #27 0x00007fffede3ff86 in vcl::Window::ImplCallPaint(vcl::Region const*, ImplPaintFlags)
        (this=0x55555edaa900, pRegion=0x7fff78004c40, nPaintFlags=(ImplPaintFlags::PaintAllChildren | ImplPaintFlags::PaintChildren | ImplPaintFlags::Erase)) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:623
    #28 0x00007fffede3f791 in PaintHelper::~PaintHelper() (this=0x7fff8d088cc8) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:552
    #29 0x00007fffede3ff86 in vcl::Window::ImplCallPaint(vcl::Region const*, ImplPaintFlags)
        (this=0x55555ed29400, pRegion=0x7fff78004c00, nPaintFlags=(ImplPaintFlags::PaintAllChildren | ImplPaintFlags::PaintChildren | ImplPaintFlags::Erase)) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:623
    #30 0x00007fffede3f791 in PaintHelper::~PaintHelper() (this=0x7fff8d088f08) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:552
    #31 0x00007fffede3ff86 in vcl::Window::ImplCallPaint(vcl::Region const*, ImplPaintFlags)
        (this=0x55555eccc910, pRegion=0x7fff78005d40, nPaintFlags=(ImplPaintFlags::PaintAllChildren | ImplPaintFlags::PaintChildren | ImplPaintFlags::Erase)) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:623
    #32 0x00007fffede3f791 in PaintHelper::~PaintHelper() (this=0x7fff8d089148) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:552
    #33 0x00007fffede3ff86 in vcl::Window::ImplCallPaint(vcl::Region const*, ImplPaintFlags)
        (this=0x55555eda7a70, pRegion=0x7fff78004ac0, nPaintFlags=(ImplPaintFlags::PaintAllChildren | ImplPaintFlags::PaintChildren | ImplPaintFlags::Erase)) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:623
    --Type <RET> for more, q to quit, c to continue without paging--
    #34 0x00007fffede3f791 in PaintHelper::~PaintHelper() (this=0x7fff8d089388) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:552
    #35 0x00007fffede3ff86 in vcl::Window::ImplCallPaint(vcl::Region const*, ImplPaintFlags)
        (this=0x55555eda2210, pRegion=0x7fff78001230, nPaintFlags=(ImplPaintFlags::PaintAllChildren | ImplPaintFlags::PaintChildren | ImplPaintFlags::Erase)) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:623
    #36 0x00007fffede3f791 in PaintHelper::~PaintHelper() (this=0x7fff8d0895c8) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:552
    #37 0x00007fffede3ff86 in vcl::Window::ImplCallPaint(vcl::Region const*, ImplPaintFlags)
        (this=0x55555ecd5d20, pRegion=0x7fff78002510, nPaintFlags=(ImplPaintFlags::PaintAllChildren | ImplPaintFlags::PaintChildren | ImplPaintFlags::Erase)) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:623
    #38 0x00007fffede3f791 in PaintHelper::~PaintHelper() (this=0x7fff8d089808) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:552
    #39 0x00007fffede3ff86 in vcl::Window::ImplCallPaint(vcl::Region const*, ImplPaintFlags)
        (this=0x55555ecdae90, pRegion=0x7fff78001ae0, nPaintFlags=(ImplPaintFlags::PaintAllChildren | ImplPaintFlags::PaintChildren | ImplPaintFlags::Erase)) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:623
    #40 0x00007fffede3f791 in PaintHelper::~PaintHelper() (this=0x7fff8d089a48) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:552
    #41 0x00007fffede3ff86 in vcl::Window::ImplCallPaint(vcl::Region const*, ImplPaintFlags)
        (this=0x55555bb5ddb0, pRegion=0x55555eb4e340, nPaintFlags=(ImplPaintFlags::PaintAllChildren | ImplPaintFlags::PaintChildren | ImplPaintFlags::Erase)) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:623
    #42 0x00007fffede3f791 in PaintHelper::~PaintHelper() (this=0x7fff8d089c88) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:552
    #43 0x00007fffede3ff86 in vcl::Window::ImplCallPaint(vcl::Region const*, ImplPaintFlags)
        (this=0x55555ed5c6e0, pRegion=0x7fff78001310, nPaintFlags=(ImplPaintFlags::PaintAllChildren | ImplPaintFlags::PaintChildren | ImplPaintFlags::Erase)) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:623
    #44 0x00007fffede3f791 in PaintHelper::~PaintHelper() (this=0x7fff8d089ec8) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:552
    #45 0x00007fffede3ff86 in vcl::Window::ImplCallPaint(vcl::Region const*, ImplPaintFlags) (this=0x55555eb3aa10, pRegion=0x0, nPaintFlags=(ImplPaintFlags::PaintAllChildren | ImplPaintFlags::PaintChildren | ImplPaintFlags::Erase))
        at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:623
    #46 0x00007fffede41a32 in vcl::Window::PaintImmediately() (this=0x5555607c7ca0) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:1340
    #47 0x00007fffede415a0 in vcl::Window::PaintImmediately() (this=0x5555607c49b0) at /home/michi/development/git/libreoffice/vcl/source/window/paint.cxx:1280
    #48 0x00007fffee17f7ac in Edit::ImplInvalidateOrRepaint() (this=0x5555607c49b0) at /home/michi/development/git/libreoffice/vcl/source/control/edit.cxx:451
    #49 0x00007fffee182a52 in Edit::ImplAlignAndPaint() (this=0x5555607c49b0) at /home/michi/development/git/libreoffice/vcl/source/control/edit.cxx:1170
    #50 0x00007fffee183ef0 in Edit::ImplInsertText(rtl::OUString const&, Selection const*, bool) (this=0x5555607c49b0, rStr="ee", pNewSel=0x7fff8d08a6b8, bIsUserInput=false)
        at /home/michi/development/git/libreoffice/vcl/source/control/edit.cxx:885
    #51 0x00007fffee1841ca in Edit::ImplSetText(rtl::OUString const&, Selection const*) (this=0x5555607c49b0, rText="ee", pNewSelection=0x7fff8d08a6b8) at /home/michi/development/git/libreoffice/vcl/source/control/edit.cxx:918
    #52 0x00007fffee18cfd4 in Edit::SetText(rtl::OUString const&) (this=0x5555607c49b0, rStr="ee") at /home/michi/development/git/libreoffice/vcl/source/control/edit.cxx:2560
    #53 0x00007fffee8d974a in SalInstanceEntry::set_text(rtl::OUString const&) (this=0x5555607c4200, rText="ee") at /home/michi/development/git/libreoffice/vcl/source/app/salvtables.cxx:3383
    #54 0x00007fff7ef6054a in pcr::OEditControl::setValue(com::sun::uno::Any const&) (this=0x5555607c7660, _rValue=uno::Any("string": "ee"))
        at /home/michi/development/git/libreoffice/extensions/source/propctrlr/standardcontrol.cxx:200
    #55 0x00007fff7ed96819 in pcr::OBrowserListBox::impl_setControlAsPropertyValue(pcr::ListBoxLine const&, com::sun::uno::Any const&) (_rLine=..., _rPropertyValue=uno::Any("string": "ee"))
        at /home/michi/development/git/libreoffice/extensions/source/propctrlr/browserlistbox.cxx:551
    #56 0x00007fff7ed96485 in pcr::OBrowserListBox::SetPropertyValue(rtl::OUString const&, com::sun::uno::Any const&, bool) (this=0x55555eda22b0, _rEntryName="HelpText", _rValue=uno::Any("string": "ee"), _bUnknownValue=false)
        at /home/michi/development/git/libreoffice/extensions/source/propctrlr/browserlistbox.cxx:403
    #57 0x00007fff7ef432e8 in pcr::OPropertyEditor::SetPropertyValue(rtl::OUString const&, com::sun::uno::Any const&, bool) (this=0x55555ecdb3e0, rEntryName="HelpText", _rValue=uno::Any("string": "ee"), _bUnknownValue=false)
        at /home/michi/development/git/libreoffice/extensions/source/propctrlr/propertyeditor.cxx:277
    #58 0x00007fff7ef01d4f in pcr::OPropertyBrowserController::Commit(rtl::OUString const&, com::sun::uno::Any const&) (this=0x55555c5f1b40, rName="HelpText", _rValue=uno::Any("string": "ee"))
        at /home/michi/development/git/libreoffice/extensions/source/propctrlr/propcontroller.cxx:1337
    #59 0x00007fff7ed954ae in pcr::OBrowserListBox::valueChanged(com::sun::uno::Reference<com::sun::inspection::XPropertyControl> const&) (this=0x55555eda22b0, _rxControl=uno::Reference to (pcr::OEditControl *) 0x5555607c76b0)
        at /home/michi/development/git/libreoffice/extensions/source/propctrlr/browserlistbox.cxx:637
    #60 0x00007fff7ed95268 in pcr::PropertyControlContext_Impl::impl_processEvent_throw(comphelper::AnyEvent const&) (this=0x55555ed29270, _rEvent=...)
        at /home/michi/development/git/libreoffice/extensions/source/propctrlr/browserlistbox.cxx:291
    #61 0x00007fff7ed950b9 in pcr::PropertyControlContext_Impl::processEvent(comphelper::AnyEvent const&) (this=0x55555ed29270, _rEvent=...) at /home/michi/development/git/libreoffice/extensions/source/propctrlr/browserlistbox.cxx:272
    #62 0x00007ffff6118a2e in comphelper::AsyncEventNotifierBase::execute() (this=0x5555568d6620) at /home/michi/development/git/libreoffice/comphelper/source/misc/asyncnotification.cxx:139
    #63 0x00007ffff6119165 in comphelper::AsyncEventNotifier::execute() (this=0x5555568d6620) at /home/michi/development/git/libreoffice/comphelper/source/misc/asyncnotification.cxx:156
    #64 0x00007ffff7d0f6ec in salhelper::Thread::run() (this=0x5555568d6630) at /home/michi/development/git/libreoffice/salhelper/source/thread.cxx:39
    #65 0x00007ffff7d0ff2e in threadFunc(void*) (param=0x5555568d6640) at include/osl/thread.hxx:189
    #66 0x00007ffff7fa4b2d in osl_thread_start_Impl(void*) (pData=0x55555ecc3e40) at /home/michi/development/git/libreoffice/sal/osl/unx/thread.cxx:237
    #67 0x00007ffff78a645c in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:444
    #68 0x00007ffff7926bbc in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81

Change-Id: I9e0b0d973997e3f4f50a9a31e9692325664fd262
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/165995
Tested-by: Jenkins
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
2024-04-11 23:54:14 +02:00

1179 lines
50 KiB
C++

/* -*- 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 <QtGraphics_Controls.hxx>
#include <QtGui/QPainter>
#include <QtWidgets/QApplication>
#include <QtWidgets/QFrame>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include <QtInstance.hxx>
#include <QtTools.hxx>
#include <QtGraphicsBase.hxx>
#include <vcl/decoview.hxx>
/**
Conversion function between VCL ControlState together with
ImplControlValue and Qt state flags.
@param nControlState State of the widget (default, focused, ...) in Native Widget Framework.
@param aValue Value held by the widget (on, off, ...)
*/
static QStyle::State vclStateValue2StateFlag(ControlState nControlState,
const ImplControlValue& aValue)
{
QStyle::State nState
= ((nControlState & ControlState::ENABLED) ? QStyle::State_Enabled : QStyle::State_None)
| ((nControlState & ControlState::FOCUSED)
? QStyle::State_HasFocus | QStyle::State_KeyboardFocusChange
: QStyle::State_None)
| ((nControlState & ControlState::PRESSED) ? QStyle::State_Sunken : QStyle::State_None)
| ((nControlState & ControlState::SELECTED) ? QStyle::State_Selected : QStyle::State_None)
| ((nControlState & ControlState::ROLLOVER) ? QStyle::State_MouseOver
: QStyle::State_None);
switch (aValue.getTristateVal())
{
case ButtonValue::On:
nState |= QStyle::State_On;
break;
case ButtonValue::Off:
nState |= QStyle::State_Off;
break;
case ButtonValue::Mixed:
nState |= QStyle::State_NoChange;
break;
default:
break;
}
return nState;
}
static void lcl_ApplyBackgroundColorToStyleOption(QStyleOption& rOption,
const Color& rBackgroundColor)
{
if (rBackgroundColor != COL_AUTO)
{
QColor aColor = toQColor(rBackgroundColor);
for (QPalette::ColorRole role : { QPalette::Window, QPalette::Button, QPalette::Base })
rOption.palette.setColor(role, aColor);
}
}
QtGraphics_Controls::QtGraphics_Controls(const QtGraphicsBase& rGraphics)
: m_rGraphics(rGraphics)
{
}
bool QtGraphics_Controls::isNativeControlSupported(ControlType type, ControlPart part)
{
switch (type)
{
case ControlType::Tooltip:
case ControlType::Progress:
case ControlType::ListNode:
return (part == ControlPart::Entire);
case ControlType::Pushbutton:
case ControlType::Radiobutton:
case ControlType::Checkbox:
return (part == ControlPart::Entire) || (part == ControlPart::Focus);
case ControlType::ListHeader:
return (part == ControlPart::Button);
case ControlType::Menubar:
case ControlType::MenuPopup:
case ControlType::Editbox:
case ControlType::MultilineEditbox:
case ControlType::Combobox:
case ControlType::Toolbar:
case ControlType::Frame:
case ControlType::Scrollbar:
case ControlType::WindowBackground:
case ControlType::Fixedline:
return true;
case ControlType::Listbox:
return (part == ControlPart::Entire || part == ControlPart::HasBackgroundTexture);
case ControlType::Spinbox:
return (part == ControlPart::Entire || part == ControlPart::HasBackgroundTexture);
case ControlType::Slider:
return (part == ControlPart::TrackHorzArea || part == ControlPart::TrackVertArea);
case ControlType::TabItem:
case ControlType::TabPane:
return ((part == ControlPart::Entire) || part == ControlPart::TabPaneWithHeader);
default:
break;
}
return false;
}
inline int QtGraphics_Controls::pixelMetric(QStyle::PixelMetric metric, const QStyleOption* option,
const QWidget* pWidget)
{
return QApplication::style()->pixelMetric(metric, option, pWidget);
}
inline QSize QtGraphics_Controls::sizeFromContents(QStyle::ContentsType type,
const QStyleOption* option,
const QSize& contentsSize)
{
return QApplication::style()->sizeFromContents(type, option, contentsSize);
}
inline QRect QtGraphics_Controls::subControlRect(QStyle::ComplexControl control,
const QStyleOptionComplex* option,
QStyle::SubControl subControl)
{
return QApplication::style()->subControlRect(control, option, subControl);
}
inline QRect QtGraphics_Controls::subElementRect(QStyle::SubElement element,
const QStyleOption* option)
{
return QApplication::style()->subElementRect(element, option);
}
void QtGraphics_Controls::draw(QStyle::ControlElement element, QStyleOption& rOption, QImage* image,
const Color& rBackgroundColor, QStyle::State const state, QRect rect)
{
const QRect& targetRect = !rect.isNull() ? rect : image->rect();
rOption.state |= state;
rOption.rect = downscale(targetRect);
lcl_ApplyBackgroundColorToStyleOption(rOption, rBackgroundColor);
QPainter painter(image);
QApplication::style()->drawControl(element, &rOption, &painter);
}
void QtGraphics_Controls::draw(QStyle::PrimitiveElement element, QStyleOption& rOption,
QImage* image, const Color& rBackgroundColor,
QStyle::State const state, QRect rect)
{
const QRect& targetRect = !rect.isNull() ? rect : image->rect();
rOption.state |= state;
rOption.rect = downscale(targetRect);
lcl_ApplyBackgroundColorToStyleOption(rOption, rBackgroundColor);
QPainter painter(image);
QApplication::style()->drawPrimitive(element, &rOption, &painter);
}
void QtGraphics_Controls::draw(QStyle::ComplexControl element, QStyleOptionComplex& rOption,
QImage* image, const Color& rBackgroundColor,
QStyle::State const state)
{
const QRect& targetRect = image->rect();
rOption.state |= state;
rOption.rect = downscale(targetRect);
lcl_ApplyBackgroundColorToStyleOption(rOption, rBackgroundColor);
QPainter painter(image);
QApplication::style()->drawComplexControl(element, &rOption, &painter);
}
void QtGraphics_Controls::drawFrame(QStyle::PrimitiveElement element, QImage* image,
const Color& rBackgroundColor, QStyle::State const& state,
bool bClip, QStyle::PixelMetric eLineMetric)
{
const int fw = pixelMetric(eLineMetric);
QStyleOptionFrame option;
option.frameShape = QFrame::StyledPanel;
option.state = QStyle::State_Sunken | state;
option.lineWidth = fw;
QRect aRect = downscale(image->rect());
option.rect = aRect;
lcl_ApplyBackgroundColorToStyleOption(option, rBackgroundColor);
QPainter painter(image);
if (bClip)
painter.setClipRegion(QRegion(aRect).subtracted(aRect.adjusted(fw, fw, -fw, -fw)));
QApplication::style()->drawPrimitive(element, &option, &painter);
}
void QtGraphics_Controls::fillQStyleOptionTab(const ImplControlValue& value, QStyleOptionTab& sot)
{
const TabitemValue& rValue = static_cast<const TabitemValue&>(value);
if (rValue.isFirst())
sot.position = rValue.isLast() ? QStyleOptionTab::OnlyOneTab : QStyleOptionTab::Beginning;
else if (rValue.isLast())
sot.position = rValue.isFirst() ? QStyleOptionTab::OnlyOneTab : QStyleOptionTab::End;
else
sot.position = QStyleOptionTab::Middle;
}
void QtGraphics_Controls::fullQStyleOptionTabWidgetFrame(QStyleOptionTabWidgetFrame& option,
bool bDownscale)
{
option.state = QStyle::State_Enabled;
option.rightCornerWidgetSize = QSize(0, 0);
option.leftCornerWidgetSize = QSize(0, 0);
int nLineWidth = pixelMetric(QStyle::PM_DefaultFrameWidth);
option.lineWidth = bDownscale ? std::max(1, downscale(nLineWidth, Round::Ceil)) : nLineWidth;
option.midLineWidth = 0;
option.shape = QTabBar::RoundedNorth;
}
bool QtGraphics_Controls::drawNativeControl(ControlType type, ControlPart part,
const tools::Rectangle& rControlRegion,
ControlState nControlState,
const ImplControlValue& value, const OUString&,
const Color& rBackgroundColor)
{
bool nativeSupport = isNativeControlSupported(type, part);
if (!nativeSupport)
{
assert(!nativeSupport && "drawNativeControl called without native support!");
return false;
}
if (m_lastPopupRect.isValid()
&& (type != ControlType::MenuPopup || part != ControlPart::MenuItem))
m_lastPopupRect = QRect();
bool returnVal = true;
QRect widgetRect = toQRect(rControlRegion);
//if no image, or resized, make a new image
if (!m_image || m_image->size() != widgetRect.size())
{
m_image.reset(new QImage(widgetRect.width(), widgetRect.height(),
QImage::Format_ARGB32_Premultiplied));
m_image->setDevicePixelRatio(m_rGraphics.devicePixelRatioF());
}
// Default image color - just once
switch (type)
{
case ControlType::MenuPopup:
if (part == ControlPart::MenuItemCheckMark || part == ControlPart::MenuItemRadioMark)
{
// it is necessary to fill the background transparently first, as this
// is painted after menuitem highlight, otherwise there would be a grey area
m_image->fill(Qt::transparent);
break;
}
[[fallthrough]]; // QPalette::Window
case ControlType::Menubar:
case ControlType::WindowBackground:
m_image->fill(QApplication::palette().color(QPalette::Window).rgb());
break;
case ControlType::Tooltip:
m_image->fill(QApplication::palette().color(QPalette::ToolTipBase).rgb());
break;
case ControlType::Scrollbar:
if ((part == ControlPart::DrawBackgroundVert)
|| (part == ControlPart::DrawBackgroundHorz))
{
m_image->fill(QApplication::palette().color(QPalette::Window).rgb());
break;
}
[[fallthrough]]; // Qt::transparent
default:
m_image->fill(Qt::transparent);
break;
}
if (type == ControlType::Pushbutton)
{
const PushButtonValue& rPBValue = static_cast<const PushButtonValue&>(value);
if (part == ControlPart::Focus)
// Nothing to do. Drawing focus separately is not needed because that's
// already handled by the ControlState::FOCUSED state being set when
// drawing the entire control
return true;
assert(part == ControlPart::Entire);
QStyleOptionButton option;
if (nControlState & ControlState::DEFAULT)
option.features |= QStyleOptionButton::DefaultButton;
if (rPBValue.m_bFlatButton)
option.features |= QStyleOptionButton::Flat;
draw(QStyle::CE_PushButton, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
}
else if (type == ControlType::Menubar)
{
if (part == ControlPart::MenuItem)
{
QStyleOptionMenuItem option;
option.state = vclStateValue2StateFlag(nControlState, value);
if ((nControlState & ControlState::ROLLOVER)
&& QApplication::style()->styleHint(QStyle::SH_MenuBar_MouseTracking))
option.state |= QStyle::State_Selected;
if (nControlState
& ControlState::SELECTED) // Passing State_Sunken is currently not documented.
option.state |= QStyle::State_Sunken; // But some kinds of QStyle interpret it.
draw(QStyle::CE_MenuBarItem, option, m_image.get(), rBackgroundColor);
}
else if (part == ControlPart::Entire)
{
QStyleOptionMenuItem option;
draw(QStyle::CE_MenuBarEmptyArea, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
}
else
{
returnVal = false;
}
}
else if (type == ControlType::MenuPopup)
{
assert(part == ControlPart::MenuItem ? m_lastPopupRect.isValid()
: !m_lastPopupRect.isValid());
if (part == ControlPart::MenuItem)
{
QStyleOptionMenuItem option;
draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
// HACK: LO core first paints the entire popup and only then it paints menu items,
// but QMenu::paintEvent() paints popup frame after all items. That means highlighted
// items here would paint the highlight over the frame border. Since calls to ControlPart::MenuItem
// are always preceded by calls to ControlPart::Entire, just remember the size for the whole
// popup (otherwise not possible to get here) and draw the border afterwards.
QRect framerect(m_lastPopupRect.topLeft() - widgetRect.topLeft(),
widgetRect.size().expandedTo(m_lastPopupRect.size()));
QStyleOptionFrame frame;
draw(QStyle::PE_FrameMenu, frame, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value), framerect);
}
else if (part == ControlPart::Separator)
{
QStyleOptionMenuItem option;
option.menuItemType = QStyleOptionMenuItem::Separator;
// Painting the whole menu item area results in different background
// with at least Plastique style, so clip only to the separator itself
// (QSize( 2, 2 ) is hardcoded in Qt)
option.rect = m_image->rect();
QSize size = sizeFromContents(QStyle::CT_MenuItem, &option, QSize(2, 2));
QRect rect = m_image->rect();
QPoint center = rect.center();
rect.setHeight(size.height());
rect.moveCenter(center);
option.state |= vclStateValue2StateFlag(nControlState, value);
option.rect = rect;
QPainter painter(m_image.get());
// don't paint over popup frame border (like the hack above, but here it can be simpler)
const int fw = pixelMetric(QStyle::PM_MenuPanelWidth);
painter.setClipRect(rect.adjusted(fw, 0, -fw, 0));
QApplication::style()->drawControl(QStyle::CE_MenuItem, &option, &painter);
}
else if (part == ControlPart::MenuItemCheckMark || part == ControlPart::MenuItemRadioMark)
{
QStyleOptionMenuItem option;
option.checkType = (part == ControlPart::MenuItemCheckMark)
? QStyleOptionMenuItem::NonExclusive
: QStyleOptionMenuItem::Exclusive;
option.checked = bool(nControlState & ControlState::PRESSED);
// widgetRect is now the rectangle for the checkbox/radiobutton itself, but Qt
// paints the whole menu item, so translate position (and it'll be clipped);
// it is also necessary to fill the background transparently first, as this
// is painted after menuitem highlight, otherwise there would be a grey area
assert(value.getType() == ControlType::MenuPopup);
const MenupopupValue* menuVal = static_cast<const MenupopupValue*>(&value);
QRect menuItemRect(toQRect(menuVal->maItemRect));
QRect rect(menuItemRect.topLeft() - widgetRect.topLeft(),
widgetRect.size().expandedTo(menuItemRect.size()));
// checkboxes are always displayed next to images in menus, so are never centered
const int focus_size = pixelMetric(QStyle::PM_FocusFrameHMargin);
rect.moveTo(-focus_size, rect.y());
draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState & ~ControlState::PRESSED, value), rect);
}
else if (part == ControlPart::Entire)
{
QStyleOptionMenuItem option;
option.state = vclStateValue2StateFlag(nControlState, value);
draw(QStyle::PE_PanelMenu, option, m_image.get(), rBackgroundColor);
// Try hard to get any frame!
QStyleOptionFrame frame;
draw(QStyle::PE_FrameMenu, frame, m_image.get(), rBackgroundColor);
draw(QStyle::PE_FrameWindow, frame, m_image.get(), rBackgroundColor);
m_lastPopupRect = widgetRect;
}
else
returnVal = false;
}
else if ((type == ControlType::Toolbar) && (part == ControlPart::Button))
{
QStyleOptionToolButton option;
option.arrowType = Qt::NoArrow;
option.subControls = QStyle::SC_ToolButton;
option.state = vclStateValue2StateFlag(nControlState, value);
option.state |= QStyle::State_Raised | QStyle::State_Enabled | QStyle::State_AutoRaise;
draw(QStyle::CC_ToolButton, option, m_image.get(), rBackgroundColor);
}
else if ((type == ControlType::Toolbar) && (part == ControlPart::Entire))
{
QStyleOptionToolBar option;
draw(QStyle::CE_ToolBar, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
}
else if ((type == ControlType::Toolbar)
&& (part == ControlPart::ThumbVert || part == ControlPart::ThumbHorz))
{
// reduce paint area only to the handle area
const int handleExtend = pixelMetric(QStyle::PM_ToolBarHandleExtent);
QStyleOption option;
QRect aRect = m_image->rect();
if (part == ControlPart::ThumbVert)
{
aRect.setWidth(handleExtend);
option.state = QStyle::State_Horizontal;
}
else
aRect.setHeight(handleExtend);
draw(QStyle::PE_IndicatorToolBarHandle, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value), aRect);
}
else if (type == ControlType::Editbox || type == ControlType::MultilineEditbox)
{
drawFrame(QStyle::PE_FrameLineEdit, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value), false);
}
else if (type == ControlType::Combobox)
{
QStyleOptionComboBox option;
option.editable = true;
draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
}
else if (type == ControlType::Listbox)
{
QStyleOptionComboBox option;
option.editable = false;
switch (part)
{
case ControlPart::ListboxWindow:
drawFrame(QStyle::PE_Frame, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value), true,
QStyle::PM_ComboBoxFrameWidth);
break;
case ControlPart::SubEdit:
draw(QStyle::CE_ComboBoxLabel, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
break;
case ControlPart::Entire:
draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
break;
case ControlPart::ButtonDown:
option.subControls = QStyle::SC_ComboBoxArrow;
draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
break;
default:
returnVal = false;
break;
}
}
else if (type == ControlType::ListNode)
{
QStyleOption option;
option.state = vclStateValue2StateFlag(nControlState, value);
option.state |= QStyle::State_Item | QStyle::State_Children;
if (value.getTristateVal() == ButtonValue::On)
option.state |= QStyle::State_Open;
draw(QStyle::PE_IndicatorBranch, option, m_image.get(), rBackgroundColor);
}
else if (type == ControlType::ListHeader)
{
QStyleOptionHeader option;
draw(QStyle::CE_HeaderSection, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
}
else if (type == ControlType::Checkbox)
{
if (part == ControlPart::Entire)
{
QStyleOptionButton option;
// clear FOCUSED bit, focus is drawn separately
nControlState &= ~ControlState::FOCUSED;
draw(QStyle::CE_CheckBox, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
}
else if (part == ControlPart::Focus)
{
QStyleOptionFocusRect option;
draw(QStyle::PE_FrameFocusRect, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
}
}
else if (type == ControlType::Scrollbar)
{
if ((part == ControlPart::DrawBackgroundVert) || (part == ControlPart::DrawBackgroundHorz))
{
QStyleOptionSlider option;
assert(value.getType() == ControlType::Scrollbar);
const ScrollbarValue* sbVal = static_cast<const ScrollbarValue*>(&value);
//if the scroll bar is active (aka not degenerate... allow for hover events)
if (sbVal->mnVisibleSize < sbVal->mnMax)
option.state = QStyle::State_MouseOver;
bool horizontal = (part == ControlPart::DrawBackgroundHorz); //horizontal or vertical
option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
if (horizontal)
option.state |= QStyle::State_Horizontal;
// If the scrollbar has a mnMin == 0 and mnMax == 0 then mnVisibleSize is set to -1?!
// I don't know if a negative mnVisibleSize makes any sense, so just handle this case
// without crashing LO with a SIGFPE in the Qt library.
const tools::Long nVisibleSize
= (sbVal->mnMin == sbVal->mnMax) ? 0 : sbVal->mnVisibleSize;
option.minimum = sbVal->mnMin;
option.maximum = sbVal->mnMax - nVisibleSize;
option.maximum = qMax(option.maximum, option.minimum); // bnc#619772
option.sliderValue = sbVal->mnCur;
option.sliderPosition = sbVal->mnCur;
option.pageStep = nVisibleSize;
if (part == ControlPart::DrawBackgroundHorz)
option.upsideDown
= (QGuiApplication::isRightToLeft()
&& sbVal->maButton1Rect.Left() < sbVal->maButton2Rect.Left())
|| (QGuiApplication::isLeftToRight()
&& sbVal->maButton1Rect.Left() > sbVal->maButton2Rect.Left());
//setup the active control... always the slider
if (sbVal->mnThumbState & ControlState::ROLLOVER)
option.activeSubControls = QStyle::SC_ScrollBarSlider;
draw(QStyle::CC_ScrollBar, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
}
else
{
returnVal = false;
}
}
else if (type == ControlType::Spinbox)
{
QStyleOptionSpinBox option;
option.frame = true;
// determine active control
if (value.getType() == ControlType::SpinButtons)
{
const SpinbuttonValue* pSpinVal = static_cast<const SpinbuttonValue*>(&value);
if (pSpinVal->mnUpperState & ControlState::PRESSED)
option.activeSubControls |= QStyle::SC_SpinBoxUp;
if (pSpinVal->mnLowerState & ControlState::PRESSED)
option.activeSubControls |= QStyle::SC_SpinBoxDown;
if (pSpinVal->mnUpperState & ControlState::ENABLED)
option.stepEnabled |= QAbstractSpinBox::StepUpEnabled;
if (pSpinVal->mnLowerState & ControlState::ENABLED)
option.stepEnabled |= QAbstractSpinBox::StepDownEnabled;
if (pSpinVal->mnUpperState & ControlState::ROLLOVER)
option.state = QStyle::State_MouseOver;
if (pSpinVal->mnLowerState & ControlState::ROLLOVER)
option.state = QStyle::State_MouseOver;
}
draw(QStyle::CC_SpinBox, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
}
else if (type == ControlType::Radiobutton)
{
if (part == ControlPart::Entire)
{
QStyleOptionButton option;
// clear FOCUSED bit, focus is drawn separately
nControlState &= ~ControlState::FOCUSED;
draw(QStyle::CE_RadioButton, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
}
else if (part == ControlPart::Focus)
{
QStyleOptionFocusRect option;
draw(QStyle::PE_FrameFocusRect, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
}
}
else if (type == ControlType::Tooltip)
{
QStyleOption option;
draw(QStyle::PE_PanelTipLabel, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
}
else if (type == ControlType::Frame)
{
drawFrame(QStyle::PE_Frame, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
}
else if (type == ControlType::WindowBackground)
{
// Nothing to do - see "Default image color" switch ^^
}
else if (type == ControlType::Fixedline)
{
QStyleOptionMenuItem option;
option.menuItemType = QStyleOptionMenuItem::Separator;
option.state = vclStateValue2StateFlag(nControlState, value);
option.state |= QStyle::State_Item;
draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor);
}
else if (type == ControlType::Slider
&& (part == ControlPart::TrackHorzArea || part == ControlPart::TrackVertArea))
{
assert(value.getType() == ControlType::Slider);
const SliderValue* slVal = static_cast<const SliderValue*>(&value);
QStyleOptionSlider option;
option.state = vclStateValue2StateFlag(nControlState, value);
option.maximum = slVal->mnMax;
option.minimum = slVal->mnMin;
option.sliderPosition = option.sliderValue = slVal->mnCur;
bool horizontal = (part == ControlPart::TrackHorzArea); //horizontal or vertical
option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
if (horizontal)
option.state |= QStyle::State_Horizontal;
draw(QStyle::CC_Slider, option, m_image.get(), rBackgroundColor);
}
else if (type == ControlType::Progress && part == ControlPart::Entire)
{
QStyleOptionProgressBar option;
option.minimum = 0;
option.maximum = widgetRect.width();
option.progress = value.getNumericVal();
draw(QStyle::CE_ProgressBar, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
}
else if (type == ControlType::TabItem && part == ControlPart::Entire)
{
QStyleOptionTab sot;
fillQStyleOptionTab(value, sot);
draw(QStyle::CE_TabBarTabShape, sot, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value));
}
else if (type == ControlType::TabPane && part == ControlPart::Entire)
{
const TabPaneValue& rValue = static_cast<const TabPaneValue&>(value);
// get the overlap size for the tabs, so they will overlap the frame
QStyleOptionTab tabOverlap;
tabOverlap.shape = QTabBar::RoundedNorth;
TabPaneValue::m_nOverlap = pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap);
QStyleOptionTabWidgetFrame option;
fullQStyleOptionTabWidgetFrame(option, false);
option.tabBarRect = toQRect(rValue.m_aTabHeaderRect);
option.selectedTabRect
= rValue.m_aSelectedTabRect.IsEmpty() ? QRect() : toQRect(rValue.m_aSelectedTabRect);
option.tabBarSize = toQSize(rValue.m_aTabHeaderRect.GetSize());
option.rect = m_image->rect();
QRect aRect = subElementRect(QStyle::SE_TabWidgetTabPane, &option);
draw(QStyle::PE_FrameTabWidget, option, m_image.get(), rBackgroundColor,
vclStateValue2StateFlag(nControlState, value), aRect);
}
else
{
returnVal = false;
}
return returnVal;
}
bool QtGraphics_Controls::getNativeControlRegion(
ControlType type, ControlPart part, const tools::Rectangle& controlRegion,
ControlState controlState, const ImplControlValue& val, const OUString& rCaption,
tools::Rectangle& nativeBoundingRegion, tools::Rectangle& nativeContentRegion)
{
QtInstance* pQtInstance(GetQtInstance());
assert(pQtInstance);
if (!pQtInstance->IsMainThread())
{
bool bRet;
pQtInstance->RunInMainThread([&]() {
bRet = getNativeControlRegion(type, part, controlRegion, controlState, val, rCaption,
nativeBoundingRegion, nativeContentRegion);
});
return bRet;
}
bool retVal = false;
QRect boundingRect = toQRect(controlRegion);
QRect contentRect = boundingRect;
QStyleOptionComplex styleOption;
switch (type)
{
// Metrics of the push button
case ControlType::Pushbutton:
if (part == ControlPart::Entire)
{
styleOption.state = vclStateValue2StateFlag(controlState, val);
if (controlState & ControlState::DEFAULT)
{
int size = upscale(pixelMetric(QStyle::PM_ButtonDefaultIndicator, &styleOption),
Round::Ceil);
boundingRect.adjust(-size, -size, size, size);
retVal = true;
}
}
else if (part == ControlPart::Focus)
retVal = true;
break;
case ControlType::Editbox:
case ControlType::MultilineEditbox:
{
// we have to get stable borders, otherwise layout loops.
// so we simply only scale the detected borders.
QStyleOptionFrame fo;
fo.frameShape = QFrame::StyledPanel;
fo.state = QStyle::State_Sunken;
fo.lineWidth = pixelMetric(QStyle::PM_DefaultFrameWidth);
fo.rect = downscale(contentRect);
fo.rect.setSize(sizeFromContents(QStyle::CT_LineEdit, &fo, fo.rect.size()));
QRect aSubRect = subElementRect(QStyle::SE_LineEditContents, &fo);
// VCL tests borders with small defaults before layout, where Qt returns no sub-rect,
// so this gets us at least some frame.
int nLine = upscale(fo.lineWidth, Round::Ceil);
int nLeft = qMin(-nLine, upscale(fo.rect.left() - aSubRect.left(), Round::Floor));
int nTop = qMin(-nLine, upscale(fo.rect.top() - aSubRect.top(), Round::Floor));
int nRight = qMax(nLine, upscale(fo.rect.right() - aSubRect.right(), Round::Ceil));
int nBottom = qMax(nLine, upscale(fo.rect.bottom() - aSubRect.bottom(), Round::Ceil));
boundingRect.adjust(nLeft, nTop, nRight, nBottom);
// tdf#150451: ensure a minimum size that fits text content + frame at top and bottom.
// Themes may use the widget type for determining the actual frame width to use,
// so pass a dummy QLineEdit
//
// NOTE: This is currently only done here for the minimum size calculation and
// not above because the handling for edit boxes here and in the calling code
// currently does all kinds of "interesting" things like doing extra size adjustments
// or passing the content rect where the bounding rect would be expected,...
// Ideally this should be cleaned up in the callers and all platform integrations
// to adhere to what the doc in vcl/inc/WidgetDrawInterface.hxx says, but this
// here keeps it working with existing code for now.
// (s.a. discussion in https://gerrit.libreoffice.org/c/core/+/146516 for more details)
QLineEdit aDummyEdit;
const int nFrameWidth = pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, &aDummyEdit);
QFontMetrics aFontMetrics(QApplication::font());
const int minHeight = upscale(aFontMetrics.height() + 2 * nFrameWidth, Round::Floor);
if (boundingRect.height() < minHeight)
{
const int nDiff = minHeight - boundingRect.height();
boundingRect.setHeight(boundingRect.height() + nDiff);
contentRect.setHeight(contentRect.height() + nDiff);
}
retVal = true;
break;
}
case ControlType::Checkbox:
if (part == ControlPart::Entire)
{
styleOption.state = vclStateValue2StateFlag(controlState, val);
int nWidth = pixelMetric(QStyle::PM_IndicatorWidth, &styleOption);
int nHeight = pixelMetric(QStyle::PM_IndicatorHeight, &styleOption);
contentRect.setSize(upscale(QSize(nWidth, nHeight), Round::Ceil));
int nHMargin = pixelMetric(QStyle::PM_FocusFrameHMargin, &styleOption);
int nVMargin = pixelMetric(QStyle::PM_FocusFrameVMargin, &styleOption);
contentRect.adjust(0, 0, 2 * upscale(nHMargin, Round::Ceil),
2 * upscale(nVMargin, Round::Ceil));
boundingRect = contentRect;
retVal = true;
}
break;
case ControlType::Combobox:
case ControlType::Listbox:
{
QStyleOptionComboBox cbo;
cbo.rect = downscale(QRect(0, 0, contentRect.width(), contentRect.height()));
cbo.state = vclStateValue2StateFlag(controlState, val);
switch (part)
{
case ControlPart::Entire:
{
// find out the minimum size that should be used
// assume contents is a text line
QSize aContentSize = downscale(contentRect.size(), Round::Ceil);
QFontMetrics aFontMetrics(QApplication::font());
aContentSize.setHeight(aFontMetrics.height());
QSize aMinSize = upscale(
sizeFromContents(QStyle::CT_ComboBox, &cbo, aContentSize), Round::Ceil);
if (aMinSize.height() > contentRect.height())
contentRect.setHeight(aMinSize.height());
boundingRect = contentRect;
retVal = true;
break;
}
case ControlPart::ButtonDown:
{
contentRect = upscale(
subControlRect(QStyle::CC_ComboBox, &cbo, QStyle::SC_ComboBoxArrow));
contentRect.translate(boundingRect.left(), boundingRect.top());
retVal = true;
break;
}
case ControlPart::SubEdit:
{
contentRect = upscale(
subControlRect(QStyle::CC_ComboBox, &cbo, QStyle::SC_ComboBoxEditField));
contentRect.translate(boundingRect.left(), boundingRect.top());
retVal = true;
break;
}
default:
break;
}
break;
}
case ControlType::Spinbox:
{
QStyleOptionSpinBox sbo;
sbo.frame = true;
sbo.rect = downscale(QRect(0, 0, contentRect.width(), contentRect.height()));
sbo.state = vclStateValue2StateFlag(controlState, val);
switch (part)
{
case ControlPart::Entire:
{
QSize aContentSize = downscale(contentRect.size(), Round::Ceil);
QFontMetrics aFontMetrics(QApplication::font());
aContentSize.setHeight(aFontMetrics.height());
QSize aMinSize = upscale(
sizeFromContents(QStyle::CT_SpinBox, &sbo, aContentSize), Round::Ceil);
if (aMinSize.height() > contentRect.height())
contentRect.setHeight(aMinSize.height());
boundingRect = contentRect;
retVal = true;
break;
}
case ControlPart::ButtonUp:
contentRect
= upscale(subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxUp));
contentRect.translate(boundingRect.left(), boundingRect.top());
retVal = true;
break;
case ControlPart::ButtonDown:
contentRect
= upscale(subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxDown));
contentRect.translate(boundingRect.left(), boundingRect.top());
retVal = true;
break;
case ControlPart::SubEdit:
contentRect = upscale(
subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxEditField));
contentRect.translate(boundingRect.left(), boundingRect.top());
retVal = true;
break;
default:
break;
}
break;
}
case ControlType::MenuPopup:
{
int h, w;
switch (part)
{
case ControlPart::MenuItemCheckMark:
h = upscale(pixelMetric(QStyle::PM_IndicatorHeight), Round::Floor);
w = upscale(pixelMetric(QStyle::PM_IndicatorWidth), Round::Floor);
retVal = true;
break;
case ControlPart::MenuItemRadioMark:
h = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorHeight), Round::Floor);
w = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorWidth), Round::Floor);
retVal = true;
break;
default:
break;
}
if (retVal)
{
contentRect = QRect(0, 0, w, h);
boundingRect = contentRect;
}
break;
}
case ControlType::Frame:
{
if (part == ControlPart::Border)
{
int nFrameWidth = upscale(pixelMetric(QStyle::PM_DefaultFrameWidth), Round::Ceil);
contentRect.adjust(nFrameWidth, nFrameWidth, -nFrameWidth, -nFrameWidth);
retVal = true;
}
break;
}
case ControlType::Radiobutton:
{
const int h = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorHeight), Round::Ceil);
const int w = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorWidth), Round::Ceil);
contentRect = QRect(boundingRect.left(), boundingRect.top(), w, h);
int nHMargin = pixelMetric(QStyle::PM_FocusFrameHMargin, &styleOption);
int nVMargin = pixelMetric(QStyle::PM_FocusFrameVMargin, &styleOption);
contentRect.adjust(0, 0, upscale(2 * nHMargin, Round::Ceil),
upscale(2 * nVMargin, Round::Ceil));
boundingRect = contentRect;
retVal = true;
break;
}
case ControlType::Slider:
{
const int w = upscale(pixelMetric(QStyle::PM_SliderLength), Round::Ceil);
if (part == ControlPart::ThumbHorz)
{
contentRect
= QRect(boundingRect.left(), boundingRect.top(), w, boundingRect.height());
boundingRect = contentRect;
retVal = true;
}
else if (part == ControlPart::ThumbVert)
{
contentRect
= QRect(boundingRect.left(), boundingRect.top(), boundingRect.width(), w);
boundingRect = contentRect;
retVal = true;
}
break;
}
case ControlType::Toolbar:
{
const int nWorH = upscale(pixelMetric(QStyle::PM_ToolBarHandleExtent), Round::Ceil);
if (part == ControlPart::ThumbHorz)
{
contentRect
= QRect(boundingRect.left(), boundingRect.top(), boundingRect.width(), nWorH);
boundingRect = contentRect;
retVal = true;
}
else if (part == ControlPart::ThumbVert)
{
contentRect
= QRect(boundingRect.left(), boundingRect.top(), nWorH, boundingRect.height());
boundingRect = contentRect;
retVal = true;
}
else if (part == ControlPart::Button)
{
QStyleOptionToolButton option;
option.arrowType = Qt::NoArrow;
option.features = QStyleOptionToolButton::None;
option.rect = downscale(QRect({ 0, 0 }, contentRect.size()));
contentRect = upscale(
subControlRect(QStyle::CC_ToolButton, &option, QStyle::SC_ToolButton));
boundingRect = contentRect;
retVal = true;
}
break;
}
case ControlType::Scrollbar:
{
// core can't handle 3-button scrollbars well, so we fix that in hitTestNativeControl(),
// for the rest also provide the track area (i.e. area not taken by buttons)
if (part == ControlPart::TrackVertArea || part == ControlPart::TrackHorzArea)
{
QStyleOptionSlider option;
bool horizontal = (part == ControlPart::TrackHorzArea); //horizontal or vertical
option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
if (horizontal)
option.state |= QStyle::State_Horizontal;
// getNativeControlRegion usually gets ImplControlValue as 'val' (i.e. not the proper
// subclass), so use random sensible values (doesn't matter anyway, as the wanted
// geometry here depends only on button sizes)
option.maximum = 10;
option.minimum = 0;
option.sliderPosition = option.sliderValue = 4;
option.pageStep = 2;
// Adjust coordinates to make the widget appear to be at (0,0), i.e. make
// widget and screen coordinates the same. QStyle functions should use screen
// coordinates but at least QPlastiqueStyle::subControlRect() is buggy
// and sometimes uses widget coordinates.
option.rect = downscale(QRect({ 0, 0 }, contentRect.size()));
contentRect = upscale(
subControlRect(QStyle::CC_ScrollBar, &option, QStyle::SC_ScrollBarGroove));
contentRect.translate(boundingRect.left()
- (contentRect.width() - boundingRect.width()),
boundingRect.top());
boundingRect = contentRect;
retVal = true;
}
break;
}
case ControlType::TabItem:
{
QStyleOptionTab sot;
fillQStyleOptionTab(val, sot);
QSize aMinSize = upscale(sizeFromContents(QStyle::CT_TabBarTab, &sot,
downscale(contentRect.size(), Round::Ceil)),
Round::Ceil);
contentRect.setSize(aMinSize);
boundingRect = contentRect;
retVal = true;
break;
}
case ControlType::TabPane:
{
const TabPaneValue& rValue = static_cast<const TabPaneValue&>(val);
QStyleOptionTabWidgetFrame sotwf;
fullQStyleOptionTabWidgetFrame(sotwf, true);
QSize contentSize(
std::max(rValue.m_aTabHeaderRect.GetWidth(), controlRegion.GetWidth()),
rValue.m_aTabHeaderRect.GetHeight() + controlRegion.GetHeight());
QSize aMinSize = upscale(
sizeFromContents(QStyle::CT_TabWidget, &sotwf, downscale(contentSize, Round::Ceil)),
Round::Ceil);
contentRect.setSize(aMinSize);
boundingRect = contentRect;
retVal = true;
break;
}
default:
break;
}
if (retVal)
{
nativeBoundingRegion = toRectangle(boundingRect);
nativeContentRegion = toRectangle(contentRect);
}
return retVal;
}
/** Test whether the position is in the native widget.
If the return value is true, bIsInside contains information whether
aPos was or was not inside the native widget specified by the
nType/nPart combination.
*/
bool QtGraphics_Controls::hitTestNativeControl(ControlType nType, ControlPart nPart,
const tools::Rectangle& rControlRegion,
const Point& rPos, bool& rIsInside)
{
if (nType == ControlType::Scrollbar)
{
if (nPart != ControlPart::ButtonUp && nPart != ControlPart::ButtonDown
&& nPart != ControlPart::ButtonLeft && nPart != ControlPart::ButtonRight)
{ // we adjust only for buttons (because some scrollbars have 3 buttons,
// and LO core doesn't handle such scrollbars well)
return false;
}
rIsInside = false;
bool bHorizontal = (nPart == ControlPart::ButtonLeft || nPart == ControlPart::ButtonRight);
QRect rect = toQRect(rControlRegion);
QPoint pos(rPos.X(), rPos.Y());
// Adjust coordinates to make the widget appear to be at (0,0), i.e. make
// widget and screen coordinates the same. QStyle functions should use screen
// coordinates but at least QPlastiqueStyle::subControlRect() is buggy
// and sometimes uses widget coordinates.
pos -= rect.topLeft();
rect.moveTo(0, 0);
QStyleOptionSlider options;
options.orientation = bHorizontal ? Qt::Horizontal : Qt::Vertical;
if (bHorizontal)
options.state |= QStyle::State_Horizontal;
options.rect = rect;
// some random sensible values, since we call this code only for scrollbar buttons,
// the slider position does not exactly matter
options.maximum = 10;
options.minimum = 0;
options.sliderPosition = options.sliderValue = 4;
options.pageStep = 2;
QStyle::SubControl control
= QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar, &options, pos);
if (nPart == ControlPart::ButtonUp || nPart == ControlPart::ButtonLeft)
rIsInside = (control == QStyle::SC_ScrollBarSubLine);
else // DOWN, RIGHT
rIsInside = (control == QStyle::SC_ScrollBarAddLine);
return true;
}
return false;
}
inline int QtGraphics_Controls::downscale(int size, Round eRound)
{
return static_cast<int>(eRound == Round::Ceil ? ceil(size / m_rGraphics.devicePixelRatioF())
: floor(size / m_rGraphics.devicePixelRatioF()));
}
inline int QtGraphics_Controls::upscale(int size, Round eRound)
{
return static_cast<int>(eRound == Round::Ceil ? ceil(size * m_rGraphics.devicePixelRatioF())
: floor(size * m_rGraphics.devicePixelRatioF()));
}
inline QRect QtGraphics_Controls::downscale(const QRect& rect)
{
return QRect(downscale(rect.x(), Round::Floor), downscale(rect.y(), Round::Floor),
downscale(rect.width(), Round::Ceil), downscale(rect.height(), Round::Ceil));
}
inline QRect QtGraphics_Controls::upscale(const QRect& rect)
{
return QRect(upscale(rect.x(), Round::Floor), upscale(rect.y(), Round::Floor),
upscale(rect.width(), Round::Ceil), upscale(rect.height(), Round::Ceil));
}
inline QSize QtGraphics_Controls::downscale(const QSize& size, Round eRound)
{
return QSize(downscale(size.width(), eRound), downscale(size.height(), eRound));
}
inline QSize QtGraphics_Controls::upscale(const QSize& size, Round eRound)
{
return QSize(upscale(size.width(), eRound), upscale(size.height(), eRound));
}
inline QPoint QtGraphics_Controls::upscale(const QPoint& point, Round eRound)
{
return QPoint(upscale(point.x(), eRound), upscale(point.y(), eRound));
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */