tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / vcl / qt5 / QtGraphics_Controls.cxx
blob513e236f58049ed7728bc74f754b2bcb32a5efe8
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
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 <QtInstance.hxx>
29 #include <QtTools.hxx>
30 #include <QtGraphicsBase.hxx>
31 #include <vcl/decoview.hxx>
33 /**
34 Conversion function between VCL ControlState together with
35 ImplControlValue and Qt state flags.
36 @param nControlState State of the widget (default, focused, ...) in Native Widget Framework.
37 @param aValue Value held by the widget (on, off, ...)
39 static QStyle::State vclStateValue2StateFlag(ControlState nControlState,
40 const ImplControlValue& aValue)
42 QStyle::State nState
43 = ((nControlState & ControlState::ENABLED) ? QStyle::State_Enabled : QStyle::State_None)
44 | ((nControlState & ControlState::FOCUSED)
45 ? QStyle::State_HasFocus | QStyle::State_KeyboardFocusChange
46 : QStyle::State_None)
47 | ((nControlState & ControlState::PRESSED) ? QStyle::State_Sunken : QStyle::State_None)
48 | ((nControlState & ControlState::SELECTED) ? QStyle::State_Selected : QStyle::State_None)
49 | ((nControlState & ControlState::ROLLOVER) ? QStyle::State_MouseOver
50 : QStyle::State_None);
52 switch (aValue.getTristateVal())
54 case ButtonValue::On:
55 nState |= QStyle::State_On;
56 break;
57 case ButtonValue::Off:
58 nState |= QStyle::State_Off;
59 break;
60 case ButtonValue::Mixed:
61 nState |= QStyle::State_NoChange;
62 break;
63 default:
64 break;
67 return nState;
70 static void lcl_ApplyBackgroundColorToStyleOption(QStyleOption& rOption,
71 const Color& rBackgroundColor)
73 if (rBackgroundColor != COL_AUTO)
75 QColor aColor = toQColor(rBackgroundColor);
76 for (QPalette::ColorRole role : { QPalette::Window, QPalette::Button, QPalette::Base })
77 rOption.palette.setColor(role, aColor);
81 QtGraphics_Controls::QtGraphics_Controls(const QtGraphicsBase& rGraphics)
82 : m_rGraphics(rGraphics)
86 bool QtGraphics_Controls::isNativeControlSupported(ControlType type, ControlPart part)
88 switch (type)
90 case ControlType::Tooltip:
91 case ControlType::Progress:
92 case ControlType::ListNode:
93 return (part == ControlPart::Entire);
95 case ControlType::Pushbutton:
96 case ControlType::Radiobutton:
97 case ControlType::Checkbox:
98 return (part == ControlPart::Entire) || (part == ControlPart::Focus);
100 case ControlType::ListHeader:
101 return (part == ControlPart::Button);
103 case ControlType::Menubar:
104 case ControlType::MenuPopup:
105 case ControlType::Editbox:
106 case ControlType::MultilineEditbox:
107 case ControlType::Combobox:
108 case ControlType::Toolbar:
109 case ControlType::Frame:
110 case ControlType::Scrollbar:
111 case ControlType::WindowBackground:
112 case ControlType::Fixedline:
113 return true;
115 case ControlType::Listbox:
116 return (part == ControlPart::Entire || part == ControlPart::HasBackgroundTexture);
118 case ControlType::Spinbox:
119 return (part == ControlPart::Entire || part == ControlPart::HasBackgroundTexture);
121 case ControlType::Slider:
122 return (part == ControlPart::TrackHorzArea || part == ControlPart::TrackVertArea);
124 case ControlType::TabItem:
125 case ControlType::TabPane:
126 return ((part == ControlPart::Entire) || part == ControlPart::TabPaneWithHeader);
128 default:
129 break;
132 return false;
135 inline int QtGraphics_Controls::pixelMetric(QStyle::PixelMetric metric, const QStyleOption* option,
136 const QWidget* pWidget)
138 return QApplication::style()->pixelMetric(metric, option, pWidget);
141 inline QSize QtGraphics_Controls::sizeFromContents(QStyle::ContentsType type,
142 const QStyleOption* option,
143 const QSize& contentsSize)
145 return QApplication::style()->sizeFromContents(type, option, contentsSize);
148 inline QRect QtGraphics_Controls::subControlRect(QStyle::ComplexControl control,
149 const QStyleOptionComplex* option,
150 QStyle::SubControl subControl)
152 return QApplication::style()->subControlRect(control, option, subControl);
155 inline QRect QtGraphics_Controls::subElementRect(QStyle::SubElement element,
156 const QStyleOption* option)
158 return QApplication::style()->subElementRect(element, option);
161 void QtGraphics_Controls::draw(QStyle::ControlElement element, QStyleOption& rOption, QImage* image,
162 const Color& rBackgroundColor, QStyle::State const state, QRect rect)
164 const QRect targetRect = !rect.isNull() ? rect : image->rect();
166 rOption.state |= state;
167 rOption.rect = downscale(targetRect);
169 lcl_ApplyBackgroundColorToStyleOption(rOption, rBackgroundColor);
171 QPainter painter(image);
172 QApplication::style()->drawControl(element, &rOption, &painter);
175 void QtGraphics_Controls::draw(QStyle::PrimitiveElement element, QStyleOption& rOption,
176 QImage* image, const Color& rBackgroundColor,
177 QStyle::State const state, QRect rect)
179 const QRect targetRect = !rect.isNull() ? rect : image->rect();
181 rOption.state |= state;
182 rOption.rect = downscale(targetRect);
184 lcl_ApplyBackgroundColorToStyleOption(rOption, rBackgroundColor);
186 QPainter painter(image);
187 QApplication::style()->drawPrimitive(element, &rOption, &painter);
190 void QtGraphics_Controls::draw(QStyle::ComplexControl element, QStyleOptionComplex& rOption,
191 QImage* image, const Color& rBackgroundColor,
192 QStyle::State const state)
194 const QRect targetRect = image->rect();
196 rOption.state |= state;
197 rOption.rect = downscale(targetRect);
199 lcl_ApplyBackgroundColorToStyleOption(rOption, rBackgroundColor);
201 QPainter painter(image);
202 QApplication::style()->drawComplexControl(element, &rOption, &painter);
205 void QtGraphics_Controls::drawFrame(QStyle::PrimitiveElement element, QImage* image,
206 const Color& rBackgroundColor, QStyle::State const& state,
207 bool bClip, QStyle::PixelMetric eLineMetric)
209 const int fw = pixelMetric(eLineMetric);
210 QStyleOptionFrame option;
211 option.frameShape = QFrame::StyledPanel;
212 option.state = QStyle::State_Sunken | state;
213 option.lineWidth = fw;
215 QRect aRect = downscale(image->rect());
216 option.rect = aRect;
218 lcl_ApplyBackgroundColorToStyleOption(option, rBackgroundColor);
220 QPainter painter(image);
221 if (bClip)
222 painter.setClipRegion(QRegion(aRect).subtracted(aRect.adjusted(fw, fw, -fw, -fw)));
223 QApplication::style()->drawPrimitive(element, &option, &painter);
226 static QTabBar::Shape lcl_mapTabBarPosition(TabBarPosition eTabPos)
228 switch (eTabPos)
230 case TabBarPosition::Bottom:
231 return QTabBar::RoundedSouth;
232 case TabBarPosition::Left:
233 return QTabBar::RoundedWest;
234 case TabBarPosition::Right:
235 return QTabBar::RoundedEast;
236 case TabBarPosition::Top:
237 return QTabBar::RoundedNorth;
238 default:
239 assert(false && "Unhandled tab bar position");
240 return QTabBar::RoundedNorth;
244 void QtGraphics_Controls::fillQStyleOptionTab(const ImplControlValue& value, QStyleOptionTab& sot)
246 const TabitemValue& rValue = static_cast<const TabitemValue&>(value);
247 if (rValue.isFirst())
248 sot.position = rValue.isLast() ? QStyleOptionTab::OnlyOneTab : QStyleOptionTab::Beginning;
249 else if (rValue.isLast())
250 sot.position = rValue.isFirst() ? QStyleOptionTab::OnlyOneTab : QStyleOptionTab::End;
251 else
252 sot.position = QStyleOptionTab::Middle;
254 sot.shape = lcl_mapTabBarPosition(rValue.meTabBarPosition);
257 void QtGraphics_Controls::fullQStyleOptionTabWidgetFrame(QStyleOptionTabWidgetFrame& option,
258 bool bDownscale)
260 option.state = QStyle::State_Enabled;
261 option.rightCornerWidgetSize = QSize(0, 0);
262 option.leftCornerWidgetSize = QSize(0, 0);
263 int nLineWidth = pixelMetric(QStyle::PM_DefaultFrameWidth);
264 option.lineWidth = bDownscale ? std::max(1, downscale(nLineWidth, Round::Ceil)) : nLineWidth;
265 option.midLineWidth = 0;
266 option.shape = QTabBar::RoundedNorth;
269 bool QtGraphics_Controls::drawNativeControl(ControlType type, ControlPart part,
270 const tools::Rectangle& rControlRegion,
271 ControlState nControlState,
272 const ImplControlValue& value, const OUString&,
273 const Color& rBackgroundColor)
275 bool nativeSupport = isNativeControlSupported(type, part);
276 if (!nativeSupport)
278 assert(!nativeSupport && "drawNativeControl called without native support!");
279 return false;
282 if (m_lastPopupRect.isValid()
283 && (type != ControlType::MenuPopup || part != ControlPart::MenuItem))
284 m_lastPopupRect = QRect();
286 bool returnVal = true;
288 QRect widgetRect = toQRect(rControlRegion);
290 //if no image, or resized, make a new image
291 if (!m_image || m_image->size() != widgetRect.size())
293 m_image.reset(new QImage(widgetRect.width(), widgetRect.height(),
294 QImage::Format_ARGB32_Premultiplied));
295 m_image->setDevicePixelRatio(m_rGraphics.devicePixelRatioF());
298 // Default image color - just once
299 switch (type)
301 case ControlType::MenuPopup:
302 if (part == ControlPart::MenuItemCheckMark || part == ControlPart::MenuItemRadioMark)
304 // it is necessary to fill the background transparently first, as this
305 // is painted after menuitem highlight, otherwise there would be a grey area
306 m_image->fill(Qt::transparent);
307 break;
309 [[fallthrough]]; // QPalette::Window
310 case ControlType::Menubar:
311 case ControlType::WindowBackground:
312 m_image->fill(QApplication::palette().color(QPalette::Window).rgb());
313 break;
314 case ControlType::Tooltip:
315 m_image->fill(QApplication::palette().color(QPalette::ToolTipBase).rgb());
316 break;
317 case ControlType::Scrollbar:
318 if ((part == ControlPart::DrawBackgroundVert)
319 || (part == ControlPart::DrawBackgroundHorz))
321 m_image->fill(QApplication::palette().color(QPalette::Window).rgb());
322 break;
324 [[fallthrough]]; // Qt::transparent
325 default:
326 m_image->fill(Qt::transparent);
327 break;
330 if (type == ControlType::Pushbutton)
332 const PushButtonValue& rPBValue = static_cast<const PushButtonValue&>(value);
333 if (part == ControlPart::Focus)
334 // Nothing to do. Drawing focus separately is not needed because that's
335 // already handled by the ControlState::FOCUSED state being set when
336 // drawing the entire control
337 return true;
338 assert(part == ControlPart::Entire);
339 QStyleOptionButton option;
340 if (nControlState & ControlState::DEFAULT)
341 option.features |= QStyleOptionButton::DefaultButton;
342 if (rPBValue.m_bFlatButton)
343 option.features |= QStyleOptionButton::Flat;
344 draw(QStyle::CE_PushButton, option, m_image.get(), rBackgroundColor,
345 vclStateValue2StateFlag(nControlState, value));
347 else if (type == ControlType::Menubar)
349 if (part == ControlPart::MenuItem)
351 QStyleOptionMenuItem option;
352 option.state = vclStateValue2StateFlag(nControlState, value);
353 if ((nControlState & ControlState::ROLLOVER)
354 && QApplication::style()->styleHint(QStyle::SH_MenuBar_MouseTracking))
355 option.state |= QStyle::State_Selected;
357 if (nControlState
358 & ControlState::SELECTED) // Passing State_Sunken is currently not documented.
359 option.state |= QStyle::State_Sunken; // But some kinds of QStyle interpret it.
361 draw(QStyle::CE_MenuBarItem, option, m_image.get(), rBackgroundColor);
363 else if (part == ControlPart::Entire)
365 QStyleOptionMenuItem option;
366 draw(QStyle::CE_MenuBarEmptyArea, option, m_image.get(), rBackgroundColor,
367 vclStateValue2StateFlag(nControlState, value));
369 else
371 returnVal = false;
374 else if (type == ControlType::MenuPopup)
376 assert(part == ControlPart::MenuItem ? m_lastPopupRect.isValid()
377 : !m_lastPopupRect.isValid());
378 if (part == ControlPart::MenuItem)
380 QStyleOptionMenuItem option;
381 draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor,
382 vclStateValue2StateFlag(nControlState, value));
383 // HACK: LO core first paints the entire popup and only then it paints menu items,
384 // but QMenu::paintEvent() paints popup frame after all items. That means highlighted
385 // items here would paint the highlight over the frame border. Since calls to ControlPart::MenuItem
386 // are always preceded by calls to ControlPart::Entire, just remember the size for the whole
387 // popup (otherwise not possible to get here) and draw the border afterwards.
388 QRect framerect(m_lastPopupRect.topLeft() - widgetRect.topLeft(),
389 widgetRect.size().expandedTo(m_lastPopupRect.size()));
390 QStyleOptionFrame frame;
391 draw(QStyle::PE_FrameMenu, frame, m_image.get(), rBackgroundColor,
392 vclStateValue2StateFlag(nControlState, value), framerect);
394 else if (part == ControlPart::Separator)
396 QStyleOptionMenuItem option;
397 option.menuItemType = QStyleOptionMenuItem::Separator;
398 // Painting the whole menu item area results in different background
399 // with at least Plastique style, so clip only to the separator itself
400 // (QSize( 2, 2 ) is hardcoded in Qt)
401 option.rect = m_image->rect();
402 QSize size = sizeFromContents(QStyle::CT_MenuItem, &option, QSize(2, 2));
403 QRect rect = m_image->rect();
404 QPoint center = rect.center();
405 rect.setHeight(size.height());
406 rect.moveCenter(center);
407 option.state |= vclStateValue2StateFlag(nControlState, value);
408 option.rect = rect;
410 QPainter painter(m_image.get());
411 // don't paint over popup frame border (like the hack above, but here it can be simpler)
412 const int fw = pixelMetric(QStyle::PM_MenuPanelWidth);
413 painter.setClipRect(rect.adjusted(fw, 0, -fw, 0));
414 QApplication::style()->drawControl(QStyle::CE_MenuItem, &option, &painter);
416 else if (part == ControlPart::MenuItemCheckMark || part == ControlPart::MenuItemRadioMark)
418 QStyleOptionMenuItem option;
419 option.checkType = (part == ControlPart::MenuItemCheckMark)
420 ? QStyleOptionMenuItem::NonExclusive
421 : QStyleOptionMenuItem::Exclusive;
422 option.checked = bool(nControlState & ControlState::PRESSED);
423 // widgetRect is now the rectangle for the checkbox/radiobutton itself, but Qt
424 // paints the whole menu item, so translate position (and it'll be clipped);
425 // it is also necessary to fill the background transparently first, as this
426 // is painted after menuitem highlight, otherwise there would be a grey area
427 assert(value.getType() == ControlType::MenuPopup);
428 const MenupopupValue* menuVal = static_cast<const MenupopupValue*>(&value);
429 QRect menuItemRect(toQRect(menuVal->maItemRect));
430 QRect rect(menuItemRect.topLeft() - widgetRect.topLeft(),
431 widgetRect.size().expandedTo(menuItemRect.size()));
432 // checkboxes are always displayed next to images in menus, so are never centered
433 const int focus_size = pixelMetric(QStyle::PM_FocusFrameHMargin);
434 rect.moveTo(-focus_size, rect.y());
435 draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor,
436 vclStateValue2StateFlag(nControlState & ~ControlState::PRESSED, value), rect);
438 else if (part == ControlPart::Entire)
440 QStyleOptionMenuItem option;
441 option.state = vclStateValue2StateFlag(nControlState, value);
442 draw(QStyle::PE_PanelMenu, option, m_image.get(), rBackgroundColor);
443 // Try hard to get any frame!
444 QStyleOptionFrame frame;
445 draw(QStyle::PE_FrameMenu, frame, m_image.get(), rBackgroundColor);
446 draw(QStyle::PE_FrameWindow, frame, m_image.get(), rBackgroundColor);
447 m_lastPopupRect = widgetRect;
449 else
450 returnVal = false;
452 else if ((type == ControlType::Toolbar) && (part == ControlPart::Button))
454 QStyleOptionToolButton option;
456 option.arrowType = Qt::NoArrow;
457 option.subControls = QStyle::SC_ToolButton;
458 option.state = vclStateValue2StateFlag(nControlState, value);
459 option.state |= QStyle::State_Raised | QStyle::State_Enabled | QStyle::State_AutoRaise;
461 draw(QStyle::CC_ToolButton, option, m_image.get(), rBackgroundColor);
463 else if ((type == ControlType::Toolbar) && (part == ControlPart::Entire))
465 QStyleOptionToolBar option;
466 draw(QStyle::CE_ToolBar, option, m_image.get(), rBackgroundColor,
467 vclStateValue2StateFlag(nControlState, value));
469 else if ((type == ControlType::Toolbar)
470 && (part == ControlPart::ThumbVert || part == ControlPart::ThumbHorz))
472 // reduce paint area only to the handle area
473 const int handleExtend = pixelMetric(QStyle::PM_ToolBarHandleExtent);
474 QStyleOption option;
475 QRect aRect = m_image->rect();
476 if (part == ControlPart::ThumbVert)
478 aRect.setWidth(handleExtend);
479 option.state = QStyle::State_Horizontal;
481 else
482 aRect.setHeight(handleExtend);
483 draw(QStyle::PE_IndicatorToolBarHandle, option, m_image.get(), rBackgroundColor,
484 vclStateValue2StateFlag(nControlState, value), aRect);
486 else if (type == ControlType::Editbox || type == ControlType::MultilineEditbox)
488 drawFrame(QStyle::PE_FrameLineEdit, m_image.get(), rBackgroundColor,
489 vclStateValue2StateFlag(nControlState, value), false);
491 else if (type == ControlType::Combobox)
493 QStyleOptionComboBox option;
494 option.editable = true;
495 draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
496 vclStateValue2StateFlag(nControlState, value));
498 else if (type == ControlType::Listbox)
500 QStyleOptionComboBox option;
501 option.editable = false;
502 switch (part)
504 case ControlPart::ListboxWindow:
505 drawFrame(QStyle::PE_Frame, m_image.get(), rBackgroundColor,
506 vclStateValue2StateFlag(nControlState, value), true,
507 QStyle::PM_ComboBoxFrameWidth);
508 break;
509 case ControlPart::SubEdit:
510 draw(QStyle::CE_ComboBoxLabel, option, m_image.get(), rBackgroundColor,
511 vclStateValue2StateFlag(nControlState, value));
512 break;
513 case ControlPart::Entire:
514 draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
515 vclStateValue2StateFlag(nControlState, value));
516 break;
517 case ControlPart::ButtonDown:
518 option.subControls = QStyle::SC_ComboBoxArrow;
519 draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
520 vclStateValue2StateFlag(nControlState, value));
521 break;
522 default:
523 returnVal = false;
524 break;
527 else if (type == ControlType::ListNode)
529 QStyleOption option;
530 option.state = vclStateValue2StateFlag(nControlState, value);
531 option.state |= QStyle::State_Item | QStyle::State_Children;
533 if (value.getTristateVal() == ButtonValue::On)
534 option.state |= QStyle::State_Open;
536 draw(QStyle::PE_IndicatorBranch, option, m_image.get(), rBackgroundColor);
538 else if (type == ControlType::ListHeader)
540 QStyleOptionHeader option;
541 draw(QStyle::CE_HeaderSection, option, m_image.get(), rBackgroundColor,
542 vclStateValue2StateFlag(nControlState, value));
544 else if (type == ControlType::Checkbox)
546 if (part == ControlPart::Entire)
548 QStyleOptionButton option;
549 // clear FOCUSED bit, focus is drawn separately
550 nControlState &= ~ControlState::FOCUSED;
551 draw(QStyle::CE_CheckBox, option, m_image.get(), rBackgroundColor,
552 vclStateValue2StateFlag(nControlState, value));
554 else if (part == ControlPart::Focus)
556 QStyleOptionFocusRect option;
557 draw(QStyle::PE_FrameFocusRect, option, m_image.get(), rBackgroundColor,
558 vclStateValue2StateFlag(nControlState, value));
561 else if (type == ControlType::Scrollbar)
563 if ((part == ControlPart::DrawBackgroundVert) || (part == ControlPart::DrawBackgroundHorz))
565 QStyleOptionSlider option;
566 assert(value.getType() == ControlType::Scrollbar);
567 const ScrollbarValue* sbVal = static_cast<const ScrollbarValue*>(&value);
569 //if the scroll bar is active (aka not degenerate... allow for hover events)
570 if (sbVal->mnVisibleSize < sbVal->mnMax)
571 option.state = QStyle::State_MouseOver;
573 bool horizontal = (part == ControlPart::DrawBackgroundHorz); //horizontal or vertical
574 option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
575 if (horizontal)
576 option.state |= QStyle::State_Horizontal;
578 // If the scrollbar has a mnMin == 0 and mnMax == 0 then mnVisibleSize is set to -1?!
579 // I don't know if a negative mnVisibleSize makes any sense, so just handle this case
580 // without crashing LO with a SIGFPE in the Qt library.
581 const tools::Long nVisibleSize
582 = (sbVal->mnMin == sbVal->mnMax) ? 0 : sbVal->mnVisibleSize;
584 option.minimum = sbVal->mnMin;
585 option.maximum = sbVal->mnMax - nVisibleSize;
586 option.maximum = qMax(option.maximum, option.minimum); // bnc#619772
587 option.sliderValue = sbVal->mnCur;
588 option.sliderPosition = sbVal->mnCur;
589 option.pageStep = nVisibleSize;
590 if (part == ControlPart::DrawBackgroundHorz)
591 option.upsideDown
592 = (QGuiApplication::isRightToLeft()
593 && sbVal->maButton1Rect.Left() < sbVal->maButton2Rect.Left())
594 || (QGuiApplication::isLeftToRight()
595 && sbVal->maButton1Rect.Left() > sbVal->maButton2Rect.Left());
597 //setup the active control... always the slider
598 if (sbVal->mnThumbState & ControlState::ROLLOVER)
599 option.activeSubControls = QStyle::SC_ScrollBarSlider;
601 draw(QStyle::CC_ScrollBar, option, m_image.get(), rBackgroundColor,
602 vclStateValue2StateFlag(nControlState, value));
604 else
606 returnVal = false;
609 else if (type == ControlType::Spinbox)
611 QStyleOptionSpinBox option;
612 option.frame = true;
614 // determine active control
615 if (value.getType() == ControlType::SpinButtons)
617 const SpinbuttonValue* pSpinVal = static_cast<const SpinbuttonValue*>(&value);
618 if (pSpinVal->mnUpperState & ControlState::PRESSED)
619 option.activeSubControls |= QStyle::SC_SpinBoxUp;
620 if (pSpinVal->mnLowerState & ControlState::PRESSED)
621 option.activeSubControls |= QStyle::SC_SpinBoxDown;
622 if (pSpinVal->mnUpperState & ControlState::ENABLED)
623 option.stepEnabled |= QAbstractSpinBox::StepUpEnabled;
624 if (pSpinVal->mnLowerState & ControlState::ENABLED)
625 option.stepEnabled |= QAbstractSpinBox::StepDownEnabled;
626 if (pSpinVal->mnUpperState & ControlState::ROLLOVER)
627 option.state = QStyle::State_MouseOver;
628 if (pSpinVal->mnLowerState & ControlState::ROLLOVER)
629 option.state = QStyle::State_MouseOver;
632 draw(QStyle::CC_SpinBox, option, m_image.get(), rBackgroundColor,
633 vclStateValue2StateFlag(nControlState, value));
635 else if (type == ControlType::Radiobutton)
637 if (part == ControlPart::Entire)
639 QStyleOptionButton option;
640 // clear FOCUSED bit, focus is drawn separately
641 nControlState &= ~ControlState::FOCUSED;
642 draw(QStyle::CE_RadioButton, option, m_image.get(), rBackgroundColor,
643 vclStateValue2StateFlag(nControlState, value));
645 else if (part == ControlPart::Focus)
647 QStyleOptionFocusRect option;
648 draw(QStyle::PE_FrameFocusRect, option, m_image.get(), rBackgroundColor,
649 vclStateValue2StateFlag(nControlState, value));
652 else if (type == ControlType::Tooltip)
654 QStyleOption option;
655 draw(QStyle::PE_PanelTipLabel, option, m_image.get(), rBackgroundColor,
656 vclStateValue2StateFlag(nControlState, value));
658 else if (type == ControlType::Frame)
660 drawFrame(QStyle::PE_Frame, m_image.get(), rBackgroundColor,
661 vclStateValue2StateFlag(nControlState, value));
663 else if (type == ControlType::WindowBackground)
665 // Nothing to do - see "Default image color" switch ^^
667 else if (type == ControlType::Fixedline)
669 QStyleOptionMenuItem option;
670 option.menuItemType = QStyleOptionMenuItem::Separator;
671 option.state = vclStateValue2StateFlag(nControlState, value);
672 option.state |= QStyle::State_Item;
674 draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor);
676 else if (type == ControlType::Slider
677 && (part == ControlPart::TrackHorzArea || part == ControlPart::TrackVertArea))
679 assert(value.getType() == ControlType::Slider);
680 const SliderValue* slVal = static_cast<const SliderValue*>(&value);
681 QStyleOptionSlider option;
683 option.state = vclStateValue2StateFlag(nControlState, value);
684 option.maximum = slVal->mnMax;
685 option.minimum = slVal->mnMin;
686 option.sliderPosition = option.sliderValue = slVal->mnCur;
687 bool horizontal = (part == ControlPart::TrackHorzArea); //horizontal or vertical
688 option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
689 if (horizontal)
690 option.state |= QStyle::State_Horizontal;
692 draw(QStyle::CC_Slider, option, m_image.get(), rBackgroundColor);
694 else if (type == ControlType::Progress && part == ControlPart::Entire)
696 QStyleOptionProgressBar option;
697 option.minimum = 0;
698 option.maximum = widgetRect.width();
699 option.progress = value.getNumericVal();
701 draw(QStyle::CE_ProgressBar, option, m_image.get(), rBackgroundColor,
702 vclStateValue2StateFlag(nControlState, value));
704 else if (type == ControlType::TabItem && part == ControlPart::Entire)
706 QStyleOptionTab sot;
707 fillQStyleOptionTab(value, sot);
708 draw(QStyle::CE_TabBarTabShape, sot, m_image.get(), rBackgroundColor,
709 vclStateValue2StateFlag(nControlState, value));
711 else if (type == ControlType::TabPane && part == ControlPart::Entire)
713 const TabPaneValue& rValue = static_cast<const TabPaneValue&>(value);
715 // get the overlap size for the tabs, so they will overlap the frame
716 QStyleOptionTab tabOverlap;
717 tabOverlap.shape = QTabBar::RoundedNorth;
718 TabPaneValue::m_nOverlap = pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap);
720 QStyleOptionTabWidgetFrame option;
721 fullQStyleOptionTabWidgetFrame(option, false);
722 option.tabBarRect = toQRect(rValue.m_aTabHeaderRect);
723 option.selectedTabRect
724 = rValue.m_aSelectedTabRect.IsEmpty() ? QRect() : toQRect(rValue.m_aSelectedTabRect);
725 option.tabBarSize = toQSize(rValue.m_aTabHeaderRect.GetSize());
726 option.rect = m_image->rect();
727 QRect aRect = subElementRect(QStyle::SE_TabWidgetTabPane, &option);
728 draw(QStyle::PE_FrameTabWidget, option, m_image.get(), rBackgroundColor,
729 vclStateValue2StateFlag(nControlState, value), aRect);
731 else
733 returnVal = false;
736 return returnVal;
739 bool QtGraphics_Controls::getNativeControlRegion(
740 ControlType type, ControlPart part, const tools::Rectangle& controlRegion,
741 ControlState controlState, const ImplControlValue& val, const OUString& rCaption,
742 tools::Rectangle& nativeBoundingRegion, tools::Rectangle& nativeContentRegion)
744 QtInstance& rQtInstance = GetQtInstance();
745 if (!rQtInstance.IsMainThread())
747 bool bRet;
748 rQtInstance.RunInMainThread([&]() {
749 bRet = getNativeControlRegion(type, part, controlRegion, controlState, val, rCaption,
750 nativeBoundingRegion, nativeContentRegion);
752 return bRet;
755 bool retVal = false;
757 QRect boundingRect = toQRect(controlRegion);
758 QRect contentRect = boundingRect;
759 QStyleOptionComplex styleOption;
761 switch (type)
763 // Metrics of the push button
764 case ControlType::Pushbutton:
765 if (part == ControlPart::Entire)
767 styleOption.state = vclStateValue2StateFlag(controlState, val);
769 if (controlState & ControlState::DEFAULT)
771 int size = upscale(pixelMetric(QStyle::PM_ButtonDefaultIndicator, &styleOption),
772 Round::Ceil);
773 boundingRect.adjust(-size, -size, size, size);
774 retVal = true;
777 else if (part == ControlPart::Focus)
778 retVal = true;
779 break;
780 case ControlType::Editbox:
781 case ControlType::MultilineEditbox:
783 // we have to get stable borders, otherwise layout loops.
784 // so we simply only scale the detected borders.
785 QStyleOptionFrame fo;
786 fo.frameShape = QFrame::StyledPanel;
787 fo.state = QStyle::State_Sunken;
788 fo.lineWidth = pixelMetric(QStyle::PM_DefaultFrameWidth);
789 fo.rect = downscale(contentRect);
790 fo.rect.setSize(sizeFromContents(QStyle::CT_LineEdit, &fo, fo.rect.size()));
791 QRect aSubRect = subElementRect(QStyle::SE_LineEditContents, &fo);
793 // VCL tests borders with small defaults before layout, where Qt returns no sub-rect,
794 // so this gets us at least some frame.
795 int nLine = upscale(fo.lineWidth, Round::Ceil);
796 int nLeft = qMin(-nLine, upscale(fo.rect.left() - aSubRect.left(), Round::Floor));
797 int nTop = qMin(-nLine, upscale(fo.rect.top() - aSubRect.top(), Round::Floor));
798 int nRight = qMax(nLine, upscale(fo.rect.right() - aSubRect.right(), Round::Ceil));
799 int nBottom = qMax(nLine, upscale(fo.rect.bottom() - aSubRect.bottom(), Round::Ceil));
800 boundingRect.adjust(nLeft, nTop, nRight, nBottom);
802 // tdf#150451: ensure a minimum size that fits text content + frame at top and bottom.
803 // Themes may use the widget type for determining the actual frame width to use,
804 // so pass a dummy QLineEdit
806 // NOTE: This is currently only done here for the minimum size calculation and
807 // not above because the handling for edit boxes here and in the calling code
808 // currently does all kinds of "interesting" things like doing extra size adjustments
809 // or passing the content rect where the bounding rect would be expected,...
810 // Ideally this should be cleaned up in the callers and all platform integrations
811 // to adhere to what the doc in vcl/inc/WidgetDrawInterface.hxx says, but this
812 // here keeps it working with existing code for now.
813 // (s.a. discussion in https://gerrit.libreoffice.org/c/core/+/146516 for more details)
814 QLineEdit aDummyEdit;
815 const int nFrameWidth = pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, &aDummyEdit);
816 QFontMetrics aFontMetrics(QApplication::font());
817 const int minHeight = upscale(aFontMetrics.height() + 2 * nFrameWidth, Round::Floor);
818 if (boundingRect.height() < minHeight)
820 const int nDiff = minHeight - boundingRect.height();
821 boundingRect.setHeight(boundingRect.height() + nDiff);
822 contentRect.setHeight(contentRect.height() + nDiff);
825 retVal = true;
826 break;
828 case ControlType::Checkbox:
829 if (part == ControlPart::Entire)
831 styleOption.state = vclStateValue2StateFlag(controlState, val);
833 int nWidth = pixelMetric(QStyle::PM_IndicatorWidth, &styleOption);
834 int nHeight = pixelMetric(QStyle::PM_IndicatorHeight, &styleOption);
835 contentRect.setSize(upscale(QSize(nWidth, nHeight), Round::Ceil));
837 int nHMargin = pixelMetric(QStyle::PM_FocusFrameHMargin, &styleOption);
838 int nVMargin = pixelMetric(QStyle::PM_FocusFrameVMargin, &styleOption);
839 contentRect.adjust(0, 0, 2 * upscale(nHMargin, Round::Ceil),
840 2 * upscale(nVMargin, Round::Ceil));
842 boundingRect = contentRect;
843 retVal = true;
845 break;
846 case ControlType::Combobox:
847 case ControlType::Listbox:
849 QStyleOptionComboBox cbo;
851 cbo.rect = downscale(QRect(0, 0, contentRect.width(), contentRect.height()));
852 cbo.state = vclStateValue2StateFlag(controlState, val);
854 switch (part)
856 case ControlPart::Entire:
858 // find out the minimum size that should be used
859 // assume contents is a text line
860 QSize aContentSize = downscale(contentRect.size(), Round::Ceil);
861 QFontMetrics aFontMetrics(QApplication::font());
862 aContentSize.setHeight(aFontMetrics.height());
863 QSize aMinSize = upscale(
864 sizeFromContents(QStyle::CT_ComboBox, &cbo, aContentSize), Round::Ceil);
865 if (aMinSize.height() > contentRect.height())
866 contentRect.setHeight(aMinSize.height());
867 boundingRect = contentRect;
868 retVal = true;
869 break;
871 case ControlPart::ButtonDown:
873 contentRect = upscale(
874 subControlRect(QStyle::CC_ComboBox, &cbo, QStyle::SC_ComboBoxArrow));
875 contentRect.translate(boundingRect.left(), boundingRect.top());
876 retVal = true;
877 break;
879 case ControlPart::SubEdit:
881 contentRect = upscale(
882 subControlRect(QStyle::CC_ComboBox, &cbo, QStyle::SC_ComboBoxEditField));
883 contentRect.translate(boundingRect.left(), boundingRect.top());
884 retVal = true;
885 break;
887 default:
888 break;
890 break;
892 case ControlType::Spinbox:
894 QStyleOptionSpinBox sbo;
895 sbo.frame = true;
897 sbo.rect = downscale(QRect(0, 0, contentRect.width(), contentRect.height()));
898 sbo.state = vclStateValue2StateFlag(controlState, val);
900 switch (part)
902 case ControlPart::Entire:
904 QSize aContentSize = downscale(contentRect.size(), Round::Ceil);
905 QFontMetrics aFontMetrics(QApplication::font());
906 aContentSize.setHeight(aFontMetrics.height());
907 QSize aMinSize = upscale(
908 sizeFromContents(QStyle::CT_SpinBox, &sbo, aContentSize), Round::Ceil);
909 if (aMinSize.height() > contentRect.height())
910 contentRect.setHeight(aMinSize.height());
911 boundingRect = contentRect;
912 retVal = true;
913 break;
915 case ControlPart::ButtonUp:
916 contentRect
917 = upscale(subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxUp));
918 contentRect.translate(boundingRect.left(), boundingRect.top());
919 retVal = true;
920 break;
921 case ControlPart::ButtonDown:
922 contentRect
923 = upscale(subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxDown));
924 contentRect.translate(boundingRect.left(), boundingRect.top());
925 retVal = true;
926 break;
927 case ControlPart::SubEdit:
928 contentRect = upscale(
929 subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxEditField));
930 contentRect.translate(boundingRect.left(), boundingRect.top());
931 retVal = true;
932 break;
933 default:
934 break;
936 break;
938 case ControlType::MenuPopup:
940 int h, w;
941 switch (part)
943 case ControlPart::MenuItemCheckMark:
944 h = upscale(pixelMetric(QStyle::PM_IndicatorHeight), Round::Floor);
945 w = upscale(pixelMetric(QStyle::PM_IndicatorWidth), Round::Floor);
946 retVal = true;
947 break;
948 case ControlPart::MenuItemRadioMark:
949 h = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorHeight), Round::Floor);
950 w = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorWidth), Round::Floor);
951 retVal = true;
952 break;
953 default:
954 break;
956 if (retVal)
958 contentRect = QRect(0, 0, w, h);
959 boundingRect = contentRect;
961 break;
963 case ControlType::Frame:
965 if (part == ControlPart::Border)
967 int nFrameWidth = upscale(pixelMetric(QStyle::PM_DefaultFrameWidth), Round::Ceil);
968 contentRect.adjust(nFrameWidth, nFrameWidth, -nFrameWidth, -nFrameWidth);
969 retVal = true;
971 break;
973 case ControlType::Radiobutton:
975 const int h = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorHeight), Round::Ceil);
976 const int w = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorWidth), Round::Ceil);
978 contentRect = QRect(boundingRect.left(), boundingRect.top(), w, h);
979 int nHMargin = pixelMetric(QStyle::PM_FocusFrameHMargin, &styleOption);
980 int nVMargin = pixelMetric(QStyle::PM_FocusFrameVMargin, &styleOption);
981 contentRect.adjust(0, 0, upscale(2 * nHMargin, Round::Ceil),
982 upscale(2 * nVMargin, Round::Ceil));
983 boundingRect = contentRect;
985 retVal = true;
986 break;
988 case ControlType::Slider:
990 const int w = upscale(pixelMetric(QStyle::PM_SliderLength), Round::Ceil);
991 if (part == ControlPart::ThumbHorz)
993 contentRect
994 = QRect(boundingRect.left(), boundingRect.top(), w, boundingRect.height());
995 boundingRect = contentRect;
996 retVal = true;
998 else if (part == ControlPart::ThumbVert)
1000 contentRect
1001 = QRect(boundingRect.left(), boundingRect.top(), boundingRect.width(), w);
1002 boundingRect = contentRect;
1003 retVal = true;
1005 break;
1007 case ControlType::Toolbar:
1009 const int nWorH = upscale(pixelMetric(QStyle::PM_ToolBarHandleExtent), Round::Ceil);
1010 if (part == ControlPart::ThumbHorz)
1012 contentRect
1013 = QRect(boundingRect.left(), boundingRect.top(), boundingRect.width(), nWorH);
1014 boundingRect = contentRect;
1015 retVal = true;
1017 else if (part == ControlPart::ThumbVert)
1019 contentRect
1020 = QRect(boundingRect.left(), boundingRect.top(), nWorH, boundingRect.height());
1021 boundingRect = contentRect;
1022 retVal = true;
1024 else if (part == ControlPart::Button)
1026 QStyleOptionToolButton option;
1027 option.arrowType = Qt::NoArrow;
1028 option.features = QStyleOptionToolButton::None;
1029 option.rect = downscale(QRect({ 0, 0 }, contentRect.size()));
1030 contentRect = upscale(
1031 subControlRect(QStyle::CC_ToolButton, &option, QStyle::SC_ToolButton));
1032 boundingRect = contentRect;
1033 retVal = true;
1035 break;
1037 case ControlType::Scrollbar:
1039 // core can't handle 3-button scrollbars well, so we fix that in hitTestNativeControl(),
1040 // for the rest also provide the track area (i.e. area not taken by buttons)
1041 if (part == ControlPart::TrackVertArea || part == ControlPart::TrackHorzArea)
1043 QStyleOptionSlider option;
1044 bool horizontal = (part == ControlPart::TrackHorzArea); //horizontal or vertical
1045 option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
1046 if (horizontal)
1047 option.state |= QStyle::State_Horizontal;
1048 // getNativeControlRegion usually gets ImplControlValue as 'val' (i.e. not the proper
1049 // subclass), so use random sensible values (doesn't matter anyway, as the wanted
1050 // geometry here depends only on button sizes)
1051 option.maximum = 10;
1052 option.minimum = 0;
1053 option.sliderPosition = option.sliderValue = 4;
1054 option.pageStep = 2;
1055 // Adjust coordinates to make the widget appear to be at (0,0), i.e. make
1056 // widget and screen coordinates the same. QStyle functions should use screen
1057 // coordinates but at least QPlastiqueStyle::subControlRect() is buggy
1058 // and sometimes uses widget coordinates.
1059 option.rect = downscale(QRect({ 0, 0 }, contentRect.size()));
1060 contentRect = upscale(
1061 subControlRect(QStyle::CC_ScrollBar, &option, QStyle::SC_ScrollBarGroove));
1062 contentRect.translate(boundingRect.left()
1063 - (contentRect.width() - boundingRect.width()),
1064 boundingRect.top());
1065 boundingRect = contentRect;
1066 retVal = true;
1068 break;
1070 case ControlType::TabItem:
1072 QStyleOptionTab sot;
1073 fillQStyleOptionTab(val, sot);
1074 QSize aMinSize = upscale(sizeFromContents(QStyle::CT_TabBarTab, &sot,
1075 downscale(contentRect.size(), Round::Ceil)),
1076 Round::Ceil);
1077 contentRect.setSize(aMinSize);
1078 boundingRect = contentRect;
1079 retVal = true;
1080 break;
1082 case ControlType::TabPane:
1084 const TabPaneValue& rValue = static_cast<const TabPaneValue&>(val);
1085 QStyleOptionTabWidgetFrame sotwf;
1086 fullQStyleOptionTabWidgetFrame(sotwf, true);
1087 QSize contentSize(
1088 std::max(rValue.m_aTabHeaderRect.GetWidth(), controlRegion.GetWidth()),
1089 rValue.m_aTabHeaderRect.GetHeight() + controlRegion.GetHeight());
1090 QSize aMinSize = upscale(
1091 sizeFromContents(QStyle::CT_TabWidget, &sotwf, downscale(contentSize, Round::Ceil)),
1092 Round::Ceil);
1093 contentRect.setSize(aMinSize);
1094 boundingRect = contentRect;
1095 retVal = true;
1096 break;
1098 default:
1099 break;
1101 if (retVal)
1103 nativeBoundingRegion = toRectangle(boundingRect);
1104 nativeContentRegion = toRectangle(contentRect);
1107 return retVal;
1110 /** Test whether the position is in the native widget.
1111 If the return value is true, bIsInside contains information whether
1112 aPos was or was not inside the native widget specified by the
1113 nType/nPart combination.
1115 bool QtGraphics_Controls::hitTestNativeControl(ControlType nType, ControlPart nPart,
1116 const tools::Rectangle& rControlRegion,
1117 const Point& rPos, bool& rIsInside)
1119 if (nType == ControlType::Scrollbar)
1121 if (nPart != ControlPart::ButtonUp && nPart != ControlPart::ButtonDown
1122 && nPart != ControlPart::ButtonLeft && nPart != ControlPart::ButtonRight)
1123 { // we adjust only for buttons (because some scrollbars have 3 buttons,
1124 // and LO core doesn't handle such scrollbars well)
1125 return false;
1127 rIsInside = false;
1128 bool bHorizontal = (nPart == ControlPart::ButtonLeft || nPart == ControlPart::ButtonRight);
1129 QRect rect = toQRect(rControlRegion);
1130 QPoint pos(rPos.X(), rPos.Y());
1131 // Adjust coordinates to make the widget appear to be at (0,0), i.e. make
1132 // widget and screen coordinates the same. QStyle functions should use screen
1133 // coordinates but at least QPlastiqueStyle::subControlRect() is buggy
1134 // and sometimes uses widget coordinates.
1135 pos -= rect.topLeft();
1136 rect.moveTo(0, 0);
1137 QStyleOptionSlider options;
1138 options.orientation = bHorizontal ? Qt::Horizontal : Qt::Vertical;
1139 if (bHorizontal)
1140 options.state |= QStyle::State_Horizontal;
1141 options.rect = rect;
1142 // some random sensible values, since we call this code only for scrollbar buttons,
1143 // the slider position does not exactly matter
1144 options.maximum = 10;
1145 options.minimum = 0;
1146 options.sliderPosition = options.sliderValue = 4;
1147 options.pageStep = 2;
1148 QStyle::SubControl control
1149 = QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar, &options, pos);
1150 if (nPart == ControlPart::ButtonUp || nPart == ControlPart::ButtonLeft)
1151 rIsInside = (control == QStyle::SC_ScrollBarSubLine);
1152 else // DOWN, RIGHT
1153 rIsInside = (control == QStyle::SC_ScrollBarAddLine);
1154 return true;
1156 return false;
1159 inline int QtGraphics_Controls::downscale(int size, Round eRound)
1161 return static_cast<int>(eRound == Round::Ceil ? ceil(size / m_rGraphics.devicePixelRatioF())
1162 : floor(size / m_rGraphics.devicePixelRatioF()));
1165 inline int QtGraphics_Controls::upscale(int size, Round eRound)
1167 return static_cast<int>(eRound == Round::Ceil ? ceil(size * m_rGraphics.devicePixelRatioF())
1168 : floor(size * m_rGraphics.devicePixelRatioF()));
1171 inline QRect QtGraphics_Controls::downscale(const QRect& rect)
1173 return QRect(downscale(rect.x(), Round::Floor), downscale(rect.y(), Round::Floor),
1174 downscale(rect.width(), Round::Ceil), downscale(rect.height(), Round::Ceil));
1177 inline QRect QtGraphics_Controls::upscale(const QRect& rect)
1179 return QRect(upscale(rect.x(), Round::Floor), upscale(rect.y(), Round::Floor),
1180 upscale(rect.width(), Round::Ceil), upscale(rect.height(), Round::Ceil));
1183 inline QSize QtGraphics_Controls::downscale(const QSize& size, Round eRound)
1185 return QSize(downscale(size.width(), eRound), downscale(size.height(), eRound));
1188 inline QSize QtGraphics_Controls::upscale(const QSize& size, Round eRound)
1190 return QSize(upscale(size.width(), eRound), upscale(size.height(), eRound));
1193 inline QPoint QtGraphics_Controls::upscale(const QPoint& point, Round eRound)
1195 return QPoint(upscale(point.x(), eRound), upscale(point.y(), eRound));
1198 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */