- Allow switching between themes without restart (except classic) - Rework icon loading and recolouring logic to react to theme changes - Automatically react to light/dark theme change - Remove explicit selection of monochrome tray icon variant (selected automatically now) - Update theme background colours for Big Sur - Update application icon to match Big Sur HIG The tray icon doesn't respond perfectly to theme changes yet on Big Sur, since we need different icons for dark and light theme and cannot simply let the OS recolour the icon for us (we do that, too, but only as an additional fallback). At the moment, there is no signal to listen to that would allow this. This patch adds a few generic methods to OSUtils for detecting and communicating theme changes, which are only stubs for Windows and Linux at the moment and need to be implemented in future commits. Fixes #4933 Fixes #5349
4860 lines
217 KiB
C++
4860 lines
217 KiB
C++
/*
|
|
* 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>
|
|
|
|
#ifdef Q_OS_MACOS
|
|
#include <QOperatingSystemVersion>
|
|
#endif
|
|
|
|
#include <cmath>
|
|
|
|
#include "gui/Icons.h"
|
|
|
|
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);
|
|
}
|
|
|
|
#ifdef Q_OS_MACOS
|
|
QColor tabBarBase(const QPalette& pal)
|
|
{
|
|
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSBigSur) {
|
|
return hack_isLightPalette(pal) ? QRgb(0xD4D4D4) : QRgb(0x2A2A2A);
|
|
}
|
|
return hack_isLightPalette(pal) ? QRgb(0xDD1D1D1) : QRgb(0x252525);
|
|
}
|
|
QColor tabBarBaseInactive(const QPalette& pal)
|
|
{
|
|
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSBigSur) {
|
|
return hack_isLightPalette(pal) ? QRgb(0xF5F5F5) : QRgb(0x2D2D2D);
|
|
}
|
|
return hack_isLightPalette(pal) ? QRgb(0xF4F4F4) : QRgb(0x282828);
|
|
}
|
|
#endif
|
|
} // 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,
|
|
|
|
S_tabBarBase,
|
|
S_tabBarBase_inactive,
|
|
|
|
// 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_tabBarBase_inactive + 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];
|
|
|
|
#ifdef Q_OS_MACOS
|
|
colors[S_tabBarBase] = Dc::tabBarBase(pal);
|
|
colors[S_tabBarBase_inactive] = Dc::tabBarBaseInactive(pal);
|
|
#else
|
|
colors[S_tabBarBase] = pal.color(QPalette::Active, QPalette::Window);
|
|
colors[S_tabBarBase_inactive] = pal.color(QPalette::Inactive, QPalette::Window);
|
|
#endif
|
|
|
|
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, ©, 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;
|
|
|
|
#ifdef Q_OS_MACOS
|
|
painter->fillRect(widget->rect(),
|
|
swatch.color(option->state & QStyle::State_Active ? S_tabBarBase : S_tabBarBase_inactive));
|
|
#endif
|
|
|
|
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;
|
|
|
|
#ifdef Q_OS_MACOS
|
|
if (auto* mainWindow = qobject_cast<QMainWindow*>(widget->window())) {
|
|
// Fill toolbar background with transparent pixels to reveal the
|
|
// gradient background drawn by the Cocoa platform plugin.
|
|
// Inspired by qmacstyle_mac.mm.
|
|
if (m_drawNativeMacOsToolBar && toolBar && toolBar->toolBarArea == Qt::TopToolBarArea
|
|
&& mainWindow->unifiedTitleAndToolBarOnMac()) {
|
|
painter->setCompositionMode(QPainter::CompositionMode_Source);
|
|
painter->fillRect(option->rect, Qt::transparent);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
// clang-format off
|
|
QPalette::ColorRole textRole =
|
|
!isEnabled ? QPalette::Text : isSelected ? QPalette::HighlightedText : QPalette::ButtonText;
|
|
// clang-format on
|
|
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();
|
|
}
|
|
|
|
QIcon BaseStyle::standardIcon(StandardPixmap sp, const QStyleOption* opt, const QWidget* widget) const
|
|
{
|
|
switch (sp) {
|
|
case SP_ToolBarHorizontalExtensionButton:
|
|
return icons()->icon("chevron-double-down");
|
|
case SP_ToolBarVerticalExtensionButton:
|
|
return icons()->icon("chevron-double-right");
|
|
case SP_LineEditClearButton:
|
|
return icons()->icon(
|
|
QString("edit-clear-locationbar-").append((opt->direction == Qt::LeftToRight) ? "rtl" : "ltr"));
|
|
default:
|
|
return QCommonStyle::standardIcon(sp, opt, widget);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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 ]
|