mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-04-04 21:17:43 +03:00
4868 lines
218 KiB
C++
4868 lines
218 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>
|
|
|
|
#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)
|
|
{
|
|
return hack_isLightPalette(pal) ? QRgb(0xD1D1D1) : QRgb(0x252525);
|
|
}
|
|
QColor tabBarBaseInactive(const QPalette& pal)
|
|
{
|
|
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);
|
|
QPalette::ColorRole textRole =
|
|
!isEnabled ? QPalette::Text : isSelected ? QPalette::HighlightedText : QPalette::ButtonText;
|
|
painter->setBrush(option->palette.brush(option->palette.currentColorGroup(), textRole));
|
|
qreal rx, ry, rw, rh;
|
|
QRectF(checkRect).getRect(&rx, &ry, &rw, &rh);
|
|
qreal dim = qMin(checkRect.width(), checkRect.height()) * 0.75;
|
|
QRectF rf(rx + rw / dim, ry + rh / dim, dim, dim);
|
|
painter->drawEllipse(rf);
|
|
}
|
|
} else {
|
|
// If we want mouse-down to immediately show the item as
|
|
// checked/unchecked (kinda bad if the user is click-holding on the
|
|
// menu instead of click-clicking.)
|
|
//
|
|
// if ((isChecked && !isSunken) || (!isChecked && isSunken)) {
|
|
if (isChecked) {
|
|
Ph::drawCheck(painter, d->checkBox_pen_scratch, checkRect, swatch, signColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
const bool hasIcon = !menuItem->icon.isNull();
|
|
|
|
if (hasIcon) {
|
|
QRect iconRect = Ph::menuItemIconRect(metrics, option->direction, itemRect, hasSubMenu);
|
|
QIcon::Mode mode = isEnabled ? QIcon::Normal : QIcon::Disabled;
|
|
if (isSelected && isEnabled)
|
|
mode = QIcon::Selected;
|
|
QIcon::State state = isChecked ? QIcon::On : QIcon::Off;
|
|
|
|
// TODO hmm, we might be ending up with blurry icons at size 15 instead
|
|
// of 16 for example on Windows.
|
|
//
|
|
// int smallIconSize =
|
|
// proxy()->pixelMetric(PM_SmallIconSize, option, widget);
|
|
// QSize iconSize(smallIconSize, smallIconSize);
|
|
int iconExtent = qMin(iconRect.width(), iconRect.height());
|
|
QSize iconSize(iconExtent, iconExtent);
|
|
if (auto combo = qobject_cast<const QComboBox*>(widget)) {
|
|
iconSize = combo->iconSize();
|
|
}
|
|
QWindow* window = widget ? widget->windowHandle() : nullptr;
|
|
QPixmap pixmap = menuItem->icon.pixmap(window, iconSize, mode, state);
|
|
const int pixw = static_cast<int>(pixmap.width() / pixmap.devicePixelRatio());
|
|
const int pixh = static_cast<int>(pixmap.height() / pixmap.devicePixelRatio());
|
|
QRect pixmapRect = QStyle::alignedRect(option->direction, Qt::AlignCenter, QSize(pixw, pixh), iconRect);
|
|
painter->drawPixmap(pixmapRect.topLeft(), pixmap);
|
|
}
|
|
|
|
// Draw main text and mnemonic text
|
|
QStringRef s(&menuItem->text);
|
|
if (!s.isEmpty()) {
|
|
QRect textRect =
|
|
Ph::menuItemTextRect(metrics, option->direction, itemRect, hasSubMenu, hasIcon, menuItem->tabWidth);
|
|
int t = s.indexOf(QLatin1Char('\t'));
|
|
int text_flags =
|
|
Qt::AlignLeft | Qt::AlignTop | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
|
|
if (!styleHint(SH_UnderlineShortcut, menuItem, widget))
|
|
text_flags |= Qt::TextHideMnemonic;
|
|
#if 0
|
|
painter->save();
|
|
#endif
|
|
painter->setPen(swatch.pen(isSelected ? S_highlightedText : S_text));
|
|
|
|
// Comment from original Qt code which did some dance with the font:
|
|
//
|
|
// font may not have any "hard" flags set. We override the point size so
|
|
// that when it is resolved against the device, this font will win. This
|
|
// is mainly to handle cases where someone sets the font on the window
|
|
// and then the combo inherits it and passes it onward. At that point the
|
|
// resolve mask is very, very weak. This makes it stonger.
|
|
#if 0
|
|
QFont font = menuItem->font;
|
|
font.setPointSizeF(QFontInfo(menuItem->font).pointSizeF());
|
|
painter->setFont(font);
|
|
#endif
|
|
|
|
// My comment:
|
|
//
|
|
// What actually looks like is happening is that the qplatformtheme may
|
|
// have set a per-class font for menus. The QComboMenuDelegate sets the
|
|
// combo box's own font on the QStyleOptionMenuItem when passing it in
|
|
// here and when calling sizeFromContents with CT_MenuItem, but the
|
|
// QPainter we're called with hasn't had its font set to it -- it's still
|
|
// set to the QMenu/QMenuItem app fonts hash font. So if it's a menu
|
|
// coming from a combo box, let's just go ahead and set the font for it
|
|
// if it doesn't match, since that's probably what it wanted to do. I
|
|
// think. And as described above, we have to do the weird dance with the
|
|
// resolve mask... which is some internal Qt detail that we aren't
|
|
// supposed to have to deal with, but here we are.
|
|
//
|
|
// Ok, there's another problem, and QFusionStyle also suffers from it: in
|
|
// high DPI, setting the pointSizeF and setting the font again won't
|
|
// necessarily give us the right font (at least in Windows.) The font
|
|
// might have too thin of a weight, and probably other problems. So just
|
|
// forget about it: we'll have Phantom return 0 for the style hint that
|
|
// the combo box uses to determine if it should use a QMenu popup instead
|
|
// of a regular dropdown menu thing. The popup menu might actually be
|
|
// better for usability in some cases, and it's how combos work on Mac
|
|
// and BeOS, but it won't work anyway for editable combo boxes in Qt, and
|
|
// the font issues just make it not worth it. So we'll have a dropdown
|
|
// guy like a traditional Windows thing.
|
|
//
|
|
// If you want to try it out again, go to SH_ComboBox_Popup and have it
|
|
// return 1.
|
|
//
|
|
// Alternatively, we could instead have the CT_MenuItem handling code try
|
|
// to be aggressively clever and use the qt app font hash to look up the
|
|
// expected font for a QMenu and use that for calculating its metrics.
|
|
// Unfortunately, that probably won't work so great if the combo/menu
|
|
// actually wants to use custom fonts in its listing, since we'd be
|
|
// ignoring it. That's how UseQMenuForComboBoxPopup currently works,
|
|
// though it tests for Qt::WA_SetFont as an attempt at recognizing when
|
|
// it shouldn't use the qt font hash for QMenu.
|
|
#if 0
|
|
if (qobject_cast<const QComboBox*>(widget)) {
|
|
QFont font = menuItem->font;
|
|
font.setPointSizeF(QFontInfo(menuItem->font).pointSizeF());
|
|
painter->setFont(font);
|
|
}
|
|
#endif
|
|
|
|
// Draw mnemonic text
|
|
if (t >= 0) {
|
|
QRect mnemonicR =
|
|
Ph::menuItemMnemonicRect(metrics, option->direction, itemRect, hasSubMenu, menuItem->tabWidth);
|
|
const QStringRef textToDrawRef = s.mid(t + 1);
|
|
const QString unsafeTextToDraw = QString::fromRawData(textToDrawRef.constData(), textToDrawRef.size());
|
|
painter->drawText(mnemonicR, text_flags, unsafeTextToDraw);
|
|
s = s.left(t);
|
|
}
|
|
const QStringRef textToDrawRef = s.left(t);
|
|
const QString unsafeTextToDraw = QString::fromRawData(textToDrawRef.constData(), textToDrawRef.size());
|
|
painter->drawText(textRect, text_flags, unsafeTextToDraw);
|
|
|
|
#if 0
|
|
painter->restore();
|
|
#endif
|
|
}
|
|
|
|
// SubMenu Arrow
|
|
if (hasSubMenu) {
|
|
Qt::ArrowType arrow = option->direction == Qt::RightToLeft ? Qt::LeftArrow : Qt::RightArrow;
|
|
QRect arrowRect = Ph::menuItemArrowRect(metrics, option->direction, itemRect);
|
|
Swatchy arrowColor = isSelected ? S_highlightedText : S_indicator_current;
|
|
Ph::drawArrow(painter, arrowRect, arrow, swatch.brush(arrowColor));
|
|
}
|
|
painter->restore();
|
|
break;
|
|
}
|
|
case CE_MenuHMargin:
|
|
case CE_MenuVMargin:
|
|
case CE_MenuEmptyArea:
|
|
break;
|
|
case CE_PushButton: {
|
|
auto btn = qstyleoption_cast<const QStyleOptionButton*>(option);
|
|
if (!btn)
|
|
break;
|
|
proxy()->drawControl(CE_PushButtonBevel, btn, painter, widget);
|
|
QStyleOptionButton subopt = *btn;
|
|
subopt.rect = subElementRect(SE_PushButtonContents, btn, widget);
|
|
proxy()->drawControl(CE_PushButtonLabel, &subopt, painter, widget);
|
|
break;
|
|
}
|
|
case CE_PushButtonLabel: {
|
|
auto button = qstyleoption_cast<const QStyleOptionButton*>(option);
|
|
if (!button)
|
|
break;
|
|
// This code is very similar to QCommonStyle's implementation, but doesn't
|
|
// set the icon mode to active when focused.
|
|
QRect textRect = button->rect;
|
|
int tf = Qt::AlignVCenter | Qt::TextShowMnemonic;
|
|
if (!proxy()->styleHint(SH_UnderlineShortcut, button, widget))
|
|
tf |= Qt::TextHideMnemonic;
|
|
if (!button->icon.isNull()) {
|
|
// Center both icon and text
|
|
QRect iconRect;
|
|
QIcon::Mode mode = button->state & State_Enabled ? QIcon::Normal : QIcon::Disabled;
|
|
QIcon::State state = button->state & State_On ? QIcon::On : QIcon::Off;
|
|
auto window = widget ? widget->window()->windowHandle() : nullptr;
|
|
QPixmap pixmap = button->icon.pixmap(window, button->iconSize, mode, state);
|
|
int pixmapWidth = static_cast<int>(pixmap.width() / pixmap.devicePixelRatio());
|
|
int pixmapHeight = static_cast<int>(pixmap.height() / pixmap.devicePixelRatio());
|
|
int labelWidth = pixmapWidth;
|
|
int labelHeight = pixmapHeight;
|
|
// 4 is hardcoded in QPushButton::sizeHint()
|
|
int iconSpacing = 4;
|
|
int textWidth = button->fontMetrics.boundingRect(option->rect, tf, button->text).width();
|
|
if (!button->text.isEmpty())
|
|
labelWidth += (textWidth + iconSpacing);
|
|
iconRect = QRect(textRect.x() + (textRect.width() - labelWidth) / 2,
|
|
textRect.y() + (textRect.height() - labelHeight) / 2,
|
|
pixmapWidth,
|
|
pixmapHeight);
|
|
iconRect = visualRect(button->direction, textRect, iconRect);
|
|
tf |= Qt::AlignLeft; // left align, we adjust the text-rect instead
|
|
if (button->direction == Qt::RightToLeft)
|
|
textRect.setRight(iconRect.left() - iconSpacing);
|
|
else
|
|
textRect.setLeft(iconRect.left() + iconRect.width() + iconSpacing);
|
|
if (button->state & (State_On | State_Sunken))
|
|
iconRect.translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, option, widget),
|
|
proxy()->pixelMetric(PM_ButtonShiftVertical, option, widget));
|
|
painter->drawPixmap(iconRect, pixmap);
|
|
} else {
|
|
tf |= Qt::AlignHCenter;
|
|
}
|
|
if (button->state & (State_On | State_Sunken))
|
|
textRect.translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, option, widget),
|
|
proxy()->pixelMetric(PM_ButtonShiftVertical, option, widget));
|
|
if (button->features & QStyleOptionButton::HasMenu) {
|
|
int indicatorSize = proxy()->pixelMetric(PM_MenuButtonIndicator, button, widget);
|
|
if (button->direction == Qt::LeftToRight)
|
|
textRect = textRect.adjusted(0, 0, -indicatorSize, 0);
|
|
else
|
|
textRect = textRect.adjusted(indicatorSize, 0, 0, 0);
|
|
}
|
|
proxy()->drawItemText(painter,
|
|
textRect,
|
|
tf,
|
|
button->palette,
|
|
(button->state & State_Enabled),
|
|
button->text,
|
|
QPalette::ButtonText);
|
|
break;
|
|
}
|
|
case CE_MenuBarEmptyArea: {
|
|
QRect rect = option->rect;
|
|
if (Phantom::MenuBarDrawBorder) {
|
|
Ph::fillRectEdges(painter, rect, Qt::BottomEdge, 1, swatch.color(S_window_divider));
|
|
}
|
|
painter->fillRect(rect.adjusted(0, 0, 0, -1), swatch.color(S_window));
|
|
break;
|
|
}
|
|
case CE_TabBarTabShape: {
|
|
auto tab = qstyleoption_cast<const QStyleOptionTab*>(option);
|
|
if (!tab)
|
|
break;
|
|
bool rtlHorTabs = (tab->direction == Qt::RightToLeft
|
|
&& (tab->shape == QTabBar::RoundedNorth || tab->shape == QTabBar::RoundedSouth));
|
|
bool isSelected = tab->state & State_Selected;
|
|
bool lastTab = ((!rtlHorTabs && tab->position == QStyleOptionTab::End)
|
|
|| (rtlHorTabs && tab->position == QStyleOptionTab::Beginning));
|
|
bool onlyOne = tab->position == QStyleOptionTab::OnlyOneTab;
|
|
int tabOverlap = pixelMetric(PM_TabBarTabOverlap, option, widget);
|
|
const qreal rounding = Ph::TabBarTab_Rounding;
|
|
Qt::Edge outerEdge = Qt::TopEdge;
|
|
Qt::Edge edgeTowardNextTab = Qt::RightEdge;
|
|
switch (tab->shape) {
|
|
case QTabBar::RoundedNorth:
|
|
outerEdge = Qt::TopEdge;
|
|
edgeTowardNextTab = Qt::RightEdge;
|
|
break;
|
|
case QTabBar::RoundedSouth:
|
|
outerEdge = Qt::BottomEdge;
|
|
edgeTowardNextTab = Qt::RightEdge;
|
|
break;
|
|
case QTabBar::RoundedWest:
|
|
outerEdge = Qt::LeftEdge;
|
|
edgeTowardNextTab = Qt::BottomEdge;
|
|
break;
|
|
case QTabBar::RoundedEast:
|
|
outerEdge = Qt::RightEdge;
|
|
edgeTowardNextTab = Qt::BottomEdge;
|
|
break;
|
|
default:
|
|
QCommonStyle::drawControl(element, tab, painter, widget);
|
|
return;
|
|
}
|
|
Qt::Edge innerEdge = Ph::oppositeEdge(outerEdge);
|
|
Qt::Edge edgeAwayNextTab = Ph::oppositeEdge(edgeTowardNextTab);
|
|
QRect shapeClipRect = Ph::expandRect(option->rect, innerEdge, -2);
|
|
QRect drawRect = Ph::expandRect(shapeClipRect, innerEdge, 3 + 2 * rounding + 1);
|
|
if (!onlyOne && !lastTab) {
|
|
drawRect = Ph::expandRect(drawRect, edgeTowardNextTab, tabOverlap);
|
|
shapeClipRect = Ph::expandRect(shapeClipRect, edgeTowardNextTab, tabOverlap);
|
|
}
|
|
if (!isSelected) {
|
|
int offset = proxy()->pixelMetric(PM_TabBarTabShiftVertical, option, widget);
|
|
drawRect = Ph::expandRect(drawRect, outerEdge, -offset);
|
|
}
|
|
painter->save();
|
|
painter->setClipRect(shapeClipRect);
|
|
bool hasFrame = tab->features & QStyleOptionTab::HasFrame && !tab->documentMode;
|
|
Swatchy tabFrameColor, thisFillColor, specular;
|
|
if (hasFrame) {
|
|
tabFrameColor = S_tabFrame;
|
|
if (isSelected) {
|
|
thisFillColor = S_tabFrame;
|
|
specular = S_tabFrame_specular;
|
|
} else {
|
|
thisFillColor = S_inactiveTabYesFrame;
|
|
specular = Ph::TabBar_InactiveTabsHaveSpecular ? S_inactiveTabYesFrame_specular : S_none;
|
|
}
|
|
} else {
|
|
tabFrameColor = S_window;
|
|
if (isSelected) {
|
|
thisFillColor = S_window;
|
|
specular = S_window_specular;
|
|
} else {
|
|
thisFillColor = S_inactiveTabNoFrame;
|
|
specular = Ph::TabBar_InactiveTabsHaveSpecular ? S_inactiveTabNoFrame_specular : S_none;
|
|
}
|
|
}
|
|
auto frameColor = isSelected ? S_frame_outline : S_window_outline;
|
|
Ph::paintBorderedRoundRect(painter, drawRect, rounding, swatch, frameColor, thisFillColor);
|
|
Ph::paintBorderedRoundRect(painter, drawRect.adjusted(1, 1, -1, -1), rounding, swatch, specular, S_none);
|
|
painter->restore();
|
|
if (isSelected) {
|
|
QRect highlightRect = drawRect.adjusted(2, 1, -2, 0);
|
|
highlightRect.setHeight(Ph::dpiScaled(2.0));
|
|
QRect highlightRectSpec = highlightRect.adjusted(-1, -1, 1, 0);
|
|
painter->fillRect(highlightRectSpec, Ph::DeriveColors::lightSpecularOf(swatch.color(S_highlight)));
|
|
painter->fillRect(highlightRect, swatch.color(S_highlight));
|
|
|
|
QRect refillRect = Ph::rectFromInnerEdgeWithThickness(shapeClipRect, innerEdge, 2);
|
|
refillRect = Ph::rectTranslatedTowardEdge(refillRect, innerEdge, 2);
|
|
refillRect = Ph::expandRect(refillRect, edgeAwayNextTab | edgeTowardNextTab, -1);
|
|
painter->fillRect(refillRect, swatch.color(tabFrameColor));
|
|
Ph::fillRectEdges(painter, refillRect, edgeAwayNextTab | edgeTowardNextTab, 1, swatch.color(specular));
|
|
}
|
|
break;
|
|
}
|
|
case CE_ItemViewItem: {
|
|
auto ivopt = qstyleoption_cast<const QStyleOptionViewItem*>(option);
|
|
if (!ivopt)
|
|
break;
|
|
// Hack to work around broken grid line drawing in Qt's table view code:
|
|
//
|
|
// We tell it that the grid line color is a color via
|
|
// SH_Table_GridLineColor. It draws the grid lines, but it in high DPI it's
|
|
// broken because it uses a pen/path to draw the line, which makes it too
|
|
// narrow, subpixel-incorrectly-antialiased, and/or offset from its correct
|
|
// position. So when we draw the item view items in a table view, we'll
|
|
// also try to paint 1 pixel outside of our current rect to try to fill in
|
|
// the incorrectly painted areas where the grid lines are.
|
|
//
|
|
// Also note that the table views with the bad drawing code, when
|
|
// scrolling, will leave garbage behind in the incorrectly-drawn grid line
|
|
// areas. This will also paint over that.
|
|
bool overdrawGridHack = false;
|
|
if (auto tableWidget = qobject_cast<const QTableView*>(widget)) {
|
|
overdrawGridHack = tableWidget->showGrid() && tableWidget->gridStyle() == Qt::SolidLine;
|
|
}
|
|
if (overdrawGridHack) {
|
|
QRect r = option->rect.adjusted(-1, -1, 1, 1);
|
|
Ph::fillRectOutline(painter, r, 1, swatch.color(S_base_divider));
|
|
}
|
|
QCommonStyle::drawControl(element, option, painter, widget);
|
|
break;
|
|
}
|
|
case CE_ShapedFrame: {
|
|
auto frameopt = qstyleoption_cast<const QStyleOptionFrame*>(option);
|
|
if (frameopt) {
|
|
if (frameopt->frameShape == QFrame::HLine) {
|
|
QRect r = option->rect;
|
|
r.setY(r.y() + r.height() / 2);
|
|
r.setHeight(2);
|
|
painter->fillRect(r, swatch.color(S_tabFrame_specular));
|
|
r.setHeight(1);
|
|
painter->fillRect(r, swatch.color(S_frame_outline));
|
|
break;
|
|
} else if (frameopt->frameShape == QFrame::VLine) {
|
|
QRect r = option->rect;
|
|
r.setX(r.x() + r.width() / 2);
|
|
r.setWidth(2);
|
|
painter->fillRect(r, swatch.color(S_tabFrame_specular));
|
|
r.setWidth(1);
|
|
painter->fillRect(r, swatch.color(S_frame_outline));
|
|
break;
|
|
}
|
|
}
|
|
QCommonStyle::drawControl(element, option, painter, widget);
|
|
break;
|
|
}
|
|
default:
|
|
QCommonStyle::drawControl(element, option, painter, widget);
|
|
break;
|
|
}
|
|
}
|
|
|
|
QPalette BaseStyle::standardPalette() const
|
|
{
|
|
return QCommonStyle::standardPalette();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
QPixmap BaseStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap& pixmap, const QStyleOption* opt) const
|
|
{
|
|
// Default icon highlight is way too subtle
|
|
if (iconMode == QIcon::Selected) {
|
|
QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
|
QPainter painter(&img);
|
|
|
|
painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);
|
|
|
|
QColor color =
|
|
Phantom::DeriveColors::adjustLightness(opt->palette.color(QPalette::Normal, QPalette::Highlight), .25);
|
|
color.setAlphaF(0.25);
|
|
painter.fillRect(0, 0, img.width(), img.height(), color);
|
|
|
|
painter.end();
|
|
|
|
return QPixmap::fromImage(img);
|
|
}
|
|
return QCommonStyle::generatedIconPixmap(iconMode, pixmap, opt);
|
|
}
|
|
|
|
int BaseStyle::styleHint(StyleHint hint,
|
|
const QStyleOption* option,
|
|
const QWidget* widget,
|
|
QStyleHintReturn* returnData) const
|
|
{
|
|
switch (hint) {
|
|
case SH_Slider_SnapToValue:
|
|
case SH_PrintDialog_RightAlignButtons:
|
|
case SH_FontDialog_SelectAssociatedText:
|
|
case SH_ComboBox_ListMouseTracking:
|
|
case SH_Slider_StopMouseOverSlider:
|
|
case SH_ScrollBar_MiddleClickAbsolutePosition:
|
|
case SH_TitleBar_AutoRaise:
|
|
case SH_TitleBar_NoBorder:
|
|
case SH_ItemView_ArrowKeysNavigateIntoChildren:
|
|
case SH_ItemView_ChangeHighlightOnFocus:
|
|
case SH_MenuBar_MouseTracking:
|
|
case SH_Menu_MouseTracking:
|
|
return 1;
|
|
case SH_Menu_SupportsSections:
|
|
return 0;
|
|
#ifndef Q_OS_MAC
|
|
case SH_MenuBar_AltKeyNavigation:
|
|
return 1;
|
|
#endif
|
|
#if defined(QT_PLATFORM_UIKIT)
|
|
case SH_ComboBox_UseNativePopup:
|
|
return 1;
|
|
#endif
|
|
case SH_ItemView_ShowDecorationSelected:
|
|
// QWindowsStyle does this as well -- QCommonStyle seems to have some
|
|
// internal confusion buried within its private implementation of laying
|
|
// out and drawing item views where it can't keep track of what's
|
|
// considered a decoration and what's not. For tree views, if you give 0
|
|
// for ShowDecorationSelected, it applies only to the disclosure indicator
|
|
// and not to the QIcon/pixmap that might be present for the item. So
|
|
// selecting an item in a tree view will have the selection color drawn
|
|
// underneath the icon/pixmap, but not the disclosure indicator. However,
|
|
// in list views, if you give 0 for ShowDecorationSelected, it will *not*
|
|
// draw the selection color underneath the icon/pixmap. There's no way to
|
|
// access this internal logic in QCommonStyle without fully reimplementing
|
|
// the huge mass of stuff for item view layout and drawing. Therefore, the
|
|
// best we can do is at least try to get consistent behavior: if it's a
|
|
// list view, just always return 1 for ShowDecorationSelected.
|
|
if (!Phantom::ShowItemViewDecorationSelected && qobject_cast<const QListView*>(widget))
|
|
return 1;
|
|
return Phantom::ShowItemViewDecorationSelected;
|
|
case SH_ItemView_MovementWithoutUpdatingSelection:
|
|
return 1;
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0))
|
|
case SH_ItemView_ScrollMode:
|
|
return QAbstractItemView::ScrollPerPixel;
|
|
#endif
|
|
case SH_ScrollBar_ContextMenu:
|
|
#ifdef Q_OS_MAC
|
|
return 0;
|
|
#else
|
|
return 1;
|
|
#endif
|
|
// Some Linux distros might want to enable this, but it doesn't behave very
|
|
// consistently with varied QPalettes, depending on how the QPA and icons
|
|
// deal with both light and dark themes. It might seem weird to just disable
|
|
// this, but none of (Mac, Windows, BeOS/Haiku) show icons in dialog buttons,
|
|
// and the results on Linux are generally pretty messy -- not sure why it's
|
|
// historically been the default, especially when other button types
|
|
// generally don't have any icons.
|
|
case SH_DialogButtonBox_ButtonsHaveIcons:
|
|
return 0;
|
|
case SH_ScrollBar_Transient:
|
|
return 1;
|
|
case SH_EtchDisabledText:
|
|
case SH_DitherDisabledText:
|
|
case SH_ToolBox_SelectedPageTitleBold:
|
|
case SH_Menu_AllowActiveAndDisabled:
|
|
case SH_MainWindow_SpaceBelowMenuBar:
|
|
case SH_MessageBox_CenterButtons:
|
|
case SH_RubberBand_Mask:
|
|
case SH_ScrollView_FrameOnlyAroundContents:
|
|
return 0;
|
|
case SH_ComboBox_Popup: {
|
|
return Phantom::UseQMenuForComboBoxPopup;
|
|
// Fusion did this, but we don't because of font bugs (especially in high
|
|
// DPI) with the QMenu that the combo box will create instead of a dropdown
|
|
// view. See notes in CE_MenuItem for more details.
|
|
if (auto cmb = qstyleoption_cast<const QStyleOptionComboBox*>(option))
|
|
return !cmb->editable;
|
|
return 0;
|
|
}
|
|
case SH_Table_GridLineColor: {
|
|
using namespace Phantom::SwatchColors;
|
|
namespace Ph = Phantom;
|
|
if (!option)
|
|
return 0;
|
|
auto ph_swatchPtr = Ph::getCachedSwatchOfQPalette(&d->swatchCache, &d->headSwatchFastKey, option->palette);
|
|
const Ph::PhSwatch& swatch = *ph_swatchPtr.data();
|
|
// Qt code in table views for drawing grid lines is broken. See case for
|
|
// CE_ItemViewItem painting for more information.
|
|
return static_cast<int>(swatch.color(S_base_divider).rgb());
|
|
}
|
|
case SH_MessageBox_TextInteractionFlags:
|
|
return Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse;
|
|
case SH_WizardStyle:
|
|
return QWizard::ClassicStyle;
|
|
case SH_Menu_SubMenuPopupDelay:
|
|
// Returning 0 will break sloppy submenus even if they're enabled
|
|
return 10;
|
|
case SH_Menu_SloppySubMenus:
|
|
return true;
|
|
case SH_Menu_SubMenuSloppyCloseTimeout:
|
|
return 500;
|
|
case SH_Menu_SubMenuDontStartSloppyOnLeave:
|
|
return 1;
|
|
case SH_Menu_SubMenuSloppySelectOtherActions:
|
|
return 1;
|
|
case SH_Menu_SubMenuUniDirection:
|
|
return 1;
|
|
case SH_Menu_SubMenuUniDirectionFailCount:
|
|
return 1;
|
|
case SH_Menu_SubMenuResetWhenReenteringParent:
|
|
return 0;
|
|
#ifdef Q_OS_MAC
|
|
case SH_Menu_FlashTriggeredItem:
|
|
return 1;
|
|
case SH_Menu_FadeOutOnHide:
|
|
return 0;
|
|
#endif
|
|
case SH_WindowFrame_Mask:
|
|
return 0;
|
|
case SH_UnderlineShortcut: {
|
|
return false;
|
|
}
|
|
case SH_Widget_Animate:
|
|
return 1;
|
|
default:
|
|
break;
|
|
}
|
|
return QCommonStyle::styleHint(hint, option, widget, returnData);
|
|
}
|
|
|
|
QRect BaseStyle::subElementRect(SubElement sr, const QStyleOption* opt, const QWidget* w) const
|
|
{
|
|
switch (sr) {
|
|
case SE_ProgressBarLabel:
|
|
case SE_ProgressBarContents:
|
|
case SE_ProgressBarGroove:
|
|
return opt->rect;
|
|
case SE_PushButtonFocusRect: {
|
|
QRect r = QCommonStyle::subElementRect(sr, opt, w);
|
|
r.adjust(0, 1, 0, -1);
|
|
return r;
|
|
}
|
|
case SE_DockWidgetTitleBarText: {
|
|
auto titlebar = qstyleoption_cast<const QStyleOptionDockWidget*>(opt);
|
|
if (!titlebar)
|
|
break;
|
|
QRect r = QCommonStyle::subElementRect(sr, opt, w);
|
|
bool verticalTitleBar = titlebar->verticalTitleBar;
|
|
if (verticalTitleBar) {
|
|
r.adjust(0, 0, 0, -4);
|
|
} else {
|
|
if (opt->direction == Qt::LeftToRight)
|
|
r.adjust(4, 0, 0, 0);
|
|
else
|
|
r.adjust(0, 0, -4, 0);
|
|
}
|
|
return r;
|
|
}
|
|
case SE_TreeViewDisclosureItem: {
|
|
if (Phantom::BranchesOnEdge) {
|
|
// Shove it all the way to the left (or right) side, probably outside of
|
|
// the rect it gave us. Old-school.
|
|
QRect rect = opt->rect;
|
|
if (opt->direction != Qt::RightToLeft) {
|
|
rect.moveLeft(0);
|
|
if (rect.width() < rect.height())
|
|
rect.setWidth(rect.height());
|
|
} else {
|
|
// todo
|
|
}
|
|
return rect;
|
|
}
|
|
break;
|
|
}
|
|
case SE_LineEditContents: {
|
|
QRect r = QCommonStyle::subElementRect(sr, opt, w);
|
|
int pad = Phantom::dpiScaled(Phantom::LineEdit_ContentsHPad);
|
|
return r.adjusted(pad, 0, -pad, 0);
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return QCommonStyle::subElementRect(sr, opt, w);
|
|
}
|
|
|
|
// Table header layout reference
|
|
// -----------------------------
|
|
//
|
|
// begin: QStyleOptionHeader::Beginning;
|
|
// mid: QStyleOptionHeader::Middle;
|
|
// end: QStyleOptionHeader::End;
|
|
// one: QStyleOptionHeader::OnlyOneSection;
|
|
// one*:
|
|
// This is specified as QStyleOptionHeader::OnlyOneSection, but the call to
|
|
// drawControl(CE_HeaderSection...) is being performed by an instance of
|
|
// QTableCornerButton, defined in qtableview.cpp as a subclass of
|
|
// QAbstractButton. Only table views can have these corner buttons, and they
|
|
// only appear if there are both at least 1 column and 1 row visible.
|
|
//
|
|
// Configuration A: A table view with both columns and rows
|
|
//
|
|
// Configuration B: A list view, or a tree view, or a table view with no rows
|
|
// in the data or all rows hidden, such that the corner button is also made
|
|
// hidden.
|
|
//
|
|
// Configuration C: A table view with no columns in the data or all columns
|
|
// hidden, such that the corner button is also made hidden.
|
|
//
|
|
// Configuration A, Left-to-right, 4x4
|
|
// [ one* ][ begin ][ mid ][ mid ][ end ]
|
|
// [ begin ]
|
|
// [ mid ]
|
|
// [ mid ]
|
|
// [ end ]
|
|
//
|
|
// Configuration A, Left-to-right, 2x2
|
|
// [ one* ][ begin ][ end ]
|
|
// [ begin ]
|
|
// [ end ]
|
|
//
|
|
// Configuration A, Left-to-right, 1x1
|
|
// [ one* ][ one ]
|
|
// [ one ]
|
|
//
|
|
// Configuration A, Right-to-left, 4x4
|
|
// [ begin ][ mid ][ mid ][ end ][ one* ]
|
|
// [ begin ]
|
|
// [ mid ]
|
|
// [ mid ]
|
|
// [ end ]
|
|
//
|
|
// Configuration A, Right-to-left, 2x2
|
|
// [ begin ][ end ][ one* ]
|
|
// [ begin ]
|
|
// [ end ]
|
|
//
|
|
// Configuration A, Right-to-left, 1x1
|
|
// [ one ][ one* ]
|
|
// [ one ]
|
|
//
|
|
// Configuration B, Left-to-right and right-to-left, 4 columns (table view:
|
|
// 4 columns with 0 rows, list/tree view: 4 columns, rows count doesn't matter):
|
|
// [ begin ][ mid ][ mid ][ end ]
|
|
//
|
|
// Configuration B, Left-to-right and right-to-left, 2 columns (table view:
|
|
// 2 columns with 0 rows, list/tree view: 2 columns, rows count doesn't matter):
|
|
// [ begin ][ end ]
|
|
//
|
|
// Configuration B, Left-to-right and right-to-left, 1 column (table view:
|
|
// 1 column with 0 rows, list view: 1 column, rows count doesn't matter):
|
|
// [ one ]
|
|
//
|
|
// Configuration C, left-to-right and right-to-left, table view with no columns
|
|
// and 4 rows:
|
|
// [ begin ]
|
|
// [ mid ]
|
|
// [ mid ]
|
|
// [ end ]
|
|
//
|
|
// Configuration C, left-to-right and right-to-left, table view with no columns
|
|
// and 2 rows:
|
|
// [ begin ]
|
|
// [ end ]
|
|
//
|
|
// Configuration C, left-to-right and right-to-left, table view with no columns
|
|
// and 1 row:
|
|
// [ one ]
|