File indexing completed on 2024-05-12 04:44:29
0001 // SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com> 0002 // SPDX-License-Identifier: BSD-2-Clause OR MIT 0003 0004 // Own headers 0005 // First the interface, which forces the header to be self-contained. 0006 #include "abstractdiagram.h" 0007 // Second, the private implementation. 0008 #include "abstractdiagram_p.h" // IWYU pragma: associated 0009 0010 #include "helper.h" 0011 #include <qcolor.h> 0012 #include <qglobal.h> 0013 #include <qimage.h> 0014 #include <qnamespace.h> 0015 #include <qpalette.h> 0016 #include <qsize.h> 0017 #include <qstyle.h> 0018 #include <qstyleoption.h> 0019 #include <qwidget.h> 0020 class QHideEvent; 0021 class QShowEvent; 0022 0023 namespace PerceptualColor 0024 { 0025 /** @brief The constructor. 0026 * @param parent The widget’s parent widget. This parameter will be passed 0027 * to the base class’s constructor. */ 0028 AbstractDiagram::AbstractDiagram(QWidget *parent) 0029 : QWidget(parent) 0030 , d_pointer(new AbstractDiagramPrivate()) 0031 { 0032 } 0033 0034 /** @brief Destructor */ 0035 AbstractDiagram::~AbstractDiagram() noexcept 0036 { 0037 } 0038 0039 /** @brief The color for painting focus indicators 0040 * @returns The color for painting focus indicators. This color is based on 0041 * the current widget style at the moment this function is called. The value 0042 * might therefore be different on the next function call, if the widget style 0043 * has been switched by the user in the meantime. 0044 * @note As there is no build-in support in Qt to get this information, we 0045 * have to do some best guess, which might go wrong on some styles. */ 0046 QColor AbstractDiagram::focusIndicatorColor() const 0047 { 0048 return palette().color(QPalette::ColorGroup::Active, QPalette::ColorRole::Highlight); 0049 } 0050 0051 /** @brief The rounded size of the widget measured in 0052 * <em>physical pixels</em>. 0053 * 0054 * @returns The rounded size of this widget, 0055 * measured in <em>physical pixels</em>, based on 0056 * <tt>QPaintDevice::devicePixelRatioF()</tt>. This is the recommended 0057 * image size for calling <tt>QPainter::drawImage()</tt> during a paint event. 0058 * Both, width and height are guaranteed to be ≥ 0. 0059 * 0060 * Example: You want to prepare a <tt>QImage</tt> of the hole widget to be 0061 * used in <tt>QWidget::paintEvent()</tt>. To make sure a crisp rendering, 0062 * you have to 0063 * 0064 * - Prepare an image with the size that this function returns. 0065 * - Set <tt>QImage::setDevicePixelRatio()</tt> of the image to the same 0066 * value as <tt>QPaintDevice::devicePixelRatioF()</tt> of the widget. 0067 * - Actually paint the image on the widget at position <tt>(0, 0)</tt> 0068 * <em>without</em> anti-aliasing. 0069 * 0070 * @note If <tt>QPaintDevice::devicePixelRatioF()</tt> is not an integer, 0071 * the result of this function is rounded down. Qt’s widget geometry code 0072 * has no documentation about how this is handled. However, Qt seems to 0073 * round up starting with 0.5, at least on Linux/X11. But there are a few 0074 * themes (for example the “Kvantum style engine” with the style 0075 * “MildGradientKvantum”) that seem to round down: This becomes visible, as 0076 * the corresponding last physical pixels are not automatically redrawn before 0077 * executing the <tt>paintEvent()</tt> code. To avoid relying on undocumented 0078 * behaviour and to avoid known problems with some styles, this function 0079 * is conservative and always rounds down. */ 0080 QSize AbstractDiagram::physicalPixelSize() const 0081 { 0082 // Assert that static_cast<int> always rounds down. 0083 static_assert(static_cast<int>(1.9) == 1); 0084 static_assert(static_cast<int>(1.5) == 1); 0085 static_assert(static_cast<int>(1.0) == 1); 0086 // Multiply the size with the (floating point) scale factor 0087 // and than round down (by using static_cast<int>). 0088 const int width = static_cast<int>(size().width() * devicePixelRatioF()); 0089 const int height = static_cast<int>(size().height() * devicePixelRatioF()); 0090 return QSize(qMax(width, 0), qMax(height, 0)); 0091 } 0092 0093 /** @brief The maximum possible size of a square within the widget, measured 0094 * in <em>physical pixels</em>. 0095 * 0096 * This is the shorter value of width and height of the widget. 0097 * 0098 * @returns The maximum possible size of a square within the widget, measured 0099 * in <em>physical pixels</em>. Both, width and height are guaranteed 0100 * to be ≥ 0. 0101 * 0102 * @sa @ref maximumWidgetSquareSize */ 0103 int AbstractDiagram::maximumPhysicalSquareSize() const 0104 { 0105 return qMin(physicalPixelSize().width(), physicalPixelSize().height()); 0106 } 0107 0108 /** @brief The maximum possible size of a square within the widget, measured 0109 * in <em>device-independent pixels</em>. 0110 * 0111 * This is the conversion of @ref maximumPhysicalSquareSize to the unit 0112 * <em>device-independent pixels</em>. It might be <em>smaller</em> than 0113 * the shortest value of <tt>QWidget::width()</tt> and 0114 * <tt>QWidget::height()</tt> because @ref maximumPhysicalSquareSize 0115 * might have rounded down. 0116 * 0117 * @returns The maximum possible size of a square within the widget, measured 0118 * in <em>device-independent pixels</em>. */ 0119 qreal AbstractDiagram::maximumWidgetSquareSize() const 0120 { 0121 return (maximumPhysicalSquareSize() / devicePixelRatioF()); 0122 } 0123 0124 /** @brief Background for semi-transparent colors. 0125 * 0126 * When showing a semi-transparent color, there has to be a background 0127 * on which it is shown. This function provides a suitable background 0128 * for showcasing a color. 0129 * 0130 * Example code (to use within a class that inherits from 0131 * @ref PerceptualColor::AbstractDiagram): 0132 * @snippet testabstractdiagram.cpp useTransparencyBackground 0133 * 0134 * @returns An image of a mosaic of neutral gray rectangles of different 0135 * lightness. You can use this as tiles to paint a background. 0136 * 0137 * @note The image is considering QWidget::devicePixelRatioF() to deliver 0138 * crisp (correctly scaled) images also for high-DPI devices. 0139 * The painting does not use floating point drawing, but rounds 0140 * to full integers. Therefore, the result is always a sharp image. 0141 * This function takes care that each square has the same physical pixel 0142 * size, without scaling errors or anti-aliasing errors. 0143 * 0144 * @internal 0145 * @sa @ref transparencyBackground(qreal devicePixelRatioF) 0146 * @endinternal */ 0147 QImage AbstractDiagram::transparencyBackground() const 0148 { 0149 return PerceptualColor::transparencyBackground(devicePixelRatioF()); 0150 } 0151 0152 /** @brief The outline thickness of a handle. 0153 * 0154 * @returns The outline thickness of a (either circular or linear) handle. 0155 * Measured in <em>device-independent pixels</em>. */ 0156 int AbstractDiagram::handleOutlineThickness() const 0157 { 0158 /** @note The return value is constant. For a given object instance, this 0159 * function returns the same value every time it is called. This constant 0160 * value may be different for different instances of the object. */ 0161 return 2; 0162 } 0163 0164 /** @brief The radius of a circular handle. 0165 * @returns The radius of a circular handle, measured in 0166 * <em>device-independent pixels</em>. */ 0167 qreal AbstractDiagram::handleRadius() const 0168 { 0169 /** @note The return value is constant. For a given object instance, this 0170 * function returns the same value every time it is called. This constant 0171 * value may be different for different instances of the object. */ 0172 return handleOutlineThickness() * 2.5; 0173 } 0174 0175 /** @brief The thickness of a color gradient. 0176 * 0177 * This is the thickness of a one-dimensional gradient, for example in 0178 * a slider or a color wheel. 0179 * 0180 * @returns The thickness of a slider or a color wheel, measured in 0181 * <em>device-independent pixels</em>. 0182 * 0183 * @sa @ref gradientMinimumLength() */ 0184 int AbstractDiagram::gradientThickness() const 0185 { 0186 ensurePolished(); 0187 int result = 0; 0188 QStyleOptionSlider styleOption; 0189 styleOption.initFrom(this); // Sets also QStyle::State_MouseOver 0190 styleOption.orientation = Qt::Horizontal; 0191 result = qMax(result, style()->pixelMetric(QStyle::PM_SliderThickness, &styleOption, this)); 0192 styleOption.orientation = Qt::Vertical; 0193 result = qMax(result, style()->pixelMetric(QStyle::PM_SliderThickness, &styleOption, this)); 0194 result = qMax(result, qRound(handleRadius())); 0195 // No supplementary space for ticks is added. 0196 return result; 0197 } 0198 0199 /** @brief The minimum length of a color gradient. 0200 * 0201 * This is the minimum length of a one-dimensional gradient, for example in 0202 * a slider or a color wheel. This is also the minimum width and minimum 0203 * height of two-dimensional gradients. 0204 * 0205 * @returns The length of a gradient, measured in 0206 * <em>device-independent pixels</em>. 0207 * 0208 * @sa @ref gradientThickness() */ 0209 int AbstractDiagram::gradientMinimumLength() const 0210 { 0211 ensurePolished(); 0212 QStyleOptionSlider option; 0213 option.initFrom(this); 0214 return qMax( 0215 // Parameter: style-based value: 0216 qMax( 0217 // Similar to QSlider sizeHint(): 0218 84, 0219 // Similar to QSlider::minimumSizeHint(): 0220 style()->pixelMetric(QStyle::PM_SliderLength, &option, this)), 0221 // Parameter: 0222 gradientThickness()); 0223 } 0224 0225 /** @brief The empty space around diagrams reserved for the focus indicator. 0226 * 0227 * Measured in <em>device-independent pixels</em>. 0228 * 0229 * @returns The empty space around diagrams reserved for the focus 0230 * indicator. */ 0231 int AbstractDiagram::spaceForFocusIndicator() const 0232 { 0233 // 1 × handleOutlineThickness() for the focus indicator itself. 0234 // 2 × handleOutlineThickness() for the space between the focus indicator 0235 // and the diagram. 0236 return 3 * handleOutlineThickness(); 0237 } 0238 0239 /** @brief An appropriate color for a handle, depending on the background 0240 * lightness. 0241 * @param lightness The background lightness. Valid range: <tt>[0, 100]</tt>. 0242 * @returns An appropriate color for a handle. This color will provide 0243 * contrast to the background. */ 0244 QColor AbstractDiagram::handleColorFromBackgroundLightness(qreal lightness) const 0245 { 0246 if (lightness >= 50) { 0247 return Qt::black; 0248 } 0249 return Qt::white; 0250 } 0251 0252 /** @brief If this widget is actually visible. 0253 * 0254 * Unlike <tt>QWidget::isVisible</tt>, minimized windows are <em>not</em> 0255 * considered visible. 0256 * 0257 * Changes can be observed with 0258 * @ref AbstractDiagram::actualVisibilityToggledEvent. 0259 * 0260 * @returns If this widget is actually visible. 0261 * 0262 * @internal 0263 * 0264 * This information is based on the last @ref AbstractDiagram::showEvent 0265 * or @ref AbstractDiagram::hideEvent that was received. */ 0266 bool AbstractDiagram::isActuallyVisible() const 0267 { 0268 return d_pointer->m_isActuallyVisible; 0269 } 0270 0271 /** @brief Event occurring after @ref isActuallyVisible has been toggled. 0272 * 0273 * This function is called if and only if @ref isActuallyVisible has 0274 * actually been toggled. */ 0275 void AbstractDiagram::actualVisibilityToggledEvent() 0276 { 0277 } 0278 0279 /** @brief React on a show event. 0280 * 0281 * Reimplemented from base class. 0282 * 0283 * @param event The show event. 0284 * 0285 * @internal 0286 * 0287 * @sa @ref AbstractDiagram::isActuallyVisible */ 0288 void AbstractDiagram::showEvent(QShowEvent *event) 0289 { 0290 QWidget::showEvent(event); 0291 if (d_pointer->m_isActuallyVisible == false) { 0292 d_pointer->m_isActuallyVisible = true; 0293 actualVisibilityToggledEvent(); 0294 } 0295 } 0296 0297 /** @brief React on a hide event. 0298 * 0299 * Reimplemented from base class. 0300 * 0301 * @param event The hide event. 0302 * 0303 * @internal 0304 * 0305 * @sa @ref AbstractDiagram::isActuallyVisible */ 0306 void AbstractDiagram::hideEvent(QHideEvent *event) 0307 { 0308 QWidget::hideEvent(event); 0309 if (d_pointer->m_isActuallyVisible == true) { 0310 d_pointer->m_isActuallyVisible = false; 0311 actualVisibilityToggledEvent(); 0312 } 0313 } 0314 0315 /** @brief An alternative to QWidget::update(). It’s a workaround 0316 * that avoids trouble with overload resolution. 0317 * 0318 * Connecting a signal to the slot <tt> 0319 * <a href="https://doc.qt.io/qt-6/qwidget.html#update">QWidget::update()</a> 0320 * </tt> is surprisingly difficult, at least if you want to use the functor 0321 * syntax (which provides compile-time checks) for the connection. A simple 0322 * connection fails to compile because it fails to do a correct overload 0323 * resolution, as there is more than one slot called <tt>update</tt>. Now, <tt> 0324 * <a href="https://doc.qt.io/qt-6/qtglobal.html#qOverload">qOverload<>() 0325 * </a></tt> can be used to choose the correct overload, but in this special 0326 * case, <tt><a href="https://doc.qt.io/qt-6/qtglobal.html#qOverload"> 0327 * qOverload<>()</a></tt> generates compiler warnings. 0328 * 0329 * Instead of connecting to <tt> 0330 * <a href="https://doc.qt.io/qt-6/qwidget.html#update">QWidget::update() 0331 * </a></tt> directly, simply connect to this slot instead. It calls 0332 * the actual <tt><a href="https://doc.qt.io/qt-6/qwidget.html#update"> 0333 * QWidget::update()</a></tt>, but avoids the annoyance with the overload 0334 * resolution */ 0335 void AbstractDiagram::callUpdate() 0336 { 0337 update(); 0338 } 0339 0340 } // namespace PerceptualColor