Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/styles/base/BaseStyle.cpp')
-rw-r--r--src/gui/styles/base/BaseStyle.cpp4808
1 files changed, 4808 insertions, 0 deletions
diff --git a/src/gui/styles/base/BaseStyle.cpp b/src/gui/styles/base/BaseStyle.cpp
new file mode 100644
index 000000000..aac9daf1c
--- /dev/null
+++ b/src/gui/styles/base/BaseStyle.cpp
@@ -0,0 +1,4808 @@
+/*
+ * Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
+ * Copyright (C) 2019 Andrew Richards
+ *
+ * Derived from Phantomstyle and relicensed under the GPLv2 or v3.
+ * https://github.com/randrew/phantomstyle
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "BaseStyle.h"
+#include "phantomcolor.h"
+
+#include <QAbstractItemView>
+#include <QApplication>
+#include <QComboBox>
+#include <QDialogButtonBox>
+#include <QFile>
+#include <QHeaderView>
+#include <QListView>
+#include <QMainWindow>
+#include <QMenu>
+#include <QPainter>
+#include <QPainterPath>
+#include <QPoint>
+#include <QPolygon>
+#include <QPushButton>
+#include <QScrollBar>
+#include <QSharedData>
+#include <QSlider>
+#include <QSpinBox>
+#include <QSplitter>
+#include <QString>
+#include <QStyleOption>
+#include <QTableView>
+#include <QToolBar>
+#include <QToolButton>
+#include <QTreeView>
+#include <QWindow>
+#include <QWizard>
+#include <QtMath>
+#include <qdrawutil.h>
+
+#include <cmath>
+
+QT_BEGIN_NAMESPACE
+Q_GUI_EXPORT int qt_defaultDpiX();
+QT_END_NAMESPACE
+
+// Redefine Q_FALLTHROUGH for older Qt versions
+#ifndef Q_FALLTHROUGH
+#if (defined(Q_CC_GNU) && Q_CC_GNU >= 700) && !defined(Q_CC_INTEL)
+#define Q_FALLTHROUGH() __attribute__((fallthrough))
+#else
+#define Q_FALLTHROUGH() (void)0
+#endif
+#endif
+
+namespace Phantom
+{
+ namespace
+ {
+ constexpr qint16 DefaultFrameWidth = 6;
+ constexpr qint16 SplitterMaxLength = 25; // Length of splitter handle (not thickness)
+ constexpr qint16 MenuMinimumWidth = 20; // Smallest width that menu items can have
+ constexpr qint16 MenuBar_FrameWidth = 6;
+ constexpr qint16 SpinBox_ButtonWidth = 15;
+
+ // These two are currently not based on font, but could be
+ constexpr qint16 LineEdit_ContentsHPad = 5;
+ constexpr qint16 ComboBox_NonEditable_ContentsHPad = 7;
+ constexpr qint16 HeaderSortIndicator_HOffset = 1;
+ constexpr qint16 HeaderSortIndicator_VOffset = 2;
+ constexpr qint16 TabBar_InctiveVShift = 0;
+
+ constexpr qreal TabBarTab_Rounding = 1.0;
+ constexpr qreal SpinBox_Rounding = 1.0;
+ constexpr qreal LineEdit_Rounding = 1.0;
+ constexpr qreal FrameFocusRect_Rounding = 1.0;
+ constexpr qreal PushButton_Rounding = 1.0;
+ constexpr qreal ToolButton_Rounding = 1.0;
+ constexpr qreal ProgressBar_Rounding = 1.0;
+ constexpr qreal GroupBox_Rounding = 1.0;
+ constexpr qreal SliderGroove_Rounding = 1.0;
+ constexpr qreal SliderHandle_Rounding = 1.0;
+
+ constexpr qreal CheckMark_WidthOfHeightScale = 0.8;
+ constexpr qreal PushButton_HorizontalPaddingFontHeightRatio = 1.0;
+ constexpr qreal TabBar_HPaddingFontRatio = 1.25;
+ constexpr qreal TabBar_VPaddingFontRatio = 1.0 / 1.25;
+ constexpr qreal GroupBox_LabelBottomMarginFontRatio = 1.0 / 4.0;
+ constexpr qreal ComboBox_ArrowMarginRatio = 1.0 / 3.25;
+
+ constexpr qreal MenuBar_HorizontalPaddingFontRatio = 1.0 / 2.0;
+ constexpr qreal MenuBar_VerticalPaddingFontRatio = 1.0 / 3.0;
+
+ constexpr qreal MenuItem_LeftMarginFontRatio = 1.0 / 2.0;
+ constexpr qreal MenuItem_RightMarginForTextFontRatio = 1.0 / 1.5;
+ constexpr qreal MenuItem_RightMarginForArrowFontRatio = 1.0 / 4.0;
+ constexpr qreal MenuItem_VerticalMarginsFontRatio = 1.0 / 5.0;
+ // Number that's multiplied with a font's height to get the space between a
+ // menu item's checkbox (or other sign) and its text (or icon).
+ constexpr qreal MenuItem_CheckRightSpaceFontRatio = 1.0 / 4.0;
+ constexpr qreal MenuItem_TextMnemonicSpaceFontRatio = 1.5;
+ constexpr qreal MenuItem_SubMenuArrowSpaceFontRatio = 1.0 / 1.5;
+ constexpr qreal MenuItem_SubMenuArrowWidthFontRatio = 1.0 / 2.75;
+ constexpr qreal MenuItem_SeparatorHeightFontRatio = 1.0 / 1.5;
+ constexpr qreal MenuItem_CheckMarkVerticalInsetFontRatio = 1.0 / 5.0;
+ constexpr qreal MenuItem_IconRightSpaceFontRatio = 1.0 / 3.0;
+
+ constexpr bool BranchesOnEdge = false;
+ constexpr bool OverhangShadows = false;
+ constexpr bool IndicatorShadows = false;
+ constexpr bool MenuExtraBottomMargin = true;
+ constexpr bool MenuBarLeftMargin = false;
+ constexpr bool MenuBarDrawBorder = false;
+ constexpr bool AllowToolBarAutoRaise = true;
+ // Note that this only applies to the disclosure etc. decorators in tree views.
+ constexpr bool ShowItemViewDecorationSelected = false;
+ constexpr bool UseQMenuForComboBoxPopup = true;
+ constexpr bool ItemView_UseFontHeightForDecorationSize = true;
+
+ // Whether or not the non-raised tabs in a tab bar have shininess/highlights to
+ // them. Setting this to false adds an extra visual hint for distinguishing
+ // between the current and non-current tabs, but makes the non-current tabs
+ // appear less clickable. Other ways to increase the visual differences could
+ // be to increase the color contrast for the background fill color, or increase
+ // the vertical offset. However, increasing the vertical offset comes with some
+ // layout challenges, and increasing the color contrast further may visually
+ // imply an incorrect layout structure. Not sure what's best.
+ //
+ // This doesn't disable creating the color/brush resource, even though it's
+ // currently a compile-time-only option, because it may be changed to be part
+ // of some dynamic config system for Phantom in the future, or have a
+ // per-widget style hint associated with it.
+ const bool TabBar_InactiveTabsHaveSpecular = false;
+
+ struct Grad
+ {
+ Grad(const QColor& from, const QColor& to)
+ {
+ rgbA = Rgb::ofQColor(from);
+ rgbB = Rgb::ofQColor(to);
+ lA = rgbA.toHsl().l;
+ lB = rgbB.toHsl().l;
+ }
+ QColor sample(qreal alpha) const
+ {
+ Hsl hsl = Rgb::lerp(rgbA, rgbB, alpha).toHsl();
+ hsl.l = Phantom::lerp(lA, lB, alpha);
+ return hsl.toQColor();
+ }
+ Rgb rgbA, rgbB;
+ qreal lA, lB;
+ };
+
+ namespace DeriveColors
+ {
+ Q_NEVER_INLINE QColor adjustLightness(const QColor& qcolor, qreal ld)
+ {
+ Hsl hsl = Hsl::ofQColor(qcolor);
+ const qreal gamma = 3.0;
+ hsl.l = std::pow(Phantom::saturate(std::pow(hsl.l, 1.0 / gamma) + ld * 0.8), gamma);
+ return hsl.toQColor();
+ }
+ bool hack_isLightPalette(const QPalette& pal)
+ {
+ Hsl hsl0 = Hsl::ofQColor(pal.color(QPalette::WindowText));
+ Hsl hsl1 = Hsl::ofQColor(pal.color(QPalette::Window));
+ return hsl0.l < hsl1.l;
+ }
+ QColor buttonColor(const QPalette& pal)
+ {
+ // temp hack
+ if (pal.color(QPalette::Button) == pal.color(QPalette::Window))
+ return adjustLightness(pal.color(QPalette::Button), 0.01);
+ return pal.color(QPalette::Button);
+ }
+ QColor highlightedOutlineOf(const QPalette& pal)
+ {
+ return adjustLightness(pal.color(QPalette::Highlight), -0.08);
+ }
+ QColor dividerColor(const QColor& underlying)
+ {
+ return adjustLightness(underlying, -0.05);
+ }
+ QColor lightDividerColor(const QColor& underlying)
+ {
+ return adjustLightness(underlying, 0.02);
+ }
+ QColor outlineOf(const QPalette& pal)
+ {
+ return adjustLightness(pal.color(QPalette::Window), -0.1);
+ }
+ QColor gutterColorOf(const QPalette& pal)
+ {
+ return adjustLightness(pal.color(QPalette::Window), -0.05);
+ }
+ QColor darkGutterColorOf(const QPalette& pal)
+ {
+ return adjustLightness(pal.color(QPalette::Window), -0.08);
+ }
+ QColor lightShadeOf(const QColor& underlying)
+ {
+ return adjustLightness(underlying, 0.08);
+ }
+ QColor darkShadeOf(const QColor& underlying)
+ {
+ return adjustLightness(underlying, -0.08);
+ }
+ QColor overhangShadowOf(const QColor& underlying)
+ {
+ return adjustLightness(underlying, -0.05);
+ }
+ QColor sliderGutterShadowOf(const QColor& underlying)
+ {
+ return adjustLightness(underlying, -0.01);
+ }
+ QColor specularOf(const QColor& underlying)
+ {
+ return adjustLightness(underlying, 0.01);
+ }
+ QColor lightSpecularOf(const QColor& underlying)
+ {
+ return adjustLightness(underlying, 0.05);
+ }
+ QColor pressedOf(const QColor& color)
+ {
+ return adjustLightness(color, -0.05);
+ }
+ QColor darkPressedOf(const QColor& color)
+ {
+ return adjustLightness(color, -0.08);
+ }
+ QColor lightOnOf(const QColor& color)
+ {
+ return adjustLightness(color, -0.04);
+ }
+ QColor onOf(const QColor& color)
+ {
+ return adjustLightness(color, -0.08);
+ }
+ QColor indicatorColorOf(const QPalette& palette, QPalette::ColorGroup group = QPalette::Current)
+ {
+ if (hack_isLightPalette(palette)) {
+ qreal adjust = (palette.currentColorGroup() == QPalette::Disabled) ? 0.09 : 0.32;
+ return adjustLightness(palette.color(group, QPalette::WindowText), adjust);
+ }
+ return adjustLightness(palette.color(group, QPalette::WindowText), -0.05);
+ }
+ QColor inactiveTabFillColorOf(const QColor& underlying)
+ {
+ // used to be -0.01
+ return adjustLightness(underlying, -0.025);
+ }
+ QColor progressBarOutlineColorOf(const QPalette& pal)
+ {
+ // Pretty wasteful
+ Hsl hsl0 = Hsl::ofQColor(pal.color(QPalette::Window));
+ Hsl hsl1 = Hsl::ofQColor(pal.color(QPalette::Highlight));
+ hsl1.l = Phantom::saturate(qMin(hsl0.l - 0.1, hsl1.l - 0.2));
+ return hsl1.toQColor();
+ }
+ QColor itemViewMultiSelectionCurrentBorderOf(const QPalette& pal)
+ {
+ return adjustLightness(pal.color(QPalette::Highlight), -0.15);
+ }
+ QColor itemViewHeaderOnLineColorOf(const QPalette& pal)
+ {
+ return hack_isLightPalette(pal)
+ ? highlightedOutlineOf(pal)
+ : Grad(pal.color(QPalette::WindowText), pal.color(QPalette::Window)).sample(0.5);
+ }
+ } // namespace DeriveColors
+
+ namespace SwatchColors
+ {
+ enum SwatchColor
+ {
+ S_none = 0,
+ S_window,
+ S_button,
+ S_base,
+ S_text,
+ S_windowText,
+ S_highlight,
+ S_highlightedText,
+ S_scrollbarGutter,
+ S_scrollbarSlider,
+ S_window_outline,
+ S_window_specular,
+ S_window_divider,
+ S_window_lighter,
+ S_window_darker,
+ S_frame_outline,
+ S_button_specular,
+ S_button_pressed,
+ S_button_on,
+ S_button_pressed_specular,
+ S_sliderHandle,
+ S_sliderHandle_pressed,
+ S_sliderHandle_specular,
+ S_sliderHandle_pressed_specular,
+ S_base_shadow,
+ S_base_divider,
+ S_windowText_disabled,
+ S_highlight_outline,
+ S_highlight_specular,
+ S_progressBar_outline,
+ S_inactiveTabYesFrame,
+ S_inactiveTabNoFrame,
+ S_inactiveTabYesFrame_specular,
+ S_inactiveTabNoFrame_specular,
+ S_indicator_current,
+ S_indicator_disabled,
+ S_itemView_multiSelection_currentBorder,
+ S_itemView_headerOnLine,
+ S_scrollbarGutter_disabled,
+
+ // Aliases
+ S_progressBar = S_highlight,
+ S_progressBar_specular = S_highlight_specular,
+ S_tabFrame = S_window,
+ S_tabFrame_specular = S_window_specular,
+ };
+ }
+
+ using Swatchy = SwatchColors::SwatchColor;
+
+ enum
+ {
+ Num_SwatchColors = SwatchColors::S_scrollbarGutter_disabled + 1,
+ Num_ShadowSteps = 3,
+ };
+
+ struct PhSwatch : public QSharedData
+ {
+ // The pens store the brushes within them, so storing the brushes here as
+ // well is redundant. However, QPen::brush() does not return its brush by
+ // reference, so we'd end up doing a bunch of inc/dec work every time we use
+ // one. Also, it saves us the indirection of chasing two pointers (Swatch ->
+ // QPen -> QBrush) every time we want to get a QColor.
+ QBrush brushes[Num_SwatchColors];
+ QPen pens[Num_SwatchColors];
+ QColor scrollbarShadowColors[Num_ShadowSteps];
+
+ // Note: the casts to int in the assert macros are to suppress a false
+ // positive warning for tautological comparison in the clang linter.
+ inline const QColor& color(Swatchy swatchValue) const
+ {
+ Q_ASSERT(swatchValue >= 0 && static_cast<int>(swatchValue) < Num_SwatchColors);
+ return brushes[swatchValue].color();
+ }
+ inline const QBrush& brush(Swatchy swatchValue) const
+ {
+ Q_ASSERT(swatchValue >= 0 && static_cast<int>(swatchValue) < Num_SwatchColors);
+ return brushes[swatchValue];
+ }
+ inline const QPen& pen(Swatchy swatchValue) const
+ {
+ Q_ASSERT(swatchValue >= 0 && static_cast<int>(swatchValue) < Num_SwatchColors);
+ return pens[swatchValue];
+ }
+
+ void loadFromQPalette(const QPalette& pal);
+ };
+
+ using PhSwatchPtr = QExplicitlySharedDataPointer<PhSwatch>;
+ using PhCacheEntry = QPair<uint, PhSwatchPtr>;
+ enum : int
+ {
+ Num_ColorCacheEntries = 10,
+ };
+ using PhSwatchCache = QVarLengthArray<PhCacheEntry, Num_ColorCacheEntries>;
+ Q_NEVER_INLINE void PhSwatch::loadFromQPalette(const QPalette& pal)
+ {
+ using namespace SwatchColors;
+ namespace Dc = DeriveColors;
+ bool isLight = Dc::hack_isLightPalette(pal);
+ QColor colors[Num_SwatchColors];
+ colors[S_none] = QColor();
+
+ colors[S_window] = pal.color(QPalette::Window);
+ colors[S_button] = pal.color(QPalette::Button);
+ if (colors[S_button] == colors[S_window])
+ colors[S_button] = Dc::adjustLightness(colors[S_button], 0.01);
+ colors[S_base] = pal.color(QPalette::Base);
+ colors[S_text] = pal.color(QPalette::Text);
+ colors[S_windowText] = pal.color(QPalette::WindowText);
+ colors[S_highlight] = pal.color(QPalette::Highlight);
+ colors[S_highlightedText] = pal.color(QPalette::HighlightedText);
+ colors[S_scrollbarGutter] = isLight ? Dc::gutterColorOf(pal) : Dc::darkGutterColorOf(pal);
+ colors[S_scrollbarSlider] = isLight ? colors[S_button] : Dc::adjustLightness(colors[S_window], 0.2);
+
+ colors[S_window_outline] =
+ isLight ? Dc::adjustLightness(colors[S_window], -0.1) : Dc::adjustLightness(colors[S_window], 0.03);
+ colors[S_window_specular] = Dc::specularOf(colors[S_window]);
+ colors[S_window_divider] =
+ isLight ? Dc::dividerColor(colors[S_window]) : Dc::lightDividerColor(colors[S_window]);
+ colors[S_window_lighter] = Dc::lightShadeOf(colors[S_window]);
+ colors[S_window_darker] = Dc::darkShadeOf(colors[S_window]);
+ colors[S_frame_outline] = isLight ? colors[S_window_outline] : Dc::adjustLightness(colors[S_window], 0.08);
+ colors[S_button_specular] =
+ isLight ? Dc::specularOf(colors[S_button]) : Dc::lightSpecularOf(colors[S_button]);
+ colors[S_button_pressed] = isLight ? Dc::pressedOf(colors[S_button]) : Dc::darkPressedOf(colors[S_button]);
+ colors[S_button_on] = isLight ? Dc::lightOnOf(colors[S_button]) : Dc::onOf(colors[S_button]);
+ colors[S_button_pressed_specular] =
+ isLight ? Dc::specularOf(colors[S_button_pressed]) : Dc::lightSpecularOf(colors[S_button_pressed]);
+
+ colors[S_sliderHandle] = isLight ? colors[S_button] : Dc::adjustLightness(colors[S_button], -0.03);
+ colors[S_sliderHandle_specular] =
+ isLight ? Dc::specularOf(colors[S_sliderHandle]) : Dc::lightSpecularOf(colors[S_sliderHandle]);
+ colors[S_sliderHandle_pressed] =
+ isLight ? colors[S_button_pressed] : Dc::adjustLightness(colors[S_button_pressed], 0.03);
+ colors[S_sliderHandle_pressed_specular] = isLight ? Dc::specularOf(colors[S_sliderHandle_pressed])
+ : Dc::lightSpecularOf(colors[S_sliderHandle_pressed]);
+
+ colors[S_base_shadow] = Dc::overhangShadowOf(colors[S_base]);
+ colors[S_base_divider] = colors[S_window_divider];
+ colors[S_windowText_disabled] = pal.color(QPalette::Disabled, QPalette::WindowText);
+ colors[S_highlight_outline] = isLight ? Dc::adjustLightness(colors[S_highlight], -0.02)
+ : Dc::adjustLightness(colors[S_highlight], 0.05);
+ colors[S_highlight_specular] = Dc::specularOf(colors[S_highlight]);
+ colors[S_progressBar_outline] = Dc::progressBarOutlineColorOf(pal);
+ colors[S_inactiveTabYesFrame] = Dc::inactiveTabFillColorOf(colors[S_tabFrame]);
+ colors[S_inactiveTabNoFrame] = Dc::inactiveTabFillColorOf(colors[S_window]);
+ colors[S_inactiveTabYesFrame_specular] = Dc::specularOf(colors[S_inactiveTabYesFrame]);
+ colors[S_inactiveTabNoFrame_specular] = Dc::specularOf(colors[S_inactiveTabNoFrame]);
+ colors[S_indicator_current] = Dc::indicatorColorOf(pal, QPalette::Current);
+ colors[S_indicator_disabled] = Dc::indicatorColorOf(pal, QPalette::Disabled);
+ colors[S_itemView_multiSelection_currentBorder] = Dc::itemViewMultiSelectionCurrentBorderOf(pal);
+ colors[S_itemView_headerOnLine] = Dc::itemViewHeaderOnLineColorOf(pal);
+ colors[S_scrollbarGutter_disabled] = colors[S_window];
+
+ brushes[S_none] = Qt::NoBrush;
+ for (int i = S_none + 1; i < Num_SwatchColors; ++i) {
+ // todo try to reuse
+ brushes[i] = colors[i];
+ }
+ pens[S_none] = Qt::NoPen;
+ // QPen::setColor constructs a QBrush behind the scenes, so better to just
+ // re-use the ones we already made.
+ for (int i = S_none + 1; i < Num_SwatchColors; ++i) {
+ pens[i].setBrush(brushes[i]);
+ // Width is already 1, don't need to set it. Caps and joins already fine at
+ // their defaults, too.
+ }
+
+ Grad gutterGrad(Dc::sliderGutterShadowOf(colors[S_scrollbarGutter]), colors[S_scrollbarGutter]);
+ for (int i = 0; i < Num_ShadowSteps; ++i) {
+ scrollbarShadowColors[i] = gutterGrad.sample(i / static_cast<qreal>(Num_ShadowSteps));
+ }
+ }
+
+ // This is the "hash" (not really a hash) function we'll use on the happy fast
+ // path when looking up a PhSwatch for a given QPalette. It's fragile, because
+ // it uses QPalette::cacheKey(), so it may not match even when the contents
+ // (currentColorGroup + the RGB colors) of the QPalette are actually a match.
+ // But it's cheaper to calculate, so we'll store a single one of these "hashes"
+ // for the head (most recently used) cached PhSwatch, and check to see if it
+ // matches. This is the most common case, so we can usually save some work by
+ // doing this. (The second most common case is probably having a different
+ // ColorGroup but the rest of the contents are the same, but we don't have a
+ // special path for that.)
+ inline quint64 fastfragile_hash_qpalette(const QPalette& p)
+ {
+ union
+ {
+ qint64 i;
+ quint64 u;
+ } x;
+ x.i = p.cacheKey();
+ // QPalette::ColorGroup has range 0..5 (inclusive), so it only uses 3 bits.
+ // The high 32 bits in QPalette::cacheKey() are a global incrementing serial
+ // number for the QPalette creation. We don't store (2^29-1) things in our
+ // cache, and I doubt that many will ever be created in a real application
+ // while also retaining some of them across such a broad time range, so it's
+ // really unlikely that repurposing these top 3 bits to also include the
+ // QPalette::currentColorGroup() (which the cacheKey doesn't include for some
+ // reason...) will generate a collision.
+ //
+ // This may not be true in the future if the way the QPalette::cacheKey() is
+ // generated changes. If that happens, change to use the definition of
+ // `fastfragile_hash_qpalette` below, which is less likely to collide with an
+ // arbitrarily numbered key but also does more work.
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ x.u = x.u ^ (static_cast<quint64>(p.currentColorGroup()) << (64 - 3));
+ return x.u;
+#else
+ // Use this definition here if the contents/layout of QPalette::cacheKey()
+ // (as in, the C++ code in qpalette.cpp) are changed. We'll also put a Qt6
+ // guard for it, so that it will default to a more safe definition on the
+ // next guaranteed big breaking change for Qt. A warning will hopefully get
+ // someone to double-check it at some point in the future.
+#warning "Verify contents and layout of QPalette::cacheKey() have not changed"
+ QtPrivate::QHashCombine c;
+ uint h = qHash(p.currentColorGroup());
+ h = c(h, (uint)(x.u & 0xFFFFFFFFu));
+ h = c(h, (uint)((x.u >> 32) & 0xFFFFFFFFu));
+ return h;
+#endif
+ }
+
+ // This hash function is for when we want an actual accurate hash of a
+ // QPalette. QPalette's cacheKey() isn't very reliable -- it seems to change to
+ // a new random number whenever it's modified, with the exception of the
+ // currentColorGroup being changed. This kind of sucks for us, because it means
+ // two QPalette's can have the same contents but hash to different values. And
+ // this actually happens a lot! We'll do the hashing ourselves. Also, we're not
+ // interested in all of the colors, only some of them, and we ignore
+ // pens/brushes.
+ uint accurate_hash_qpalette(const QPalette& p)
+ {
+ // Probably shouldn't use this, could replace with our own guy. It's not a
+ // great hasher anyway.
+ QtPrivate::QHashCombine c;
+ uint h = qHash(p.currentColorGroup());
+ QPalette::ColorRole const roles[] = {QPalette::Window,
+ QPalette::Button,
+ QPalette::Base,
+ QPalette::Text,
+ QPalette::WindowText,
+ QPalette::Highlight,
+ QPalette::HighlightedText};
+ for (auto role : roles) {
+ h = c(h, p.color(role).rgb());
+ }
+ return h;
+ }
+
+ Q_NEVER_INLINE PhSwatchPtr
+ deep_getCachedSwatchOfQPalette(PhSwatchCache* cache,
+ int cacheCount, // Just saving a call to cache->count()
+ const QPalette& qpalette)
+ {
+ // Calculate our hash key from the QPalette's current ColorGroup and the
+ // actual RGBA values that we use. We have to mix the ColorGroup in
+ // ourselves, because QPalette does not account for it in the cache key.
+ uint key = accurate_hash_qpalette(qpalette);
+ int n = cacheCount;
+ int idx = -1;
+ for (int i = 0; i < n; ++i) {
+ const auto& x = cache->at(i);
+ if (x.first == key) {
+ idx = i;
+ break;
+ }
+ }
+ if (idx == -1) {
+ PhSwatchPtr ptr;
+ if (n < Num_ColorCacheEntries) {
+ ptr = new PhSwatch;
+ } else {
+ // Remove the oldest guy from the cache. Remember that because we may
+ // re-enter QStyle functions multiple times when drawing or calculating
+ // something, we may have to load several swaitches derived from
+ // different QPalettes on different stack frames at the same time. But as
+ // an extra cost-savings measure, we'll check and see if something else
+ // has a reference to the removed guy. If there aren't any references to
+ // it, then we'll re-use it directly instead of allocating a new one. (We
+ // will only ever run into the case where we can't re-use it directly if
+ // some other stack frame has a reference to it.) This is nice because
+ // then the QPens and QBrushes don't all also have to reallocate their d
+ // ptr stuff.
+ ptr = cache->last().second;
+ cache->removeLast();
+ ptr.detach();
+ }
+ ptr->loadFromQPalette(qpalette);
+ cache->prepend(PhCacheEntry(key, ptr));
+ return ptr;
+ } else {
+ if (idx == 0) {
+ return cache->at(idx).second;
+ }
+ PhCacheEntry e = cache->at(idx);
+ // Using std::move from algorithm could be more efficient here, but I don't
+ // want to depend on algorithm or write this myself. Small N with a movable
+ // type means it doesn't really matter in this case.
+ cache->remove(idx);
+ cache->prepend(e);
+ return e.second;
+ }
+ }
+
+ Q_NEVER_INLINE PhSwatchPtr
+ getCachedSwatchOfQPalette(PhSwatchCache* cache,
+ quint64* headSwatchFastKey, // Optimistic fast-path quick hash key
+ const QPalette& qpalette)
+ {
+ quint64 ck = fastfragile_hash_qpalette(qpalette);
+ int cacheCount = cache->count();
+ // This hint is counter-productive if we're being called in a way that
+ // interleaves different QPalettes. But misses to this optimistic path were
+ // rare in my tests. (Probably not going to amount to any significant
+ // difference, anyway.)
+ if (Q_LIKELY(cacheCount > 0 && *headSwatchFastKey == ck)) {
+ return cache->at(0).second;
+ }
+ *headSwatchFastKey = ck;
+ return deep_getCachedSwatchOfQPalette(cache, cacheCount, qpalette);
+ }
+
+ } // namespace
+} // namespace Phantom
+
+class BaseStylePrivate
+{
+public:
+ BaseStylePrivate();
+
+ // A fast'n'easy hash of QPalette::cacheKey()+QPalette::currentColorGroup()
+ // of only the head element of swatchCache list. The most common thing that
+ // happens when deriving a PhSwatch from a QPalette is that we just end up
+ // re-using the last one that we used. For that case, we can potentially save
+ // calling `accurate_hash_qpalette()` and instead use the value returned by
+ // QPalette::cacheKey() (and QPalette::currentColorGroup()) and compare it to
+ // the last one that we used. If it matches, then we know we can just use the
+ // head of the cache list without having to do any further checks, which
+ // saves a few hundred (!) nanoseconds.
+ //
+ // However, the `QPalette::cacheKey()` value is fragile and may change even
+ // if none of the colors in the QPalette have changed. In other words, all of
+ // the colors in a QPalette may match another QPalette (or a derived
+ // PhSwatch) even if the `QPalette::cacheKey()` value is different.
+ //
+ // So if `QPalette::cacheKey()+currentColorGroup()` doesn't match, then we'll
+ // use our more accurate `accurate_hash_qpalette()` to get a more accurate
+ // comparison key, and then search through the cache list to find a matching
+ // cached PhSwatch. (The more accurate cache key is what we store alongside
+ // each PhSwatch element, as the `.first` in each QPair. The
+ // QPalette::cacheKey() that we associate with the PhSwatch in the head
+ // position, `headSwatchFastKey`, is only stored for our single head element,
+ // as a special fast case.) If we find it, we'll move it to the head of the
+ // cache list. If not, we'll make a new one, and put it at the head. Either
+ // way, the `headSwatchFastKey` will be updated to the
+ // `fastfragile_qpalette_hash()` of the QPalette that we needed to derive a
+ // PhSwatch from, so that if we get called with the same QPalette again next
+ // time (which is probably going to be the case), it'll match and we can take
+ // the fast path.
+ quint64 headSwatchFastKey;
+
+ Phantom::PhSwatchCache swatchCache;
+ QPen checkBox_pen_scratch;
+};
+
+namespace Phantom
+{
+ namespace
+ {
+
+ // Minimal QPainter save/restore just for pen, brush, and AA render hint. If
+ // you touch more than that, this won't help you. But if you're only touching
+ // those things, this will save you some typing from manually storing/saving
+ // those properties each time.
+ struct PSave final
+ {
+ Q_DISABLE_COPY(PSave)
+
+ explicit PSave(QPainter* painter_)
+ {
+ Q_ASSERT(painter_);
+ painter = painter_;
+ pen = painter_->pen();
+ brush = painter_->brush();
+ hintAA = painter_->testRenderHint(QPainter::Antialiasing);
+ }
+ Q_NEVER_INLINE void restore()
+ {
+ QPainter* p = painter;
+ if (!p)
+ return;
+ bool hintAA_ = hintAA;
+ // QPainter will check both pen and brush for equality when setting, so we
+ // should set it unconditionally here.
+ p->setPen(pen);
+ p->setBrush(brush);
+ // But it won't check the render hint to guard against doing extra work.
+ // We'll do that ourselves. (Though at least for the raster engine, this
+ // doesn't cause very much work to occur. But it still chases a few
+ // pointers.)
+ if (p->testRenderHint(QPainter::Antialiasing) != hintAA_) {
+ p->setRenderHint(QPainter::Antialiasing, hintAA_);
+ }
+ painter = nullptr;
+ pen = QPen();
+ brush = QBrush();
+ hintAA = false;
+ }
+ ~PSave()
+ {
+ restore();
+ }
+
+ private:
+ QPainter* painter;
+ QPen pen;
+ QBrush brush;
+ bool hintAA;
+ };
+
+ const qreal Pi = M_PI;
+
+ qreal dpiScaled(qreal value)
+ {
+#ifdef Q_OS_MAC
+ // On mac the DPI is always 72 so we should not scale it
+ return value;
+#else
+ const qreal scale = qt_defaultDpiX() / 96.0;
+ return value * scale;
+#endif
+ }
+
+ struct MenuItemMetrics
+ {
+ int fontHeight;
+ int frameThickness;
+ int leftMargin;
+ int rightMarginForText;
+ int rightMarginForArrow;
+ int topMargin;
+ int bottomMargin;
+ int checkWidth;
+ int checkRightSpace;
+ int iconRightSpace;
+ int mnemonicSpace;
+ int arrowSpace;
+ int arrowWidth;
+ int separatorHeight;
+ int totalHeight;
+
+ static MenuItemMetrics ofFontHeight(int fontHeight);
+
+ private:
+ MenuItemMetrics()
+ {
+ }
+ };
+
+ MenuItemMetrics MenuItemMetrics::ofFontHeight(int fontHeight)
+ {
+ MenuItemMetrics m;
+ m.fontHeight = fontHeight;
+ m.frameThickness = dpiScaled(1.0);
+ m.leftMargin = static_cast<int>(fontHeight * MenuItem_LeftMarginFontRatio);
+ m.rightMarginForText = static_cast<int>(fontHeight * MenuItem_RightMarginForTextFontRatio);
+ m.rightMarginForArrow = static_cast<int>(fontHeight * MenuItem_RightMarginForArrowFontRatio);
+ m.topMargin = static_cast<int>(fontHeight * MenuItem_VerticalMarginsFontRatio);
+ m.bottomMargin = static_cast<int>(fontHeight * MenuItem_VerticalMarginsFontRatio);
+ int checkVMargin = static_cast<int>(fontHeight * MenuItem_CheckMarkVerticalInsetFontRatio);
+ int checkHeight = fontHeight - checkVMargin * 2;
+ if (checkHeight < 0)
+ checkHeight = 0;
+ m.checkWidth = static_cast<int>(checkHeight * CheckMark_WidthOfHeightScale);
+ m.checkRightSpace = static_cast<int>(fontHeight * MenuItem_CheckRightSpaceFontRatio);
+ m.iconRightSpace = static_cast<int>(fontHeight * MenuItem_IconRightSpaceFontRatio);
+ m.mnemonicSpace = static_cast<int>(fontHeight * MenuItem_TextMnemonicSpaceFontRatio);
+ m.arrowSpace = static_cast<int>(fontHeight * MenuItem_SubMenuArrowSpaceFontRatio);
+ m.arrowWidth = static_cast<int>(fontHeight * MenuItem_SubMenuArrowWidthFontRatio);
+ m.separatorHeight = static_cast<int>(fontHeight * MenuItem_SeparatorHeightFontRatio);
+ // Odd numbers only
+ m.separatorHeight = (m.separatorHeight / 2) * 2 + 1;
+ m.totalHeight = fontHeight + m.frameThickness * 2 + m.topMargin + m.bottomMargin;
+ return m;
+ }
+
+ QRect menuItemContentRect(const MenuItemMetrics& metrics, QRect itemRect, bool hasArrow)
+ {
+ QRect r = itemRect;
+ int ft = metrics.frameThickness;
+ int rm = hasArrow ? metrics.rightMarginForArrow : metrics.rightMarginForText;
+ r.adjust(ft + metrics.leftMargin, ft + metrics.topMargin, -(ft + rm), -(ft + metrics.bottomMargin));
+ return r.isValid() ? r : QRect();
+ }
+ QRect
+ menuItemCheckRect(const MenuItemMetrics& metrics, Qt::LayoutDirection direction, QRect itemRect, bool hasArrow)
+ {
+ QRect r = menuItemContentRect(metrics, itemRect, hasArrow);
+ int checkVMargin = static_cast<int>(metrics.fontHeight * MenuItem_CheckMarkVerticalInsetFontRatio);
+ if (checkVMargin < 0)
+ checkVMargin = 0;
+ r.setSize(QSize(metrics.checkWidth, metrics.fontHeight));
+ r.adjust(0, checkVMargin, 0, -checkVMargin);
+ return QStyle::visualRect(direction, itemRect, r) & itemRect;
+ }
+ QRect
+ menuItemIconRect(const MenuItemMetrics& metrics, Qt::LayoutDirection direction, QRect itemRect, bool hasArrow)
+ {
+ QRect r = menuItemContentRect(metrics, itemRect, hasArrow);
+ r.setX(r.x() + metrics.checkWidth + metrics.checkRightSpace);
+ r.setSize(QSize(metrics.fontHeight, metrics.fontHeight));
+ return QStyle::visualRect(direction, itemRect, r) & itemRect;
+ }
+ QRect menuItemTextRect(const MenuItemMetrics& metrics,
+ Qt::LayoutDirection direction,
+ QRect itemRect,
+ bool hasArrow,
+ bool hasIcon,
+ int tabWidth)
+ {
+ QRect r = menuItemContentRect(metrics, itemRect, hasArrow);
+ r.setX(r.x() + metrics.checkWidth + metrics.checkRightSpace);
+ if (hasIcon) {
+ r.setX(r.x() + metrics.fontHeight + metrics.iconRightSpace);
+ }
+ r.setWidth(r.width() - tabWidth);
+ r.setHeight(metrics.fontHeight);
+ r &= itemRect;
+ return QStyle::visualRect(direction, itemRect, r);
+ }
+ QRect menuItemMnemonicRect(const MenuItemMetrics& metrics,
+ Qt::LayoutDirection direction,
+ QRect itemRect,
+ bool hasArrow,
+ int tabWidth)
+ {
+ QRect r = menuItemContentRect(metrics, itemRect, hasArrow);
+ int x = r.x() + r.width() - tabWidth;
+ if (hasArrow)
+ x -= metrics.arrowSpace + metrics.arrowWidth;
+ r.setX(x);
+ r.setHeight(metrics.fontHeight);
+ r &= itemRect;
+ return QStyle::visualRect(direction, itemRect, r);
+ }
+ QRect menuItemArrowRect(const MenuItemMetrics& metrics, Qt::LayoutDirection direction, QRect itemRect)
+ {
+ QRect r = menuItemContentRect(metrics, itemRect, true);
+ int x = r.x() + r.width() - metrics.arrowWidth;
+ r.setX(x);
+ r &= itemRect;
+ return QStyle::visualRect(direction, itemRect, r);
+ }
+
+ Q_NEVER_INLINE
+ void progressBarFillRects(const QStyleOptionProgressBar* bar,
+ // The rect that represents the filled/completed region
+ QRect& outFilled,
+ // The rect that represents the incomplete region
+ QRect& outNonFilled,
+ // Whether or not the progress bar is indeterminate
+ bool& outIsIndeterminate)
+ {
+ QRect ra = bar->rect;
+ QRect rb = ra;
+ bool isHorizontal = bar->orientation != Qt::Vertical;
+ bool isInverted = bar->invertedAppearance;
+ bool isIndeterminate = bar->minimum == 0 && bar->maximum == 0;
+ bool isForward = !isHorizontal || bar->direction != Qt::RightToLeft;
+ if (isInverted)
+ isForward = !isForward;
+ int maxLen = isHorizontal ? ra.width() : ra.height();
+ const auto availSteps = qMax(Q_INT64_C(1), qint64(bar->maximum) - bar->minimum);
+ const auto progress = qMax(bar->progress, bar->minimum); // workaround for bug in QProgressBar
+ const auto progressSteps = qint64(progress) - bar->minimum;
+ const auto progressBarWidth = progressSteps * maxLen / availSteps;
+ int barLen = isIndeterminate ? maxLen : progressBarWidth;
+ if (isHorizontal) {
+ if (isForward) {
+ ra.setWidth(barLen);
+ rb.setX(barLen);
+ } else {
+ ra.setX(ra.x() + ra.width() - barLen);
+ rb.setWidth(rb.width() - barLen);
+ }
+ } else {
+ if (isForward) {
+ ra.setY(ra.y() + ra.height() - barLen);
+ rb.setHeight(rb.height() - barLen);
+ } else {
+ ra.setHeight(barLen);
+ rb.setY(barLen);
+ }
+ }
+ outFilled = ra;
+ outNonFilled = rb;
+ outIsIndeterminate = isIndeterminate;
+ }
+
+ int calcBigLineSize(int radius)
+ {
+ int bigLineSize = radius / 6;
+ if (bigLineSize < 4)
+ bigLineSize = 4;
+ if (bigLineSize > radius / 2)
+ bigLineSize = radius / 2;
+ return bigLineSize;
+ }
+ Q_NEVER_INLINE QPointF calcRadialPos(const QStyleOptionSlider* dial, qreal offset)
+ {
+ const int width = dial->rect.width();
+ const int height = dial->rect.height();
+ const int r = qMin(width, height) / 2;
+ const int currentSliderPosition =
+ dial->upsideDown ? dial->sliderPosition : (dial->maximum - dial->sliderPosition);
+ qreal a = 0;
+ if (dial->maximum == dial->minimum)
+ a = Pi / 2;
+ else if (dial->dialWrapping)
+ a = Pi * 3 / 2 - (currentSliderPosition - dial->minimum) * 2 * Pi / (dial->maximum - dial->minimum);
+ else
+ a = (Pi * 8 - (currentSliderPosition - dial->minimum) * 10 * Pi / (dial->maximum - dial->minimum)) / 6;
+ qreal xc = width / 2.0;
+ qreal yc = height / 2.0;
+ qreal len = r - calcBigLineSize(r) - 3;
+ qreal back = offset * len;
+ QPointF pos(QPointF(xc + back * qCos(a), yc - back * qSin(a)));
+ return pos;
+ }
+ Q_NEVER_INLINE QPolygonF calcLines(const QStyleOptionSlider* dial)
+ {
+ QPolygonF poly;
+ qreal width = dial->rect.width();
+ qreal height = dial->rect.height();
+ qreal r = qMin(width, height) / 2.0;
+ int bigLineSize = calcBigLineSize(r);
+
+ qreal xc = width / 2.0 + 0.5;
+ qreal yc = height / 2.0 + 0.5;
+ const int ns = dial->tickInterval;
+ if (!ns) // Invalid values may be set by Qt Designer.
+ return poly;
+ int notches = (dial->maximum + ns - 1 - dial->minimum) / ns;
+ if (notches <= 0)
+ return poly;
+ if (dial->maximum < dial->minimum || dial->maximum - dial->minimum > 1000) {
+ int maximum = dial->minimum + 1000;
+ notches = (maximum + ns - 1 - dial->minimum) / ns;
+ }
+ poly.resize(2 + 2 * notches);
+ int smallLineSize = bigLineSize / 2;
+ for (int i = 0; i <= notches; ++i) {
+ qreal angle =
+ dial->dialWrapping ? Pi * 3 / 2 - i * 2 * Pi / notches : (Pi * 8 - i * 10 * Pi / notches) / 6;
+ qreal s = qSin(angle);
+ qreal c = qCos(angle);
+ if (i == 0 || (((ns * i) % (dial->pageStep ? dial->pageStep : 1)) == 0)) {
+ poly[2 * i] = QPointF(xc + (r - bigLineSize) * c, yc - (r - bigLineSize) * s);
+ poly[2 * i + 1] = QPointF(xc + r * c, yc - r * s);
+ } else {
+ poly[2 * i] = QPointF(xc + (r - 1 - smallLineSize) * c, yc - (r - 1 - smallLineSize) * s);
+ poly[2 * i + 1] = QPointF(xc + (r - 1) * c, yc - (r - 1) * s);
+ }
+ }
+ return poly;
+ }
+ // This will draw a nice and shiny QDial for us. We don't want
+ // all the shinyness in QWindowsStyle, hence we place it here
+ Q_NEVER_INLINE void drawDial(const QStyleOptionSlider* option, QPainter* painter)
+ {
+ namespace Dc = Phantom::DeriveColors;
+ const QPalette& pal = option->palette;
+ QColor buttonColor = Dc::buttonColor(option->palette);
+ const int width = option->rect.width();
+ const int height = option->rect.height();
+ const bool enabled = option->state & QStyle::State_Enabled;
+ qreal r = qMin(width, height) / 2.0;
+ r -= r / 50.0;
+ painter->save();
+ painter->setRenderHint(QPainter::Antialiasing);
+ // Draw notches
+ if (option->subControls & QStyle::SC_DialTickmarks) {
+ painter->setPen(pal.color(QPalette::Disabled, QPalette::Text));
+ painter->drawLines(calcLines(option));
+ }
+ const qreal d_ = r / 6;
+ const qreal dx = option->rect.x() + d_ + (width - 2 * r) / 2 + 1;
+ const qreal dy = option->rect.y() + d_ + (height - 2 * r) / 2 + 1;
+ QRectF br = QRectF(dx + 0.5, dy + 0.5, int(r * 2 - 2 * d_ - 2), int(r * 2 - 2 * d_ - 2));
+ if (enabled) {
+ painter->setBrush(buttonColor);
+ } else {
+ painter->setBrush(Qt::NoBrush);
+ }
+ painter->setPen(Dc::outlineOf(option->palette));
+ painter->drawEllipse(br);
+ painter->setBrush(Qt::NoBrush);
+ painter->setPen(Dc::specularOf(buttonColor));
+ painter->drawEllipse(br.adjusted(1, 1, -1, -1));
+ if (option->state & QStyle::State_HasFocus) {
+ QColor highlight = pal.highlight().color();
+ highlight.setHsv(highlight.hue(), qMin(160, highlight.saturation()), qMax(230, highlight.value()));
+ highlight.setAlpha(127);
+ painter->setPen(QPen(highlight, 2.0));
+ painter->setBrush(Qt::NoBrush);
+ painter->drawEllipse(br.adjusted(-1, -1, 1, 1));
+ }
+ QPointF dp = calcRadialPos(option, 0.70);
+ const qreal ds = r / 7.0;
+ QRectF dialRect(dp.x() - ds, dp.y() - ds, 2 * ds, 2 * ds);
+ painter->setBrush(option->palette.color(QPalette::Window));
+ painter->setPen(Dc::outlineOf(option->palette));
+ painter->drawEllipse(dialRect.adjusted(-1, -1, 1, 1));
+ painter->restore();
+ }
+
+ int fontMetricsWidth(const QFontMetrics& fontMetrics, const QString& text)
+ {
+#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
+ return fontMetrics.width(text, text.size(), Qt::TextBypassShaping);
+#else
+ return fontMetrics.horizontalAdvance(text);
+#endif
+ }
+
+ // This always draws the arrow with the correct aspect ratio, even if the
+ // provided bounding rect is non-square. The base edge of the triangle is
+ // snapped to a whole pixel to avoid anti-aliasing making it look soft.
+ //
+ // Expected time (release): 5usecs for regular-sized arrows
+ Q_NEVER_INLINE void drawArrow(QPainter* p, QRect rect, Qt::ArrowType arrowDirection, const QBrush& brush)
+ {
+ const qreal ArrowBaseRatio = 0.70;
+ qreal irx, iry, irw, irh;
+ QRectF(rect).getRect(&irx, &iry, &irw, &irh);
+ if (irw < 1.0 || irh < 1.0)
+ return;
+ qreal dw, dh;
+ if (arrowDirection == Qt::LeftArrow || arrowDirection == Qt::RightArrow) {
+ dw = ArrowBaseRatio;
+ dh = 1.0;
+ } else {
+ dw = 1.0;
+ dh = ArrowBaseRatio;
+ }
+ QSizeF sz = QSizeF(dw, dh).scaled(irw, irh, Qt::KeepAspectRatio);
+ qreal aw = sz.width();
+ qreal ah = sz.height();
+ qreal ax, ay;
+ ax = irx + (irw - aw) / 2;
+ ay = iry + (irh - ah) / 2;
+ QRectF arrowRect(ax, ay, aw, ah);
+ QPointF points[3];
+ switch (arrowDirection) {
+ case Qt::DownArrow:
+ arrowRect.setTop(std::round(arrowRect.top()));
+ points[0] = arrowRect.topLeft();
+ points[1] = arrowRect.topRight();
+ points[2] = QPointF(arrowRect.center().x(), arrowRect.bottom());
+ break;
+ case Qt::RightArrow: {
+ arrowRect.setLeft(std::round(arrowRect.left()));
+ points[0] = arrowRect.topLeft();
+ points[1] = arrowRect.bottomLeft();
+ points[2] = QPointF(arrowRect.right(), arrowRect.center().y());
+ break;
+ }
+ case Qt::LeftArrow:
+ arrowRect.setRight(std::round(arrowRect.right()));
+ points[0] = arrowRect.topRight();
+ points[1] = arrowRect.bottomRight();
+ points[2] = QPointF(arrowRect.left(), arrowRect.center().y());
+ break;
+ case Qt::UpArrow:
+ default:
+ arrowRect.setBottom(std::round(arrowRect.bottom()));
+ points[0] = arrowRect.bottomLeft();
+ points[1] = arrowRect.bottomRight();
+ points[2] = QPointF(arrowRect.center().x(), arrowRect.top());
+ break;
+ }
+ auto oldPen = p->pen();
+ auto oldBrush = p->brush();
+ bool oldAA = p->testRenderHint(QPainter::Antialiasing);
+ p->setPen(Qt::NoPen);
+ p->setBrush(brush);
+ if (!oldAA) {
+ p->setRenderHint(QPainter::Antialiasing);
+ }
+ p->drawConvexPolygon(points, 3);
+ p->setPen(oldPen);
+ p->setBrush(oldBrush);
+ if (!oldAA) {
+ p->setRenderHint(QPainter::Antialiasing, false);
+ }
+ }
+
+ // Pass allowEnabled as false to always draw the arrow with the disabled color,
+ // even if the underlying palette's current color group is not disabled. Useful
+ // for parts of widgets which may want to be drawn as disabled even if the
+ // actual widget is not set as disabled, such as scrollbar step buttons when
+ // the scrollbar has no movable range.
+ Q_NEVER_INLINE void drawArrow(QPainter* painter,
+ QRect rect,
+ Qt::ArrowType type,
+ const PhSwatch& swatch,
+ bool allowEnabled = true,
+ qreal lightnessAdjustment = 0.0)
+ {
+ if (rect.isEmpty())
+ return;
+ using namespace SwatchColors;
+ auto brush = swatch.brush(allowEnabled ? S_indicator_current : S_indicator_disabled);
+ brush.setColor(DeriveColors::adjustLightness(brush.color(), lightnessAdjustment));
+ Phantom::drawArrow(painter, rect, type, brush);
+ }
+
+ // This draws exactly within the rect provided. If you provide a square rect,
+ // it will appear too wide -- you probably want to shrink the width of your
+ // square first by multiplying it with CheckMark_WidthOfHeightScale.
+ Q_NEVER_INLINE void
+ drawCheck(QPainter* painter, QPen& scratchPen, const QRectF& r, const PhSwatch& swatch, Swatchy color)
+ {
+ using namespace Phantom::SwatchColors;
+ qreal rx, ry, rw, rh;
+ QRectF(r).getRect(&rx, &ry, &rw, &rh);
+ qreal penWidth = 0.25 * qMin(rw, rh);
+ qreal dimx = rw - penWidth;
+ qreal dimy = rh - penWidth;
+ if (dimx < 0.5 || dimy < 0.5)
+ return;
+ qreal x = (rw - dimx) / 2 + rx;
+ qreal y = (rh - dimy) / 2 + ry;
+ QPointF points[3];
+ points[0] = QPointF(0.0, 0.55);
+ points[1] = QPointF(0.4, 1.0);
+ points[2] = QPointF(1.0, 0);
+ for (int i = 0; i < 3; ++i) {
+ QPointF pnt = points[i];
+ pnt.setX(pnt.x() * dimx + x);
+ pnt.setY(pnt.y() * dimy + y);
+ points[i] = pnt;
+ }
+ scratchPen.setBrush(swatch.brush(color));
+ scratchPen.setCapStyle(Qt::RoundCap);
+ scratchPen.setJoinStyle(Qt::RoundJoin);
+ scratchPen.setWidthF(penWidth);
+ Phantom::PSave save(painter);
+ if (!painter->testRenderHint(QPainter::Antialiasing))
+ painter->setRenderHint(QPainter::Antialiasing);
+ painter->setPen(scratchPen);
+ painter->setBrush(Qt::NoBrush);
+ painter->drawPolyline(points, 3);
+ }
+
+ Q_NEVER_INLINE void
+ drawHyphen(QPainter* painter, QPen& scratchPen, const QRectF& r, const PhSwatch& swatch, Swatchy color)
+ {
+ using namespace Phantom::SwatchColors;
+ qreal rx, ry, rw, rh;
+ QRectF(r).getRect(&rx, &ry, &rw, &rh);
+ qreal penWidth = 0.25 * qMin(rw, rh);
+ qreal dimx = rw - penWidth;
+ qreal dimy = rh - penWidth;
+ if (dimx < 0.5 || dimy < 0.5)
+ return;
+ qreal x = (rw - dimx) / 2 + rx;
+ qreal y = (rh - dimy) / 2 + ry;
+ QPointF p0(0.0 * dimx + x, 0.5 * dimy + y);
+ QPointF p1(1.0 * dimx + x, 0.5 * dimy + y);
+ scratchPen.setBrush(swatch.brush(color));
+ scratchPen.setCapStyle(Qt::RoundCap);
+ scratchPen.setWidthF(penWidth);
+ Phantom::PSave save(painter);
+ if (!painter->testRenderHint(QPainter::Antialiasing))
+ painter->setRenderHint(QPainter::Antialiasing);
+ painter->setPen(scratchPen);
+ painter->setBrush(Qt::NoBrush);
+ painter->drawLine(p0, p1);
+ }
+
+ Q_NEVER_INLINE void
+ drawMdiButton(QPainter* painter, const QStyleOptionTitleBar* option, QRect tmp, bool hover, bool sunken)
+ {
+ QColor dark;
+ dark.setHsv(option->palette.button().color().hue(),
+ qMin<int>(255, (option->palette.button().color().saturation())),
+ qMin<int>(255, option->palette.button().color().value() * 0.7));
+ QColor highlight = option->palette.highlight().color();
+ bool active = (option->titleBarState & QStyle::State_Active);
+ QColor titleBarHighlight(255, 255, 255, 60);
+ if (sunken)
+ painter->fillRect(tmp.adjusted(1, 1, -1, -1), option->palette.highlight().color().darker(120));
+ else if (hover)
+ painter->fillRect(tmp.adjusted(1, 1, -1, -1), QColor(255, 255, 255, 20));
+ if (sunken)
+ titleBarHighlight = highlight.darker(130);
+ QColor mdiButtonBorderColor(active ? option->palette.highlight().color().darker(180) : dark.darker(110));
+ painter->setPen(QPen(mdiButtonBorderColor));
+ const QLine lines[4] = {QLine(tmp.left() + 2, tmp.top(), tmp.right() - 2, tmp.top()),
+ QLine(tmp.left() + 2, tmp.bottom(), tmp.right() - 2, tmp.bottom()),
+ QLine(tmp.left(), tmp.top() + 2, tmp.left(), tmp.bottom() - 2),
+ QLine(tmp.right(), tmp.top() + 2, tmp.right(), tmp.bottom() - 2)};
+ painter->drawLines(lines, 4);
+ const QPoint points[4] = {QPoint(tmp.left() + 1, tmp.top() + 1),
+ QPoint(tmp.right() - 1, tmp.top() + 1),
+ QPoint(tmp.left() + 1, tmp.bottom() - 1),
+ QPoint(tmp.right() - 1, tmp.bottom() - 1)};
+ painter->drawPoints(points, 4);
+ painter->setPen(titleBarHighlight);
+ painter->drawLine(tmp.left() + 2, tmp.top() + 1, tmp.right() - 2, tmp.top() + 1);
+ painter->drawLine(tmp.left() + 1, tmp.top() + 2, tmp.left() + 1, tmp.bottom() - 2);
+ }
+
+ Q_NEVER_INLINE void fillRectOutline(QPainter* p, QRect rect, QMargins margins, const QColor& brush)
+ {
+ int x, y, w, h;
+ rect.getRect(&x, &y, &w, &h);
+ int ml = margins.left();
+ int mt = margins.top();
+ int mr = margins.right();
+ int mb = margins.bottom();
+ QRect r0(x, y, w, mt);
+ QRect r1(x, y + mt, ml, h - (mt + mb));
+ QRect r2((x + w) - mr, y + mt, mr, h - (mt + mb));
+ QRect r3(x, (y + h) - mb, w, mb);
+ p->fillRect(r0 & rect, brush);
+ p->fillRect(r1 & rect, brush);
+ p->fillRect(r2 & rect, brush);
+ p->fillRect(r3 & rect, brush);
+ }
+ void fillRectOutline(QPainter* p, QRect rect, int thickness, const QColor& color)
+ {
+ fillRectOutline(p, rect, QMargins(thickness, thickness, thickness, thickness), color);
+ }
+ Q_NEVER_INLINE void
+ fillRectEdges(QPainter* p, QRect rect, Qt::Edges edges, QMargins margins, const QColor& color)
+ {
+ int x, y, w, h;
+ rect.getRect(&x, &y, &w, &h);
+ if (edges & Qt::LeftEdge) {
+ int ml = margins.left();
+ QRect r0(x, y, ml, h);
+ p->fillRect(r0 & rect, color);
+ }
+ if (edges & Qt::TopEdge) {
+ int mt = margins.top();
+ QRect r1(x, y, w, mt);
+ p->fillRect(r1 & rect, color);
+ }
+ if (edges & Qt::RightEdge) {
+ int mr = margins.right();
+ QRect r2((x + w) - mr, y, mr, h);
+ p->fillRect(r2 & rect, color);
+ }
+ if (edges & Qt::BottomEdge) {
+ int mb = margins.bottom();
+ QRect r3(x, (y + h) - mb, w, mb);
+ p->fillRect(r3 & rect, color);
+ }
+ }
+ void fillRectEdges(QPainter* p, QRect rect, Qt::Edges edges, int thickness, const QColor& color)
+ {
+ fillRectEdges(p, rect, edges, QMargins(thickness, thickness, thickness, thickness), color);
+ }
+ inline QRect expandRect(QRect rect, Qt::Edges edges, int delta)
+ {
+ int l = edges & Qt::LeftEdge ? -delta : 0;
+ int t = edges & Qt::TopEdge ? -delta : 0;
+ int r = edges & Qt::RightEdge ? delta : 0;
+ int b = edges & Qt::BottomEdge ? delta : 0;
+ return rect.adjusted(l, t, r, b);
+ }
+ inline Qt::Edge oppositeEdge(Qt::Edge edge)
+ {
+ switch (edge) {
+ case Qt::LeftEdge:
+ return Qt::RightEdge;
+ case Qt::TopEdge:
+ return Qt::BottomEdge;
+ case Qt::RightEdge:
+ return Qt::LeftEdge;
+ case Qt::BottomEdge:
+ return Qt::TopEdge;
+ }
+ return Qt::TopEdge;
+ }
+ inline QRect rectTranslatedTowardEdge(QRect rect, Qt::Edge edge, int delta)
+ {
+ switch (edge) {
+ case Qt::LeftEdge:
+ return rect.translated(-delta, 0);
+ case Qt::TopEdge:
+ return rect.translated(0, -delta);
+ case Qt::RightEdge:
+ return rect.translated(delta, 0);
+ case Qt::BottomEdge:
+ return rect.translated(0, delta);
+ }
+ return rect;
+ }
+ Q_NEVER_INLINE QRect rectFromInnerEdgeWithThickness(QRect rect, Qt::Edge edge, int thickness)
+ {
+ int x, y, w, h;
+ rect.getRect(&x, &y, &w, &h);
+ QRect r;
+ switch (edge) {
+ case Qt::LeftEdge:
+ r = QRect(x, y, thickness, h);
+ break;
+ case Qt::TopEdge:
+ r = QRect(x, y, w, thickness);
+ break;
+ case Qt::RightEdge:
+ r = QRect((x + w) - thickness, y, thickness, h);
+ break;
+ case Qt::BottomEdge:
+ r = QRect(x, (y + h) - thickness, w, thickness);
+ break;
+ }
+ return r & rect;
+ }
+ Q_NEVER_INLINE void
+ paintSolidRoundRect(QPainter* p, QRect rect, qreal radius, const PhSwatch& swatch, Swatchy fill)
+ {
+ if (!fill)
+ return;
+ bool aa = p->testRenderHint(QPainter::Antialiasing);
+ if (radius > 0.5) {
+ if (!aa)
+ p->setRenderHint(QPainter::Antialiasing);
+ p->setPen(swatch.pen(SwatchColors::S_none));
+ p->setBrush(swatch.brush(fill));
+ p->drawRoundedRect(rect, radius, radius);
+ } else {
+ if (aa)
+ p->setRenderHint(QPainter::Antialiasing, false);
+ p->fillRect(rect, swatch.color(fill));
+ }
+ }
+ Q_NEVER_INLINE void paintBorderedRoundRect(QPainter* p,
+ QRect rect,
+ qreal radius,
+ const PhSwatch& swatch,
+ Swatchy stroke,
+ Swatchy fill)
+ {
+ if (rect.width() < 1 || rect.height() < 1)
+ return;
+ if (!stroke && !fill)
+ return;
+ bool aa = p->testRenderHint(QPainter::Antialiasing);
+ if (radius > 0.5) {
+ if (!aa)
+ p->setRenderHint(QPainter::Antialiasing);
+ p->setPen(swatch.pen(stroke));
+ p->setBrush(swatch.brush(fill));
+ QRectF rf(rect.x() + 0.5, rect.y() + 0.5, rect.width() - 1.0, rect.height() - 1.0);
+ p->drawRoundedRect(rf, radius, radius);
+ } else {
+ if (aa)
+ p->setRenderHint(QPainter::Antialiasing, false);
+ if (stroke) {
+ fillRectOutline(p, rect, 1, swatch.color(stroke));
+ }
+ if (fill) {
+ p->fillRect(rect.adjusted(1, 1, -1, -1), swatch.color(fill));
+ }
+ }
+ }
+ } // namespace
+} // namespace Phantom
+
+BaseStylePrivate::BaseStylePrivate()
+ : headSwatchFastKey(0)
+{
+}
+
+BaseStyle::BaseStyle()
+ : d(new BaseStylePrivate)
+{
+ setObjectName(QLatin1String("Phantom"));
+}
+
+BaseStyle::~BaseStyle()
+{
+ delete d;
+}
+
+// Draw text in a rectangle. The current pen set on the painter is used, unless
+// an explicit textRole is set, in which case the palette will be used. The
+// enabled bool indicates whether the text is enabled or not, and can influence
+// how the text is drawn outside of just color. Wrapping and alignment flags
+// can be passed in `alignment`.
+void BaseStyle::drawItemText(QPainter* painter,
+ const QRect& rect,
+ int alignment,
+ const QPalette& pal,
+ bool enabled,
+ const QString& text,
+ QPalette::ColorRole textRole) const
+{
+ Q_UNUSED(enabled);
+ if (text.isEmpty())
+ return;
+ if (textRole == QPalette::NoRole) {
+ painter->drawText(rect, alignment, text);
+ return;
+ }
+ QPen savedPen = painter->pen();
+ const QBrush& newBrush = pal.brush(textRole);
+ bool changed = false;
+ if (savedPen.brush() != newBrush) {
+ changed = true;
+ painter->setPen(QPen(newBrush, savedPen.widthF()));
+ }
+ painter->drawText(rect, alignment, text);
+ if (changed) {
+ painter->setPen(savedPen);
+ }
+}
+
+void BaseStyle::drawPrimitive(PrimitiveElement elem,
+ const QStyleOption* option,
+ QPainter* painter,
+ const QWidget* widget) const
+{
+ Q_ASSERT(option);
+ if (!option)
+ return;
+#ifdef BUILD_WITH_EASY_PROFILER
+ EASY_BLOCK("drawPrimitive");
+ const char* elemCString = QMetaEnum::fromType<QStyle::PrimitiveElement>().valueToKey(elem);
+ EASY_TEXT("Element", elemCString);
+#endif
+ using Swatchy = Phantom::Swatchy;
+ using namespace Phantom::SwatchColors;
+ namespace Ph = Phantom;
+ auto ph_swatchPtr = getCachedSwatchOfQPalette(&d->swatchCache, &d->headSwatchFastKey, option->palette);
+ const Ph::PhSwatch& swatch = *ph_swatchPtr.data();
+ const int state = option->state;
+ // Cast to int here to suppress warnings about cases listed which are not in
+ // the original enum. This is for custom primitive elements.
+ switch (static_cast<int>(elem)) {
+ case PE_Frame: {
+ if (widget && widget->inherits("QComboBoxPrivateContainer")) {
+ QStyleOption copy = *option;
+ copy.state |= State_Raised;
+ proxy()->drawPrimitive(PE_PanelMenu, &copy, painter, widget);
+ break;
+ }
+ Ph::fillRectOutline(painter, option->rect, 1, swatch.color(S_frame_outline));
+ break;
+ }
+ case PE_FrameMenu: {
+ break;
+ }
+ case PE_FrameDockWidget: {
+ painter->save();
+ QColor softshadow = option->palette.background().color().darker(120);
+ QRect r = option->rect;
+ painter->setPen(softshadow);
+ painter->drawRect(r.adjusted(0, 0, -1, -1));
+ painter->setPen(QPen(option->palette.light(), 1));
+ painter->drawLine(QPoint(r.left() + 1, r.top() + 1), QPoint(r.left() + 1, r.bottom() - 1));
+ painter->setPen(QPen(option->palette.background().color().darker(120)));
+ painter->drawLine(QPoint(r.left() + 1, r.bottom() - 1), QPoint(r.right() - 2, r.bottom() - 1));
+ painter->drawLine(QPoint(r.right() - 1, r.top() + 1), QPoint(r.right() - 1, r.bottom() - 1));
+ painter->restore();
+ break;
+ }
+ case PE_FrameGroupBox: {
+ QRect frame = option->rect;
+ Ph::PSave save(painter);
+ bool isFlat = false;
+ if (auto groupBox = qstyleoption_cast<const QStyleOptionGroupBox*>(option)) {
+ isFlat = groupBox->features & QStyleOptionFrame::Flat;
+ } else if (auto frameOpt = qstyleoption_cast<const QStyleOptionFrame*>(option)) {
+ isFlat = frameOpt->features & QStyleOptionFrame::Flat;
+ }
+ if (isFlat) {
+ Ph::fillRectEdges(painter, frame, Qt::TopEdge, 1, swatch.color(S_window_divider));
+ } else {
+ Ph::paintBorderedRoundRect(painter, frame, Ph::GroupBox_Rounding, swatch, S_frame_outline, S_none);
+ }
+ break;
+ }
+ case PE_IndicatorBranch: {
+ if (!(option->state & State_Children))
+ break;
+ Qt::ArrowType arrow;
+ if (option->state & State_Open) {
+ arrow = Qt::DownArrow;
+ } else if (option->direction != Qt::RightToLeft) {
+ arrow = Qt::RightArrow;
+ } else {
+ arrow = Qt::LeftArrow;
+ }
+ bool useSelectionColor = false;
+ if (option->state & State_Selected) {
+ if (auto ivopt = qstyleoption_cast<const QStyleOptionViewItem*>(option)) {
+ useSelectionColor = ivopt->showDecorationSelected;
+ }
+ }
+ Swatchy color = useSelectionColor ? S_highlightedText : S_indicator_current;
+ QRect r = option->rect;
+ if (Ph::BranchesOnEdge) {
+ // TODO RTL
+ r.moveLeft(0);
+ if (r.width() < r.height())
+ r.setWidth(r.height());
+ }
+ int adj = qMin(r.width(), r.height()) / 4;
+ r.adjust(adj, adj, -adj, -adj);
+ Ph::drawArrow(painter, r, arrow, swatch.brush(color));
+ break;
+ }
+ case PE_IndicatorMenuCheckMark: {
+ // For this PE, QCommonStyle treats State_On as drawing the check with the
+ // highlighted text color, and otherwise with the regular text color. I
+ // guess we should match that behavior, even though it's not consistent
+ // with other check box/mark drawing in QStyle (buttons and item view
+ // items.) QCommonStyle also doesn't care about tri-state or unchecked
+ // states -- it seems that if you call this, you want a check, and nothing
+ // else.
+ //
+ // We'll also catch State_Selected and treat it equivalently (the way you'd
+ // expect.) We'll use windowText instead of text, though -- probably
+ // doesn't matter.
+ Swatchy fgColor = S_windowText;
+ bool isSelected = option->state & (State_Selected | State_On);
+ bool isEnabled = option->state & State_Enabled;
+ if (isSelected) {
+ fgColor = S_highlightedText;
+ } else if (!isEnabled) {
+ fgColor = S_windowText_disabled;
+ }
+ qreal rx, ry, rw, rh;
+ QRectF(option->rect).getRect(&rx, &ry, &rw, &rh);
+ qreal dim = qMin(rw, rh);
+ const qreal insetScale = 0.8;
+ qreal dimx = dim * insetScale * Ph::CheckMark_WidthOfHeightScale;
+ qreal dimy = dim * insetScale;
+ QRectF r_(rx + (rw - dimx) / 2, ry + (rh - dimy) / 2, dimx, dimy);
+ Ph::drawCheck(painter, d->checkBox_pen_scratch, r_, swatch, fgColor);
+ break;
+ }
+ // Called for the content area on tree view rows that are selected
+ case PE_PanelItemViewItem: {
+ QCommonStyle::drawPrimitive(elem, option, painter, widget);
+ break;
+ }
+ // Called for left-of-item-content-area on tree view rows that are selected
+ case PE_PanelItemViewRow: {
+ QCommonStyle::drawPrimitive(elem, option, painter, widget);
+ break;
+ }
+ case PE_FrameTabBarBase: {
+ auto tbb = qstyleoption_cast<const QStyleOptionTabBarBase*>(option);
+ if (!tbb)
+ break;
+ Qt::Edge edge = Qt::TopEdge;
+ switch (tbb->shape) {
+ case QTabBar::RoundedNorth:
+ case QTabBar::TriangularNorth:
+ edge = Qt::TopEdge;
+ break;
+ case QTabBar::RoundedSouth:
+ case QTabBar::TriangularSouth:
+ edge = Qt::BottomEdge;
+ break;
+ case QTabBar::RoundedWest:
+ case QTabBar::TriangularWest:
+ edge = Qt::LeftEdge;
+ break;
+ case QTabBar::RoundedEast:
+ case QTabBar::TriangularEast:
+ edge = Qt::RightEdge;
+ break;
+ }
+ Ph::fillRectEdges(painter, option->rect, edge, 1, swatch.color(S_frame_outline));
+ // TODO need to check here if we're drawing with window or button color as
+ // the frame fill. Assuming window right now, but could be wrong.
+ Ph::fillRectEdges(painter, Ph::expandRect(option->rect, edge, -1), edge, 1, swatch.color(S_tabFrame_specular));
+ break;
+ }
+ case PE_PanelScrollAreaCorner: {
+ bool isLeftToRight = option->direction != Qt::RightToLeft;
+ Qt::Edges edges = Qt::TopEdge;
+ QRect bgRect = option->rect;
+ if (isLeftToRight) {
+ edges |= Qt::LeftEdge;
+ bgRect.setX(bgRect.x() + 1);
+ } else {
+ edges |= Qt::RightEdge;
+ bgRect.setWidth(bgRect.width() - 1);
+ }
+ painter->fillRect(bgRect, swatch.color(S_window));
+ Ph::fillRectEdges(painter, option->rect, edges, 1, swatch.color(S_window_outline));
+ break;
+ }
+ case PE_IndicatorArrowUp:
+ case PE_IndicatorArrowDown:
+ case PE_IndicatorArrowRight:
+ case PE_IndicatorArrowLeft: {
+ int rx, ry, rw, rh;
+ option->rect.getRect(&rx, &ry, &rw, &rh);
+ if (rw <= 1 || rh <= 1)
+ break;
+ Qt::ArrowType arrow = Qt::UpArrow;
+ switch (elem) {
+ case PE_IndicatorArrowUp:
+ arrow = Qt::UpArrow;
+ break;
+ case PE_IndicatorArrowDown:
+ arrow = Qt::DownArrow;
+ break;
+ case PE_IndicatorArrowRight:
+ arrow = Qt::RightArrow;
+ break;
+ case PE_IndicatorArrowLeft:
+ arrow = Qt::LeftArrow;
+ break;
+ default:
+ break;
+ }
+ // The caller may give us a huge rect and expect a normal-sized icon inside
+ // of it, so we don't want to fill the entire thing with an arrow,
+ // otherwise certain buttons will look weird, like the tab bar scroll
+ // buttons. Might want to break these out into editable parameters?
+ const int MaxArrowExt = Ph::dpiScaled(12);
+ const int MinMargin = qMin(rw, rh) / 4;
+ int aw, ah;
+ aw = qMin(MaxArrowExt, rw) - MinMargin;
+ ah = qMin(MaxArrowExt, rh) - MinMargin;
+ if (aw <= 2 || ah <= 2)
+ break;
+ // QCommonStyle's implementation of CC_ToolButton for non-instant popups
+ // gives us a pretty big rectangle to draw the arrow in -- shrink it. This
+ // is kind of a dirty temp hack thing until we do something smarter, like
+ // fully reimplement CC_ToolButton. Note that it passes us a regular
+ // QStyleOption and not a QStyleOptionToolButton in this case, so try to
+ // save some work before doing the inherits test.
+ if (arrow == Qt::DownArrow && !qstyleoption_cast<const QStyleOptionToolButton*>(option) && widget) {
+ auto tbutton = qobject_cast<const QToolButton*>(widget);
+ if (tbutton && tbutton->popupMode() != QToolButton::InstantPopup && tbutton->defaultAction()) {
+ int dim = static_cast<int>(qMin(rw, rh) * 0.25);
+ aw -= dim;
+ ah -= dim;
+ // We have another hack in PE_IndicatorButtonDropDown where we shift
+ // the edge left or right by 1px to avoid having two borders touching
+ // (we make it overlap instead.) So we'll need to compensate for that
+ // in the arrow's position to avoid it looking off-center.
+ rw += 1;
+ if (option->direction != Qt::RightToLeft) {
+ rx -= 1;
+ }
+ }
+ }
+ aw += (rw - aw) % 2;
+ ah += (rh - ah) % 2;
+ int ax = (rw - aw) / 2 + rx;
+ int ay = (rh - ah) / 2 + ry;
+ Ph::drawArrow(painter, QRect(ax, ay, aw, ah), arrow, swatch);
+ break;
+ }
+ case PE_IndicatorItemViewItemCheck: {
+ QStyleOptionButton button;
+ button.QStyleOption::operator=(*option);
+ button.state &= ~State_MouseOver;
+ proxy()->drawPrimitive(PE_IndicatorCheckBox, &button, painter, widget);
+ return;
+ }
+ case PE_IndicatorHeaderArrow: {
+ auto header = qstyleoption_cast<const QStyleOptionHeader*>(option);
+ if (!header)
+ return;
+ QRect r = header->rect;
+ QPoint offset = QPoint(Phantom::HeaderSortIndicator_HOffset, Phantom::HeaderSortIndicator_VOffset);
+ qreal lightness = Phantom::DeriveColors::hack_isLightPalette(widget->palette()) ? 0.03 : 0.0;
+ if (header->sortIndicator & QStyleOptionHeader::SortUp) {
+ Ph::drawArrow(painter, r.translated(offset), Qt::DownArrow, swatch, true, lightness);
+ } else if (header->sortIndicator & QStyleOptionHeader::SortDown) {
+ Ph::drawArrow(painter, r.translated(offset), Qt::UpArrow, swatch, true, lightness);
+ }
+ break;
+ }
+ case PE_IndicatorButtonDropDown: {
+ // Temp hack until we implement CC_ToolButton: avoid double-stacked border
+ // by clipping off one edge slightly.
+ QStyleOption opt0 = *option;
+ if (opt0.direction != Qt::RightToLeft) {
+ opt0.rect.adjust(-1, 0, 0, 0);
+ } else {
+ opt0.rect.adjust(0, 0, 1, 0);
+ }
+ proxy()->drawPrimitive(PE_PanelButtonTool, &opt0, painter, widget);
+ break;
+ }
+
+ case PE_IndicatorToolBarSeparator: {
+ QRect r = option->rect;
+ if (option->state & State_Horizontal) {
+ if (r.height() >= 10)
+ r.adjust(0, 3, 0, -3);
+ r.setWidth(r.width() / 2 + 1);
+ Ph::fillRectEdges(painter, r, Qt::RightEdge, 1, swatch.color(S_window_divider));
+ } else {
+ // TODO replace with new code
+ const int margin = 6;
+ const int offset = r.height() / 2;
+ painter->setPen(QPen(option->palette.background().color().darker(110)));
+ painter->drawLine(r.topLeft().x() + margin,
+ r.topLeft().y() + offset,
+ r.topRight().x() - margin,
+ r.topRight().y() + offset);
+ painter->setPen(QPen(option->palette.background().color().lighter(110)));
+ painter->drawLine(r.topLeft().x() + margin,
+ r.topLeft().y() + offset + 1,
+ r.topRight().x() - margin,
+ r.topRight().y() + offset + 1);
+ }
+ break;
+ }
+ case PE_PanelButtonTool: {
+ bool isDown = option->state & State_Sunken;
+ bool isOn = option->state & State_On;
+ bool hasFocus = (option->state & State_HasFocus && option->state & State_KeyboardFocusChange);
+ const qreal rounding = Ph::ToolButton_Rounding;
+ Swatchy outline = S_window_outline;
+ Swatchy fill = S_button;
+ Swatchy specular = S_button_specular;
+ if (isDown) {
+ fill = S_button_pressed;
+ specular = S_button_pressed_specular;
+ } else if (isOn) {
+ fill = S_button_on;
+ specular = S_none;
+ }
+ if (hasFocus) {
+ outline = S_highlight_outline;
+ }
+ QRect r = option->rect;
+ Ph::PSave save(painter);
+ Ph::paintBorderedRoundRect(painter, r, rounding, swatch, outline, fill);
+ Ph::paintBorderedRoundRect(painter, r.adjusted(1, 1, -1, -1), rounding, swatch, specular, S_none);
+ break;
+ }
+ case PE_IndicatorDockWidgetResizeHandle: {
+ QStyleOption dockWidgetHandle = *option;
+ bool horizontal = option->state & State_Horizontal;
+ dockWidgetHandle.state =
+ !horizontal ? (dockWidgetHandle.state | State_Horizontal) : (dockWidgetHandle.state & ~State_Horizontal);
+ proxy()->drawControl(CE_Splitter, &dockWidgetHandle, painter, widget);
+ break;
+ }
+ case PE_FrameWindow: {
+ break;
+ }
+ case PE_FrameLineEdit: {
+ QRect r = option->rect;
+ bool hasFocus = option->state & State_HasFocus;
+ bool isEnabled = option->state & State_Enabled;
+ const qreal rounding = Ph::LineEdit_Rounding;
+ auto pen = hasFocus ? S_highlight_outline : S_window_outline;
+ Ph::PSave save(painter);
+ Ph::paintBorderedRoundRect(painter, r, rounding, swatch, pen, S_none);
+ save.restore();
+ if (Ph::OverhangShadows && !hasFocus && isEnabled) {
+ // Imperfect when rounded, may leave a gap on left and right. Going
+ // closer would eat into the outline, though.
+ Ph::fillRectEdges(painter,
+ r.adjusted(qRound(rounding / 2) + 1, 1, -(qRound(rounding / 2) + 1), -1),
+ Qt::TopEdge,
+ 1,
+ swatch.color(S_base_shadow));
+ }
+ break;
+ }
+ case PE_PanelLineEdit: {
+ auto panel = qstyleoption_cast<const QStyleOptionFrame*>(option);
+ if (!panel)
+ break;
+ Ph::PSave save(painter);
+ // We intentionally don't inset the fill rect, even if the frame will paint
+ // over the perimeter, because an inset with rounding enabled may cause
+ // some miscolored separated pixels between the fill and the border, since
+ // we're forced to paint them in two separate draw calls.
+ Ph::paintSolidRoundRect(painter, option->rect, Ph::LineEdit_Rounding, swatch, S_base);
+ save.restore();
+ if (panel->lineWidth > 0)
+ proxy()->drawPrimitive(PE_FrameLineEdit, option, painter, widget);
+ break;
+ }
+ case PE_IndicatorCheckBox: {
+ auto checkbox = qstyleoption_cast<const QStyleOptionButton*>(option);
+ if (!checkbox)
+ break;
+ QRect r = option->rect;
+ bool isHighlighted = option->state & State_HasFocus && option->state & State_KeyboardFocusChange;
+ bool isSelected = option->state & State_Selected;
+ bool isFlat = checkbox->features & QStyleOptionButton::Flat;
+ bool isEnabled = option->state & State_Enabled;
+ bool isPressed = state & State_Sunken;
+ Swatchy outlineColor = isHighlighted ? S_highlight_outline : S_window_outline;
+ Swatchy bgFillColor = isPressed ? S_highlight : S_base;
+ Swatchy fgColor = isFlat ? S_windowText : S_text;
+ if (isPressed && !isFlat) {
+ fgColor = S_highlightedText;
+ }
+ // Bare checkmarks that are selected should draw with the highlighted text
+ // color.
+ if (isSelected && isFlat) {
+ fgColor = S_highlightedText;
+ }
+ if (!isFlat) {
+ QRect fillR = r;
+ Ph::fillRectOutline(painter, fillR, 1, swatch.color(outlineColor));
+ fillR.adjust(1, 1, -1, -1);
+ if (Ph::IndicatorShadows && !isPressed && isEnabled) {
+ Ph::fillRectEdges(painter, fillR, Qt::TopEdge, 1, swatch.color(S_base_shadow));
+ fillR.adjust(0, 1, 0, 0);
+ }
+ painter->fillRect(fillR, swatch.color(bgFillColor));
+ }
+ if (checkbox->state & State_NoChange) {
+ const qreal insetScale = 0.7;
+ qreal rx, ry, rw, rh;
+ QRectF(r.adjusted(1, 1, -1, -1)).getRect(&rx, &ry, &rw, &rh);
+ qreal dimx = rw * insetScale;
+ qreal dimy = rh * insetScale;
+ QRectF r_(rx + (rw - dimx) / 2, ry + (rh - dimy) / 2, dimx, dimy);
+ Ph::drawHyphen(painter, d->checkBox_pen_scratch, r_, swatch, fgColor);
+ } else if (checkbox->state & State_On) {
+ const qreal insetScale = 0.8;
+ qreal rx, ry, rw, rh;
+ QRectF(r.adjusted(1, 1, -1, -1)).getRect(&rx, &ry, &rw, &rh);
+ // kinda wrong, assumes we're already square, but we probably are
+ qreal dimx = rw * insetScale * Ph::CheckMark_WidthOfHeightScale;
+ qreal dimy = rh * insetScale;
+ QRectF r_(rx + (rw - dimx) / 2, ry + (rh - dimy) / 2, dimx, dimy);
+ Ph::drawCheck(painter, d->checkBox_pen_scratch, r_, swatch, fgColor);
+ }
+ break;
+ }
+ case PE_IndicatorRadioButton: {
+ qreal rx, ry, rw, rh;
+ QRectF(option->rect).getRect(&rx, &ry, &rw, &rh);
+ bool isHighlighted = option->state & State_HasFocus && option->state & State_KeyboardFocusChange;
+ bool isSunken = state & State_Sunken;
+ bool isEnabled = state & State_Enabled;
+ Swatchy outlineColor = isHighlighted ? S_highlight_outline : S_window_outline;
+ Swatchy bgFillColor = isSunken ? S_highlight : S_base;
+ QPointF circleCenter(rx + rw / 2.0, ry + rh / 2.0);
+ const qreal lineThickness = 1.0;
+ qreal outlineRadius = (qMin(rw, rh) - lineThickness) / 2.0;
+ qreal fillRadius = outlineRadius - lineThickness / 2.0;
+ Ph::PSave save(painter);
+ painter->setRenderHint(QPainter::Antialiasing);
+ painter->setBrush(swatch.brush(bgFillColor));
+ painter->setPen(swatch.pen(outlineColor));
+ painter->drawEllipse(circleCenter, outlineRadius, outlineRadius);
+ if (Ph::IndicatorShadows && !isSunken && isEnabled) {
+ // Really slow, just a temp demo test
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(swatch.brush(S_base_shadow));
+ QPainterPath path0, path1;
+ path0.addEllipse(circleCenter, fillRadius, fillRadius);
+ path1.addEllipse(circleCenter + QPointF(0, 1.25), fillRadius, fillRadius);
+ QPainterPath path2 = path0 - path1;
+ painter->drawPath(path2);
+ }
+ if (state & State_On) {
+ Swatchy fgColor = isSunken ? S_highlightedText : S_windowText;
+ qreal checkmarkRadius = outlineRadius / 2.32;
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(swatch.brush(fgColor));
+ painter->drawEllipse(circleCenter, checkmarkRadius, checkmarkRadius);
+ }
+ break;
+ }
+ case PE_IndicatorToolBarHandle: {
+ if (!option)
+ break;
+ QRect r = option->rect;
+ if (r.width() < 3 || r.height() < 3)
+ break;
+ int rows = 3;
+ int columns = 2;
+ if (option->state & State_Horizontal) {
+ } else {
+ qSwap(columns, rows);
+ }
+ int dotLen = Ph::dpiScaled(2);
+ QSize occupied(dotLen * (columns * 2 - 1), dotLen * (rows * 2 - 1));
+ QRect rr = QStyle::alignedRect(option->direction, Qt::AlignCenter, QSize(occupied), r);
+ int x = rr.x();
+ int y = rr.y();
+ for (int row = 0; row < rows; ++row) {
+ for (int col = 0; col < columns; ++col) {
+ int x_ = x + col * 2 * dotLen;
+ int y_ = y + row * 2 * dotLen;
+ painter->fillRect(x_, y_, dotLen, dotLen, swatch.color(S_window_divider));
+ }
+ }
+ break;
+ }
+ case PE_FrameDefaultButton:
+ break;
+ case PE_FrameFocusRect: {
+ auto fropt = qstyleoption_cast<const QStyleOptionFocusRect*>(option);
+ if (!fropt)
+ break;
+ //### check for d->alt_down
+ if (!(fropt->state & State_KeyboardFocusChange))
+ return;
+ if (fropt->state & State_Item) {
+ if (auto itemView = qobject_cast<const QAbstractItemView*>(widget)) {
+ // TODO either our grid line hack is interfering, or Qt has a bug, but
+ // in RTL layout the grid borders can leave junk behind in the grid
+ // areas and the right edge of the focus rect may not get painted.
+ // (Sometimes it will, though.) To replicate, set to RTL mode, and move
+ // the current around in a table view without the selection being on
+ // the current.
+ if (option->state & QStyle::State_Selected) {
+ bool showCurrent = true;
+ bool hasTableGrid = false;
+ const auto selectionMode = itemView->selectionMode();
+ if (selectionMode == QAbstractItemView::SingleSelection) {
+ showCurrent = false;
+ } else {
+ // Table views will can have a "current" frame drawn even if the
+ // "current" is within the selected range. Other item views won't,
+ // which means the "current" frame will be invisible if it's on a
+ // selected item. This is a compromise between the broken drawing
+ // behavior of Qt item views of drawing "current" frames when they
+ // don't make sense (like a tree view where you can only select
+ // entire rows, but Qt will the frame rect around whatever column
+ // was last clicked on by the mouse, but using keyboard navigation
+ // has no effect) and not drawing them at all.
+ bool isTableView = false;
+ if (auto tableView = qobject_cast<const QTableView*>(itemView)) {
+ hasTableGrid = tableView->showGrid();
+ isTableView = true;
+ }
+ const auto selectionModel = itemView->selectionModel();
+ if (selectionModel) {
+ const auto selection = selectionModel->selection();
+ if (selection.count() == 1) {
+ const auto& range = selection.at(0);
+ if (isTableView) {
+ // For table views, we don't draw the "current" frame if
+ // there is exactly one cell selected and the "current" is
+ // that cell, or if there is exactly one row or one column
+ // selected with the behavior set to the corresponding
+ // selection, and the "current" is that one row or column.
+ const auto selectionBehavior = itemView->selectionBehavior();
+ if ((range.width() == 1 && range.height() == 1)
+ || (selectionBehavior == QAbstractItemView::SelectRows && range.height() == 1)
+ || (selectionBehavior == QAbstractItemView::SelectColumns
+ && range.width() == 1)) {
+ showCurrent = false;
+ }
+ } else {
+ // For any other type of item view, don't draw the "current"
+ // frame if there is a single contiguous selection, and the
+ // "current" is within that selection. If there's a
+ // discontiguous selection, that means the user is probably
+ // doing something more advanced, and we should just draw the
+ // focus frame, even if Qt might be doing it badly in some
+ // cases.
+ showCurrent = false;
+ }
+ }
+ }
+ }
+ if (showCurrent) {
+ // TODO handle dark-highlight-light-text
+ const QColor& borderColor = swatch.color(S_itemView_multiSelection_currentBorder);
+ const int thickness = hasTableGrid ? 2 : 1;
+ Ph::fillRectOutline(painter, option->rect, thickness, borderColor);
+ }
+ } else {
+ Ph::fillRectOutline(painter, option->rect, 1, swatch.color(S_highlight_outline));
+ }
+ break;
+ }
+ }
+ // It would be nice to also handle QTreeView's allColumnsShowFocus thing in
+ // the above code, in addition to the normal cases for focus rects in item
+ // views. Unfortunately, with allColumnsShowFocus set to true,
+ // QTreeView::drawRow() calls the style to paint with PE_FrameFocusRect for
+ // the row frame with the widget set to nullptr. This makes it basically
+ // impossible to figure out that we need to draw a special frame for it.
+ // So, if any application code is using that mode in a QTreeView, it won't
+ // get special item view frames. Too bad.
+ Ph::PSave save(painter);
+ Ph::paintBorderedRoundRect(
+ painter, option->rect, Ph::FrameFocusRect_Rounding, swatch, S_highlight_outline, S_none);
+ break;
+ }
+ case PE_PanelButtonCommand:
+ case PE_PanelButtonBevel: {
+ bool isDefault = false;
+ bool isFlat = false;
+ bool isDown = option->state & State_Sunken;
+ bool isOn = option->state & State_On;
+ if (auto button = qstyleoption_cast<const QStyleOptionButton*>(option)) {
+ isDefault = (button->features & QStyleOptionButton::DefaultButton) && (button->state & State_Enabled);
+ isFlat = (button->features & QStyleOptionButton::Flat);
+ }
+ if (isFlat && !isDown && !isOn)
+ break;
+ bool isEnabled = option->state & State_Enabled;
+ Q_UNUSED(isEnabled);
+ bool hasFocus = (option->state & State_HasFocus && option->state & State_KeyboardFocusChange);
+ const qreal rounding = Ph::PushButton_Rounding;
+ Swatchy outline = S_window_outline;
+ Swatchy fill = S_button;
+ Swatchy specular = S_button_specular;
+ if (isDown) {
+ fill = S_button_pressed;
+ specular = S_button_pressed_specular;
+ } else if (isOn) {
+ // kinda repurposing this, hmm
+ fill = S_scrollbarGutter;
+ specular = S_button_pressed_specular;
+ }
+ if (hasFocus || isDefault) {
+ outline = S_highlight_outline;
+ }
+ QRect r = option->rect;
+ Ph::PSave save(painter);
+ Ph::paintBorderedRoundRect(painter, r, rounding, swatch, outline, fill);
+ Ph::paintBorderedRoundRect(painter, r.adjusted(1, 1, -1, -1), rounding, swatch, specular, S_none);
+ break;
+ }
+ case PE_FrameTabWidget: {
+ QRect bgRect = option->rect.adjusted(1, 1, -1, -1);
+ painter->fillRect(bgRect, swatch.color(S_tabFrame));
+ auto twf = qstyleoption_cast<const QStyleOptionTabWidgetFrame*>(option);
+ if (!twf)
+ break;
+ Ph::fillRectOutline(painter, option->rect, 1, swatch.color(S_frame_outline));
+ Ph::fillRectOutline(painter, bgRect, 1, swatch.color(S_tabFrame_specular));
+ break;
+ }
+ case PE_FrameStatusBarItem:
+ break;
+ case PE_IndicatorTabClose:
+ case Phantom_PE_IndicatorTabNew: {
+ Swatchy fg = S_windowText;
+ Swatchy bg = S_none;
+ if ((option->state & State_Enabled) && (option->state & State_MouseOver)) {
+ fg = S_highlightedText;
+ bg = option->state & State_Sunken ? S_highlight_outline : S_highlight;
+ }
+ // temp code
+ Ph::PSave save(painter);
+ if (bg) {
+ Ph::paintSolidRoundRect(painter, option->rect, Ph::PushButton_Rounding, swatch, bg);
+ }
+ QPen pen = swatch.pen(fg);
+ pen.setCapStyle(Qt::RoundCap);
+ pen.setWidthF(1.5);
+ painter->setBrush(Qt::NoBrush);
+ painter->setPen(pen);
+ painter->setRenderHint(QPainter::Antialiasing);
+ QRect r = option->rect;
+ // int adj = (int)((qreal)qMin(r.width(), r.height()) * (1.0 / 2.5));
+ int adj = Ph::dpiScaled(5.0);
+ r.adjust(adj, adj, -adj, -adj);
+ qreal x, y, w, h;
+ QRectF(r).getRect(&x, &y, &w, &h);
+ // painter->translate(-0.5, -0.5);
+ switch (static_cast<int>(elem)) {
+ case PE_IndicatorTabClose:
+ painter->drawLine(QPointF(x - 0.5, y - 0.5), QPointF(x + 0.5 + w, y + 0.5 + h));
+ painter->drawLine(QPointF(x - 0.5, y + h + 0.5), QPointF(x + 0.5 + w, y - 0.5));
+ break;
+ case Phantom_PE_IndicatorTabNew:
+ // kinda hacky here on extra len
+ painter->drawLine(QPointF(x + w / 2, y - 1.0), QPointF(x + w / 2, y + h + 1.0));
+ painter->drawLine(QPointF(x - 1.0, y + h / 2), QPointF(x + w + 1.0, y + h / 2));
+ break;
+ }
+ save.restore();
+ // painter->fillRect(option->rect, QColor(255, 0, 0, 30));
+ break;
+ }
+ case PE_PanelMenu: {
+ bool isBelowMenuBar = false;
+ // works but currently unused
+ // QPoint gp = widget->mapToGlobal(widget->rect().topLeft());
+ // gp.setY(gp.y() - 1);
+ // QWidget* bar = qApp->widgetAt(gp);
+ // if (bar && bar->inherits("QMenuBar")) {
+ // isBelowMenuBar = true;
+ // }
+ Ph::fillRectOutline(painter, option->rect, 1, swatch.color(S_window_divider));
+ QRect bgRect = option->rect.adjusted(1, isBelowMenuBar ? 0 : 1, -1, -1);
+ painter->fillRect(bgRect, swatch.color(S_window));
+ break;
+ }
+ case Phantom_PE_ScrollBarSliderVertical: {
+ bool isLeftToRight = option->direction != Qt::RightToLeft;
+ bool isSunken = option->state & State_Sunken;
+ Swatchy thumbFill, thumbSpecular;
+ if (isSunken) {
+ thumbFill = S_button_pressed;
+ thumbSpecular = S_button_pressed_specular;
+ } else {
+ thumbFill = S_scrollbarSlider;
+ thumbSpecular = S_button_specular;
+ }
+ Qt::Edges edges;
+ QRect edgeRect = option->rect;
+ QRect mainRect = option->rect;
+ edgeRect.adjust(0, -1, 0, 1);
+ if (isLeftToRight) {
+ edges = Qt::LeftEdge | Qt::TopEdge | Qt::BottomEdge;
+ mainRect.setX(mainRect.x() + 1);
+ } else {
+ edges = Qt::TopEdge | Qt::BottomEdge | Qt::RightEdge;
+ mainRect.setWidth(mainRect.width() - 1);
+ }
+ Ph::fillRectEdges(painter, edgeRect, edges, 1, swatch.color(S_window_outline));
+ painter->fillRect(mainRect, swatch.color(thumbFill));
+ Ph::fillRectOutline(painter, mainRect, 1, swatch.color(thumbSpecular));
+ break;
+ }
+ case Phantom_PE_WindowFrameColor: {
+ painter->fillRect(option->rect, swatch.color(S_window_outline));
+ break;
+ }
+ default:
+ QCommonStyle::drawPrimitive(elem, option, painter, widget);
+ break;
+ }
+}
+
+void BaseStyle::drawControl(ControlElement element,
+ const QStyleOption* option,
+ QPainter* painter,
+ const QWidget* widget) const
+{
+#ifdef BUILD_WITH_EASY_PROFILER
+ EASY_BLOCK("drawControl");
+ const char* elemCString = QMetaEnum::fromType<QStyle::ControlElement>().valueToKey(element);
+ EASY_TEXT("Element", elemCString);
+#endif
+ using Swatchy = Phantom::Swatchy;
+ using namespace Phantom::SwatchColors;
+ namespace Ph = Phantom;
+ auto ph_swatchPtr = Ph::getCachedSwatchOfQPalette(&d->swatchCache, &d->headSwatchFastKey, option->palette);
+ const Ph::PhSwatch& swatch = *ph_swatchPtr.data();
+
+ switch (element) {
+ case CE_CheckBox: {
+ QCommonStyle::drawControl(element, option, painter, widget);
+ // painter->fillRect(option->rect, QColor(255, 0, 0, 90));
+ break;
+ }
+ case CE_ComboBoxLabel: {
+ auto cb = qstyleoption_cast<const QStyleOptionComboBox*>(option);
+ if (!cb)
+ break;
+ QRect editRect = proxy()->subControlRect(CC_ComboBox, cb, SC_ComboBoxEditField, widget);
+ painter->save();
+ painter->setClipRect(editRect);
+ if (!cb->currentIcon.isNull()) {
+ QIcon::Mode mode = cb->state & State_Enabled ? QIcon::Normal : QIcon::Disabled;
+ QPixmap pixmap = cb->currentIcon.pixmap(cb->iconSize, mode);
+ QRect iconRect(editRect);
+ iconRect.setWidth(cb->iconSize.width() + 4);
+ iconRect = alignedRect(cb->direction, Qt::AlignLeft | Qt::AlignVCenter, iconRect.size(), editRect);
+ if (cb->editable)
+ painter->fillRect(iconRect, cb->palette.brush(QPalette::Base));
+ proxy()->drawItemPixmap(painter, iconRect, Qt::AlignCenter, pixmap);
+
+ if (cb->direction == Qt::RightToLeft)
+ editRect.translate(-4 - cb->iconSize.width(), 0);
+ else
+ editRect.translate(cb->iconSize.width() + 4, 0);
+ }
+ if (!cb->currentText.isEmpty() && !cb->editable) {
+ proxy()->drawItemText(painter,
+ editRect.adjusted(1, 0, -1, 0),
+ visualAlignment(cb->direction, Qt::AlignLeft | Qt::AlignVCenter),
+ cb->palette,
+ cb->state & State_Enabled,
+ cb->currentText,
+ cb->editable ? QPalette::Text : QPalette::ButtonText);
+ }
+ painter->restore();
+ break;
+ }
+ case CE_Splitter: {
+ QRect r = option->rect;
+ // We don't have anything useful to draw if it's too thin
+ if (r.width() < 5 || r.height() < 5)
+ break;
+ int length = Ph::dpiScaled(Ph::SplitterMaxLength);
+ int thickness = Ph::dpiScaled(1);
+ QSize size;
+ if (option->state & State_Horizontal) {
+ if (r.height() < length)
+ length = r.height();
+ size = QSize(thickness, length);
+ } else {
+ if (r.width() < length)
+ length = r.width();
+ size = QSize(length, thickness);
+ }
+ QRect filledRect = QStyle::alignedRect(option->direction, Qt::AlignCenter, size, r);
+ painter->fillRect(filledRect, swatch.color(S_button_specular));
+ Ph::fillRectOutline(painter, filledRect.adjusted(-1, 0, 1, 0), 1, swatch.color(S_window_divider));
+ break;
+ }
+ // TODO update this for phantom
+ case CE_RubberBand: {
+ if (!qstyleoption_cast<const QStyleOptionRubberBand*>(option))
+ break;
+ QColor highlight = option->palette.color(QPalette::Active, QPalette::Highlight);
+ painter->save();
+ QColor penColor = highlight.darker(120);
+ penColor.setAlpha(180);
+ painter->setPen(penColor);
+ QColor dimHighlight(qMin(highlight.red() / 2 + 110, 255),
+ qMin(highlight.green() / 2 + 110, 255),
+ qMin(highlight.blue() / 2 + 110, 255));
+ dimHighlight.setAlpha(widget && widget->isTopLevel() ? 255 : 80);
+ painter->setRenderHint(QPainter::Antialiasing, true);
+ painter->translate(0.5, 0.5);
+ painter->setBrush(dimHighlight);
+ painter->drawRoundedRect(option->rect.adjusted(0, 0, -1, -1), 1, 1);
+ QColor innerLine = Qt::white;
+ innerLine.setAlpha(40);
+ painter->setPen(innerLine);
+ painter->drawRoundedRect(option->rect.adjusted(1, 1, -2, -2), 1, 1);
+ painter->restore();
+ break;
+ }
+ case CE_SizeGrip: {
+ Qt::LayoutDirection dir = option->direction;
+ QRect rect = option->rect;
+ int rcx = rect.center().x();
+ int rcy = rect.center().y();
+ // draw grips
+ for (int i = -6; i < 12; i += 3) {
+ for (int j = -6; j < 12; j += 3) {
+ if ((dir == Qt::LeftToRight && i > -j) || (dir == Qt::RightToLeft && j > i)) {
+ painter->fillRect(rcx + i, rcy + j, 2, 2, swatch.color(S_window_lighter));
+ painter->fillRect(rcx + i, rcy + j, 1, 1, swatch.color(S_window_darker));
+ }
+ }
+ }
+ break;
+ }
+ case CE_ToolBar: {
+ auto toolBar = qstyleoption_cast<const QStyleOptionToolBar*>(option);
+ if (!toolBar)
+ break;
+ painter->fillRect(option->rect, option->palette.window().color());
+ bool isFloating = false;
+ if (auto tb = qobject_cast<const QToolBar*>(widget)) {
+ isFloating = tb->isFloating();
+ }
+ if (isFloating) {
+ Ph::fillRectOutline(painter, option->rect, 1, swatch.color(S_window_outline));
+ }
+ break;
+ }
+ case CE_DockWidgetTitle: {
+ auto dwOpt = qstyleoption_cast<const QStyleOptionDockWidget*>(option);
+ if (!dwOpt)
+ break;
+ painter->save();
+ bool verticalTitleBar = dwOpt->verticalTitleBar;
+
+ QRect titleRect = subElementRect(SE_DockWidgetTitleBarText, option, widget);
+ if (verticalTitleBar) {
+ QRect r = dwOpt->rect;
+ QRect rtrans = {r.x(), r.y(), r.height(), r.width()};
+ titleRect = QRect(rtrans.left() + r.bottom() - titleRect.bottom(),
+ rtrans.top() + titleRect.left() - r.left(),
+ titleRect.height(),
+ titleRect.width());
+ painter->translate(rtrans.left(), rtrans.top() + rtrans.width());
+ painter->rotate(-90);
+ painter->translate(-rtrans.left(), -rtrans.top());
+ }
+ if (!dwOpt->title.isEmpty()) {
+ QString titleText = painter->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight, titleRect.width());
+ proxy()->drawItemText(painter,
+ titleRect,
+ Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic,
+ dwOpt->palette,
+ dwOpt->state & State_Enabled,
+ titleText,
+ QPalette::WindowText);
+ }
+ painter->restore();
+ break;
+ }
+ case CE_HeaderSection: {
+ auto header = qstyleoption_cast<const QStyleOptionHeader*>(option);
+ if (!header)
+ break;
+ QRect rect = header->rect;
+ Qt::Orientation orientation = header->orientation;
+ QStyleOptionHeader::SectionPosition position = header->position;
+ // See the "Table header layout reference" comment block at the bottom of
+ // this file for more information to help understand what's going on.
+ bool isLeftToRight = header->direction != Qt::RightToLeft;
+ bool isHorizontal = orientation == Qt::Horizontal;
+ bool isVertical = orientation == Qt::Vertical;
+ bool isEnd = position == QStyleOptionHeader::End;
+ bool isBegin = position == QStyleOptionHeader::Beginning;
+ bool isOnlyOne = position == QStyleOptionHeader::OnlyOneSection;
+ Qt::Edges edges;
+ bool spansToEnd = false;
+ bool isSpecialCorner = false;
+ if ((isHorizontal && isLeftToRight && isEnd) || (isHorizontal && !isLeftToRight && isBegin)
+ || (isVertical && isEnd) || isOnlyOne) {
+ auto hv = qobject_cast<const QHeaderView*>(widget);
+ if (hv) {
+ spansToEnd = hv->stretchLastSection();
+ // In the case where the header item is not stretched to the end, but
+ // could plausibly be in a position where it could happen to be exactly
+ // the right width or height to be appear to be stretched to the end,
+ // we'll check to see if it actually does exactly meet the right (or
+ // bottom in vertical, or left in RTL) edge, and omit drawing the edge
+ // if that's the case. This can commonly happen if you have a tree or
+ // list view and don't set it to stretch, but the widget is still sized
+ // exactly to hold the one column. (It could also happen if there's
+ // user code running to manually stretch the last section as
+ // necessary.)
+ if (!spansToEnd) {
+ QRect viewBound = hv->contentsRect();
+ if (isHorizontal) {
+ if (isLeftToRight) {
+ spansToEnd = rect.right() == viewBound.right();
+ } else {
+ spansToEnd = rect.left() == viewBound.left();
+ }
+ } else if (isVertical) {
+ spansToEnd = rect.bottom() == viewBound.bottom();
+ }
+ }
+ } else {
+ // We only need to do this check in RTL, because the corner button in
+ // RTL *doesn't* need hacks applied. In LTR, we can just treat the
+ // corner button like anything else on the horizontal header bar, and
+ // can skip doing this inherits check.
+ if (isOnlyOne && !isLeftToRight && widget && widget->inherits("QTableCornerButton")) {
+ isSpecialCorner = true;
+ }
+ }
+ }
+
+ if (isSpecialCorner) {
+ // In RTL layout, the corner button in a table view doesn't have any
+ // offset problems. This branch we're on is only taken if we're in RTL
+ // layout and this is the corner button being drawn.
+ edges |= Qt::BottomEdge;
+ if (isLeftToRight)
+ edges |= Qt::RightEdge;
+ else
+ edges |= Qt::LeftEdge;
+ } else if (isHorizontal) {
+ // This branch is taken for horizontal headers in either layout direction
+ // or for the corner button in LTR.
+ edges |= Qt::BottomEdge;
+ if (isLeftToRight) {
+ // In LTR, this code path may be for both the corner button *and* the
+ // actual header item. It doesn't matter in this case, and we were able
+ // to avoid doing an extra inherits call earlier.
+ if (!spansToEnd) {
+ edges |= Qt::RightEdge;
+ }
+ } else {
+ // Note: in right-to-left layouts for horizontal headers, the header
+ // view will unfortunately be shifted to the right by 1 pixel, due to
+ // what appears to be a Qt bug. This causes the vertical lines we draw
+ // in the header view to misalign with the grid, and causes the
+ // rightmost section to have its right edge clipped off. Therefore,
+ // we'll draw the separator on the on the right edge instead of the
+ // left edge. (We would have expected to draw it on the left edge in
+ // RTL layout.) This makes it line up with the grid again, except for
+ // the last section. right by 1 pixel.
+ //
+ // In RTL, the "Begin" position is on the left side for some reason
+ // (the same as LTR.) So "End" is always on the right. Ok, whatever.
+ // See the table at the bottom of this file if you're confused.
+ if (!isOnlyOne && !isEnd) {
+ edges |= Qt::RightEdge;
+ }
+ // The leftmost section in RTL has to draw on both its right and left
+ // edges, instead of just 1 edge like every other configuration. The
+ // left edge will be offset by 1 pixel from the grid, but it's the best
+ // we can do.
+ if (isBegin && !spansToEnd) {
+ edges |= Qt::LeftEdge;
+ }
+ }
+ } else if (isVertical) {
+ if (isLeftToRight) {
+ edges |= Qt::RightEdge;
+ } else {
+ edges |= Qt::LeftEdge;
+ }
+ if (!spansToEnd) {
+ edges |= Qt::BottomEdge;
+ }
+ }
+ QRect bgRect = Ph::expandRect(rect, edges, -1);
+ painter->fillRect(bgRect, swatch.color(S_window));
+ Ph::fillRectEdges(painter, rect, edges, 1, swatch.color(S_frame_outline));
+ break;
+ }
+ case CE_HeaderLabel: {
+ auto header = qstyleoption_cast<const QStyleOptionHeader*>(option);
+ if (!header)
+ break;
+ QRect rect = header->rect;
+ if (!header->icon.isNull()) {
+ int iconExtent = qMin(qMin(rect.height(), rect.width()), option->fontMetrics.height());
+ auto window = widget ? widget->window()->windowHandle() : nullptr;
+ QPixmap pixmap = header->icon.pixmap(window,
+ QSize(iconExtent, iconExtent),
+ (header->state & State_Enabled) ? QIcon::Normal : QIcon::Disabled);
+ int pixw = static_cast<int>(pixmap.width() / pixmap.devicePixelRatio());
+ QRect aligned = alignedRect(
+ header->direction, QFlag(header->iconAlignment), pixmap.size() / pixmap.devicePixelRatio(), rect);
+ QRect inter = aligned.intersected(rect);
+ painter->drawPixmap(inter.x(),
+ inter.y(),
+ pixmap,
+ inter.x() - aligned.x(),
+ inter.y() - aligned.y(),
+ static_cast<int>(aligned.width() * pixmap.devicePixelRatio()),
+ static_cast<int>(pixmap.height() * pixmap.devicePixelRatio()));
+ int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, option, widget);
+ if (header->direction == Qt::LeftToRight)
+ rect.setLeft(rect.left() + pixw + margin);
+ else
+ rect.setRight(rect.right() - pixw - margin);
+ }
+ proxy()->drawItemText(painter,
+ rect,
+ header->textAlignment,
+ header->palette,
+ (header->state & State_Enabled),
+ header->text,
+ QPalette::ButtonText);
+
+ // But we still need some kind of indicator, so draw a line
+ bool drawHighlightLine = option->state & State_On;
+ // Special logic: if the selection mode of the item view is to select every
+ // row or every column, there's no real need to draw special "this
+ // row/column is selected" highlight indicators in the header view. The
+ // application programmer can also disable this explicitly on the header
+ // view, but it's nice to have it done automatically, I think.
+ if (drawHighlightLine) {
+ const QAbstractItemView* itemview = nullptr;
+ // Header view itself is an item view, and we don't care about its
+ // selection behavior -- we care about the actual item view. So try to
+ // get the widget as the header first, then find the item view from
+ // there.
+ auto headerview = qobject_cast<const QHeaderView*>(widget);
+ if (headerview) {
+ // Also don't care about highlights if there's only one row or column.
+ drawHighlightLine = headerview->count() > 1;
+ itemview = qobject_cast<const QAbstractItemView*>(headerview->parentWidget());
+ }
+ if (drawHighlightLine && itemview) {
+ auto selBehavior = itemview->selectionBehavior();
+ if (selBehavior == QAbstractItemView::SelectRows && header->orientation == Qt::Horizontal)
+ drawHighlightLine = false;
+ else if (selBehavior == QAbstractItemView::SelectColumns && header->orientation == Qt::Vertical)
+ drawHighlightLine = false;
+ }
+ }
+
+ if (drawHighlightLine) {
+ QRect r = option->rect;
+ Qt::Edge edge;
+ if (header->orientation == Qt::Horizontal) {
+ edge = Qt::BottomEdge;
+ r.adjust(-2, 1, 1, 1);
+ } else {
+ bool isLeftToRight = option->direction != Qt::RightToLeft;
+ if (isLeftToRight) {
+ edge = Qt::RightEdge;
+ r.adjust(1, -2, 1, 1);
+ } else {
+ edge = Qt::LeftEdge;
+ r.adjust(-1, -2, -1, 1);
+ }
+ }
+ Ph::fillRectEdges(painter, r, edge, 1, swatch.color(S_itemView_headerOnLine));
+ }
+ break;
+ }
+ case CE_ProgressBarGroove: {
+ const qreal rounding = Ph::ProgressBar_Rounding;
+ QRect rect = option->rect;
+ Ph::PSave save(painter);
+ Ph::paintBorderedRoundRect(painter, rect, rounding, swatch, S_window_outline, S_base);
+ save.restore();
+ if (Ph::OverhangShadows && option->state & State_Enabled) {
+ // Inner shadow
+ const QColor& shadowColor = swatch.color(S_base_shadow);
+ // We can either have the shadow cut into the rounded corners, or leave a
+ // 1px gap, due to AA.
+ Ph::fillRectEdges(painter,
+ rect.adjusted(qRound(rounding / 2) + 1, 1, -(qRound(rounding / 2) + 1), -1),
+ Qt::TopEdge,
+ 1,
+ shadowColor);
+ }
+ break;
+ }
+ case CE_ProgressBarContents: {
+ auto bar = qstyleoption_cast<const QStyleOptionProgressBar*>(option);
+ if (!bar)
+ break;
+ const qreal rounding = Ph::ProgressBar_Rounding;
+ QRect filled, nonFilled;
+ bool isIndeterminate = false;
+ Ph::progressBarFillRects(bar, filled, nonFilled, isIndeterminate);
+ if (isIndeterminate || bar->progress > bar->minimum) {
+ Ph::PSave save(painter);
+ Ph::paintBorderedRoundRect(painter, filled, rounding, swatch, S_progressBar_outline, S_progressBar);
+ Ph::paintBorderedRoundRect(
+ painter, filled.adjusted(1, 1, -1, -1), rounding, swatch, S_progressBar_specular, S_none);
+ if (isIndeterminate) {
+ // TODO paint indeterminate indicator
+ }
+ }
+ break;
+ }
+ case CE_ProgressBarLabel: {
+ auto bar = qstyleoption_cast<const QStyleOptionProgressBar*>(option);
+ if (!bar)
+ break;
+ if (bar->text.isEmpty())
+ break;
+ QRect r = bar->rect.adjusted(2, 2, -2, -2);
+ if (r.isEmpty() || !r.isValid())
+ break;
+ QSize textSize = option->fontMetrics.size(Qt::TextBypassShaping, bar->text);
+ QRect textRect = QStyle::alignedRect(option->direction, Qt::AlignCenter, textSize, option->rect);
+ textRect &= r;
+ if (textRect.isEmpty())
+ break;
+ QRect filled, nonFilled;
+ bool isIndeterminate = false;
+ Ph::progressBarFillRects(bar, filled, nonFilled, isIndeterminate);
+ QRect textNonFilledR = textRect & nonFilled;
+ QRect textFilledR = textRect & filled;
+ bool needsNonFilled = !textNonFilledR.isEmpty();
+ bool needsFilled = !textFilledR.isEmpty();
+ bool needsMasking = needsNonFilled && needsFilled;
+ Ph::PSave save(painter);
+ if (needsNonFilled) {
+ if (needsMasking) {
+ painter->save();
+ painter->setClipRect(textNonFilledR);
+ }
+ painter->setPen(swatch.pen(S_text));
+ painter->setBrush(Qt::NoBrush);
+ painter->drawText(textRect, bar->text, Qt::AlignHCenter | Qt::AlignVCenter);
+ if (needsMasking) {
+ painter->restore();
+ }
+ }
+ if (needsFilled) {
+ if (needsMasking) {
+ painter->save();
+ painter->setClipRect(textFilledR);
+ }
+ painter->setPen(swatch.pen(S_highlightedText));
+ painter->setBrush(Qt::NoBrush);
+ painter->drawText(textRect, bar->text, Qt::AlignHCenter | Qt::AlignVCenter);
+ if (needsMasking) {
+ painter->restore();
+ }
+ }
+ break;
+ }
+ case CE_MenuBarItem: {
+ auto mbi = qstyleoption_cast<const QStyleOptionMenuItem*>(option);
+ if (!mbi)
+ break;
+ const QRect r = option->rect;
+ QRect textRect = r;
+ textRect.setY(textRect.y() + (r.height() - option->fontMetrics.height()) / 2);
+ int alignment = Qt::AlignHCenter | Qt::AlignTop | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
+ if (!proxy()->styleHint(SH_UnderlineShortcut, mbi, widget))
+ alignment |= Qt::TextHideMnemonic;
+ const auto itemState = mbi->state;
+ bool maybeHasAltKeyNavFocus = itemState & State_Selected && itemState & State_HasFocus;
+ bool isSelected = itemState & State_Selected || itemState & State_Sunken;
+ if (!isSelected && maybeHasAltKeyNavFocus && widget) {
+ isSelected = widget->hasFocus();
+ }
+ Swatchy fill = isSelected ? S_highlight : S_window;
+ painter->fillRect(r, swatch.color(fill));
+ QPalette::ColorRole textRole = isSelected ? QPalette::HighlightedText : QPalette::Text;
+ proxy()->drawItemText(
+ painter, textRect, alignment, mbi->palette, mbi->state & State_Enabled, mbi->text, textRole);
+ if (Phantom::MenuBarDrawBorder && !isSelected) {
+ Ph::fillRectEdges(painter, r, Qt::BottomEdge, 1, swatch.color(S_window_divider));
+ }
+ break;
+ }
+
+ case CE_MenuItem: {
+ auto menuItem = qstyleoption_cast<const QStyleOptionMenuItem*>(option);
+ if (!menuItem)
+ break;
+ const auto metrics = Ph::MenuItemMetrics::ofFontHeight(option->fontMetrics.height());
+ // Draws one item in a popup menu.
+ if (menuItem->menuItemType == QStyleOptionMenuItem::Separator) {
+ // Phantom ignores text and icons in menu separators, because
+ // 1) The text and icons for separators don't render on Mac native menus
+ // 2) There doesn't seem to be a way to account for the width of the text
+ // properly (Fusion will often draw separator text clipped off)
+ // 3) Setting text on separators also seems to mess up the metrics for
+ // menu items on Mac native menus
+ QRect r = option->rect;
+ r.setHeight(r.height() / 2 + 1);
+ Ph::fillRectEdges(painter, r, Qt::BottomEdge, 1, swatch.color(S_window_divider));
+ break;
+ }
+ const QRect itemRect = option->rect;
+ painter->save();
+ bool isSelected = menuItem->state & State_Selected && menuItem->state & State_Enabled;
+ bool isCheckable = menuItem->checkType != QStyleOptionMenuItem::NotCheckable;
+ bool isChecked = menuItem->checked;
+ bool isSunken = menuItem->state & State_Sunken;
+ bool isEnabled = menuItem->state & State_Enabled;
+ bool hasSubMenu = menuItem->menuItemType == QStyleOptionMenuItem::SubMenu;
+ if (isSelected) {
+ Swatchy fillColor = isSunken ? S_highlight_outline : S_highlight;
+ painter->fillRect(option->rect, swatch.color(fillColor));
+ }
+
+ if (isCheckable) {
+ // Note: check rect might be misaligned vertically if it's a menu from a
+ // combo box. Probably a bug in Qt code?
+ QRect checkRect = Ph::menuItemCheckRect(metrics, option->direction, itemRect, hasSubMenu);
+ Swatchy signColor = !isEnabled ? S_windowText : isSelected ? S_highlightedText : S_windowText;
+ if (menuItem->checkType & QStyleOptionMenuItem::Exclusive) {
+ // Radio button
+ if (isChecked) {
+ painter->setRenderHint(QPainter::Antialiasing);
+ painter->setPen(Qt::NoPen);
+ QPalette::ColorRole textRole =
+ !isEnabled ? QPalette::Text : isSelected ? QPalette::HighlightedText : QPalette::ButtonText;
+ painter->setBrush(option->palette.brush(option->palette.currentColorGroup(), textRole));
+ qreal rx, ry, rw, rh;
+ QRectF(checkRect).getRect(&rx, &ry, &rw, &rh);
+ qreal dim = qMin(checkRect.width(), checkRect.height()) * 0.75;
+ QRectF rf(rx + rw / dim, ry + rh / dim, dim, dim);
+ painter->drawEllipse(rf);
+ }
+ } else {
+ // If we want mouse-down to immediately show the item as
+ // checked/unchecked (kinda bad if the user is click-holding on the
+ // menu instead of click-clicking.)
+ //
+ // if ((isChecked && !isSunken) || (!isChecked && isSunken)) {
+ if (isChecked) {
+ Ph::drawCheck(painter, d->checkBox_pen_scratch, checkRect, swatch, signColor);
+ }
+ }
+ }
+
+ const bool hasIcon = !menuItem->icon.isNull();
+
+ if (hasIcon) {
+ QRect iconRect = Ph::menuItemIconRect(metrics, option->direction, itemRect, hasSubMenu);
+ QIcon::Mode mode = isEnabled ? QIcon::Normal : QIcon::Disabled;
+ if (isSelected && isEnabled)
+ mode = QIcon::Selected;
+ QIcon::State state = isChecked ? QIcon::On : QIcon::Off;
+
+ // TODO hmm, we might be ending up with blurry icons at size 15 instead
+ // of 16 for example on Windows.
+ //
+ // int smallIconSize =
+ // proxy()->pixelMetric(PM_SmallIconSize, option, widget);
+ // QSize iconSize(smallIconSize, smallIconSize);
+ int iconExtent = qMin(iconRect.width(), iconRect.height());
+ QSize iconSize(iconExtent, iconExtent);
+ if (auto combo = qobject_cast<const QComboBox*>(widget)) {
+ iconSize = combo->iconSize();
+ }
+ QWindow* window = widget ? widget->windowHandle() : nullptr;
+ QPixmap pixmap = menuItem->icon.pixmap(window, iconSize, mode, state);
+ const int pixw = static_cast<int>(pixmap.width() / pixmap.devicePixelRatio());
+ const int pixh = static_cast<int>(pixmap.height() / pixmap.devicePixelRatio());
+ QRect pixmapRect = QStyle::alignedRect(option->direction, Qt::AlignCenter, QSize(pixw, pixh), iconRect);
+ painter->drawPixmap(pixmapRect.topLeft(), pixmap);
+ }
+
+ // Draw main text and mnemonic text
+ QStringRef s(&menuItem->text);
+ if (!s.isEmpty()) {
+ QRect textRect =
+ Ph::menuItemTextRect(metrics, option->direction, itemRect, hasSubMenu, hasIcon, menuItem->tabWidth);
+ int t = s.indexOf(QLatin1Char('\t'));
+ int text_flags =
+ Qt::AlignLeft | Qt::AlignTop | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
+ if (!styleHint(SH_UnderlineShortcut, menuItem, widget))
+ text_flags |= Qt::TextHideMnemonic;
+#if 0
+ painter->save();
+#endif
+ painter->setPen(swatch.pen(isSelected ? S_highlightedText : S_text));
+
+ // Comment from original Qt code which did some dance with the font:
+ //
+ // font may not have any "hard" flags set. We override the point size so
+ // that when it is resolved against the device, this font will win. This
+ // is mainly to handle cases where someone sets the font on the window
+ // and then the combo inherits it and passes it onward. At that point the
+ // resolve mask is very, very weak. This makes it stonger.
+#if 0
+ QFont font = menuItem->font;
+ font.setPointSizeF(QFontInfo(menuItem->font).pointSizeF());
+ painter->setFont(font);
+#endif
+
+ // My comment:
+ //
+ // What actually looks like is happening is that the qplatformtheme may
+ // have set a per-class font for menus. The QComboMenuDelegate sets the
+ // combo box's own font on the QStyleOptionMenuItem when passing it in
+ // here and when calling sizeFromContents with CT_MenuItem, but the
+ // QPainter we're called with hasn't had its font set to it -- it's still
+ // set to the QMenu/QMenuItem app fonts hash font. So if it's a menu
+ // coming from a combo box, let's just go ahead and set the font for it
+ // if it doesn't match, since that's probably what it wanted to do. I
+ // think. And as described above, we have to do the weird dance with the
+ // resolve mask... which is some internal Qt detail that we aren't
+ // supposed to have to deal with, but here we are.
+ //
+ // Ok, there's another problem, and QFusionStyle also suffers from it: in
+ // high DPI, setting the pointSizeF and setting the font again won't
+ // necessarily give us the right font (at least in Windows.) The font
+ // might have too thin of a weight, and probably other problems. So just
+ // forget about it: we'll have Phantom return 0 for the style hint that
+ // the combo box uses to determine if it should use a QMenu popup instead
+ // of a regular dropdown menu thing. The popup menu might actually be
+ // better for usability in some cases, and it's how combos work on Mac
+ // and BeOS, but it won't work anyway for editable combo boxes in Qt, and
+ // the font issues just make it not worth it. So we'll have a dropdown
+ // guy like a traditional Windows thing.
+ //
+ // If you want to try it out again, go to SH_ComboBox_Popup and have it
+ // return 1.
+ //
+ // Alternatively, we could instead have the CT_MenuItem handling code try
+ // to be aggressively clever and use the qt app font hash to look up the
+ // expected font for a QMenu and use that for calculating its metrics.
+ // Unfortunately, that probably won't work so great if the combo/menu
+ // actually wants to use custom fonts in its listing, since we'd be
+ // ignoring it. That's how UseQMenuForComboBoxPopup currently works,
+ // though it tests for Qt::WA_SetFont as an attempt at recognizing when
+ // it shouldn't use the qt font hash for QMenu.
+#if 0
+ if (qobject_cast<const QComboBox*>(widget)) {
+ QFont font = menuItem->font;
+ font.setPointSizeF(QFontInfo(menuItem->font).pointSizeF());
+ painter->setFont(font);
+ }
+#endif
+
+ // Draw mnemonic text
+ if (t >= 0) {
+ QRect mnemonicR =
+ Ph::menuItemMnemonicRect(metrics, option->direction, itemRect, hasSubMenu, menuItem->tabWidth);
+ const QStringRef textToDrawRef = s.mid(t + 1);
+ const QString unsafeTextToDraw = QString::fromRawData(textToDrawRef.constData(), textToDrawRef.size());
+ painter->drawText(mnemonicR, text_flags, unsafeTextToDraw);
+ s = s.left(t);
+ }
+ const QStringRef textToDrawRef = s.left(t);
+ const QString unsafeTextToDraw = QString::fromRawData(textToDrawRef.constData(), textToDrawRef.size());
+ painter->drawText(textRect, text_flags, unsafeTextToDraw);
+
+#if 0
+ painter->restore();
+#endif
+ }
+
+ // SubMenu Arrow
+ if (hasSubMenu) {
+ Qt::ArrowType arrow = option->direction == Qt::RightToLeft ? Qt::LeftArrow : Qt::RightArrow;
+ QRect arrowRect = Ph::menuItemArrowRect(metrics, option->direction, itemRect);
+ Swatchy arrowColor = isSelected ? S_highlightedText : S_indicator_current;
+ Ph::drawArrow(painter, arrowRect, arrow, swatch.brush(arrowColor));
+ }
+ painter->restore();
+ break;
+ }
+ case CE_MenuHMargin:
+ case CE_MenuVMargin:
+ case CE_MenuEmptyArea:
+ break;
+ case CE_PushButton: {
+ auto btn = qstyleoption_cast<const QStyleOptionButton*>(option);
+ if (!btn)
+ break;
+ proxy()->drawControl(CE_PushButtonBevel, btn, painter, widget);
+ QStyleOptionButton subopt = *btn;
+ subopt.rect = subElementRect(SE_PushButtonContents, btn, widget);
+ proxy()->drawControl(CE_PushButtonLabel, &subopt, painter, widget);
+ break;
+ }
+ case CE_PushButtonLabel: {
+ auto button = qstyleoption_cast<const QStyleOptionButton*>(option);
+ if (!button)
+ break;
+ // This code is very similar to QCommonStyle's implementation, but doesn't
+ // set the icon mode to active when focused.
+ QRect textRect = button->rect;
+ int tf = Qt::AlignVCenter | Qt::TextShowMnemonic;
+ if (!proxy()->styleHint(SH_UnderlineShortcut, button, widget))
+ tf |= Qt::TextHideMnemonic;
+ if (!button->icon.isNull()) {
+ // Center both icon and text
+ QRect iconRect;
+ QIcon::Mode mode = button->state & State_Enabled ? QIcon::Normal : QIcon::Disabled;
+ QIcon::State state = button->state & State_On ? QIcon::On : QIcon::Off;
+ auto window = widget ? widget->window()->windowHandle() : nullptr;
+ QPixmap pixmap = button->icon.pixmap(window, button->iconSize, mode, state);
+ int pixmapWidth = static_cast<int>(pixmap.width() / pixmap.devicePixelRatio());
+ int pixmapHeight = static_cast<int>(pixmap.height() / pixmap.devicePixelRatio());
+ int labelWidth = pixmapWidth;
+ int labelHeight = pixmapHeight;
+ // 4 is hardcoded in QPushButton::sizeHint()
+ int iconSpacing = 4;
+ int textWidth = button->fontMetrics.boundingRect(option->rect, tf, button->text).width();
+ if (!button->text.isEmpty())
+ labelWidth += (textWidth + iconSpacing);
+ iconRect = QRect(textRect.x() + (textRect.width() - labelWidth) / 2,
+ textRect.y() + (textRect.height() - labelHeight) / 2,
+ pixmapWidth,
+ pixmapHeight);
+ iconRect = visualRect(button->direction, textRect, iconRect);
+ tf |= Qt::AlignLeft; // left align, we adjust the text-rect instead
+ if (button->direction == Qt::RightToLeft)
+ textRect.setRight(iconRect.left() - iconSpacing);
+ else
+ textRect.setLeft(iconRect.left() + iconRect.width() + iconSpacing);
+ if (button->state & (State_On | State_Sunken))
+ iconRect.translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, option, widget),
+ proxy()->pixelMetric(PM_ButtonShiftVertical, option, widget));
+ painter->drawPixmap(iconRect, pixmap);
+ } else {
+ tf |= Qt::AlignHCenter;
+ }
+ if (button->state & (State_On | State_Sunken))
+ textRect.translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, option, widget),
+ proxy()->pixelMetric(PM_ButtonShiftVertical, option, widget));
+ if (button->features & QStyleOptionButton::HasMenu) {
+ int indicatorSize = proxy()->pixelMetric(PM_MenuButtonIndicator, button, widget);
+ if (button->direction == Qt::LeftToRight)
+ textRect = textRect.adjusted(0, 0, -indicatorSize, 0);
+ else
+ textRect = textRect.adjusted(indicatorSize, 0, 0, 0);
+ }
+ proxy()->drawItemText(painter,
+ textRect,
+ tf,
+ button->palette,
+ (button->state & State_Enabled),
+ button->text,
+ QPalette::ButtonText);
+ break;
+ }
+ case CE_MenuBarEmptyArea: {
+ QRect rect = option->rect;
+ if (Phantom::MenuBarDrawBorder) {
+ Ph::fillRectEdges(painter, rect, Qt::BottomEdge, 1, swatch.color(S_window_divider));
+ }
+ painter->fillRect(rect.adjusted(0, 0, 0, -1), swatch.color(S_window));
+ break;
+ }
+ case CE_TabBarTabShape: {
+ auto tab = qstyleoption_cast<const QStyleOptionTab*>(option);
+ if (!tab)
+ break;
+ bool rtlHorTabs = (tab->direction == Qt::RightToLeft
+ && (tab->shape == QTabBar::RoundedNorth || tab->shape == QTabBar::RoundedSouth));
+ bool isSelected = tab->state & State_Selected;
+ bool lastTab = ((!rtlHorTabs && tab->position == QStyleOptionTab::End)
+ || (rtlHorTabs && tab->position == QStyleOptionTab::Beginning));
+ bool onlyOne = tab->position == QStyleOptionTab::OnlyOneTab;
+ int tabOverlap = pixelMetric(PM_TabBarTabOverlap, option, widget);
+ const qreal rounding = Ph::TabBarTab_Rounding;
+ Qt::Edge outerEdge = Qt::TopEdge;
+ Qt::Edge edgeTowardNextTab = Qt::RightEdge;
+ switch (tab->shape) {
+ case QTabBar::RoundedNorth:
+ outerEdge = Qt::TopEdge;
+ edgeTowardNextTab = Qt::RightEdge;
+ break;
+ case QTabBar::RoundedSouth:
+ outerEdge = Qt::BottomEdge;
+ edgeTowardNextTab = Qt::RightEdge;
+ break;
+ case QTabBar::RoundedWest:
+ outerEdge = Qt::LeftEdge;
+ edgeTowardNextTab = Qt::BottomEdge;
+ break;
+ case QTabBar::RoundedEast:
+ outerEdge = Qt::RightEdge;
+ edgeTowardNextTab = Qt::BottomEdge;
+ break;
+ default:
+ QCommonStyle::drawControl(element, tab, painter, widget);
+ return;
+ }
+ Qt::Edge innerEdge = Ph::oppositeEdge(outerEdge);
+ Qt::Edge edgeAwayNextTab = Ph::oppositeEdge(edgeTowardNextTab);
+ QRect shapeClipRect = Ph::expandRect(option->rect, innerEdge, -2);
+ QRect drawRect = Ph::expandRect(shapeClipRect, innerEdge, 3 + 2 * rounding + 1);
+ if (!onlyOne && !lastTab) {
+ drawRect = Ph::expandRect(drawRect, edgeTowardNextTab, tabOverlap);
+ shapeClipRect = Ph::expandRect(shapeClipRect, edgeTowardNextTab, tabOverlap);
+ }
+ if (!isSelected) {
+ int offset = proxy()->pixelMetric(PM_TabBarTabShiftVertical, option, widget);
+ drawRect = Ph::expandRect(drawRect, outerEdge, -offset);
+ }
+ painter->save();
+ painter->setClipRect(shapeClipRect);
+ bool hasFrame = tab->features & QStyleOptionTab::HasFrame && !tab->documentMode;
+ Swatchy tabFrameColor, thisFillColor, specular;
+ if (hasFrame) {
+ tabFrameColor = S_tabFrame;
+ if (isSelected) {
+ thisFillColor = S_tabFrame;
+ specular = S_tabFrame_specular;
+ } else {
+ thisFillColor = S_inactiveTabYesFrame;
+ specular = Ph::TabBar_InactiveTabsHaveSpecular ? S_inactiveTabYesFrame_specular : S_none;
+ }
+ } else {
+ tabFrameColor = S_window;
+ if (isSelected) {
+ thisFillColor = S_window;
+ specular = S_window_specular;
+ } else {
+ thisFillColor = S_inactiveTabNoFrame;
+ specular = Ph::TabBar_InactiveTabsHaveSpecular ? S_inactiveTabNoFrame_specular : S_none;
+ }
+ }
+ auto frameColor = isSelected ? S_frame_outline : S_window_outline;
+ Ph::paintBorderedRoundRect(painter, drawRect, rounding, swatch, frameColor, thisFillColor);
+ Ph::paintBorderedRoundRect(painter, drawRect.adjusted(1, 1, -1, -1), rounding, swatch, specular, S_none);
+ painter->restore();
+ if (isSelected) {
+ QRect highlightRect = drawRect.adjusted(2, 1, -2, 0);
+ highlightRect.setHeight(Ph::dpiScaled(2.0));
+ QRect highlightRectSpec = highlightRect.adjusted(-1, -1, 1, 0);
+ painter->fillRect(highlightRectSpec, Ph::DeriveColors::lightSpecularOf(swatch.color(S_highlight)));
+ painter->fillRect(highlightRect, swatch.color(S_highlight));
+
+ QRect refillRect = Ph::rectFromInnerEdgeWithThickness(shapeClipRect, innerEdge, 2);
+ refillRect = Ph::rectTranslatedTowardEdge(refillRect, innerEdge, 2);
+ refillRect = Ph::expandRect(refillRect, edgeAwayNextTab | edgeTowardNextTab, -1);
+ painter->fillRect(refillRect, swatch.color(tabFrameColor));
+ Ph::fillRectEdges(painter, refillRect, edgeAwayNextTab | edgeTowardNextTab, 1, swatch.color(specular));
+ }
+ break;
+ }
+ case CE_ItemViewItem: {
+ auto ivopt = qstyleoption_cast<const QStyleOptionViewItem*>(option);
+ if (!ivopt)
+ break;
+ // Hack to work around broken grid line drawing in Qt's table view code:
+ //
+ // We tell it that the grid line color is a color via
+ // SH_Table_GridLineColor. It draws the grid lines, but it in high DPI it's
+ // broken because it uses a pen/path to draw the line, which makes it too
+ // narrow, subpixel-incorrectly-antialiased, and/or offset from its correct
+ // position. So when we draw the item view items in a table view, we'll
+ // also try to paint 1 pixel outside of our current rect to try to fill in
+ // the incorrectly painted areas where the grid lines are.
+ //
+ // Also note that the table views with the bad drawing code, when
+ // scrolling, will leave garbage behind in the incorrectly-drawn grid line
+ // areas. This will also paint over that.
+ bool overdrawGridHack = false;
+ if (auto tableWidget = qobject_cast<const QTableView*>(widget)) {
+ overdrawGridHack = tableWidget->showGrid() && tableWidget->gridStyle() == Qt::SolidLine;
+ }
+ if (overdrawGridHack) {
+ QRect r = option->rect.adjusted(-1, -1, 1, 1);
+ Ph::fillRectOutline(painter, r, 1, swatch.color(S_base_divider));
+ }
+ QCommonStyle::drawControl(element, option, painter, widget);
+ break;
+ }
+ case CE_ShapedFrame: {
+ auto frameopt = qstyleoption_cast<const QStyleOptionFrame*>(option);
+ if (frameopt) {
+ if (frameopt->frameShape == QFrame::HLine) {
+ QRect r = option->rect;
+ r.setY(r.y() + r.height() / 2);
+ r.setHeight(2);
+ painter->fillRect(r, swatch.color(S_tabFrame_specular));
+ r.setHeight(1);
+ painter->fillRect(r, swatch.color(S_frame_outline));
+ break;
+ } else if (frameopt->frameShape == QFrame::VLine) {
+ QRect r = option->rect;
+ r.setX(r.x() + r.width() / 2);
+ r.setWidth(2);
+ painter->fillRect(r, swatch.color(S_tabFrame_specular));
+ r.setWidth(1);
+ painter->fillRect(r, swatch.color(S_frame_outline));
+ break;
+ }
+ }
+ QCommonStyle::drawControl(element, option, painter, widget);
+ break;
+ }
+ default:
+ QCommonStyle::drawControl(element, option, painter, widget);
+ break;
+ }
+}
+
+QPalette BaseStyle::standardPalette() const
+{
+ return QCommonStyle::standardPalette();
+}
+
+void BaseStyle::drawComplexControl(ComplexControl control,
+ const QStyleOptionComplex* option,
+ QPainter* painter,
+ const QWidget* widget) const
+{
+#ifdef BUILD_WITH_EASY_PROFILER
+ EASY_BLOCK("drawControl");
+ const char* controlCString = QMetaEnum::fromType<QStyle::ComplexControl>().valueToKey(control);
+ EASY_TEXT("ComplexControl", controlCString);
+#endif
+ using Swatchy = Phantom::Swatchy;
+ using namespace Phantom::SwatchColors;
+ namespace Ph = Phantom;
+ auto ph_swatchPtr = Ph::getCachedSwatchOfQPalette(&d->swatchCache, &d->headSwatchFastKey, option->palette);
+ const Ph::PhSwatch& swatch = *ph_swatchPtr.data();
+
+ switch (control) {
+ case CC_GroupBox: {
+ auto groupBox = qstyleoption_cast<const QStyleOptionGroupBox*>(option);
+ if (!groupBox)
+ break;
+ painter->save();
+ // Draw frame
+ QRect textRect = proxy()->subControlRect(CC_GroupBox, option, SC_GroupBoxLabel, widget);
+ QRect checkBoxRect = proxy()->subControlRect(CC_GroupBox, option, SC_GroupBoxCheckBox, widget);
+
+ if (groupBox->subControls & QStyle::SC_GroupBoxFrame) {
+ QStyleOptionFrame frame;
+ frame.QStyleOption::operator=(*groupBox);
+ frame.features = groupBox->features;
+ frame.lineWidth = groupBox->lineWidth;
+ frame.midLineWidth = groupBox->midLineWidth;
+ frame.rect = proxy()->subControlRect(CC_GroupBox, option, SC_GroupBoxFrame, widget);
+ proxy()->drawPrimitive(PE_FrameGroupBox, &frame, painter, widget);
+ }
+
+ // Draw title
+ if ((groupBox->subControls & QStyle::SC_GroupBoxLabel) && !groupBox->text.isEmpty()) {
+ // groupBox->textColor gets the incorrect palette here
+ painter->setPen(QPen(option->palette.windowText(), 1));
+ unsigned alignment = groupBox->textAlignment;
+ if (!proxy()->styleHint(QStyle::SH_UnderlineShortcut, option, widget))
+ alignment |= Qt::TextHideMnemonic;
+
+ proxy()->drawItemText(painter,
+ textRect,
+ alignment | Qt::TextShowMnemonic | Qt::AlignLeft,
+ groupBox->palette,
+ groupBox->state & State_Enabled,
+ groupBox->text,
+ QPalette::NoRole);
+
+ if (groupBox->state & State_HasFocus) {
+ QStyleOptionFocusRect fropt;
+ fropt.QStyleOption::operator=(*groupBox);
+ fropt.rect = textRect.adjusted(-1, 0, 1, 0);
+ proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget);
+ }
+ }
+
+ // Draw checkbox
+ if (groupBox->subControls & SC_GroupBoxCheckBox) {
+ QStyleOptionButton box;
+ box.QStyleOption::operator=(*groupBox);
+ box.rect = checkBoxRect;
+ proxy()->drawPrimitive(PE_IndicatorCheckBox, &box, painter, widget);
+ }
+ painter->restore();
+ break;
+ }
+ case CC_SpinBox: {
+ auto spinBox = qstyleoption_cast<const QStyleOptionSpinBox*>(option);
+ if (!spinBox)
+ break;
+ const qreal rounding = Ph::SpinBox_Rounding;
+ bool isLeftToRight = option->direction != Qt::RightToLeft;
+ const QRect rect = spinBox->rect;
+ bool sunken = spinBox->state & State_Sunken;
+ bool upIsActive = spinBox->activeSubControls == SC_SpinBoxUp;
+ bool downIsActive = spinBox->activeSubControls == SC_SpinBoxDown;
+ bool hasFocus = option->state & State_HasFocus;
+ bool isEnabled = option->state & State_Enabled;
+ QRect upRect = proxy()->subControlRect(CC_SpinBox, spinBox, SC_SpinBoxUp, widget);
+ QRect downRect = proxy()->subControlRect(CC_SpinBox, spinBox, SC_SpinBoxDown, widget);
+ if (spinBox->frame) {
+ QRect upDownRect = upRect | downRect;
+ upDownRect.adjust(0, -1, 0, 1);
+ painter->save(); // 0
+ // Fill background
+ Ph::paintBorderedRoundRect(painter, rect, rounding, swatch, S_none, S_base);
+ // Draw button fill
+ painter->setClipRect(upDownRect);
+ // Side with the border
+ Qt::Edge edge = isLeftToRight ? Qt::LeftEdge : Qt::RightEdge;
+ Ph::paintBorderedRoundRect(
+ painter, Ph::expandRect(upDownRect, Ph::oppositeEdge(edge), -1), rounding, swatch, S_none, S_button);
+ painter->restore(); // 0
+ if (Ph::OverhangShadows && !hasFocus && isEnabled) {
+ // Imperfect, leaves tiny gap on left and right. Going closer would eat
+ // into the outline, though.
+ QRect shadowRect = rect.adjusted(qRound(rounding / 2), 1, -qRound(rounding / 2), -1);
+ if (isLeftToRight) {
+ shadowRect.setRight(upDownRect.left());
+ } else {
+ shadowRect.setLeft(upDownRect.right());
+ }
+ Ph::fillRectEdges(painter, shadowRect, Qt::TopEdge, 1, swatch.color(S_base_shadow));
+ }
+ if ((spinBox->stepEnabled & QAbstractSpinBox::StepUpEnabled) && upIsActive && sunken) {
+ painter->fillRect(upRect, swatch.color(S_button_pressed));
+ }
+ if ((spinBox->stepEnabled & QAbstractSpinBox::StepDownEnabled) && downIsActive && sunken) {
+ painter->fillRect(downRect, swatch.color(S_button_pressed));
+ }
+ // Left or right border line
+ Ph::fillRectEdges(painter, upDownRect, edge, 1, swatch.color(S_window_outline));
+ Ph::PSave save(painter);
+ // Outline over entire frame
+ Swatchy outlineColor = hasFocus ? S_highlight_outline : S_window_outline;
+ Ph::paintBorderedRoundRect(painter, rect, rounding, swatch, outlineColor, S_none);
+ save.restore();
+ }
+
+ if (spinBox->buttonSymbols == QAbstractSpinBox::PlusMinus) {
+ Ph::PSave save(painter);
+ // TODO fix up old fusion code here
+ int centerX = upRect.center().x();
+ int centerY = upRect.center().y();
+ Swatchy arrowColorUp =
+ spinBox->stepEnabled & QAbstractSpinBox::StepUpEnabled ? S_indicator_current : S_indicator_disabled;
+ Swatchy arrowColorDown =
+ spinBox->stepEnabled & QAbstractSpinBox::StepDownEnabled ? S_indicator_current : S_indicator_disabled;
+ painter->setPen(swatch.pen(arrowColorUp));
+ painter->drawLine(centerX - 1, centerY, centerX + 3, centerY);
+ painter->drawLine(centerX + 1, centerY - 2, centerX + 1, centerY + 2);
+ centerX = downRect.center().x();
+ centerY = downRect.center().y();
+ painter->setPen(arrowColorDown);
+ painter->drawLine(centerX - 1, centerY, centerX + 3, centerY);
+ } else if (spinBox->buttonSymbols == QAbstractSpinBox::UpDownArrows) {
+ int xoffs = isLeftToRight ? 0 : 1;
+ Ph::drawArrow(painter,
+ upRect.adjusted(4 + xoffs, 1, -5 + xoffs, 1),
+ Qt::UpArrow,
+ swatch,
+ spinBox->stepEnabled & QAbstractSpinBox::StepUpEnabled);
+ Ph::drawArrow(painter,
+ downRect.adjusted(4 + xoffs, 0, -5 + xoffs, -1),
+ Qt::DownArrow,
+ swatch,
+ spinBox->stepEnabled & QAbstractSpinBox::StepDownEnabled);
+ }
+ break;
+ }
+ case CC_TitleBar: {
+ auto titleBar = qstyleoption_cast<const QStyleOptionTitleBar*>(option);
+ if (!titleBar)
+ break;
+ painter->save();
+ const int buttonMargin = 5;
+ bool active = (titleBar->titleBarState & State_Active);
+ QRect fullRect = titleBar->rect;
+ QPalette palette = option->palette;
+ QColor highlight = option->palette.highlight().color();
+ QColor outline = option->palette.dark().color();
+
+ QColor titleBarFrameBorder(active ? highlight.darker(180) : outline.darker(110));
+ QColor titleBarHighlight(active ? highlight.lighter(120) : palette.background().color().lighter(120));
+ QColor textColor(active ? 0xffffff : 0xff000000);
+ QColor textAlphaColor(active ? 0xffffff : 0xff000000);
+
+ {
+ // Fill title
+ QColor titlebarColor = QColor(active ? highlight : palette.background().color());
+ painter->fillRect(option->rect.adjusted(1, 1, -1, 0), titlebarColor);
+ // Frame and rounded corners
+ painter->setPen(titleBarFrameBorder);
+
+ // top outline
+ painter->drawLine(fullRect.left() + 5, fullRect.top(), fullRect.right() - 5, fullRect.top());
+ painter->drawLine(fullRect.left(), fullRect.top() + 4, fullRect.left(), fullRect.bottom());
+ const QPoint points[5] = {QPoint(fullRect.left() + 4, fullRect.top() + 1),
+ QPoint(fullRect.left() + 3, fullRect.top() + 1),
+ QPoint(fullRect.left() + 2, fullRect.top() + 2),
+ QPoint(fullRect.left() + 1, fullRect.top() + 3),
+ QPoint(fullRect.left() + 1, fullRect.top() + 4)};
+ painter->drawPoints(points, 5);
+
+ painter->drawLine(fullRect.right(), fullRect.top() + 4, fullRect.right(), fullRect.bottom());
+ const QPoint points2[5] = {QPoint(fullRect.right() - 3, fullRect.top() + 1),
+ QPoint(fullRect.right() - 4, fullRect.top() + 1),
+ QPoint(fullRect.right() - 2, fullRect.top() + 2),
+ QPoint(fullRect.right() - 1, fullRect.top() + 3),
+ QPoint(fullRect.right() - 1, fullRect.top() + 4)};
+ painter->drawPoints(points2, 5);
+
+ // draw bottomline
+ painter->drawLine(fullRect.right(), fullRect.bottom(), fullRect.left(), fullRect.bottom());
+
+ // top highlight
+ painter->setPen(titleBarHighlight);
+ painter->drawLine(fullRect.left() + 6, fullRect.top() + 1, fullRect.right() - 6, fullRect.top() + 1);
+ }
+ // draw title
+ QRect textRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarLabel, widget);
+ painter->setPen(active ? (titleBar->palette.text().color().lighter(120)) : titleBar->palette.text().color());
+ // Note workspace also does elliding but it does not use the correct font
+ QString title = painter->fontMetrics().elidedText(titleBar->text, Qt::ElideRight, textRect.width() - 14);
+ painter->drawText(textRect.adjusted(1, 1, 1, 1), title, QTextOption(Qt::AlignHCenter | Qt::AlignVCenter));
+ painter->setPen(Qt::white);
+ if (active)
+ painter->drawText(textRect, title, QTextOption(Qt::AlignHCenter | Qt::AlignVCenter));
+ // min button
+ if ((titleBar->subControls & SC_TitleBarMinButton) && (titleBar->titleBarFlags & Qt::WindowMinimizeButtonHint)
+ && !(titleBar->titleBarState & Qt::WindowMinimized)) {
+ QRect minButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarMinButton, widget);
+ if (minButtonRect.isValid()) {
+ bool hover =
+ (titleBar->activeSubControls & SC_TitleBarMinButton) && (titleBar->state & State_MouseOver);
+ bool sunken = (titleBar->activeSubControls & SC_TitleBarMinButton) && (titleBar->state & State_Sunken);
+ Ph::drawMdiButton(painter, titleBar, minButtonRect, hover, sunken);
+ QRect minButtonIconRect =
+ minButtonRect.adjusted(buttonMargin, buttonMargin, -buttonMargin, -buttonMargin);
+ painter->setPen(textColor);
+ painter->drawLine(minButtonIconRect.center().x() - 2,
+ minButtonIconRect.center().y() + 3,
+ minButtonIconRect.center().x() + 3,
+ minButtonIconRect.center().y() + 3);
+ painter->drawLine(minButtonIconRect.center().x() - 2,
+ minButtonIconRect.center().y() + 4,
+ minButtonIconRect.center().x() + 3,
+ minButtonIconRect.center().y() + 4);
+ painter->setPen(textAlphaColor);
+ painter->drawLine(minButtonIconRect.center().x() - 3,
+ minButtonIconRect.center().y() + 3,
+ minButtonIconRect.center().x() - 3,
+ minButtonIconRect.center().y() + 4);
+ painter->drawLine(minButtonIconRect.center().x() + 4,
+ minButtonIconRect.center().y() + 3,
+ minButtonIconRect.center().x() + 4,
+ minButtonIconRect.center().y() + 4);
+ }
+ }
+ // max button
+ if ((titleBar->subControls & SC_TitleBarMaxButton) && (titleBar->titleBarFlags & Qt::WindowMaximizeButtonHint)
+ && !(titleBar->titleBarState & Qt::WindowMaximized)) {
+ QRect maxButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarMaxButton, widget);
+ if (maxButtonRect.isValid()) {
+ bool hover =
+ (titleBar->activeSubControls & SC_TitleBarMaxButton) && (titleBar->state & State_MouseOver);
+ bool sunken = (titleBar->activeSubControls & SC_TitleBarMaxButton) && (titleBar->state & State_Sunken);
+ Ph::drawMdiButton(painter, titleBar, maxButtonRect, hover, sunken);
+
+ QRect maxButtonIconRect =
+ maxButtonRect.adjusted(buttonMargin, buttonMargin, -buttonMargin, -buttonMargin);
+
+ painter->setPen(textColor);
+ painter->drawRect(maxButtonIconRect.adjusted(0, 0, -1, -1));
+ painter->drawLine(maxButtonIconRect.left() + 1,
+ maxButtonIconRect.top() + 1,
+ maxButtonIconRect.right() - 1,
+ maxButtonIconRect.top() + 1);
+ painter->setPen(textAlphaColor);
+ const QPoint points[4] = {maxButtonIconRect.topLeft(),
+ maxButtonIconRect.topRight(),
+ maxButtonIconRect.bottomLeft(),
+ maxButtonIconRect.bottomRight()};
+ painter->drawPoints(points, 4);
+ }
+ }
+
+ // close button
+ if ((titleBar->subControls & SC_TitleBarCloseButton) && (titleBar->titleBarFlags & Qt::WindowSystemMenuHint)) {
+ QRect closeButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarCloseButton, widget);
+ if (closeButtonRect.isValid()) {
+ bool hover =
+ (titleBar->activeSubControls & SC_TitleBarCloseButton) && (titleBar->state & State_MouseOver);
+ bool sunken =
+ (titleBar->activeSubControls & SC_TitleBarCloseButton) && (titleBar->state & State_Sunken);
+ Ph::drawMdiButton(painter, titleBar, closeButtonRect, hover, sunken);
+ QRect closeIconRect =
+ closeButtonRect.adjusted(buttonMargin, buttonMargin, -buttonMargin, -buttonMargin);
+ painter->setPen(textAlphaColor);
+ const QLine lines[4] = {QLine(closeIconRect.left() + 1,
+ closeIconRect.top(),
+ closeIconRect.right(),
+ closeIconRect.bottom() - 1),
+ QLine(closeIconRect.left(),
+ closeIconRect.top() + 1,
+ closeIconRect.right() - 1,
+ closeIconRect.bottom()),
+ QLine(closeIconRect.right() - 1,
+ closeIconRect.top(),
+ closeIconRect.left(),
+ closeIconRect.bottom() - 1),
+ QLine(closeIconRect.right(),
+ closeIconRect.top() + 1,
+ closeIconRect.left() + 1,
+ closeIconRect.bottom())};
+ painter->drawLines(lines, 4);
+ const QPoint points[4] = {closeIconRect.topLeft(),
+ closeIconRect.topRight(),
+ closeIconRect.bottomLeft(),
+ closeIconRect.bottomRight()};
+ painter->drawPoints(points, 4);
+
+ painter->setPen(textColor);
+ painter->drawLine(closeIconRect.left() + 1,
+ closeIconRect.top() + 1,
+ closeIconRect.right() - 1,
+ closeIconRect.bottom() - 1);
+ painter->drawLine(closeIconRect.left() + 1,
+ closeIconRect.bottom() - 1,
+ closeIconRect.right() - 1,
+ closeIconRect.top() + 1);
+ }
+ }
+
+ // normalize button
+ if ((titleBar->subControls & SC_TitleBarNormalButton)
+ && (((titleBar->titleBarFlags & Qt::WindowMinimizeButtonHint)
+ && (titleBar->titleBarState & Qt::WindowMinimized))
+ || ((titleBar->titleBarFlags & Qt::WindowMaximizeButtonHint)
+ && (titleBar->titleBarState & Qt::WindowMaximized)))) {
+ QRect normalButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarNormalButton, widget);
+ if (normalButtonRect.isValid()) {
+
+ bool hover =
+ (titleBar->activeSubControls & SC_TitleBarNormalButton) && (titleBar->state & State_MouseOver);
+ bool sunken =
+ (titleBar->activeSubControls & SC_TitleBarNormalButton) && (titleBar->state & State_Sunken);
+ QRect normalButtonIconRect =
+ normalButtonRect.adjusted(buttonMargin, buttonMargin, -buttonMargin, -buttonMargin);
+ Ph::drawMdiButton(painter, titleBar, normalButtonRect, hover, sunken);
+
+ QRect frontWindowRect = normalButtonIconRect.adjusted(0, 3, -3, 0);
+ painter->setPen(textColor);
+ painter->drawRect(frontWindowRect.adjusted(0, 0, -1, -1));
+ painter->drawLine(frontWindowRect.left() + 1,
+ frontWindowRect.top() + 1,
+ frontWindowRect.right() - 1,
+ frontWindowRect.top() + 1);
+ painter->setPen(textAlphaColor);
+ const QPoint points[4] = {frontWindowRect.topLeft(),
+ frontWindowRect.topRight(),
+ frontWindowRect.bottomLeft(),
+ frontWindowRect.bottomRight()};
+ painter->drawPoints(points, 4);
+
+ QRect backWindowRect = normalButtonIconRect.adjusted(3, 0, 0, -3);
+ QRegion clipRegion = backWindowRect;
+ clipRegion -= frontWindowRect;
+ painter->save();
+ painter->setClipRegion(clipRegion);
+ painter->setPen(textColor);
+ painter->drawRect(backWindowRect.adjusted(0, 0, -1, -1));
+ painter->drawLine(backWindowRect.left() + 1,
+ backWindowRect.top() + 1,
+ backWindowRect.right() - 1,
+ backWindowRect.top() + 1);
+ painter->setPen(textAlphaColor);
+ const QPoint points2[4] = {backWindowRect.topLeft(),
+ backWindowRect.topRight(),
+ backWindowRect.bottomLeft(),
+ backWindowRect.bottomRight()};
+ painter->drawPoints(points2, 4);
+ painter->restore();
+ }
+ }
+
+ // context help button
+ if (titleBar->subControls & SC_TitleBarContextHelpButton
+ && (titleBar->titleBarFlags & Qt::WindowContextHelpButtonHint)) {
+ QRect contextHelpButtonRect =
+ proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarContextHelpButton, widget);
+ if (contextHelpButtonRect.isValid()) {
+ bool hover =
+ (titleBar->activeSubControls & SC_TitleBarContextHelpButton) && (titleBar->state & State_MouseOver);
+ bool sunken =
+ (titleBar->activeSubControls & SC_TitleBarContextHelpButton) && (titleBar->state & State_Sunken);
+ Ph::drawMdiButton(painter, titleBar, contextHelpButtonRect, hover, sunken);
+ // This is lame, but I doubt it will get used often. Previously, XPM
+ // icon was used here (very poorly, by re-allocating a QImage over and
+ // over and modifying/painting it)
+ QIcon helpIcon = QCommonStyle::standardIcon(QStyle::SP_DialogHelpButton);
+ helpIcon.paint(painter, contextHelpButtonRect.adjusted(4, 4, -4, -4));
+ }
+ }
+
+ // shade button
+ if (titleBar->subControls & SC_TitleBarShadeButton && (titleBar->titleBarFlags & Qt::WindowShadeButtonHint)) {
+ QRect shadeButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarShadeButton, widget);
+ if (shadeButtonRect.isValid()) {
+ bool hover =
+ (titleBar->activeSubControls & SC_TitleBarShadeButton) && (titleBar->state & State_MouseOver);
+ bool sunken =
+ (titleBar->activeSubControls & SC_TitleBarShadeButton) && (titleBar->state & State_Sunken);
+ Ph::drawMdiButton(painter, titleBar, shadeButtonRect, hover, sunken);
+ Ph::drawArrow(painter, shadeButtonRect.adjusted(5, 7, -5, -7), Qt::UpArrow, swatch);
+ }
+ }
+
+ // unshade button
+ if (titleBar->subControls & SC_TitleBarUnshadeButton && (titleBar->titleBarFlags & Qt::WindowShadeButtonHint)) {
+ QRect unshadeButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarUnshadeButton, widget);
+ if (unshadeButtonRect.isValid()) {
+ bool hover =
+ (titleBar->activeSubControls & SC_TitleBarUnshadeButton) && (titleBar->state & State_MouseOver);
+ bool sunken =
+ (titleBar->activeSubControls & SC_TitleBarUnshadeButton) && (titleBar->state & State_Sunken);
+ Ph::drawMdiButton(painter, titleBar, unshadeButtonRect, hover, sunken);
+ Ph::drawArrow(painter, unshadeButtonRect.adjusted(5, 7, -5, -7), Qt::DownArrow, swatch);
+ }
+ }
+
+ if ((titleBar->subControls & SC_TitleBarSysMenu) && (titleBar->titleBarFlags & Qt::WindowSystemMenuHint)) {
+ QRect iconRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarSysMenu, widget);
+ if (iconRect.isValid()) {
+ if (!titleBar->icon.isNull()) {
+ titleBar->icon.paint(painter, iconRect);
+ } else {
+ QStyleOption tool = *titleBar;
+ QPixmap pm = proxy()->standardIcon(SP_TitleBarMenuButton, &tool, widget).pixmap(16, 16);
+ tool.rect = iconRect;
+ painter->save();
+ proxy()->drawItemPixmap(painter, iconRect, Qt::AlignCenter, pm);
+ painter->restore();
+ }
+ }
+ }
+ painter->restore();
+ break;
+ }
+ case CC_ScrollBar: {
+ auto scrollBar = qstyleoption_cast<const QStyleOptionSlider*>(option);
+ if (!scrollBar)
+ break;
+ auto pr = proxy();
+ QRect scrollBarSubLine = pr->subControlRect(control, scrollBar, SC_ScrollBarSubLine, widget);
+ QRect scrollBarAddLine = pr->subControlRect(control, scrollBar, SC_ScrollBarAddLine, widget);
+ QRect scrollBarSlider = pr->subControlRect(control, scrollBar, SC_ScrollBarSlider, widget);
+ QRect scrollBarGroove = pr->subControlRect(control, scrollBar, SC_ScrollBarGroove, widget);
+
+ int padding = Ph::dpiScaled(4);
+ scrollBarSlider.setX(scrollBarSlider.x() + padding);
+ scrollBarSlider.setY(scrollBarSlider.y() + padding);
+ // Width and height should be reduced by 2 * padding, but somehow padding is enough.
+ scrollBarSlider.setWidth(scrollBarSlider.width() - padding);
+ scrollBarSlider.setHeight(scrollBarSlider.height() - padding);
+
+ // Groove/gutter/trench area
+ if (scrollBar->subControls & SC_ScrollBarGroove) {
+ painter->fillRect(scrollBarGroove, swatch.color(S_window));
+ }
+
+ // Slider thumb
+ if (scrollBar->subControls & SC_ScrollBarSlider) {
+ qreal radius =
+ (scrollBar->orientation == Qt::Horizontal ? scrollBarSlider.height() : scrollBarSlider.width()) / 2.0;
+ painter->fillRect(scrollBarSlider, swatch.color(S_window));
+ Ph::paintSolidRoundRect(painter, scrollBarSlider, radius, swatch, S_scrollbarSlider);
+ }
+
+ // The SubLine (up/left) buttons
+ if (scrollBar->subControls & SC_ScrollBarSubLine) {
+ painter->fillRect(scrollBarSubLine, swatch.color(S_window));
+ }
+
+ // The AddLine (down/right) button
+ if (scrollBar->subControls & SC_ScrollBarAddLine) {
+ painter->fillRect(scrollBarAddLine, swatch.color(S_window));
+ }
+ break;
+ }
+ case CC_ComboBox: {
+ auto comboBox = qstyleoption_cast<const QStyleOptionComboBox*>(option);
+ if (!comboBox)
+ break;
+ painter->save();
+ bool isLeftToRight = option->direction != Qt::RightToLeft;
+ bool hasFocus = option->state & State_HasFocus && option->state & State_KeyboardFocusChange;
+ bool isSunken = comboBox->state & State_Sunken;
+ QRect rect = comboBox->rect;
+ QRect downArrowRect = proxy()->subControlRect(CC_ComboBox, comboBox, SC_ComboBoxArrow, widget);
+ // Draw a line edit
+ if (comboBox->editable) {
+ Swatchy buttonFill = isSunken ? S_button_pressed : S_button;
+ // if (!hasOptions)
+ // buttonFill = S_window;
+ painter->fillRect(rect, swatch.color(buttonFill));
+ if (comboBox->frame) {
+ QStyleOptionFrame buttonOption;
+ buttonOption.QStyleOption::operator=(*comboBox);
+ buttonOption.rect = rect;
+ buttonOption.state =
+ (comboBox->state & (State_Enabled | State_MouseOver | State_HasFocus)) | State_KeyboardFocusChange;
+ if (isSunken) {
+ buttonOption.state |= State_Sunken;
+ buttonOption.state &= ~State_MouseOver;
+ }
+ proxy()->drawPrimitive(PE_FrameLineEdit, &buttonOption, painter, widget);
+ QRect fr = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxEditField, widget);
+ QRect br = rect;
+ if (isLeftToRight) {
+ br.setLeft(fr.x() + fr.width());
+ } else {
+ br.setRight(fr.left() - 1);
+ }
+ Qt::Edge edge = isLeftToRight ? Qt::LeftEdge : Qt::RightEdge;
+ Swatchy color = hasFocus ? S_highlight_outline : S_window_outline;
+ br.adjust(0, 1, 0, -1);
+ Ph::fillRectEdges(painter, br, edge, 1, swatch.color(color));
+ br.adjust(1, 0, -1, 0);
+ Swatchy specular = isSunken ? S_button_pressed_specular : S_button_specular;
+ Ph::fillRectOutline(painter, br, 1, swatch.color(specular));
+ }
+ } else {
+ QStyleOptionButton buttonOption;
+ buttonOption.QStyleOption::operator=(*comboBox);
+ buttonOption.rect = rect;
+ buttonOption.state =
+ comboBox->state
+ & (State_Enabled | State_MouseOver | State_HasFocus | State_Active | State_KeyboardFocusChange);
+ // Combo boxes should be shown to be keyboard interactive if they're
+ // focused at all, not just if the user has pressed tab to enter keyboard
+ // focus change mode. This is because the up/down arrows can, regardless
+ // of having pressed tab, control the combo box selection.
+ if (comboBox->state & State_HasFocus)
+ buttonOption.state |= State_KeyboardFocusChange;
+ if (isSunken) {
+ buttonOption.state |= State_Sunken;
+ buttonOption.state &= ~State_MouseOver;
+ }
+ proxy()->drawPrimitive(PE_PanelButtonCommand, &buttonOption, painter, widget);
+ }
+ if (comboBox->subControls & SC_ComboBoxArrow) {
+ int margin =
+ static_cast<int>(qMin(downArrowRect.width(), downArrowRect.height()) * Ph::ComboBox_ArrowMarginRatio);
+ QRect r = downArrowRect;
+ r.adjust(margin, margin, -margin, -margin);
+ // Draw the up/down arrow
+ Ph::drawArrow(painter, r, Qt::DownArrow, swatch);
+ }
+ painter->restore();
+ break;
+ }
+ case CC_Slider: {
+ auto slider = qstyleoption_cast<const QStyleOptionSlider*>(option);
+ if (!slider)
+ break;
+ const QRect groove = proxy()->subControlRect(CC_Slider, option, SC_SliderGroove, widget);
+ const QRect handle = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget);
+ bool horizontal = slider->orientation == Qt::Horizontal;
+ bool ticksAbove = slider->tickPosition & QSlider::TicksAbove;
+ bool ticksBelow = slider->tickPosition & QSlider::TicksBelow;
+ Swatchy outlineColor = S_window_outline;
+ if (option->state & State_HasFocus && option->state & State_KeyboardFocusChange)
+ outlineColor = S_highlight_outline;
+ if ((option->subControls & SC_SliderGroove) && groove.isValid()) {
+ QRect g0 = groove;
+ if (g0.height() > 5)
+ g0.adjust(0, 1, 0, -1);
+ Ph::PSave saver(painter);
+ Swatchy gutterColor = option->state & State_Enabled ? S_scrollbarGutter : S_window;
+ Ph::paintBorderedRoundRect(painter, groove, Ph::SliderGroove_Rounding, swatch, outlineColor, gutterColor);
+ }
+ if (option->subControls & SC_SliderTickmarks) {
+ Ph::PSave save(painter);
+ painter->setPen(swatch.pen(S_window_outline));
+ int tickSize = proxy()->pixelMetric(PM_SliderTickmarkOffset, option, widget);
+ int available = proxy()->pixelMetric(PM_SliderSpaceAvailable, slider, widget);
+ int interval = slider->tickInterval;
+ if (interval <= 0) {
+ interval = slider->singleStep;
+ if (QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, interval, available)
+ - QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, 0, available)
+ < 3)
+ interval = slider->pageStep;
+ }
+ if (interval <= 0)
+ interval = 1;
+
+ int v = slider->minimum;
+ int len = proxy()->pixelMetric(PM_SliderLength, slider, widget);
+ while (v <= slider->maximum + 1) {
+ if (v == slider->maximum + 1 && interval == 1)
+ break;
+ const int v_ = qMin(v, slider->maximum);
+ int pos = sliderPositionFromValue(slider->minimum,
+ slider->maximum,
+ v_,
+ (horizontal ? slider->rect.width() : slider->rect.height()) - len,
+ slider->upsideDown)
+ + len / 2;
+ int extra = 2 - ((v_ == slider->minimum || v_ == slider->maximum) ? 1 : 0);
+
+ if (horizontal) {
+ if (ticksAbove) {
+ painter->drawLine(pos, slider->rect.top() + extra, pos, slider->rect.top() + tickSize);
+ }
+ if (ticksBelow) {
+ painter->drawLine(pos, slider->rect.bottom() - extra, pos, slider->rect.bottom() - tickSize);
+ }
+ } else {
+ if (ticksAbove) {
+ painter->drawLine(slider->rect.left() + extra, pos, slider->rect.left() + tickSize, pos);
+ }
+ if (ticksBelow) {
+ painter->drawLine(slider->rect.right() - extra, pos, slider->rect.right() - tickSize, pos);
+ }
+ }
+ // in the case where maximum is max int
+ int nextInterval = v + interval;
+ if (nextInterval < v)
+ break;
+ v = nextInterval;
+ }
+ }
+ // draw handle
+ if ((option->subControls & SC_SliderHandle)) {
+ bool isPressed = option->state & QStyle::State_Sunken && option->activeSubControls & SC_SliderHandle;
+ QRect r = handle;
+ Swatchy handleOutline, handleFill, handleSpecular;
+ if (option->state & State_HasFocus && option->state & State_KeyboardFocusChange) {
+ handleOutline = S_highlight_outline;
+ } else {
+ handleOutline = S_window_outline;
+ }
+ if (isPressed) {
+ handleFill = S_sliderHandle_pressed;
+ handleSpecular = S_sliderHandle_pressed_specular;
+ } else {
+ handleFill = S_sliderHandle;
+ handleSpecular = S_sliderHandle_specular;
+ }
+ Ph::PSave save(painter);
+ Ph::paintBorderedRoundRect(painter, r, Ph::SliderHandle_Rounding, swatch, handleOutline, handleFill);
+ r.adjust(1, 1, -1, -1);
+ Ph::paintBorderedRoundRect(painter, r, Ph::SliderHandle_Rounding, swatch, handleSpecular, S_none);
+ }
+ break;
+ }
+ case CC_ToolButton: {
+ auto tbopt = qstyleoption_cast<const QStyleOptionToolButton*>(option);
+ if (Ph::AllowToolBarAutoRaise || !tbopt || !widget || !widget->parent()
+ || !widget->parent()->inherits("QToolBar")) {
+ QCommonStyle::drawComplexControl(control, option, painter, widget);
+ break;
+ }
+ QStyleOptionToolButton opt_;
+ opt_.QStyleOptionToolButton::operator=(*tbopt);
+ opt_.state &= ~State_AutoRaise;
+ QCommonStyle::drawComplexControl(control, &opt_, painter, widget);
+ break;
+ }
+ case CC_Dial:
+ if (auto dial = qstyleoption_cast<const QStyleOptionSlider*>(option))
+ Ph::drawDial(dial, painter);
+ break;
+ default:
+ QCommonStyle::drawComplexControl(control, option, painter, widget);
+ break;
+ }
+}
+
+int BaseStyle::pixelMetric(PixelMetric metric, const QStyleOption* option, const QWidget* widget) const
+{
+ // Calculate pixel metrics.
+ // Use immediate return if value is not supposed to be dpi-scaled.
+ int val = -1;
+ switch (metric) {
+ case PM_SliderTickmarkOffset:
+ val = 6;
+ break;
+ case PM_ToolTipLabelFrameWidth:
+ case PM_HeaderMargin:
+ case PM_ButtonMargin:
+ case PM_SpinBoxFrameWidth:
+ val = Phantom::DefaultFrameWidth;
+ break;
+ case PM_ButtonDefaultIndicator:
+ case PM_ButtonShiftHorizontal:
+ val = 0;
+ break;
+ case PM_ButtonShiftVertical:
+ if (qobject_cast<const QToolButton*>(widget)) {
+ return 0;
+ }
+ val = 1;
+ break;
+ case PM_ComboBoxFrameWidth:
+ return 1;
+ case PM_DefaultFrameWidth:
+ // Original comment from fusion:
+ // Do not dpi-scale because the drawn frame is always exactly 1 pixel thick
+ // My note:
+ // I seriously doubt, with all of the hacky add-or-remove-1 things
+ // everywhere in fusion (and still in phantom), and the fact that fusion is
+ // totally broken in high dpi, that this actually holds true.
+ if (qobject_cast<const QAbstractItemView*>(widget)) {
+ return 1;
+ }
+ val = qMax(1, Phantom::DefaultFrameWidth - 2);
+ break;
+ case PM_MessageBoxIconSize:
+ val = 48;
+ break;
+ case PM_DialogButtonsSeparator:
+ case PM_ScrollBarSliderMin:
+ val = 26;
+ break;
+ case PM_TitleBarHeight:
+ val = 24;
+ break;
+ case PM_ScrollBarExtent:
+ val = 12;
+ break;
+ case PM_SliderThickness:
+ case PM_SliderLength:
+ val = 15;
+ break;
+ case PM_DockWidgetTitleMargin:
+ val = 1;
+ break;
+ case PM_MenuVMargin:
+ case PM_MenuHMargin:
+ case PM_MenuPanelWidth:
+ val = 0;
+ break;
+ case PM_MenuBarItemSpacing:
+ val = 0;
+ break;
+ case PM_MenuBarHMargin:
+ // option is usually nullptr, use widget instead to get font metrics
+ if (!Phantom::MenuBarLeftMargin || !widget) {
+ val = 0;
+ break;
+ }
+ return widget->fontMetrics().height() * Phantom::MenuBar_HorizontalPaddingFontRatio;
+ case PM_MenuBarVMargin:
+ case PM_MenuBarPanelWidth:
+ val = 0;
+ break;
+ case PM_ToolBarSeparatorExtent:
+ val = 9;
+ break;
+ case PM_ToolBarHandleExtent: {
+ int dotLen = Phantom::dpiScaled(2);
+ return dotLen * (3 * 2 - 1);
+ }
+ case PM_ToolBarItemSpacing:
+ val = 1;
+ break;
+ case PM_ToolBarFrameWidth:
+ val = Phantom::MenuBar_FrameWidth;
+ break;
+ case PM_ToolBarItemMargin:
+ val = 1;
+ break;
+ case PM_ToolBarExtensionExtent:
+ val = 32;
+ break;
+ case PM_ListViewIconSize:
+ case PM_SmallIconSize:
+ if (Phantom::ItemView_UseFontHeightForDecorationSize && widget
+ && qobject_cast<const QAbstractItemView*>(widget)) {
+ // QAbstractItemView::viewOptions() always uses nullptr for the
+ // styleoption when querying for PM_SmallIconSize. The best we can do is
+ // use the font set on the widget itself, which is obviously going to be
+ // wrong if the row has a custom font set on it. Hmm.
+ return widget->fontMetrics().height();
+ }
+ val = 16;
+ break;
+ case PM_ButtonIconSize: {
+ if (option)
+ return option->fontMetrics.height();
+ if (widget)
+ return widget->fontMetrics().height();
+ val = 16;
+ break;
+ }
+ case PM_DockWidgetTitleBarButtonMargin:
+ val = 2;
+ break;
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
+ case PM_TitleBarButtonSize:
+ val = 19;
+ break;
+#endif
+ case PM_MaximumDragDistance:
+ return -1; // Do not dpi-scale because the value is magic
+ case PM_TabCloseIndicatorWidth:
+ case PM_TabCloseIndicatorHeight:
+ val = 16;
+ break;
+ case PM_TabBarTabHSpace:
+ // Contents may clip out horizontally if we don't some extra pixels here or
+ // in sizeFromContents for CT_TabBarTab.
+ if (!option)
+ break;
+ return static_cast<int>(option->fontMetrics.height() * Phantom::TabBar_HPaddingFontRatio)
+ + static_cast<int>(Phantom::dpiScaled(4));
+ case PM_TabBarTabVSpace:
+ if (!option)
+ break;
+ return static_cast<int>(option->fontMetrics.height() * Phantom::TabBar_VPaddingFontRatio)
+ + static_cast<int>(Phantom::dpiScaled(2));
+ case PM_TabBarTabOverlap:
+ val = 1;
+ break;
+ case PM_TabBarBaseOverlap:
+ val = 2;
+ break;
+ case PM_TabBarIconSize: {
+ if (!widget)
+ break;
+ return widget->fontMetrics().height();
+ }
+ case PM_TabBarTabShiftVertical: {
+ val = Phantom::TabBar_InctiveVShift;
+ break;
+ }
+ case PM_SubMenuOverlap:
+ val = 0;
+ break;
+ case PM_DockWidgetHandleExtent:
+ case PM_SplitterWidth:
+ val = 5;
+ break;
+ case PM_IndicatorHeight:
+ case PM_IndicatorWidth:
+ case PM_ExclusiveIndicatorHeight:
+ case PM_ExclusiveIndicatorWidth:
+ if (option)
+ return option->fontMetrics.height();
+ if (widget)
+ return widget->fontMetrics().height();
+ val = 14;
+ break;
+ case PM_ScrollView_ScrollBarOverlap:
+ case PM_ScrollView_ScrollBarSpacing:
+ val = 0;
+ break;
+ case PM_TreeViewIndentation: {
+ if (widget)
+ return widget->fontMetrics().height();
+ val = 12;
+ break;
+ }
+ default:
+ val = QCommonStyle::pixelMetric(metric, option, widget);
+ }
+ return Phantom::dpiScaled(val);
+}
+
+QSize BaseStyle::sizeFromContents(ContentsType type,
+ const QStyleOption* option,
+ const QSize& size,
+ const QWidget* widget) const
+{
+ namespace Ph = Phantom;
+ // Cases which do not rely on the parent class to do any work
+ switch (type) {
+ case CT_RadioButton:
+ case CT_CheckBox: {
+ auto btn = qstyleoption_cast<const QStyleOptionButton*>(option);
+ if (!btn)
+ break;
+ bool isRadio = type == CT_RadioButton;
+ int w = proxy()->pixelMetric(isRadio ? PM_ExclusiveIndicatorWidth : PM_IndicatorWidth, btn, widget);
+ int h = proxy()->pixelMetric(isRadio ? PM_ExclusiveIndicatorHeight : PM_IndicatorHeight, btn, widget);
+ int margins = 0;
+ if (!btn->icon.isNull() || !btn->text.isEmpty())
+ margins =
+ proxy()->pixelMetric(isRadio ? PM_RadioButtonLabelSpacing : PM_CheckBoxLabelSpacing, option, widget);
+ return QSize(size.width() + w + margins, qMax(size.height(), h));
+ }
+ case CT_MenuBarItem: {
+ int fontHeight = option ? option->fontMetrics.height() : size.height();
+ int w = static_cast<int>(fontHeight * Ph::MenuBar_HorizontalPaddingFontRatio);
+ int h = static_cast<int>(fontHeight * Ph::MenuBar_VerticalPaddingFontRatio);
+ int line = Ph::dpiScaled(1);
+ return QSize(size.width() + w * 2, size.height() + h * 2 + line);
+ }
+ case CT_MenuItem: {
+ auto menuItem = qstyleoption_cast<const QStyleOptionMenuItem*>(option);
+ if (!menuItem)
+ return size;
+ bool hasTabChar = menuItem->text.contains(QLatin1Char('\t'));
+ bool hasSubMenu = menuItem->menuItemType == QStyleOptionMenuItem::SubMenu;
+ bool isSeparator = menuItem->menuItemType == QStyleOptionMenuItem::Separator;
+ int fontMetricsHeight = -1;
+ // See notes at CE_MenuItem and SH_ComboBox_Popup for more information
+ if (Ph::UseQMenuForComboBoxPopup && qobject_cast<const QComboBox*>(widget)) {
+ if (!widget->testAttribute(Qt::WA_SetFont))
+ fontMetricsHeight = QFontMetrics(qApp->font("QMenu")).height();
+ }
+ if (fontMetricsHeight == -1) {
+ fontMetricsHeight = option->fontMetrics.height();
+ }
+ auto metrics = Ph::MenuItemMetrics::ofFontHeight(fontMetricsHeight);
+ // Incoming width is the sum of the visual widths of the main item text and
+ // the mnemonic text (if any). To this width we will add the widths of the
+ // other features for this menu item -- the icon/checkbox, spacing between
+ // icon/text/mnemonic, etc. For cases like separators without any text, we
+ // may disregard the width.
+ //
+ // Height is the text height, probably.
+ int w = size.width();
+ // Frame
+ w += metrics.frameThickness * 2;
+ // Left margins don't depend on whether or not we have a submenu arrow.
+ // Calculating the right margins requires knowing whether or not the menu
+ // item has a submenu arrow.
+ w += metrics.leftMargin;
+ // Phantom treats every menu item with the same space on the left for a
+ // check mark, even if it doesn't have the checkable property.
+ w += metrics.checkWidth + metrics.checkRightSpace;
+
+ if (!menuItem->icon.isNull()) {
+ // Phantom disregards any user-specified icon sizing at the moment.
+ w += metrics.fontHeight;
+ w += metrics.iconRightSpace;
+ }
+
+ // Tab character is used for separating the shortcut text
+ if (hasTabChar)
+ w += metrics.mnemonicSpace;
+ if (hasSubMenu)
+ w += metrics.arrowSpace + metrics.arrowWidth + metrics.rightMarginForArrow;
+ else
+ w += metrics.rightMarginForText;
+ int h;
+ if (isSeparator) {
+ h = metrics.separatorHeight;
+ } else {
+ h = metrics.totalHeight;
+ }
+ if (!menuItem->icon.isNull()) {
+ if (auto combo = qobject_cast<const QComboBox*>(widget)) {
+ h = qMax(combo->iconSize().height() + 2, h);
+ }
+ }
+ QSize sz;
+ sz.setWidth(qMax<int>(w, Ph::dpiScaled(Ph::MenuMinimumWidth)));
+ sz.setHeight(h);
+ return sz;
+ }
+ case CT_Menu: {
+ if (!Ph::MenuExtraBottomMargin || !option || !widget)
+ break;
+ // Trick the QMenu into putting a margin only at the bottom by adding extra
+ // height to the contents size. We only want to add this tricky space if
+ // there is at least more than 1 item in the menu.
+ const auto acts = widget->actions();
+ if (acts.count() < 2)
+ break;
+ // We only want to add the tricky space if there's at least 1 separator,
+ // otherwise it looks weird.
+ bool anySeps = false;
+ for (auto act : acts) {
+ if (act->isSeparator()) {
+ anySeps = true;
+ break;
+ }
+ }
+ if (!anySeps)
+ break;
+ int fheight = option->fontMetrics.height();
+ int vmargin = static_cast<int>(fheight * Ph::MenuItem_SeparatorHeightFontRatio) / 2;
+ QSize sz = size;
+ sz.setHeight(sz.height() + vmargin);
+ return sz;
+ }
+ case CT_TabBarTab: {
+ // Placeholder in case we change this in the future
+ return size;
+ }
+ case CT_Slider: {
+ QSize sz = size;
+ if (qobject_cast<const QSlider*>(widget)->orientation() == Qt::Horizontal) {
+ sz.setHeight(sz.height() + PM_SliderTickmarkOffset);
+ } else {
+ sz.setWidth(sz.width() + PM_SliderTickmarkOffset);
+ }
+ return sz;
+ }
+ case CT_GroupBox: {
+ // This doesn't seem to get used except once by QGroupBox for
+ // minimumSizeHint(). After that, the sizing/layout calculations seem to
+ // use the rects given by subControlRect().
+ auto opt = qstyleoption_cast<const QStyleOptionGroupBox*>(option);
+ if (!opt)
+ break;
+ // Checkbox and text height already accounted for, but margin between text
+ // and frame isn't.
+ int xadd = 0;
+ int yadd = 0;
+ if (opt->subControls & (SC_GroupBoxCheckBox | SC_GroupBoxLabel)) {
+ int fontHeight = option->fontMetrics.height();
+ yadd += static_cast<int>(fontHeight * Phantom::GroupBox_LabelBottomMarginFontRatio);
+ }
+ // We can test for the frame in general, but unfortunately testing to see
+ // if it's the 1-line "flat" style or 4-line box/rect "anything else" style
+ // doesn't seem to be possible here, only when painting.
+ if (opt->subControls & SC_GroupBoxFrame) {
+ xadd += 2;
+ yadd += 2;
+ }
+ return QSize(size.width() + xadd, size.height() + yadd);
+ }
+ case CT_ItemViewItem: {
+ auto vopt = qstyleoption_cast<const QStyleOptionViewItem*>(option);
+ if (!vopt)
+ break;
+ QSize sz = QCommonStyle::sizeFromContents(type, option, size, widget);
+ sz += QSize(0, Phantom::DefaultFrameWidth);
+ // QCommonStyle has a bunch of complicated logic for laying out/calculating
+ // rects of view items, which is locked behind a private data guy. In
+ // sizeFromContents for CT_ItemViewItem, it unions all of the item row's
+ // rects together and then, if the decoration height is exactly the same as
+ // the row height, it adds 2 pixels (not dpi scaled) to the height. The
+ // comment says it's to prevent "icons from overlapping" but I have no idea
+ // how that's supposed to help. And we don't necessarily want those extra 2
+ // pixels. Anyway, I don't want to copy and paste all of that code into
+ // Phantom and then maintain it. So when Phantom is in the mode where we're
+ // basing the item view decoration sizes off of the font size, we'll just
+ // take a guess when QCommonStyle has added 2 to the height (because the
+ // row height and decoration height are both the font height), and
+ // re-remove those two pixels.
+#if 1
+ if (Phantom::ItemView_UseFontHeightForDecorationSize) {
+ int fh = vopt->fontMetrics.height();
+ if (sz.height() == fh + 2 && vopt->decorationSize.height() == fh) {
+ sz.setHeight(fh);
+ }
+ }
+#endif
+ return sz;
+ }
+ case CT_HeaderSection: {
+ auto hdr = qstyleoption_cast<const QStyleOptionHeader*>(option);
+ if (!hdr)
+ break;
+ // This is pretty crummy. Should also check if we need multi-line support
+ // or not.
+ bool nullIcon = hdr->icon.isNull();
+ int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, hdr, widget);
+ int iconSize = nullIcon ? 0 : option->fontMetrics.height();
+ QSize txt = hdr->fontMetrics.size(Qt::TextSingleLine | Qt::TextBypassShaping, hdr->text);
+ QSize sz;
+ sz.setHeight(margin + qMax(iconSize, txt.height()) + margin);
+ sz.setWidth((nullIcon ? 0 : margin) + iconSize + (hdr->text.isNull() ? 0 : margin) + txt.width() + margin);
+ if (hdr->sortIndicator != QStyleOptionHeader::None) {
+ if (hdr->orientation == Qt::Horizontal)
+ sz.rwidth() += sz.height() + margin;
+ else
+ sz.rheight() += sz.width() + margin;
+ }
+ return sz;
+ }
+ default:
+ break;
+ }
+
+ // Cases which modify the size given by the parent class
+ QSize newSize = QCommonStyle::sizeFromContents(type, option, size, widget);
+ switch (type) {
+ case CT_PushButton: {
+ auto pbopt = qstyleoption_cast<const QStyleOptionButton*>(option);
+ if (!pbopt || pbopt->text.isEmpty())
+ break;
+ int hpad = static_cast<int>(pbopt->fontMetrics.height() * Phantom::PushButton_HorizontalPaddingFontHeightRatio);
+ newSize.rwidth() += hpad * 2;
+ if (widget && qobject_cast<const QDialogButtonBox*>(widget->parent())) {
+ int dialogButtonMinWidth = Phantom::dpiScaled(80);
+ newSize.rwidth() = qMax(newSize.width(), dialogButtonMinWidth);
+ }
+ break;
+ }
+ case CT_ToolButton:
+#if defined(Q_OS_MACOS)
+ newSize += QSize(Ph::dpiScaled(6 + Phantom::DefaultFrameWidth), Ph::dpiScaled(6 + Phantom::DefaultFrameWidth));
+#elif defined(Q_OS_WIN)
+ newSize += QSize(Ph::dpiScaled(4 + Phantom::DefaultFrameWidth), Ph::dpiScaled(4 + Phantom::DefaultFrameWidth));
+#else
+ newSize += QSize(Ph::dpiScaled(3 + Phantom::DefaultFrameWidth), Ph::dpiScaled(3 + Phantom::DefaultFrameWidth));
+#endif
+ break;
+ case CT_ComboBox: {
+ newSize += QSize(0, Ph::dpiScaled(4 + Phantom::DefaultFrameWidth));
+ auto cb = qstyleoption_cast<const QStyleOptionComboBox*>(option);
+ // Non-editable combo boxes have some extra padding on the left side,
+ // similar to push buttons. We should account for that here to avoid text
+ // being clipped off.
+ if (cb) {
+ int pad = 0;
+ if (cb->editable) {
+ pad = Ph::dpiScaled(Ph::LineEdit_ContentsHPad);
+ } else {
+ pad = Ph::dpiScaled(Ph::ComboBox_NonEditable_ContentsHPad);
+ }
+ newSize.rwidth() += pad * 2;
+ }
+ break;
+ }
+ case CT_LineEdit: {
+ newSize += QSize(0, 4);
+ int pad = Ph::dpiScaled(Ph::LineEdit_ContentsHPad);
+ newSize.rwidth() += pad * 2;
+ break;
+ }
+ case CT_SpinBox:
+ // No changes needed
+ break;
+ case CT_SizeGrip:
+ newSize += QSize(4, 4);
+ break;
+ case CT_MdiControls:
+ newSize -= QSize(1, 0);
+ break;
+ default:
+ break;
+ }
+ return newSize;
+}
+
+void BaseStyle::polish(QApplication* app)
+{
+ if (!app) {
+ return;
+ }
+
+ Q_INIT_RESOURCE(styles);
+
+ QString stylesheet;
+ QFile baseStylesheetFile(":/styles/base/basestyle.qss");
+ if (baseStylesheetFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ stylesheet = baseStylesheetFile.readAll();
+ baseStylesheetFile.close();
+ } else {
+ qWarning("Failed to load base theme stylesheet.");
+ }
+
+ stylesheet.append(getAppStyleSheet());
+ app->setStyleSheet(stylesheet);
+ QCommonStyle::polish(app);
+}
+
+QRect BaseStyle::subControlRect(ComplexControl control,
+ const QStyleOptionComplex* option,
+ SubControl subControl,
+ const QWidget* widget) const
+{
+ namespace Ph = Phantom;
+ QRect rect = QCommonStyle::subControlRect(control, option, subControl, widget);
+ switch (control) {
+ case CC_Slider: {
+ auto slider = qstyleoption_cast<const QStyleOptionSlider*>(option);
+ if (!slider)
+ break;
+ int tickSize = proxy()->pixelMetric(PM_SliderTickmarkOffset, option, widget);
+ switch (subControl) {
+ case SC_SliderHandle: {
+ if (slider->orientation == Qt::Horizontal) {
+ rect.setHeight(proxy()->pixelMetric(PM_SliderThickness));
+ rect.setWidth(proxy()->pixelMetric(PM_SliderLength));
+ int centerY = slider->rect.center().y() - rect.height() / 2;
+ if (slider->tickPosition & QSlider::TicksAbove)
+ centerY += tickSize;
+ if (slider->tickPosition & QSlider::TicksBelow)
+ centerY -= tickSize;
+ rect.moveTop(centerY);
+ } else {
+ rect.setWidth(proxy()->pixelMetric(PM_SliderThickness));
+ rect.setHeight(proxy()->pixelMetric(PM_SliderLength));
+ int centerX = slider->rect.center().x() - rect.width() / 2;
+ if (slider->tickPosition & QSlider::TicksAbove)
+ centerX += tickSize;
+ if (slider->tickPosition & QSlider::TicksBelow)
+ centerX -= tickSize;
+ rect.moveLeft(centerX);
+ }
+ break;
+ }
+ case SC_SliderGroove: {
+ QPoint grooveCenter = slider->rect.center();
+ const int grooveThickness = Ph::dpiScaled(7);
+ if (slider->orientation == Qt::Horizontal) {
+ rect.setHeight(grooveThickness);
+ if (slider->tickPosition & QSlider::TicksAbove)
+ grooveCenter.ry() += tickSize;
+ if (slider->tickPosition & QSlider::TicksBelow)
+ grooveCenter.ry() -= tickSize;
+ } else {
+ rect.setWidth(grooveThickness);
+ if (slider->tickPosition & QSlider::TicksAbove)
+ grooveCenter.rx() += tickSize;
+ if (slider->tickPosition & QSlider::TicksBelow)
+ grooveCenter.rx() -= tickSize;
+ }
+ rect.moveCenter(grooveCenter);
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ case CC_SpinBox: {
+ auto spinbox = qstyleoption_cast<const QStyleOptionSpinBox*>(option);
+ if (!spinbox)
+ break;
+ // Some leftover Fusion code here. Should clean up this mess.
+ int center = spinbox->rect.height() / 2;
+ int fw = spinbox->frame ? 1 : 0;
+ int y = fw;
+ const int buttonWidth = static_cast<int>(Ph::dpiScaled(Ph::SpinBox_ButtonWidth)) + 2;
+ int x, lx, rx;
+ x = spinbox->rect.width() - y - buttonWidth + 2;
+ lx = fw;
+ rx = x - fw;
+ switch (subControl) {
+ case SC_SpinBoxUp:
+ if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons)
+ return {};
+ rect = QRect(x, fw, buttonWidth, center - fw);
+ break;
+ case SC_SpinBoxDown:
+ if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons)
+ return QRect();
+
+ rect = QRect(x, center, buttonWidth, spinbox->rect.bottom() - center - fw + 1);
+ break;
+ case SC_SpinBoxEditField:
+ if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons) {
+ rect = QRect(lx, fw, spinbox->rect.width() - 2 * fw, spinbox->rect.height() - 2 * fw);
+ } else {
+ rect = QRect(lx, fw, rx - qMax(fw - 1, 0), spinbox->rect.height() - 2 * fw);
+ }
+ break;
+ case SC_SpinBoxFrame:
+ rect = spinbox->rect;
+ break;
+ default:
+ break;
+ }
+ rect = visualRect(spinbox->direction, spinbox->rect, rect);
+ break;
+ }
+ case CC_GroupBox: {
+ auto groupBox = qstyleoption_cast<const QStyleOptionGroupBox*>(option);
+ if (!groupBox)
+ break;
+ switch (subControl) {
+ case SC_GroupBoxFrame:
+ case SC_GroupBoxContents: {
+ QRect r = option->rect;
+ if (groupBox->subControls & (SC_GroupBoxLabel | SC_GroupBoxCheckBox)) {
+ int fontHeight = option->fontMetrics.height();
+ int topMargin = qMax(pixelMetric(PM_ExclusiveIndicatorHeight), fontHeight);
+ topMargin += static_cast<int>(fontHeight * Ph::GroupBox_LabelBottomMarginFontRatio);
+ r.setTop(r.top() + topMargin);
+ }
+ if (subControl == SC_GroupBoxContents && groupBox->subControls & SC_GroupBoxFrame) {
+ // Testing against groupBox->features for the frame type doesn't seem
+ // to work here.
+ r.adjust(1, 1, -1, -1);
+ }
+ return r;
+ }
+ case SC_GroupBoxCheckBox:
+ case SC_GroupBoxLabel: {
+ // Accurate height doesn't matter -- the other group box style
+ // implementations also fail with multi-line or too-tall text.
+ int textHeight = option->fontMetrics.height();
+ // width()/horizontalAdvance() is faster than size() and good enough for
+ // us, since we only support a single line of text here anyway.
+ int textWidth = Phantom::fontMetricsWidth(option->fontMetrics, groupBox->text);
+ int indicatorWidth = proxy()->pixelMetric(PM_IndicatorWidth, option, widget);
+ int indicatorHeight = proxy()->pixelMetric(PM_IndicatorHeight, option, widget);
+ int margin = 0;
+ int indicatorRightSpace = textHeight / 3;
+ int contentWidth = textWidth;
+ if (option->subControls & QStyle::SC_GroupBoxCheckBox) {
+ contentWidth += indicatorWidth + indicatorRightSpace;
+ }
+ int x = margin;
+ int y = 0;
+ switch (groupBox->textAlignment & Qt::AlignHorizontal_Mask) {
+ case Qt::AlignHCenter:
+ x += (option->rect.width() - contentWidth) / 2;
+ break;
+ case Qt::AlignRight:
+ x += option->rect.width() - contentWidth;
+ break;
+ default:
+ break;
+ }
+ int w, h;
+ if (subControl == SC_GroupBoxCheckBox) {
+ w = indicatorWidth;
+ h = indicatorHeight;
+ if (textHeight > indicatorHeight) {
+ y = (textHeight - indicatorHeight) / 2;
+ }
+ } else {
+ w = contentWidth;
+ h = textHeight;
+ if (option->subControls & QStyle::SC_GroupBoxCheckBox) {
+ x += indicatorWidth + indicatorRightSpace;
+ w -= indicatorWidth + indicatorRightSpace;
+ }
+ }
+ return visualRect(option->direction, option->rect, QRect(x, y, w, h));
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ case CC_ComboBox: {
+ auto cb = qstyleoption_cast<const QStyleOptionComboBox*>(option);
+ if (!cb)
+ return QRect();
+ int frame = cb->frame ? proxy()->pixelMetric(PM_ComboBoxFrameWidth, cb, widget) : 0;
+ QRect r = option->rect;
+ r.adjust(frame, frame, -frame, -frame);
+ int dim = qMin(r.width(), r.height());
+ if (dim < 1)
+ return QRect();
+ switch (subControl) {
+ case SC_ComboBoxFrame:
+ return cb->rect;
+ case SC_ComboBoxArrow: {
+ QRect r0 = r;
+ r0.setX((r0.x() + r0.width()) - dim + 1);
+ return visualRect(option->direction, option->rect, r0);
+ }
+ case SC_ComboBoxEditField: {
+ // Add extra padding if not editable
+ int pad = 0;
+ if (cb->editable) {
+ // Line edit padding already added
+ } else {
+ pad = Ph::dpiScaled(Ph::ComboBox_NonEditable_ContentsHPad);
+ }
+ r.adjust(pad, 0, -dim, 0);
+ return visualRect(option->direction, option->rect, r);
+ }
+ case SC_ComboBoxListBoxPopup: {
+ return cb->rect;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ case CC_TitleBar: {
+ auto tb = qstyleoption_cast<const QStyleOptionTitleBar*>(option);
+ if (!tb)
+ break;
+ SubControl sc = subControl;
+ QRect& ret = rect;
+ const int indent = 3;
+ const int controlTopMargin = 3;
+ const int controlBottomMargin = 3;
+ const int controlWidthMargin = 2;
+ const int controlHeight = tb->rect.height() - controlTopMargin - controlBottomMargin;
+ const int delta = controlHeight + controlWidthMargin;
+ int offset = 0;
+ bool isMinimized = tb->titleBarState & Qt::WindowMinimized;
+ bool isMaximized = tb->titleBarState & Qt::WindowMaximized;
+ switch (sc) {
+ case SC_TitleBarLabel:
+ if (tb->titleBarFlags & (Qt::WindowTitleHint | Qt::WindowSystemMenuHint)) {
+ ret = tb->rect;
+ if (tb->titleBarFlags & Qt::WindowSystemMenuHint)
+ ret.adjust(delta, 0, -delta, 0);
+ if (tb->titleBarFlags & Qt::WindowMinimizeButtonHint)
+ ret.adjust(0, 0, -delta, 0);
+ if (tb->titleBarFlags & Qt::WindowMaximizeButtonHint)
+ ret.adjust(0, 0, -delta, 0);
+ if (tb->titleBarFlags & Qt::WindowShadeButtonHint)
+ ret.adjust(0, 0, -delta, 0);
+ if (tb->titleBarFlags & Qt::WindowContextHelpButtonHint)
+ ret.adjust(0, 0, -delta, 0);
+ }
+ break;
+ case SC_TitleBarContextHelpButton:
+ if (tb->titleBarFlags & Qt::WindowContextHelpButtonHint)
+ offset += delta;
+ Q_FALLTHROUGH();
+ case SC_TitleBarMinButton:
+ if (!isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint))
+ offset += delta;
+ else if (sc == SC_TitleBarMinButton)
+ break;
+ Q_FALLTHROUGH();
+ case SC_TitleBarNormalButton:
+ if (isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint))
+ offset += delta;
+ else if (isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint))
+ offset += delta;
+ else if (sc == SC_TitleBarNormalButton)
+ break;
+ Q_FALLTHROUGH();
+ case SC_TitleBarMaxButton:
+ if (!isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint))
+ offset += delta;
+ else if (sc == SC_TitleBarMaxButton)
+ break;
+ Q_FALLTHROUGH();
+ case SC_TitleBarShadeButton:
+ if (!isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint))
+ offset += delta;
+ else if (sc == SC_TitleBarShadeButton)
+ break;
+ Q_FALLTHROUGH();
+ case SC_TitleBarUnshadeButton:
+ if (isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint))
+ offset += delta;
+ else if (sc == SC_TitleBarUnshadeButton)
+ break;
+ Q_FALLTHROUGH();
+ case SC_TitleBarCloseButton:
+ if (tb->titleBarFlags & Qt::WindowSystemMenuHint)
+ offset += delta;
+ else if (sc == SC_TitleBarCloseButton)
+ break;
+ ret.setRect(
+ tb->rect.right() - indent - offset, tb->rect.top() + controlTopMargin, controlHeight, controlHeight);
+ break;
+ case SC_TitleBarSysMenu:
+ if (tb->titleBarFlags & Qt::WindowSystemMenuHint) {
+ ret.setRect(tb->rect.left() + controlWidthMargin + indent,
+ tb->rect.top() + controlTopMargin,
+ controlHeight,
+ controlHeight);
+ }
+ break;
+ default:
+ break;
+ }
+ ret = visualRect(tb->direction, tb->rect, ret);
+ break;
+ }
+ default:
+ break;
+ }
+
+ return rect;
+}
+
+QRect BaseStyle::itemPixmapRect(const QRect& r, int flags, const QPixmap& pixmap) const
+{
+ return QCommonStyle::itemPixmapRect(r, flags, pixmap);
+}
+
+void BaseStyle::drawItemPixmap(QPainter* painter, const QRect& rect, int alignment, const QPixmap& pixmap) const
+{
+ QCommonStyle::drawItemPixmap(painter, rect, alignment, pixmap);
+}
+
+QStyle::SubControl BaseStyle::hitTestComplexControl(ComplexControl cc,
+ const QStyleOptionComplex* opt,
+ const QPoint& pt,
+ const QWidget* w) const
+{
+ return QCommonStyle::hitTestComplexControl(cc, opt, pt, w);
+}
+
+QPixmap BaseStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap& pixmap, const QStyleOption* opt) const
+{
+ // Default icon highlight is way too subtle
+ if (iconMode == QIcon::Selected) {
+ QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied);
+ QPainter painter(&img);
+
+ painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);
+
+ QColor color =
+ Phantom::DeriveColors::adjustLightness(opt->palette.color(QPalette::Normal, QPalette::Highlight), .25);
+ color.setAlphaF(0.25);
+ painter.fillRect(0, 0, img.width(), img.height(), color);
+
+ painter.end();
+
+ return QPixmap::fromImage(img);
+ }
+ return QCommonStyle::generatedIconPixmap(iconMode, pixmap, opt);
+}
+
+int BaseStyle::styleHint(StyleHint hint,
+ const QStyleOption* option,
+ const QWidget* widget,
+ QStyleHintReturn* returnData) const
+{
+ switch (hint) {
+ case SH_Slider_SnapToValue:
+ case SH_PrintDialog_RightAlignButtons:
+ case SH_FontDialog_SelectAssociatedText:
+ case SH_ComboBox_ListMouseTracking:
+ case SH_Slider_StopMouseOverSlider:
+ case SH_ScrollBar_MiddleClickAbsolutePosition:
+ case SH_TitleBar_AutoRaise:
+ case SH_TitleBar_NoBorder:
+ case SH_ItemView_ArrowKeysNavigateIntoChildren:
+ case SH_ItemView_ChangeHighlightOnFocus:
+ case SH_MenuBar_MouseTracking:
+ case SH_Menu_MouseTracking:
+ return 1;
+ case SH_Menu_SupportsSections:
+ return 0;
+#ifndef Q_OS_MAC
+ case SH_MenuBar_AltKeyNavigation:
+ return 1;
+#endif
+#if defined(QT_PLATFORM_UIKIT)
+ case SH_ComboBox_UseNativePopup:
+ return 1;
+#endif
+ case SH_ItemView_ShowDecorationSelected:
+ // QWindowsStyle does this as well -- QCommonStyle seems to have some
+ // internal confusion buried within its private implementation of laying
+ // out and drawing item views where it can't keep track of what's
+ // considered a decoration and what's not. For tree views, if you give 0
+ // for ShowDecorationSelected, it applies only to the disclosure indicator
+ // and not to the QIcon/pixmap that might be present for the item. So
+ // selecting an item in a tree view will have the selection color drawn
+ // underneath the icon/pixmap, but not the disclosure indicator. However,
+ // in list views, if you give 0 for ShowDecorationSelected, it will *not*
+ // draw the selection color underneath the icon/pixmap. There's no way to
+ // access this internal logic in QCommonStyle without fully reimplementing
+ // the huge mass of stuff for item view layout and drawing. Therefore, the
+ // best we can do is at least try to get consistent behavior: if it's a
+ // list view, just always return 1 for ShowDecorationSelected.
+ if (!Phantom::ShowItemViewDecorationSelected && qobject_cast<const QListView*>(widget))
+ return 1;
+ return Phantom::ShowItemViewDecorationSelected;
+ case SH_ItemView_MovementWithoutUpdatingSelection:
+ return 1;
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0))
+ case SH_ItemView_ScrollMode:
+ return QAbstractItemView::ScrollPerPixel;
+#endif
+ case SH_ScrollBar_ContextMenu:
+#ifdef Q_OS_MAC
+ return 0;
+#else
+ return 1;
+#endif
+ // Some Linux distros might want to enable this, but it doesn't behave very
+ // consistently with varied QPalettes, depending on how the QPA and icons
+ // deal with both light and dark themes. It might seem weird to just disable
+ // this, but none of (Mac, Windows, BeOS/Haiku) show icons in dialog buttons,
+ // and the results on Linux are generally pretty messy -- not sure why it's
+ // historically been the default, especially when other button types
+ // generally don't have any icons.
+ case SH_DialogButtonBox_ButtonsHaveIcons:
+ return 0;
+ case SH_ScrollBar_Transient:
+ return 1;
+ case SH_EtchDisabledText:
+ case SH_DitherDisabledText:
+ case SH_ToolBox_SelectedPageTitleBold:
+ case SH_Menu_AllowActiveAndDisabled:
+ case SH_MainWindow_SpaceBelowMenuBar:
+ case SH_MessageBox_CenterButtons:
+ case SH_RubberBand_Mask:
+ case SH_ScrollView_FrameOnlyAroundContents:
+ return 0;
+ case SH_ComboBox_Popup: {
+ return Phantom::UseQMenuForComboBoxPopup;
+ // Fusion did this, but we don't because of font bugs (especially in high
+ // DPI) with the QMenu that the combo box will create instead of a dropdown
+ // view. See notes in CE_MenuItem for more details.
+ if (auto cmb = qstyleoption_cast<const QStyleOptionComboBox*>(option))
+ return !cmb->editable;
+ return 0;
+ }
+ case SH_Table_GridLineColor: {
+ using namespace Phantom::SwatchColors;
+ namespace Ph = Phantom;
+ if (!option)
+ return 0;
+ auto ph_swatchPtr = Ph::getCachedSwatchOfQPalette(&d->swatchCache, &d->headSwatchFastKey, option->palette);
+ const Ph::PhSwatch& swatch = *ph_swatchPtr.data();
+ // Qt code in table views for drawing grid lines is broken. See case for
+ // CE_ItemViewItem painting for more information.
+ return static_cast<int>(swatch.color(S_base_divider).rgb());
+ }
+ case SH_MessageBox_TextInteractionFlags:
+ return Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse;
+ case SH_WizardStyle:
+ return QWizard::ClassicStyle;
+ case SH_Menu_SubMenuPopupDelay:
+ // Returning 0 will break sloppy submenus even if they're enabled
+ return 10;
+ case SH_Menu_SloppySubMenus:
+ return true;
+ case SH_Menu_SubMenuSloppyCloseTimeout:
+ return 500;
+ case SH_Menu_SubMenuDontStartSloppyOnLeave:
+ return 1;
+ case SH_Menu_SubMenuSloppySelectOtherActions:
+ return 1;
+ case SH_Menu_SubMenuUniDirection:
+ return 1;
+ case SH_Menu_SubMenuUniDirectionFailCount:
+ return 1;
+ case SH_Menu_SubMenuResetWhenReenteringParent:
+ return 0;
+#ifdef Q_OS_MAC
+ case SH_Menu_FlashTriggeredItem:
+ return 1;
+ case SH_Menu_FadeOutOnHide:
+ return 0;
+#endif
+ case SH_WindowFrame_Mask:
+ return 0;
+ case SH_UnderlineShortcut: {
+ return false;
+ }
+ case SH_Widget_Animate:
+ return 1;
+ default:
+ break;
+ }
+ return QCommonStyle::styleHint(hint, option, widget, returnData);
+}
+
+QRect BaseStyle::subElementRect(SubElement sr, const QStyleOption* opt, const QWidget* w) const
+{
+ switch (sr) {
+ case SE_ProgressBarLabel:
+ case SE_ProgressBarContents:
+ case SE_ProgressBarGroove:
+ return opt->rect;
+ case SE_PushButtonFocusRect: {
+ QRect r = QCommonStyle::subElementRect(sr, opt, w);
+ r.adjust(0, 1, 0, -1);
+ return r;
+ }
+ case SE_DockWidgetTitleBarText: {
+ auto titlebar = qstyleoption_cast<const QStyleOptionDockWidget*>(opt);
+ if (!titlebar)
+ break;
+ QRect r = QCommonStyle::subElementRect(sr, opt, w);
+ bool verticalTitleBar = titlebar->verticalTitleBar;
+ if (verticalTitleBar) {
+ r.adjust(0, 0, 0, -4);
+ } else {
+ if (opt->direction == Qt::LeftToRight)
+ r.adjust(4, 0, 0, 0);
+ else
+ r.adjust(0, 0, -4, 0);
+ }
+ return r;
+ }
+ case SE_TreeViewDisclosureItem: {
+ if (Phantom::BranchesOnEdge) {
+ // Shove it all the way to the left (or right) side, probably outside of
+ // the rect it gave us. Old-school.
+ QRect rect = opt->rect;
+ if (opt->direction != Qt::RightToLeft) {
+ rect.moveLeft(0);
+ if (rect.width() < rect.height())
+ rect.setWidth(rect.height());
+ } else {
+ // todo
+ }
+ return rect;
+ }
+ break;
+ }
+ case SE_LineEditContents: {
+ QRect r = QCommonStyle::subElementRect(sr, opt, w);
+ int pad = Phantom::dpiScaled(Phantom::LineEdit_ContentsHPad);
+ return r.adjusted(pad, 0, -pad, 0);
+ }
+ default:
+ break;
+ }
+ return QCommonStyle::subElementRect(sr, opt, w);
+}
+
+// Table header layout reference
+// -----------------------------
+//
+// begin: QStyleOptionHeader::Beginning;
+// mid: QStyleOptionHeader::Middle;
+// end: QStyleOptionHeader::End;
+// one: QStyleOptionHeader::OnlyOneSection;
+// one*:
+// This is specified as QStyleOptionHeader::OnlyOneSection, but the call to
+// drawControl(CE_HeaderSection...) is being performed by an instance of
+// QTableCornerButton, defined in qtableview.cpp as a subclass of
+// QAbstractButton. Only table views can have these corner buttons, and they
+// only appear if there are both at least 1 column and 1 row visible.
+//
+// Configuration A: A table view with both columns and rows
+//
+// Configuration B: A list view, or a tree view, or a table view with no rows
+// in the data or all rows hidden, such that the corner button is also made
+// hidden.
+//
+// Configuration C: A table view with no columns in the data or all columns
+// hidden, such that the corner button is also made hidden.
+//
+// Configuration A, Left-to-right, 4x4
+// [ one* ][ begin ][ mid ][ mid ][ end ]
+// [ begin ]
+// [ mid ]
+// [ mid ]
+// [ end ]
+//
+// Configuration A, Left-to-right, 2x2
+// [ one* ][ begin ][ end ]
+// [ begin ]
+// [ end ]
+//
+// Configuration A, Left-to-right, 1x1
+// [ one* ][ one ]
+// [ one ]
+//
+// Configuration A, Right-to-left, 4x4
+// [ begin ][ mid ][ mid ][ end ][ one* ]
+// [ begin ]
+// [ mid ]
+// [ mid ]
+// [ end ]
+//
+// Configuration A, Right-to-left, 2x2
+// [ begin ][ end ][ one* ]
+// [ begin ]
+// [ end ]
+//
+// Configuration A, Right-to-left, 1x1
+// [ one ][ one* ]
+// [ one ]
+//
+// Configuration B, Left-to-right and right-to-left, 4 columns (table view:
+// 4 columns with 0 rows, list/tree view: 4 columns, rows count doesn't matter):
+// [ begin ][ mid ][ mid ][ end ]
+//
+// Configuration B, Left-to-right and right-to-left, 2 columns (table view:
+// 2 columns with 0 rows, list/tree view: 2 columns, rows count doesn't matter):
+// [ begin ][ end ]
+//
+// Configuration B, Left-to-right and right-to-left, 1 column (table view:
+// 1 column with 0 rows, list view: 1 column, rows count doesn't matter):
+// [ one ]
+//
+// Configuration C, left-to-right and right-to-left, table view with no columns
+// and 4 rows:
+// [ begin ]
+// [ mid ]
+// [ mid ]
+// [ end ]
+//
+// Configuration C, left-to-right and right-to-left, table view with no columns
+// and 2 rows:
+// [ begin ]
+// [ end ]
+//
+// Configuration C, left-to-right and right-to-left, table view with no columns
+// and 1 row:
+// [ one ]