calc: on editing invalidation of view with different zoom is wrong
[LibreOffice.git] / vcl / qt5 / QtGraphics_Controls.cxx
blobc647152606e82b81afa79aa0e0392f6567de8168
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::Radiobutton:
95 case ControlType::Checkbox:
96 return (part == ControlPart::Entire) || (part == ControlPart::Focus);
97 case ControlType::Pushbutton:
98 return (part == ControlPart::Entire);
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 void QtGraphics_Controls::fillQStyleOptionTab(const ImplControlValue& value, QStyleOptionTab& sot)
228 const TabitemValue& rValue = static_cast<const TabitemValue&>(value);
229 if (rValue.isFirst())
230 sot.position = rValue.isLast() ? QStyleOptionTab::OnlyOneTab : QStyleOptionTab::Beginning;
231 else if (rValue.isLast())
232 sot.position = rValue.isFirst() ? QStyleOptionTab::OnlyOneTab : QStyleOptionTab::End;
233 else
234 sot.position = QStyleOptionTab::Middle;
237 void QtGraphics_Controls::fullQStyleOptionTabWidgetFrame(QStyleOptionTabWidgetFrame& option,
238 bool bDownscale)
240 option.state = QStyle::State_Enabled;
241 option.rightCornerWidgetSize = QSize(0, 0);
242 option.leftCornerWidgetSize = QSize(0, 0);
243 int nLineWidth = pixelMetric(QStyle::PM_DefaultFrameWidth);
244 option.lineWidth = bDownscale ? std::max(1, downscale(nLineWidth, Round::Ceil)) : nLineWidth;
245 option.midLineWidth = 0;
246 option.shape = QTabBar::RoundedNorth;
249 bool QtGraphics_Controls::drawNativeControl(ControlType type, ControlPart part,
250 const tools::Rectangle& rControlRegion,
251 ControlState nControlState,
252 const ImplControlValue& value, const OUString&,
253 const Color& rBackgroundColor)
255 bool nativeSupport = isNativeControlSupported(type, part);
256 if (!nativeSupport)
258 assert(!nativeSupport && "drawNativeControl called without native support!");
259 return false;
262 if (m_lastPopupRect.isValid()
263 && (type != ControlType::MenuPopup || part != ControlPart::MenuItem))
264 m_lastPopupRect = QRect();
266 bool returnVal = true;
268 QRect widgetRect = toQRect(rControlRegion);
270 //if no image, or resized, make a new image
271 if (!m_image || m_image->size() != widgetRect.size())
273 m_image.reset(new QImage(widgetRect.width(), widgetRect.height(),
274 QImage::Format_ARGB32_Premultiplied));
275 m_image->setDevicePixelRatio(m_rGraphics.devicePixelRatioF());
278 // Default image color - just once
279 switch (type)
281 case ControlType::MenuPopup:
282 if (part == ControlPart::MenuItemCheckMark || part == ControlPart::MenuItemRadioMark)
284 // it is necessary to fill the background transparently first, as this
285 // is painted after menuitem highlight, otherwise there would be a grey area
286 m_image->fill(Qt::transparent);
287 break;
289 [[fallthrough]]; // QPalette::Window
290 case ControlType::Menubar:
291 case ControlType::WindowBackground:
292 m_image->fill(QApplication::palette().color(QPalette::Window).rgb());
293 break;
294 case ControlType::Tooltip:
295 m_image->fill(QApplication::palette().color(QPalette::ToolTipBase).rgb());
296 break;
297 case ControlType::Scrollbar:
298 if ((part == ControlPart::DrawBackgroundVert)
299 || (part == ControlPart::DrawBackgroundHorz))
301 m_image->fill(QApplication::palette().color(QPalette::Window).rgb());
302 break;
304 [[fallthrough]]; // Qt::transparent
305 default:
306 m_image->fill(Qt::transparent);
307 break;
310 if (type == ControlType::Pushbutton)
312 const PushButtonValue& rPBValue = static_cast<const PushButtonValue&>(value);
313 assert(part == ControlPart::Entire);
314 QStyleOptionButton option;
315 if (nControlState & ControlState::DEFAULT)
316 option.features |= QStyleOptionButton::DefaultButton;
317 if (rPBValue.m_bFlatButton)
318 option.features |= QStyleOptionButton::Flat;
319 draw(QStyle::CE_PushButton, option, m_image.get(), rBackgroundColor,
320 vclStateValue2StateFlag(nControlState, value));
322 else if (type == ControlType::Menubar)
324 if (part == ControlPart::MenuItem)
326 QStyleOptionMenuItem option;
327 option.state = vclStateValue2StateFlag(nControlState, value);
328 if ((nControlState & ControlState::ROLLOVER)
329 && QApplication::style()->styleHint(QStyle::SH_MenuBar_MouseTracking))
330 option.state |= QStyle::State_Selected;
332 if (nControlState
333 & ControlState::SELECTED) // Passing State_Sunken is currently not documented.
334 option.state |= QStyle::State_Sunken; // But some kinds of QStyle interpret it.
336 draw(QStyle::CE_MenuBarItem, option, m_image.get(), rBackgroundColor);
338 else if (part == ControlPart::Entire)
340 QStyleOptionMenuItem option;
341 draw(QStyle::CE_MenuBarEmptyArea, option, m_image.get(), rBackgroundColor,
342 vclStateValue2StateFlag(nControlState, value));
344 else
346 returnVal = false;
349 else if (type == ControlType::MenuPopup)
351 assert(part == ControlPart::MenuItem ? m_lastPopupRect.isValid()
352 : !m_lastPopupRect.isValid());
353 if (part == ControlPart::MenuItem)
355 QStyleOptionMenuItem option;
356 draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor,
357 vclStateValue2StateFlag(nControlState, value));
358 // HACK: LO core first paints the entire popup and only then it paints menu items,
359 // but QMenu::paintEvent() paints popup frame after all items. That means highlighted
360 // items here would paint the highlight over the frame border. Since calls to ControlPart::MenuItem
361 // are always preceded by calls to ControlPart::Entire, just remember the size for the whole
362 // popup (otherwise not possible to get here) and draw the border afterwards.
363 QRect framerect(m_lastPopupRect.topLeft() - widgetRect.topLeft(),
364 widgetRect.size().expandedTo(m_lastPopupRect.size()));
365 QStyleOptionFrame frame;
366 draw(QStyle::PE_FrameMenu, frame, m_image.get(), rBackgroundColor,
367 vclStateValue2StateFlag(nControlState, value), framerect);
369 else if (part == ControlPart::Separator)
371 QStyleOptionMenuItem option;
372 option.menuItemType = QStyleOptionMenuItem::Separator;
373 // Painting the whole menu item area results in different background
374 // with at least Plastique style, so clip only to the separator itself
375 // (QSize( 2, 2 ) is hardcoded in Qt)
376 option.rect = m_image->rect();
377 QSize size = sizeFromContents(QStyle::CT_MenuItem, &option, QSize(2, 2));
378 QRect rect = m_image->rect();
379 QPoint center = rect.center();
380 rect.setHeight(size.height());
381 rect.moveCenter(center);
382 option.state |= vclStateValue2StateFlag(nControlState, value);
383 option.rect = rect;
385 QPainter painter(m_image.get());
386 // don't paint over popup frame border (like the hack above, but here it can be simpler)
387 const int fw = pixelMetric(QStyle::PM_MenuPanelWidth);
388 painter.setClipRect(rect.adjusted(fw, 0, -fw, 0));
389 QApplication::style()->drawControl(QStyle::CE_MenuItem, &option, &painter);
391 else if (part == ControlPart::MenuItemCheckMark || part == ControlPart::MenuItemRadioMark)
393 QStyleOptionMenuItem option;
394 option.checkType = (part == ControlPart::MenuItemCheckMark)
395 ? QStyleOptionMenuItem::NonExclusive
396 : QStyleOptionMenuItem::Exclusive;
397 option.checked = bool(nControlState & ControlState::PRESSED);
398 // widgetRect is now the rectangle for the checkbox/radiobutton itself, but Qt
399 // paints the whole menu item, so translate position (and it'll be clipped);
400 // it is also necessary to fill the background transparently first, as this
401 // is painted after menuitem highlight, otherwise there would be a grey area
402 assert(value.getType() == ControlType::MenuPopup);
403 const MenupopupValue* menuVal = static_cast<const MenupopupValue*>(&value);
404 QRect menuItemRect(toQRect(menuVal->maItemRect));
405 QRect rect(menuItemRect.topLeft() - widgetRect.topLeft(),
406 widgetRect.size().expandedTo(menuItemRect.size()));
407 // checkboxes are always displayed next to images in menus, so are never centered
408 const int focus_size = pixelMetric(QStyle::PM_FocusFrameHMargin);
409 rect.moveTo(-focus_size, rect.y());
410 draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor,
411 vclStateValue2StateFlag(nControlState & ~ControlState::PRESSED, value), rect);
413 else if (part == ControlPart::Entire)
415 QStyleOptionMenuItem option;
416 option.state = vclStateValue2StateFlag(nControlState, value);
417 draw(QStyle::PE_PanelMenu, option, m_image.get(), rBackgroundColor);
418 // Try hard to get any frame!
419 QStyleOptionFrame frame;
420 draw(QStyle::PE_FrameMenu, frame, m_image.get(), rBackgroundColor);
421 draw(QStyle::PE_FrameWindow, frame, m_image.get(), rBackgroundColor);
422 m_lastPopupRect = widgetRect;
424 else
425 returnVal = false;
427 else if ((type == ControlType::Toolbar) && (part == ControlPart::Button))
429 QStyleOptionToolButton option;
431 option.arrowType = Qt::NoArrow;
432 option.subControls = QStyle::SC_ToolButton;
433 option.state = vclStateValue2StateFlag(nControlState, value);
434 option.state |= QStyle::State_Raised | QStyle::State_Enabled | QStyle::State_AutoRaise;
436 draw(QStyle::CC_ToolButton, option, m_image.get(), rBackgroundColor);
438 else if ((type == ControlType::Toolbar) && (part == ControlPart::Entire))
440 QStyleOptionToolBar option;
441 draw(QStyle::CE_ToolBar, option, m_image.get(), rBackgroundColor,
442 vclStateValue2StateFlag(nControlState, value));
444 else if ((type == ControlType::Toolbar)
445 && (part == ControlPart::ThumbVert || part == ControlPart::ThumbHorz))
447 // reduce paint area only to the handle area
448 const int handleExtend = pixelMetric(QStyle::PM_ToolBarHandleExtent);
449 QStyleOption option;
450 QRect aRect = m_image->rect();
451 if (part == ControlPart::ThumbVert)
453 aRect.setWidth(handleExtend);
454 option.state = QStyle::State_Horizontal;
456 else
457 aRect.setHeight(handleExtend);
458 draw(QStyle::PE_IndicatorToolBarHandle, option, m_image.get(), rBackgroundColor,
459 vclStateValue2StateFlag(nControlState, value), aRect);
461 else if (type == ControlType::Editbox || type == ControlType::MultilineEditbox)
463 drawFrame(QStyle::PE_FrameLineEdit, m_image.get(), rBackgroundColor,
464 vclStateValue2StateFlag(nControlState, value), false);
466 else if (type == ControlType::Combobox)
468 QStyleOptionComboBox option;
469 option.editable = true;
470 draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
471 vclStateValue2StateFlag(nControlState, value));
473 else if (type == ControlType::Listbox)
475 QStyleOptionComboBox option;
476 option.editable = false;
477 switch (part)
479 case ControlPart::ListboxWindow:
480 drawFrame(QStyle::PE_Frame, m_image.get(), rBackgroundColor,
481 vclStateValue2StateFlag(nControlState, value), true,
482 QStyle::PM_ComboBoxFrameWidth);
483 break;
484 case ControlPart::SubEdit:
485 draw(QStyle::CE_ComboBoxLabel, option, m_image.get(), rBackgroundColor,
486 vclStateValue2StateFlag(nControlState, value));
487 break;
488 case ControlPart::Entire:
489 draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
490 vclStateValue2StateFlag(nControlState, value));
491 break;
492 case ControlPart::ButtonDown:
493 option.subControls = QStyle::SC_ComboBoxArrow;
494 draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
495 vclStateValue2StateFlag(nControlState, value));
496 break;
497 default:
498 returnVal = false;
499 break;
502 else if (type == ControlType::ListNode)
504 QStyleOption option;
505 option.state = vclStateValue2StateFlag(nControlState, value);
506 option.state |= QStyle::State_Item | QStyle::State_Children;
508 if (value.getTristateVal() == ButtonValue::On)
509 option.state |= QStyle::State_Open;
511 draw(QStyle::PE_IndicatorBranch, option, m_image.get(), rBackgroundColor);
513 else if (type == ControlType::ListHeader)
515 QStyleOptionHeader option;
516 draw(QStyle::CE_HeaderSection, option, m_image.get(), rBackgroundColor,
517 vclStateValue2StateFlag(nControlState, value));
519 else if (type == ControlType::Checkbox)
521 if (part == ControlPart::Entire)
523 QStyleOptionButton option;
524 // clear FOCUSED bit, focus is drawn separately
525 nControlState &= ~ControlState::FOCUSED;
526 draw(QStyle::CE_CheckBox, option, m_image.get(), rBackgroundColor,
527 vclStateValue2StateFlag(nControlState, value));
529 else if (part == ControlPart::Focus)
531 QStyleOptionFocusRect option;
532 draw(QStyle::PE_FrameFocusRect, option, m_image.get(), rBackgroundColor,
533 vclStateValue2StateFlag(nControlState, value));
536 else if (type == ControlType::Scrollbar)
538 if ((part == ControlPart::DrawBackgroundVert) || (part == ControlPart::DrawBackgroundHorz))
540 QStyleOptionSlider option;
541 assert(value.getType() == ControlType::Scrollbar);
542 const ScrollbarValue* sbVal = static_cast<const ScrollbarValue*>(&value);
544 //if the scroll bar is active (aka not degenerate... allow for hover events)
545 if (sbVal->mnVisibleSize < sbVal->mnMax)
546 option.state = QStyle::State_MouseOver;
548 bool horizontal = (part == ControlPart::DrawBackgroundHorz); //horizontal or vertical
549 option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
550 if (horizontal)
551 option.state |= QStyle::State_Horizontal;
553 // If the scrollbar has a mnMin == 0 and mnMax == 0 then mnVisibleSize is set to -1?!
554 // I don't know if a negative mnVisibleSize makes any sense, so just handle this case
555 // without crashing LO with a SIGFPE in the Qt library.
556 const tools::Long nVisibleSize
557 = (sbVal->mnMin == sbVal->mnMax) ? 0 : sbVal->mnVisibleSize;
559 option.minimum = sbVal->mnMin;
560 option.maximum = sbVal->mnMax - nVisibleSize;
561 option.maximum = qMax(option.maximum, option.minimum); // bnc#619772
562 option.sliderValue = sbVal->mnCur;
563 option.sliderPosition = sbVal->mnCur;
564 option.pageStep = nVisibleSize;
565 if (part == ControlPart::DrawBackgroundHorz)
566 option.upsideDown
567 = (QGuiApplication::isRightToLeft()
568 && sbVal->maButton1Rect.Left() < sbVal->maButton2Rect.Left())
569 || (QGuiApplication::isLeftToRight()
570 && sbVal->maButton1Rect.Left() > sbVal->maButton2Rect.Left());
572 //setup the active control... always the slider
573 if (sbVal->mnThumbState & ControlState::ROLLOVER)
574 option.activeSubControls = QStyle::SC_ScrollBarSlider;
576 draw(QStyle::CC_ScrollBar, option, m_image.get(), rBackgroundColor,
577 vclStateValue2StateFlag(nControlState, value));
579 else
581 returnVal = false;
584 else if (type == ControlType::Spinbox)
586 QStyleOptionSpinBox option;
587 option.frame = true;
589 // determine active control
590 if (value.getType() == ControlType::SpinButtons)
592 const SpinbuttonValue* pSpinVal = static_cast<const SpinbuttonValue*>(&value);
593 if (pSpinVal->mnUpperState & ControlState::PRESSED)
594 option.activeSubControls |= QStyle::SC_SpinBoxUp;
595 if (pSpinVal->mnLowerState & ControlState::PRESSED)
596 option.activeSubControls |= QStyle::SC_SpinBoxDown;
597 if (pSpinVal->mnUpperState & ControlState::ENABLED)
598 option.stepEnabled |= QAbstractSpinBox::StepUpEnabled;
599 if (pSpinVal->mnLowerState & ControlState::ENABLED)
600 option.stepEnabled |= QAbstractSpinBox::StepDownEnabled;
601 if (pSpinVal->mnUpperState & ControlState::ROLLOVER)
602 option.state = QStyle::State_MouseOver;
603 if (pSpinVal->mnLowerState & ControlState::ROLLOVER)
604 option.state = QStyle::State_MouseOver;
607 draw(QStyle::CC_SpinBox, option, m_image.get(), rBackgroundColor,
608 vclStateValue2StateFlag(nControlState, value));
610 else if (type == ControlType::Radiobutton)
612 if (part == ControlPart::Entire)
614 QStyleOptionButton option;
615 // clear FOCUSED bit, focus is drawn separately
616 nControlState &= ~ControlState::FOCUSED;
617 draw(QStyle::CE_RadioButton, option, m_image.get(), rBackgroundColor,
618 vclStateValue2StateFlag(nControlState, value));
620 else if (part == ControlPart::Focus)
622 QStyleOptionFocusRect option;
623 draw(QStyle::PE_FrameFocusRect, option, m_image.get(), rBackgroundColor,
624 vclStateValue2StateFlag(nControlState, value));
627 else if (type == ControlType::Tooltip)
629 QStyleOption option;
630 draw(QStyle::PE_PanelTipLabel, option, m_image.get(), rBackgroundColor,
631 vclStateValue2StateFlag(nControlState, value));
633 else if (type == ControlType::Frame)
635 drawFrame(QStyle::PE_Frame, m_image.get(), rBackgroundColor,
636 vclStateValue2StateFlag(nControlState, value));
638 else if (type == ControlType::WindowBackground)
640 // Nothing to do - see "Default image color" switch ^^
642 else if (type == ControlType::Fixedline)
644 QStyleOptionMenuItem option;
645 option.menuItemType = QStyleOptionMenuItem::Separator;
646 option.state = vclStateValue2StateFlag(nControlState, value);
647 option.state |= QStyle::State_Item;
649 draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor);
651 else if (type == ControlType::Slider
652 && (part == ControlPart::TrackHorzArea || part == ControlPart::TrackVertArea))
654 assert(value.getType() == ControlType::Slider);
655 const SliderValue* slVal = static_cast<const SliderValue*>(&value);
656 QStyleOptionSlider option;
658 option.state = vclStateValue2StateFlag(nControlState, value);
659 option.maximum = slVal->mnMax;
660 option.minimum = slVal->mnMin;
661 option.sliderPosition = option.sliderValue = slVal->mnCur;
662 bool horizontal = (part == ControlPart::TrackHorzArea); //horizontal or vertical
663 option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
664 if (horizontal)
665 option.state |= QStyle::State_Horizontal;
667 draw(QStyle::CC_Slider, option, m_image.get(), rBackgroundColor);
669 else if (type == ControlType::Progress && part == ControlPart::Entire)
671 QStyleOptionProgressBar option;
672 option.minimum = 0;
673 option.maximum = widgetRect.width();
674 option.progress = value.getNumericVal();
676 draw(QStyle::CE_ProgressBar, option, m_image.get(), rBackgroundColor,
677 vclStateValue2StateFlag(nControlState, value));
679 else if (type == ControlType::TabItem && part == ControlPart::Entire)
681 QStyleOptionTab sot;
682 fillQStyleOptionTab(value, sot);
683 draw(QStyle::CE_TabBarTabShape, sot, m_image.get(), rBackgroundColor,
684 vclStateValue2StateFlag(nControlState, value));
686 else if (type == ControlType::TabPane && part == ControlPart::Entire)
688 const TabPaneValue& rValue = static_cast<const TabPaneValue&>(value);
690 // get the overlap size for the tabs, so they will overlap the frame
691 QStyleOptionTab tabOverlap;
692 tabOverlap.shape = QTabBar::RoundedNorth;
693 TabPaneValue::m_nOverlap = pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap);
695 QStyleOptionTabWidgetFrame option;
696 fullQStyleOptionTabWidgetFrame(option, false);
697 option.tabBarRect = toQRect(rValue.m_aTabHeaderRect);
698 option.selectedTabRect
699 = rValue.m_aSelectedTabRect.IsEmpty() ? QRect() : toQRect(rValue.m_aSelectedTabRect);
700 option.tabBarSize = toQSize(rValue.m_aTabHeaderRect.GetSize());
701 option.rect = m_image->rect();
702 QRect aRect = subElementRect(QStyle::SE_TabWidgetTabPane, &option);
703 draw(QStyle::PE_FrameTabWidget, option, m_image.get(), rBackgroundColor,
704 vclStateValue2StateFlag(nControlState, value), aRect);
706 else
708 returnVal = false;
711 return returnVal;
714 bool QtGraphics_Controls::getNativeControlRegion(ControlType type, ControlPart part,
715 const tools::Rectangle& controlRegion,
716 ControlState controlState,
717 const ImplControlValue& val, const OUString&,
718 tools::Rectangle& nativeBoundingRegion,
719 tools::Rectangle& nativeContentRegion)
721 bool retVal = false;
723 QRect boundingRect = toQRect(controlRegion);
724 QRect contentRect = boundingRect;
725 QStyleOptionComplex styleOption;
727 switch (type)
729 // Metrics of the push button
730 case ControlType::Pushbutton:
731 if (part == ControlPart::Entire)
733 styleOption.state = vclStateValue2StateFlag(controlState, val);
735 if (controlState & ControlState::DEFAULT)
737 int size = upscale(pixelMetric(QStyle::PM_ButtonDefaultIndicator, &styleOption),
738 Round::Ceil);
739 boundingRect.adjust(-size, -size, size, size);
740 retVal = true;
743 break;
744 case ControlType::Editbox:
745 case ControlType::MultilineEditbox:
747 // we have to get stable borders, otherwise layout loops.
748 // so we simply only scale the detected borders.
749 QStyleOptionFrame fo;
750 fo.frameShape = QFrame::StyledPanel;
751 fo.state = QStyle::State_Sunken;
752 fo.lineWidth = pixelMetric(QStyle::PM_DefaultFrameWidth);
753 fo.rect = downscale(contentRect);
754 fo.rect.setSize(sizeFromContents(QStyle::CT_LineEdit, &fo, fo.rect.size()));
755 QRect aSubRect = subElementRect(QStyle::SE_LineEditContents, &fo);
757 // VCL tests borders with small defaults before layout, where Qt returns no sub-rect,
758 // so this gets us at least some frame.
759 int nLine = upscale(fo.lineWidth, Round::Ceil);
760 int nLeft = qMin(-nLine, upscale(fo.rect.left() - aSubRect.left(), Round::Floor));
761 int nTop = qMin(-nLine, upscale(fo.rect.top() - aSubRect.top(), Round::Floor));
762 int nRight = qMax(nLine, upscale(fo.rect.right() - aSubRect.right(), Round::Ceil));
763 int nBottom = qMax(nLine, upscale(fo.rect.bottom() - aSubRect.bottom(), Round::Ceil));
764 boundingRect.adjust(nLeft, nTop, nRight, nBottom);
766 // tdf#150451: ensure a minimium size that fits text content + frame at top and bottom.
767 // Themes may use the widget type for determining the actual frame width to use,
768 // so pass a dummy QLineEdit
770 // NOTE: This is currently only done here for the minimum size calculation and
771 // not above because the handling for edit boxes here and in the calling code
772 // currently does all kinds of "interesting" things like doing extra size adjustments
773 // or passing the content rect where the bounding rect would be expected,...
774 // Ideally this should be cleaned up in the callers and all platform integrations
775 // to adhere to what the doc in vcl/inc/WidgetDrawInterface.hxx says, but this
776 // here keeps it working with existing code for now.
777 // (s.a. discussion in https://gerrit.libreoffice.org/c/core/+/146516 for more details)
778 QLineEdit aDummyEdit;
779 const int nFrameWidth = pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, &aDummyEdit);
780 QFontMetrics aFontMetrics(QApplication::font());
781 const int minHeight = upscale(aFontMetrics.height() + 2 * nFrameWidth, Round::Floor);
782 if (boundingRect.height() < minHeight)
784 const int nDiff = minHeight - boundingRect.height();
785 boundingRect.setHeight(boundingRect.height() + nDiff);
786 contentRect.setHeight(contentRect.height() + nDiff);
789 retVal = true;
790 break;
792 case ControlType::Checkbox:
793 if (part == ControlPart::Entire)
795 styleOption.state = vclStateValue2StateFlag(controlState, val);
797 int nWidth = pixelMetric(QStyle::PM_IndicatorWidth, &styleOption);
798 int nHeight = pixelMetric(QStyle::PM_IndicatorHeight, &styleOption);
799 contentRect.setSize(upscale(QSize(nWidth, nHeight), Round::Ceil));
801 int nHMargin = pixelMetric(QStyle::PM_FocusFrameHMargin, &styleOption);
802 int nVMargin = pixelMetric(QStyle::PM_FocusFrameVMargin, &styleOption);
803 contentRect.adjust(0, 0, 2 * upscale(nHMargin, Round::Ceil),
804 2 * upscale(nVMargin, Round::Ceil));
806 boundingRect = contentRect;
807 retVal = true;
809 break;
810 case ControlType::Combobox:
811 case ControlType::Listbox:
813 QStyleOptionComboBox cbo;
815 cbo.rect = downscale(QRect(0, 0, contentRect.width(), contentRect.height()));
816 cbo.state = vclStateValue2StateFlag(controlState, val);
818 switch (part)
820 case ControlPart::Entire:
822 // find out the minimum size that should be used
823 // assume contents is a text line
824 QSize aContentSize = downscale(contentRect.size(), Round::Ceil);
825 QFontMetrics aFontMetrics(QApplication::font());
826 aContentSize.setHeight(aFontMetrics.height());
827 QSize aMinSize = upscale(
828 sizeFromContents(QStyle::CT_ComboBox, &cbo, aContentSize), Round::Ceil);
829 if (aMinSize.height() > contentRect.height())
830 contentRect.setHeight(aMinSize.height());
831 boundingRect = contentRect;
832 retVal = true;
833 break;
835 case ControlPart::ButtonDown:
837 contentRect = upscale(
838 subControlRect(QStyle::CC_ComboBox, &cbo, QStyle::SC_ComboBoxArrow));
839 contentRect.translate(boundingRect.left(), boundingRect.top());
840 retVal = true;
841 break;
843 case ControlPart::SubEdit:
845 contentRect = upscale(
846 subControlRect(QStyle::CC_ComboBox, &cbo, QStyle::SC_ComboBoxEditField));
847 contentRect.translate(boundingRect.left(), boundingRect.top());
848 retVal = true;
849 break;
851 default:
852 break;
854 break;
856 case ControlType::Spinbox:
858 QStyleOptionSpinBox sbo;
859 sbo.frame = true;
861 sbo.rect = downscale(QRect(0, 0, contentRect.width(), contentRect.height()));
862 sbo.state = vclStateValue2StateFlag(controlState, val);
864 switch (part)
866 case ControlPart::Entire:
868 QSize aContentSize = downscale(contentRect.size(), Round::Ceil);
869 QFontMetrics aFontMetrics(QApplication::font());
870 aContentSize.setHeight(aFontMetrics.height());
871 QSize aMinSize = upscale(
872 sizeFromContents(QStyle::CT_SpinBox, &sbo, aContentSize), Round::Ceil);
873 if (aMinSize.height() > contentRect.height())
874 contentRect.setHeight(aMinSize.height());
875 boundingRect = contentRect;
876 retVal = true;
877 break;
879 case ControlPart::ButtonUp:
880 contentRect
881 = upscale(subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxUp));
882 contentRect.translate(boundingRect.left(), boundingRect.top());
883 retVal = true;
884 break;
885 case ControlPart::ButtonDown:
886 contentRect
887 = upscale(subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxDown));
888 contentRect.translate(boundingRect.left(), boundingRect.top());
889 retVal = true;
890 break;
891 case ControlPart::SubEdit:
892 contentRect = upscale(
893 subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxEditField));
894 contentRect.translate(boundingRect.left(), boundingRect.top());
895 retVal = true;
896 break;
897 default:
898 break;
900 break;
902 case ControlType::MenuPopup:
904 int h, w;
905 switch (part)
907 case ControlPart::MenuItemCheckMark:
908 h = upscale(pixelMetric(QStyle::PM_IndicatorHeight), Round::Floor);
909 w = upscale(pixelMetric(QStyle::PM_IndicatorWidth), Round::Floor);
910 retVal = true;
911 break;
912 case ControlPart::MenuItemRadioMark:
913 h = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorHeight), Round::Floor);
914 w = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorWidth), Round::Floor);
915 retVal = true;
916 break;
917 default:
918 break;
920 if (retVal)
922 contentRect = QRect(0, 0, w, h);
923 boundingRect = contentRect;
925 break;
927 case ControlType::Frame:
929 if (part == ControlPart::Border)
931 int nFrameWidth = upscale(pixelMetric(QStyle::PM_DefaultFrameWidth), Round::Ceil);
932 contentRect.adjust(nFrameWidth, nFrameWidth, -nFrameWidth, -nFrameWidth);
933 retVal = true;
935 break;
937 case ControlType::Radiobutton:
939 const int h = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorHeight), Round::Ceil);
940 const int w = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorWidth), Round::Ceil);
942 contentRect = QRect(boundingRect.left(), boundingRect.top(), w, h);
943 int nHMargin = pixelMetric(QStyle::PM_FocusFrameHMargin, &styleOption);
944 int nVMargin = pixelMetric(QStyle::PM_FocusFrameVMargin, &styleOption);
945 contentRect.adjust(0, 0, upscale(2 * nHMargin, Round::Ceil),
946 upscale(2 * nVMargin, Round::Ceil));
947 boundingRect = contentRect;
949 retVal = true;
950 break;
952 case ControlType::Slider:
954 const int w = upscale(pixelMetric(QStyle::PM_SliderLength), Round::Ceil);
955 if (part == ControlPart::ThumbHorz)
957 contentRect
958 = QRect(boundingRect.left(), boundingRect.top(), w, boundingRect.height());
959 boundingRect = contentRect;
960 retVal = true;
962 else if (part == ControlPart::ThumbVert)
964 contentRect
965 = QRect(boundingRect.left(), boundingRect.top(), boundingRect.width(), w);
966 boundingRect = contentRect;
967 retVal = true;
969 break;
971 case ControlType::Toolbar:
973 const int nWorH = upscale(pixelMetric(QStyle::PM_ToolBarHandleExtent), Round::Ceil);
974 if (part == ControlPart::ThumbHorz)
976 contentRect
977 = QRect(boundingRect.left(), boundingRect.top(), boundingRect.width(), nWorH);
978 boundingRect = contentRect;
979 retVal = true;
981 else if (part == ControlPart::ThumbVert)
983 contentRect
984 = QRect(boundingRect.left(), boundingRect.top(), nWorH, boundingRect.height());
985 boundingRect = contentRect;
986 retVal = true;
988 else if (part == ControlPart::Button)
990 QStyleOptionToolButton option;
991 option.arrowType = Qt::NoArrow;
992 option.features = QStyleOptionToolButton::None;
993 option.rect = downscale(QRect({ 0, 0 }, contentRect.size()));
994 contentRect = upscale(
995 subControlRect(QStyle::CC_ToolButton, &option, QStyle::SC_ToolButton));
996 boundingRect = contentRect;
997 retVal = true;
999 break;
1001 case ControlType::Scrollbar:
1003 // core can't handle 3-button scrollbars well, so we fix that in hitTestNativeControl(),
1004 // for the rest also provide the track area (i.e. area not taken by buttons)
1005 if (part == ControlPart::TrackVertArea || part == ControlPart::TrackHorzArea)
1007 QStyleOptionSlider option;
1008 bool horizontal = (part == ControlPart::TrackHorzArea); //horizontal or vertical
1009 option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
1010 if (horizontal)
1011 option.state |= QStyle::State_Horizontal;
1012 // getNativeControlRegion usually gets ImplControlValue as 'val' (i.e. not the proper
1013 // subclass), so use random sensible values (doesn't matter anyway, as the wanted
1014 // geometry here depends only on button sizes)
1015 option.maximum = 10;
1016 option.minimum = 0;
1017 option.sliderPosition = option.sliderValue = 4;
1018 option.pageStep = 2;
1019 // Adjust coordinates to make the widget appear to be at (0,0), i.e. make
1020 // widget and screen coordinates the same. QStyle functions should use screen
1021 // coordinates but at least QPlastiqueStyle::subControlRect() is buggy
1022 // and sometimes uses widget coordinates.
1023 option.rect = downscale(QRect({ 0, 0 }, contentRect.size()));
1024 contentRect = upscale(
1025 subControlRect(QStyle::CC_ScrollBar, &option, QStyle::SC_ScrollBarGroove));
1026 contentRect.translate(boundingRect.left()
1027 - (contentRect.width() - boundingRect.width()),
1028 boundingRect.top());
1029 boundingRect = contentRect;
1030 retVal = true;
1032 break;
1034 case ControlType::TabItem:
1036 QStyleOptionTab sot;
1037 fillQStyleOptionTab(val, sot);
1038 QSize aMinSize = upscale(sizeFromContents(QStyle::CT_TabBarTab, &sot,
1039 downscale(contentRect.size(), Round::Ceil)),
1040 Round::Ceil);
1041 contentRect.setSize(aMinSize);
1042 boundingRect = contentRect;
1043 retVal = true;
1044 break;
1046 case ControlType::TabPane:
1048 const TabPaneValue& rValue = static_cast<const TabPaneValue&>(val);
1049 QStyleOptionTabWidgetFrame sotwf;
1050 fullQStyleOptionTabWidgetFrame(sotwf, true);
1051 QSize contentSize(
1052 std::max(rValue.m_aTabHeaderRect.GetWidth(), controlRegion.GetWidth()),
1053 rValue.m_aTabHeaderRect.GetHeight() + controlRegion.GetHeight());
1054 QSize aMinSize = upscale(
1055 sizeFromContents(QStyle::CT_TabWidget, &sotwf, downscale(contentSize, Round::Ceil)),
1056 Round::Ceil);
1057 contentRect.setSize(aMinSize);
1058 boundingRect = contentRect;
1059 retVal = true;
1060 break;
1062 default:
1063 break;
1065 if (retVal)
1067 nativeBoundingRegion = toRectangle(boundingRect);
1068 nativeContentRegion = toRectangle(contentRect);
1071 return retVal;
1074 /** Test whether the position is in the native widget.
1075 If the return value is true, bIsInside contains information whether
1076 aPos was or was not inside the native widget specified by the
1077 nType/nPart combination.
1079 bool QtGraphics_Controls::hitTestNativeControl(ControlType nType, ControlPart nPart,
1080 const tools::Rectangle& rControlRegion,
1081 const Point& rPos, bool& rIsInside)
1083 if (nType == ControlType::Scrollbar)
1085 if (nPart != ControlPart::ButtonUp && nPart != ControlPart::ButtonDown
1086 && nPart != ControlPart::ButtonLeft && nPart != ControlPart::ButtonRight)
1087 { // we adjust only for buttons (because some scrollbars have 3 buttons,
1088 // and LO core doesn't handle such scrollbars well)
1089 return false;
1091 rIsInside = false;
1092 bool bHorizontal = (nPart == ControlPart::ButtonLeft || nPart == ControlPart::ButtonRight);
1093 QRect rect = toQRect(rControlRegion);
1094 QPoint pos(rPos.X(), rPos.Y());
1095 // Adjust coordinates to make the widget appear to be at (0,0), i.e. make
1096 // widget and screen coordinates the same. QStyle functions should use screen
1097 // coordinates but at least QPlastiqueStyle::subControlRect() is buggy
1098 // and sometimes uses widget coordinates.
1099 pos -= rect.topLeft();
1100 rect.moveTo(0, 0);
1101 QStyleOptionSlider options;
1102 options.orientation = bHorizontal ? Qt::Horizontal : Qt::Vertical;
1103 if (bHorizontal)
1104 options.state |= QStyle::State_Horizontal;
1105 options.rect = rect;
1106 // some random sensible values, since we call this code only for scrollbar buttons,
1107 // the slider position does not exactly matter
1108 options.maximum = 10;
1109 options.minimum = 0;
1110 options.sliderPosition = options.sliderValue = 4;
1111 options.pageStep = 2;
1112 QStyle::SubControl control
1113 = QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar, &options, pos);
1114 if (nPart == ControlPart::ButtonUp || nPart == ControlPart::ButtonLeft)
1115 rIsInside = (control == QStyle::SC_ScrollBarSubLine);
1116 else // DOWN, RIGHT
1117 rIsInside = (control == QStyle::SC_ScrollBarAddLine);
1118 return true;
1120 return false;
1123 inline int QtGraphics_Controls::downscale(int size, Round eRound)
1125 return static_cast<int>(eRound == Round::Ceil ? ceil(size / m_rGraphics.devicePixelRatioF())
1126 : floor(size / m_rGraphics.devicePixelRatioF()));
1129 inline int QtGraphics_Controls::upscale(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 QRect QtGraphics_Controls::downscale(const QRect& rect)
1137 return QRect(downscale(rect.x(), Round::Floor), downscale(rect.y(), Round::Floor),
1138 downscale(rect.width(), Round::Ceil), downscale(rect.height(), Round::Ceil));
1141 inline QRect QtGraphics_Controls::upscale(const QRect& rect)
1143 return QRect(upscale(rect.x(), Round::Floor), upscale(rect.y(), Round::Floor),
1144 upscale(rect.width(), Round::Ceil), upscale(rect.height(), Round::Ceil));
1147 inline QSize QtGraphics_Controls::downscale(const QSize& size, Round eRound)
1149 return QSize(downscale(size.width(), eRound), downscale(size.height(), eRound));
1152 inline QSize QtGraphics_Controls::upscale(const QSize& size, Round eRound)
1154 return QSize(upscale(size.width(), eRound), upscale(size.height(), eRound));
1157 inline QPoint QtGraphics_Controls::upscale(const QPoint& point, Round eRound)
1159 return QPoint(upscale(point.x(), eRound), upscale(point.y(), eRound));
1162 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */