Update git submodules
[LibreOffice.git] / vcl / qt5 / Qt5Graphics_Controls.cxx
blob9d677962123413edf8eb2f3737a8bfff91ccb5c5
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <Qt5Graphics_Controls.hxx>
22 #include <QtGui/QPainter>
23 #include <QtWidgets/QApplication>
24 #include <QtWidgets/QFrame>
25 #include <QtWidgets/QLabel>
27 #include <qt5/Qt5Tools.hxx>
28 #include <qt5/Qt5GraphicsBase.hxx>
29 #include <vcl/decoview.hxx>
31 /**
32 Conversion function between VCL ControlState together with
33 ImplControlValue and Qt state flags.
34 @param nControlState State of the widget (default, focused, ...) in Native Widget Framework.
35 @param aValue Value held by the widget (on, off, ...)
37 static QStyle::State vclStateValue2StateFlag(ControlState nControlState,
38 const ImplControlValue& aValue)
40 QStyle::State nState
41 = ((nControlState & ControlState::ENABLED) ? QStyle::State_Enabled : QStyle::State_None)
42 | ((nControlState & ControlState::FOCUSED) ? QStyle::State_HasFocus : QStyle::State_None)
43 | ((nControlState & ControlState::PRESSED) ? QStyle::State_Sunken : QStyle::State_None)
44 | ((nControlState & ControlState::SELECTED) ? QStyle::State_Selected : QStyle::State_None)
45 | ((nControlState & ControlState::ROLLOVER) ? QStyle::State_MouseOver
46 : QStyle::State_None);
48 switch (aValue.getTristateVal())
50 case ButtonValue::On:
51 nState |= QStyle::State_On;
52 break;
53 case ButtonValue::Off:
54 nState |= QStyle::State_Off;
55 break;
56 case ButtonValue::Mixed:
57 nState |= QStyle::State_NoChange;
58 break;
59 default:
60 break;
63 return nState;
66 static void lcl_ApplyBackgroundColorToStyleOption(QStyleOption& rOption,
67 const Color& rBackgroundColor)
69 if (rBackgroundColor != COL_AUTO)
71 QColor aColor = toQColor(rBackgroundColor);
72 for (QPalette::ColorRole role : { QPalette::Window, QPalette::Button, QPalette::Base })
73 rOption.palette.setColor(role, aColor);
77 Qt5Graphics_Controls::Qt5Graphics_Controls(const Qt5GraphicsBase& rGraphics)
78 : m_rGraphics(rGraphics)
82 bool Qt5Graphics_Controls::isNativeControlSupported(ControlType type, ControlPart part)
84 switch (type)
86 case ControlType::Tooltip:
87 case ControlType::Progress:
88 case ControlType::ListNode:
89 return (part == ControlPart::Entire);
91 case ControlType::Radiobutton:
92 case ControlType::Checkbox:
93 return (part == ControlPart::Entire) || (part == ControlPart::Focus);
94 case ControlType::Pushbutton:
95 return (part == ControlPart::Entire);
97 case ControlType::ListHeader:
98 return (part == ControlPart::Button);
100 case ControlType::Menubar:
101 case ControlType::MenuPopup:
102 case ControlType::Editbox:
103 case ControlType::MultilineEditbox:
104 case ControlType::Combobox:
105 case ControlType::Toolbar:
106 case ControlType::Frame:
107 case ControlType::Scrollbar:
108 case ControlType::WindowBackground:
109 case ControlType::Fixedline:
110 return true;
112 case ControlType::Listbox:
113 return (part == ControlPart::Entire || part == ControlPart::HasBackgroundTexture);
115 case ControlType::Spinbox:
116 return (part == ControlPart::Entire || part == ControlPart::HasBackgroundTexture);
118 case ControlType::Slider:
119 return (part == ControlPart::TrackHorzArea || part == ControlPart::TrackVertArea);
121 case ControlType::TabItem:
122 case ControlType::TabPane:
123 return ((part == ControlPart::Entire) || part == ControlPart::TabPaneWithHeader);
125 default:
126 break;
129 return false;
132 inline int Qt5Graphics_Controls::pixelMetric(QStyle::PixelMetric metric, const QStyleOption* option)
134 return QApplication::style()->pixelMetric(metric, option);
137 inline QSize Qt5Graphics_Controls::sizeFromContents(QStyle::ContentsType type,
138 const QStyleOption* option,
139 const QSize& contentsSize)
141 return QApplication::style()->sizeFromContents(type, option, contentsSize);
144 inline QRect Qt5Graphics_Controls::subControlRect(QStyle::ComplexControl control,
145 const QStyleOptionComplex* option,
146 QStyle::SubControl subControl)
148 return QApplication::style()->subControlRect(control, option, subControl);
151 inline QRect Qt5Graphics_Controls::subElementRect(QStyle::SubElement element,
152 const QStyleOption* option)
154 return QApplication::style()->subElementRect(element, option);
157 void Qt5Graphics_Controls::draw(QStyle::ControlElement element, QStyleOption& rOption,
158 QImage* image, const Color& rBackgroundColor,
159 QStyle::State const state, QRect rect)
161 const QRect& targetRect = !rect.isNull() ? rect : image->rect();
163 rOption.state |= state;
164 rOption.rect = downscale(targetRect);
166 lcl_ApplyBackgroundColorToStyleOption(rOption, rBackgroundColor);
168 QPainter painter(image);
169 QApplication::style()->drawControl(element, &rOption, &painter);
172 void Qt5Graphics_Controls::draw(QStyle::PrimitiveElement element, QStyleOption& rOption,
173 QImage* image, const Color& rBackgroundColor,
174 QStyle::State const state, QRect rect)
176 const QRect& targetRect = !rect.isNull() ? rect : image->rect();
178 rOption.state |= state;
179 rOption.rect = downscale(targetRect);
181 lcl_ApplyBackgroundColorToStyleOption(rOption, rBackgroundColor);
183 QPainter painter(image);
184 QApplication::style()->drawPrimitive(element, &rOption, &painter);
187 void Qt5Graphics_Controls::draw(QStyle::ComplexControl element, QStyleOptionComplex& rOption,
188 QImage* image, const Color& rBackgroundColor,
189 QStyle::State const state)
191 const QRect& targetRect = image->rect();
193 rOption.state |= state;
194 rOption.rect = downscale(targetRect);
196 lcl_ApplyBackgroundColorToStyleOption(rOption, rBackgroundColor);
198 QPainter painter(image);
199 QApplication::style()->drawComplexControl(element, &rOption, &painter);
202 void Qt5Graphics_Controls::drawFrame(QStyle::PrimitiveElement element, QImage* image,
203 const Color& rBackgroundColor, QStyle::State const& state,
204 bool bClip, QStyle::PixelMetric eLineMetric)
206 const int fw = pixelMetric(eLineMetric);
207 QStyleOptionFrame option;
208 option.frameShape = QFrame::StyledPanel;
209 option.state = QStyle::State_Sunken | state;
210 option.lineWidth = fw;
212 QRect aRect = downscale(image->rect());
213 option.rect = aRect;
215 lcl_ApplyBackgroundColorToStyleOption(option, rBackgroundColor);
217 QPainter painter(image);
218 if (bClip)
219 painter.setClipRegion(QRegion(aRect).subtracted(aRect.adjusted(fw, fw, -fw, -fw)));
220 QApplication::style()->drawPrimitive(element, &option, &painter);
223 void Qt5Graphics_Controls::fillQStyleOptionTab(const ImplControlValue& value, QStyleOptionTab& sot)
225 const TabitemValue& rValue = static_cast<const TabitemValue&>(value);
226 if (rValue.isFirst())
227 sot.position = rValue.isLast() ? QStyleOptionTab::OnlyOneTab : QStyleOptionTab::Beginning;
228 else if (rValue.isLast())
229 sot.position = rValue.isFirst() ? QStyleOptionTab::OnlyOneTab : QStyleOptionTab::End;
230 else
231 sot.position = QStyleOptionTab::Middle;
234 void Qt5Graphics_Controls::fullQStyleOptionTabWidgetFrame(QStyleOptionTabWidgetFrame& option,
235 bool bDownscale)
237 option.state = QStyle::State_Enabled;
238 option.rightCornerWidgetSize = QSize(0, 0);
239 option.leftCornerWidgetSize = QSize(0, 0);
240 int nLineWidth = pixelMetric(QStyle::PM_DefaultFrameWidth);
241 option.lineWidth = bDownscale ? std::max(1, downscale(nLineWidth, Round::Ceil)) : nLineWidth;
242 option.midLineWidth = 0;
243 option.shape = QTabBar::RoundedNorth;
246 bool Qt5Graphics_Controls::drawNativeControl(ControlType type, ControlPart part,
247 const tools::Rectangle& rControlRegion,
248 ControlState nControlState,
249 const ImplControlValue& value, const OUString&,
250 const Color& rBackgroundColor)
252 bool nativeSupport = isNativeControlSupported(type, part);
253 if (!nativeSupport)
255 assert(!nativeSupport && "drawNativeControl called without native support!");
256 return false;
259 if (m_lastPopupRect.isValid()
260 && (type != ControlType::MenuPopup || part != ControlPart::MenuItem))
261 m_lastPopupRect = QRect();
263 bool returnVal = true;
265 QRect widgetRect = toQRect(rControlRegion);
267 //if no image, or resized, make a new image
268 if (!m_image || m_image->size() != widgetRect.size())
270 m_image.reset(new QImage(widgetRect.width(), widgetRect.height(),
271 QImage::Format_ARGB32_Premultiplied));
272 m_image->setDevicePixelRatio(m_rGraphics.devicePixelRatioF());
275 // Default image color - just once
276 switch (type)
278 case ControlType::MenuPopup:
279 if (part == ControlPart::MenuItemCheckMark || part == ControlPart::MenuItemRadioMark)
281 // it is necessary to fill the background transparently first, as this
282 // is painted after menuitem highlight, otherwise there would be a grey area
283 m_image->fill(Qt::transparent);
284 break;
286 [[fallthrough]]; // QPalette::Window
287 case ControlType::Menubar:
288 case ControlType::WindowBackground:
289 m_image->fill(QApplication::palette().color(QPalette::Window).rgb());
290 break;
291 case ControlType::Tooltip:
292 m_image->fill(QApplication::palette().color(QPalette::ToolTipBase).rgb());
293 break;
294 case ControlType::Scrollbar:
295 if ((part == ControlPart::DrawBackgroundVert)
296 || (part == ControlPart::DrawBackgroundHorz))
298 m_image->fill(QApplication::palette().color(QPalette::Window).rgb());
299 break;
301 [[fallthrough]]; // Qt::transparent
302 default:
303 m_image->fill(Qt::transparent);
304 break;
307 if (type == ControlType::Pushbutton)
309 const PushButtonValue& rPBValue = static_cast<const PushButtonValue&>(value);
310 assert(part == ControlPart::Entire);
311 QStyleOptionButton option;
312 if (nControlState & ControlState::DEFAULT)
313 option.features |= QStyleOptionButton::DefaultButton;
314 if (rPBValue.m_bFlatButton)
315 option.features |= QStyleOptionButton::Flat;
316 draw(QStyle::CE_PushButton, option, m_image.get(), rBackgroundColor,
317 vclStateValue2StateFlag(nControlState, value));
319 else if (type == ControlType::Menubar)
321 if (part == ControlPart::MenuItem)
323 QStyleOptionMenuItem option;
324 option.state = vclStateValue2StateFlag(nControlState, value);
325 if ((nControlState & ControlState::ROLLOVER)
326 && QApplication::style()->styleHint(QStyle::SH_MenuBar_MouseTracking))
327 option.state |= QStyle::State_Selected;
329 if (nControlState
330 & ControlState::SELECTED) // Passing State_Sunken is currently not documented.
331 option.state |= QStyle::State_Sunken; // But some kinds of QStyle interpret it.
333 draw(QStyle::CE_MenuBarItem, option, m_image.get(), rBackgroundColor);
335 else if (part == ControlPart::Entire)
337 QStyleOptionMenuItem option;
338 draw(QStyle::CE_MenuBarEmptyArea, option, m_image.get(), rBackgroundColor,
339 vclStateValue2StateFlag(nControlState, value));
341 else
343 returnVal = false;
346 else if (type == ControlType::MenuPopup)
348 assert(part == ControlPart::MenuItem ? m_lastPopupRect.isValid()
349 : !m_lastPopupRect.isValid());
350 if (part == ControlPart::MenuItem)
352 QStyleOptionMenuItem option;
353 draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor,
354 vclStateValue2StateFlag(nControlState, value));
355 // HACK: LO core first paints the entire popup and only then it paints menu items,
356 // but QMenu::paintEvent() paints popup frame after all items. That means highlighted
357 // items here would paint the highlight over the frame border. Since calls to ControlPart::MenuItem
358 // are always preceded by calls to ControlPart::Entire, just remember the size for the whole
359 // popup (otherwise not possible to get here) and draw the border afterwards.
360 QRect framerect(m_lastPopupRect.topLeft() - widgetRect.topLeft(),
361 widgetRect.size().expandedTo(m_lastPopupRect.size()));
362 QStyleOptionFrame frame;
363 draw(QStyle::PE_FrameMenu, frame, m_image.get(), rBackgroundColor,
364 vclStateValue2StateFlag(nControlState, value), framerect);
366 else if (part == ControlPart::Separator)
368 QStyleOptionMenuItem option;
369 option.menuItemType = QStyleOptionMenuItem::Separator;
370 // Painting the whole menu item area results in different background
371 // with at least Plastique style, so clip only to the separator itself
372 // (QSize( 2, 2 ) is hardcoded in Qt)
373 option.rect = m_image->rect();
374 QSize size = sizeFromContents(QStyle::CT_MenuItem, &option, QSize(2, 2));
375 QRect rect = m_image->rect();
376 QPoint center = rect.center();
377 rect.setHeight(size.height());
378 rect.moveCenter(center);
379 option.state |= vclStateValue2StateFlag(nControlState, value);
380 option.rect = rect;
382 QPainter painter(m_image.get());
383 // don't paint over popup frame border (like the hack above, but here it can be simpler)
384 const int fw = pixelMetric(QStyle::PM_MenuPanelWidth);
385 painter.setClipRect(rect.adjusted(fw, 0, -fw, 0));
386 QApplication::style()->drawControl(QStyle::CE_MenuItem, &option, &painter);
388 else if (part == ControlPart::MenuItemCheckMark || part == ControlPart::MenuItemRadioMark)
390 QStyleOptionMenuItem option;
391 option.checkType = (part == ControlPart::MenuItemCheckMark)
392 ? QStyleOptionMenuItem::NonExclusive
393 : QStyleOptionMenuItem::Exclusive;
394 option.checked = bool(nControlState & ControlState::PRESSED);
395 // widgetRect is now the rectangle for the checkbox/radiobutton itself, but Qt
396 // paints the whole menu item, so translate position (and it'll be clipped);
397 // it is also necessary to fill the background transparently first, as this
398 // is painted after menuitem highlight, otherwise there would be a grey area
399 assert(value.getType() == ControlType::MenuPopup);
400 const MenupopupValue* menuVal = static_cast<const MenupopupValue*>(&value);
401 QRect menuItemRect(toQRect(menuVal->maItemRect));
402 QRect rect(menuItemRect.topLeft() - widgetRect.topLeft(),
403 widgetRect.size().expandedTo(menuItemRect.size()));
404 // checkboxes are always displayed next to images in menus, so are never centered
405 const int focus_size = pixelMetric(QStyle::PM_FocusFrameHMargin);
406 rect.moveTo(-focus_size, rect.y());
407 draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor,
408 vclStateValue2StateFlag(nControlState & ~ControlState::PRESSED, value), rect);
410 else if (part == ControlPart::Entire)
412 QStyleOptionMenuItem option;
413 option.state = vclStateValue2StateFlag(nControlState, value);
414 draw(QStyle::PE_PanelMenu, option, m_image.get(), rBackgroundColor);
415 // Try hard to get any frame!
416 QStyleOptionFrame frame;
417 draw(QStyle::PE_FrameMenu, frame, m_image.get(), rBackgroundColor);
418 draw(QStyle::PE_FrameWindow, frame, m_image.get(), rBackgroundColor);
419 m_lastPopupRect = widgetRect;
421 else
422 returnVal = false;
424 else if ((type == ControlType::Toolbar) && (part == ControlPart::Button))
426 QStyleOptionToolButton option;
428 option.arrowType = Qt::NoArrow;
429 option.subControls = QStyle::SC_ToolButton;
430 option.state = vclStateValue2StateFlag(nControlState, value);
431 option.state |= QStyle::State_Raised | QStyle::State_Enabled | QStyle::State_AutoRaise;
433 draw(QStyle::CC_ToolButton, option, m_image.get(), rBackgroundColor);
435 else if ((type == ControlType::Toolbar) && (part == ControlPart::Entire))
437 QStyleOptionToolBar option;
438 draw(QStyle::CE_ToolBar, option, m_image.get(), rBackgroundColor,
439 vclStateValue2StateFlag(nControlState, value));
441 else if ((type == ControlType::Toolbar)
442 && (part == ControlPart::ThumbVert || part == ControlPart::ThumbHorz))
444 // reduce paint area only to the handle area
445 const int handleExtend = pixelMetric(QStyle::PM_ToolBarHandleExtent);
446 QStyleOption option;
447 QRect aRect = m_image->rect();
448 if (part == ControlPart::ThumbVert)
450 aRect.setWidth(handleExtend);
451 option.state = QStyle::State_Horizontal;
453 else
454 aRect.setHeight(handleExtend);
455 draw(QStyle::PE_IndicatorToolBarHandle, option, m_image.get(), rBackgroundColor,
456 vclStateValue2StateFlag(nControlState, value), aRect);
458 else if (type == ControlType::Editbox || type == ControlType::MultilineEditbox)
460 drawFrame(QStyle::PE_FrameLineEdit, m_image.get(), rBackgroundColor,
461 vclStateValue2StateFlag(nControlState, value), false);
463 else if (type == ControlType::Combobox)
465 QStyleOptionComboBox option;
466 option.editable = true;
467 draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
468 vclStateValue2StateFlag(nControlState, value));
470 else if (type == ControlType::Listbox)
472 QStyleOptionComboBox option;
473 option.editable = false;
474 switch (part)
476 case ControlPart::ListboxWindow:
477 drawFrame(QStyle::PE_Frame, m_image.get(), rBackgroundColor,
478 vclStateValue2StateFlag(nControlState, value), true,
479 QStyle::PM_ComboBoxFrameWidth);
480 break;
481 case ControlPart::SubEdit:
482 draw(QStyle::CE_ComboBoxLabel, option, m_image.get(), rBackgroundColor,
483 vclStateValue2StateFlag(nControlState, value));
484 break;
485 case ControlPart::Entire:
486 draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
487 vclStateValue2StateFlag(nControlState, value));
488 break;
489 case ControlPart::ButtonDown:
490 option.subControls = QStyle::SC_ComboBoxArrow;
491 draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
492 vclStateValue2StateFlag(nControlState, value));
493 break;
494 default:
495 returnVal = false;
496 break;
499 else if (type == ControlType::ListNode)
501 QStyleOption option;
502 option.state = vclStateValue2StateFlag(nControlState, value);
503 option.state |= QStyle::State_Item | QStyle::State_Children;
505 if (value.getTristateVal() == ButtonValue::On)
506 option.state |= QStyle::State_Open;
508 draw(QStyle::PE_IndicatorBranch, option, m_image.get(), rBackgroundColor);
510 else if (type == ControlType::ListHeader)
512 QStyleOptionHeader option;
513 draw(QStyle::CE_HeaderSection, option, m_image.get(), rBackgroundColor,
514 vclStateValue2StateFlag(nControlState, value));
516 else if (type == ControlType::Checkbox)
518 if (part == ControlPart::Entire)
520 QStyleOptionButton option;
521 // clear FOCUSED bit, focus is drawn separately
522 nControlState &= ~ControlState::FOCUSED;
523 draw(QStyle::CE_CheckBox, option, m_image.get(), rBackgroundColor,
524 vclStateValue2StateFlag(nControlState, value));
526 else if (part == ControlPart::Focus)
528 QStyleOptionFocusRect option;
529 draw(QStyle::PE_FrameFocusRect, option, m_image.get(), rBackgroundColor,
530 vclStateValue2StateFlag(nControlState, value));
533 else if (type == ControlType::Scrollbar)
535 if ((part == ControlPart::DrawBackgroundVert) || (part == ControlPart::DrawBackgroundHorz))
537 QStyleOptionSlider option;
538 assert(value.getType() == ControlType::Scrollbar);
539 const ScrollbarValue* sbVal = static_cast<const ScrollbarValue*>(&value);
541 //if the scroll bar is active (aka not degenerate... allow for hover events)
542 if (sbVal->mnVisibleSize < sbVal->mnMax)
543 option.state = QStyle::State_MouseOver;
545 bool horizontal = (part == ControlPart::DrawBackgroundHorz); //horizontal or vertical
546 option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
547 if (horizontal)
548 option.state |= QStyle::State_Horizontal;
550 // If the scrollbar has a mnMin == 0 and mnMax == 0 then mnVisibleSize is set to -1?!
551 // I don't know if a negative mnVisibleSize makes any sense, so just handle this case
552 // without crashing LO with a SIGFPE in the Qt library.
553 const tools::Long nVisibleSize
554 = (sbVal->mnMin == sbVal->mnMax) ? 0 : sbVal->mnVisibleSize;
556 option.minimum = sbVal->mnMin;
557 option.maximum = sbVal->mnMax - nVisibleSize;
558 option.maximum = qMax(option.maximum, option.minimum); // bnc#619772
559 option.sliderValue = sbVal->mnCur;
560 option.sliderPosition = sbVal->mnCur;
561 option.pageStep = nVisibleSize;
562 if (part == ControlPart::DrawBackgroundHorz)
563 option.upsideDown
564 = (QGuiApplication::isRightToLeft()
565 && sbVal->maButton1Rect.Left() < sbVal->maButton2Rect.Left())
566 || (QGuiApplication::isLeftToRight()
567 && sbVal->maButton1Rect.Left() > sbVal->maButton2Rect.Left());
569 //setup the active control... always the slider
570 if (sbVal->mnThumbState & ControlState::ROLLOVER)
571 option.activeSubControls = QStyle::SC_ScrollBarSlider;
573 draw(QStyle::CC_ScrollBar, option, m_image.get(), rBackgroundColor,
574 vclStateValue2StateFlag(nControlState, value));
576 else
578 returnVal = false;
581 else if (type == ControlType::Spinbox)
583 QStyleOptionSpinBox option;
584 option.frame = true;
586 // determine active control
587 if (value.getType() == ControlType::SpinButtons)
589 const SpinbuttonValue* pSpinVal = static_cast<const SpinbuttonValue*>(&value);
590 if (pSpinVal->mnUpperState & ControlState::PRESSED)
591 option.activeSubControls |= QStyle::SC_SpinBoxUp;
592 if (pSpinVal->mnLowerState & ControlState::PRESSED)
593 option.activeSubControls |= QStyle::SC_SpinBoxDown;
594 if (pSpinVal->mnUpperState & ControlState::ENABLED)
595 option.stepEnabled |= QAbstractSpinBox::StepUpEnabled;
596 if (pSpinVal->mnLowerState & ControlState::ENABLED)
597 option.stepEnabled |= QAbstractSpinBox::StepDownEnabled;
598 if (pSpinVal->mnUpperState & ControlState::ROLLOVER)
599 option.state = QStyle::State_MouseOver;
600 if (pSpinVal->mnLowerState & ControlState::ROLLOVER)
601 option.state = QStyle::State_MouseOver;
604 draw(QStyle::CC_SpinBox, option, m_image.get(), rBackgroundColor,
605 vclStateValue2StateFlag(nControlState, value));
607 else if (type == ControlType::Radiobutton)
609 if (part == ControlPart::Entire)
611 QStyleOptionButton option;
612 // clear FOCUSED bit, focus is drawn separately
613 nControlState &= ~ControlState::FOCUSED;
614 draw(QStyle::CE_RadioButton, option, m_image.get(), rBackgroundColor,
615 vclStateValue2StateFlag(nControlState, value));
617 else if (part == ControlPart::Focus)
619 QStyleOptionFocusRect option;
620 draw(QStyle::PE_FrameFocusRect, option, m_image.get(), rBackgroundColor,
621 vclStateValue2StateFlag(nControlState, value));
624 else if (type == ControlType::Tooltip)
626 QStyleOption option;
627 draw(QStyle::PE_PanelTipLabel, option, m_image.get(), rBackgroundColor,
628 vclStateValue2StateFlag(nControlState, value));
630 else if (type == ControlType::Frame)
632 drawFrame(QStyle::PE_Frame, m_image.get(), rBackgroundColor,
633 vclStateValue2StateFlag(nControlState, value));
635 else if (type == ControlType::WindowBackground)
637 // Nothing to do - see "Default image color" switch ^^
639 else if (type == ControlType::Fixedline)
641 QStyleOptionMenuItem option;
642 option.menuItemType = QStyleOptionMenuItem::Separator;
643 option.state = vclStateValue2StateFlag(nControlState, value);
644 option.state |= QStyle::State_Item;
646 draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor);
648 else if (type == ControlType::Slider
649 && (part == ControlPart::TrackHorzArea || part == ControlPart::TrackVertArea))
651 assert(value.getType() == ControlType::Slider);
652 const SliderValue* slVal = static_cast<const SliderValue*>(&value);
653 QStyleOptionSlider option;
655 option.state = vclStateValue2StateFlag(nControlState, value);
656 option.maximum = slVal->mnMax;
657 option.minimum = slVal->mnMin;
658 option.sliderPosition = option.sliderValue = slVal->mnCur;
659 bool horizontal = (part == ControlPart::TrackHorzArea); //horizontal or vertical
660 option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
661 if (horizontal)
662 option.state |= QStyle::State_Horizontal;
664 draw(QStyle::CC_Slider, option, m_image.get(), rBackgroundColor);
666 else if (type == ControlType::Progress && part == ControlPart::Entire)
668 SAL_WNODEPRECATED_DECLARATIONS_PUSH
669 QStyleOptionProgressBarV2 option;
670 SAL_WNODEPRECATED_DECLARATIONS_POP
671 option.minimum = 0;
672 option.maximum = widgetRect.width();
673 option.progress = value.getNumericVal();
675 draw(QStyle::CE_ProgressBar, option, m_image.get(), rBackgroundColor,
676 vclStateValue2StateFlag(nControlState, value));
678 else if (type == ControlType::TabItem && part == ControlPart::Entire)
680 QStyleOptionTab sot;
681 fillQStyleOptionTab(value, sot);
682 draw(QStyle::CE_TabBarTabShape, sot, m_image.get(), rBackgroundColor,
683 vclStateValue2StateFlag(nControlState, value));
685 else if (type == ControlType::TabPane && part == ControlPart::Entire)
687 const TabPaneValue& rValue = static_cast<const TabPaneValue&>(value);
689 // get the overlap size for the tabs, so they will overlap the frame
690 QStyleOptionTab tabOverlap;
691 tabOverlap.shape = QTabBar::RoundedNorth;
692 TabPaneValue::m_nOverlap = pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap);
694 QStyleOptionTabWidgetFrame option;
695 fullQStyleOptionTabWidgetFrame(option, false);
696 option.tabBarRect = toQRect(rValue.m_aTabHeaderRect);
697 option.selectedTabRect
698 = rValue.m_aSelectedTabRect.IsEmpty() ? QRect() : toQRect(rValue.m_aSelectedTabRect);
699 option.tabBarSize = toQSize(rValue.m_aTabHeaderRect.GetSize());
700 option.rect = m_image->rect();
701 QRect aRect = subElementRect(QStyle::SE_TabWidgetTabPane, &option);
702 draw(QStyle::PE_FrameTabWidget, option, m_image.get(), rBackgroundColor,
703 vclStateValue2StateFlag(nControlState, value), aRect);
705 else
707 returnVal = false;
710 return returnVal;
713 bool Qt5Graphics_Controls::getNativeControlRegion(ControlType type, ControlPart part,
714 const tools::Rectangle& controlRegion,
715 ControlState controlState,
716 const ImplControlValue& val, const OUString&,
717 tools::Rectangle& nativeBoundingRegion,
718 tools::Rectangle& nativeContentRegion)
720 bool retVal = false;
722 QRect boundingRect = toQRect(controlRegion);
723 QRect contentRect = boundingRect;
724 QStyleOptionComplex styleOption;
726 switch (type)
728 // Metrics of the push button
729 case ControlType::Pushbutton:
730 if (part == ControlPart::Entire)
732 styleOption.state = vclStateValue2StateFlag(controlState, val);
734 if (controlState & ControlState::DEFAULT)
736 int size = upscale(pixelMetric(QStyle::PM_ButtonDefaultIndicator, &styleOption),
737 Round::Ceil);
738 boundingRect.adjust(-size, -size, size, size);
739 retVal = true;
742 break;
743 case ControlType::Editbox:
744 case ControlType::MultilineEditbox:
746 // we have to get stable borders, otherwise layout loops.
747 // so we simply only scale the detected borders.
748 QStyleOptionFrame fo;
749 fo.frameShape = QFrame::StyledPanel;
750 fo.state = QStyle::State_Sunken;
751 fo.lineWidth = pixelMetric(QStyle::PM_DefaultFrameWidth);
752 fo.rect = downscale(contentRect);
753 fo.rect.setSize(sizeFromContents(QStyle::CT_LineEdit, &fo, fo.rect.size()));
754 QRect aSubRect = subElementRect(QStyle::SE_LineEditContents, &fo);
756 // VCL tests borders with small defaults before layout, where Qt returns no sub-rect,
757 // so this gets us at least some frame.
758 int nLine = upscale(fo.lineWidth, Round::Ceil);
759 int nLeft = qMin(-nLine, upscale(fo.rect.left() - aSubRect.left(), Round::Floor));
760 int nTop = qMin(-nLine, upscale(fo.rect.top() - aSubRect.top(), Round::Floor));
761 int nRight = qMax(nLine, upscale(fo.rect.right() - aSubRect.right(), Round::Ceil));
762 int nBottom = qMax(nLine, upscale(fo.rect.bottom() - aSubRect.bottom(), Round::Ceil));
763 boundingRect.adjust(nLeft, nTop, nRight, nBottom);
764 retVal = true;
765 break;
767 case ControlType::Checkbox:
768 if (part == ControlPart::Entire)
770 styleOption.state = vclStateValue2StateFlag(controlState, val);
772 int nWidth = pixelMetric(QStyle::PM_IndicatorWidth, &styleOption);
773 int nHeight = pixelMetric(QStyle::PM_IndicatorHeight, &styleOption);
774 contentRect.setSize(upscale(QSize(nWidth, nHeight), Round::Ceil));
776 int nHMargin = pixelMetric(QStyle::PM_FocusFrameHMargin, &styleOption);
777 int nVMargin = pixelMetric(QStyle::PM_FocusFrameVMargin, &styleOption);
778 contentRect.adjust(0, 0, 2 * upscale(nHMargin, Round::Ceil),
779 2 * upscale(nVMargin, Round::Ceil));
781 boundingRect = contentRect;
782 retVal = true;
784 break;
785 case ControlType::Combobox:
786 case ControlType::Listbox:
788 QStyleOptionComboBox cbo;
790 cbo.rect = downscale(QRect(0, 0, contentRect.width(), contentRect.height()));
791 cbo.state = vclStateValue2StateFlag(controlState, val);
793 switch (part)
795 case ControlPart::Entire:
797 // find out the minimum size that should be used
798 // assume contents is a text line
799 QSize aContentSize = downscale(contentRect.size(), Round::Ceil);
800 aContentSize.setHeight(QApplication::fontMetrics().height());
801 QSize aMinSize = upscale(
802 sizeFromContents(QStyle::CT_ComboBox, &cbo, aContentSize), Round::Ceil);
803 if (aMinSize.height() > contentRect.height())
804 contentRect.setHeight(aMinSize.height());
805 boundingRect = contentRect;
806 retVal = true;
807 break;
809 case ControlPart::ButtonDown:
811 contentRect = upscale(
812 subControlRect(QStyle::CC_ComboBox, &cbo, QStyle::SC_ComboBoxArrow));
813 contentRect.translate(boundingRect.left(), boundingRect.top());
814 retVal = true;
815 break;
817 case ControlPart::SubEdit:
819 contentRect = upscale(
820 subControlRect(QStyle::CC_ComboBox, &cbo, QStyle::SC_ComboBoxEditField));
821 contentRect.translate(boundingRect.left(), boundingRect.top());
822 retVal = true;
823 break;
825 default:
826 break;
828 break;
830 case ControlType::Spinbox:
832 QStyleOptionSpinBox sbo;
833 sbo.frame = true;
835 sbo.rect = downscale(QRect(0, 0, contentRect.width(), contentRect.height()));
836 sbo.state = vclStateValue2StateFlag(controlState, val);
838 switch (part)
840 case ControlPart::Entire:
842 QSize aContentSize = downscale(contentRect.size(), Round::Ceil);
843 aContentSize.setHeight(QApplication::fontMetrics().height());
844 QSize aMinSize = upscale(
845 sizeFromContents(QStyle::CT_SpinBox, &sbo, aContentSize), Round::Ceil);
846 if (aMinSize.height() > contentRect.height())
847 contentRect.setHeight(aMinSize.height());
848 boundingRect = contentRect;
849 retVal = true;
850 break;
852 case ControlPart::ButtonUp:
853 contentRect
854 = upscale(subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxUp));
855 contentRect.translate(boundingRect.left(), boundingRect.top());
856 retVal = true;
857 break;
858 case ControlPart::ButtonDown:
859 contentRect
860 = upscale(subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxDown));
861 contentRect.translate(boundingRect.left(), boundingRect.top());
862 retVal = true;
863 break;
864 case ControlPart::SubEdit:
865 contentRect = upscale(
866 subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxEditField));
867 contentRect.translate(boundingRect.left(), boundingRect.top());
868 retVal = true;
869 break;
870 default:
871 break;
873 break;
875 case ControlType::MenuPopup:
877 int h, w;
878 switch (part)
880 case ControlPart::MenuItemCheckMark:
881 h = upscale(pixelMetric(QStyle::PM_IndicatorHeight), Round::Floor);
882 w = upscale(pixelMetric(QStyle::PM_IndicatorWidth), Round::Floor);
883 retVal = true;
884 break;
885 case ControlPart::MenuItemRadioMark:
886 h = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorHeight), Round::Floor);
887 w = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorWidth), Round::Floor);
888 retVal = true;
889 break;
890 default:
891 break;
893 if (retVal)
895 contentRect = QRect(0, 0, w, h);
896 boundingRect = contentRect;
898 break;
900 case ControlType::Frame:
902 if (part == ControlPart::Border)
904 int nFrameWidth = upscale(pixelMetric(QStyle::PM_DefaultFrameWidth), Round::Ceil);
905 contentRect.adjust(nFrameWidth, nFrameWidth, -nFrameWidth, -nFrameWidth);
906 retVal = true;
908 break;
910 case ControlType::Radiobutton:
912 const int h = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorHeight), Round::Ceil);
913 const int w = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorWidth), Round::Ceil);
915 contentRect = QRect(boundingRect.left(), boundingRect.top(), w, h);
916 int nHMargin = pixelMetric(QStyle::PM_FocusFrameHMargin, &styleOption);
917 int nVMargin = pixelMetric(QStyle::PM_FocusFrameVMargin, &styleOption);
918 contentRect.adjust(0, 0, upscale(2 * nHMargin, Round::Ceil),
919 upscale(2 * nVMargin, Round::Ceil));
920 boundingRect = contentRect;
922 retVal = true;
923 break;
925 case ControlType::Slider:
927 const int w = upscale(pixelMetric(QStyle::PM_SliderLength), Round::Ceil);
928 if (part == ControlPart::ThumbHorz)
930 contentRect
931 = QRect(boundingRect.left(), boundingRect.top(), w, boundingRect.height());
932 boundingRect = contentRect;
933 retVal = true;
935 else if (part == ControlPart::ThumbVert)
937 contentRect
938 = QRect(boundingRect.left(), boundingRect.top(), boundingRect.width(), w);
939 boundingRect = contentRect;
940 retVal = true;
942 break;
944 case ControlType::Toolbar:
946 const int nWorH = upscale(pixelMetric(QStyle::PM_ToolBarHandleExtent), Round::Ceil);
947 if (part == ControlPart::ThumbHorz)
949 contentRect
950 = QRect(boundingRect.left(), boundingRect.top(), boundingRect.width(), nWorH);
951 boundingRect = contentRect;
952 retVal = true;
954 else if (part == ControlPart::ThumbVert)
956 contentRect
957 = QRect(boundingRect.left(), boundingRect.top(), nWorH, boundingRect.height());
958 boundingRect = contentRect;
959 retVal = true;
961 else if (part == ControlPart::Button)
963 QStyleOptionToolButton option;
964 option.arrowType = Qt::NoArrow;
965 option.features = QStyleOptionToolButton::None;
966 option.rect = downscale(QRect({ 0, 0 }, contentRect.size()));
967 contentRect = upscale(
968 subControlRect(QStyle::CC_ToolButton, &option, QStyle::SC_ToolButton));
969 boundingRect = contentRect;
970 retVal = true;
972 break;
974 case ControlType::Scrollbar:
976 // core can't handle 3-button scrollbars well, so we fix that in hitTestNativeControl(),
977 // for the rest also provide the track area (i.e. area not taken by buttons)
978 if (part == ControlPart::TrackVertArea || part == ControlPart::TrackHorzArea)
980 QStyleOptionSlider option;
981 bool horizontal = (part == ControlPart::TrackHorzArea); //horizontal or vertical
982 option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
983 if (horizontal)
984 option.state |= QStyle::State_Horizontal;
985 // getNativeControlRegion usually gets ImplControlValue as 'val' (i.e. not the proper
986 // subclass), so use random sensible values (doesn't matter anyway, as the wanted
987 // geometry here depends only on button sizes)
988 option.maximum = 10;
989 option.minimum = 0;
990 option.sliderPosition = option.sliderValue = 4;
991 option.pageStep = 2;
992 // Adjust coordinates to make the widget appear to be at (0,0), i.e. make
993 // widget and screen coordinates the same. QStyle functions should use screen
994 // coordinates but at least QPlastiqueStyle::subControlRect() is buggy
995 // and sometimes uses widget coordinates.
996 option.rect = downscale(QRect({ 0, 0 }, contentRect.size()));
997 contentRect = upscale(
998 subControlRect(QStyle::CC_ScrollBar, &option, QStyle::SC_ScrollBarGroove));
999 contentRect.translate(boundingRect.left()
1000 - (contentRect.width() - boundingRect.width()),
1001 boundingRect.top());
1002 boundingRect = contentRect;
1003 retVal = true;
1005 break;
1007 case ControlType::TabItem:
1009 QStyleOptionTab sot;
1010 fillQStyleOptionTab(val, sot);
1011 QSize aMinSize = upscale(sizeFromContents(QStyle::CT_TabBarTab, &sot,
1012 downscale(contentRect.size(), Round::Ceil)),
1013 Round::Ceil);
1014 contentRect.setSize(aMinSize);
1015 boundingRect = contentRect;
1016 retVal = true;
1017 break;
1019 case ControlType::TabPane:
1021 const TabPaneValue& rValue = static_cast<const TabPaneValue&>(val);
1022 QStyleOptionTabWidgetFrame sotwf;
1023 fullQStyleOptionTabWidgetFrame(sotwf, true);
1024 QSize contentSize(
1025 std::max(rValue.m_aTabHeaderRect.GetWidth(), controlRegion.GetWidth()),
1026 rValue.m_aTabHeaderRect.GetHeight() + controlRegion.GetHeight());
1027 QSize aMinSize = upscale(
1028 sizeFromContents(QStyle::CT_TabWidget, &sotwf, downscale(contentSize, Round::Ceil)),
1029 Round::Ceil);
1030 contentRect.setSize(aMinSize);
1031 boundingRect = contentRect;
1032 retVal = true;
1033 break;
1035 default:
1036 break;
1038 if (retVal)
1040 nativeBoundingRegion = toRectangle(boundingRect);
1041 nativeContentRegion = toRectangle(contentRect);
1044 return retVal;
1047 /** Test whether the position is in the native widget.
1048 If the return value is true, bIsInside contains information whether
1049 aPos was or was not inside the native widget specified by the
1050 nType/nPart combination.
1052 bool Qt5Graphics_Controls::hitTestNativeControl(ControlType nType, ControlPart nPart,
1053 const tools::Rectangle& rControlRegion,
1054 const Point& rPos, bool& rIsInside)
1056 if (nType == ControlType::Scrollbar)
1058 if (nPart != ControlPart::ButtonUp && nPart != ControlPart::ButtonDown
1059 && nPart != ControlPart::ButtonLeft && nPart != ControlPart::ButtonRight)
1060 { // we adjust only for buttons (because some scrollbars have 3 buttons,
1061 // and LO core doesn't handle such scrollbars well)
1062 return false;
1064 rIsInside = false;
1065 bool bHorizontal = (nPart == ControlPart::ButtonLeft || nPart == ControlPart::ButtonRight);
1066 QRect rect = toQRect(rControlRegion);
1067 QPoint pos(rPos.X(), rPos.Y());
1068 // Adjust coordinates to make the widget appear to be at (0,0), i.e. make
1069 // widget and screen coordinates the same. QStyle functions should use screen
1070 // coordinates but at least QPlastiqueStyle::subControlRect() is buggy
1071 // and sometimes uses widget coordinates.
1072 pos -= rect.topLeft();
1073 rect.moveTo(0, 0);
1074 QStyleOptionSlider options;
1075 options.orientation = bHorizontal ? Qt::Horizontal : Qt::Vertical;
1076 if (bHorizontal)
1077 options.state |= QStyle::State_Horizontal;
1078 options.rect = rect;
1079 // some random sensible values, since we call this code only for scrollbar buttons,
1080 // the slider position does not exactly matter
1081 options.maximum = 10;
1082 options.minimum = 0;
1083 options.sliderPosition = options.sliderValue = 4;
1084 options.pageStep = 2;
1085 QStyle::SubControl control
1086 = QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar, &options, pos);
1087 if (nPart == ControlPart::ButtonUp || nPart == ControlPart::ButtonLeft)
1088 rIsInside = (control == QStyle::SC_ScrollBarSubLine);
1089 else // DOWN, RIGHT
1090 rIsInside = (control == QStyle::SC_ScrollBarAddLine);
1091 return true;
1093 return false;
1096 inline int Qt5Graphics_Controls::downscale(int size, Round eRound)
1098 return static_cast<int>(eRound == Round::Ceil ? ceil(size / m_rGraphics.devicePixelRatioF())
1099 : floor(size / m_rGraphics.devicePixelRatioF()));
1102 inline int Qt5Graphics_Controls::upscale(int size, Round eRound)
1104 return static_cast<int>(eRound == Round::Ceil ? ceil(size * m_rGraphics.devicePixelRatioF())
1105 : floor(size * m_rGraphics.devicePixelRatioF()));
1108 inline QRect Qt5Graphics_Controls::downscale(const QRect& rect)
1110 return QRect(downscale(rect.x(), Round::Floor), downscale(rect.y(), Round::Floor),
1111 downscale(rect.width(), Round::Ceil), downscale(rect.height(), Round::Ceil));
1114 inline QRect Qt5Graphics_Controls::upscale(const QRect& rect)
1116 return QRect(upscale(rect.x(), Round::Floor), upscale(rect.y(), Round::Floor),
1117 upscale(rect.width(), Round::Ceil), upscale(rect.height(), Round::Ceil));
1120 inline QSize Qt5Graphics_Controls::downscale(const QSize& size, Round eRound)
1122 return QSize(downscale(size.width(), eRound), downscale(size.height(), eRound));
1125 inline QSize Qt5Graphics_Controls::upscale(const QSize& size, Round eRound)
1127 return QSize(upscale(size.width(), eRound), upscale(size.height(), eRound));
1130 inline QPoint Qt5Graphics_Controls::upscale(const QPoint& point, Round eRound)
1132 return QPoint(upscale(point.x(), eRound), upscale(point.y(), eRound));
1135 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */