bump product version to 7.6.3.2-android
[LibreOffice.git] / vcl / qt5 / QtGraphics_Controls.cxx
blob81ab7a7edc7b35c27d958862b55c3c189a95c3d7
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 <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>
32 /**
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)
41 QStyle::State nState
42 = ((nControlState & ControlState::ENABLED) ? QStyle::State_Enabled : QStyle::State_None)
43 | ((nControlState & ControlState::FOCUSED)
44 ? QStyle::State_HasFocus | QStyle::State_KeyboardFocusChange
45 : QStyle::State_None)
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())
53 case ButtonValue::On:
54 nState |= QStyle::State_On;
55 break;
56 case ButtonValue::Off:
57 nState |= QStyle::State_Off;
58 break;
59 case ButtonValue::Mixed:
60 nState |= QStyle::State_NoChange;
61 break;
62 default:
63 break;
66 return nState;
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)
87 switch (type)
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:
112 return true;
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);
127 default:
128 break;
131 return false;
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());
215 option.rect = aRect;
217 lcl_ApplyBackgroundColorToStyleOption(option, rBackgroundColor);
219 QPainter painter(image);
220 if (bClip)
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;
232 else
233 sot.position = QStyleOptionTab::Middle;
236 void QtGraphics_Controls::fullQStyleOptionTabWidgetFrame(QStyleOptionTabWidgetFrame& option,
237 bool bDownscale)
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);
255 if (!nativeSupport)
257 assert(!nativeSupport && "drawNativeControl called without native support!");
258 return false;
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
278 switch (type)
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);
286 break;
288 [[fallthrough]]; // QPalette::Window
289 case ControlType::Menubar:
290 case ControlType::WindowBackground:
291 m_image->fill(QApplication::palette().color(QPalette::Window).rgb());
292 break;
293 case ControlType::Tooltip:
294 m_image->fill(QApplication::palette().color(QPalette::ToolTipBase).rgb());
295 break;
296 case ControlType::Scrollbar:
297 if ((part == ControlPart::DrawBackgroundVert)
298 || (part == ControlPart::DrawBackgroundHorz))
300 m_image->fill(QApplication::palette().color(QPalette::Window).rgb());
301 break;
303 [[fallthrough]]; // Qt::transparent
304 default:
305 m_image->fill(Qt::transparent);
306 break;
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
316 return true;
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;
336 if (nControlState
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));
348 else
350 returnVal = false;
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);
387 option.rect = rect;
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;
428 else
429 returnVal = false;
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);
453 QStyleOption option;
454 QRect aRect = m_image->rect();
455 if (part == ControlPart::ThumbVert)
457 aRect.setWidth(handleExtend);
458 option.state = QStyle::State_Horizontal;
460 else
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;
481 switch (part)
483 case ControlPart::ListboxWindow:
484 drawFrame(QStyle::PE_Frame, m_image.get(), rBackgroundColor,
485 vclStateValue2StateFlag(nControlState, value), true,
486 QStyle::PM_ComboBoxFrameWidth);
487 break;
488 case ControlPart::SubEdit:
489 draw(QStyle::CE_ComboBoxLabel, option, m_image.get(), rBackgroundColor,
490 vclStateValue2StateFlag(nControlState, value));
491 break;
492 case ControlPart::Entire:
493 draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
494 vclStateValue2StateFlag(nControlState, value));
495 break;
496 case ControlPart::ButtonDown:
497 option.subControls = QStyle::SC_ComboBoxArrow;
498 draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
499 vclStateValue2StateFlag(nControlState, value));
500 break;
501 default:
502 returnVal = false;
503 break;
506 else if (type == ControlType::ListNode)
508 QStyleOption option;
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;
554 if (horizontal)
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)
570 option.upsideDown
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));
583 else
585 returnVal = false;
588 else if (type == ControlType::Spinbox)
590 QStyleOptionSpinBox option;
591 option.frame = true;
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)
633 QStyleOption option;
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;
668 if (horizontal)
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;
676 option.minimum = 0;
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)
685 QStyleOptionTab sot;
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);
710 else
712 returnVal = false;
715 return returnVal;
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)
725 bool retVal = false;
727 QRect boundingRect = toQRect(controlRegion);
728 QRect contentRect = boundingRect;
729 QStyleOptionComplex styleOption;
731 switch (type)
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),
742 Round::Ceil);
743 boundingRect.adjust(-size, -size, size, size);
744 retVal = true;
747 else if (part == ControlPart::Focus)
748 retVal = true;
749 break;
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);
795 retVal = true;
796 break;
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;
813 retVal = true;
815 break;
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);
824 switch (part)
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;
838 retVal = true;
839 break;
841 case ControlPart::ButtonDown:
843 contentRect = upscale(
844 subControlRect(QStyle::CC_ComboBox, &cbo, QStyle::SC_ComboBoxArrow));
845 contentRect.translate(boundingRect.left(), boundingRect.top());
846 retVal = true;
847 break;
849 case ControlPart::SubEdit:
851 contentRect = upscale(
852 subControlRect(QStyle::CC_ComboBox, &cbo, QStyle::SC_ComboBoxEditField));
853 contentRect.translate(boundingRect.left(), boundingRect.top());
854 retVal = true;
855 break;
857 default:
858 break;
860 break;
862 case ControlType::Spinbox:
864 QStyleOptionSpinBox sbo;
865 sbo.frame = true;
867 sbo.rect = downscale(QRect(0, 0, contentRect.width(), contentRect.height()));
868 sbo.state = vclStateValue2StateFlag(controlState, val);
870 switch (part)
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;
882 retVal = true;
883 break;
885 case ControlPart::ButtonUp:
886 contentRect
887 = upscale(subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxUp));
888 contentRect.translate(boundingRect.left(), boundingRect.top());
889 retVal = true;
890 break;
891 case ControlPart::ButtonDown:
892 contentRect
893 = upscale(subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxDown));
894 contentRect.translate(boundingRect.left(), boundingRect.top());
895 retVal = true;
896 break;
897 case ControlPart::SubEdit:
898 contentRect = upscale(
899 subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxEditField));
900 contentRect.translate(boundingRect.left(), boundingRect.top());
901 retVal = true;
902 break;
903 default:
904 break;
906 break;
908 case ControlType::MenuPopup:
910 int h, w;
911 switch (part)
913 case ControlPart::MenuItemCheckMark:
914 h = upscale(pixelMetric(QStyle::PM_IndicatorHeight), Round::Floor);
915 w = upscale(pixelMetric(QStyle::PM_IndicatorWidth), Round::Floor);
916 retVal = true;
917 break;
918 case ControlPart::MenuItemRadioMark:
919 h = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorHeight), Round::Floor);
920 w = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorWidth), Round::Floor);
921 retVal = true;
922 break;
923 default:
924 break;
926 if (retVal)
928 contentRect = QRect(0, 0, w, h);
929 boundingRect = contentRect;
931 break;
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);
939 retVal = true;
941 break;
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;
955 retVal = true;
956 break;
958 case ControlType::Slider:
960 const int w = upscale(pixelMetric(QStyle::PM_SliderLength), Round::Ceil);
961 if (part == ControlPart::ThumbHorz)
963 contentRect
964 = QRect(boundingRect.left(), boundingRect.top(), w, boundingRect.height());
965 boundingRect = contentRect;
966 retVal = true;
968 else if (part == ControlPart::ThumbVert)
970 contentRect
971 = QRect(boundingRect.left(), boundingRect.top(), boundingRect.width(), w);
972 boundingRect = contentRect;
973 retVal = true;
975 break;
977 case ControlType::Toolbar:
979 const int nWorH = upscale(pixelMetric(QStyle::PM_ToolBarHandleExtent), Round::Ceil);
980 if (part == ControlPart::ThumbHorz)
982 contentRect
983 = QRect(boundingRect.left(), boundingRect.top(), boundingRect.width(), nWorH);
984 boundingRect = contentRect;
985 retVal = true;
987 else if (part == ControlPart::ThumbVert)
989 contentRect
990 = QRect(boundingRect.left(), boundingRect.top(), nWorH, boundingRect.height());
991 boundingRect = contentRect;
992 retVal = true;
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;
1003 retVal = true;
1005 break;
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;
1016 if (horizontal)
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;
1022 option.minimum = 0;
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;
1036 retVal = true;
1038 break;
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)),
1046 Round::Ceil);
1047 contentRect.setSize(aMinSize);
1048 boundingRect = contentRect;
1049 retVal = true;
1050 break;
1052 case ControlType::TabPane:
1054 const TabPaneValue& rValue = static_cast<const TabPaneValue&>(val);
1055 QStyleOptionTabWidgetFrame sotwf;
1056 fullQStyleOptionTabWidgetFrame(sotwf, true);
1057 QSize contentSize(
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)),
1062 Round::Ceil);
1063 contentRect.setSize(aMinSize);
1064 boundingRect = contentRect;
1065 retVal = true;
1066 break;
1068 default:
1069 break;
1071 if (retVal)
1073 nativeBoundingRegion = toRectangle(boundingRect);
1074 nativeContentRegion = toRectangle(contentRect);
1077 return retVal;
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)
1095 return false;
1097 rIsInside = false;
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();
1106 rect.moveTo(0, 0);
1107 QStyleOptionSlider options;
1108 options.orientation = bHorizontal ? Qt::Horizontal : Qt::Vertical;
1109 if (bHorizontal)
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);
1122 else // DOWN, RIGHT
1123 rIsInside = (control == QStyle::SC_ScrollBarAddLine);
1124 return true;
1126 return false;
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: */