1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <QtGraphics_Controls.hxx>
22 #include <QtGui/QPainter>
23 #include <QtWidgets/QApplication>
24 #include <QtWidgets/QFrame>
25 #include <QtWidgets/QLabel>
26 #include <QtWidgets/QLineEdit>
28 #include <QtTools.hxx>
29 #include <QtGraphicsBase.hxx>
30 #include <vcl/decoview.hxx>
33 Conversion function between VCL ControlState together with
34 ImplControlValue and Qt state flags.
35 @param nControlState State of the widget (default, focused, ...) in Native Widget Framework.
36 @param aValue Value held by the widget (on, off, ...)
38 static QStyle::State
vclStateValue2StateFlag(ControlState nControlState
,
39 const ImplControlValue
& aValue
)
42 = ((nControlState
& ControlState::ENABLED
) ? QStyle::State_Enabled
: QStyle::State_None
)
43 | ((nControlState
& ControlState::FOCUSED
)
44 ? QStyle::State_HasFocus
| QStyle::State_KeyboardFocusChange
46 | ((nControlState
& ControlState::PRESSED
) ? QStyle::State_Sunken
: QStyle::State_None
)
47 | ((nControlState
& ControlState::SELECTED
) ? QStyle::State_Selected
: QStyle::State_None
)
48 | ((nControlState
& ControlState::ROLLOVER
) ? QStyle::State_MouseOver
49 : QStyle::State_None
);
51 switch (aValue
.getTristateVal())
54 nState
|= QStyle::State_On
;
56 case ButtonValue::Off
:
57 nState
|= QStyle::State_Off
;
59 case ButtonValue::Mixed
:
60 nState
|= QStyle::State_NoChange
;
69 static void lcl_ApplyBackgroundColorToStyleOption(QStyleOption
& rOption
,
70 const Color
& rBackgroundColor
)
72 if (rBackgroundColor
!= COL_AUTO
)
74 QColor aColor
= toQColor(rBackgroundColor
);
75 for (QPalette::ColorRole role
: { QPalette::Window
, QPalette::Button
, QPalette::Base
})
76 rOption
.palette
.setColor(role
, aColor
);
80 QtGraphics_Controls::QtGraphics_Controls(const QtGraphicsBase
& rGraphics
)
81 : m_rGraphics(rGraphics
)
85 bool QtGraphics_Controls::isNativeControlSupported(ControlType type
, ControlPart part
)
89 case ControlType::Tooltip
:
90 case ControlType::Progress
:
91 case ControlType::ListNode
:
92 return (part
== ControlPart::Entire
);
94 case ControlType::Pushbutton
:
95 case ControlType::Radiobutton
:
96 case ControlType::Checkbox
:
97 return (part
== ControlPart::Entire
) || (part
== ControlPart::Focus
);
99 case ControlType::ListHeader
:
100 return (part
== ControlPart::Button
);
102 case ControlType::Menubar
:
103 case ControlType::MenuPopup
:
104 case ControlType::Editbox
:
105 case ControlType::MultilineEditbox
:
106 case ControlType::Combobox
:
107 case ControlType::Toolbar
:
108 case ControlType::Frame
:
109 case ControlType::Scrollbar
:
110 case ControlType::WindowBackground
:
111 case ControlType::Fixedline
:
114 case ControlType::Listbox
:
115 return (part
== ControlPart::Entire
|| part
== ControlPart::HasBackgroundTexture
);
117 case ControlType::Spinbox
:
118 return (part
== ControlPart::Entire
|| part
== ControlPart::HasBackgroundTexture
);
120 case ControlType::Slider
:
121 return (part
== ControlPart::TrackHorzArea
|| part
== ControlPart::TrackVertArea
);
123 case ControlType::TabItem
:
124 case ControlType::TabPane
:
125 return ((part
== ControlPart::Entire
) || part
== ControlPart::TabPaneWithHeader
);
134 inline int QtGraphics_Controls::pixelMetric(QStyle::PixelMetric metric
, const QStyleOption
* option
,
135 const QWidget
* pWidget
)
137 return QApplication::style()->pixelMetric(metric
, option
, pWidget
);
140 inline QSize
QtGraphics_Controls::sizeFromContents(QStyle::ContentsType type
,
141 const QStyleOption
* option
,
142 const QSize
& contentsSize
)
144 return QApplication::style()->sizeFromContents(type
, option
, contentsSize
);
147 inline QRect
QtGraphics_Controls::subControlRect(QStyle::ComplexControl control
,
148 const QStyleOptionComplex
* option
,
149 QStyle::SubControl subControl
)
151 return QApplication::style()->subControlRect(control
, option
, subControl
);
154 inline QRect
QtGraphics_Controls::subElementRect(QStyle::SubElement element
,
155 const QStyleOption
* option
)
157 return QApplication::style()->subElementRect(element
, option
);
160 void QtGraphics_Controls::draw(QStyle::ControlElement element
, QStyleOption
& rOption
, QImage
* image
,
161 const Color
& rBackgroundColor
, QStyle::State
const state
, QRect rect
)
163 const QRect
& targetRect
= !rect
.isNull() ? rect
: image
->rect();
165 rOption
.state
|= state
;
166 rOption
.rect
= downscale(targetRect
);
168 lcl_ApplyBackgroundColorToStyleOption(rOption
, rBackgroundColor
);
170 QPainter
painter(image
);
171 QApplication::style()->drawControl(element
, &rOption
, &painter
);
174 void QtGraphics_Controls::draw(QStyle::PrimitiveElement element
, QStyleOption
& rOption
,
175 QImage
* image
, const Color
& rBackgroundColor
,
176 QStyle::State
const state
, QRect rect
)
178 const QRect
& targetRect
= !rect
.isNull() ? rect
: image
->rect();
180 rOption
.state
|= state
;
181 rOption
.rect
= downscale(targetRect
);
183 lcl_ApplyBackgroundColorToStyleOption(rOption
, rBackgroundColor
);
185 QPainter
painter(image
);
186 QApplication::style()->drawPrimitive(element
, &rOption
, &painter
);
189 void QtGraphics_Controls::draw(QStyle::ComplexControl element
, QStyleOptionComplex
& rOption
,
190 QImage
* image
, const Color
& rBackgroundColor
,
191 QStyle::State
const state
)
193 const QRect
& targetRect
= image
->rect();
195 rOption
.state
|= state
;
196 rOption
.rect
= downscale(targetRect
);
198 lcl_ApplyBackgroundColorToStyleOption(rOption
, rBackgroundColor
);
200 QPainter
painter(image
);
201 QApplication::style()->drawComplexControl(element
, &rOption
, &painter
);
204 void QtGraphics_Controls::drawFrame(QStyle::PrimitiveElement element
, QImage
* image
,
205 const Color
& rBackgroundColor
, QStyle::State
const& state
,
206 bool bClip
, QStyle::PixelMetric eLineMetric
)
208 const int fw
= pixelMetric(eLineMetric
);
209 QStyleOptionFrame option
;
210 option
.frameShape
= QFrame::StyledPanel
;
211 option
.state
= QStyle::State_Sunken
| state
;
212 option
.lineWidth
= fw
;
214 QRect aRect
= downscale(image
->rect());
217 lcl_ApplyBackgroundColorToStyleOption(option
, rBackgroundColor
);
219 QPainter
painter(image
);
221 painter
.setClipRegion(QRegion(aRect
).subtracted(aRect
.adjusted(fw
, fw
, -fw
, -fw
)));
222 QApplication::style()->drawPrimitive(element
, &option
, &painter
);
225 void QtGraphics_Controls::fillQStyleOptionTab(const ImplControlValue
& value
, QStyleOptionTab
& sot
)
227 const TabitemValue
& rValue
= static_cast<const TabitemValue
&>(value
);
228 if (rValue
.isFirst())
229 sot
.position
= rValue
.isLast() ? QStyleOptionTab::OnlyOneTab
: QStyleOptionTab::Beginning
;
230 else if (rValue
.isLast())
231 sot
.position
= rValue
.isFirst() ? QStyleOptionTab::OnlyOneTab
: QStyleOptionTab::End
;
233 sot
.position
= QStyleOptionTab::Middle
;
236 void QtGraphics_Controls::fullQStyleOptionTabWidgetFrame(QStyleOptionTabWidgetFrame
& option
,
239 option
.state
= QStyle::State_Enabled
;
240 option
.rightCornerWidgetSize
= QSize(0, 0);
241 option
.leftCornerWidgetSize
= QSize(0, 0);
242 int nLineWidth
= pixelMetric(QStyle::PM_DefaultFrameWidth
);
243 option
.lineWidth
= bDownscale
? std::max(1, downscale(nLineWidth
, Round::Ceil
)) : nLineWidth
;
244 option
.midLineWidth
= 0;
245 option
.shape
= QTabBar::RoundedNorth
;
248 bool QtGraphics_Controls::drawNativeControl(ControlType type
, ControlPart part
,
249 const tools::Rectangle
& rControlRegion
,
250 ControlState nControlState
,
251 const ImplControlValue
& value
, const OUString
&,
252 const Color
& rBackgroundColor
)
254 bool nativeSupport
= isNativeControlSupported(type
, part
);
257 assert(!nativeSupport
&& "drawNativeControl called without native support!");
261 if (m_lastPopupRect
.isValid()
262 && (type
!= ControlType::MenuPopup
|| part
!= ControlPart::MenuItem
))
263 m_lastPopupRect
= QRect();
265 bool returnVal
= true;
267 QRect widgetRect
= toQRect(rControlRegion
);
269 //if no image, or resized, make a new image
270 if (!m_image
|| m_image
->size() != widgetRect
.size())
272 m_image
.reset(new QImage(widgetRect
.width(), widgetRect
.height(),
273 QImage::Format_ARGB32_Premultiplied
));
274 m_image
->setDevicePixelRatio(m_rGraphics
.devicePixelRatioF());
277 // Default image color - just once
280 case ControlType::MenuPopup
:
281 if (part
== ControlPart::MenuItemCheckMark
|| part
== ControlPart::MenuItemRadioMark
)
283 // it is necessary to fill the background transparently first, as this
284 // is painted after menuitem highlight, otherwise there would be a grey area
285 m_image
->fill(Qt::transparent
);
288 [[fallthrough
]]; // QPalette::Window
289 case ControlType::Menubar
:
290 case ControlType::WindowBackground
:
291 m_image
->fill(QApplication::palette().color(QPalette::Window
).rgb());
293 case ControlType::Tooltip
:
294 m_image
->fill(QApplication::palette().color(QPalette::ToolTipBase
).rgb());
296 case ControlType::Scrollbar
:
297 if ((part
== ControlPart::DrawBackgroundVert
)
298 || (part
== ControlPart::DrawBackgroundHorz
))
300 m_image
->fill(QApplication::palette().color(QPalette::Window
).rgb());
303 [[fallthrough
]]; // Qt::transparent
305 m_image
->fill(Qt::transparent
);
309 if (type
== ControlType::Pushbutton
)
311 const PushButtonValue
& rPBValue
= static_cast<const PushButtonValue
&>(value
);
312 if (part
== ControlPart::Focus
)
313 // Nothing to do. Drawing focus separately is not needed because that's
314 // already handled by the ControlState::FOCUSED state being set when
315 // drawing the entire control
317 assert(part
== ControlPart::Entire
);
318 QStyleOptionButton option
;
319 if (nControlState
& ControlState::DEFAULT
)
320 option
.features
|= QStyleOptionButton::DefaultButton
;
321 if (rPBValue
.m_bFlatButton
)
322 option
.features
|= QStyleOptionButton::Flat
;
323 draw(QStyle::CE_PushButton
, option
, m_image
.get(), rBackgroundColor
,
324 vclStateValue2StateFlag(nControlState
, value
));
326 else if (type
== ControlType::Menubar
)
328 if (part
== ControlPart::MenuItem
)
330 QStyleOptionMenuItem option
;
331 option
.state
= vclStateValue2StateFlag(nControlState
, value
);
332 if ((nControlState
& ControlState::ROLLOVER
)
333 && QApplication::style()->styleHint(QStyle::SH_MenuBar_MouseTracking
))
334 option
.state
|= QStyle::State_Selected
;
337 & ControlState::SELECTED
) // Passing State_Sunken is currently not documented.
338 option
.state
|= QStyle::State_Sunken
; // But some kinds of QStyle interpret it.
340 draw(QStyle::CE_MenuBarItem
, option
, m_image
.get(), rBackgroundColor
);
342 else if (part
== ControlPart::Entire
)
344 QStyleOptionMenuItem option
;
345 draw(QStyle::CE_MenuBarEmptyArea
, option
, m_image
.get(), rBackgroundColor
,
346 vclStateValue2StateFlag(nControlState
, value
));
353 else if (type
== ControlType::MenuPopup
)
355 assert(part
== ControlPart::MenuItem
? m_lastPopupRect
.isValid()
356 : !m_lastPopupRect
.isValid());
357 if (part
== ControlPart::MenuItem
)
359 QStyleOptionMenuItem option
;
360 draw(QStyle::CE_MenuItem
, option
, m_image
.get(), rBackgroundColor
,
361 vclStateValue2StateFlag(nControlState
, value
));
362 // HACK: LO core first paints the entire popup and only then it paints menu items,
363 // but QMenu::paintEvent() paints popup frame after all items. That means highlighted
364 // items here would paint the highlight over the frame border. Since calls to ControlPart::MenuItem
365 // are always preceded by calls to ControlPart::Entire, just remember the size for the whole
366 // popup (otherwise not possible to get here) and draw the border afterwards.
367 QRect
framerect(m_lastPopupRect
.topLeft() - widgetRect
.topLeft(),
368 widgetRect
.size().expandedTo(m_lastPopupRect
.size()));
369 QStyleOptionFrame frame
;
370 draw(QStyle::PE_FrameMenu
, frame
, m_image
.get(), rBackgroundColor
,
371 vclStateValue2StateFlag(nControlState
, value
), framerect
);
373 else if (part
== ControlPart::Separator
)
375 QStyleOptionMenuItem option
;
376 option
.menuItemType
= QStyleOptionMenuItem::Separator
;
377 // Painting the whole menu item area results in different background
378 // with at least Plastique style, so clip only to the separator itself
379 // (QSize( 2, 2 ) is hardcoded in Qt)
380 option
.rect
= m_image
->rect();
381 QSize size
= sizeFromContents(QStyle::CT_MenuItem
, &option
, QSize(2, 2));
382 QRect rect
= m_image
->rect();
383 QPoint center
= rect
.center();
384 rect
.setHeight(size
.height());
385 rect
.moveCenter(center
);
386 option
.state
|= vclStateValue2StateFlag(nControlState
, value
);
389 QPainter
painter(m_image
.get());
390 // don't paint over popup frame border (like the hack above, but here it can be simpler)
391 const int fw
= pixelMetric(QStyle::PM_MenuPanelWidth
);
392 painter
.setClipRect(rect
.adjusted(fw
, 0, -fw
, 0));
393 QApplication::style()->drawControl(QStyle::CE_MenuItem
, &option
, &painter
);
395 else if (part
== ControlPart::MenuItemCheckMark
|| part
== ControlPart::MenuItemRadioMark
)
397 QStyleOptionMenuItem option
;
398 option
.checkType
= (part
== ControlPart::MenuItemCheckMark
)
399 ? QStyleOptionMenuItem::NonExclusive
400 : QStyleOptionMenuItem::Exclusive
;
401 option
.checked
= bool(nControlState
& ControlState::PRESSED
);
402 // widgetRect is now the rectangle for the checkbox/radiobutton itself, but Qt
403 // paints the whole menu item, so translate position (and it'll be clipped);
404 // it is also necessary to fill the background transparently first, as this
405 // is painted after menuitem highlight, otherwise there would be a grey area
406 assert(value
.getType() == ControlType::MenuPopup
);
407 const MenupopupValue
* menuVal
= static_cast<const MenupopupValue
*>(&value
);
408 QRect
menuItemRect(toQRect(menuVal
->maItemRect
));
409 QRect
rect(menuItemRect
.topLeft() - widgetRect
.topLeft(),
410 widgetRect
.size().expandedTo(menuItemRect
.size()));
411 // checkboxes are always displayed next to images in menus, so are never centered
412 const int focus_size
= pixelMetric(QStyle::PM_FocusFrameHMargin
);
413 rect
.moveTo(-focus_size
, rect
.y());
414 draw(QStyle::CE_MenuItem
, option
, m_image
.get(), rBackgroundColor
,
415 vclStateValue2StateFlag(nControlState
& ~ControlState::PRESSED
, value
), rect
);
417 else if (part
== ControlPart::Entire
)
419 QStyleOptionMenuItem option
;
420 option
.state
= vclStateValue2StateFlag(nControlState
, value
);
421 draw(QStyle::PE_PanelMenu
, option
, m_image
.get(), rBackgroundColor
);
422 // Try hard to get any frame!
423 QStyleOptionFrame frame
;
424 draw(QStyle::PE_FrameMenu
, frame
, m_image
.get(), rBackgroundColor
);
425 draw(QStyle::PE_FrameWindow
, frame
, m_image
.get(), rBackgroundColor
);
426 m_lastPopupRect
= widgetRect
;
431 else if ((type
== ControlType::Toolbar
) && (part
== ControlPart::Button
))
433 QStyleOptionToolButton option
;
435 option
.arrowType
= Qt::NoArrow
;
436 option
.subControls
= QStyle::SC_ToolButton
;
437 option
.state
= vclStateValue2StateFlag(nControlState
, value
);
438 option
.state
|= QStyle::State_Raised
| QStyle::State_Enabled
| QStyle::State_AutoRaise
;
440 draw(QStyle::CC_ToolButton
, option
, m_image
.get(), rBackgroundColor
);
442 else if ((type
== ControlType::Toolbar
) && (part
== ControlPart::Entire
))
444 QStyleOptionToolBar option
;
445 draw(QStyle::CE_ToolBar
, option
, m_image
.get(), rBackgroundColor
,
446 vclStateValue2StateFlag(nControlState
, value
));
448 else if ((type
== ControlType::Toolbar
)
449 && (part
== ControlPart::ThumbVert
|| part
== ControlPart::ThumbHorz
))
451 // reduce paint area only to the handle area
452 const int handleExtend
= pixelMetric(QStyle::PM_ToolBarHandleExtent
);
454 QRect aRect
= m_image
->rect();
455 if (part
== ControlPart::ThumbVert
)
457 aRect
.setWidth(handleExtend
);
458 option
.state
= QStyle::State_Horizontal
;
461 aRect
.setHeight(handleExtend
);
462 draw(QStyle::PE_IndicatorToolBarHandle
, option
, m_image
.get(), rBackgroundColor
,
463 vclStateValue2StateFlag(nControlState
, value
), aRect
);
465 else if (type
== ControlType::Editbox
|| type
== ControlType::MultilineEditbox
)
467 drawFrame(QStyle::PE_FrameLineEdit
, m_image
.get(), rBackgroundColor
,
468 vclStateValue2StateFlag(nControlState
, value
), false);
470 else if (type
== ControlType::Combobox
)
472 QStyleOptionComboBox option
;
473 option
.editable
= true;
474 draw(QStyle::CC_ComboBox
, option
, m_image
.get(), rBackgroundColor
,
475 vclStateValue2StateFlag(nControlState
, value
));
477 else if (type
== ControlType::Listbox
)
479 QStyleOptionComboBox option
;
480 option
.editable
= false;
483 case ControlPart::ListboxWindow
:
484 drawFrame(QStyle::PE_Frame
, m_image
.get(), rBackgroundColor
,
485 vclStateValue2StateFlag(nControlState
, value
), true,
486 QStyle::PM_ComboBoxFrameWidth
);
488 case ControlPart::SubEdit
:
489 draw(QStyle::CE_ComboBoxLabel
, option
, m_image
.get(), rBackgroundColor
,
490 vclStateValue2StateFlag(nControlState
, value
));
492 case ControlPart::Entire
:
493 draw(QStyle::CC_ComboBox
, option
, m_image
.get(), rBackgroundColor
,
494 vclStateValue2StateFlag(nControlState
, value
));
496 case ControlPart::ButtonDown
:
497 option
.subControls
= QStyle::SC_ComboBoxArrow
;
498 draw(QStyle::CC_ComboBox
, option
, m_image
.get(), rBackgroundColor
,
499 vclStateValue2StateFlag(nControlState
, value
));
506 else if (type
== ControlType::ListNode
)
509 option
.state
= vclStateValue2StateFlag(nControlState
, value
);
510 option
.state
|= QStyle::State_Item
| QStyle::State_Children
;
512 if (value
.getTristateVal() == ButtonValue::On
)
513 option
.state
|= QStyle::State_Open
;
515 draw(QStyle::PE_IndicatorBranch
, option
, m_image
.get(), rBackgroundColor
);
517 else if (type
== ControlType::ListHeader
)
519 QStyleOptionHeader option
;
520 draw(QStyle::CE_HeaderSection
, option
, m_image
.get(), rBackgroundColor
,
521 vclStateValue2StateFlag(nControlState
, value
));
523 else if (type
== ControlType::Checkbox
)
525 if (part
== ControlPart::Entire
)
527 QStyleOptionButton option
;
528 // clear FOCUSED bit, focus is drawn separately
529 nControlState
&= ~ControlState::FOCUSED
;
530 draw(QStyle::CE_CheckBox
, option
, m_image
.get(), rBackgroundColor
,
531 vclStateValue2StateFlag(nControlState
, value
));
533 else if (part
== ControlPart::Focus
)
535 QStyleOptionFocusRect option
;
536 draw(QStyle::PE_FrameFocusRect
, option
, m_image
.get(), rBackgroundColor
,
537 vclStateValue2StateFlag(nControlState
, value
));
540 else if (type
== ControlType::Scrollbar
)
542 if ((part
== ControlPart::DrawBackgroundVert
) || (part
== ControlPart::DrawBackgroundHorz
))
544 QStyleOptionSlider option
;
545 assert(value
.getType() == ControlType::Scrollbar
);
546 const ScrollbarValue
* sbVal
= static_cast<const ScrollbarValue
*>(&value
);
548 //if the scroll bar is active (aka not degenerate... allow for hover events)
549 if (sbVal
->mnVisibleSize
< sbVal
->mnMax
)
550 option
.state
= QStyle::State_MouseOver
;
552 bool horizontal
= (part
== ControlPart::DrawBackgroundHorz
); //horizontal or vertical
553 option
.orientation
= horizontal
? Qt::Horizontal
: Qt::Vertical
;
555 option
.state
|= QStyle::State_Horizontal
;
557 // If the scrollbar has a mnMin == 0 and mnMax == 0 then mnVisibleSize is set to -1?!
558 // I don't know if a negative mnVisibleSize makes any sense, so just handle this case
559 // without crashing LO with a SIGFPE in the Qt library.
560 const tools::Long nVisibleSize
561 = (sbVal
->mnMin
== sbVal
->mnMax
) ? 0 : sbVal
->mnVisibleSize
;
563 option
.minimum
= sbVal
->mnMin
;
564 option
.maximum
= sbVal
->mnMax
- nVisibleSize
;
565 option
.maximum
= qMax(option
.maximum
, option
.minimum
); // bnc#619772
566 option
.sliderValue
= sbVal
->mnCur
;
567 option
.sliderPosition
= sbVal
->mnCur
;
568 option
.pageStep
= nVisibleSize
;
569 if (part
== ControlPart::DrawBackgroundHorz
)
571 = (QGuiApplication::isRightToLeft()
572 && sbVal
->maButton1Rect
.Left() < sbVal
->maButton2Rect
.Left())
573 || (QGuiApplication::isLeftToRight()
574 && sbVal
->maButton1Rect
.Left() > sbVal
->maButton2Rect
.Left());
576 //setup the active control... always the slider
577 if (sbVal
->mnThumbState
& ControlState::ROLLOVER
)
578 option
.activeSubControls
= QStyle::SC_ScrollBarSlider
;
580 draw(QStyle::CC_ScrollBar
, option
, m_image
.get(), rBackgroundColor
,
581 vclStateValue2StateFlag(nControlState
, value
));
588 else if (type
== ControlType::Spinbox
)
590 QStyleOptionSpinBox option
;
593 // determine active control
594 if (value
.getType() == ControlType::SpinButtons
)
596 const SpinbuttonValue
* pSpinVal
= static_cast<const SpinbuttonValue
*>(&value
);
597 if (pSpinVal
->mnUpperState
& ControlState::PRESSED
)
598 option
.activeSubControls
|= QStyle::SC_SpinBoxUp
;
599 if (pSpinVal
->mnLowerState
& ControlState::PRESSED
)
600 option
.activeSubControls
|= QStyle::SC_SpinBoxDown
;
601 if (pSpinVal
->mnUpperState
& ControlState::ENABLED
)
602 option
.stepEnabled
|= QAbstractSpinBox::StepUpEnabled
;
603 if (pSpinVal
->mnLowerState
& ControlState::ENABLED
)
604 option
.stepEnabled
|= QAbstractSpinBox::StepDownEnabled
;
605 if (pSpinVal
->mnUpperState
& ControlState::ROLLOVER
)
606 option
.state
= QStyle::State_MouseOver
;
607 if (pSpinVal
->mnLowerState
& ControlState::ROLLOVER
)
608 option
.state
= QStyle::State_MouseOver
;
611 draw(QStyle::CC_SpinBox
, option
, m_image
.get(), rBackgroundColor
,
612 vclStateValue2StateFlag(nControlState
, value
));
614 else if (type
== ControlType::Radiobutton
)
616 if (part
== ControlPart::Entire
)
618 QStyleOptionButton option
;
619 // clear FOCUSED bit, focus is drawn separately
620 nControlState
&= ~ControlState::FOCUSED
;
621 draw(QStyle::CE_RadioButton
, option
, m_image
.get(), rBackgroundColor
,
622 vclStateValue2StateFlag(nControlState
, value
));
624 else if (part
== ControlPart::Focus
)
626 QStyleOptionFocusRect option
;
627 draw(QStyle::PE_FrameFocusRect
, option
, m_image
.get(), rBackgroundColor
,
628 vclStateValue2StateFlag(nControlState
, value
));
631 else if (type
== ControlType::Tooltip
)
634 draw(QStyle::PE_PanelTipLabel
, option
, m_image
.get(), rBackgroundColor
,
635 vclStateValue2StateFlag(nControlState
, value
));
637 else if (type
== ControlType::Frame
)
639 drawFrame(QStyle::PE_Frame
, m_image
.get(), rBackgroundColor
,
640 vclStateValue2StateFlag(nControlState
, value
));
642 else if (type
== ControlType::WindowBackground
)
644 // Nothing to do - see "Default image color" switch ^^
646 else if (type
== ControlType::Fixedline
)
648 QStyleOptionMenuItem option
;
649 option
.menuItemType
= QStyleOptionMenuItem::Separator
;
650 option
.state
= vclStateValue2StateFlag(nControlState
, value
);
651 option
.state
|= QStyle::State_Item
;
653 draw(QStyle::CE_MenuItem
, option
, m_image
.get(), rBackgroundColor
);
655 else if (type
== ControlType::Slider
656 && (part
== ControlPart::TrackHorzArea
|| part
== ControlPart::TrackVertArea
))
658 assert(value
.getType() == ControlType::Slider
);
659 const SliderValue
* slVal
= static_cast<const SliderValue
*>(&value
);
660 QStyleOptionSlider option
;
662 option
.state
= vclStateValue2StateFlag(nControlState
, value
);
663 option
.maximum
= slVal
->mnMax
;
664 option
.minimum
= slVal
->mnMin
;
665 option
.sliderPosition
= option
.sliderValue
= slVal
->mnCur
;
666 bool horizontal
= (part
== ControlPart::TrackHorzArea
); //horizontal or vertical
667 option
.orientation
= horizontal
? Qt::Horizontal
: Qt::Vertical
;
669 option
.state
|= QStyle::State_Horizontal
;
671 draw(QStyle::CC_Slider
, option
, m_image
.get(), rBackgroundColor
);
673 else if (type
== ControlType::Progress
&& part
== ControlPart::Entire
)
675 QStyleOptionProgressBar option
;
677 option
.maximum
= widgetRect
.width();
678 option
.progress
= value
.getNumericVal();
680 draw(QStyle::CE_ProgressBar
, option
, m_image
.get(), rBackgroundColor
,
681 vclStateValue2StateFlag(nControlState
, value
));
683 else if (type
== ControlType::TabItem
&& part
== ControlPart::Entire
)
686 fillQStyleOptionTab(value
, sot
);
687 draw(QStyle::CE_TabBarTabShape
, sot
, m_image
.get(), rBackgroundColor
,
688 vclStateValue2StateFlag(nControlState
, value
));
690 else if (type
== ControlType::TabPane
&& part
== ControlPart::Entire
)
692 const TabPaneValue
& rValue
= static_cast<const TabPaneValue
&>(value
);
694 // get the overlap size for the tabs, so they will overlap the frame
695 QStyleOptionTab tabOverlap
;
696 tabOverlap
.shape
= QTabBar::RoundedNorth
;
697 TabPaneValue::m_nOverlap
= pixelMetric(QStyle::PM_TabBarBaseOverlap
, &tabOverlap
);
699 QStyleOptionTabWidgetFrame option
;
700 fullQStyleOptionTabWidgetFrame(option
, false);
701 option
.tabBarRect
= toQRect(rValue
.m_aTabHeaderRect
);
702 option
.selectedTabRect
703 = rValue
.m_aSelectedTabRect
.IsEmpty() ? QRect() : toQRect(rValue
.m_aSelectedTabRect
);
704 option
.tabBarSize
= toQSize(rValue
.m_aTabHeaderRect
.GetSize());
705 option
.rect
= m_image
->rect();
706 QRect aRect
= subElementRect(QStyle::SE_TabWidgetTabPane
, &option
);
707 draw(QStyle::PE_FrameTabWidget
, option
, m_image
.get(), rBackgroundColor
,
708 vclStateValue2StateFlag(nControlState
, value
), aRect
);
718 bool QtGraphics_Controls::getNativeControlRegion(ControlType type
, ControlPart part
,
719 const tools::Rectangle
& controlRegion
,
720 ControlState controlState
,
721 const ImplControlValue
& val
, const OUString
&,
722 tools::Rectangle
& nativeBoundingRegion
,
723 tools::Rectangle
& nativeContentRegion
)
727 QRect boundingRect
= toQRect(controlRegion
);
728 QRect contentRect
= boundingRect
;
729 QStyleOptionComplex styleOption
;
733 // Metrics of the push button
734 case ControlType::Pushbutton
:
735 if (part
== ControlPart::Entire
)
737 styleOption
.state
= vclStateValue2StateFlag(controlState
, val
);
739 if (controlState
& ControlState::DEFAULT
)
741 int size
= upscale(pixelMetric(QStyle::PM_ButtonDefaultIndicator
, &styleOption
),
743 boundingRect
.adjust(-size
, -size
, size
, size
);
747 else if (part
== ControlPart::Focus
)
750 case ControlType::Editbox
:
751 case ControlType::MultilineEditbox
:
753 // we have to get stable borders, otherwise layout loops.
754 // so we simply only scale the detected borders.
755 QStyleOptionFrame fo
;
756 fo
.frameShape
= QFrame::StyledPanel
;
757 fo
.state
= QStyle::State_Sunken
;
758 fo
.lineWidth
= pixelMetric(QStyle::PM_DefaultFrameWidth
);
759 fo
.rect
= downscale(contentRect
);
760 fo
.rect
.setSize(sizeFromContents(QStyle::CT_LineEdit
, &fo
, fo
.rect
.size()));
761 QRect aSubRect
= subElementRect(QStyle::SE_LineEditContents
, &fo
);
763 // VCL tests borders with small defaults before layout, where Qt returns no sub-rect,
764 // so this gets us at least some frame.
765 int nLine
= upscale(fo
.lineWidth
, Round::Ceil
);
766 int nLeft
= qMin(-nLine
, upscale(fo
.rect
.left() - aSubRect
.left(), Round::Floor
));
767 int nTop
= qMin(-nLine
, upscale(fo
.rect
.top() - aSubRect
.top(), Round::Floor
));
768 int nRight
= qMax(nLine
, upscale(fo
.rect
.right() - aSubRect
.right(), Round::Ceil
));
769 int nBottom
= qMax(nLine
, upscale(fo
.rect
.bottom() - aSubRect
.bottom(), Round::Ceil
));
770 boundingRect
.adjust(nLeft
, nTop
, nRight
, nBottom
);
772 // tdf#150451: ensure a minimum size that fits text content + frame at top and bottom.
773 // Themes may use the widget type for determining the actual frame width to use,
774 // so pass a dummy QLineEdit
776 // NOTE: This is currently only done here for the minimum size calculation and
777 // not above because the handling for edit boxes here and in the calling code
778 // currently does all kinds of "interesting" things like doing extra size adjustments
779 // or passing the content rect where the bounding rect would be expected,...
780 // Ideally this should be cleaned up in the callers and all platform integrations
781 // to adhere to what the doc in vcl/inc/WidgetDrawInterface.hxx says, but this
782 // here keeps it working with existing code for now.
783 // (s.a. discussion in https://gerrit.libreoffice.org/c/core/+/146516 for more details)
784 QLineEdit aDummyEdit
;
785 const int nFrameWidth
= pixelMetric(QStyle::PM_DefaultFrameWidth
, nullptr, &aDummyEdit
);
786 QFontMetrics
aFontMetrics(QApplication::font());
787 const int minHeight
= upscale(aFontMetrics
.height() + 2 * nFrameWidth
, Round::Floor
);
788 if (boundingRect
.height() < minHeight
)
790 const int nDiff
= minHeight
- boundingRect
.height();
791 boundingRect
.setHeight(boundingRect
.height() + nDiff
);
792 contentRect
.setHeight(contentRect
.height() + nDiff
);
798 case ControlType::Checkbox
:
799 if (part
== ControlPart::Entire
)
801 styleOption
.state
= vclStateValue2StateFlag(controlState
, val
);
803 int nWidth
= pixelMetric(QStyle::PM_IndicatorWidth
, &styleOption
);
804 int nHeight
= pixelMetric(QStyle::PM_IndicatorHeight
, &styleOption
);
805 contentRect
.setSize(upscale(QSize(nWidth
, nHeight
), Round::Ceil
));
807 int nHMargin
= pixelMetric(QStyle::PM_FocusFrameHMargin
, &styleOption
);
808 int nVMargin
= pixelMetric(QStyle::PM_FocusFrameVMargin
, &styleOption
);
809 contentRect
.adjust(0, 0, 2 * upscale(nHMargin
, Round::Ceil
),
810 2 * upscale(nVMargin
, Round::Ceil
));
812 boundingRect
= contentRect
;
816 case ControlType::Combobox
:
817 case ControlType::Listbox
:
819 QStyleOptionComboBox cbo
;
821 cbo
.rect
= downscale(QRect(0, 0, contentRect
.width(), contentRect
.height()));
822 cbo
.state
= vclStateValue2StateFlag(controlState
, val
);
826 case ControlPart::Entire
:
828 // find out the minimum size that should be used
829 // assume contents is a text line
830 QSize aContentSize
= downscale(contentRect
.size(), Round::Ceil
);
831 QFontMetrics
aFontMetrics(QApplication::font());
832 aContentSize
.setHeight(aFontMetrics
.height());
833 QSize aMinSize
= upscale(
834 sizeFromContents(QStyle::CT_ComboBox
, &cbo
, aContentSize
), Round::Ceil
);
835 if (aMinSize
.height() > contentRect
.height())
836 contentRect
.setHeight(aMinSize
.height());
837 boundingRect
= contentRect
;
841 case ControlPart::ButtonDown
:
843 contentRect
= upscale(
844 subControlRect(QStyle::CC_ComboBox
, &cbo
, QStyle::SC_ComboBoxArrow
));
845 contentRect
.translate(boundingRect
.left(), boundingRect
.top());
849 case ControlPart::SubEdit
:
851 contentRect
= upscale(
852 subControlRect(QStyle::CC_ComboBox
, &cbo
, QStyle::SC_ComboBoxEditField
));
853 contentRect
.translate(boundingRect
.left(), boundingRect
.top());
862 case ControlType::Spinbox
:
864 QStyleOptionSpinBox sbo
;
867 sbo
.rect
= downscale(QRect(0, 0, contentRect
.width(), contentRect
.height()));
868 sbo
.state
= vclStateValue2StateFlag(controlState
, val
);
872 case ControlPart::Entire
:
874 QSize aContentSize
= downscale(contentRect
.size(), Round::Ceil
);
875 QFontMetrics
aFontMetrics(QApplication::font());
876 aContentSize
.setHeight(aFontMetrics
.height());
877 QSize aMinSize
= upscale(
878 sizeFromContents(QStyle::CT_SpinBox
, &sbo
, aContentSize
), Round::Ceil
);
879 if (aMinSize
.height() > contentRect
.height())
880 contentRect
.setHeight(aMinSize
.height());
881 boundingRect
= contentRect
;
885 case ControlPart::ButtonUp
:
887 = upscale(subControlRect(QStyle::CC_SpinBox
, &sbo
, QStyle::SC_SpinBoxUp
));
888 contentRect
.translate(boundingRect
.left(), boundingRect
.top());
891 case ControlPart::ButtonDown
:
893 = upscale(subControlRect(QStyle::CC_SpinBox
, &sbo
, QStyle::SC_SpinBoxDown
));
894 contentRect
.translate(boundingRect
.left(), boundingRect
.top());
897 case ControlPart::SubEdit
:
898 contentRect
= upscale(
899 subControlRect(QStyle::CC_SpinBox
, &sbo
, QStyle::SC_SpinBoxEditField
));
900 contentRect
.translate(boundingRect
.left(), boundingRect
.top());
908 case ControlType::MenuPopup
:
913 case ControlPart::MenuItemCheckMark
:
914 h
= upscale(pixelMetric(QStyle::PM_IndicatorHeight
), Round::Floor
);
915 w
= upscale(pixelMetric(QStyle::PM_IndicatorWidth
), Round::Floor
);
918 case ControlPart::MenuItemRadioMark
:
919 h
= upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorHeight
), Round::Floor
);
920 w
= upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorWidth
), Round::Floor
);
928 contentRect
= QRect(0, 0, w
, h
);
929 boundingRect
= contentRect
;
933 case ControlType::Frame
:
935 if (part
== ControlPart::Border
)
937 int nFrameWidth
= upscale(pixelMetric(QStyle::PM_DefaultFrameWidth
), Round::Ceil
);
938 contentRect
.adjust(nFrameWidth
, nFrameWidth
, -nFrameWidth
, -nFrameWidth
);
943 case ControlType::Radiobutton
:
945 const int h
= upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorHeight
), Round::Ceil
);
946 const int w
= upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorWidth
), Round::Ceil
);
948 contentRect
= QRect(boundingRect
.left(), boundingRect
.top(), w
, h
);
949 int nHMargin
= pixelMetric(QStyle::PM_FocusFrameHMargin
, &styleOption
);
950 int nVMargin
= pixelMetric(QStyle::PM_FocusFrameVMargin
, &styleOption
);
951 contentRect
.adjust(0, 0, upscale(2 * nHMargin
, Round::Ceil
),
952 upscale(2 * nVMargin
, Round::Ceil
));
953 boundingRect
= contentRect
;
958 case ControlType::Slider
:
960 const int w
= upscale(pixelMetric(QStyle::PM_SliderLength
), Round::Ceil
);
961 if (part
== ControlPart::ThumbHorz
)
964 = QRect(boundingRect
.left(), boundingRect
.top(), w
, boundingRect
.height());
965 boundingRect
= contentRect
;
968 else if (part
== ControlPart::ThumbVert
)
971 = QRect(boundingRect
.left(), boundingRect
.top(), boundingRect
.width(), w
);
972 boundingRect
= contentRect
;
977 case ControlType::Toolbar
:
979 const int nWorH
= upscale(pixelMetric(QStyle::PM_ToolBarHandleExtent
), Round::Ceil
);
980 if (part
== ControlPart::ThumbHorz
)
983 = QRect(boundingRect
.left(), boundingRect
.top(), boundingRect
.width(), nWorH
);
984 boundingRect
= contentRect
;
987 else if (part
== ControlPart::ThumbVert
)
990 = QRect(boundingRect
.left(), boundingRect
.top(), nWorH
, boundingRect
.height());
991 boundingRect
= contentRect
;
994 else if (part
== ControlPart::Button
)
996 QStyleOptionToolButton option
;
997 option
.arrowType
= Qt::NoArrow
;
998 option
.features
= QStyleOptionToolButton::None
;
999 option
.rect
= downscale(QRect({ 0, 0 }, contentRect
.size()));
1000 contentRect
= upscale(
1001 subControlRect(QStyle::CC_ToolButton
, &option
, QStyle::SC_ToolButton
));
1002 boundingRect
= contentRect
;
1007 case ControlType::Scrollbar
:
1009 // core can't handle 3-button scrollbars well, so we fix that in hitTestNativeControl(),
1010 // for the rest also provide the track area (i.e. area not taken by buttons)
1011 if (part
== ControlPart::TrackVertArea
|| part
== ControlPart::TrackHorzArea
)
1013 QStyleOptionSlider option
;
1014 bool horizontal
= (part
== ControlPart::TrackHorzArea
); //horizontal or vertical
1015 option
.orientation
= horizontal
? Qt::Horizontal
: Qt::Vertical
;
1017 option
.state
|= QStyle::State_Horizontal
;
1018 // getNativeControlRegion usually gets ImplControlValue as 'val' (i.e. not the proper
1019 // subclass), so use random sensible values (doesn't matter anyway, as the wanted
1020 // geometry here depends only on button sizes)
1021 option
.maximum
= 10;
1023 option
.sliderPosition
= option
.sliderValue
= 4;
1024 option
.pageStep
= 2;
1025 // Adjust coordinates to make the widget appear to be at (0,0), i.e. make
1026 // widget and screen coordinates the same. QStyle functions should use screen
1027 // coordinates but at least QPlastiqueStyle::subControlRect() is buggy
1028 // and sometimes uses widget coordinates.
1029 option
.rect
= downscale(QRect({ 0, 0 }, contentRect
.size()));
1030 contentRect
= upscale(
1031 subControlRect(QStyle::CC_ScrollBar
, &option
, QStyle::SC_ScrollBarGroove
));
1032 contentRect
.translate(boundingRect
.left()
1033 - (contentRect
.width() - boundingRect
.width()),
1034 boundingRect
.top());
1035 boundingRect
= contentRect
;
1040 case ControlType::TabItem
:
1042 QStyleOptionTab sot
;
1043 fillQStyleOptionTab(val
, sot
);
1044 QSize aMinSize
= upscale(sizeFromContents(QStyle::CT_TabBarTab
, &sot
,
1045 downscale(contentRect
.size(), Round::Ceil
)),
1047 contentRect
.setSize(aMinSize
);
1048 boundingRect
= contentRect
;
1052 case ControlType::TabPane
:
1054 const TabPaneValue
& rValue
= static_cast<const TabPaneValue
&>(val
);
1055 QStyleOptionTabWidgetFrame sotwf
;
1056 fullQStyleOptionTabWidgetFrame(sotwf
, true);
1058 std::max(rValue
.m_aTabHeaderRect
.GetWidth(), controlRegion
.GetWidth()),
1059 rValue
.m_aTabHeaderRect
.GetHeight() + controlRegion
.GetHeight());
1060 QSize aMinSize
= upscale(
1061 sizeFromContents(QStyle::CT_TabWidget
, &sotwf
, downscale(contentSize
, Round::Ceil
)),
1063 contentRect
.setSize(aMinSize
);
1064 boundingRect
= contentRect
;
1073 nativeBoundingRegion
= toRectangle(boundingRect
);
1074 nativeContentRegion
= toRectangle(contentRect
);
1080 /** Test whether the position is in the native widget.
1081 If the return value is true, bIsInside contains information whether
1082 aPos was or was not inside the native widget specified by the
1083 nType/nPart combination.
1085 bool QtGraphics_Controls::hitTestNativeControl(ControlType nType
, ControlPart nPart
,
1086 const tools::Rectangle
& rControlRegion
,
1087 const Point
& rPos
, bool& rIsInside
)
1089 if (nType
== ControlType::Scrollbar
)
1091 if (nPart
!= ControlPart::ButtonUp
&& nPart
!= ControlPart::ButtonDown
1092 && nPart
!= ControlPart::ButtonLeft
&& nPart
!= ControlPart::ButtonRight
)
1093 { // we adjust only for buttons (because some scrollbars have 3 buttons,
1094 // and LO core doesn't handle such scrollbars well)
1098 bool bHorizontal
= (nPart
== ControlPart::ButtonLeft
|| nPart
== ControlPart::ButtonRight
);
1099 QRect rect
= toQRect(rControlRegion
);
1100 QPoint
pos(rPos
.X(), rPos
.Y());
1101 // Adjust coordinates to make the widget appear to be at (0,0), i.e. make
1102 // widget and screen coordinates the same. QStyle functions should use screen
1103 // coordinates but at least QPlastiqueStyle::subControlRect() is buggy
1104 // and sometimes uses widget coordinates.
1105 pos
-= rect
.topLeft();
1107 QStyleOptionSlider options
;
1108 options
.orientation
= bHorizontal
? Qt::Horizontal
: Qt::Vertical
;
1110 options
.state
|= QStyle::State_Horizontal
;
1111 options
.rect
= rect
;
1112 // some random sensible values, since we call this code only for scrollbar buttons,
1113 // the slider position does not exactly matter
1114 options
.maximum
= 10;
1115 options
.minimum
= 0;
1116 options
.sliderPosition
= options
.sliderValue
= 4;
1117 options
.pageStep
= 2;
1118 QStyle::SubControl control
1119 = QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar
, &options
, pos
);
1120 if (nPart
== ControlPart::ButtonUp
|| nPart
== ControlPart::ButtonLeft
)
1121 rIsInside
= (control
== QStyle::SC_ScrollBarSubLine
);
1123 rIsInside
= (control
== QStyle::SC_ScrollBarAddLine
);
1129 inline int QtGraphics_Controls::downscale(int size
, Round eRound
)
1131 return static_cast<int>(eRound
== Round::Ceil
? ceil(size
/ m_rGraphics
.devicePixelRatioF())
1132 : floor(size
/ m_rGraphics
.devicePixelRatioF()));
1135 inline int QtGraphics_Controls::upscale(int size
, Round eRound
)
1137 return static_cast<int>(eRound
== Round::Ceil
? ceil(size
* m_rGraphics
.devicePixelRatioF())
1138 : floor(size
* m_rGraphics
.devicePixelRatioF()));
1141 inline QRect
QtGraphics_Controls::downscale(const QRect
& rect
)
1143 return QRect(downscale(rect
.x(), Round::Floor
), downscale(rect
.y(), Round::Floor
),
1144 downscale(rect
.width(), Round::Ceil
), downscale(rect
.height(), Round::Ceil
));
1147 inline QRect
QtGraphics_Controls::upscale(const QRect
& rect
)
1149 return QRect(upscale(rect
.x(), Round::Floor
), upscale(rect
.y(), Round::Floor
),
1150 upscale(rect
.width(), Round::Ceil
), upscale(rect
.height(), Round::Ceil
));
1153 inline QSize
QtGraphics_Controls::downscale(const QSize
& size
, Round eRound
)
1155 return QSize(downscale(size
.width(), eRound
), downscale(size
.height(), eRound
));
1158 inline QSize
QtGraphics_Controls::upscale(const QSize
& size
, Round eRound
)
1160 return QSize(upscale(size
.width(), eRound
), upscale(size
.height(), eRound
));
1163 inline QPoint
QtGraphics_Controls::upscale(const QPoint
& point
, Round eRound
)
1165 return QPoint(upscale(point
.x(), eRound
), upscale(point
.y(), eRound
));
1168 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */