Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,134 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/AutoCustomWindowDecorations.h>
#include <AzQtComponents/Components/WindowDecorationWrapper.h>
#include <QWidget>
#include <QFileDialog>
#include <QMessageBox>
#include <QInputDialog>
#include <QDockWidget>
#include <QApplication>
using namespace AzQtComponents;
static bool widgetHasCustomWindowDecorations(const QWidget* w)
{
if (!w)
{
return false;
}
auto wrapper = qobject_cast<WindowDecorationWrapper*>(w->parentWidget());
if (!wrapper)
{
return false;
}
// Simply having a decoration wrapper parent doesn't mean the widget has decorations.
return wrapper->guest() == w;
}
static bool isQWinWidget(const QWidget* w)
{
// We can't include the QWinWidget header from AzQtComponents, so use metaobject.
const QMetaObject* mo = w->metaObject()->superClass();
return mo && (strcmp(mo->className(), "QWinWidget") == 0);
}
static bool widgetShouldHaveCustomDecorations(const QWidget* w, AutoCustomWindowDecorations::Mode mode)
{
if (!w || qobject_cast<const WindowDecorationWrapper*>(w) ||
qobject_cast<const QDockWidget*>(w) ||
qobject_cast<const QFileDialog*>(w) || // QFileDialog is native
w->property("HasNoWindowDecorations").toBool() || // Allows decorations to be disabled
isQWinWidget(w))
{
// If wrapper itself, don't recurse.
// If QDockWidget then also return false, they are styled with QDockWidget::setTitleBarWidget() instead.
return false;
}
if (!(w->windowFlags() & Qt::Window))
{
return false;
}
if ((w->windowFlags() & Qt::Popup) == Qt::Popup || (w->windowFlags() & Qt::FramelessWindowHint))
{
return false;
}
if (mode == AutoCustomWindowDecorations::Mode_None)
{
return false;
}
else if (mode == AutoCustomWindowDecorations::Mode_AnyWindow)
{
return true;
}
else if (mode == AutoCustomWindowDecorations::Mode_Approved)
{
// Don't put QDockWidget here, it uses QDockWidget::setTitleBarWidget() instead.
return qobject_cast<const QMessageBox*>(w) || qobject_cast<const QInputDialog*>(w);
}
return false;
}
AutoCustomWindowDecorations::AutoCustomWindowDecorations(QObject* parent)
: QObject(parent)
{
qApp->installEventFilter(this);
}
void AutoCustomWindowDecorations::ensureCustomWindowDecorations(QWidget* w)
{
if (widgetShouldHaveCustomDecorations(w, m_mode) && !widgetHasCustomWindowDecorations(w))
{
auto wrapper = new WindowDecorationWrapper(WindowDecorationWrapper::OptionAutoAttach |
WindowDecorationWrapper::OptionAutoTitleBarButtons, w->parentWidget());
w->setParent(wrapper, w->windowFlags());
// After porting to Qt 5.12, automatically decorated dialogs wouldn't preserve their
// size after decoration. This workaround forces their size back to the one configured
// in the .ui file, if any.
if (w->testAttribute(Qt::WA_Resized)) {
w->resize(w->size());
}
}
}
void AutoCustomWindowDecorations::setMode(AutoCustomWindowDecorations::Mode mode)
{
m_mode = mode;
}
bool AutoCustomWindowDecorations::eventFilter(QObject* watched, QEvent* ev)
{
if (ev->type() == QEvent::Show)
{
if (auto w = qobject_cast<QWidget*>(watched))
{
if (strcmp(w->metaObject()->className(), "QDockWidgetGroupWindow") != 0)
{
ensureCustomWindowDecorations(w);
}
}
}
return QObject::eventFilter(watched, ev);
}
@@ -0,0 +1,40 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <QObject>
namespace AzQtComponents
{
/**
* An helper class to deal with all the code related to automatic window decorations
*/
class AutoCustomWindowDecorations : public QObject
{
public:
enum Mode
{
Mode_None = 0, // No auto window decorations
Mode_Approved = 1, // Nice window decorations for hardcoded types (QMessageBox, QInputDialog (add more as you wish))
Mode_AnyWindow = 2 // Any widget having the Qt::WindowFlag will get custom window decorations
};
explicit AutoCustomWindowDecorations(QObject* parent = nullptr);
// The default is Mode_Approved
void setMode(Mode);
protected:
bool eventFilter(QObject* watched, QEvent* ev) override;
private:
void ensureCustomWindowDecorations(QWidget* w);
Mode m_mode = Mode_Approved;
};
} // namespace AzQtComponents
@@ -0,0 +1,25 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/ButtonDivider.h>
namespace AzQtComponents
{
ButtonDivider::ButtonDivider(QWidget* parent)
: QFrame(parent)
{
}
} // namespace AzQtComponents
#include "Components/moc_ButtonDivider.cpp"
@@ -0,0 +1,34 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QFrame>
#endif
class QPainter;
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API ButtonDivider
: public QFrame
{
Q_OBJECT
public:
explicit ButtonDivider(QWidget* parent = nullptr);
};
} // namespace AzQtComponents
@@ -0,0 +1,73 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/ButtonStripe.h>
#include <QGridLayout>
#include <QButtonGroup>
#include <QPushButton>
#include <QVariant>
namespace AzQtComponents
{
ButtonStripe::ButtonStripe(QWidget* parent)
: QWidget(parent)
, m_gridLayout(new QGridLayout(this))
, m_buttonGroup(new QButtonGroup(this))
{
m_gridLayout->setSpacing(0);
}
void ButtonStripe::addButtons(const QStringList& buttonNames, int current)
{
const auto buttonsCount = buttonNames.size();
for (int i = 0; i < buttonsCount; ++i)
{
auto pushButton = new QPushButton(buttonNames.at(i));
pushButton->setCheckable(true);
if (i == 0)
{
pushButton->setProperty("class", "ButtonStripeButtonFirst");
}
else if (i == buttonsCount - 1)
{
pushButton->setProperty("class", "ButtonStripeButtonLast");
}
else
{
pushButton->setProperty("class", "ButtonStripeButtonCenter");
}
m_buttonGroup->addButton(pushButton, i);
m_gridLayout->addWidget(pushButton, 0, m_gridLayout->columnCount());
m_buttons.append(pushButton);
connect(pushButton, &QPushButton::clicked, this, [this, pushButton] {
emit buttonClicked(m_buttons.indexOf(pushButton));
});
}
setCurrent(current);
}
void ButtonStripe::setCurrent(int index)
{
const int numButtons = m_buttons.size();
if (index < numButtons && index >= 0)
{
m_buttons.at(index)->setChecked(true);
}
}
}
#include "Components/moc_ButtonStripe.cpp"
@@ -0,0 +1,50 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QWidget>
#include <QList>
#endif
class QGridLayout;
class QPushButton;
class QButtonGroup;
class QStringList;
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API ButtonStripe
: public QWidget
{
Q_OBJECT
public:
explicit ButtonStripe(QWidget* parent = nullptr);
void addButtons(const QStringList& buttonNames, int current = 0);
void setCurrent(int index);
signals:
void buttonClicked(int);
private:
QGridLayout* const m_gridLayout;
QButtonGroup* const m_buttonGroup;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
QList<QPushButton*> m_buttons;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
};
} // namespace AzQtComponents
@@ -0,0 +1,60 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/ConfigHelpers.h>
#include <QPoint>
#include <QPixmap>
#include <QCursor>
namespace AzQtComponents
{
namespace ConfigHelpers
{
/* Template specialization for QPixmap.
*
* Entry in *Config.ini should look like this:
* [key]
* Path=path/to/pixmap
*/
template <>
void read(QSettings& settings, const QString& key, QPixmap& pixmap)
{
QString path;
ConfigHelpers::read<QString>(settings, key, path);
const QPixmap testPixmap(path);
if (!testPixmap.isNull())
{
pixmap = testPixmap;
}
}
/* Template specialization for QCursor.
*
* Entry in *Config.ini should look like this:
* [key]
* Path=path/to/cursor/pixmap
*/
template <>
void read(QSettings& settings, const QString& key, QCursor& cursor)
{
QPixmap cursorPixmap;
ConfigHelpers::read<QPixmap>(settings, key, cursorPixmap);
if (!cursorPixmap.isNull())
{
cursor = QCursor(cursorPixmap);
}
}
} // namespace ConfigHelpers
} // namespace AzQtComponents
@@ -0,0 +1,139 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <QFile>
#include <QFileSystemWatcher>
#include <QSettings>
#include <QString>
#include <functional>
namespace AzQtComponents
{
namespace ConfigHelpers
{
/* The aim of the *Config.ini files is to provide a human read/writable file which allows
* developers and designers to tweak UI settings without having to rebuild the application.
* Types that QVariant can convert to QString are stored as one might expect:
*
* MyBool=true
* MyInt=5
* MyDecimal=3.14
* MyString=A stored string
*
* QColor is not normally stored by QSettings as a human readable string, it is encoded as a
* QVariant, but the following syntax works with the QVariant::value<QColor> call:
*
* MyColor=#ffffff
*
* Many types that QVariant cannot convert to QString, such as QPoint, QRect and QSize, can
* be specified as follows:
*
* MyPoint=@Point(12 16)
* MyRect=@Rect(0 0 16 16)
* MySize=@Size(4 4)
*
* However there are some types which QVariant does not store in a human readable manner, or
* cannot store at all. For these we provide template specialisations. For example, QPixmap
* can be read as follows:
*
* QPixmap pixmap;
* ConfigHelpers::read<QPixmap>(settings, QStringLiteral("MyPixmap"), pixmap);
*
* QPixmap is specified like this:
*
* MyPixmap=path/to/image.png
*/
template <class T>
void read(QSettings& settings, const QString& key, T& configValue)
{
// Sets configValue to the value of key in settings. If key does not exist, configValue
// is unchanged.
configValue = settings.value(key, QVariant::fromValue(configValue)).template value<T>();
}
template <>
void read(QSettings& settings, const QString& key, QPixmap& configValue);
template <>
void read(QSettings& settings, const QString& key, QCursor& configValue);
/* ConfigHelpers::loadConfig loads the ConfigType from a QSettings IniFormat file and
* watches that file for further changes. When changes occur, the notify function is called.
* Note that in Qt terminology, the notify function can be either a signal or a slot.
*
* Note that in case it does not go without saying, the config pointer must be valid as long
* as the watcher is, otherwise a file change on disk will result in a memory access
* violation.
*
* ConfigType is simply a struct containing the configuration options:
*
* struct Config
* {
* int height = -1;
* };
*
* This function expects WidgetType to have the following static functions:
*
* static Config loadConfig(QSettings& settings);
* static Config defaultConfig();
*/
template <typename ConfigType, typename WidgetType>
void loadConfig(QFileSystemWatcher* watcher, ConfigType* config, const QString& path, const QObject* context, const std::function<void()>& notify)
{
if (QFile::exists(path))
{
// add to the file watcher
watcher->addPath(path);
// connect the relead slot()
QObject::connect(watcher, &QFileSystemWatcher::fileChanged, context, [path, config, notify](const QString& changedPath) {
if (changedPath == path)
{
QSettings settings(path, QSettings::IniFormat);
*config = WidgetType::loadConfig(settings);
Q_EMIT notify();
}
});
QSettings settings(path, QSettings::IniFormat);
*config = WidgetType::loadConfig(settings);
}
else
{
*config = WidgetType::defaultConfig();
}
}
/* GroupGuard ensures that QSettings::endGroup is called when it is destroyed.
*/
class GroupGuard
{
public:
GroupGuard(QSettings* settings, const QString& prefix)
: m_settings(settings)
{
m_settings->beginGroup(prefix);
}
~GroupGuard()
{
m_settings->endGroup();
}
private:
QSettings* m_settings;
};
} // namespace ConfigHelpers
} // namespace AzQtComponents
@@ -0,0 +1,214 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/DockBar.h>
#include <QLinearGradient>
#include <QPainter>
#include <QRect>
#include <QFont>
#include <QFontMetrics>
#include <QPixmapCache>
// Constant for the dock bar text font family
static const char* g_dockBarFontFamily = "Open Sans";
// Constant for the dock bar text point size
static const int g_dockBarFontPointSize = 8;
// Constant for application icon path
static const char* g_applicationIconPath = ":/stylesheet/img/ly_application_icon.png";
// Constant for dock bar tear handle icon path
static const char* g_dockBarTearIconPath = ":/stylesheet/img/titlebar_tear.png";
namespace AzQtComponents
{
namespace
{
QPixmap tearIcon()
{
QPixmap tearIcon;
if (!QPixmapCache::find(QStringLiteral("dockBarTearIcon"), &tearIcon))
{
tearIcon.load(g_dockBarTearIconPath);
QPixmapCache::insert(QStringLiteral("dockBarTearIcon"), tearIcon);
}
return tearIcon;
}
} // namespace
void DockBar::drawFrame(QPainter* painter, const QRect& area, bool drawSideBorders, const DockBarColors& colors)
{
painter->save();
// Top row
painter->setPen(colors.firstLine);
painter->drawLine(0, 1, area.right(), 1);
// Background
QLinearGradient background(area.topLeft(), area.bottomLeft());
background.setColorAt(0, colors.gradientStart);
background.setColorAt(1, colors.gradientEnd);
painter->fillRect(area.adjusted(0, 2, 0, -1), background);
// Frame
painter->setPen(colors.frame);
painter->drawLine(0, area.top(), area.right(), area.top()); // top
painter->drawLine(0, area.bottom(), area.right(), area.bottom()); // bottom
// We can't draw left and right border here, because the titlebar is not as wide as the
// dockwidget, because Qt internally sets the title bar width to dockwidget.width() - 2 * frame.
// Setting frame to 0 would fix it, but then we wouldn't have border/shadow.
// We draw these two lines inside StyledDockWidget::paintEvent() instead.
if (drawSideBorders)
{
painter->drawLine(0, 0, 0, area.height() - 1); // left
painter->drawLine(area.right(), 0, area.right(), area.height() - 1); // right
}
painter->restore();
}
void DockBar::drawTabContents(QPainter* painter, const QRect& area, const DockBarColors& colors, const QString& title)
{
painter->save();
// Draw either the tear icon or the application icon
const int iconWidth = drawIcon(painter, area.x(), tearIcon());
// Draw the title using the icon width as the left margin
drawTabTitle(painter, iconWidth, area, area.right(), colors.text, title);
painter->restore();
}
QString DockBar::GetTabTitleElided(const QString& title, int& textWidth)
{
const QFontMetrics fontMetrics({ g_dockBarFontFamily, g_dockBarFontPointSize });
textWidth = fontMetrics.horizontalAdvance(title);
if (textWidth > MaxTabTitleWidth)
{
textWidth = MaxTabTitleWidth;
}
return fontMetrics.elidedText(title, Qt::ElideRight, textWidth);
}
/**
* Return the minimum width in pixels of a dock bar based on the title width plus all the margin offsets
*/
int DockBar::GetTabTitleMinWidth(const QString& title, bool enableTear)
{
// Calculate the base width of the text (capped at our max title width) plus margins
int textWidth = 0;
GetTabTitleElided(title, textWidth);
int width = HandleLeftMargin + TitleLeftMargin + textWidth + TitleRightMargin + ButtonsSpacing;
// If we have enabled tearing, add in the width of the tear icon
if (enableTear)
{
width += tearIcon().width();
}
return width;
}
/**
* Return the appropriate DockBarColors struct based on if it is active or not
*/
DockBarColors DockBar::getColors(bool active)
{
if (active)
{
return {
{
204, 204, 204
},{
33, 34, 35
},{
64, 68, 69
},{
64, 72, 80
},{
54, 61, 68
}
};
}
else
{
return {
{
204, 204, 204
},{
33, 34, 35
},{
64, 68, 69
},{
65, 68, 69
},{
54, 56, 57
}
};
}
}
/**
* Create a dock tab widget that extends a QTabWidget with a custom DockTabBar to replace the default tab bar
*/
DockBar::DockBar(QObject* parent)
: QObject(parent)
, m_tearIcon(g_dockBarTearIconPath)
, m_applicationIcon(g_applicationIconPath)
{
}
/**
* Draw the specified icon and return its width
*/
int DockBar::drawIcon(QPainter* painter, int x, const QPixmap& icon)
{
painter->drawPixmap(QPointF(HandleLeftMargin + x, Height / 2 - icon.height() / 2), icon, icon.rect());
return icon.width();
}
/**
* Draw the specified title on our dock tab bar
*/
void DockBar::drawTabTitle(QPainter* painter, int leftContentWidth, const QRect& area,
int buttonsX, const QColor& color, const QString& title)
{
if (title.isEmpty())
{
return;
}
const int textX = HandleLeftMargin + leftContentWidth + TitleLeftMargin + area.x();
const int maxX = buttonsX - TitleRightMargin;
QFont f(painter->font());
f.setFamily(g_dockBarFontFamily);
f.setPointSize(g_dockBarFontPointSize);
painter->setFont(f);
painter->setPen(color);
// Cap our maximum allowed tab title width
int textWidth = maxX - textX;
// Elide our title text if it exceeds the maximum width
QFontMetrics fontMetrics = painter->fontMetrics();
QString elidedTitle = fontMetrics.elidedText(title, Qt::ElideRight, textWidth);
// We use the Qt::TextSingleLine flag to make sure whitespace is all treated
// as spaces so that the text all prints on a single line, otherwise it would
// try to render the text on multiple lines even if you restrict the height
painter->drawText(QRectF(textX, 0, textWidth, area.height() - 1), Qt::AlignVCenter | Qt::TextSingleLine, elidedTitle);
}
} // namespace AzQtComponents
#include "Components/moc_DockBar.cpp"
@@ -0,0 +1,71 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QColor>
#include <QObject>
#include <QPixmap>
#include <QString>
#endif
class QPainter;
class QRect;
namespace AzQtComponents
{
struct DockBarColors
{
QColor text;
QColor frame;
QColor firstLine;
QColor gradientStart;
QColor gradientEnd;
};
class AZ_QT_COMPONENTS_API DockBar
: public QObject
{
Q_OBJECT
public:
enum
{
Height = 32,
HandleLeftMargin = 3,
TitleLeftMargin = 8,
TitleRightMargin = 18,
CloseButtonRightMargin = 2,
ButtonsSpacing = 5,
ResizeTopMargin = 4,
MaxTabTitleWidth = 200
};
static void drawFrame(QPainter* painter, const QRect& area, bool drawSideBorders, const DockBarColors& colors);
static void drawTabContents(QPainter* painter, const QRect& area, const DockBarColors& colors, const QString& title);
static QString GetTabTitleElided(const QString& title, int& textWidth);
static int GetTabTitleMinWidth(const QString& title, bool enableTear = true);
static DockBarColors getColors(bool active);
explicit DockBar(QObject* parent = nullptr);
DockBarColors GetColors(bool active) { return DockBar::getColors(active); }
private:
static int drawIcon(QPainter* painter, int x, const QPixmap& icon);
static void drawTabTitle(QPainter* painter, int leftContentWidth, const QRect& area,
int buttonsX, const QColor& color, const QString& title);
QPixmap m_tearIcon;
QPixmap m_applicationIcon;
};
} // namespace AzQtComponents
@@ -0,0 +1,203 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/DockBarButton.h>
#include <AzQtComponents/Components/ConfigHelpers.h>
#include <AzQtComponents/Components/Widgets/TabWidget.h>
#include <AzQtComponents/Components/Style.h>
#include <QApplication>
#include <QStyleOptionToolButton>
#include <QStylePainter>
namespace AzQtComponents
{
DockBarButton::Config DockBarButton::loadConfig(QSettings& settings)
{
Config config = defaultConfig();
ConfigHelpers::read<int>(settings, QStringLiteral("ButtonIconSize"), config.buttonIconSize);
ConfigHelpers::read<int>(settings, QStringLiteral("DefaultButtonMargin"), config.defaultButtonMargin);
ConfigHelpers::read<int>(settings, QStringLiteral("MenuIndicatorWidth"), config.menuIndicatorWidth);
ConfigHelpers::read<QString>(settings, QStringLiteral("MenuIndicatorIcon"), config.menuIndicatorIcon);
ConfigHelpers::read<QSize>(settings, QStringLiteral("MenuIndicatorIconSize"), config.menuIndicatorIconSize);
ConfigHelpers::read<QColor>(settings, QStringLiteral("HoverBackgroundColor"), config.hoverBackgroundColor);
ConfigHelpers::read<QColor>(settings, QStringLiteral("CloseHoverBackgroundColor"), config.closeHoverBackgroundColor);
ConfigHelpers::read<QColor>(settings, QStringLiteral("SelectedBackgroundColor"), config.selectedBackgroundColor);
ConfigHelpers::read<QColor>(settings, QStringLiteral("CloseSelectedBackgroundColor"), config.closeSelectedBackgroundColor);
return config;
}
DockBarButton::Config DockBarButton::defaultConfig()
{
Config config;
config.buttonIconSize = 16;
config.defaultButtonMargin = 1;
config.menuIndicatorWidth = 10;
config.menuIndicatorIcon = QStringLiteral(":/stylesheet/img/UI20/menu-indicator.svg");
config.menuIndicatorIconSize = QSize(6, 3);
config.hoverBackgroundColor = QStringLiteral("#11FFFFFF");
config.closeHoverBackgroundColor = QStringLiteral("#B80D1C");
config.selectedBackgroundColor = QStringLiteral("#1A1A1A");
config.closeSelectedBackgroundColor = QStringLiteral("#850914");
return config;
}
/**
* Create a dock bar button that can be shared between any kind of docking bars for common actions
*/
DockBarButton::DockBarButton(DockBarButton::WindowDecorationButton buttonType, QWidget* parent, bool darkStyle)
: QToolButton(parent)
, m_buttonType(buttonType)
, m_isDarkStyle(darkStyle)
{
Style::addClass(this, NoMargins);
switch (m_buttonType)
{
case DockBarButton::CloseButton:
Style::addClass(this, QStringLiteral("close"));
break;
case DockBarButton::MaximizeButton:
Style::addClass(this, QStringLiteral("maximize"));
break;
case DockBarButton::MinimizeButton:
Style::addClass(this, QStringLiteral("minimize"));
break;
case DockBarButton::DividerButton:
break;
}
if (m_isDarkStyle)
{
Style::addClass(this, QStringLiteral("dark"));
}
// Handle when our button is clicked
QObject::connect(this, &QToolButton::clicked, this, &DockBarButton::handleButtonClick);
// Our dock bar buttons only need click focus, they don't need to accept
// focus by tabbing
setFocusPolicy(Qt::ClickFocus);
}
void DockBarButton::paintEvent(QPaintEvent *)
{
QStylePainter p(this);
QStyleOptionToolButton option;
initStyleOption(&option);
// Set the icon based on m_buttonType. This allows the icon to be changed in a QStyle, or in
// a Qt Style Sheet by changing the titlebar-close-icon, titlebar-maximize-icon, and
// titlebar-minimize-icon properties.
// Used in combination with the close, maximize, minimize and dark classes set in the
// constructor, and :hover and :pressed selectors available to buttons, we have full control
// of the pixmap in the style sheet.
switch (m_buttonType)
{
case DockBarButton::CloseButton:
option.icon = style()->standardIcon(QStyle::SP_TitleBarCloseButton, &option, this);
break;
case DockBarButton::MaximizeButton:
option.icon = style()->standardIcon(QStyle::SP_TitleBarMaxButton, &option, this);
break;
case DockBarButton::MinimizeButton:
option.icon = style()->standardIcon(QStyle::SP_TitleBarMinButton, &option, this);
break;
default:
break;
}
p.drawComplexControl(QStyle::CC_ToolButton, option);
}
/**
* Handle our button clicks by emitting a signal with our button type
*/
void DockBarButton::handleButtonClick()
{
if (!window())
{
return;
}
emit buttonPressed(m_buttonType);
}
bool DockBarButton::drawDockBarButton(const Style* style, const QStyleOptionComplex* option, QPainter* painter, const QWidget* widget, const Config& config)
{
auto dockBarButton = qobject_cast<const DockBarButton*>(widget);
auto buttonOption = qstyleoption_cast<const QStyleOptionToolButton*>(option);
if (!dockBarButton || !buttonOption)
{
return false;
}
QRect buttonRect = style->subControlRect(QStyle::CC_ToolButton, option, QStyle::SC_ToolButton, widget);
QRect menuRect = style->subControlRect(QStyle::CC_ToolButton, option, QStyle::SC_ToolButtonMenu, widget);
painter->save();
QStyleOptionToolButton label = *buttonOption;
// Do not draw hover rect if the button is used in a Tab
auto tabBarParent = qobject_cast<const AzQtComponents::TabBar*>(widget->parent());
bool mouseOver = buttonOption->state & QStyle::State_MouseOver;
Qt::MouseButtons mouseButtons = QApplication::mouseButtons();
bool mouseDown = mouseButtons & Qt::LeftButton;
if (!tabBarParent && mouseOver)
{
painter->setPen(Qt::NoPen);
if (dockBarButton->m_buttonType == DockBarButton::CloseButton)
{
if (mouseDown)
{
painter->setBrush(config.closeSelectedBackgroundColor);
}
else
{
painter->setBrush(config.closeHoverBackgroundColor);
}
}
else
{
if (mouseDown)
{
painter->setBrush(config.selectedBackgroundColor);
}
else
{
painter->setBrush(config.hoverBackgroundColor);
}
}
const QRect highlightRect = buttonOption->rect;
painter->drawRect(highlightRect);
}
label.rect = buttonRect;
style->drawControl(QStyle::CE_ToolButtonLabel, &label, painter, widget);
painter->restore();
return true;
}
} // namespace AzQtComponents
#include "Components/moc_DockBarButton.cpp"
@@ -0,0 +1,92 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QProxyStyle>
#include <QToolButton>
#endif
class QSettings;
class QEvent;
class QMouseEvent;
class QWidget;
namespace AzQtComponents
{
class Style;
class AZ_QT_COMPONENTS_API DockBarButton
: public QToolButton
{
Q_OBJECT
public:
struct Config
{
int buttonIconSize;
int defaultButtonMargin;
int menuIndicatorWidth;
QString menuIndicatorIcon;
QSize menuIndicatorIconSize;
QColor hoverBackgroundColor;
QColor closeHoverBackgroundColor;
QColor selectedBackgroundColor;
QColor closeSelectedBackgroundColor;
};
/*!
* Loads the button config data from a settings object.
*/
static Config loadConfig(QSettings& settings);
/*!
* Returns default button config data.
*/
static Config defaultConfig();
enum WindowDecorationButton
{
CloseButton,
MaximizeButton,
MinimizeButton,
DividerButton
};
Q_ENUM(WindowDecorationButton)
explicit DockBarButton(DockBarButton::WindowDecorationButton buttonType, QWidget* parent = nullptr, bool darkStyle = false);
DockBarButton::WindowDecorationButton buttonType() const { return m_buttonType; }
/*
* Expose the button type using a QT property so that test automation can read it
*/
Q_PROPERTY(WindowDecorationButton buttonType MEMBER m_buttonType CONSTANT)
Q_SIGNALS:
void buttonPressed(const DockBarButton::WindowDecorationButton type);
protected:
void paintEvent(QPaintEvent* event) override;
private:
friend class Style;
static bool drawDockBarButton(const Style* style, const QStyleOptionComplex* option, QPainter* painter, const QWidget* widget, const Config& config);
void handleButtonClick();
const DockBarButton::WindowDecorationButton m_buttonType;
bool m_isDarkStyle;
};
} // namespace AzQtComponents
@@ -0,0 +1,53 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <QVariant>
#include <AzQtComponents/Components/DockMainWindow.h>
namespace AzQtComponents
{
static const char* FancyDockingOwnerPropertyName = "fancydocking_owner";
/**
* Create a dock main window that extends the QMainWindow so we can construct
* our own custom context popup menu
*/
DockMainWindow::DockMainWindow(QWidget* parent, Qt::WindowFlags flags)
: QMainWindow(parent, flags)
{
setCursor(Qt::ArrowCursor);
}
/**
* Override of QMainWindow::createPopupMenu to not show any context menu when
* right-clicking on the space between our dock widgets
*/
QMenu* DockMainWindow::createPopupMenu()
{
return nullptr;
}
void DockMainWindow::SetFancyDockingOwner(QWidget* instance)
{
setProperty(FancyDockingOwnerPropertyName, QVariant::fromValue(instance));
}
bool DockMainWindow::HasFancyDocking()
{
return property(FancyDockingOwnerPropertyName).isValid();
}
} // namespace AzQtComponents
#include "Components/moc_DockMainWindow.cpp"
@@ -0,0 +1,37 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QMainWindow>
#endif
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API DockMainWindow
: public QMainWindow
{
Q_OBJECT
public:
explicit DockMainWindow(QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags());
QMenu* createPopupMenu() override;
/// Set if this main window is owned by a fancy docking instance
void SetFancyDockingOwner(QWidget* instance);
/// Return whether or not this main window is configured with fancy docking
bool HasFancyDocking();
};
} // namespace AzQtComponents
@@ -0,0 +1,370 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Debug/Trace.h>
#include <AzQtComponents/Components/DockBar.h>
#include <AzQtComponents/Components/DockBarButton.h>
#include <AzQtComponents/Components/DockMainWindow.h>
#include <AzQtComponents/Components/DockTabBar.h>
#include <AzQtComponents/Components/StyledDockWidget.h>
#include <AzQtComponents/Components/Titlebar.h>
#include <QAction>
#include <QApplication>
#include <QContextMenuEvent>
#include <QGraphicsOpacityEffect>
#include <QMenu>
#include <QMouseEvent>
#include <QToolButton>
#include <QStyleOptionTab>
// Constant for the width of the close button and its total offset (width + margin spacing)
static const int g_closeButtonWidth = 19;
static const int g_closeButtonOffset = g_closeButtonWidth + AzQtComponents::DockBar::ButtonsSpacing;
// Constant for the color of our tab indicator underlay
static const QColor g_tabIndicatorUnderlayColor(Qt::black);
// Constant for the opacity of our tab indicator underlay
static const qreal g_tabIndicatorUnderlayOpacity = 0.75;
// Constant for the duration of our tab animations (in milliseconds)
static const int g_tabAnimationDurationMS = 250;
namespace AzQtComponents
{
/**
* The close button is only present on the active tab, so return the close button offset for the
* current index, otherwise none
*/
int DockTabBar::closeButtonOffsetForIndex(const QStyleOptionTab* option)
{
return (option->state & QStyle::State_Selected) ? g_closeButtonOffset : 0;
}
/**
* Create a dock tab widget that extends a QTabWidget with a custom DockTabBar to replace the default tab bar
*/
DockTabBar::DockTabBar(QWidget* parent)
: TabBar(parent)
, m_tabIndicatorUnderlay(new QWidget(this))
, m_leftButton(nullptr)
, m_rightButton(nullptr)
, m_contextMenu(nullptr)
, m_closeTabMenuAction(nullptr)
, m_closeTabGroupMenuAction(nullptr)
, m_menuActionTabIndex(-1)
, m_singleTabFillsWidth(false)
{
setFixedHeight(DockBar::Height);
setMovable(true);
SetUseMaxWidth(true);
// Handle our close tab button clicks
QObject::connect(this, &DockTabBar::tabCloseRequested, this, &DockTabBar::closeTab);
// Handle when our current tab index changes
QObject::connect(this, &TabBar::currentChanged, this, &DockTabBar::currentIndexChanged);
// Our QTabBar base class has left/right indicator buttons for scrolling
// through the tab header if all the tabs don't fit in the given space for
// the widget, but they just float over the tabs, so we have added a
// semi-transparent underlay that will be positioned below them so that
// it looks better
QPalette underlayPalette;
underlayPalette.setColor(QPalette::Window, g_tabIndicatorUnderlayColor);
m_tabIndicatorUnderlay->setAutoFillBackground(true);
m_tabIndicatorUnderlay->setPalette(underlayPalette);
QGraphicsOpacityEffect* effect = new QGraphicsOpacityEffect(m_tabIndicatorUnderlay);
effect->setOpacity(g_tabIndicatorUnderlayOpacity);
m_tabIndicatorUnderlay->setGraphicsEffect(effect);
// The QTabBar has two QToolButton children that are used as left/right
// indicators to scroll across the tab header when the width is too short
// to fit all of the tabs
for (QToolButton* button : findChildren<QToolButton*>(QString(), Qt::FindDirectChildrenOnly))
{
// Grab references to each button for use later
if (button->accessibleName() == TabBar::tr("Scroll Left"))
{
m_leftButton = button;
}
else
{
m_rightButton = button;
}
}
}
void DockTabBar::setIsShowingWindowControls(bool show)
{
m_isShowingWindowControls = show;
}
/**
* Handle resizing appropriately when our parent tab widget is resized,
* otherwise when there is only one tab it won't know to stretch to the
* full width
*/
QSize DockTabBar::sizeHint() const
{
if (m_singleTabFillsWidth && count() == 1)
{
return TabBar::tabSizeHint(0);
}
return TabBar::sizeHint();
}
void DockTabBar::setSingleTabFillsWidth(bool singleTabFillsWidth)
{
if (m_singleTabFillsWidth == singleTabFillsWidth)
{
return;
}
m_singleTabFillsWidth = singleTabFillsWidth;
emit singleTabFillsWidthChanged(m_singleTabFillsWidth);
}
QString DockTabBar::tabText(int index) const
{
QString title = QTabBar::tabText(index);
int titleWidth;
return DockBar::GetTabTitleElided(title, titleWidth);
}
/**
* Any time the tab layout changes (e.g. tabs are added/removed/resized or active tab changed),
* we need to check if we need to add our tab indicator underlay, and
* update which tab close button is visible
*/
void DockTabBar::tabLayoutChange()
{
TabBar::tabLayoutChange();
// Only the active tab's close button should be shown
const ButtonPosition closeSide = (ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, this);
const int numTabs = count();
const int activeTabIndex = currentIndex();
for (int i = 0; i < numTabs; ++i)
{
if (auto button = tabButton(i, closeSide))
{
button->setVisible(i == activeTabIndex);
}
}
// If the tab indicators are showing, then we need to show our underlay
if (m_leftButton->isVisible())
{
// The underlay will take up the combined space behind the left and
// right indicator buttons
QRect total = m_leftButton->geometry();
total = total.united(m_rightButton->geometry());
m_tabIndicatorUnderlay->setGeometry(total);
// The indicator buttons get raised when shown, so we need to stack
// our underlay under the left button, which will place it under
// both indicator buttons, and then show it
m_tabIndicatorUnderlay->stackUnder(m_leftButton);
m_tabIndicatorUnderlay->show();
}
else
{
m_tabIndicatorUnderlay->hide();
}
}
void DockTabBar::tabInserted(int index)
{
auto closeButton = new DockBarButton(DockBarButton::CloseButton);
connect(closeButton, &DockBarButton::clicked, this, [=] {
int widgetIndex = tabAt(closeButton->pos());
if (widgetIndex >= 0)
{
emit tabCloseRequested(widgetIndex);
}
});
const ButtonPosition closeSide = (ButtonPosition) style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, this);
setTabButton(index, closeSide, closeButton);
}
/**
* Handle the right-click context menu event by displaying our custom menu
* with options to close/undock individual tabs or the entire tab group
*/
void DockTabBar::contextMenuEvent(QContextMenuEvent* event)
{
// Figure out the index of the tab the event was triggered on, or use
// the currently active tab if the event was triggered in the header
// dead zone
int index = tabAt(event->pos());
if (index == -1 && !m_isShowingWindowControls)
{
index = currentIndex();
}
m_menuActionTabIndex = index;
// Need to create our context menu/actions if this is the first time
// it has been invoked
if (!m_contextMenu)
{
m_contextMenu = new QMenu(this);
// Action to close the specified tab, and leave the text blank since
// it will be dynamically set using the title of the specified tab
m_closeTabMenuAction = m_contextMenu->addAction(QString());
QObject::connect(m_closeTabMenuAction, &QAction::triggered, this, [this]() { emit closeTab(m_menuActionTabIndex); });
// Action to close all of the tabs in our tab widget
m_closeTabGroupMenuAction = m_contextMenu->addAction(tr("Close Tab Group"));
QObject::connect(m_closeTabGroupMenuAction, &QAction::triggered, this, &DockTabBar::closeTabGroup);
// Separate the close actions from the undock actions
m_contextMenu->addSeparator();
// Action to undock the specified tab, and leave the text blank since
// it will be dynamically set using the title of the specified tab
m_undockTabMenuAction = m_contextMenu->addAction(QString());
QObject::connect(m_undockTabMenuAction, &QAction::triggered, this, [this]() { emit undockTab(m_menuActionTabIndex); });
// Action to undock the entire tab widget
m_undockTabGroupMenuAction = m_contextMenu->addAction(tr("Undock Tab Group"));
QObject::connect(m_undockTabGroupMenuAction, &QAction::triggered, this ,[this]() { emit undockTab(-1); });
}
if (index >= 0)
{
// Update the menu labels for the close/undock individual tab actions
QString tabName = tabText(index);
m_closeTabMenuAction->setText(tr("Close %1").arg(tabName));
m_undockTabMenuAction->setText(tr("Undock %1").arg(tabName));
// Only enable the close/undock group actions if we have more than one
// tab in our tab widget
bool enableGroupActions = (count() > 1);
m_closeTabGroupMenuAction->setEnabled(enableGroupActions);
// Don't enable the undock action if this dock widget is the only pane
// in a floating window or if it isn't docked in one of our dock main windows
QWidget* tabWidget = parentWidget();
bool enableUndock = true;
if (tabWidget)
{
// The main case is when we have a tab bar for a tab widget
QWidget* tabWidgetParent = tabWidget->parentWidget();
StyledDockWidget* dockWidgetContainer = qobject_cast<StyledDockWidget*>(tabWidgetParent);
if (!dockWidgetContainer)
{
// The other case is when this tab bar is being used for a solo dock widget by the TitleBar
// so that it looks like a tab, so we need to look one level up
dockWidgetContainer = qobject_cast<StyledDockWidget*>(tabWidgetParent->parentWidget());
}
if (dockWidgetContainer)
{
DockMainWindow* dockMainWindow = qobject_cast<DockMainWindow*>(dockWidgetContainer->parentWidget());
enableUndock = dockMainWindow && !dockWidgetContainer->isSingleFloatingChild();
}
}
m_undockTabGroupMenuAction->setEnabled(enableGroupActions && enableUndock);
// Enable the undock action if there are multiple tabs or if this isn't
// a single tab in a floating window
m_undockTabMenuAction->setEnabled(enableGroupActions || enableUndock);
// Show the context menu
m_contextMenu->exec(event->globalPos());
}
else
{
// Show Window context menu
// The Floating Window structure is fixed, so we should always get the parent. If we don't, bail out.
if (!parent() || !parent()->parent() || !parent()->parent()->parent())
{
AZ_Warning("DockTabBar", false,
"Could not access the parent floating window to trigger its context menu - invalid floating window structure?");
return;
}
auto parentFloatingWindow = qobject_cast<StyledDockWidget*>(parent()->parent()->parent()->parent());
if (parentFloatingWindow)
{
auto parentFloatingWindowTitleBar = qobject_cast<AzQtComponents::TitleBar*>(parentFloatingWindow->customTitleBar());
if (parentFloatingWindowTitleBar)
{
QContextMenuEvent contextMenuEvent(QContextMenuEvent::Reason::Mouse, event->pos(), event->globalPos());
QApplication::sendEvent(parentFloatingWindowTitleBar, &contextMenuEvent);
}
}
}
}
/**
* Close all of the tabs in our tab widget
*/
void DockTabBar::closeTabGroup()
{
// Close each of the tabs using our signal trigger so they are cleaned
// up properly
int numTabs = count();
for (int i = 0; i < numTabs; ++i)
{
emit closeTab(0);
}
}
/**
* When our tab index changes, we need to force a resize event to trigger a layout change, since the tabSizeHint needs
* to be updated because we only show the close button on the active tab
*/
void DockTabBar::currentIndexChanged(int current)
{
Q_UNUSED(current);
resizeEvent(nullptr);
}
/**
* Override the mouse press event handler to fix a Qt issue where the QTabBar
* doesn't ensure that it's the left mouse button that has been pressed
* early enough, even though it properly checks for it first in its mouse
* release event handler
*/
void DockTabBar::mousePressEvent(QMouseEvent* event)
{
if (event->button() != Qt::LeftButton)
{
event->ignore();
return;
}
TabBar::mousePressEvent(event);
}
/**
* Send a dummy MouseButtonRelease event to the QTabBar to ensure that the tab move animations
* get triggered when a tab is dragged out of the tab bar.
*/
void DockTabBar::finishDrag()
{
QMouseEvent event(QEvent::MouseButtonRelease, {0.0f, 0.0f}, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
mouseReleaseEvent(&event);
}
} // namespace AzQtComponents
#include "Components/moc_DockTabBar.cpp"
@@ -0,0 +1,79 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <AzQtComponents/Components/Widgets/TabWidget.h>
#endif
class QAction;
class QMenu;
class QMouseEvent;
class QToolButton;
class QStyleOption;
class QStyleOptionTab;
namespace AzQtComponents
{
class DockBar;
class AZ_QT_COMPONENTS_API DockTabBar
: public TabBar
{
Q_OBJECT
Q_PROPERTY(bool singleTabFillsWidth READ singleTabFillsWidth WRITE setSingleTabFillsWidth NOTIFY singleTabFillsWidthChanged)
public:
static int closeButtonOffsetForIndex(const QStyleOptionTab* option);
explicit DockTabBar(QWidget* parent = nullptr);
using TabBar::mouseMoveEvent;
using TabBar::mouseReleaseEvent;
void contextMenuEvent(QContextMenuEvent* event) override;
void finishDrag();
QSize sizeHint() const override;
bool singleTabFillsWidth() const { return m_singleTabFillsWidth; }
void setSingleTabFillsWidth(bool singleTabFillsWidth);
void setIsShowingWindowControls(bool show);
QString tabText(int index) const;
Q_SIGNALS:
void closeTab(int index);
void undockTab(int index);
void singleTabFillsWidthChanged(bool singleTabFillsWidth);
protected:
void mousePressEvent(QMouseEvent* event) override;
void tabLayoutChange() override;
void tabInserted(int index) override;
protected Q_SLOTS:
void currentIndexChanged(int current);
void closeTabGroup();
private:
QWidget* m_tabIndicatorUnderlay;
QToolButton* m_leftButton;
QToolButton* m_rightButton;
QMenu* m_contextMenu;
QAction* m_closeTabMenuAction;
QAction* m_closeTabGroupMenuAction;
QAction* m_undockTabMenuAction;
QAction* m_undockTabGroupMenuAction;
int m_menuActionTabIndex;
bool m_singleTabFillsWidth;
bool m_isShowingWindowControls = false;
};
} // namespace AzQtComponents
@@ -0,0 +1,336 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/DockTabWidget.h>
#include <AzQtComponents/Components/DockTabBar.h>
#include <AzQtComponents/Components/StyledDockWidget.h>
#include <AzQtComponents/Components/RepolishMinimizer.h>
#include <QContextMenuEvent>
#include <QDockWidget>
#include <QStackedWidget>
#include <QString>
namespace AzQtComponents
{
/**
* Create a dock tab widget that extends a QTabWidget with a custom DockTabBar to replace the default tab bar
*/
DockTabWidget::DockTabWidget(QWidget* mainEditorWindow, QWidget* parent)
: TabWidget(parent)
, m_tabBar(new DockTabBar)
, m_mainEditorWindow(mainEditorWindow)
{
// Replace the default tab bar with our custom DockTabBar to override the styling and docking behaviors.
// Setting the custom tab bar parents it to ourself, so it will get cleaned up whenever our DockTabWidget is destroyed.
setCustomTabBar(m_tabBar);
// Listen for selected tab changes
QObject::connect(m_tabBar, &DockTabBar::tabBarClicked, this, &DockTabWidget::handleTabIndexPressed);
// Listen for close requests from our tabs
QObject::connect(m_tabBar, &DockTabBar::closeTab, this, &DockTabWidget::handleTabCloseRequested);
// Forward on undock requests from our tabs
QObject::connect(m_tabBar, &DockTabBar::undockTab, this, &DockTabWidget::undockTab);
// Enable spacing of the overflow button to allow dragging even when the TabBar gets crowded
setOverflowButtonSpacing(true);
}
/**
* Small wrapper around the QTabWidget add tab so we can strip off the title bar widget automatically
*/
int DockTabWidget::addTab(QDockWidget* page)
{
if (!page)
{
return -1;
}
// Set an empty QWidget as the custom title bar to hide it, since our tab widget will drive it's own custom tab bar
// that will replace it (the empty QWidget is parented to the dock widget, so it will be cleaned up whenever the dock widget is deleted).
// We can't just set a nullptr because the QDockWidget will install a default title bar instead of leaving it empty, which is what we want.
page->setTitleBarWidget(new QWidget());
// Let the QTabWidget handle the rest
AzQtComponents::RepolishMinimizer minimizer;
int tab = TabWidget::addTab(page, page->windowTitle());
// If a tabbed dock widget is about to close, we need to remove it from
// our tab widget ourselves, otherwise it won't know to recreate its
// createCustomTitleBar() that we set to empty when adding to a tab widget
auto dockWidget = qobject_cast<AzQtComponents::StyledDockWidget*>(page);
if (dockWidget)
{
QObject::connect(dockWidget, &StyledDockWidget::aboutToClose, this, &DockTabWidget::handleTabAboutToClose);
}
// Make sure that changes to the window title get reflected by the tab too
m_titleBarChangedConnections[page] = connect(page, &QWidget::windowTitleChanged, this, [this, page]() {
// have to find this widget in the list, since the index might have changed
int tabIndex = indexOf(page);
if (tabIndex != -1)
{
setTabText(tabIndex, page->windowTitle());
}
});
return tab;
}
/**
* Small wrapper around the QTabWidget remove tab so we can put the custom
* title bar back on the dock widget since we stripped it off when adding the tab
* and unparent it
*/
void DockTabWidget::removeTab(int index)
{
AzQtComponents::StyledDockWidget* dockWidget = qobject_cast<AzQtComponents::StyledDockWidget*>(widget(index));
if (dockWidget)
{
// Stop listening to title bar changed events
if (m_titleBarChangedConnections.find(dockWidget) != m_titleBarChangedConnections.end())
{
QObject::disconnect(m_titleBarChangedConnections[dockWidget]);
m_titleBarChangedConnections.remove(dockWidget);
}
// Restore the custom title bar so if the user re-opens that view,
// the custom title bar will still be there
dockWidget->createCustomTitleBar();
// We also need to unparent it before we remove it from
// the tab widget, or else the tab widget may try to delete it during cleanup
// if it was the last remaining tab, which results in the tab widget being deleted.
// Unparenting the dock widget will trigger the QTabWidget to call removeTab
// itself, so we shouldn't call it here, or else it would end up removing
// the index twice, which could inadvertently remove two different widgets.
// We accomplish the unparent by re-parenting the dock widget to the
// main editor window, which will allow it to be restored properly later.
AzQtComponents::RepolishMinimizer minimizer;
dockWidget->setParent(m_mainEditorWindow);
}
}
/**
* Overloaded function to be able to remove a tab by passing the dock widget
*/
void DockTabWidget::removeTab(QDockWidget* page)
{
int index = indexOf(page);
if (index != -1)
{
removeTab(index);
}
}
/**
* Attempt to close all of the tabs and return whether or not the tabs were
* closed successfully
*/
bool DockTabWidget::closeTabs()
{
int numTabs = count();
for (int i = 0; i < numTabs; ++i)
{
if (!handleTabCloseRequested(0))
{
return false;
}
}
return true;
}
/**
* Expose QTabBar API to reorder existing tabs
*/
void DockTabWidget::moveTab(int from, int to)
{
m_tabBar->moveTab(from, to);
}
/**
* Handle the close event for our tab widget by trying to close all of the tabs
*/
void DockTabWidget::closeEvent(QCloseEvent* event)
{
// If any of the tabs couldn't be closed, then ignore our close event
if (!closeTabs())
{
event->ignore();
return;
}
TabWidget::closeEvent(event);
}
/**
* Handle passing our right-click context menu event to our custom tab bar
* when necessary
*/
void DockTabWidget::contextMenuEvent(QContextMenuEvent* event)
{
// The custom tab bar doesn't take up the full width of our tab widget,
// so we need to forward on any right-click events in this dead zone
// on the right of the tab bar to it so it can display the appropriate
// context menu
if (event->pos().y() <= m_tabBar->height())
{
m_tabBar->contextMenuEvent(event);
}
}
/**
* Emit a signal with a reference to the widget that was inserted
*/
void DockTabWidget::tabInserted(int index)
{
TabWidget::tabInserted(index);
emit tabWidgetInserted(widget(index));
}
/**
* Emit our tab count changed signal whenever a tab is removed (we use this elsewhere to handle tearing down the tab widget when no tabs are left)
*/
void DockTabWidget::tabRemoved(int index)
{
TabWidget::tabRemoved(index);
emit tabCountChanged(count());
}
/**
* Handle closing tabs when requested from our tab bar close button
*/
bool DockTabWidget::handleTabCloseRequested(int index)
{
AzQtComponents::StyledDockWidget* dockWidget = qobject_cast<AzQtComponents::StyledDockWidget*>(widget(index));
if (dockWidget)
{
// Send the close event to the widget, so it has the opportunity to reject or save its state.
// If the DeleteOnClose attribute is set, then the tab will be removed automatically if the
// close is successful when the widget is deleted. Otherwise, it will get removed since we
// are listening to the aboutToClose signal from our tabbed dock widgets.
return dockWidget->close();
}
return false;
}
void DockTabWidget::handleTabAboutToClose()
{
auto dockWidget = qobject_cast<AzQtComponents::StyledDockWidget*>(sender());
if (dockWidget)
{
removeTab(dockWidget);
}
}
void DockTabWidget::handleTabIndexPressed(int index)
{
// Give the dock widget for the selected tab focus, since the user is explicitly switching
// to that tab
QDockWidget* dockWidget = qobject_cast<QDockWidget*>(widget(index));
if (dockWidget)
{
dockWidget->setFocus();
}
// Pass along our signal that the tab index was pressed
Q_EMIT tabIndexPressed(index);
}
/**
* Handle clicks in the tab bar. Will only pick up clicks in the black, unoccupied area.
*/
void DockTabWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::MouseButton::LeftButton && event->pos().y() <= m_tabBar->height())
{
emit tabIndexPressed(-1);
}
else
{
TabWidget::mousePressEvent(event);
}
}
/**
* Pass along the mouse move events to our custom tab bar
*/
void DockTabWidget::mouseMoveEvent(QMouseEvent* event)
{
m_tabBar->mouseMoveEvent(event);
}
/**
* Pass along the mouse release events to our custom tab bar
*/
void DockTabWidget::mouseReleaseEvent(QMouseEvent* event)
{
m_tabBar->mouseReleaseEvent(event);
}
void DockTabWidget::mouseDoubleClickEvent(QMouseEvent* event)
{
if (event->button() == Qt::MouseButton::LeftButton && event->pos().y() <= m_tabBar->height())
{
emit tabBarDoubleClicked();
}
else
{
TabWidget::mouseDoubleClickEvent(event);
}
}
/**
* Handle clearing the internal drag state of our custom tab bar
*/
void DockTabWidget::finishDrag()
{
m_tabBar->finishDrag();
}
/**
* Check if this dock widget is tabbed in our custom dock tab widget
*/
bool DockTabWidget::IsTabbed(QDockWidget* dockWidget)
{
// If our dock widget is tabbed, it will have a valid tab widget parent
return ParentTabWidget(dockWidget);
}
/**
* Return the tab widget holding this dock widget if it is a tab, otherwise nullptr
*/
AzQtComponents::DockTabWidget* DockTabWidget::ParentTabWidget(QDockWidget* dockWidget)
{
if (dockWidget)
{
// If our dock widget is tabbed, it will be parented to a QStackedWidget that is parented to
// our dock tab widget
QStackedWidget* stackedWidget = qobject_cast<QStackedWidget*>(dockWidget->parentWidget());
if (stackedWidget)
{
AzQtComponents::DockTabWidget* tabWidget = qobject_cast<AzQtComponents::DockTabWidget*>(stackedWidget->parentWidget());
return tabWidget;
}
}
return nullptr;
}
} // namespace AzQtComponents
#include "Components/moc_DockTabWidget.cpp"
@@ -0,0 +1,75 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <AzQtComponents/Components/Widgets/TabWidget.h>
#include <QMap>
#endif
class QDockWidget;
class QMouseEvent;
namespace AzQtComponents
{
class DockTabBar;
class AZ_QT_COMPONENTS_API DockTabWidget
: public TabWidget
{
Q_OBJECT
public:
explicit DockTabWidget(QWidget* mainEditorWindow, QWidget* parent = nullptr);
int addTab(QDockWidget* page);
void removeTab(int index);
void removeTab(QDockWidget* page);
bool closeTabs();
void moveTab(int from, int to);
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void mouseDoubleClickEvent(QMouseEvent* event) override;
void finishDrag();
static bool IsTabbed(QDockWidget* dockWidget);
static DockTabWidget* ParentTabWidget(QDockWidget* dockWidget);
Q_SIGNALS:
void tabIndexPressed(int index);
void tabCountChanged(int count);
void tabWidgetInserted(QWidget* widget);
void undockTab(int index);
void tabBarDoubleClicked();
protected:
void closeEvent(QCloseEvent* event) override;
void contextMenuEvent(QContextMenuEvent* event) override;
void tabInserted(int index) override;
void tabRemoved(int index) override;
protected Q_SLOTS:
bool handleTabCloseRequested(int index);
void handleTabAboutToClose();
void handleTabIndexPressed(int index);
private:
DockTabBar* m_tabBar;
QWidget* m_mainEditorWindow;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
QMap<QDockWidget*, QMetaObject::Connection> m_titleBarChangedConnections;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
};
} // namespace AzQtComponents
@@ -0,0 +1,47 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#import <Cocoa/Cocoa.h>
#include <QWidget>
void setCocoaMouseCursor(QWidget* widget)
{
if (widget == nullptr)
{
// no widget, default cursor
[[NSCursor arrowCursor] set];
}
else
{
// otherwhise activate the Cocoa mouse
// cursor matching the one set via Qt
switch (widget->cursor().shape())
{
case Qt::ArrowCursor:
[[NSCursor arrowCursor] set];
break;
case Qt::SizeHorCursor:
case Qt::SplitVCursor:
[[NSCursor resizeUpDownCursor] set];
break;
case Qt::SizeVerCursor:
case Qt::SplitHCursor:
[[NSCursor resizeLeftRightCursor] set];
break;
default:
// for all other cursors we do nothing
// since this is only to fix the splitter handle cursors for now
break;
}
}
}
@@ -0,0 +1,97 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "ExtendedLabel.h"
AZ_PUSH_DISABLE_WARNING(4251 4244, "-Wunknown-warning-option") // 4251: 'QVariant::d': struct 'QVariant::Private' needs to have dll-interface to be used by clients of class 'QVariant'
#include <QVariant>
AZ_POP_DISABLE_WARNING
namespace AzQtComponents
{
ExtendedLabel::ExtendedLabel(QWidget* parent)
: QLabel(parent)
{
}
void ExtendedLabel::setPixmap(const QPixmap& p)
{
m_pix = p;
rescale();
}
void ExtendedLabel::resizeEvent(QResizeEvent* /*event*/)
{
rescale();
}
void ExtendedLabel::rescale()
{
if (!m_pix.isNull())
{
setAlignment(Qt::AlignCenter);
int realWidth;
int realHeight;
m_pixmapSize = property("pixmapSize").toSize();
if (m_pixmapSize.isValid())
{
realWidth = qMin(m_pixmapSize.width(), this->width());
realHeight = qMin(m_pixmapSize.height(), this->height());
}
else
{
realWidth = this->width();
realHeight = this->height();
}
double aspectRatioExpected = aspectRatio(
realWidth,
realHeight);
double aspectRatioSource = aspectRatio(
m_pix.width(),
m_pix.height());
//use the one that fits
if (aspectRatioExpected > aspectRatioSource)
{
QLabel::setPixmap(m_pix.scaledToHeight(realHeight));
}
else
{
QLabel::setPixmap(m_pix.scaledToWidth(realWidth));
}
}
}
double ExtendedLabel::aspectRatio(int w, int h)
{
return static_cast<double>(w) / static_cast<double>(h);
}
int ExtendedLabel::heightForWidth(int width) const
{
if (!m_pix.isNull())
{
return static_cast<int>(static_cast<qreal>(m_pix.height()) * width / m_pix.width());
}
return QLabel::heightForWidth(width);
}
void ExtendedLabel::mousePressEvent(QMouseEvent* /*event*/)
{
emit clicked();
}
} // namespace AzQtComponents
#include "Components/moc_ExtendedLabel.cpp"
@@ -0,0 +1,62 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/PlatformDef.h>
AZ_PUSH_DISABLE_WARNING(4251 4244 4800, "-Wunknown-warning-option") // 4251: class 'QScopedPointer<QBrushData,QBrushDataPointerDeleter>' needs to have dll-interface to be used by clients of class 'QBrush'
// 4800: 'uint': forcing value to bool 'true' or 'false' (performance warning)
#include <QLabel>
AZ_POP_DISABLE_WARNING
#include <AzQtComponents/AzQtComponentsAPI.h>
#endif
//! An improved QLabel control with better QPixmap resize functionality and click signal
/*!
ExtendedLabel allows to automatically downscale its QPixmap
while preserving image's aspect ratio. The maximum QPixmap size is set
to the minimum of [QLabel size] OR [maxPixMapWidth and maxPixMapHeight values].
*/
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API ExtendedLabel
: public QLabel
{
Q_OBJECT
public:
explicit ExtendedLabel(QWidget* parent = nullptr);
int heightForWidth(int width) const override;
public Q_SLOTS:
void setPixmap(const QPixmap& p);
void resizeEvent(QResizeEvent* event) override;
Q_SIGNALS:
void clicked();
protected:
void mousePressEvent(QMouseEvent* event) override;
private:
QPixmap m_pix;
QSize m_pixmapSize;
void rescale();
static double aspectRatio(int w, int h);
};
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,274 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <AzQtComponents/Components/DockMainWindow.h>
#include <AzQtComponents/Components/DockTabWidget.h>
#include <AzQtComponents/Components/StyledDockWidget.h>
#include <AzQtComponents/Components/FancyDockingDropZoneWidget.h>
#include <QColor>
#include <QMetaType>
#include <QPixmap>
#include <QString>
#include <QStringList>
#include <QPointer>
#include <QScopedPointer>
#include <QMap>
#include <QVariant>
#include <QScreen>
#include <QSize>
#include <QApplication>
#endif
class QDesktopWidget;
class QTimer;
namespace AzQtComponents
{
class FancyDockingGhostWidget;
class FancyDockingDropZoneWidget;
/**
* This class implements the Visual Studio docking style for a QMainWindow.
* To use is, just create an instance of this class and give the QMainWindow as
* a parent.
*
* One should use saveState/restoreState/restoreDockWidget on the instance
* if this class rather than directly on the QMainWindow.
*/
class AZ_QT_COMPONENTS_API FancyDocking
: public QWidget
{
Q_OBJECT
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] mainWindow: the window to apply the fancy docking to
//! \param[in] identifierPrefix: prefix to use for properties on widgets/windows to flag them
//! as owned by this instance of the FancyDocking manager. Can be anything, as long
//! as it is unique
FancyDocking(DockMainWindow* mainWindow, const char* identifierPrefix = "fancydocking");
~FancyDocking();
struct TabContainerType
{
QString floatingDockName;
QStringList tabNames;
int currentIndex;
};
// Grabbing a widget needs both a pixmap and a size, because the size of the pixmap
// will not take the screen's scaleFactor into account
struct WidgetGrab
{
QPixmap screenGrab;
QSize size;
};
bool restoreState(const QByteArray& layout_data);
QByteArray saveState();
bool restoreDockWidget(QDockWidget* dock);
DockTabWidget* tabifyDockWidget(QDockWidget* dropTarget, QDockWidget* dropped, QMainWindow* mainWindow, WidgetGrab* droppedGrab = nullptr);
void setAbsoluteCornersForDockArea(QMainWindow* mainWindow, Qt::DockWidgetArea area);
void makeDockWidgetFloating(QDockWidget* dock, const QRect& geometry);
void splitDockWidget(QMainWindow* mainWindow, QDockWidget* target, QDockWidget* dropped, Qt::Orientation orientation);
void disableAutoSaveLayout(QDockWidget* dock);
void enableAutoSaveLayout(QDockWidget* dock);
bool IsDockWidgetBeingDragged(QDockWidget* dock);
DockTabWidget* createTabWidget(QMainWindow* mainWindow, QDockWidget* widgetToReplace, QString name = QString());
protected:
bool eventFilter(QObject* watched, QEvent* event) override;
private Q_SLOTS:
void onTabIndexPressed(int index);
void onTabCountChanged(int count);
void onCurrentTabIndexChanged(int index);
void onTabWidgetInserted(QWidget* widget);
void onUndockTab(int index);
void onTabBarDoubleClicked();
void onUndockDockWidget();
void updateDockingGeometry();
void handleScreenAdded(QScreen* screen);
void handleScreenRemoved(QScreen* screen);
void onDropZoneHoverFadeInUpdate();
void updateFloatingPixmap();
private:
typedef QMap<QString, QPair<QStringList, QByteArray> > SerializedMapType;
typedef QMap<QString, TabContainerType> SerializedTabType;
// Used to save/restore, history as follows:
// 5001 - Initial version
// 5002 - Added custom tab containers
// 5003 - Extended tab restoration to floating windows
// 5004 - Got rid of single floating dock widgets, all are now floating
// main windows so that they get the extra top bar for repositioning
// without engaging docking
// 5005 - Reworked the m_restoreFloatings to handle restoring floating panes
// to their previous location properly since now even single floating
// panes are within a floating QMainWindow
enum
{
VersionMarker = 5005
};
enum SnappedSide
{
SnapLeft = 0x1,
SnapRight = 0x2,
SnapTop = 0x4,
SnapBottom = 0x8
};
bool dockMousePressEvent(QDockWidget* dock, QMouseEvent* event);
bool dockMouseMoveEvent(QDockWidget* dock, QMouseEvent* event);
bool dockMouseReleaseEvent(QDockWidget* dock, QMouseEvent* event);
bool canDragDockWidget(QDockWidget* dock, QPoint mousePos);
void startDraggingWidget(QDockWidget* dock, const QPoint& pressPos, int tabIndex = -1);
Qt::DockWidgetArea dockAreaForPos(const QPoint& globalPos);
QWidget* dropTargetForWidget(QWidget* widget, const QPoint& globalPos, QWidget* exclude) const;
QWidget* dropWidgetUnderMouse(const QPoint& globalPos, QWidget* exclude) const;
QRect getAbsoluteDropZone(QWidget* dock, Qt::DockWidgetArea& area, const QPoint& globalPos = QPoint());
void setupDropZones(QWidget* dock, const QPoint& globalPos = QPoint());
void raiseDockWidgets();
void dropDockWidget(QDockWidget* dock, QWidget* onto, Qt::DockWidgetArea area);
QMainWindow* createFloatingMainWindow(const QString& name, const QRect& geometry, bool skipTitleBarDrawing = false);
QString getUniqueDockWidgetName(const QString& prefix);
void destroyIfUseless(QMainWindow* mw);
void clearDraggingState();
QDockWidget* getTabWidgetContainer(QObject* obj);
void undockDockWidget(QDockWidget* dockWidget, QDockWidget* placeholder = nullptr);
void SetDragOrDockOnFloatingMainWindow(QMainWindow* mainWindow);
int NumVisibleDockWidgets(QMainWindow* mainWindow);
void QueueUpdateFloatingWindowTitle(QMainWindow* mainWindow);
void AddTitleBarButtons(AzQtComponents::DockTabWidget* tabWidget, AzQtComponents::TitleBar* titleBar);
void RemoveTitleBarButtons(AzQtComponents::DockTabWidget* tabWidget, AzQtComponents::TitleBar* titleBar = nullptr);
void UpdateTitleBars(QMainWindow* mainWindow);
bool IsFloatingDockWidget(QDockWidget* dockWidget);
void StartDropZone(QWidget* dropZoneContainer, const QPoint& globalPos);
void StopDropZone();
bool ForceTabbedDocksEnabled() const;
void RepaintFloatingIndicators();
void SetFloatingPixmapClipping(QWidget* dropOnto, Qt::DockWidgetArea area);
void AdjustForSnapping(QRect& rect, QScreen* cursorScreen);
bool AdjustForSnappingToScreenEdges(QRect& rect, QScreen* cursorScreen);
bool AdjustForSnappingToFloatingWindow(QRect& rect, const QRect& floatingRect);
bool AnyDockWidgetsExist(QStringList names);
int titleBarOffset(const QDockWidget* dockWidget) const;
QPoint multiscreenMapFromGlobal(const QPoint& point) const;
bool WidgetContainsPoint(QWidget* widget, const QPoint& pos) const;
QMainWindow* m_mainWindow;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
QList<QScreen*> m_desktopScreens;
#ifdef AZ_PLATFORM_WINDOWS
QList<QWidget*> m_perScreenFullScreenWidgets;
#endif
// An empty QWidget used as a placeholder when dragging a dock window
// as opposed to creating a new one each time we start dragging a dock widget
QWidget* m_emptyWidget;
FancyDockingDropZoneState m_dropZoneState;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
// When a user hovers over a drop zone, we will fade it in using this timer
QTimer* m_dropZoneHoverFadeInTimer;
struct DragState
{
QPointer<QDockWidget> dock;
QPoint pressPos;
WidgetGrab dockWidgetScreenGrab; // Save a screen grab of the widget we are dragging so we can paint it as we drag
QPointer<DockTabWidget> tabWidget;
QPointer<QWidget> draggedWidget;
QPointer<QDockWidget> draggedDockWidget; // This could be different from m_state.dock if the dock widget being dragged is tabbed
QPointer<QDockWidget> floatingDockContainer;
bool updateInProgress = false;
int snappedSide = 0;
// A setter, so you don't forget to initialize one of them
void setPlaceholder(const QRect& rect, QScreen* screen)
{
m_placeholderScreen = screen;
m_placeholder = rect;
}
// Overload
void setPlaceholder(const QRect& rect, int screenIndex)
{
setPlaceholder(rect, screenIndex == -1 ? nullptr : qApp->screens().at(screenIndex));
}
QRect placeholder() const
{
return m_placeholder;
}
QScreen* placeholderScreen() const
{
return m_placeholderScreen;
}
private:
QPointer<QScreen> m_placeholderScreen;
QRect m_placeholder;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
} m_state;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
// map QDockWidget name with its last floating dock.
QMap<QString, QString> m_placeholders;
// map floating dock name with it's serialization and the geometry
QMap<QString, QPair<QByteArray, QRect> > m_restoreFloatings;
// Map of the last tab container a dock widget was tabbed in (if any)
QMap<QString, QString> m_lastTabContainerForDockWidget;
// Map of the last floating screen grab of a dock widget based on its name
// so we can restore its previous floating size/placeholder image when
// dragging it around
QMap<QString, WidgetGrab> m_lastFloatingScreenGrab;
QScopedPointer<FancyDockingGhostWidget> m_ghostWidget;
QMap<QScreen*, FancyDockingDropZoneWidget*> m_dropZoneWidgets;
QList<FancyDockingDropZoneWidget*> m_activeDropZoneWidgets;
QList<QString> m_orderedFloatingDockWidgetNames;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
QString m_floatingWindowIdentifierPrefix;
QString m_tabContainerIdentifierPrefix;
};
} // namespace AzQtComponents
Q_DECLARE_METATYPE(AzQtComponents::FancyDocking::TabContainerType);
@@ -0,0 +1,360 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/FancyDockingDropZoneWidget.h>
#include <QMainWindow>
#include <QCloseEvent>
#include <QPainter>
#include <QGuiApplication>
#include <QScreen>
#include <QWindow>
#include <AzQtComponents/Utilities/QtWindowUtilities.h>
namespace AzQtComponents
{
static FancyDockingDropZoneConstants g_Constants;
FancyDockingDropZoneConstants::FancyDockingDropZoneConstants()
{
draggingDockWidgetOpacity = 0.6;
dropZoneOpacity = 0.4;
dropZoneSizeInPixels = 40;
minDockSizeBeforeDropZoneScalingInPixels = dropZoneSizeInPixels * 3;
dropZoneScaleFactor = 0.25;
centerTabDropZoneScale = 0.5;
centerTabIconScale = 0.5;
dropZoneColor = QColor(155, 155, 155);
dropZoneBorderColor = Qt::black;
dropZoneBorderInPixels = 1;
absoluteDropZoneSizeInPixels = 25;
dockingTargetDelayMS = 110;
dropZoneHoverFadeUpdateIntervalMS = 20;
dropZoneHoverFadeIncrement = dropZoneOpacity / (dockingTargetDelayMS / dropZoneHoverFadeUpdateIntervalMS);
centerDropZoneIconPath = QString(":/stylesheet/img/UI20/docking/tabs_icon.svg");
}
FancyDockingDropZoneWidget::FancyDockingDropZoneWidget(QMainWindow* mainWindow, QWidget* coordinatesRelativeTo, QScreen* screen, FancyDockingDropZoneState* dropZoneState)
// NOTE: this will not work with multiple monitors if this widget has a parent. The floating drop zone
// won't render on anything other than the parent's screen for some reason, if the parent is set.
: QWidget(nullptr, Qt::WindowFlags(Qt::ToolTip | Qt::BypassWindowManagerHint | Qt::FramelessWindowHint))
, m_mainWindow(mainWindow)
, m_relativeTo(coordinatesRelativeTo)
, m_screen(screen)
, m_dropZoneState(dropZoneState)
{
m_dropZoneState->registerListener(this);
setAttribute(Qt::WA_TranslucentBackground);
setAttribute(Qt::WA_TransparentForMouseEvents);
setAttribute(Qt::WA_NoSystemBackground);
setAutoFillBackground(false);
setGeometry(screen->availableGeometry());
Stop();
}
FancyDockingDropZoneWidget::~FancyDockingDropZoneWidget()
{
m_dropZoneState->unregisterListener(this);
}
QScreen* FancyDockingDropZoneWidget::GetScreen()
{
return m_screen;
}
void FancyDockingDropZoneWidget::Start()
{
QWindow *window = windowHandle();
if (!window)
{
// So we don't crash when setting a pixmap before having a window
create();
window = windowHandle();
}
if (window->screen() != m_screen)
{
// Qt usually handles screens with different scale factors correctly, however
// when the geometry's origin is in a gap between monitors it won't map to native
// coordinates correctly, we have to set the screen before setting geometry, so that
// Qt uses the correct scale factor when mapping to native coordinates.
// (By gap we mean that monitors are not consecutive, they might be consecutive
// in native coordinates, but due to using scale factors there will be gaps that don't
// belong to any screen.
window->setScreen(m_screen);
}
QRect screenGeometry = window->screen()->geometry();
if (screenGeometry != geometry())
{
setGeometry(screenGeometry);
}
show();
}
void FancyDockingDropZoneWidget::Stop()
{
#ifdef AZ_PLATFORM_MAC
// macOS needs a bit help, WA_TransparentForMouseEvents doesn't always work
// there's a rare edge case when dragging while a popup is open which leads to events being sent to this window
// even if it's not visible. As far as I can tell it's not a Qt bug, the OS sends the events to this window.
// So here's this workaround
setGeometry(0, 0, 1, 1);
#endif
hide();
}
void FancyDockingDropZoneWidget::paintEvent(QPaintEvent* ev)
{
(void)ev;
if (!isVisible())
{
return;
}
if (!m_dropZoneState->dragging())
{
return;
}
bool modifiedKeyPressed = CheckModifierKey();
if (!modifiedKeyPressed)
{
QPainter painter(this);
QWidget* dropOnto = m_dropZoneState->dropOnto();
if (dropOnto)
{
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dropOnto);
if (!mainWindow)
{
mainWindow = qobject_cast<QMainWindow*>(dropOnto->parentWidget());
}
// If our drop target isn't a QMainWindow, then retrieve the base Editor QMainWindow.
// Since *this* widget is not properly parented against the FancyDocking widget,
// (which is because Qt doesn't support rendering across multiple screens properly)
// we need to manually clip against any floating windows.
if (!mainWindow || mainWindow == m_mainWindow)
{
AzQtComponents::SetClipRegionForDockingWidgets(this, painter, m_mainWindow);
}
}
// Draw all of the normal drop zones if they exist (if a dock widget is hovered over)
painter.setPen(Qt::NoPen);
painter.setOpacity(g_Constants.dropZoneOpacity);
auto dropZones = m_dropZoneState->dropZones();
for (auto it = dropZones.cbegin(); it != dropZones.cend(); ++it)
{
const Qt::DockWidgetArea area = it.key();
const QPolygon& dropZoneShape = it.value();
paintDropZone(area, dropZoneShape, painter);
}
// Draw the absolute drop zone and drop borders if they exist
fillAbsoluteDropZone(painter);
paintDropBorderLines(painter);
}
}
void FancyDockingDropZoneWidget::closeEvent(QCloseEvent* ev)
{
ev->ignore(); // Don't close the window.
}
void FancyDockingDropZoneWidget::paintDropZone(const Qt::DockWidgetArea area, QPolygon dropZoneShape, QPainter& painter)
{
painter.save();
// If this drop zone is currently hovered over, then set the on hover color
// and the hover opacity as it fades in
if (area == m_dropZoneState->dropArea() && !m_dropZoneState->onAbsoluteDropZone())
{
painter.setOpacity(m_dropZoneState->dropZoneHoverOpacity());
painter.setBrush(m_dropZoneState->dropZoneColorOnHover());
}
// Otherwise, set the normal color
else
{
painter.setBrush(g_Constants.dropZoneColor);
}
// negate the window position to offset everything by that much
QPoint offset = mapFromGlobal(m_relativeTo->mapToGlobal(QPoint(0, 0)));
dropZoneShape = dropZoneShape.translated(offset);
// If this is the center tab drop zone, then we need to draw a circle and the tabs icon
if (area == Qt::AllDockWidgetAreas)
{
// Use antialiasing to make sure that ellipses aren't jagged
painter.setRenderHint(QPainter::Antialiasing);
// If the center drop zone isn't currently hovered over, then draw the
// circle first so that the tab icon is drawn on top
const QRect& dropZoneRect = dropZoneShape.boundingRect();
if (area != m_dropZoneState->dropArea())
{
painter.drawEllipse(dropZoneRect);
}
// Scale the tabs icon based on the drop zone size and our specified offset
// Doing this through QIcon to make sure that SVG is rendered already in desired resolution
const QSize& dropZoneSize = dropZoneRect.size();
const QSize requestedIconSize = dropZoneSize * g_Constants.centerTabIconScale;
const QIcon dropZoneIcon = QIcon(g_Constants.centerDropZoneIconPath);
const QPixmap dropZonePixmap = dropZoneIcon.pixmap(requestedIconSize);
const QSize receivedIconSize = dropZoneIcon.actualSize(requestedIconSize);
// Draw the icon in the center of the drop zone with full opacity
const QPoint& dropZoneCenter = dropZoneRect.center();
int tabsIconX = dropZoneCenter.x() - (receivedIconSize.width() / 2);
int tabsIconY = dropZoneCenter.y() - (receivedIconSize.height() / 2);
qreal opacity = painter.opacity();
painter.setOpacity(1);
painter.drawPixmap(tabsIconX, tabsIconY, dropZonePixmap);
// If the center drop zone is currently hovered over, then draw the
// circle for the drop zone after the tab icon so it gets drawn on
// top so they both get the hover color
if (area == m_dropZoneState->dropArea())
{
painter.setOpacity(opacity);
painter.drawEllipse(dropZoneRect);
}
}
// Otherwise just draw the trapezoid for the drop zone
else
{
painter.drawPolygon(dropZoneShape);
}
painter.restore();
}
void FancyDockingDropZoneWidget::fillAbsoluteDropZone(QPainter& painter)
{
// Draw the absolute drop zone if it is valid with the proper color (on hover vs normal)
if (shouldFillAbsoluteDropZone())
{
// negate the window position to offset everything by that much
QPoint offset = mapFromGlobal(m_relativeTo->mapToGlobal(QPoint(0, 0)));
QRect absoluteDropZoneRect = m_dropZoneState->absoluteDropZoneRect().translated(offset);
painter.save();
if (m_dropZoneState->onAbsoluteDropZone())
{
painter.setOpacity(m_dropZoneState->dropZoneHoverOpacity());
painter.setBrush(m_dropZoneState->dropZoneColorOnHover());
}
else
{
painter.setBrush(g_Constants.dropZoneColor);
}
painter.drawRect(absoluteDropZoneRect);
painter.restore();
}
}
bool FancyDockingDropZoneWidget::shouldFillAbsoluteDropZone() const
{
// Draw the absolute drop zone if it is valid with the proper color (on hover vs normal)
return m_dropZoneState->absoluteDropZoneRect().isValid();
}
bool FancyDockingDropZoneWidget::shouldPaintDropBorderLines() const
{
// Don't draw the border lines if we don't have a valid drop target, or if
// we don't have multiple drop zones to draw (if there's only one drop zone, it means
// we only have the center tab drop zone)
return m_dropZoneState->dropOnto() && m_dropZoneState->dropZones().size() > 1;
}
void FancyDockingDropZoneWidget::paintDropBorderLines(QPainter& painter)
{
// Don't draw the border lines if we don't have a valid drop target, or if
// there are no normal drop zones
if (!shouldPaintDropBorderLines())
{
return;
}
// Retrieve the outer (dock widget) and inner corner points so that we can draw the lines
// separating their borders.
QWidget* dropOnto = m_dropZoneState->dropOnto();
QRect dockDropZoneRect = m_dropZoneState->dockDropZoneRect();
const QPoint topLeft = mapFromGlobal(dropOnto->mapToGlobal(dockDropZoneRect.topLeft()));
const QPoint topRight = mapFromGlobal(dropOnto->mapToGlobal(dockDropZoneRect.topRight()));
const QPoint bottomLeft = mapFromGlobal(dropOnto->mapToGlobal(dockDropZoneRect.bottomLeft()));
const QPoint bottomRight = mapFromGlobal(dropOnto->mapToGlobal(dockDropZoneRect.bottomRight()));
// negate the window position to offset everything by that much
QPoint offset = mapFromGlobal(m_relativeTo->mapToGlobal(QPoint(0, 0)));
QRect innerDropZoneRect = m_dropZoneState->innerDropZoneRect().translated(offset);
const QPoint innerTopLeft = innerDropZoneRect.topLeft();
const QPoint innerTopRight = innerDropZoneRect.topRight();
const QPoint innerBottomLeft = innerDropZoneRect.bottomLeft();
const QPoint innerBottomRight = innerDropZoneRect.bottomRight();
// Draw the lines using the appropriate pen
QPen dropZoneBorderPen(g_Constants.dropZoneBorderColor);
dropZoneBorderPen.setWidth(g_Constants.dropZoneBorderInPixels);
painter.setPen(dropZoneBorderPen);
painter.setOpacity(1);
painter.drawLine(topLeft, innerTopLeft);
painter.drawLine(topRight, innerTopRight);
painter.drawLine(bottomLeft, innerBottomLeft);
painter.drawLine(bottomRight, innerBottomRight);
// If we have a valid absolute drop zone, then draw a border line between it and the drop zone it shares a side with
if (m_dropZoneState->absoluteDropZoneRect().isValid())
{
switch (m_dropZoneState->absoluteDropZoneArea())
{
case Qt::LeftDockWidgetArea:
painter.drawLine(topLeft, bottomLeft);
break;
case Qt::RightDockWidgetArea:
painter.drawLine(topRight, bottomRight);
break;
case Qt::TopDockWidgetArea:
painter.drawLine(topLeft, topRight);
break;
case Qt::BottomDockWidgetArea:
painter.drawLine(bottomLeft, bottomRight);
break;
}
}
}
bool FancyDockingDropZoneWidget::CheckModifierKey()
{
// use query instead of keyboardModifiers() so that it queries the actual state,
// instead of the state as recorded by events processed so far.
// Slower, but more accurate when we're dragging the window
return (QGuiApplication::queryKeyboardModifiers() & Qt::ControlModifier);
}
} // namespace AzQtComponents
#include "Components/moc_FancyDockingDropZoneWidget.cpp"
@@ -0,0 +1,341 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <qglobal.h> // For qreal
#include <qnamespace.h> // For Qt::DockArea
#include <QColor>
#include <QString>
#include <QMap>
#include <QPointer>
#include <QRect>
#include <QWidget>
#include <QPolygon>
#endif
class QScreen;
class QMainWindow;
class QPainter;
namespace AzQtComponents
{
struct AZ_QT_COMPONENTS_API FancyDockingDropZoneConstants
{
// Constant for the opacity of the screen grab for the dock widget being dragged
qreal draggingDockWidgetOpacity;
// Constant for the opacity of the normal drop zones
qreal dropZoneOpacity;
// Constant for the default drop zone size (in pixels)
int dropZoneSizeInPixels;
// Constant for the dock width/height size (in pixels) before we need to start
// scaling down the drop zone sizes, or else they will overlap with the center
// tab icon or each other
int minDockSizeBeforeDropZoneScalingInPixels;
// Constant for the factor by which we must scale down the drop zone sizes once
// the dock width/height size is too small
qreal dropZoneScaleFactor;
// Constant for the percentage to scale down the inner drop zone rectangle for the center tab drop zone
qreal centerTabDropZoneScale;
// Constant for the percentage to scale down the center tab drop zone for the center tab icon
qreal centerTabIconScale;
// Constant for the drop zone hotspot default color
QColor dropZoneColor;
// Constant for the drop zone border color
QColor dropZoneBorderColor;
// Constant for the border width in pixels separating the drop zones
int dropZoneBorderInPixels;
// Constant for the border width in pixels separating the drop zones
int absoluteDropZoneSizeInPixels;
// Constant for the delay (in milliseconds) before a drop zone becomes active
// once it is hovered over
int dockingTargetDelayMS;
// Constant for the rate at which we will update (fade in) the drop zone opacity
// when hovered over (in milliseconds)
int dropZoneHoverFadeUpdateIntervalMS;
// Constant for the incremental opacity increase for the hovered drop zone
// that will fade in to the full drop zone opacity in the desired time
qreal dropZoneHoverFadeIncrement;
// Constant for the path to the center drop zone tabs icon
QString centerDropZoneIconPath;
FancyDockingDropZoneConstants();
FancyDockingDropZoneConstants(const FancyDockingDropZoneConstants&) = delete;
FancyDockingDropZoneConstants& operator=(const FancyDockingDropZoneConstants&) = delete;
};
class FancyDockingDropZoneState
{
public:
const QMap<Qt::DockWidgetArea, QPolygon> dropZones() const
{
return m_dropZones;
}
void setDropZones(const QMap<Qt::DockWidgetArea, QPolygon>& zones)
{
if (zones != m_dropZones)
{
m_dropZones = zones;
repaintDropZoneWidgets();
}
}
bool hasDropZones() const
{
return !m_dropZones.isEmpty();
}
QPointer<QWidget> dropOnto() const
{
return m_dropOnto;
}
void setDropOnto(QWidget *widget)
{
if (m_dropOnto != widget)
{
m_dropOnto = widget;
repaintDropZoneWidgets();
}
}
Qt::DockWidgetArea dropArea() const
{
return m_dropArea;
}
void setDropArea(Qt::DockWidgetArea area)
{
if (m_dropArea != area)
{
m_dropArea = area;
repaintDropZoneWidgets();
}
}
qreal dropZoneHoverOpacity()
{
return m_dropZoneHoverOpacity;
}
void setDropZoneHoverOpacity(qreal opacity)
{
if (m_dropZoneHoverOpacity != opacity)
{
m_dropZoneHoverOpacity = opacity;
repaintDropZoneWidgets();
}
}
QColor dropZoneColorOnHover()
{
return m_dropZoneColorOnHover;
}
void setDropZoneColorOnHover(const QColor &color)
{
if (m_dropZoneColorOnHover != color)
{
m_dropZoneColorOnHover = color;
repaintDropZoneWidgets();
}
}
bool onAbsoluteDropZone() const
{
return m_onAbsoluteDropZone;
}
void setOnAbsoluteDropZone(bool absolute)
{
// Don't issue a repaint. This doesn't affect our painting and only used by FancyDocking.
// FancyDocking toggles this per each mouse move event, would be a big CPU waste
m_onAbsoluteDropZone = absolute;
}
QRect absoluteDropZoneRect() const
{
return m_absoluteDropZoneRect;
}
void setAbsoluteDropZoneRect(QRect rect)
{
if (m_absoluteDropZoneRect != rect)
{
m_absoluteDropZoneRect = rect;
repaintDropZoneWidgets();
}
}
Qt::DockWidgetArea absoluteDropZoneArea() const
{
return m_absoluteDropZoneArea;
}
void setAbsoluteDropZoneArea(Qt::DockWidgetArea area)
{
if (m_absoluteDropZoneArea != area)
{
m_absoluteDropZoneArea = area;
repaintDropZoneWidgets();
}
}
QRect dockDropZoneRect() const
{
return m_dockDropZoneRect;
}
void setDockDropZoneRect(QRect rect)
{
if (m_dockDropZoneRect != rect)
{
m_dockDropZoneRect = rect;
repaintDropZoneWidgets();
}
}
QRect innerDropZoneRect() const
{
return m_innerDropZoneRect;
}
void setInnerDropZoneRect(QRect rect)
{
if (m_innerDropZoneRect != rect)
{
m_innerDropZoneRect = rect;
repaintDropZoneWidgets();
}
}
bool dragging() const
{
return m_dragging;
}
void setDragging(bool dragging)
{
if (m_dragging != dragging)
{
m_dragging = dragging;
repaintDropZoneWidgets();
}
}
void registerListener(QWidget* listener)
{
m_listeners << listener;
}
void unregisterListener(QWidget* listener)
{
m_listeners.removeOne(listener);
}
private:
void repaintDropZoneWidgets()
{
for (QWidget* listener : m_listeners)
{
listener->update();
}
}
// The drop zone area mapped to the QPolygon in which we can drop QDockWidget for that zone
QMap<Qt::DockWidgetArea, QPolygon> m_dropZones;
// The QMainWindow or QDockWidget on which we are going to drop
QPointer<QWidget> m_dropOnto;
Qt::DockWidgetArea m_dropArea;
// Used in conjunction with the above timer, the opacity of a drop zone
// when hovered over will fade in dynamically
qreal m_dropZoneHoverOpacity = 0.0f;
QColor m_dropZoneColorOnHover;
// The absolute drop zone rectangle and drop area
bool m_onAbsoluteDropZone = false;
QRect m_absoluteDropZoneRect;
Qt::DockWidgetArea m_absoluteDropZoneArea;
// The outer and inner rectangles of our current drop zones
QRect m_dockDropZoneRect;
QRect m_innerDropZoneRect;
bool m_dragging = false;
QVector<QWidget*> m_listeners;
};
// Splitting this out into a separate widget so that we can have one per screen
// and so that they can be detached from any other widgets.
// This seems to be the only reliable way to get the drop zone rendering to
// work across multiple monitors under a number of different scenarios
// such as multiple monitors with different scale factors and different
// monitors selected as the primary monitor (which can matter)
class AZ_QT_COMPONENTS_API FancyDockingDropZoneWidget
: public QWidget
{
Q_OBJECT
public:
explicit FancyDockingDropZoneWidget(QMainWindow* mainWindow, QWidget* coordinatesRelativeTo, QScreen* screen, FancyDockingDropZoneState* dropZoneState);
~FancyDockingDropZoneWidget();
QScreen* GetScreen();
void Start();
void Stop();
static bool CheckModifierKey();
protected:
void paintEvent(QPaintEvent* ev) override;
void closeEvent(QCloseEvent* ev) override;
private:
void paintDropZone(const Qt::DockWidgetArea area, QPolygon dropZoneShape, QPainter& painter);
void fillAbsoluteDropZone(QPainter& painter);
bool shouldFillAbsoluteDropZone() const;
void paintDropBorderLines(QPainter& painter);
bool shouldPaintDropBorderLines() const;
QMainWindow* m_mainWindow;
QWidget* m_relativeTo;
QScreen* m_screen;
FancyDockingDropZoneState* const m_dropZoneState;
};
} // namespace AzQtComponents
@@ -0,0 +1,155 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/FancyDockingGhostWidget.h>
#include <QDebug>
#include <QCloseEvent>
#include <QScreen>
#include <QWindow>
#include <QMainWindow>
#include <QRect>
#include <QPainter>
#include <AzQtComponents/Utilities/QtWindowUtilities.h>
namespace AzQtComponents
{
FancyDockingGhostWidget::FancyDockingGhostWidget(QMainWindow* mainWindow, QWidget* parent) :
QWidget(parent, Qt::WindowFlags(Qt::ToolTip | Qt::BypassWindowManagerHint | Qt::FramelessWindowHint)),
m_mainWindow(mainWindow)
{
setAttribute(Qt::WA_TranslucentBackground);
setAttribute(Qt::WA_TransparentForMouseEvents);
setAttribute(Qt::WA_NoSystemBackground);
setAutoFillBackground(false);
}
FancyDockingGhostWidget::~FancyDockingGhostWidget()
{
}
void FancyDockingGhostWidget::setPixmapVisible(bool visible)
{
if (m_visible != visible)
{
m_visible = visible;
if (visible)
{
update();
}
}
}
void FancyDockingGhostWidget::setPixmap(const QPixmap& pixmap, const QRect& targetRect, QScreen* screen)
{
const bool needsRepaint = m_pixmap.cacheKey() != pixmap.cacheKey() || m_clipToWidgets;
m_pixmap = pixmap;
if (pixmap.isNull() || targetRect.isNull() || !screen)
{
setPixmapVisible(false);
return;
}
QWindow *window = windowHandle();
if (!window)
{
// So we don't crash when setting a pixmap before having a window
create();
window = windowHandle();
}
if (window->screen() != screen)
{
// Qt usually handles screens with different scale factors correctly, however
// when the geometry's origin is in a gap between monitors it won't map to native
// coordinates correctly, we have to set the screen before setting geometry, so that
// Qt uses the correct scale factor when mapping to native coordinates.
// (By gap we mean that monitors are not consecutive, they might be consecutive
// in native coordinates, but due to using scale factors there will be gaps that don't
// belong to any screen.
window->setScreen(screen);
}
setGeometry(targetRect);
setPixmapVisible(true);
if (needsRepaint)
{
update();
}
}
// The equivalent of lowering the pixmap under the parent's dock widgets
void FancyDockingGhostWidget::EnableClippingToDockWidgets()
{
if (!m_clipToWidgets)
{
m_clipToWidgets = true;
update();
}
}
// The equivalent of raising above all of the parent's dock widgets
void FancyDockingGhostWidget::DisableClippingToDockWidgets()
{
if (m_clipToWidgets)
{
m_clipToWidgets = false;
update();
}
}
void FancyDockingGhostWidget::closeEvent(QCloseEvent* ev)
{
ev->ignore(); // Don't close the window.
}
void FancyDockingGhostWidget::paintEvent(QPaintEvent* /* ev */)
{
QPainter painter(this);
if (m_visible && !m_pixmap.isNull())
{
if (m_clipToWidgets)
{
painter.save();
AzQtComponents::SetClipRegionForDockingWidgets(this, painter, m_mainWindow);
}
// Our rendered pixmaps might be smaller than the widget size
// We put them at the bottom of the widget.
// It's specifically because we use this widget to render
// floating dock widgets, and they often render without the titlebar
// but the geometry of the widget will take the title bar into account.
QSize widgetSize = rect().size();
int aspectRatioHeight = (m_pixmap.height() * widgetSize.width()) / m_pixmap.width();
int yOffset = 0;
if (aspectRatioHeight < widgetSize.height())
{
yOffset = widgetSize.height() - aspectRatioHeight;
}
painter.drawPixmap(QRect(0, yOffset, widgetSize.width(), aspectRatioHeight), m_pixmap);
if (m_clipToWidgets)
{
painter.restore();
}
}
else
{
// fill to blank, in case anything is cached
painter.fillRect(rect(), QColor(0, 0, 0, 0));
}
}
}
@@ -0,0 +1,53 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QWidget>
#include <QPixmap>
class QScreen;
class QMainWindow;
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API FancyDockingGhostWidget
: public QWidget
{
public:
explicit FancyDockingGhostWidget(QMainWindow* mainWindow = nullptr, QWidget* parent = nullptr);
~FancyDockingGhostWidget() override;
void setPixmap(const QPixmap& pixmap, const QRect& targetRect, QScreen* screen);
void Enable() { m_visible = true; }
void Disable() { m_visible = false; }
// The equivalent of lowering the pixmap under the parent's dock widgets
void EnableClippingToDockWidgets();
// The equivalent of raising above all of the parent's dock widgets
void DisableClippingToDockWidgets();
protected:
void closeEvent(QCloseEvent* ev) override;
void paintEvent(QPaintEvent* ev) override;
private:
void setPixmapVisible(bool);
QMainWindow* const m_mainWindow;
QPixmap m_pixmap;
bool m_visible = false; // maintain our own flag, so that we're always ready to render ignoring Qt's widget caching system
bool m_clipToWidgets = false;
};
} // namespace AzQtComponents
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,344 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QScopedPointer>
#include <QFrame>
#include <QStyledItemDelegate>
#include <QMap>
#include <QVariant>
#include <QMenu>
#include <QTimer>
#include <AzCore/std/chrono/chrono.h>
#endif
namespace Ui
{
class FilteredSearchWidget;
}
class FlowLayout;
class QTreeView;
class QSortFilterProxyModel;
class QStandardItemModel;
class QStandardItem;
class QSettings;
class QLineEdit;
class QToolButton;
class QLabel;
class QHBoxLayout;
class QIcon;
class QBoxLayout;
namespace AzQtComponents
{
class Style;
class FilteredSearchItemDelegate;
class AZ_QT_COMPONENTS_API FilterCriteriaButton
: public QFrame
{
Q_OBJECT
public:
enum class ExtraButtonType
{
None,
Locked,
Unlocked,
Visible,
};
explicit FilterCriteriaButton(const QString& labelText, QWidget* parent = nullptr, FilterCriteriaButton::ExtraButtonType type = FilterCriteriaButton::ExtraButtonType::None, const QString& extraIconFileName = QString());
protected:
QHBoxLayout* m_frameLayout;
QLabel* m_tagLabel;
signals:
void RequestClose();
void ExtraButtonClicked(FilterCriteriaButton::ExtraButtonType type);
};
struct AZ_QT_COMPONENTS_API SearchTypeFilter
{
QString category;
QString displayName;
QString extraIconFilename;
QVariant metadata;
int globalFilterValue;
bool enabled = false;
FilterCriteriaButton::ExtraButtonType typeExtraButton = FilterCriteriaButton::ExtraButtonType::None;
SearchTypeFilter() {}
SearchTypeFilter(const QString& category, const QString& displayName, FilterCriteriaButton::ExtraButtonType type = FilterCriteriaButton::ExtraButtonType::None, const QString& extraIconFilename = QString(), const QVariant& metadata = {}, int globalFilterValue = -1)
: category(category)
, displayName(displayName)
, extraIconFilename(extraIconFilename)
, metadata(metadata)
, globalFilterValue(globalFilterValue)
, typeExtraButton(type)
{
}
};
using SearchTypeFilterList = QVector<SearchTypeFilter>;
class SearchTypeSelectorTreeView;
class AZ_QT_COMPONENTS_API SearchTypeSelector : public QMenu
{
Q_OBJECT
// SearchTypeSelector popup menus are fixed width. Set the fixed width via this if you want something non-default.
// In a stylesheet, set it this way:
//
// qproperty-fixedWidth: yourIntegerVirtualPixelValueHere;
//
Q_PROPERTY(int fixedWidth READ fixedWidth WRITE setFixedWidth)
// SearchTypeSelector popup menus make a decent guess of how big the contents are in order to properly
// position the menu above or below the parent button. But even with the guess, there is configurable
// padding not taken into account. That extra padding along the bottom can be tweaked by setting
// the heightEstimatePadding value.
//
// In a stylesheet, set it this way:
//
// qproperty-heightEstimatePadding: yourIntegerVirtualPixelValueHere;
//
Q_PROPERTY(int heightEstimatePadding READ heightEstimatePadding WRITE setHeightEstimatePadding)
// Set this to false in order to hide the text filter, usually on small numbers of items.
// Defaults to true.
//
// In a stylesheet, set it this way:
//
// qproperty-lineEditSearchVisible: 0;
//
Q_PROPERTY(bool lineEditSearchVisible READ lineEditSearchVisible WRITE setLineEditSearchVisible)
// The margin to apply around the line edit search field's layout.
// Defaults to 4.
//
// In a stylesheet, set it this way:
//
// qproperty-searchLayoutMargin: 0;
//
Q_PROPERTY(int searchLayoutMargin READ searchLayoutMargin WRITE setSearchLayoutMargin)
public:
SearchTypeSelector(QWidget* parent = nullptr);
QTreeView* GetTree();
void Setup(const SearchTypeFilterList& searchTypes);
int fixedWidth() const { return m_fixedWidth; }
void setFixedWidth(int newFixedWidth);
int heightEstimatePadding() const { return m_heightEstimatePadding; }
void setHeightEstimatePadding(int newHeightEstimatePadding);
bool lineEditSearchVisible() const;
void setLineEditSearchVisible(bool visible);
int searchLayoutMargin() const;
void setSearchLayoutMargin(int newMargin);
const QString& GetFilterString() const { return m_filterString; }
signals:
void TypeToggled(int id, bool enabled);
private slots:
void FilterTextChanged(const QString& newFilter);
protected:
void estimateTableHeight(QStandardItem* firstCategory, int numCategories, QStandardItem* firstItem, int numItems);
void resetData();
// can be used to override the logic when adding items in RepopulateDataModel
virtual bool filterItemOut(int index, bool itemMatchesFilter, bool categoryMatchesFilter);
virtual void initItem(QStandardItem* item, const SearchTypeFilter& filter, int unfilteredDataIndex);
// Returns the number of items that always appear in the list, regardless of the filtering.
virtual int GetNumFixedItems() { return 0; }
void showEvent(QShowEvent* e) override;
virtual void RepopulateDataModel();
void maximizeGeometryToFitScreen();
SearchTypeSelectorTreeView* m_tree;
QStandardItemModel* m_model;
const SearchTypeFilterList* m_unfilteredData;
AZ_PUSH_DISABLE_WARNING(4127 4251, "-Wunknown-warning-option") // conditional expression is constant, needs to have dll-interface to be used by clients of class 'AzQtComponents::SearchTypeSelector'
QVector<int> m_filteredItemIndices;
AZ_POP_DISABLE_WARNING
QString m_filterString;
bool m_settingUp = false;
int m_fixedWidth = 256;
QLineEdit* m_searchField = nullptr;
QBoxLayout* m_searchLayout = nullptr;
int m_estimatedTableHeight = 0;
int m_estimatedTableWidth = 256;
int m_heightEstimatePadding = 10;
int m_searchLayoutMargin = 4;
bool m_lineEditSearchVisible = true;
};
class AZ_QT_COMPONENTS_API FilteredSearchWidget
: public QFrame
{
Q_OBJECT
Q_PROPERTY(QString placeholderText READ placeholderText WRITE setPlaceholderText NOTIFY placeholderTextChanged)
Q_PROPERTY(QString textFilter READ textFilter WRITE SetTextFilter NOTIFY TextFilterChanged)
Q_PROPERTY(bool textFilterFillsWidth READ textFilterFillsWidth WRITE setTextFilterFillsWidth NOTIFY textFilterFillsWidthChanged)
public:
struct Config
{
};
/*!
* Loads the button config data from a settings object.
*/
static Config loadConfig(QSettings& settings);
/*!
* Returns default button config data.
*/
static Config defaultConfig();
explicit FilteredSearchWidget(QWidget* parent = nullptr, bool willUseOwnSelector = false);
~FilteredSearchWidget() override;
void SetTypeFilterVisible(bool visible);
void SetTypeFilters(const SearchTypeFilterList& typeFilters);
void AddTypeFilter(const SearchTypeFilter& typeFilter);
void SetupOwnSelector(SearchTypeSelector* selector);
inline void AddTypeFilter(const QString& category, const QString& displayName, const QVariant& metadata = {}, int globalFilterValue = -1, FilterCriteriaButton::ExtraButtonType type = FilterCriteriaButton::ExtraButtonType::None, const QString& extraIconFileName = {})
{
AddTypeFilter(SearchTypeFilter(category, displayName, type, extraIconFileName, metadata, globalFilterValue));
}
void SetTextFilterVisible(bool visible);
void SetTextFilter(const QString& textFilter);
void ClearTextFilter();
void AddWidgetToSearchWidget(QWidget* w);
void SetFilteredParentVisible(bool visible);
void setEnabledFiltersVisible(bool visible);
void SetFilterState(const QString& category, const QString& displayName, bool enabled);
void SetFilterInputInterval(AZStd::chrono::milliseconds milliseconds);
QString placeholderText() const;
void setPlaceholderText(const QString& placeholderText);
QString textFilter() const;
bool hasStringFilter() const;
bool textFilterFillsWidth() const;
void setTextFilterFillsWidth(bool fillsWidth);
void clearLabelText();
void setLabelText(const QString& newLabelText);
QString labelText() const;
static QString GetBackgroundColor();
static QString GetSeparatorColor();
QToolButton* assetTypeSelectorButton() const;
signals:
void TextFilterChanged(const QString& activeTextFilter);
void TypeFilterChanged(const SearchTypeFilterList& activeTypeFilters);
void placeholderTextChanged(const QString& placeholderText);
void textFilterFillsWidthChanged(bool fillsWidth);
public slots:
virtual void ClearTypeFilter();
virtual void SetFilterStateByIndex(int index, bool enabled);
void SetFilterState(int index, bool enabled) { SetFilterStateByIndex(index, enabled); }
void readSettings(QSettings& settings, const QString& widgetName);
void writeSettings(QSettings& settings, const QString& widgetName);
protected:
void emitTypeFilterChanged();
QLineEdit* filterLineEdit() const;
QToolButton* filterTypePushButton() const;
SearchTypeSelector* filterTypeSelector() const;
const SearchTypeFilterList& typeFilters() const;
virtual FilterCriteriaButton* createCriteriaButton(const SearchTypeFilter& filter, int filterIndex);
virtual void SetupPaintDelegates();
private slots:
void UpdateTextFilterWidth();
void OnClearFilterContextMenu(const QPoint& pos);
void OnSearchContextMenu(const QPoint& pos);
void OnTextChanged(const QString& activeTextFilter);
void UpdateTextFilter();
protected:
AZ_PUSH_DISABLE_WARNING(4127 4251, "-Wunknown-warning-option") // conditional expression is constant, needs to have dll-interface to be used by clients of class 'AzQtComponents::FilteredSearchWidget'
SearchTypeFilterList m_typeFilters;
AZ_POP_DISABLE_WARNING
FlowLayout* m_flowLayout;
Ui::FilteredSearchWidget* m_ui;
SearchTypeSelector* m_selector;
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // needs to have dll-interface to be used by clients of class 'AzQtComponents::FilteredSearchWidget'
QMap<int, FilterCriteriaButton*> m_typeButtons;
AZ_POP_DISABLE_WARNING
bool m_textFilterFillsWidth;
bool m_displayEnabledFilters;
private:
int FindFilterIndex(const QString& category, const QString& displayName) const;
QTimer m_inputTimer;
QMenu* m_filterMenu;
static const char* s_filterDataProperty;
friend class Style;
static bool polish(Style* style, QWidget* widget, const Config& config);
static bool unpolish(Style* style, QWidget* widget, const Config& config);
FilteredSearchItemDelegate* m_delegate = nullptr;
};
class FilteredSearchItemDelegate : public QStyledItemDelegate
{
public:
explicit FilteredSearchItemDelegate(QWidget* parent = nullptr);
void PaintRichText(QPainter* painter, QStyleOptionViewItem& opt, QString& text) const;
void SetSelector(SearchTypeSelector* selector) { m_selector = selector; }
// QStyledItemDelegate overrides.
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
private:
SearchTypeSelector* m_selector = nullptr;
};
}
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FilteredSearchWidget</class>
<widget class="QFrame" name="FilteredSearchWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>295</width>
<height>53</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QFrame" name="textSearchContainer" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="textSearch">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="inputMask">
<string/>
</property>
<property name="text">
<string/>
</property>
<property name="frame">
<bool>false</bool>
</property>
<property name="placeholderText">
<string>Search...</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="assetTypeSelector">
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>1</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="filteredParent" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>&lt;b&gt;Filtered by:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="filteredLayout" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>AzQtComponents::ExtendedLabel</class>
<extends>QLabel</extends>
<header>AzQtComponents/Components/ExtendedLabel.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="resources.qrc"/>
</resources>
<connections/>
<layoutdefault spacing="0" margin="0"/>
</ui>
@@ -0,0 +1,216 @@
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
// Modifications copyright Amazon.com, Inc. or its affiliates
#include <QtWidgets/QWidget>
#include <AzQtComponents/Components/FlowLayout.h>
FlowLayout::FlowLayout(QWidget* parent, int margin, int hSpacing, int vSpacing)
: QLayout(parent)
, m_hSpace(hSpacing)
, m_vSpace(vSpacing)
{
setContentsMargins(margin, margin, margin, margin);
}
FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing)
: m_hSpace(hSpacing)
, m_vSpace(vSpacing)
{
setContentsMargins(margin, margin, margin, margin);
}
FlowLayout::~FlowLayout()
{
while (QLayoutItem* item = takeAt(0))
{
delete item;
}
}
void FlowLayout::addItem(QLayoutItem* item)
{
itemList.append(item);
}
int FlowLayout::horizontalSpacing() const
{
if (m_hSpace >= 0)
{
return m_hSpace;
}
else
{
return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
}
}
int FlowLayout::verticalSpacing() const
{
if (m_vSpace >= 0)
{
return m_vSpace;
}
else
{
return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
}
}
int FlowLayout::count() const
{
return itemList.size();
}
QLayoutItem* FlowLayout::itemAt(int index) const
{
return itemList.value(index);
}
QLayoutItem* FlowLayout::takeAt(int index)
{
if (index >= 0 && index < itemList.size())
{
return itemList.takeAt(index);
}
else
{
return 0;
}
}
Qt::Orientations FlowLayout::expandingDirections() const
{
return Qt::Orientations();
}
bool FlowLayout::hasHeightForWidth() const
{
return true;
}
int FlowLayout::heightForWidth(int width) const
{
int height = doLayout(QRect(0, 0, width, 0), true);
return height;
}
void FlowLayout::setGeometry(const QRect& rect)
{
QLayout::setGeometry(rect);
doLayout(rect, false);
}
QSize FlowLayout::sizeHint() const
{
return minimumSize();
}
QSize FlowLayout::minimumSize() const
{
QSize size;
QLayoutItem* item;
foreach(item, itemList)
size = size.expandedTo(item->minimumSize());
size += QSize(2 * margin(), 2 * margin());
return size;
}
int FlowLayout::doLayout(const QRect& rect, bool testOnly) const
{
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
QLayoutItem* item;
foreach(item, itemList) {
QWidget* wid = item->widget();
int spaceX = horizontalSpacing();
if (spaceX == -1)
{
spaceX = wid->style()->layoutSpacing(
QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
}
int spaceY = verticalSpacing();
if (spaceY == -1)
{
spaceY = wid->style()->layoutSpacing(
QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
}
int nextX = x + item->sizeHint().width() + spaceX;
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0)
{
x = effectiveRect.x();
y = y + lineHeight + spaceY;
nextX = x + item->sizeHint().width() + spaceX;
lineHeight = 0;
}
if (!testOnly)
{
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
}
x = nextX;
lineHeight = qMax(lineHeight, item->sizeHint().height());
}
return y + lineHeight - rect.y() + bottom;
}
int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const
{
QObject* parent = this->parent();
if (!parent)
{
return -1;
}
else if (parent->isWidgetType())
{
QWidget* pw = static_cast<QWidget*>(parent);
return pw->style()->pixelMetric(pm, 0, pw);
}
else
{
return static_cast<QLayout*>(parent)->spacing();
}
}
@@ -0,0 +1,83 @@
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
// Modifications copyright Amazon.com, Inc. or its affiliates
#ifndef FLOWLAYOUT_H
#define FLOWLAYOUT_H
#include <QtCore/QRect>
#include <QtWidgets/QLayout>
#include <QtWidgets/QStyle>
#include <AzQtComponents/AzQtComponentsAPI.h>
class AZ_QT_COMPONENTS_API FlowLayout
: public QLayout
{
public:
explicit FlowLayout(QWidget* parent, int margin = -1, int hSpacing = -1, int vSpacing = -1);
explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1);
~FlowLayout();
void addItem(QLayoutItem* item) Q_DECL_OVERRIDE;
int horizontalSpacing() const;
int verticalSpacing() const;
Qt::Orientations expandingDirections() const Q_DECL_OVERRIDE;
bool hasHeightForWidth() const Q_DECL_OVERRIDE;
int heightForWidth(int) const Q_DECL_OVERRIDE;
int count() const Q_DECL_OVERRIDE;
QLayoutItem* itemAt(int index) const Q_DECL_OVERRIDE;
QSize minimumSize() const Q_DECL_OVERRIDE;
void setGeometry(const QRect& rect) Q_DECL_OVERRIDE;
QSize sizeHint() const Q_DECL_OVERRIDE;
QLayoutItem* takeAt(int index) Q_DECL_OVERRIDE;
private:
int doLayout(const QRect& rect, bool testOnly) const;
int smartSpacing(QStyle::PixelMetric pm) const;
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option")
QList<QLayoutItem*> itemList;
AZ_POP_DISABLE_WARNING
int m_hSpace;
int m_vSpace;
};
#endif // FLOWLAYOUT_H
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5894a3649b213cf5b2d673b6e7a871815fd1d120fa68a463592f27db14eae323
size 224592
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3ca680f2444cc9e50447d057c006464566e92f2e77b0b6e26491e5bd757ed4e7
size 213292
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0fcbdb5cbeea00ae532352c7c94a7d288ebc911ba85f4d595012032dcab64ba8
size 222584
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0eeca981116621a96a484ecc58fbfcdc78fda0065fd21fd13707b63bf8a9912c
size 213420
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a54dc8488f8193bf30c3820cf6f261f911f9d328d699e1a1b8042641554cec70
size 212896
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cf5f5184c1441a1660aa52526328e9d5c2793e77b6d8d3a3ad654bdb07ab8424
size 222412
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4482d18b30c4534b5481d594b7c0bc7a9913a7c4c261985e452010a89ab755fc
size 213128
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e64e508b2aa2880f907e470c4550980ec4c0694d103a43f36150ac3f93189bee
size 217360
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:aa3b0ef53db12e3d45094030cac0e69d384e44cc5978643dd4390041cad546e2
size 221328
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:30536248e59274548d51245662f9deec7fb52946faba33aade28c41473bdd39b
size 212820
@@ -0,0 +1,90 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/GlobalEventFilter.h>
#include <QApplication>
#include <QScopedValueRollback>
#include <QWheelEvent>
#include <QWidget>
namespace AzQtComponents
{
GlobalEventFilter::GlobalEventFilter(QObject* parent)
: QObject(parent)
{
}
bool GlobalEventFilter::eventFilter(QObject* obj, QEvent* e)
{
static bool isRecursing = false;
if (isRecursing)
{
return false;
}
QScopedValueRollback<bool> guard(isRecursing, true);
switch (e->type())
{
case QEvent::Wheel:
{
auto wheelEvent = static_cast<QWheelEvent*>(e);
// If the scroll event is set to something other than begin/no phase, let the phased scroll logic in
// QApplication::notify handle the event
if (wheelEvent->phase() != Qt::NoScrollPhase && wheelEvent->phase() != Qt::ScrollBegin)
{
return false;
}
// Make the wheel event fall through to windows underneath the mouse, even if they don't have focus. If
// we don't do this, the wheel event gets turned into a focus event, followed by a wheel event. This
// would cause a user scrolling on an unfocused QSpinBox to accidentally change the value, rather than
// scrolling the view.
QWidget* widget = QApplication::widgetAt(wheelEvent->globalPosition().toPoint());
if (widget && obj != widget)
{
// Run the wheel event up the hierarchy of the target widget until the event is accepted, as the event
// is no longer being propagated automatically in Qt5.15
while (widget)
{
QPoint mappedPos = widget->mapFromGlobal(wheelEvent->globalPosition().toPoint());
QWheelEvent wheelEventCopy = QWheelEvent(
mappedPos,
wheelEvent->globalPosition().toPoint(),
wheelEvent->pixelDelta(),
wheelEvent->angleDelta(),
wheelEvent->buttons(),
wheelEvent->modifiers(),
wheelEvent->phase(),
wheelEvent->inverted(),
wheelEvent->source()
);
QApplication::instance()->sendEvent(widget, &wheelEventCopy);
if (wheelEventCopy.isAccepted())
{
return true;
}
widget = widget->parentWidget();
}
}
}
break;
}
return false;
}
} // namespace AzQtComponents
@@ -0,0 +1,28 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QObject>
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API GlobalEventFilter
: public QObject
{
public:
explicit GlobalEventFilter(QObject* watch);
bool eventFilter(QObject* obj, QEvent* e) override;
};
} // namespace AzQtComponents
@@ -0,0 +1,29 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/HelpButton.h>
#include <QIcon>
#include <QPixmap>
#include <QVariant>
namespace AzQtComponents
{
HelpButton::HelpButton(QWidget* parent)
: QPushButton(parent)
{
setProperty("class", QLatin1String("rounded")); // Gets styled by css
setIcon(QPixmap(":/stylesheet/img/question.png"));
}
} // namespace AzQtComponents
#include "Components/moc_HelpButton.cpp"
@@ -0,0 +1,31 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QPushButton>
#endif
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API HelpButton
: public QPushButton
{
Q_OBJECT
public:
explicit HelpButton(QWidget* parent = nullptr);
};
} // namespace AzQtComponents
@@ -0,0 +1,389 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/InteractiveWindowGeometryChanger.h>
#include <AzQtComponents/Utilities/QtWindowUtilities.h>
#include <QApplication>
#include <QKeyEvent>
#include <QDebug>
namespace AzQtComponents
{
enum
{
ChangeIncrement = 10 // How many pixels are added each time we move or resize
};
InteractiveWindowGeometryChanger::InteractiveWindowGeometryChanger(QWindow* target, QObject *parent)
: QObject(parent)
, m_targetWindow(target)
, m_originalCursorPos(target ? target->mapFromGlobal(QCursor::pos()) : QPoint())
{
if (m_targetWindow)
{
qApp->setOverrideCursor(Qt::SizeAllCursor);
qApp->installEventFilter(this);
m_targetWindow->setMouseGrabEnabled(true);
// Dismiss the changer if someone does intrusive stuff programatically, like maximizing the window
connect(target, &QWindow::visibilityChanged, this, &QObject::deleteLater);
connect(target, &QWindow::windowStateChanged, this, &QObject::deleteLater);
}
else
{
// this doesn't happen
qWarning() << Q_FUNC_INFO << "target window is null";
deleteLater();
Q_ASSERT(false);
}
}
InteractiveWindowGeometryChanger::~InteractiveWindowGeometryChanger()
{
m_targetWindow->setMouseGrabEnabled(false);
qApp->restoreOverrideCursor();
// Restore original cursor position
restoreCursorPosition();
}
void InteractiveWindowGeometryChanger::restoreCursorPosition()
{
if (m_targetWindow && m_restoreCursorAtExit)
{
AzQtComponents::SetCursorPos(m_targetWindow->mapToGlobal(m_originalCursorPos));
}
}
bool InteractiveWindowGeometryChanger::eventFilter(QObject* watched, QEvent* ev)
{
if (ev->type() == QEvent::KeyPress)
{
if (!m_targetWindow)
{
// The window was deleted by external factors, dismiss our changer
deleteLater();
return true;
}
auto keyPressEv = static_cast<QKeyEvent*>(ev);
switch (keyPressEv->key())
{
case Qt::Key_Left:
case Qt::Key_Up:
case Qt::Key_Right:
case Qt::Key_Down:
handleKeyPress(keyPressEv);
return true;
case Qt::Key_Escape:
case Qt::Key_Enter:
case Qt::Key_Return:
deleteLater();
return true; // consume it
default:
return QObject::eventFilter(watched, ev);
}
}
else if (ev->type() == QEvent::MouseButtonPress ||
ev->type() == QEvent::MouseButtonRelease ||
ev->type() == QEvent::MouseButtonDblClick)
{
// Any mouse click dismisses the geometry changer
deleteLater();
// We don't want to restore the cursor pos, the user just clicked somewhere, would be odd to move it
m_restoreCursorAtExit = false;
}
else if (ev->type() == QEvent::MouseMove)
{
handleMouseMove(static_cast<QMouseEvent*>(ev));
}
return QObject::eventFilter(watched, ev);
}
InteractiveWindowResizer::InteractiveWindowResizer(QWindow* target, QObject* parent)
: InteractiveWindowGeometryChanger(target, parent)
{
}
InteractiveWindowResizer::SideToResize InteractiveWindowResizer::keyToSide(int key) const
{
switch (key)
{
case Qt::Key_Left:
return LeftSide;
case Qt::Key_Up:
return TopSide;
case Qt::Key_Right:
return RightSide;
case Qt::Key_Down:
return BottomSide;
default:
return NoneSide;
}
}
bool InteractiveWindowResizer::sideIsVertical(SidesToResize side) const
{
return side & (TopSide | BottomSide);
}
bool InteractiveWindowResizer::sideIsHorizontal(SidesToResize side) const
{
return side & (LeftSide | RightSide);
}
bool InteractiveWindowResizer::sideIsCorner(SidesToResize side) const
{
return sideIsHorizontal(side) && sideIsVertical(side);
}
void InteractiveWindowResizer::handleKeyPress(QKeyEvent* ev)
{
if (m_sideToResize == NoneSide)
{
// First arrow press just determines which side we're going to resize
m_sideToResize = SidesToResize(keyToSide(ev->key()));
updateCursor();
return;
}
else if (!sideIsCorner(m_sideToResize))
{
// When resizing left or right and the user presses up or down then we start resizing
// a corner instead
if (sideIsHorizontal(m_sideToResize) && sideIsVertical(keyToSide(ev->key())))
{
// We're reisizing left or right, but user pressed up or down, so we go to corner instead
m_sideToResize |= keyToSide(ev->key());
updateCursor();
return;
}
else if (sideIsVertical(m_sideToResize) && sideIsHorizontal(keyToSide(ev->key())))
{
// We're resizing top or bottom, but user pressed left or right, so we go to corner instead
m_sideToResize |= keyToSide(ev->key());
updateCursor();
return;
}
}
// Now we do the actual resizing:
QRect geometry = m_targetWindow->geometry();
// const QRect originalGeometry = geometry;
int dx1 = 0, dx2 = 0, dy1 = 0, dy2 = 0;
bool horizontal = false;
int signedness = 0;
switch (ev->key())
{
case Qt::Key_Left:
horizontal = true;
signedness = -1;
break;
case Qt::Key_Right:
horizontal = true;
signedness = 1;
break;
case Qt::Key_Down:
horizontal = false;
signedness = 1;
break;
case Qt::Key_Up:
horizontal = false;
signedness = -1;
break;
default:
break;
}
if (horizontal)
{
if (m_sideToResize & LeftSide)
{
dx1 = ChangeIncrement * signedness;
}
else if (m_sideToResize & RightSide)
{
dx2 = ChangeIncrement * signedness;
}
}
else
{
if (m_sideToResize & TopSide)
{
dy1 = ChangeIncrement * signedness;
}
else if (m_sideToResize & BottomSide)
{
dy2 = ChangeIncrement * signedness;
}
}
geometry.adjust(dx1, dy1, dx2, dy2);
if (geometry.height() >= m_targetWindow->minimumHeight() &&
geometry.width() >= m_targetWindow->minimumWidth() &&
geometry.height() <= m_targetWindow->maximumHeight() &&
geometry.width() <= m_targetWindow->maximumWidth())
{
EnsureGeometryWithinScreenTop(geometry);
m_targetWindow->setGeometry(geometry);
updateCursor(); // Position changed, move cursor to border again
}
}
void InteractiveWindowResizer::handleMouseMove(QMouseEvent* ev)
{
if (m_sideToResize == NoneSide)
{
// First arrow press just determines which side we're going to resize, so nothing will happen here
return;
}
// Now we do the actual resizing:
QRect geometry = m_targetWindow->geometry();
if (m_sideToResize & LeftSide)
{
geometry.setLeft(ev->globalX() - 1);
}
if (m_sideToResize & RightSide)
{
geometry.setRight(ev->globalX());
}
if (m_sideToResize & TopSide)
{
geometry.setTop(ev->globalY() - 1);
}
if (m_sideToResize & BottomSide)
{
geometry.setBottom(ev->globalY());
}
if (geometry.height() >= m_targetWindow->minimumHeight() &&
geometry.width() >= m_targetWindow->minimumWidth() &&
geometry.height() <= m_targetWindow->maximumHeight() &&
geometry.width() <= m_targetWindow->maximumWidth())
{
EnsureGeometryWithinScreenTop(geometry);
m_targetWindow->setGeometry(geometry);
updateCursor(); // Position changed, move cursor to border again
}
}
void InteractiveWindowResizer::updateCursor()
{
// When pressing "Size" in the context menu, the first arrow key press will change the cursor
// shape and position it in one of the edges, or corner
const auto s = m_sideToResize; // Less verbose
QPoint newPos = m_targetWindow->position();
const int x = newPos.x();
const int y = newPos.y();
const int width = m_targetWindow->width();
const int height = m_targetWindow->height();
// Restore our previous cursor override so there's only one override stacked
qApp->restoreOverrideCursor();
// The magic +1/-1 bellow is because of QTBUG-58590, shape isn't set otherwise for modal windows
if (((s & LeftSide) && (s & TopSide)) ||
((s & BottomSide) && (s & RightSide))) // Corner
{
qApp->setOverrideCursor(Qt::SizeFDiagCursor);
newPos.setX((s & LeftSide) ? (x + 1) : (x + width - 1));
newPos.setY((s & TopSide) ? (y + 1) : (y + height - 1));
}
else if (((s & LeftSide) && (s & BottomSide)) ||
((s & RightSide) && (s & TopSide))) // Corner
{
qApp->setOverrideCursor(Qt::SizeBDiagCursor);
newPos.setX((s & LeftSide) ? (x + 1) : (x + width - 1));
newPos.setY((s & TopSide) ? (y + 1) : (y + height - 1));
}
else if (s & (LeftSide | RightSide))
{
qApp->setOverrideCursor(Qt::SizeHorCursor);
newPos.setY(y + height / 2);
newPos.setX((s & LeftSide) ? (x + 1) : (x + width - 1));
}
else if (s & (TopSide | BottomSide))
{
qApp->setOverrideCursor(Qt::SizeVerCursor);
newPos.setX(x + width / 2);
newPos.setY((s & TopSide) ? (y + 1) : (y + height - 1));
}
AzQtComponents::SetCursorPos(newPos);
}
InteractiveWindowMover::InteractiveWindowMover(QWindow* target, QObject* parent)
: InteractiveWindowGeometryChanger(target, parent)
{
}
void InteractiveWindowMover::handleKeyPress(QKeyEvent* ev)
{
m_arrowAlreadyPressed = true;
QPoint offset(0, 0);
switch (ev->key())
{
case Qt::Key_Left:
offset += QPoint(-ChangeIncrement, 0);
break;
case Qt::Key_Up:
offset += QPoint(0, -ChangeIncrement);
break;
case Qt::Key_Right:
offset += QPoint(ChangeIncrement, 0);
break;
case Qt::Key_Down:
offset += QPoint(0, ChangeIncrement);
break;
default:
Q_ASSERT(false);
return;
}
QRect geometry = m_targetWindow->geometry().translated(offset);
EnsureGeometryWithinScreenTop(geometry);
m_targetWindow->setGeometry(geometry);
// Mouse cursor travels too while we use the arrow keys
restoreCursorPosition();
}
void InteractiveWindowMover::handleMouseMove(QMouseEvent*)
{
if (!m_arrowAlreadyPressed)
{
// Mouse only moves the window if one arrow has been pressed. That's how Windows does it.
return;
}
const QPoint newPos = m_targetWindow->mapFromGlobal(QCursor::pos());
const QPoint offset = newPos - m_originalCursorPos;
QRect geometry = m_targetWindow->geometry().translated(offset);
EnsureGeometryWithinScreenTop(geometry);
m_targetWindow->setGeometry(geometry);
}
} // namespace AzQtComponents
@@ -0,0 +1,97 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QObject>
#include <QPointer>
#include <QWindow>
class QKeyEvent;
namespace AzQtComponents
{
/* This class is used to implement window resizing and moving through the keyboard.
* This implement the "Size" and "Move" functionality that can be used by right clicking
* the title bar.
*/
class AZ_QT_COMPONENTS_API InteractiveWindowGeometryChanger
: public QObject
{
Q_DISABLE_COPY(InteractiveWindowGeometryChanger)
public:
// The ctor changes the mouse cursor and installs a global event filter
explicit InteractiveWindowGeometryChanger(QWindow* target, QObject* parent);
// The dtor restores the mouse cursor and uninstalls the event filter
~InteractiveWindowGeometryChanger();
protected:
bool eventFilter(QObject* watched, QEvent* ev) override;
virtual void handleKeyPress(QKeyEvent*) = 0;
virtual void handleMouseMove(QMouseEvent*) = 0;
void restoreCursorPosition();
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'AzQtComponents::InteractiveWindowGeometryChanger::m_targetWindow': class 'QPointer<QWindow>' needs to have dll-interface to be used by clients of class 'AzQtComponents::InteractiveWindowGeometryChanger'
QPointer<QWindow> m_targetWindow;
AZ_POP_DISABLE_WARNING
const QPoint m_originalCursorPos;
bool m_restoreCursorAtExit = true;
};
// This implementation handles arrow key presses and resizes the window accordingly
class AZ_QT_COMPONENTS_API InteractiveWindowResizer : public InteractiveWindowGeometryChanger
{
public:
/*
* Windows resizing works like this, right click title bar, choose "Size"
* then the first arrow key you press will determine if you're resizing the left, top, bottom or right
* the second arrow key press will effectively resize the window.
*/
enum SideToResize
{
NoneSide = 0,
LeftSide = 1,
RightSide = 2,
TopSide = 4,
BottomSide = 8,
};
Q_DECLARE_FLAGS(SidesToResize, SideToResize)
explicit InteractiveWindowResizer(QWindow* target, QObject* parent);
protected:
void handleKeyPress(QKeyEvent*) override;
void handleMouseMove(QMouseEvent*) override;
private:
void updateCursor();
SideToResize keyToSide(int key) const;
bool sideIsVertical(SidesToResize) const;
bool sideIsHorizontal(SidesToResize) const;
bool sideIsCorner(SidesToResize) const;
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'AzQtComponents::InteractiveWindowResizer::m_sideToResize': class 'QFlags<AzQtComponents::InteractiveWindowResizer::SideToResize>' needs to have dll-interface to be used by clients of class 'AzQtComponents::InteractiveWindowResizer'
SidesToResize m_sideToResize = NoneSide;
AZ_POP_DISABLE_WARNING
};
// This implementation handles arrow key presses and moves the window accordingly
class AZ_QT_COMPONENTS_API InteractiveWindowMover : public InteractiveWindowGeometryChanger
{
public:
explicit InteractiveWindowMover(QWindow* target, QObject* parent);
protected:
void handleKeyPress(QKeyEvent*) override;
void handleMouseMove(QMouseEvent*) override;
bool m_arrowAlreadyPressed = false;
};
} // namespace AzQtComponents
@@ -0,0 +1,26 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzQtComponents/Components/StyleManager.h>
namespace AzQtComponents
{
// Here for backwards compatibility with the old name of this class
class LumberyardStylesheet : public StyleManager
{
public:
LumberyardStylesheet(QObject* parent) : StyleManager(parent) {}
};
} // namespace AzQtComponents
@@ -0,0 +1,50 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <QStyle>
/**
* RAII-style class that will block unneeded polish requests when doing reparenting.
* When QWidget::setParent() is called that triggers all children to be repolished, which is expensive
* as all stylesheet rules have to be recalculated.
*
* Repolishing is usually only needed if the stylesheet changed. You can use this class to save CPU cycles
* when you know you're going to trigger setParent() calls that you're sure wouldn't affect any styling.
*/
namespace AzQtComponents
{
class RepolishMinimizer
{
public:
RepolishMinimizer()
{
#if !defined(AZ_PLATFORM_LINUX)
// Enable optimizations
QStyle::enableMinimizePolishOptimizations(true);
#endif // !defined(AZ_PLATFORM_LINUX)
}
~RepolishMinimizer()
{
#if !defined(AZ_PLATFORM_LINUX)
// Disable optimizations. Back to normal.
QStyle::enableMinimizePolishOptimizations(false);
#endif // !defined(AZ_PLATFORM_LINUX)
}
private:
Q_DISABLE_COPY(RepolishMinimizer)
};
} // namespace AzQtComponents
@@ -0,0 +1,112 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SearchLineEdit.h"
#include <AzQtComponents/Components/SearchLineEdit.h>
#include <AzQtComponents/Components/Widgets/LineEdit.h>
#include <QIcon>
#include <QAction>
#include <QMenu>
#include <QCompleter>
#include <QStyle>
using namespace AzQtComponents;
SearchLineEdit::SearchLineEdit(QWidget* parent)
: QLineEdit(parent),
m_errorState(false)
{
setProperty("class", "SearchLineEdit");
m_searchAction = new QAction(QIcon(":/stylesheet/img/16x16/Search.png"), QString(), this);
m_searchAction->setEnabled(false);
addAction(m_searchAction, QLineEdit::LeadingPosition);
}
bool SearchLineEdit::errorState() const
{
return m_errorState;
}
void SearchLineEdit::setMenu(QMenu* menu)
{
if (!menu)
{
return;
}
m_menu = menu;
removeAction(m_searchAction);
m_searchAction = new QAction(QIcon(":/stylesheet/img/16x16/Search_more.png"), QString(), this);
m_searchAction->setEnabled(true);
addAction(m_searchAction, QLineEdit::LeadingPosition);
connect(m_searchAction, &QAction::triggered, this, &SearchLineEdit::displayMenu);
}
void SearchLineEdit::setIconToolTip(const QString& tooltip)
{
m_searchAction->setToolTip(tooltip);
}
QString SearchLineEdit::userInputText() const
{
QString lineEditText = text();
// The QCompleter doesn't seem to update the completion prefix when you delete anything, only when things are added.
// To get it to update correctly when the user deletes something, I'm using the combination of things:
//
// 1) If we have a completion, that text will be auto filled into the quick filter because of the completion model.
// So, we will compare those two values, and if they match, we know we want to search using the completion prefix.
//
// 2) If they don't match, it means that user deleted something, and the Completer didn't update it's internal state, so we'll just
// use whatever is in the text box.
//
// 3) When the text field is set to empty, the current completion gets invalidated, but the prefix doesn't, so that gets special cased out.
//
// Extra fun: If you type in something, "Like" then delete a middle character, "Lie", and then put the k back in. It will auto complete the E
// visually but the completion prefix will be the entire word.
if (completer()
&& completer()->currentCompletion().compare(lineEditText, Qt::CaseInsensitive) == 0
&& !lineEditText.isEmpty())
{
lineEditText = completer()->completionPrefix();
}
return lineEditText;
}
void SearchLineEdit::setErrorState(bool errorState)
{
if (m_errorState == errorState)
{
return;
}
m_errorState = errorState;
AzQtComponents::LineEdit::setExternalError(this, errorState);
emit errorStateChanged(m_errorState);
}
void SearchLineEdit::displayMenu()
{
if (m_menu)
{
const auto rect = QRect(0,0, width(), height());
const auto actionSelected = m_menu->exec(mapToGlobal(rect.bottomLeft()));
emit menuEntryClicked(actionSelected);
}
}
#include "Components/moc_SearchLineEdit.cpp"
@@ -0,0 +1,54 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QLineEdit>
#endif
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API SearchLineEdit
: public QLineEdit
{
Q_OBJECT
public:
explicit SearchLineEdit(QWidget* parent = nullptr);
bool errorState() const;
void setMenu(QMenu* menu);
void setIconToolTip(const QString& tooltip);
// Returns the text that the user input in the case of a QCompleter being present.
// There are some weird edge cases with using QCompleter::completionPrefix that this will handle
// And give reasonable results for.
QString userInputText() const;
public slots:
void setErrorState(bool errorState = true);
signals:
void errorStateChanged(bool errorState);
void menuEntryClicked(QAction* action);
private slots:
void displayMenu();
private:
bool m_errorState = false;
QAction* m_searchAction;
QMenu* m_menu = nullptr;
};
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,193 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: class '...' needs to have dll-interface to be used by clients of class '...'
#include <QPainterPath>
#include <QPointer>
#include <QProxyStyle>
#include <QScopedPointer>
#include <QVariant>
AZ_POP_DISABLE_WARNING
#endif
class QEvent;
class QIcon;
class QLineEdit;
class QMainWindow;
class QPainter;
class QPushButton;
class QToolBar;
namespace AzQtComponents
{
namespace Internal
{
class DialogEventFilter;
} // namespace Internal
/**
* The UI 2.0 Lumberyard Qt Style.
*
* Should not need to be used directly; use the AzQtComponents::StyleManager instead.
*
*/
class AZ_QT_COMPONENTS_API Style
: public QProxyStyle
{
Q_OBJECT
struct Data;
public:
enum
{
CORNER_RECTANGLE = -1
};
explicit Style(QStyle* style = nullptr);
~Style() override;
static QIcon icon(const QString& name);
static QColor dropZoneColorOnHover();
/*!
* Tracks this widget so that when *Config.ini files change, this widget will get repolished.
* Call this if your widget changes based on the config in it's polish() method, so that it
* dynamically reloads when the file changes on disk.
*/
void repolishOnSettingsChange(QWidget* widget);
/*!
* Returns true if the widget parameter has the css className applied
*/
static bool hasClass(const QWidget* widget, const QString& className);
/*!
* Adds the css className to the input widget
*/
static void addClass(QWidget* widget, const QString& className);
/*!
* Removes the css className from the input widget
*/
static void removeClass(QWidget* widget, const QString& className);
/*!
* Finds or loads and then caches the pixmap referenced by fileName
*/
static QPixmap cachedPixmap(const QString& fileName);
/*!
* QPainter is not guaranteed to have its QPaintEngine initialized in setRenderHint,
* so call this work around that.
* See: QTBUG-51247
*/
static void prepPainter(QPainter* painter);
/*!
* Fixes up the parent of the current application style if need be.
* This is used for internal bookkeeping to prevent crashes from styles being
* parented badly.
*/
static void fixProxyStyle(QProxyStyle* proxyStyle, QStyle* baseStyle);
/*!
* Simple helper to draw an anti-aliased frame
*/
static void drawFrame(QPainter* painter, const QPainterPath& frameRect, const QPen& border, const QBrush& background);
/*!
* Call this to explicitly mark your widget to not use the UI 2.0 styling
*/
static void flagToIgnore(QWidget* widget);
/*!
* Call this to remove the flags set from calling flagToIgnore()
*/
static void removeFlagToIgnore(QWidget* widget);
/*!
* Call this to check whether the widget should use the UI 2.0 styling
*/
static bool hasStyle(const QWidget* widget);
QSize sizeFromContents(QStyle::ContentsType type, const QStyleOption* option, const QSize& size, const QWidget* widget) const override;
void drawControl(QStyle::ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override;
void drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override;
void drawComplexControl(QStyle::ComplexControl element, const QStyleOptionComplex* option, QPainter* painter, const QWidget* widget) const override;
void drawItemText(QPainter* painter, const QRect& rectangle, int alignment, const QPalette& palette, bool enabled, const QString& text, QPalette::ColorRole textRole) const override;
void drawDragIndicator(const QStyleOption* option, QPainter* painter, const QWidget* widget) const;
QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap& pixmap, const QStyleOption* option) const override;
QRect subControlRect(ComplexControl control, const QStyleOptionComplex* option, SubControl subControl, const QWidget* widget) const override;
QRect subElementRect(SubElement element, const QStyleOption* option, const QWidget* widget) const override;
int pixelMetric(QStyle::PixelMetric metric, const QStyleOption* option, const QWidget* widget) const override;
void polish(QApplication* application) override;
void polish(QWidget* widget) override;
void unpolish(QWidget* widget) override;
QPalette standardPalette() const override;
QIcon standardIcon(StandardPixmap standardIcon, const QStyleOption* option, const QWidget* widget) const override;
int styleHint(QStyle::StyleHint hint, const QStyleOption* option, const QWidget* widget, QStyleHintReturn* returnData) const override;
// A path to draw a border frame when color != Qt::transparent
QPainterPath borderLineEditRect(const QRect& contentsRect, int borderWidth = -1, int borderRadius = CORNER_RECTANGLE) const;
// A path to draw a border frame when color == Qt::Transparent
QPainterPath lineEditRect(const QRect& contentsRect, int borderWidth = -1, int borderRadius = CORNER_RECTANGLE) const;
bool eventFilter(QObject* watched, QEvent* ev) override;
// Use this class if you have a TableView and you need to wrap calls, indirectly
// to Style::generatedIconPixmap in order to properly style icons.
class DrawWidgetSentinel
{
public:
DrawWidgetSentinel(const QWidget* widgetAboutToDraw);
~DrawWidgetSentinel();
private:
QPointer<const Style> m_style;
QPointer<const QWidget> m_lastDrawWidget;
};
#ifdef _DEBUG
protected:
bool event(QEvent*) override;
#endif
Q_SIGNALS:
void settingsReloaded(); // emitted when any config settings (*.ini) files reload
private:
void repolishWidgetDestroyed(QObject* obj);
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // needs to have dll-interface to be used by clients of class 'AzQtComponents::LineEdit'
QScopedPointer<Data> m_data;
AZ_POP_DISABLE_WARNING
// To be used when text alignment has to be forced from outside Qt
mutable QVariant m_drawItemTextAlignmentOverride;
mutable const QWidget* m_drawControlWidget = nullptr;
};
} // namespace AzQtComponents
@@ -0,0 +1,94 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzQtComponents/Components/StyleManager.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QtWidgets/private/qstylesheetstyle_p.h>
AZ_POP_DISABLE_WARNING
namespace AzQtComponents
{
namespace StyleHelpers
{
/* StyleHelpers::repolishWhenPropertyChanges is used to ensure that style sheet rules that
* depend on the property of a widget are correctly updated when that property changes.
*
* For example, given a Widget with a QLabel as a child, and property called 'drawSimple'
* one can declaratively change the pixmap of the label using the following stylesheet:
*
* Widget QLabel#icon
* {
* qproperty-pixmap: url(:/normal-pixmap.png);
* }
*
* Widget[drawSimple="true"] QLabel#icon
* {
* qproperty-pixmap: url(:/simple-pixmap.png);
* }
*
* If the NOTIFY signal of the property change is drawSimpleChanged, make the following call
* in the Widget constructor:
*
* StyleHelpers::repolishWhenPropertyChanges(this, &Widget::drawSimpleChanged);
*
* See Example::Widget in StyleSheetPage.h and ExampleWidget.qss for a working example.
*/
template <typename T, typename ...Args>
void repolishWhenPropertyChanges(T* widget, void (T::*signal)(Args...))
{
#if !defined(AZ_PLATFORM_LINUX)
QObject::connect(widget, signal, widget, [widget]() {
// Prevent asserts in Unit Tests
if (!StyleManager::isInstanced())
{
return;
}
if (auto styleSheet = StyleManager::styleSheetStyle(widget))
{
// For the widget and each of its children, QStyleSheetStyle::repolish clears
// the existing render rules, polishes the widget and sends it a StyleChange
// event. This ensure that both render rules which depend on properties, and
// properties that are set in style sheets via qproperty- are correctly updated.
styleSheet->repolish(widget);
}
});
#endif // !defined(AZ_PLATFORM_LINUX)
}
/* StyleHelpers::findParent<T> is an utility function to find recursively a parent object of
* type T. If none is found, the function will return a null pointer.
*/
template <typename T>
static T* findParent(const QObject* obj)
{
if (!obj)
{
return nullptr;
}
QObject* parent = obj->parent();
if (auto p = qobject_cast<T*>(parent))
{
return p;
}
return findParent<T>(parent);
}
} // namespace StyleHelpers
} // namespace AzQtComponents
@@ -0,0 +1,292 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Debug/Trace.h>
#include <AzQtComponents/Components/StyleManager.h>
#include <QTextStream>
#include <QApplication>
#include <QPalette>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'QFileInfo::d_ptr': class 'QSharedDataPointer<QFileInfoPrivate>' needs to have dll-interface to be used by clients of class 'QFileInfo'
#include <QDir>
AZ_POP_DISABLE_WARNING
#include <QString>
#include <QFile>
#include <QFontDatabase>
#include <QStyleFactory>
#include <QPointer>
#include <QStyle>
#include <QWidget>
#include <QDebug>
#include <QtWidgets/private/qstylesheetstyle_p.h>
#include <AzQtComponents/Components/StylesheetPreprocessor.h>
#include <AzQtComponents/Utilities/QtPluginPaths.h>
#include <AzQtComponents/Components/StyleSheetCache.h>
#include <AzQtComponents/Components/Style.h>
#include <AzQtComponents/Components/TitleBarOverdrawHandler.h>
#include <AzQtComponents/Components/AutoCustomWindowDecorations.h>
namespace AzQtComponents
{
constexpr QStringView g_styleSheetRelativePath {u"Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets"};
constexpr QStringView g_styleSheetResourcePath {u":AzQtComponents/Widgets"};
constexpr QStringView g_globalStyleSheetName {u"BaseStyleSheet.qss"};
constexpr QStringView g_searchPathPrefix {u"AzQtComponentWidgets"};
StyleManager* StyleManager::s_instance = nullptr;
static QStyle* createBaseStyle()
{
return QStyleFactory::create("Fusion");
}
void StyleManager::addSearchPaths(const QString& searchPrefix, const QString& pathOnDisk, const QString& qrcPrefix)
{
if (!s_instance)
{
qFatal("StyleManager::addSearchPaths called before instance was created");
return;
}
s_instance->m_stylesheetCache->addSearchPaths(searchPrefix, pathOnDisk, qrcPrefix);
}
bool StyleManager::setStyleSheet(QWidget* widget, QString styleFileName)
{
if (!s_instance)
{
qFatal("StyleManager::setStyleSheet called before instance was created");
return false;
}
if (!widget)
{
qFatal("StyleManager::setStyleSheet called with null widget pointer");
return false;
}
if (!styleFileName.endsWith(StyleSheetCache::styleSheetExtension()))
{
styleFileName.append(StyleSheetCache::styleSheetExtension());
}
const auto styleSheet = s_instance->m_stylesheetCache->loadStyleSheet(styleFileName);
if (styleSheet.isEmpty())
{
return false;
}
s_instance->m_widgetToStyleSheetMap.insert(widget, styleFileName);
connect(widget, &QObject::destroyed, s_instance, &StyleManager::stopTrackingWidget, Qt::UniqueConnection);
widget->setStyleSheet(styleSheet);
return true;
}
QStyleSheetStyle* StyleManager::styleSheetStyle(const QWidget* widget)
{
Q_UNUSED(widget);
// widget is currently unused, but would be required if Qt::AA_ManualStyleSheetStyle was
// not set.
if (!s_instance)
{
AZ_Warning("StyleManager", false, "StyleManager::styleSheetStyle called before instance was created");
return nullptr;
}
if (!QApplication::testAttribute(Qt::AA_ManualStyleSheetStyle))
{
qFatal("StyleManager::styleSheetStyle has not been implemented for automatically created QStyleSheetStyles");
return nullptr;
}
return s_instance->m_styleSheetStyle;
}
QStyle *StyleManager::baseStyle(const QWidget *widget)
{
const auto sss = styleSheetStyle(widget);
return sss ? sss->baseStyle() : nullptr;
}
void StyleManager::repolishStyleSheet(QWidget* widget)
{
StyleManager::styleSheetStyle(widget)->repolish(widget);
}
StyleManager::StyleManager(QObject* parent)
: QObject(parent)
, m_stylesheetPreprocessor(new StylesheetPreprocessor(this))
, m_stylesheetCache(new StyleSheetCache(this))
{
if (s_instance)
{
qFatal("A StyleManager already exists");
}
}
StyleManager::~StyleManager()
{
delete m_stylesheetPreprocessor;
s_instance = nullptr;
if (m_style)
{
delete m_style.data();
m_style.clear();
m_styleSheetStyle = nullptr;
}
}
void StyleManager::initialize(QApplication* application)
{
if (s_instance)
{
qFatal("StyleManager::Initialize called more than once");
return;
}
s_instance = this;
QApplication::setAttribute(Qt::AA_ManualStyleSheetStyle, true);
QApplication::setAttribute(Qt::AA_PropagateStyleToChildren, true);
connect(application, &QCoreApplication::aboutToQuit, this, &StyleManager::cleanupStyles);
initializeSearchPaths(application);
initializeFonts();
m_titleBarOverdrawHandler = TitleBarOverdrawHandler::createHandler(application, this);
// The window decoration wrappers require the titlebar overdraw handler
// so we can't initialize the custom window decoration monitor until the
// titlebar overdraw handler has been initialized.
m_autoCustomWindowDecorations = new AutoCustomWindowDecorations(this);
m_autoCustomWindowDecorations->setMode(AutoCustomWindowDecorations::Mode_AnyWindow);
// Style is chained as: Style -> QStyleSheetStyle -> native, meaning any CSS limitation can be tackled in Style.cpp
m_styleSheetStyle = new QStyleSheetStyle(createBaseStyle());
m_style = new Style(m_styleSheetStyle);
QApplication::setStyle(m_style);
m_style->setParent(this);
refresh();
connect(m_stylesheetCache, &StyleSheetCache::styleSheetsChanged, this, [this]
{
refresh();
});
}
void StyleManager::cleanupStyles()
{
QApplication::setStyle(createBaseStyle());
}
void StyleManager::stopTrackingWidget(QObject* object)
{
const auto widget = qobject_cast<QWidget* const>(object);
if (!widget)
{
return;
}
m_widgetToStyleSheetMap.remove(widget);
// Remove any old stylesheet
widget->setStyleSheet(QString());
}
void StyleManager::initializeFonts()
{
// yes, the path specifier could've included OpenSans- and .ttf, but I
// wanted anyone searching for OpenSans-Bold.ttf to find something so left it this way
QString openSansPathSpecifier = QStringLiteral(":/AzQtFonts/Fonts/Open_Sans/%1");
QFontDatabase::addApplicationFont(openSansPathSpecifier.arg("OpenSans-Bold.ttf"));
QFontDatabase::addApplicationFont(openSansPathSpecifier.arg("OpenSans-BoldItalic.ttf"));
QFontDatabase::addApplicationFont(openSansPathSpecifier.arg("OpenSans-ExtraBold.ttf"));
QFontDatabase::addApplicationFont(openSansPathSpecifier.arg("OpenSans-ExtraBoldItalic.ttf"));
QFontDatabase::addApplicationFont(openSansPathSpecifier.arg("OpenSans-Italic.ttf"));
QFontDatabase::addApplicationFont(openSansPathSpecifier.arg("OpenSans-Light.ttf"));
QFontDatabase::addApplicationFont(openSansPathSpecifier.arg("OpenSans-LightItalic.ttf"));
QFontDatabase::addApplicationFont(openSansPathSpecifier.arg("OpenSans-Regular.ttf"));
QFontDatabase::addApplicationFont(openSansPathSpecifier.arg("OpenSans-Semibold.ttf"));
QFontDatabase::addApplicationFont(openSansPathSpecifier.arg("OpenSans-SemiboldItalic.ttf"));
}
void StyleManager::initializeSearchPaths(QApplication* application)
{
// now that QT is initialized, we can use its path manipulation functions to set the rest up:
QString rootDir = FindEngineRootDir(application);
if (!rootDir.isEmpty())
{
QDir appPath(rootDir);
// Set the StyleSheetCache fallback prefix
const auto pathOnDisk = appPath.absoluteFilePath(g_styleSheetRelativePath.toString());
m_stylesheetCache->setFallbackSearchPaths(g_searchPathPrefix.toString(), pathOnDisk, g_styleSheetResourcePath.toString());
// add the expected editor paths
// this allows you to refer to your assets relative, like
// STYLESHEETIMAGES:something.txt
// UI:blah/blah.png
// EDITOR:blah/something.txt
QDir::addSearchPath("STYLESHEETIMAGES", appPath.filePath("Editor/Styles/StyleSheetImages"));
QDir::addSearchPath("UI", appPath.filePath("Editor/UI"));
QDir::addSearchPath("EDITOR", appPath.filePath("Editor"));
}
}
void StyleManager::refresh()
{
const auto globalStyleSheet = m_stylesheetCache->loadStyleSheet(g_globalStyleSheetName.toString());
m_styleSheetStyle->setGlobalSheet(globalStyleSheet);
// Iterate widgets and update the stylesheet (the base style has already been set)
auto i = m_widgetToStyleSheetMap.constBegin();
while (i != m_widgetToStyleSheetMap.constEnd())
{
const auto styleSheet = m_stylesheetCache->loadStyleSheet(i.value());
i.key()->setStyleSheet(styleSheet);
++i;
}
// QMessageBox uses "QMdiSubWindowTitleBar" class to query the titlebar font
// through QApplication::font() and (buggily) calculate required width of itself
// to fit the title. It bypassess stylesheets. See QMessageBoxPrivate::updateSize().
QFont titleBarFont("Open Sans");
titleBarFont.setPixelSize(18);
QApplication::setFont(titleBarFont, "QMdiSubWindowTitleBar");
}
const QColor& StyleManager::getColorByName(const QString& name)
{
return m_stylesheetPreprocessor->GetColorByName(name);
}
} // namespace AzQtComponents
#include "Components/moc_StyleManager.cpp"
#if defined(AZ_QT_COMPONENTS_STATIC)
// If we're statically compiling the lib, we need to include the compiled rcc resources
// somewhere to ensure that the linker doesn't optimize the symbols out (with Visual Studio at least)
// With dlls, there's no step to optimize out the symbols, so we don't need to do this.
#include <Components/rcc_resources.h>
#endif // #if defined(AZ_QT_COMPONENTS_STATIC)
@@ -0,0 +1,131 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QObject>
#include <QColor>
#include <QHash>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'AzQtComponents::StyleManager::m_widgetToStyleSheetMap': class 'QHash<QWidget *,QString>' needs to have dll-interface to be used by clients of class 'AzQtComponents::StyleManager'
#include <QPointer>
#endif
AZ_POP_DISABLE_WARNING
class QApplication;
class QStyle;
class QWidget;
class QStyleSheetStyle;
namespace AzQtComponents
{
class StyleSheetCache;
class StylesheetPreprocessor;
class TitleBarOverdrawHandler;
class AutoCustomWindowDecorations;
/**
* Wrapper around classes dealing with Lumberyard style.
*
* New applications should work like this:
*
* int main(int argv, char **argc)
* {
* QApplication app(argv, argc);
*
* AzQtComponents::StyleManager styleManager(&app);
* const bool useUI10 = false;
* styleManager.Initialize(&app, useUI10);
* .
* .
* .
* }
*
*/
class AZ_QT_COMPONENTS_API StyleManager
: public QObject
{
Q_OBJECT
static StyleManager* s_instance;
public:
static bool isInstanced() { return s_instance; }
static void addSearchPaths(const QString& searchPrefix, const QString& pathOnDisk, const QString& qrcPrefix);
static bool setStyleSheet(QWidget* widget, QString styleFileName);
static QStyleSheetStyle* styleSheetStyle(const QWidget* widget);
static QStyle* baseStyle(const QWidget* widget);
static void repolishStyleSheet(QWidget* widget);
explicit StyleManager(QObject* parent);
~StyleManager() override;
/*!
* Call to initialize the StyleManager, allowing it to hook into the application and apply the global style
*/
void initialize(QApplication* application);
// deprecated; introduced before the new camelCase Qt based method names were adopted.
void Initialize(QApplication* application) { initialize(application); }
/*!
* Call this to force a refresh of the global stylesheet and a reload of any settings files.
* Note that you should never need to do this manually.
*/
void refresh();
// deprecated; introduced before the new camelCase Qt based method names were adopted.
void Refresh() { refresh(); }
/*!
* Used to get a global color value by name.
* Deprecated; do not use.
* This was implemented to support skinning of the Editor,
* but that functionality is no longer supported. If you
* want to load a color instead of hard coding it, please
* embed the color into a stylesheet instead of using
* GetColorByName.
*/
const QColor& getColorByName(const QString& name);
// deprecated; introduced before the new camelCase Qt based method names were adopted.
const QColor& GetColorByName(const QString& name) { return getColorByName(name); }
private Q_SLOTS:
void cleanupStyles();
void stopTrackingWidget(QObject* object);
private:
void initializeFonts();
void initializeSearchPaths(QApplication* application);
void resetWidgetSheets();
StylesheetPreprocessor* m_stylesheetPreprocessor = nullptr;
StyleSheetCache* m_stylesheetCache = nullptr;
TitleBarOverdrawHandler* m_titleBarOverdrawHandler = nullptr;
using WidgetToStyleSheetMap = QHash<QWidget*, QString>;
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'AzQtComponents::StyleManager::m_widgetToStyleSheetMap': class 'QHash<QWidget *,QString>' needs to have dll-interface to be used by clients of class 'AzQtComponents::StyleManager'
WidgetToStyleSheetMap m_widgetToStyleSheetMap;
QStyleSheetStyle* m_styleSheetStyle = nullptr;
// Track the style as a QPointer, as the QApplication will delete it if it still has a pointer to it
QPointer<QStyle> m_style;
AZ_POP_DISABLE_WARNING
AutoCustomWindowDecorations* m_autoCustomWindowDecorations = nullptr;
};
} // namespace AzQtComponents
@@ -0,0 +1,437 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/StyleSheetCache.h>
#include <AzQtComponents/Utilities/QtPluginPaths.h>
#include <AzCore/Debug/Trace.h>
#include <QProxyStyle>
#include <QWidget>
#include <QDir>
#include <QFile>
#include <QFileSystemWatcher>
#include <QStringList>
#include <QRegExp>
#include <QDebug>
#include <QApplication>
#include <QQueue>
namespace AzQtComponents
{
StyleSheetCache::StyleSheetCache(QObject* parent)
: QObject(parent)
, m_fileWatcher(new QFileSystemWatcher(this))
, m_importExpression(new QRegExp("^\\s*@import\\s+\"?([^\"]+)\"?\\s*;(.*)$"))
{
connect(m_fileWatcher, &QFileSystemWatcher::fileChanged, this, &StyleSheetCache::fileOnDiskChanged);
}
StyleSheetCache::~StyleSheetCache()
{
}
const QString& StyleSheetCache::styleSheetExtension()
{
static const QString extension{QStringLiteral(".qss")};
return extension;
}
void StyleSheetCache::registerPathsFoundOnDisk(const QString& pathOnDisk, const QString& qrcPrefix)
{
// Do a sanity check to ensure there are no style-sheets on disk that don't exist in a qrc
QDir rootDirectory(pathOnDisk);
QQueue<QFileInfo> entriesToScan;
entriesToScan.push_back(QFileInfo(pathOnDisk));
while (!entriesToScan.empty())
{
QFileInfo entry = entriesToScan.front();
entriesToScan.pop_front();
if (!entry.exists())
{
continue;
}
if (entry.isDir())
{
for (auto subEntry : QDir(entry.absoluteFilePath()).entryInfoList({"*.qss"}, QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files))
{
entriesToScan.push_back(subEntry);
}
}
else
{
QString diskPath = entry.absoluteFilePath();
QString qrcPath = QStringLiteral("%1/%2")
.arg(qrcPrefix)
.arg(rootDirectory.relativeFilePath(diskPath));
QFileInfo qrcInfo(qrcPath);
if (qrcInfo.exists())
{
m_diskToQrcMap[diskPath] = qrcPath;
}
else
{
AZ_Warning("StyleSheetCache", false,
"No QRC entry found for style sheet found on disk. Disk path: \"%s\" Expected QRC path: \"%s\"",
diskPath.toUtf8().constData(), qrcPath.toUtf8().constData());
}
}
}
}
void StyleSheetCache::addSearchPaths(const QString& searchPrefix, const QString& pathOnDisk, const QString& qrcPrefix)
{
AZ_Warning("StyleSheetCache", m_prefixes.find(searchPrefix) == m_prefixes.end(),
"Style prefix \"%s\" was already registered, ignoring...", searchPrefix.constData());
// If pathOnDisk is a relative path, search from the engine root directory
QString diskPathToUse = pathOnDisk;
if (!QFileInfo(pathOnDisk).isAbsolute())
{
QDir rootDir(AzQtComponents::FindEngineRootDir(qApp));
diskPathToUse = rootDir.absoluteFilePath(pathOnDisk);
}
registerPathsFoundOnDisk(diskPathToUse, qrcPrefix);
// Specifying the path to the file on disk and the qrc prefix of the file in this order means
// that the style will be loaded from disk if it exists, otherwise the style in the Qt Resource
// file will be used. Styles loaded from disk will be automatically watched and re-applied if
// changes are detected, allowing much faster style iteration.
m_prefixes.insert(searchPrefix);
QDir::addSearchPath(searchPrefix, diskPathToUse);
QDir::addSearchPath(searchPrefix, qrcPrefix);
}
void StyleSheetCache::setFallbackSearchPaths(const QString& fallbackPrefix, const QString& pathOnDisk, const QString& qrcPrefix)
{
if (m_fallbackPrefix == fallbackPrefix)
{
return;
}
if (!m_fallbackPrefix.isEmpty())
{
QDir::setSearchPaths(m_fallbackPrefix, {});
}
m_fallbackPrefix = fallbackPrefix;
if (m_fallbackPrefix.isEmpty())
{
return;
}
registerPathsFoundOnDisk(pathOnDisk, qrcPrefix);
QDir::setSearchPaths(m_fallbackPrefix, {pathOnDisk, qrcPrefix});
}
void StyleSheetCache::clearCache()
{
m_styleSheetCache.clear();
}
void StyleSheetCache::fileOnDiskChanged(const QString& filePath)
{
qDebug() << "All styleSheets reloading, triggered by " << filePath << " changing on disk.";
// Much easier to just reload all stylesheets, instead of trying to figure out
// which ones are affected by this file.
// If we were to worry about just one file, we'd need to keep track of dependency
// info when preprocessing @import's
clearCache();
emit styleSheetsChanged();
}
QString StyleSheetCache::loadStyleSheet(QString styleFileName)
{
// include the file extension here; it'll make life easier when comparing file paths
if (!styleFileName.endsWith(styleSheetExtension()))
{
styleFileName.append(styleSheetExtension());
}
// check the cache
if (m_styleSheetCache.contains(styleFileName))
{
return m_styleSheetCache[styleFileName];
}
QString filePath = findStyleSheetPath(styleFileName);
if (filePath.isEmpty())
{
return QString();
}
QFileInfo filePathInfo(filePath);
// watch this file for changes now, if it's not loaded from resources
if (filePathInfo.exists() && filePathInfo.isNativePath())
{
m_fileWatcher->addPath(filePath);
QString absolutePath = filePathInfo.absoluteFilePath();
if (m_diskToQrcMap.find(absolutePath) == m_diskToQrcMap.end())
{
AZ_Error("StyleSheetCache", false, "No QRC entry was found for style-sheet loaded from disk, loading has been disabled: %s", absolutePath.toUtf8().constData());
return {};
}
}
QString loadedStyleSheet;
if (QFile::exists(filePath))
{
QFile styleSheetFile;
styleSheetFile.setFileName(filePath);
if (styleSheetFile.open(QFile::ReadOnly))
{
loadedStyleSheet = styleSheetFile.readAll();
}
}
// pre-process stylesheet
loadedStyleSheet = preprocess(styleFileName, loadedStyleSheet);
m_styleSheetCache.insert(styleFileName, loadedStyleSheet);
return loadedStyleSheet;
}
class MiniLessParser
{
public:
MiniLessParser(StyleSheetCache* cache);
QString process(const QString& styleSheet);
private:
enum class State
{
Default,
Comment,
};
void parseLine(const QString& line);
int parseForImportStatement(const QString& line);
State m_state = State::Default;
QRegExp m_importStatement;
QChar m_lastCharacter;
QString m_result;
int m_lineNumber = 0;
StyleSheetCache* m_cache = nullptr;
};
MiniLessParser::MiniLessParser(StyleSheetCache* cache)
: m_importStatement("@import\\s+\"?([^\"]+)\"?\\s*;")
, m_cache(cache)
{
}
QString MiniLessParser::process(const QString& styleSheet)
{
m_state = State::Default;
m_result.reserve(styleSheet.size());
m_lineNumber = 0;
m_lastCharacter = QChar();
QStringList lines = styleSheet.split(QRegExp("[\\n\\r]"), Qt::SkipEmptyParts);
for (QString& line : lines)
{
parseLine(line);
m_lineNumber++;
}
return m_result;
}
void MiniLessParser::parseLine(const QString& line)
{
for (int i = 0; i < line.size(); i++)
{
QChar currentChar = line[i];
switch (m_state)
{
case State::Default:
{
if (currentChar == '@')
{
// parse for import
i += parseForImportStatement(line.mid(i));
currentChar = QChar();
}
else
{
if ((m_lastCharacter == '/') && (currentChar == '*'))
{
m_state = State::Comment;
}
m_result += currentChar;
}
}
break;
case State::Comment:
{
if ((m_lastCharacter == '*') && (currentChar == '/'))
{
m_state = State::Default;
}
m_result += currentChar;
}
break;
}
m_lastCharacter = currentChar;
}
m_result += "\n";
// reset our state
m_lastCharacter = QChar();
m_state = State::Default;
}
int MiniLessParser::parseForImportStatement(const QString& line)
{
QString importName;
int ret = 0;
int pos = m_importStatement.indexIn(line, 0);
if (pos != -1)
{
importName = m_importStatement.cap(1);
ret = m_importStatement.cap(0).size();
if (!importName.isEmpty())
{
// attempt to import
QString subStyleSheet = m_cache->loadStyleSheet(importName);
if (!subStyleSheet.isEmpty())
{
// error?
}
m_result += subStyleSheet;
}
}
return ret;
}
QString StyleSheetCache::preprocess(QString styleFileName, QString loadedStyleSheet)
{
// Add in really basic support in here for less style @import statements
// This allows us to split up css chunks into other files, to group things
// in a much saner way
// check for dumb recursion
if (m_processingFiles.contains(styleFileName))
{
qDebug() << QString("Recursion found while processing styleSheets in the following order:");
for (QString& file : m_processingStack)
{
qDebug() << file;
}
return loadedStyleSheet;
}
m_processingStack.push_back(styleFileName);
m_processingFiles.insert(styleFileName);
// take a guess at the size of the string needed with imports
QString result;
result.reserve(loadedStyleSheet.size() * 3);
// run our mini less parser on the stylesheet, to process imports now
MiniLessParser lessParser(this);
result = lessParser.process(loadedStyleSheet);
m_processingStack.pop_back();
m_processingFiles.remove(styleFileName);
return result;
}
QString StyleSheetCache::findStyleSheetPath(const QString& styleFileName)
{
if (styleFileName.contains(':'))
{
// The file name is in a resource file (":/style.qss")
// or already has a prefix ("prefix:style.qss")
// or an absolute path on Windows
return styleFileName;
}
if (QFile::exists(styleFileName))
{
return styleFileName;
}
const auto stackSize = m_processingStack.size();
if (stackSize > 0)
{
// Resursively search ancestors of the file currently being processed
const auto ancestorStyleFileName = m_processingStack.pop();
const auto ancestorFilePath = findStyleSheetPath(ancestorStyleFileName);
m_processingStack.push(ancestorStyleFileName);
if (QFile::exists(ancestorFilePath))
{
auto prefix = ancestorFilePath.mid(0, ancestorFilePath.indexOf(':'));
if (ancestorFilePath.contains(':') && prefix.length() != 1)
{
// QDir::isAbsolutePath returns true for paths with a valid search prefix. The
// only way to distinguish between a prefix and absolute path on Windows is the
// length of the prefix. Prefix length must be at least two to avoid conflicting
// with Windows drive letters. However, files in Qt Resources files have a prefix
// length of zero.
const auto result = QString("%1:%2").arg(prefix, styleFileName);
if (QFile::exists(result))
{
return result;
}
}
else
{
// Assume the styleFileName is relative to the ancestorFilePath
const QFileInfo ancestorFileInfo(ancestorFilePath);
const auto result = ancestorFileInfo.absoluteDir().absoluteFilePath(styleFileName);
if (QFile::exists(result))
{
return result;
}
}
}
}
// If we didn't find it in the processing stack, search all known prefixes
for (const auto& prefix : m_prefixes)
{
const auto result = QString("%1:%2").arg(prefix, styleFileName);
if (QFile::exists(result))
{
return result;
}
}
// Finally, fall back to m_fallbackPrefix
return m_fallbackPrefix.isEmpty() ? QString() : QString("%1:%2").arg(m_fallbackPrefix, styleFileName);
}
} // namespace AzQtComponents
#include "Components/moc_StyleSheetCache.cpp"
@@ -0,0 +1,85 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QObject>
#include <QHash>
#include <QString>
#include <QScopedPointer>
#include <QSet>
#include <QStack>
#include <QMap>
#endif
class QFileSystemWatcher;
class QRegExp;
namespace AzQtComponents
{
class StyleManager;
class StyleSheetCacheTests;
class AZ_QT_COMPONENTS_API StyleSheetCache
: public QObject
{
Q_OBJECT
friend StyleManager;
friend StyleSheetCacheTests;
explicit StyleSheetCache(QObject* parent);
~StyleSheetCache();
public:
static const QString& styleSheetExtension();
void addSearchPaths(const QString& searchPrefix, const QString& pathOnDisk, const QString& qrcPrefix);
void setFallbackSearchPaths(const QString& fallbackPrefix, const QString& pathOnDisk, const QString& qrcPrefix);
QString loadStyleSheet(QString styleFileName);
public Q_SLOTS:
void clearCache();
Q_SIGNALS:
void styleSheetsChanged();
private Q_SLOTS:
void fileOnDiskChanged(const QString& filePath);
private:
void registerPathsFoundOnDisk(const QString& pathOnDisk, const QString& qrcPrefix);
QString preprocess(QString styleFileName, QString loadedStyleSheet);
QString findStyleSheetPath(const QString& styleFileName);
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'AzQtComponents::StyleSheetCache::m_styleSheetCache': class 'QHash<QString,QString>' needs to have dll-interface to be used by clients of class 'AzQtComponents::StyleSheetCache'
QHash<QString, QString> m_styleSheetCache;
QSet<QString> m_processingFiles;
QStack<QString> m_processingStack;
QFileSystemWatcher* m_fileWatcher;
QScopedPointer<QRegExp> m_importExpression;
QSet<QString> m_prefixes;
QMap<QString, QString> m_diskToQrcMap;
QString m_fallbackPrefix;
AZ_POP_DISABLE_WARNING
};
} // namespace AzQtComponents
@@ -0,0 +1,224 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/StyledBusyLabel.h>
#include <QLabel>
AZ_PUSH_DISABLE_WARNING(4244 4251, "-Wunknown-warning-option") // 4251: 'QImageIOHandler::d_ptr': class 'QScopedPointer<QImageIOHandlerPrivate,QScopedPointerDeleter<T>>' needs to have dll-interface to be used by clients of class 'QImageIOHandler'
#include <QMovie>
AZ_POP_DISABLE_WARNING
#include <QSvgWidget>
#include <QSvgRenderer>
#include <QHBoxLayout>
#include <QPainter>
namespace AzQtComponents
{
StyledBusyLabel::StyledBusyLabel(QWidget* parent)
: QWidget(parent)
, m_busyIcon(new QSvgWidget(this))
, m_oldBusyIcon(new QLabel(this))
, m_text(new QLabel(this))
{
setLayout(new QHBoxLayout);
layout()->setSpacing(6);
layout()->addWidget(m_busyIcon);
layout()->addWidget(m_oldBusyIcon);
layout()->addWidget(m_text);
m_oldBusyIcon->setMovie(new QMovie(this));
m_oldBusyIcon->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
m_busyIcon->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
m_busyIcon->setVisible(false);
m_text->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Minimum);
loadDefaultIcon();
}
bool StyledBusyLabel::unpolish(Style* style, QWidget* widget)
{
Q_UNUSED(style);
auto busyLabel = qobject_cast<StyledBusyLabel*>(widget);
if (busyLabel)
{
busyLabel->SetUseNewWidget(false);
}
return busyLabel;
}
bool StyledBusyLabel::polish(Style* style, QWidget* widget)
{
Q_UNUSED(style);
auto busyLabel = qobject_cast<StyledBusyLabel*>(widget);
if (busyLabel)
{
busyLabel->SetUseNewWidget(true);
}
return busyLabel;
}
void StyledBusyLabel::loadDefaultIcon()
{
SetBusyIcon(m_useNewWidget ? ":/stylesheet/img/loading.svg" : ":/stylesheet/img/in_progress.gif");
}
void StyledBusyLabel::SetUseNewWidget(bool usenew)
{
if (m_useNewWidget != usenew)
{
m_useNewWidget = usenew;
loadDefaultIcon();
m_oldBusyIcon->setVisible(!m_useNewWidget);
m_busyIcon->setVisible(m_useNewWidget);
if (m_useNewWidget)
{
m_oldBusyIcon->movie()->stop();
}
else
{
m_oldBusyIcon->movie()->start();
}
updateMovie();
}
}
bool StyledBusyLabel::GetIsBusy() const
{
return m_useNewWidget ? m_busyIcon->isVisible() : m_oldBusyIcon->movie()->state() == QMovie::Running;
}
void StyledBusyLabel::SetIsBusy(bool busy)
{
if (m_isBusy != busy)
{
m_isBusy = busy;
updateMovie();
}
}
QString StyledBusyLabel::GetText() const
{
return m_text->text();
}
void StyledBusyLabel::SetText(const QString& text)
{
m_text->setText(text);
}
QString StyledBusyLabel::GetBusyIcon() const
{
return m_fileName;
}
void StyledBusyLabel::SetBusyIcon(const QString& iconSource)
{
m_fileName = iconSource;
if (m_useNewWidget)
{
m_busyIcon->renderer()->load(iconSource);
connect(m_busyIcon->renderer(), &QSvgRenderer::repaintNeeded, this, &StyledBusyLabel::movieUpdated);
}
else
{
m_oldBusyIcon->movie()->setFileName(iconSource);
m_oldBusyIcon->setFixedWidth(32);
m_oldBusyIcon->movie()->setScaledSize(QSize(height(), height()));
}
updateMovie();
}
int StyledBusyLabel::GetBusyIconSize() const
{
return m_busyIconSize;
}
void StyledBusyLabel::SetBusyIconSize(int iconSize)
{
if (m_busyIconSize != iconSize)
{
m_busyIconSize = iconSize;
updateMovie();
}
}
QSize StyledBusyLabel::sizeHint() const
{
return QSize(m_busyIconSize, m_busyIconSize);
}
void StyledBusyLabel::updateMovie()
{
if (m_isBusy)
{
if (!m_useNewWidget)
{
m_oldBusyIcon->movie()->start();
}
else
{
m_busyIcon->show();
}
}
else
{
if (!m_useNewWidget)
{
m_oldBusyIcon->movie()->stop();
}
else
{
m_busyIcon->hide();
}
}
if (!m_useNewWidget)
{
m_oldBusyIcon->setFixedWidth(m_busyIconSize);
m_oldBusyIcon->movie()->setScaledSize(QSize(m_busyIconSize, m_busyIconSize));
}
else
{
m_busyIcon->setFixedSize(m_busyIconSize, m_busyIconSize);
}
}
void StyledBusyLabel::DrawTo(QPainter* painter, const QRectF& bounds) const
{
if (m_useNewWidget)
{
m_busyIcon->renderer()->render(painter, bounds);
}
else
{
painter->drawImage(bounds, m_oldBusyIcon->movie()->currentImage());
}
}
QPixmap StyledBusyLabel::GetPixmap(QSize size)
{
QPixmap pixmap(size);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
m_busyIcon->renderer()->render(&painter, pixmap.rect());
return pixmap;
}
void StyledBusyLabel::movieUpdated()
{
emit repaintNeeded();
}
} // namespace AzQtComponents
#include "Components/moc_StyledBusyLabel.cpp"
@@ -0,0 +1,74 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QWidget>
#endif
class QLabel;
class QSvgWidget;
namespace AzQtComponents
{
class Style;
// Widget to display an animated progress GIF
class AZ_QT_COMPONENTS_API StyledBusyLabel
: public QWidget
{
Q_OBJECT
public:
explicit StyledBusyLabel(QWidget* parent = nullptr);
bool GetIsBusy() const;
void SetIsBusy(bool busy);
QString GetText() const;
void SetText(const QString& text);
QString GetBusyIcon() const;
void SetBusyIcon(const QString& iconSource);
int GetBusyIconSize() const;
void SetBusyIconSize(int iconSize);
QSize sizeHint() const override;
void SetUseNewWidget(bool usenew);
void DrawTo(QPainter* painter, const QRectF& bounds) const;
QPixmap GetPixmap(QSize size);
signals:
void repaintNeeded();
public slots:
void movieUpdated();
private:
friend class Style;
void updateMovie();
void loadDefaultIcon();
static bool polish(Style* style, QWidget* widget);
static bool unpolish(Style* style, QWidget* widget);
bool m_isBusy = false;
int m_busyIconSize = 32;
QString m_fileName;
QSvgWidget* m_busyIcon = nullptr;
QLabel* m_oldBusyIcon = nullptr;
QLabel* m_text;
bool m_useNewWidget = false;
};
} // namespace AzQtComponents
@@ -0,0 +1,340 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <qglobal.h> // For Q_OS_WIN
#include <AzQtComponents/Components/StyledDetailsTableModel.h>
#include <QDebug>
namespace AzQtComponents
{
StyledDetailsTableModel::StyledDetailsTableModel(QObject* parent)
: QAbstractListModel(parent)
, m_detailsChangedRoleVector(1, StyledDetailsTableModel::Details)
{
RegisterStatusIcon(StatusError, QPixmap(QStringLiteral(":/stylesheet/img/table_error.png")));
RegisterStatusIcon(StatusWarning, QPixmap(QStringLiteral(":/stylesheet/img/table_warning.png")));
RegisterStatusIcon(StatusSuccess, QPixmap(QStringLiteral(":/stylesheet/img/table_success.png")));
}
StyledDetailsTableModel::~StyledDetailsTableModel()
{
qDeleteAll(m_entries);
}
void StyledDetailsTableModel::AddColumn(const QString& name, StyledDetailsTableModel::ColumnStyle style)
{
const int pos = m_columns.size();
beginInsertColumns({}, pos, pos);
m_columns.resize(pos + 1);
auto& col = m_columns.last();
col.name = name;
col.style = style;
endInsertColumns();
}
void StyledDetailsTableModel::MoveColumn(const QString& name, int toIndex)
{
if (toIndex < 0 || toIndex >= m_columns.size())
{
qWarning() << "Cannot move column out of bounds" << name << toIndex;
return;
}
const int oldIndex = GetColumnIndex(name);
if (oldIndex == -1)
{
qWarning() << "Cannot move non-existent column" << name;
return;
}
if (oldIndex == toIndex)
{
return;
}
const QModelIndex parent;
beginMoveColumns(parent, oldIndex, oldIndex, parent, toIndex + 1);
m_columns.insert(toIndex, m_columns.takeAt(oldIndex));
endMoveColumns();
}
void StyledDetailsTableModel::AddColumnAlias(const QString& aliasName, const QString& columnName)
{
m_columnAliases.insert(aliasName, columnName);
}
int StyledDetailsTableModel::GetColumnIndex(const QString& name) const
{
auto searchName = name;
const auto alias = m_columnAliases.find(name);
if (alias != m_columnAliases.end())
{
searchName = alias.value();
}
for (int i = 0; i < m_columns.size(); ++i)
{
if (m_columns.at(i).name == searchName)
{
return i;
}
}
return -1;
}
void StyledDetailsTableModel::AddEntry(const TableEntry& entry)
{
InternalTableEntry internalEntry;
for (const auto& t : entry.m_entries)
{
internalEntry.push_back({t.first, t.second});
}
const auto sortColName = GetColumnName(m_sortColumn);
int pos;
for (pos = m_entries.size() - 1; pos >= 0; --pos)
{
if (!AppearsAbove(&internalEntry, m_entries.at(pos), sortColName, m_sortOrder))
{
break;
}
}
++pos;
beginInsertRows({}, pos, pos);
m_entries.insert(pos, new InternalTableEntry(internalEntry));
endInsertRows();
}
void StyledDetailsTableModel::AddPrioritizedKey(const QString& key)
{
if (!m_prioritizedKeys.contains(key))
{
m_prioritizedKeys.append(key);
DetailsUpdated();
}
}
void StyledDetailsTableModel::RemovePrioritizedKey(const QString& key)
{
const int index = m_prioritizedKeys.indexOf(key);
if (index != -1)
{
m_prioritizedKeys.removeAt(index);
DetailsUpdated();
}
}
void StyledDetailsTableModel::AddDeprioritizedKey(const QString& key)
{
if (!m_deprioritizedKeys.contains(key))
{
m_deprioritizedKeys.append(key);
DetailsUpdated();
}
}
void StyledDetailsTableModel::RemoveDeprioritizedKey(const QString& key)
{
const int index = m_deprioritizedKeys.indexOf(key);
if (index != -1)
{
m_deprioritizedKeys.removeAt(index);
DetailsUpdated();
}
}
QVariant StyledDetailsTableModel::GetEntryData(const StyledDetailsTableModel::InternalTableEntry* entry, const QString& column) const
{
const auto match = [&column](const StyledDetailsTableModel::InternalTableData& data)
{
return data.key == column;
};
const auto canonical = std::find_if(entry->begin(), entry->end(), match);
if (canonical != entry->end())
{
return canonical->value;
}
const auto aliases = m_columnAliases.keys(column);
for (const auto& alias: aliases)
{
const auto aliasMatch = [&alias](const StyledDetailsTableModel::InternalTableData& data)
{
return data.key == alias;
};
const auto keyIt = std::find_if(entry->begin(), entry->end(), aliasMatch);
if (keyIt != entry->end())
{
return keyIt->value;
}
}
return {};
}
bool StyledDetailsTableModel::AppearsAbove(const StyledDetailsTableModel::InternalTableEntry* lhs,
const StyledDetailsTableModel::InternalTableEntry* rhs,
const QString& column, Qt::SortOrder order) const
{
const auto lhsVal = GetEntryData(lhs, column);
const auto rhsVal = GetEntryData(rhs, column);
if (lhsVal.toString() < rhsVal.toString())
{
return order == Qt::AscendingOrder;
}
if (lhsVal.toString() > rhsVal.toString())
{
return order == Qt::DescendingOrder;
}
return false;
}
void StyledDetailsTableModel::DetailsUpdated()
{
const int rows = rowCount();
const int columns = columnCount();
if (rows > 0 && columns > 0)
{
emit dataChanged(index(0, 0), index(rows - 1, columns - 1), m_detailsChangedRoleVector);
}
}
void StyledDetailsTableModel::sort(int colIndex, Qt::SortOrder order)
{
const auto columnName = GetColumnName(colIndex);
if (columnName.isEmpty())
{
return;
}
const QList<QPersistentModelIndex> parents = { {} };
const auto oldEntries = m_entries;
const auto compare = [columnName, order, this](const InternalTableEntry* lhs, const InternalTableEntry* rhs)
{
return AppearsAbove(lhs, rhs, columnName, order);
};
emit layoutAboutToBeChanged(parents, VerticalSortHint);
std::stable_sort(m_entries.begin(), m_entries.end(), compare);
const int colCount = m_columns.size();
for (int newRow = 0, endPos = m_entries.size(); newRow < endPos; ++newRow)
{
const auto oldRow = oldEntries.indexOf(m_entries[newRow]);
if (newRow == oldRow)
{
continue;
}
for (int col = 0; col < colCount; ++col)
{
changePersistentIndex(index(oldRow, col), index(newRow, col));
}
}
m_sortColumn = colIndex;
m_sortOrder = order;
emit layoutChanged(parents);
}
QVariant StyledDetailsTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (section < 0 || section >= m_columns.size() || orientation != Qt::Horizontal || role != Qt::DisplayRole)
{
return {};
}
return m_columns[section].name;
}
QVariant StyledDetailsTableModel::data(const QModelIndex& index, int role) const
{
if (!hasIndex(index.row(), index.column()))
{
return {};
}
const auto& column = m_columns[index.column()];
const auto& entry = m_entries[index.row()];
if (role == StyledDetailsTableModel::HasOnlyDetails)
{
for (const auto& col: m_columns)
{
if (GetEntryData(entry, col.name).isValid())
{
return false;
}
}
return true;
}
if (role == StyledDetailsTableModel::Details)
{
QStringList prioritized, normal, deprioritized;
for (auto it = entry->begin(), end = entry->end(); it != end; ++it)
{
if (GetColumnIndex(it->key) == -1)
{
QString line = QString("%1 - %2").arg(it->key, it->value.toString());
(m_prioritizedKeys.contains(it->key) ? prioritized
: m_deprioritizedKeys.contains(it->key) ? deprioritized
: normal).append(line);
}
}
return (prioritized + normal + deprioritized).join(QStringLiteral("\n"));
}
switch (column.style)
{
case TextString:
if (role == Qt::DisplayRole)
{
return GetEntryData(entry, column.name);
}
break;
case StatusIcon:
if (role == Qt::DecorationRole)
{
int statusType = GetEntryData(entry, column.name).toInt();
return m_statusIcons[statusType];
}
break;
}
return {};
}
int StyledDetailsTableModel::columnCount(const QModelIndex& index) const
{
return index.isValid() ? 0 : m_columns.size();
}
int StyledDetailsTableModel::rowCount(const QModelIndex& index) const
{
return index.isValid() ? 0 : m_entries.size();
}
void StyledDetailsTableModel::RegisterStatusIcon(int statusType, const QPixmap& icon)
{
m_statusIcons.insert(statusType, icon);
}
QString StyledDetailsTableModel::GetColumnName(int colIndex) const
{
const bool outOfRange = colIndex < 0 || colIndex >= m_columns.size();
return outOfRange ? QString() : m_columns[colIndex].name;
}
} // namespace AzQtComponents
#include "Components/moc_StyledDetailsTableModel.cpp"
@@ -0,0 +1,145 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QAbstractListModel>
#include <QString>
#include <QPixmap>
#include <QMap>
#endif
namespace AzQtComponents
{
// Used in conjunction with a StyledDetailsTableView to display
// a list of key value pairs. Any keys mapped to columns
// be displayed in columns, everything else will be combined, in priority
// order, into the Details view, shown when a row is selected.
class AZ_QT_COMPONENTS_API StyledDetailsTableModel
: public QAbstractListModel
{
Q_OBJECT
public:
enum StyledTableRoles
{
Details = Qt::UserRole,
HasOnlyDetails
};
enum ColumnStyle
{
/// Display value as text
TextString,
/// Display an icon representing value text ("error", "warning" or "status")
StatusIcon,
};
enum StatusType
{
StatusError = 100,
StatusWarning = 200,
StatusSuccess = 300,
StatusUser = 400
};
class TableEntry
{
public:
void Add(const QString& key, const QString& value)
{
m_entries.push_back(QPair<QString, QVariant>(key, value));
}
void Add(const QString& key, int statusValue)
{
m_entries.push_back(QPair<QString, QVariant>(key, statusValue));
}
private:
QVector<QPair<QString, QVariant>> m_entries;
friend class StyledDetailsTableModel;
};
explicit StyledDetailsTableModel(QObject* parent = nullptr);
~StyledDetailsTableModel() override;
void AddColumn(const QString& name, ColumnStyle style = TextString);
void MoveColumn(const QString& name, int toIndex);
void AddColumnAlias(const QString& aliasName, const QString& columnName);
int GetColumnIndex(const QString& name) const;
void AddEntry(const TableEntry& entry);
void AddPrioritizedKey(const QString& key);
void RemovePrioritizedKey(const QString& key);
void AddDeprioritizedKey(const QString& key);
void RemoveDeprioritizedKey(const QString& key);
void sort(int colIndex, Qt::SortOrder order = Qt::AscendingOrder) override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
int columnCount(const QModelIndex& index = {}) const override;
int rowCount(const QModelIndex& index = {}) const override;
void RegisterStatusIcon(int statusType, const QPixmap& icon);
private:
struct InternalTableData
{
QString key;
QVariant value;
};
typedef QVector<InternalTableData> InternalTableEntry;
struct Column
{
QString name;
bool hidden = false;
ColumnStyle style = TextString;
};
enum EntryType
{
Prioritized,
Normal,
Deprioritized
};
QString GetColumnName(int colIndex) const;
QVariant GetEntryData(const InternalTableEntry* entry, const QString& column) const;
bool AppearsAbove(const InternalTableEntry* lhs, const InternalTableEntry* rhs, const QString& column, Qt::SortOrder) const;
void DetailsUpdated();
int m_sortColumn = -1;
Qt::SortOrder m_sortOrder = Qt::AscendingOrder;
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'AzQtComponents::StyledDetailsTableModel::m_columnAliases': class 'QHash<QString,QString>' needs to have dll-interface to be used by clients of class 'AzQtComponents::StyledDetailsTableModel'
QHash<QString, QString> m_columnAliases;
QVector<Column> m_columns;
QVector<InternalTableEntry*> m_entries;
QVector<QString> m_prioritizedKeys;
QVector<QString> m_deprioritizedKeys;
QMap<int, QPixmap> m_statusIcons;
// non-static for allocation tracking
QVector<int> m_detailsChangedRoleVector;
AZ_POP_DISABLE_WARNING
};
} // namespace AzQtComponents
@@ -0,0 +1,511 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/StyledDetailsTableView.h>
#include <AzQtComponents/Components/StyledDetailsTableModel.h>
#include <QStyledItemDelegate>
#include <QItemSelectionModel>
#include <QHeaderView>
#include <QProxyStyle>
#include <QPainter>
#include <QApplication>
#include <QStyleFactory>
#include <QDebug>
#include <QScrollBar>
#include <QKeyEvent>
#include <QMimeData>
#include <QClipboard>
#include <QMenu>
#include <QTimer>
namespace AzQtComponents
{
static const int StyledTreeDetailsPadding = 20;
struct StyledDetailsTableDetailsInfo
{
StyledDetailsTableDetailsInfo(const QStyleOptionViewItem& opt, const QModelIndex& index)
{
if (auto view = qobject_cast<const QTableView*>(opt.widget))
{
auto firstCol = index.sibling(index.row(), 0);
const auto data = firstCol.data(StyledDetailsTableModel::Details);
if (data.isValid())
{
option = opt;
option.rect.setLeft(StyledTreeDetailsPadding);
option.rect.setRight(view->horizontalHeader()->length() - StyledTreeDetailsPadding);
// for some strange reason, and not well-documented, you have to use QChar::LineSeparator
// so that the view actually renders newlines.
option.text = data.toString().replace(QChar('\n'), QChar::LineSeparator);
option.features = QStyleOptionViewItem::HasDisplay | QStyleOptionViewItem::WrapText;
option.state &= ~(QStyle::State_Selected);
option.icon = {};
option.displayAlignment = Qt::Alignment(Qt::AlignVCenter | Qt::AlignLeft);
sizeHint = view->style()->sizeFromContents(QStyle::CT_ItemViewItem, &option, {}, view);
sizeHint.rheight() += StyledTreeDetailsPadding * 2;
option.rect.setTop(option.rect.bottom() - sizeHint.height());
}
}
}
QSize sizeHint = { 0, 0 };
QStyleOptionViewItem option;
};
class StyledTableStyle : public QProxyStyle
{
public:
explicit StyledTableStyle(QObject* parent)
: QProxyStyle()
{
setParent(parent);
}
QRect subElementRect(SubElement element, const QStyleOption* option, const QWidget* widget) const override
{
auto rect = QProxyStyle::subElementRect(element, option, widget);
switch (element)
{
case SE_ItemViewItemText:
case SE_ItemViewItemCheckIndicator:
case SE_ItemViewItemDecoration:
if (option->state.testFlag(State_Selected))
{
auto vOpt = static_cast<const QStyleOptionViewItem*>(option);
const auto offset = StyledDetailsTableDetailsInfo(*vOpt, vOpt->index).sizeHint.height();
if (element == SE_ItemViewItemText)
{
rect.setBottom(rect.bottom() - offset);
}
else
{
rect.setTop(rect.top() - offset);
}
}
if (element == SE_ItemViewItemDecoration)
{
rect.moveLeft(rect.left() + StyledTreeDetailsPadding);
}
break;
default:
break;
}
return rect;
}
void drawPrimitive(PrimitiveElement element, const QStyleOption* option,
QPainter* painter, const QWidget* widget) const override
{
switch (element)
{
case PE_PanelItemViewItem:
case PE_PanelItemViewRow:
{
const auto cg = !option->state.testFlag(State_Enabled) ? QPalette::Disabled
: option->state.testFlag(State_Active) ? QPalette::Normal
: QPalette::Inactive;
if (option->state.testFlag(State_Selected))
{
painter->fillRect(option->rect, option->palette.brush(cg, QPalette::Highlight));
}
else if (auto vOpt = qstyleoption_cast<const QStyleOptionViewItem*>(option))
{
if (vOpt->features.testFlag(QStyleOptionViewItem::Alternate))
{
painter->fillRect(option->rect, option->palette.brush(cg, QPalette::AlternateBase));
}
}
break;
}
default:
QProxyStyle::drawPrimitive(element, option, painter, widget);
break;
}
}
};
class StyledDetailsTableDelegate : public QStyledItemDelegate
{
public:
StyledDetailsTableDelegate(StyledDetailsTableView* table)
: QStyledItemDelegate(table)
, m_table(table)
{
}
void paint(QPainter* painter, const QStyleOptionViewItem& opt, const QModelIndex& index) const override
{
auto copy = opt;
initStyleOption(&copy, index);
PrecalculateHeights(opt, index);
QVariant decorationData = index.data(Qt::DecorationRole);
if (decorationData.isNull())
{
// draw the item once, without text, so that we get the proper background rendering of everything
auto noTextOptions = copy;
noTextOptions.text = "";
DrawItemViewItem(painter, noTextOptions);
// draw the item again, with the text this time, but with the adjusted height;
// we need to specify the height here because it was mucked with earlier for
// the Details and we want the text centered vertically within the original box
auto textOptions = copy;
textOptions.state &= ~(QStyle::State_Selected);
textOptions.rect.setHeight(m_maximumTextHeights[index.row()]);
textOptions.displayAlignment = Qt::Alignment(Qt::AlignVCenter | Qt::AlignLeft);
DrawItemViewItem(painter, textOptions);
}
else
{
// draw the cell without the pixmap so that the background draws properly
auto pixmapOptions = copy;
pixmapOptions.icon = {};
DrawItemViewItem(painter, pixmapOptions);
// draw the pixmap, centered according to the maximum text height precalculated for this row
QPixmap pix = qvariant_cast<QPixmap>(decorationData);
int maxTextHeight = m_maximumTextHeights[index.row()];
QPoint pos = { pixmapOptions.rect.center().x(), pixmapOptions.rect.top() + (maxTextHeight / 2) - (pix.height() / 2) };
painter->drawPixmap(pos, pix);
}
if (!m_detailsOptions.contains(index.row()) &&
(opt.state.testFlag(QStyle::State_Selected) ||
index.data(StyledDetailsTableModel::HasOnlyDetails).toBool()))
{
m_detailsOptions.insert(index.row(), StyledDetailsTableDetailsInfo(copy, index).option);
}
}
void DrawDetails(QWidget* viewport) const
{
QPainter painter(viewport);
for (const auto &opt: m_detailsOptions)
{
DrawItemViewItem(&painter, opt);
}
m_detailsOptions.clear();
m_precalculatedHeights.clear();
m_maximumTextHeights.clear();
}
void DrawItemViewItem(QPainter* painter, const QStyleOptionViewItem& option) const
{
const auto widget = option.widget;
const auto style = widget ? widget->style() : qApp->style();
QStyleOptionViewItem copy = option;
copy.state &= ~(QStyle::State_Selected);
style->drawControl(QStyle::CE_ItemViewItem, &copy, painter, widget);
}
QSize sizeHint(const QStyleOptionViewItem& opt, const QModelIndex& index) const override
{
QStyleOptionViewItem copy = opt;
initStyleOption(&copy, index);
const auto widget = opt.widget;
const auto style = widget ? widget->style() : qApp->style();
auto size = style->sizeFromContents(QStyle::CT_ItemViewItem, &copy, QSize(), widget);
// option not fully initialized (no State_Selected), use the widget
const auto view = qobject_cast<const QAbstractItemView*>(widget);
if (index.data(StyledDetailsTableModel::HasOnlyDetails).toBool())
{
size.rheight() = StyledDetailsTableDetailsInfo(opt, index).sizeHint.height();
}
else if (view && view->selectionModel()->isSelected(index))
{
size.rheight() += StyledDetailsTableDetailsInfo(opt, index).sizeHint.height();
}
if (copy.features.testFlag(QStyleOptionViewItem::HasDecoration))
{
size.rwidth() += StyledTreeDetailsPadding * 2;
}
return size;
}
void Reset()
{
m_detailsOptions.clear();
m_precalculatedHeights.clear();
m_maximumTextHeights.clear();
}
protected:
void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override
{
QStyledItemDelegate::initStyleOption(option, index);
option->decorationAlignment = Qt::Alignment(Qt::AlignTop | Qt::AlignHCenter);
option->decorationPosition = QStyleOptionViewItem::Left;
// Don't show focused state
option->state &= ~(QStyle::State_HasFocus);
}
void PrecalculateHeights(const QStyleOptionViewItem& opt, const QModelIndex& index) const
{
// check if we've already calculated the text heights for this row
if (m_precalculatedHeights.contains(index.row()))
{
return;
}
const auto widget = opt.widget;
const auto style = widget ? widget->style() : qApp->style();
// need to calculate it now; do some work for every row, even the invisible ones
int columnCount = m_table->model()->columnCount(QModelIndex());
for (int i = 0; i < columnCount; i++)
{
QModelIndex tempIndex = index.sibling(index.row(), i);
QVariant decorationData = tempIndex.data(Qt::DecorationRole);
if (!decorationData.isNull())
{
continue;
}
QString textData = tempIndex.data(Qt::DisplayRole).toString();
// figure out what the original height of the cell should be
// pretending it's not selected so we won't get any special details heights added
auto textOptions = opt;
textOptions.state &= ~(QStyle::State_Selected);
textOptions.features |= QStyleOptionViewItem::WrapText;
textOptions.features |= QStyleOptionViewItem::HasDisplay;
textOptions.text = textData;
textOptions.rect.setWidth(m_table->columnWidth(i));
QSize originalContentsSize = style->sizeFromContents(QStyle::CT_ItemViewItem, &textOptions, {}, widget);
m_precalculatedHeights[tempIndex.row()][i] = originalContentsSize.height();
if (m_maximumTextHeights[tempIndex.row()] < originalContentsSize.height())
{
m_maximumTextHeights[tempIndex.row()] = originalContentsSize.height();
}
}
}
private:
StyledDetailsTableView* m_table;
mutable QHash<int, QStyleOptionViewItem> m_detailsOptions;
mutable QHash<int, QHash<int, int>> m_precalculatedHeights;
mutable QHash<int, int> m_maximumTextHeights;
};
StyledDetailsTableView::StyledDetailsTableView(QWidget* parent)
: QTableView(parent)
, m_resizeTimer(new QTimer(this))
{
setStyle(new StyledTableStyle(qApp));
setAlternatingRowColors(true);
setSelectionMode(SingleSelection);
setSelectionBehavior(SelectRows);
setShowGrid(false);
setItemDelegate(new StyledDetailsTableDelegate(this));
setSortingEnabled(true);
verticalHeader()->hide();
auto font = horizontalHeader()->font();
font.setBold(true);
font.setPointSize(font.pointSize() + 1);
horizontalHeader()->setFont(font);
horizontalHeader()->setStyle(qApp->style());
horizontalHeader()->setHighlightSections(false);
horizontalHeader()->setStretchLastSection(true);
horizontalHeader()->setDefaultAlignment(Qt::Alignment(Qt::AlignLeft | Qt::AlignVCenter));
setContextMenuPolicy(Qt::ActionsContextMenu);
QAction* copyAction = new QAction(tr("Copy"), this);
copyAction->setShortcut(QKeySequence::Copy);
connect(copyAction, &QAction::triggered, this, &StyledDetailsTableView::copySelectionToClipboard);
addAction(copyAction);
m_resizeTimer->setSingleShot(true);
m_resizeTimer->setInterval(0);
connect(m_resizeTimer, &QTimer::timeout, this, [this]()
{
resizeRowsToContents();
if (m_scrollOnInsert)
{
m_scrollOnInsert = false;
scrollToBottom();
}
scheduleDelayedItemsLayout();
});
auto startResizeTimer = static_cast<void(QTimer::*)(void)>(&QTimer::start);
connect(horizontalHeader(), &QHeaderView::geometriesChanged, m_resizeTimer, startResizeTimer);
connect(horizontalHeader(), &QHeaderView::sectionResized, m_resizeTimer, startResizeTimer);
}
void StyledDetailsTableView::setModel(QAbstractItemModel* model)
{
if (selectionModel())
{
selectionModel()->disconnect(this);
}
if (model)
{
model->disconnect(this);
}
QTableView::setModel(model);
if (model)
{
auto startResizeTimer = static_cast<void(QTimer::*)(void)>(&QTimer::start);
connect(model, &QAbstractItemModel::layoutChanged, m_resizeTimer, startResizeTimer);
connect(model, &QAbstractItemModel::rowsInserted, m_resizeTimer, startResizeTimer);
connect(model, &QAbstractItemModel::rowsAboutToBeInserted, this, [this]
{
m_scrollOnInsert = !selectionModel()->hasSelection()
&& (verticalScrollBar()->value() == verticalScrollBar()->maximum());
});
connect(model, &QAbstractItemModel::dataChanged, this,
[this](const QModelIndex&, const QModelIndex&, const QVector<int>& roles)
{
if (roles.contains(StyledDetailsTableModel::Details))
{
updateItemSelection(selectionModel()->selection());
}
});
}
if (selectionModel())
{
connect(selectionModel(), &QItemSelectionModel::selectionChanged, this,
[this](const QItemSelection& sel, const QItemSelection& desel)
{
updateItemSelection(desel);
updateItemSelection(sel);
});
}
}
void StyledDetailsTableView::ResetDelegate()
{
auto delegate = static_cast<StyledDetailsTableDelegate*>(itemDelegate());
if (delegate)
{
delegate->Reset();
}
}
void StyledDetailsTableView::paintEvent(QPaintEvent* ev)
{
auto delegate = static_cast<StyledDetailsTableDelegate*>(itemDelegate());
QTableView::paintEvent(ev);
delegate->DrawDetails(viewport());
}
void StyledDetailsTableView::keyPressEvent(QKeyEvent* ev)
{
QTableView::keyPressEvent(ev);
}
QItemSelectionModel::SelectionFlags StyledDetailsTableView::selectionCommand(
const QModelIndex& index, const QEvent* event) const
{
auto base = QTableView::selectionCommand(index, event);
if (!selectionModel()->isSelected(index) || event->type() == QEvent::MouseMove ||
!base.testFlag(QItemSelectionModel::ClearAndSelect))
{
return base;
}
if (event->type() == QEvent::MouseButtonPress)
{
auto mEv = static_cast<const QMouseEvent*>(event);
if (mEv->button() != Qt::LeftButton)
{
return base;
}
}
// Toggle selection off during selection event if already selected
return QItemSelectionModel::Rows | QItemSelectionModel::Deselect;
}
void StyledDetailsTableView::copySelectionToClipboard()
{
const auto selection = selectionModel()->selection();
if (selection.isEmpty())
{
return;
}
auto index = selection.first().topLeft();
const QString details = index.data(StyledDetailsTableModel::Details).toString();
QStringList cells;
if (!index.data(StyledDetailsTableModel::HasOnlyDetails).toBool())
{
while (index.isValid())
{
cells.append(index.data(Qt::DisplayRole).toString());
index = index.sibling(index.row(), index.column() + 1);
}
}
auto clipboard = qApp->clipboard();
auto qdata = new QMimeData();
{
const static auto textFormat = QStringLiteral("%1\n%2");
qdata->setText(textFormat.arg(cells.join(QChar::fromLatin1('\t')), details).trimmed());
}
{
const static auto htmlFormat = QStringLiteral("<table><tr>%1</tr><tr colspan=%2>%3</tr></table>");
const static auto htmlCellFormat = QStringLiteral("<td>%1</td>");
const static auto cellsToHtml = [](const QStringList cells)
{
QString row;
for (const auto &cell: cells)
{
row += htmlCellFormat.arg(cell);
}
return row;
};
qdata->setHtml(htmlFormat.arg(cellsToHtml(cells),
QString::number(model()->columnCount()),
htmlCellFormat.arg(details)));
}
clipboard->setMimeData(qdata);
}
void StyledDetailsTableView::updateItemSelection(const QItemSelection& selection)
{
for (const auto& range : selection)
{
for (int i = range.top(); i <= range.bottom(); ++i)
{
resizeRowToContents(i);
}
}
}
} // namespace AzQtComponents
#include "Components/moc_StyledDetailsTableView.cpp"
@@ -0,0 +1,50 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QTableView>
#endif
class QTimer;
namespace AzQtComponents
{
class StyledDetailsTableModel;
class AZ_QT_COMPONENTS_API StyledDetailsTableView
: public QTableView
{
Q_OBJECT
public:
explicit StyledDetailsTableView(QWidget* parent = nullptr);
void setModel(QAbstractItemModel* model) override;
void ResetDelegate();
protected:
void paintEvent(QPaintEvent* ev) override;
void keyPressEvent(QKeyEvent* ev) override;
QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex&, const QEvent*) const override;
private:
void copySelectionToClipboard();
void updateItemSelection(const QItemSelection& selection);
private:
bool m_scrollOnInsert = false;
QTimer* m_resizeTimer;
};
} // namespace AzQtComponents
@@ -0,0 +1,47 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/StyledDialog.h>
#include <AzQtComponents/Components/WindowDecorationWrapper.h>
#include <QDebug>
#include <QResizeEvent>
namespace AzQtComponents
{
StyledDialog::StyledDialog(QWidget* parent, Qt::WindowFlags f)
: QDialog(new WindowDecorationWrapper(WindowDecorationWrapper::OptionAutoAttach | WindowDecorationWrapper::OptionAutoTitleBarButtons, parent), f)
{
}
void StyledDialog::enableSaveRestoreGeometry(const QString& key)
{
auto windowDecorator = qobject_cast<WindowDecorationWrapper*>(parent());
if (windowDecorator != nullptr)
{
windowDecorator->enableSaveRestoreGeometry(key);
}
}
bool StyledDialog::restoreGeometryFromSettings()
{
auto windowDecorator = qobject_cast<WindowDecorationWrapper*>(parent());
if (windowDecorator != nullptr)
{
return windowDecorator->restoreGeometryFromSettings();
}
return false;
}
} // namespace AzQtComponents
#include "Components/moc_StyledDialog.cpp"
@@ -0,0 +1,42 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
// Disables warning messages triggered by the Qt library
// 4251: class needs to have dll-interface to be used by clients of class
// 4800: forcing value to bool 'true' or 'false' (performance warning)
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option")
#include <QDialog>
AZ_POP_DISABLE_WARNING
#endif
namespace AzQtComponents
{
class WindowDecorationWrapper;
class AZ_QT_COMPONENTS_API StyledDialog
: public QDialog
{
Q_OBJECT
public:
explicit StyledDialog(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
void enableSaveRestoreGeometry(const QString& key);
bool restoreGeometryFromSettings();
};
} // namespace AzQtComponents
@@ -0,0 +1,309 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/DockMainWindow.h>
#include <AzQtComponents/Components/StyledDockWidget.h>
#include <AzQtComponents/Components/DockMainWindow.h>
#include <AzQtComponents/Components/Titlebar.h>
#include <AzQtComponents/Components/WindowDecorationWrapper.h>
#include <AzQtComponents/Components/TitleBarOverdrawHandler.h>
#include <QGuiApplication>
#include <QMainWindow>
#include <QMouseEvent>
#include <QOperatingSystemVersion>
#include <QPainter>
#include <QStyleOptionFrame>
#include <QStylePainter>
#include <QWindow>
#if QT_VERSION < QT_VERSION_CHECK(5, 6, 1) && defined(Q_OS_WIN32)
#include <QtGui/private/qwindow_p.h>
#endif
namespace AzQtComponents
{
namespace Platform
{
// Forward declare these since they will be defined per platform
void HandleFloatingWindow(QWidget* floatingWindow);
bool FloatingWindowsSupportMinimize();
}
static bool forceSkipTitleBarOverdraw()
{
#ifdef Q_OS_WIN
if ((QOperatingSystemVersion::current() < QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10)))
{
// non-win10 never uses title bar overdraw
return true;
}
return false;
#else
// Non-windows never uses title bar overdraw
return true;
#endif
}
StyledDockWidget::StyledDockWidget(const QString& name, QWidget* parent)
: StyledDockWidget(name, false, parent)
{
}
StyledDockWidget::StyledDockWidget(const QString& name, bool skipTitleBarDrawing, QWidget* parent)
: QDockWidget(name, parent)
, m_skipTitleBarOverdraw(skipTitleBarDrawing || forceSkipTitleBarOverdraw())
{
init();
}
StyledDockWidget::StyledDockWidget(QWidget* parent)
: StyledDockWidget(QString(), parent)
{
}
void StyledDockWidget::init()
{
if (doesTitleBarOverdraw() && TitleBarOverdrawHandler::getInstance())
{
TitleBarOverdrawHandler::getInstance()->addTitleBarOverdrawWidget(this);
}
connect(this, &QDockWidget::topLevelChanged, this, &StyledDockWidget::onFloatingChanged);
createCustomTitleBar();
}
StyledDockWidget::~StyledDockWidget()
{
}
/**
* Checks if this dock widget is the only visible dock widget in a floating
* main window
*/
bool StyledDockWidget::isSingleFloatingChild()
{
// Check if our parent is a fancy docking QMainWindow with no central
// widget, which means it is one of the floating main windows
DockMainWindow* parentMainWindow = qobject_cast<DockMainWindow*>(parentWidget());
if (parentMainWindow && parentMainWindow->HasFancyDocking() && !parentMainWindow->centralWidget())
{
// Make sure the parent dock widget of the main window is floating to handle
// cases where there are nested main windows
StyledDockWidget* parentDockWidget = qobject_cast<StyledDockWidget*>(parentMainWindow->parentWidget());
if (parentDockWidget && parentDockWidget->isFloating())
{
bool singleFloating = true;
for (QDockWidget* dockWidget : parentMainWindow->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly))
{
if (dockWidget->isVisible() && dockWidget != this)
{
singleFloating = false;
break;
}
}
return singleFloating;
}
}
return false;
}
void StyledDockWidget::closeEvent(QCloseEvent* event)
{
// give the sub-widget a chance to veto the close; necessary for the UI Editor, among other things
QCloseEvent closeEvent;
QCoreApplication::sendEvent(widget(), &closeEvent);
// If widget accepted the close event, we delete the dockwidget, which will also delete the child widget in case it doesn't have Qt::WA_DeleteOnClose
if (!closeEvent.isAccepted())
{
// Widget doesn't want to close
event->ignore();
return;
}
Q_EMIT aboutToClose();
QDockWidget::closeEvent(event);
}
void StyledDockWidget::showEvent(QShowEvent* event)
{
if (auto titleBar = qobject_cast<TitleBar*>(titleBarWidget()))
{
// When docked, we don't have a window frame, so draw the left and right border
titleBar->setDrawSideBorders(!isFloating());
}
if (isFloating())
{
fixFramelessFlags();
}
QDockWidget::showEvent(event);
}
bool StyledDockWidget::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
return WindowDecorationWrapper::handleNativeEvent(eventType, message, result, this);
}
/**
* Override of event handler so that we can ignore the Mouse events on our dock widgets.
* This fixes an issue where the QDockWidget only respects the movable feature on mouse press,
* not on non client area events (e.g. resizing); this also fixes another issue affecting
* DockWidgets in a standalone QWindow, that could be detached from said Window through
* the legacy Qt dragging.
* We disable the movable feature on our dock widgets so that we can use our own custom
* docking solution instead of the default Qt docking, but we need to override these events
* that get triggered when resizing otherwise it will activate the default qt docking.
*/
bool StyledDockWidget::event(QEvent* event)
{
switch (event->type())
{
case QEvent::NonClientAreaMouseMove:
case QEvent::NonClientAreaMouseButtonPress:
case QEvent::NonClientAreaMouseButtonRelease:
case QEvent::NonClientAreaMouseButtonDblClick:
{
return true;
}
case QEvent::MouseButtonPress:
case QEvent::MouseMove:
case QEvent::MouseButtonRelease:
{
// For these events, make sure FancyDocking is being used or it will disable
// Mouse events for the whole Widget
DockMainWindow* parentMainWindow = qobject_cast<DockMainWindow*>(parentWidget());
if (parentMainWindow && parentMainWindow->HasFancyDocking())
{
return true;
}
}
}
return QDockWidget::event(event);
}
void StyledDockWidget::paintEvent(QPaintEvent*)
{
// By default QDockWidget::paintEvent only draws the frame if the dock widget doesn't have
// a custom title bar and does not have native window decorations. As a result, QDockWidget
// cannot be styled using QSS when floating.
QStylePainter p(this);
QStyleOptionFrame framOpt;
framOpt.init(this);
p.drawPrimitive(QStyle::PE_FrameDockWidget, framOpt);
}
bool StyledDockWidget::doesTitleBarOverdraw() const
{
return !m_skipTitleBarOverdraw;
}
bool StyledDockWidget::skipTitleBarOverdraw() const
{
return m_skipTitleBarOverdraw;
}
void StyledDockWidget::fixFramelessFlags()
{
// This ensures we have native frames (but no native titlebar)
QWindow* w = windowHandle();
if (doesTitleBarOverdraw() && w && (w->flags() & Qt::FramelessWindowHint) && isFloating())
{
w->setFlags(WindowDecorationWrapper::specialFlagsForOS() | Qt::Tool);
}
}
void StyledDockWidget::onFloatingChanged(bool floating)
{
if (floating)
{
fixFramelessFlags();
// Perform platform-specific handling for floating windows (e.g. minimizing into the taskbar)
Platform::HandleFloatingWindow(window());
}
// If we have a custom title bar, then we need to enable the dragging
// to reposition our dock widget if floating is enabled, change it
// to be drawn in simple mode, and update the buttons
DockMainWindow* parentMainWindow = qobject_cast<DockMainWindow*>(parentWidget());
if (parentMainWindow && parentMainWindow->HasFancyDocking())
{
TitleBar* titleBar = customTitleBar();
if (titleBar)
{
titleBar->setDragEnabled(floating);
titleBar->setDrawSimple(floating);
if (floating)
{
TitleBar::WindowDecorationButtons buttons = { DockBarButton::MaximizeButton, DockBarButton::CloseButton };
if (Platform::FloatingWindowsSupportMinimize())
{
buttons.prepend(DockBarButton::MinimizeButton);
}
titleBar->setButtons(buttons);
}
}
}
}
void StyledDockWidget::createCustomTitleBar()
{
QWidget* tw = titleBarWidget();
if (tw)
{
tw->deleteLater();
}
TitleBar* titleBar = new TitleBar(this);
titleBar->setTearEnabled(true);
titleBar->setDrawSideBorders(false);
QObject::connect(titleBar, &TitleBar::undockAction, this, &StyledDockWidget::undock);
QObject::connect(this, &QDockWidget::windowTitleChanged, titleBar, &TitleBar::setWindowTitleOverride);
setTitleBarWidget(titleBar);
}
/** static */
void StyledDockWidget::drawFrame(QPainter& p, QRect rect, bool drawTop)
{
p.save();
p.setPen(QColor(33, 34, 35));
rect.adjust(0, p.pen().width(), 0, 0);
if (drawTop)
{
p.drawLine(QLine(rect.topLeft(), rect.topRight()));
}
p.drawLine(QLine(rect.topLeft(), rect.bottomLeft()));
p.drawLine(QLine(rect.topRight(), rect.bottomRight()));
p.drawLine(QLine(rect.bottomLeft(), rect.bottomRight()));
p.restore();
}
TitleBar* StyledDockWidget::customTitleBar() const
{
return qobject_cast<TitleBar*>(titleBarWidget());
}
} // namespace AzQtComponents
#include "Components/moc_StyledDockWidget.cpp"
@@ -0,0 +1,69 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QDockWidget>
#include <QPoint>
#endif
namespace AzQtComponents
{
class TitleBar;
class AZ_QT_COMPONENTS_API StyledDockWidget
: public QDockWidget
{
Q_OBJECT
public:
explicit StyledDockWidget(QWidget* parent = nullptr);
explicit StyledDockWidget(const QString& name, QWidget* parent = nullptr);
explicit StyledDockWidget(const QString& name, bool skipTitleBarOverdraw /* = false */, QWidget* parent = nullptr);
~StyledDockWidget();
static void drawFrame(QPainter& p, QRect rect, bool drawTop = true);
void createCustomTitleBar();
TitleBar* customTitleBar() const;
bool isSingleFloatingChild();
/**
* Returns true if title bar overdraw is being used.
* Title bar overdraw is only used on Windows 10. In this mode we do have native title bar but
* we draw our own on top of it instead of removing it. It's a workaround against Win10
* bug where a white stripe appears if we have native border + no native title bar.
*/
bool doesTitleBarOverdraw() const;
bool skipTitleBarOverdraw() const;
Q_SIGNALS:
void undock();
void aboutToClose();
protected:
void closeEvent(QCloseEvent* event) override;
bool nativeEvent(const QByteArray& eventType, void* message, long* result) override;
bool event(QEvent* event) override;
void showEvent(QShowEvent* event) override;
void paintEvent(QPaintEvent*) override;
private:
void fixFramelessFlags();
void onFloatingChanged(bool floating);
void init();
bool m_skipTitleBarOverdraw = false;
};
} // namespace AzQtComponents
@@ -0,0 +1,84 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/StyledLineEdit.h>
#include <QAction>
#include <QValidator>
namespace AzQtComponents
{
StyledLineEdit::StyledLineEdit(QWidget* parent)
: QLineEdit(parent)
, m_flavor(Plain)
{
setFlavor(Plain);
connect(this, &StyledLineEdit::textChanged, this, &StyledLineEdit::validateEntry);
}
StyledLineEdit::~StyledLineEdit()
{
}
StyledLineEdit::Flavor StyledLineEdit::flavor() const
{
return m_flavor;
}
void StyledLineEdit::setFlavor(StyledLineEdit::Flavor flavor)
{
if (flavor != m_flavor)
{
m_flavor = flavor;
emit flavorChanged();
}
}
void StyledLineEdit::focusInEvent(QFocusEvent* event)
{
emit(onFocus()); // Required for focus dependent custom widgets, e.g. ConfigStringLineEditCtrl.
QLineEdit::focusInEvent(event);
}
void StyledLineEdit::focusOutEvent(QFocusEvent* event)
{
emit(onFocusOut());
QLineEdit::focusOutEvent(event);
}
void StyledLineEdit::validateEntry()
{
QString textToValidate = text();
int length = textToValidate.length();
if (!validator())
{
return;
}
if (validator()->validate(textToValidate, length) == QValidator::Acceptable && length > 0)
{
setFlavor(StyledLineEdit::Valid);
}
else if (validator()->validate(textToValidate, length) == QValidator::Acceptable && length <= 0)
{
setFlavor(StyledLineEdit::Plain);
}
else
{
setFlavor(StyledLineEdit::Invalid);
}
}
} // namespace AzQtComponents
#include "Components/moc_StyledLineEdit.cpp"
@@ -0,0 +1,61 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QLineEdit>
#endif
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API StyledLineEdit
: public QLineEdit
{
Q_OBJECT
Q_PROPERTY(Flavor flavor READ flavor WRITE setFlavor NOTIFY flavorChanged)
public:
enum Flavor
{
Plain = 0,
Information,
Question,
Invalid,
Valid,
FlavorCount
};
Q_ENUM(Flavor)
explicit StyledLineEdit(QWidget* parent = nullptr);
~StyledLineEdit();
Flavor flavor() const;
void setFlavor(Flavor);
protected:
void focusInEvent(QFocusEvent* event) override;
void focusOutEvent(QFocusEvent* event) override;
signals:
void flavorChanged();
void onFocus(); // Required for focus dependent custom widgets, e.g. ConfigStringLineEditCtrl.
void onFocusOut();
private:
void validateEntry();
Flavor m_flavor;
};
} // namespace AzQtComponents
@@ -0,0 +1,640 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/StyledSpinBox.h>
#include <math.h>
#include <QApplication>
#include <QIntValidator>
#include <QLineEdit>
#include <QSignalBlocker>
namespace AzQtComponents
{
// Slider parameters
const int sliderDefaultHeight = 35;
const int sliderDefaultWidth = 192;
// Decimal precision parameters
const int decimalPrecisonDefault = 7;
const int decimalDisplayPrecisionDefault = 3;
class FocusInEventFilterPrivate
: public QObject
{
public:
FocusInEventFilterPrivate(StyledDoubleSpinBox* spinBox)
: QObject(spinBox)
, m_spinBox(spinBox) {}
protected:
bool eventFilter(QObject* obj, QEvent* event) override
{
if (event->type() == QEvent::FocusIn)
{
m_spinBox->displaySlider();
}
else if (event->type() == QEvent::FocusOut)
{
// Don't allow the focus out event to propogate if the mouse was
// clicked on the slider
if (m_spinBox->isMouseOnSlider())
{
return true;
}
// Otherwise, hide the slider once we have lost focus
else
{
m_spinBox->hideSlider();
}
}
return QObject::eventFilter(obj, event);
}
private:
StyledDoubleSpinBox* m_spinBox;
};
class ClickEventFilterPrivate
: public QObject
{
public:
explicit ClickEventFilterPrivate(StyledDoubleSpinBox* spinBox, QSlider* slider)
: QObject(spinBox)
, m_spinBox(spinBox)
{
if (slider)
{
connect(slider, &QSlider::sliderPressed, this, [this] { m_dragging = true; });
connect(slider, &QSlider::sliderReleased, this, [this] { m_dragging = false; });
}
}
~ClickEventFilterPrivate() {}
signals:
void clickOnApplication(const QPoint& pos);
protected:
bool eventFilter(QObject* obj, QEvent* event)
{
if (event->type() == QEvent::MouseButtonRelease && !m_dragging)
{
m_spinBox->handleClickOnApp(QCursor::pos());
}
return QObject::eventFilter(obj, event);
}
private:
StyledDoubleSpinBox* m_spinBox;
bool m_dragging = false;
};
StyledDoubleSpinBox::StyledDoubleSpinBox(QWidget* parent)
: QDoubleSpinBox(parent)
, m_restrictToInt(false)
, m_customSliderMinValue(0.0f)
, m_customSliderMaxValue(0.0f)
, m_hasCustomSliderRange(false)
, m_slider(nullptr)
, m_ignoreNextUpdateFromSlider(false)
, m_ignoreNextUpdateFromSpinBox(false)
, m_displayDecimals(decimalDisplayPrecisionDefault)
{
setProperty("class", "SliderSpinBox");
setButtonSymbols(QAbstractSpinBox::NoButtons);
// Set the default decimal precision we will store to a large number
// since we will be truncating the value displayed
setDecimals(decimalPrecisonDefault);
// Our tooltip will be the full decimal value, so keep it updated
// whenever our value changes
QObject::connect(this, static_cast<void(StyledDoubleSpinBox::*)(double)>(&StyledDoubleSpinBox::valueChanged), this, &StyledDoubleSpinBox::UpdateToolTip);
UpdateToolTip(value());
}
StyledDoubleSpinBox::~StyledDoubleSpinBox()
{
delete m_slider;
}
void StyledDoubleSpinBox::SetDisplayDecimals(int precision)
{
m_displayDecimals = precision;
}
QString StyledDoubleSpinBox::StringValue(double value, bool truncated) const
{
// Determine which decimal precision to use for displaying the value
int numDecimals = decimals();
if (truncated && m_displayDecimals < numDecimals)
{
numDecimals = m_displayDecimals;
}
QString stringValue = locale().toString(value, 'f', numDecimals);
// Handle special cases when we have decimals in our value
if (numDecimals > 0)
{
// Remove trailing zeros, since the locale conversion won't do
// it for us
QChar zeroDigit = locale().zeroDigit();
QString trailingZeros = QString("%1+$").arg(zeroDigit);
stringValue.remove(QRegExp(trailingZeros));
// It's possible we could be left with a decimal point on the end
// if we stripped the trailing zeros, so if that's the case, then
// add a zero digit on the end so that it is obvious that this is
// a float value
QChar decimalPoint = locale().decimalPoint();
if (stringValue.endsWith(decimalPoint))
{
stringValue.append(zeroDigit);
}
}
// Copied from the QDoubleSpinBox sub-class to handle removing the
// group separator if necessary
if (!isGroupSeparatorShown() && qAbs(value) >= 1000.0)
{
stringValue.remove(locale().groupSeparator());
}
return stringValue;
}
QString StyledDoubleSpinBox::textFromValue(double value) const
{
// If our widget is focused, then show the full decimal value, otherwise
// show the truncated value
return StringValue(value, !hasFocus());
}
void StyledDoubleSpinBox::UpdateToolTip(double value)
{
// Set our tooltip to the full decimal value
setToolTip(StringValue(value));
}
bool StyledDoubleSpinBox::SliderEnabled() const
{
// If the step size is set to 0, then don't show our slider
return singleStep() != 0.0;
}
void StyledDoubleSpinBox::showEvent(QShowEvent* ev)
{
if (!m_slider)
{
initSlider();
}
prepareSlider();
QWidget::showEvent(ev);
}
void StyledDoubleSpinBox::resizeEvent(QResizeEvent* ev)
{
if (!m_slider)
{
initSlider();
}
prepareSlider();
QDoubleSpinBox::resizeEvent(ev);
}
void StyledDoubleSpinBox::focusInEvent(QFocusEvent* event)
{
QDoubleSpinBox::focusInEvent(event);
// We need to set the special value text to an empty string, which
// effectively makes no change, but actually triggers the line edit
// display value to be updated so that when we receive focus to
// begin editing, we display the full decimal precision instead of
// the truncated display value
setSpecialValueText(QString());
}
void StyledDoubleSpinBox::displaySlider()
{
if (!SliderEnabled())
{
return;
}
if (!m_slider)
{
initSlider();
}
prepareSlider();
m_slider->setFocus();
setProperty("SliderSpinBoxFocused", true);
m_justPassFocusSlider = true;
m_slider->show();
m_slider->raise();
}
void StyledDoubleSpinBox::hideSlider()
{
if (!SliderEnabled())
{
return;
}
// prevent slider to hide when the
// spinbox is passing focus along
if (m_justPassFocusSlider)
{
m_justPassFocusSlider = false;
}
else
{
m_slider->hide();
setProperty("SliderSpinBoxFocused", false);
update();
}
}
void StyledDoubleSpinBox::handleClickOnApp(const QPoint& pos)
{
if (isClickOnSlider(pos) || isClickOnSpinBox(pos))
{
if (m_slider && m_slider->isVisible())
{
displaySlider();
}
}
else
{
hideSlider();
clearFocus();
}
}
bool StyledDoubleSpinBox::isMouseOnSlider()
{
const QPoint& globalPos = QCursor::pos();
return isClickOnSlider(globalPos);
}
bool StyledDoubleSpinBox::isClickOnSpinBox(const QPoint& globalPos)
{
const auto pos = mapFromGlobal(globalPos);
const auto spaceRect = QRect(0, 0, width(), height());
return spaceRect.contains(pos);
}
bool StyledDoubleSpinBox::isClickOnSlider(const QPoint& globalPos)
{
if (!m_slider || m_slider->isHidden())
{
return false;
}
const auto pos = m_slider->mapFromGlobal(globalPos);
auto spaceRect = m_slider->rect();
return spaceRect.contains(pos);
}
void StyledDoubleSpinBox::SetCustomSliderRange(double min, double max)
{
m_customSliderMinValue = min;
m_customSliderMaxValue = max;
m_hasCustomSliderRange = true;
}
double StyledDoubleSpinBox::GetSliderMinimum()
{
if (m_hasCustomSliderRange)
{
return m_customSliderMinValue;
}
return minimum();
}
double StyledDoubleSpinBox::GetSliderRange()
{
if (m_hasCustomSliderRange)
{
return m_customSliderMaxValue - m_customSliderMinValue;
}
return maximum() - minimum();
}
void StyledDoubleSpinBox::prepareSlider()
{
if (!SliderEnabled())
{
return;
}
// If we are treating this as integer only (like QSpinBox), then we can
// set our min/max and value directly to the slider since it uses integers
if (m_restrictToInt)
{
// If we have a custom slider range, then set that, otherwise
// use the same range for the slider as our spinbox
if (m_hasCustomSliderRange)
{
m_slider->setMinimum(static_cast<int>(m_customSliderMinValue));
m_slider->setMaximum(static_cast<int>(m_customSliderMaxValue));
}
else
{
m_slider->setMinimum(static_cast<int>(minimum()));
m_slider->setMaximum(static_cast<int>(maximum()));
}
}
// Otherwise, we need to set a custom scale for our slider using 0 as the
// minimum and a power of 10 based on our decimal precision as the maximum
else
{
int scaledMax = static_cast<int>(pow(10, (int)log10(GetSliderRange()) + decimals()));
m_slider->setMinimum(0);
m_slider->setMaximum(scaledMax);
}
// Set our slider value
updateSliderValue(value());
// detect the required background depending on how close to border is the spinbox
auto globalWindow = QApplication::activeWindow();
if (!globalWindow)
{
return;
}
auto spinBoxTopLeftGlobal = mapToGlobal(QPoint(0, 0));
int globalWidth = globalWindow->x() + globalWindow->width();
if (globalWidth - spinBoxTopLeftGlobal.x() < sliderDefaultWidth)
{
int offset = sliderDefaultWidth - width();
m_slider->setStyleSheet("background: transparent; border-image: url(:/stylesheet/img/styledspinbox-bg-right.png);");
m_slider->setGeometry(spinBoxTopLeftGlobal.x() - offset, spinBoxTopLeftGlobal.y() + height(), sliderDefaultWidth, sliderDefaultHeight);
}
else
{
m_slider->setStyleSheet("background: transparent; border-image: url(:/stylesheet/img/styledspinbox-bg-left.png);");
m_slider->setGeometry(spinBoxTopLeftGlobal.x(), spinBoxTopLeftGlobal.y() + height(), sliderDefaultWidth, sliderDefaultHeight);
}
}
void StyledDoubleSpinBox::initSlider()
{
if (!SliderEnabled())
{
return;
}
m_slider = new StyledSliderPrivate(this);
m_slider->setWindowFlags(Qt::WindowFlags(Qt::Window) | Qt::WindowFlags(Qt::FramelessWindowHint));
QObject::connect(this, static_cast<void(StyledDoubleSpinBox::*)(double)>(&StyledDoubleSpinBox::valueChanged),
this, &StyledDoubleSpinBox::updateSliderValue);
QObject::connect(m_slider, &QSlider::valueChanged,
this, &StyledDoubleSpinBox::updateValueFromSlider);
// These event filters will be automatically removed when our spin box is deleted
// since they are parented to it
qApp->installEventFilter(new ClickEventFilterPrivate(this, m_slider));
installEventFilter(new FocusInEventFilterPrivate(this));
}
void StyledDoubleSpinBox::updateSliderValue(double newVal)
{
if (!m_slider)
{
return;
}
// Ignore this update if it was triggered by the user changing the slider,
// which updated our spin box value
if (m_ignoreNextUpdateFromSpinBox)
{
m_ignoreNextUpdateFromSpinBox = false;
return;
}
// No need to continue if the slider value didn't change
int currentSliderValue = m_slider->value();
int sliderValue = ConvertToSliderValue(newVal);
if (sliderValue == currentSliderValue)
{
return;
}
// Since we are about to set the slider value, flag the next update
// to be ignored the slider so that it doesn't cause an extra loop,
// but only if the value wouldn't be out of bounds in the case where
// our slider range is different than our text input range, because
// if the value would be out of range for the slider, the value won't
// actually change if the slider value is already at the min or max
// value
bool outOfBounds = false;
if (m_hasCustomSliderRange)
{
if (m_restrictToInt)
{
if (currentSliderValue == m_customSliderMaxValue && sliderValue > m_customSliderMaxValue)
{
outOfBounds = true;
}
else if (currentSliderValue == m_customSliderMinValue && sliderValue < m_customSliderMinValue)
{
outOfBounds = true;
}
}
else
{
if (currentSliderValue == m_slider->maximum() && sliderValue > m_slider->maximum())
{
outOfBounds = true;
}
else if (currentSliderValue == 0 && sliderValue < 0)
{
outOfBounds = true;
}
}
}
if (!outOfBounds)
{
m_ignoreNextUpdateFromSlider = true;
}
// Update the slider value
m_slider->setValue(sliderValue);
}
void StyledDoubleSpinBox::updateValueFromSlider(int newVal)
{
if (!m_slider)
{
return;
}
// Ignore this updated if it was triggered by the user changing the spin box,
// which updated our slider value
if (m_ignoreNextUpdateFromSlider)
{
m_ignoreNextUpdateFromSlider = false;
return;
}
// No need to continue of the spinbox value didn't change
double currentSpinBoxValue = value();
double spinBoxValue = ConvertFromSliderValue(newVal);
if (spinBoxValue == currentSpinBoxValue)
{
return;
}
// Since we are about to set the spin box value, flag the next update
// to be ignored the spin box so that it doesn't cause an extra loop
m_ignoreNextUpdateFromSpinBox = true;
// Update the spin box value
setValue(spinBoxValue);
}
int StyledDoubleSpinBox::ConvertToSliderValue(double spinBoxValue)
{
// If we are in integer only mode, we can cast the value directly
int newVal;
if (m_restrictToInt)
{
newVal = (int)spinBoxValue;
}
// Otherwise we need to convert our spin box double value to an
// appropriate integer value for our custom slider scale
else
{
newVal = static_cast<int>((spinBoxValue - GetSliderMinimum()) / GetSliderRange()) * m_slider->maximum();
}
return newVal;
}
double StyledDoubleSpinBox::ConvertFromSliderValue(int sliderValue)
{
// If we are in integer only mode, we can cast the value directly
double newVal;
if (m_restrictToInt)
{
newVal = (double)sliderValue;
}
// Otherwise we need to convert our slider int value from its custom
// scale to an appropriate double value for our spin box
else
{
double sliderScale = (double)sliderValue / (double)m_slider->maximum();
newVal = (sliderScale * GetSliderRange()) + GetSliderMinimum();
}
return newVal;
}
StyledSliderPrivate::StyledSliderPrivate(QWidget* parent /*= nullptr*/)
: QSlider(parent)
{
setAttribute(Qt::WA_TranslucentBackground);
setOrientation(Qt::Horizontal);
hide();
}
StyledSpinBox::StyledSpinBox(QWidget* parent)
: StyledDoubleSpinBox(parent)
, m_validator(new QIntValidator(minimum(), maximum(), this))
{
// This StyledSpinBox mirrors the same functionality of the QSpinBox, so
// we need to set this flag so our StyledDoubleSpinBox knows to behave
// assuming only integer values
m_restrictToInt = true;
// To enforce integer only input, we set our decimal precision to 0 and
// change the validator of the QLineEdit to only accept integers
setDecimals(0);
QLineEdit* lineEdit = findChild<QLineEdit*>(QString(), Qt::FindDirectChildrenOnly);
if (lineEdit)
{
lineEdit->setValidator(m_validator);
}
// Added a valueChanged signal with an int parameter to mirror the behavior
// of the QSpinBox
QObject::connect(this, static_cast<void(StyledDoubleSpinBox::*)(double)>(&StyledDoubleSpinBox::valueChanged), this, [this](double val) {
emit valueChanged((int)val);
});
}
int StyledSpinBox::maximum() const
{
return (int)StyledDoubleSpinBox::maximum();
}
int StyledSpinBox::minimum() const
{
return (int)StyledDoubleSpinBox::minimum();
}
void StyledSpinBox::setMaximum(int max)
{
StyledDoubleSpinBox::setMaximum((double)max);
// Update our validator maximum
m_validator->setTop(max);
}
void StyledSpinBox::setMinimum(int min)
{
StyledDoubleSpinBox::setMinimum((double)min);
// Update our validator minimum
m_validator->setBottom(min);
}
void StyledSpinBox::setRange(int min, int max)
{
StyledDoubleSpinBox::setRange((double)min, (double)max);
// Update our validator range
m_validator->setRange(min, max);
}
void StyledSpinBox::setSingleStep(int val)
{
StyledDoubleSpinBox::setSingleStep((double)val);
}
int StyledSpinBox::singleStep() const
{
return (int)StyledDoubleSpinBox::singleStep();
}
int StyledSpinBox::value() const
{
return (int)StyledDoubleSpinBox::value();
}
void StyledSpinBox::setValue(int val)
{
StyledDoubleSpinBox::setValue((double)val);
}
} // namespace AzQtComponents
#include "Components/moc_StyledSpinBox.cpp"
@@ -0,0 +1,112 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QDoubleSpinBox>
#include <QSlider>
#include <QPoint>
#endif
class QIntValidator;
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API StyledSliderPrivate
: public QSlider
{
Q_OBJECT
public:
explicit StyledSliderPrivate(QWidget* parent = nullptr);
};
class AZ_QT_COMPONENTS_API StyledDoubleSpinBox
: public QDoubleSpinBox
{
Q_OBJECT
public:
explicit StyledDoubleSpinBox(QWidget* parent = nullptr);
~StyledDoubleSpinBox();
void displaySlider();
void hideSlider();
void handleClickOnApp(const QPoint& pos);
bool isMouseOnSlider();
void SetCustomSliderRange(double min, double max);
void SetDisplayDecimals(int precision);
QString textFromValue(double value) const override;
protected:
void showEvent(QShowEvent* ev) override;
void resizeEvent(QResizeEvent* ev) override;
void focusInEvent(QFocusEvent* event) override;
double GetSliderMinimum();
double GetSliderRange();
bool m_restrictToInt;
double m_customSliderMinValue;
double m_customSliderMaxValue;
bool m_hasCustomSliderRange;
private Q_SLOTS:
void UpdateToolTip(double value);
private:
void initSlider();
void prepareSlider();
bool isClickOnSpinBox(const QPoint& globalPos);
bool isClickOnSlider(const QPoint& globalPos);
void updateSliderValue(double newVal);
void updateValueFromSlider(int newVal);
int ConvertToSliderValue(double spinBoxValue);
double ConvertFromSliderValue(int sliderValue);
QString StringValue(double value, bool truncated = false) const;
bool SliderEnabled() const;
bool m_justPassFocusSlider = false;
StyledSliderPrivate* m_slider;
bool m_ignoreNextUpdateFromSlider;
bool m_ignoreNextUpdateFromSpinBox;
int m_displayDecimals;
};
class AZ_QT_COMPONENTS_API StyledSpinBox
: public StyledDoubleSpinBox
{
Q_OBJECT
public:
explicit StyledSpinBox(QWidget* parent = nullptr);
// Integer helper functions
int maximum() const;
int minimum() const;
void setMaximum(int max);
void setMinimum(int min);
void setRange(int min, int max);
void setSingleStep(int val);
int singleStep() const;
int value() const;
public Q_SLOTS:
void setValue(int val);
Q_SIGNALS:
void valueChanged(int val);
private:
QIntValidator* m_validator;
};
} // namespace AzQtComponents
@@ -0,0 +1,186 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Debug/Trace.h>
#include <AzQtComponents/Components/StylesheetPreprocessor.h>
#include <QtCore/QObject>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QRegularExpression>
namespace
{
const char* cStylesheetVariablesKey = "StylesheetVariables";
}
namespace AzQtComponents
{
StylesheetPreprocessor::StylesheetPreprocessor(QObject* pParent)
: QObject(pParent)
{
}
StylesheetPreprocessor::~StylesheetPreprocessor()
{
}
void StylesheetPreprocessor::ClearVariables()
{
m_namedVariables.clear();
m_cachedColors.clear();
}
void StylesheetPreprocessor::ReadVariables(const QString& jsonString)
{
QJsonDocument doc = QJsonDocument::fromJson(jsonString.toUtf8());
QJsonObject rootObject = doc.object();
//load in the stylesheet variables
if (rootObject.contains(cStylesheetVariablesKey))
{
QJsonObject variablesObject = rootObject.value(cStylesheetVariablesKey).toObject();
for (const QString& key : variablesObject.keys())
{
m_namedVariables[key] = variablesObject[key].toString();
// clear any cached colors of the same key, so that they get recached on next fetch
m_cachedColors.remove(key);
}
}
}
QString StylesheetPreprocessor::ProcessStyleSheet(const QString& stylesheetData)
{
enum class ParseState
{
Normal, Variable, Done
};
ParseState state = ParseState::Normal;
QString out;
QString varName;
auto i = stylesheetData.cbegin();
while (state != ParseState::Done && i != stylesheetData.end())
{
while (state == ParseState::Normal && i != stylesheetData.end())
{
char c = i->toLatin1();
switch (c)
{
case '@':
i++;
state = ParseState::Variable;
break;
default:
out.append(*i);
i++;
}
;
}
while (state == ParseState::Variable && i != stylesheetData.end())
{
char c = i->toLatin1();
//All characters valid in identifier
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
{
varName.append(*i);
i++;
}
else
{
//We are finished with reading the current varName
out.append(GetValueByName(varName));
varName.clear();
out.append(*i);
i++;
state = ParseState::Normal;
break;
}
}
}
return out;
}
QString StylesheetPreprocessor::GetValueByName(const QString& name)
{
if (m_namedVariables.contains(name))
{
return m_namedVariables.value(name);
}
else
{
return QString("");
}
}
const QColor& StylesheetPreprocessor::GetColorByName(const QString& name)
{
if (m_cachedColors.contains(name))
{
return m_cachedColors[name];
}
if (m_namedVariables.contains(name))
{
QColor color;
QString colorName(m_namedVariables.value(name));
bool colorSet = false;
if (QColor::isValidColor(colorName))
{
color.setNamedColor(colorName);
colorSet = true;
}
else if (colorName.startsWith("rgb"))
{
QRegularExpression expression("\\((.+)\\)");
QRegularExpressionMatch matches(expression.match(colorName));
if (matches.hasMatch())
{
QStringList colorComponents = matches.captured(1).split(',', Qt::SkipEmptyParts);
if (colorComponents.count() <= 4)
{
if (colorComponents.count() == 3)
{
colorComponents.push_back("255");
}
color.setRgb(
colorComponents[0].trimmed().toInt(),
colorComponents[1].trimmed().toInt(),
colorComponents[2].trimmed().toInt(),
colorComponents[3].trimmed().toInt()
);
colorSet = true;
}
}
}
AZ_Assert(colorSet, "Invalid color format specified for %s", name.toUtf8().data());
m_cachedColors[name] = color;
return m_cachedColors[name];
}
static QColor defaultColor;
return defaultColor;
}
} // namespace AzQtComponents
#include "Components/moc_StylesheetPreprocessor.cpp"
@@ -0,0 +1,47 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QColor>
#include <QHash>
#include <QObject>
#endif
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API StylesheetPreprocessor
: public QObject
{
Q_OBJECT
public:
explicit StylesheetPreprocessor(QObject* pParent);
~StylesheetPreprocessor();
void ClearVariables();
void ReadVariables(const QString& variables);
QString ProcessStyleSheet(const QString& stylesheetData);
const QColor& GetColorByName(const QString& name);
private:
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'AzQtComponents::StylesheetPreprocessor::m_namedVariables': class 'QHash<QString,QString>' needs to have dll-interface to be used by clients of class 'AzQtComponents::StylesheetPreprocessor'
QHash<QString, QString> m_namedVariables;
QHash<QString, QColor> m_cachedColors;
AZ_POP_DISABLE_WARNING
QString GetValueByName(const QString& name);
};
}
@@ -0,0 +1,355 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/TagSelector.h>
#include <QComboBox>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QMouseEvent>
namespace AzQtComponents
{
TagWidget::TagWidget(const QString& text, QWidget* parent)
: QPushButton(parent)
{
setText(text);
setMouseTracking(true);
// Create the close button on the right side.
setLayoutDirection(Qt::RightToLeft);
setIcon(QIcon(":/stylesheet/img/titlebarmenu/close.png"));
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
connect(this, &QPushButton::clicked, this, &TagWidget::OnClicked);
}
void TagWidget::OnClicked()
{
if (IsOverCloseButton(m_lastMouseX, m_lastMouseY))
{
emit DeleteClicked();
}
}
bool TagWidget::IsOverCloseButton(int localX, int localY)
{
Q_UNUSED(localY);
return (localX > width() - iconSize().width() * 1.5) && (localX < width());
}
void TagWidget::mouseMoveEvent(QMouseEvent* event)
{
m_lastMouseX = event->x();
m_lastMouseY = event->y();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TagWidgetContainer::TagWidgetContainer(QWidget* parent)
: QWidget(parent)
, m_widget(nullptr)
{
m_layout = new QVBoxLayout();
m_layout->setMargin(0);
setLayout(m_layout);
m_width = 250;
}
void TagWidgetContainer::SetWrapWidth(int width)
{
m_width = width;
Reinit(m_tags);
}
void TagWidgetContainer::Reinit(const QVector<QString>& tags)
{
m_tags = tags;
if (m_widget)
{
// Hide the old widget and request deletion.
m_widget->hide();
m_widget->deleteLater();
}
m_widget = new QWidget(this);
QVBoxLayout* vLayout = new QVBoxLayout();
vLayout->setAlignment(Qt::AlignLeft);
vLayout->setMargin(0);
QHBoxLayout* hLayout = nullptr;
int usedSpaceInRow = 0;
const int numTags = m_tags.count();
for (int i = 0; i < numTags; ++i)
{
// Create the new tag widget.
TagWidget* tagWidget = new TagWidget(m_tags[i]);
const int tagWidgetWidth = tagWidget->minimumSizeHint().width();
// Calculate the width we're currently using in the current row. Does the new tag still fit in the current row?
const bool isRowFull = m_width - usedSpaceInRow - tagWidgetWidth < 0;
if (isRowFull || i == 0)
{
// Add a spacer widget after the last tag widget in a row to push the tag widgets to the left.
if (i > 0)
{
QWidget* spacerWidget = new QWidget();
spacerWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
hLayout->addWidget(spacerWidget);
}
// Add a new row for the current tag widget.
hLayout = new QHBoxLayout();
hLayout->setAlignment(Qt::AlignLeft);
hLayout->setMargin(0);
vLayout->addLayout(hLayout);
// Reset the used space in the row.
usedSpaceInRow = 0;
}
// Calculate the width of the tag widgets including the spacing between them of the current row.
usedSpaceInRow += tagWidgetWidth + hLayout->spacing();
// Add the tag widget to the current row.
hLayout->addWidget(tagWidget);
// Connect the clicked event of the close button of the tag widget to the remove tag function in the container.
connect(tagWidget, &TagWidget::DeleteClicked, this, [this, tagWidget]{ RemoveTag(tagWidget->text()); });
}
m_widget->setLayout(vLayout);
m_layout->addWidget(m_widget);
}
int TagWidgetContainer::GetNumTags() const
{
return m_tags.count();
}
const QString& TagWidgetContainer::GetTag(int index) const
{
return m_tags[index];
}
const QVector<QString>& TagWidgetContainer::GetTags() const
{
return m_tags;
}
bool TagWidgetContainer::Contains(const QString& tag) const
{
return m_tags.contains(tag);
}
void TagWidgetContainer::AddTag(const QString& tag)
{
// Is the tag already present in our container? If so, return directly to avoid duplicates.
if (Contains(tag))
{
return;
}
m_tags.push_back(tag);
Reinit(m_tags);
emit TagsChanged();
}
void TagWidgetContainer::AddTags(const QVector<QString>& selectedTags)
{
m_tags.reserve(m_tags.size() + selectedTags.size());
bool changed = false;
for (const QString& tag : selectedTags)
{
// Is the tag already present in our container? Only add the tag if not to avoid duplicates.
if (!Contains(tag))
{
m_tags.push_back(tag);
changed = true;
}
}
if (changed)
{
Reinit(m_tags);
emit TagsChanged();
}
}
void TagWidgetContainer::RemoveTag(const QString& tag)
{
m_tags.removeAll(tag);
Reinit(m_tags);
emit TagsChanged();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TagSelector::TagSelector(QWidget* parent)
: QWidget(parent)
{
Init();
}
TagSelector::TagSelector(const QVector<QString>& availableTags, QWidget* parent)
: TagSelector(parent)
{
Reinit(availableTags);
}
void TagSelector::Init()
{
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setMargin(0);
// Add the tag widget container representing the currently selected tags.
m_tagWidgets = new TagWidgetContainer();
layout->addWidget(m_tagWidgets);
// Add the combo box for adding tags to the selection.
m_combo = new QComboBox();
m_combo->setEditable(true);
m_combo->lineEdit()->setPlaceholderText(tr("Enter tag name..."));
m_combo->lineEdit()->setClearButtonEnabled(true);
layout->addWidget(m_combo);
connect(m_combo, SIGNAL(activated(int)), this, SLOT(OnComboActivated(int)));
connect(m_tagWidgets, &TagWidgetContainer::TagsChanged, this, [this]{ Reinit(); emit TagsChanged(); });
}
void TagSelector::Reinit(const QVector<QString>& availableTags)
{
m_availableTags = availableTags;
Reinit();
}
void TagSelector::Reinit()
{
m_combo->blockSignals(true);
m_combo->clear();
// Fill the combo box with all available tags so that we can choose tags from them.
for (const QString& availableTag : m_availableTags)
{
// Do not show tags in the combobox that have already been selected.
if (!IsTagSelected(availableTag))
{
m_combo->addItem(availableTag);
}
}
m_combo->setCurrentText("");
m_combo->blockSignals(false);
}
bool TagSelector::IsTagSelected(const QString& tag) const
{
return m_tagWidgets->Contains(tag);
}
void TagSelector::SelectTag(const QString& tag)
{
// Is the tag available?
if (m_availableTags.indexOf(tag) == -1)
{
return;
}
// Add a tag widget to the container. The tag widgets represent the currently selected tags.
m_tagWidgets->AddTag(tag);
}
void TagSelector::SelectTags(const QVector<QString>& selectedTags)
{
QVector<QString> checkedTags;
checkedTags.reserve(selectedTags.size());
for (const QString& tag : selectedTags)
{
// Is the tag available?
if (m_availableTags.indexOf(tag) == -1)
{
continue;
}
checkedTags.push_back(tag);
}
m_tagWidgets->Reinit(checkedTags);
Reinit();
emit TagsChanged();
}
// Called when pressing enter in the combo box.
void TagSelector::OnComboActivated(int index)
{
if (index < 0 || index >= m_combo->count())
{
return;
}
QString tag = m_combo->itemText(index);
if (tag.isEmpty())
{
return;
}
// If the tag is not available, remove it so that it doesn't appear in the combo box.
if (m_availableTags.indexOf(tag) == -1)
{
m_combo->removeItem(index);
// Clear the text as the tag was not available.
m_combo->setCurrentText("");
return;
}
// Add a tag widget to the container. The tag widgets represent the currently selected tags.
m_tagWidgets->AddTag(tag);
// Clear the text as the tag got added to selection.
m_combo->setCurrentText("");
}
void TagSelector::GetSelectedTagStrings(QVector<QString>& outTags) const
{
outTags = m_tagWidgets->GetTags();
}
} // namespace AzQtComponents
#include "Components/moc_TagSelector.cpp"
@@ -0,0 +1,120 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QWidget>
#include <QVector>
#include <QString>
#include <QPushButton>
#endif
class QComboBox;
class QVBoxLayout;
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API TagWidget
: public QPushButton
{
Q_OBJECT
public:
TagWidget(const QString& text, QWidget* parent = nullptr);
signals:
void DeleteClicked();
private slots:
void OnClicked();
private:
bool IsOverCloseButton(int localX, int localY);
void mouseMoveEvent(QMouseEvent* event) override;
int m_lastMouseX;
int m_lastMouseY;
};
class AZ_QT_COMPONENTS_API TagWidgetContainer
: public QWidget
{
Q_OBJECT
public:
TagWidgetContainer(QWidget* parent = nullptr);
void SetWrapWidth(int width);
void AddTag(const QString& tag);
void AddTags(const QVector<QString>& selectedTags);
void RemoveTag(const QString& tag);
void Reinit(const QVector<QString>& tags);
int GetNumTags() const;
const QString& GetTag(int index) const;
const QVector<QString>& GetTags() const;
bool Contains(const QString& tag) const;
signals:
void TagsChanged();
private:
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'AzQtComponents::TagWidgetContainer::m_tags': class 'QVector<T>' needs to have dll-interface to be used by clients of class 'AzQtComponents::TagWidgetContainer'
QVector<QString> m_tags; //! List of tags that shall be displayed.
AZ_POP_DISABLE_WARNING
QWidget* m_widget;
QVBoxLayout* m_layout;
int m_width;
};
class AZ_QT_COMPONENTS_API TagSelector
: public QWidget
{
Q_OBJECT
public:
TagSelector(QWidget* parent = nullptr);
TagSelector(const QVector<QString>& availableTags, QWidget* parent = nullptr);
void Reinit(const QVector<QString>& availableTags);
void Reinit();
bool IsTagSelected(const QString& tag) const;
void SelectTag(const QString& tag);
/*!
* Create tag widgets for all given tags. The tag widgets represent the current tag selection.
* @param[in] selectedTags A list of tags that should be selected. Make sure the there are no duplicated tags in the given tag list.
*/
void SelectTags(const QVector<QString>& selectedTags);
void GetSelectedTagStrings(QVector<QString>& outTags) const;
signals:
void TagsChanged();
private slots:
void OnComboActivated(int index);
private:
void Init();
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'AzQtComponents::TagSelector::m_availableTags': class 'QVector<T>' needs to have dll-interface to be used by clients of class 'AzQtComponents::TagSelector'
QVector<QString> m_availableTags; //! List of available tags to choose from.
AZ_POP_DISABLE_WARNING
TagWidgetContainer* m_tagWidgets; //! List of tag widgets. Each tag widget represents one selected tag.
QComboBox* m_combo;
};
} // namespace AzQtComponents
@@ -0,0 +1,66 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/TitleBarOverdrawHandler.h>
#include <QtGlobal>
#include <QPointer>
#include <QWidget>
#include <QWindow>
#include <QApplication>
#include <QAbstractNativeEventFilter>
#include <QVector>
#ifdef Q_OS_WIN
# include <AzQtComponents/Components/TitleBarOverdrawHandler_win.h>
#endif // Q_OS_WIN
namespace AzQtComponents
{
static QPointer<TitleBarOverdrawHandler> s_titleBarOverdrawHandlerInstance;
TitleBarOverdrawHandler* TitleBarOverdrawHandler::getInstance()
{
return s_titleBarOverdrawHandlerInstance;
}
TitleBarOverdrawHandler::TitleBarOverdrawHandler(QObject* parent)
: QObject(parent)
{
Q_ASSERT(s_titleBarOverdrawHandlerInstance.isNull());
s_titleBarOverdrawHandlerInstance = this;
}
TitleBarOverdrawHandler::~TitleBarOverdrawHandler()
{
}
#ifndef Q_OS_WIN
TitleBarOverdrawHandler* TitleBarOverdrawHandler::createHandler(QApplication*, QObject* parent)
{
return new TitleBarOverdrawHandler(parent);
}
#else
TitleBarOverdrawHandler* TitleBarOverdrawHandler::createHandler(QApplication* application, QObject* parent)
{
return new TitleBarOverdrawHandlerWindows(application, parent);
}
#endif // Q_OS_WIN
} // namespace AzQtComponents
@@ -0,0 +1,44 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QObject>
#include <QMargins>
class QApplication;
class QWidget;
Q_DECLARE_METATYPE(QMargins)
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API TitleBarOverdrawHandler :
public QObject
{
public:
explicit TitleBarOverdrawHandler(QObject* parent);
~TitleBarOverdrawHandler() override;
virtual void polish(QWidget* /*widget*/) {}
virtual void addTitleBarOverdrawWidget(QWidget* /*widget*/) {}
static TitleBarOverdrawHandler* getInstance();
// Must be defined by the platform specific implementation
static TitleBarOverdrawHandler* createHandler(QApplication* application, QObject* parent);
};
} // namespace AzQtComponents
@@ -0,0 +1,224 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/TitleBarOverdrawHandler_win.h>
#include <qnamespace.h>
#include <AzQtComponents/Components/TitleBarOverdrawScreenHandler_win.h>
#include <QDockWidget>
#include <QApplication>
#include <QOperatingSystemVersion>
#include <QScreen>
#include <QWindow>
#include <QWidget>
#include <QtGui/qpa/qplatformnativeinterface.h>
#include <VersionHelpers.h>
#include <shellapi.h>
namespace AzQtComponents
{
namespace
{
QMargins getMonitorAutohiddenTaskbarMargins(HMONITOR monitor)
{
MONITORINFO mi = { sizeof(MONITORINFO) };
GetMonitorInfo(monitor, &mi);
auto hasTopmostAutohiddenTaskbar = [&mi](unsigned edge)
{
APPBARDATA data = { sizeof(APPBARDATA), NULL, 0, edge, mi.rcMonitor };
HWND bar;
if (IsWindows8OrGreater())
{
bar = reinterpret_cast<HWND>(SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &data));
}
else
{
bar = reinterpret_cast<HWND>(SHAppBarMessage(ABM_GETAUTOHIDEBAR, &data));
}
return bar && (GetWindowLong(bar, GWL_EXSTYLE) & WS_EX_TOPMOST);
};
QMargins margins;
if (hasTopmostAutohiddenTaskbar(ABE_LEFT))
{
margins.setLeft(1);
}
if (hasTopmostAutohiddenTaskbar(ABE_RIGHT))
{
margins.setRight(1);
}
if (hasTopmostAutohiddenTaskbar(ABE_TOP))
{
margins.setTop(1);
}
if (hasTopmostAutohiddenTaskbar(ABE_BOTTOM))
{
margins.setBottom(1);
}
return margins;
}
}
TitleBarOverdrawHandlerWindows::TitleBarOverdrawHandlerWindows(QApplication* application, QObject* parent)
: TitleBarOverdrawHandler(parent)
{
Q_UNUSED(application);
Q_UNUSED(parent);
m_user32Module = LoadLibraryA("user32.dll");
if (m_user32Module)
{
// The below DPI related methods exist in the user32.dll only on Windows 10 version 1607 and beyond. We attempt to locate these
// methods at runtime through GetProcAddress so this module can still load correctly on older version of windows
m_getDpiForWindowFn = (PFNGetDpiForWindow) GetProcAddress(m_user32Module, "GetDpiForWindow");
m_adjustWindowRectExForDpiFn = (PFNAdjustWindowRectExForDpi) GetProcAddress(m_user32Module, "AdjustWindowRectExForDpi");
}
}
TitleBarOverdrawHandlerWindows::~TitleBarOverdrawHandlerWindows()
{
if (m_user32Module)
{
FreeLibrary(m_user32Module);
}
}
QMargins TitleBarOverdrawHandlerWindows::customTitlebarMargins(HMONITOR monitor, unsigned style, unsigned exStyle, bool maximized, int dpi)
{
RECT rect = { 0, 0, 500, 500 };
if (m_adjustWindowRectExForDpiFn)
{
m_adjustWindowRectExForDpiFn(&rect, style, FALSE, exStyle, dpi);
}
else
{
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
}
QMargins margins(0, rect.top, 0, 0);
if (maximized)
{
margins.setTop(margins.top() - rect.left);
if (monitor)
{
margins += getMonitorAutohiddenTaskbarMargins(monitor);
}
}
return margins;
}
void TitleBarOverdrawHandlerWindows::applyOverdrawMargins(QWindow* window)
{
if (auto platformWindow = window->handle())
{
auto hWnd = (HWND)window->winId();
WINDOWPLACEMENT placement;
placement.length = sizeof(WINDOWPLACEMENT);
const bool maximized = GetWindowPlacement(hWnd, &placement) && placement.showCmd == SW_SHOWMAXIMIZED;
applyOverdrawMargins(platformWindow, hWnd, maximized);
}
else
{
// We should not create a real window (HWND) yet, so get margins using presumed style
const static unsigned style = WS_OVERLAPPEDWINDOW & ~WS_OVERLAPPED;
const static unsigned exStyle = 0;
const auto margins = customTitlebarMargins(nullptr, style, exStyle, false, 96);
// ... and apply them to the creation context for the future window
window->setProperty("_q_windowsCustomMargins", QVariant::fromValue(margins));
}
}
void TitleBarOverdrawHandlerWindows::polish(QWidget* widget)
{
if (strcmp(widget->metaObject()->className(), "QDockWidgetGroupWindow") == 0)
{
addTitleBarOverdrawWidget(widget);
}
}
void TitleBarOverdrawHandlerWindows::addTitleBarOverdrawWidget(QWidget* widget)
{
if(QOperatingSystemVersion::current() < QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10) ||
m_overdrawWidgets.contains(widget))
{
return;
}
m_overdrawWidgets.append(widget);
connect(widget, &QWidget::destroyed, this, [widget, this] {
m_overdrawWidgets.removeOne(widget);
});
if (auto handle = widget->windowHandle())
{
applyOverdrawMargins(handle);
// Use TitleBarOverdrawScreenHandler for Qt 5.12.4.1-az and upwards, it uses Qt signals instead
// of native events and prevents window resize loops on multiscreen setups with different dpis.
m_screenHandlers.push_back(new TitleBarOverdrawScreenHandler(handle, this));
}
// We might not have a window handle yet if handling a StyledDockWidget,
// make sure to apply margins once the widget becomes toplevel
else if (auto dockWidget = qobject_cast<QDockWidget*>(widget))
{
m_screenHandlers.push_back(new TitleBarOverdrawScreenHandler(dockWidget, this));
}
}
QPlatformWindow* TitleBarOverdrawHandlerWindows::overdrawWindow(const QVector<QWidget*>& overdrawWidgets, HWND hWnd)
{
for (auto widget : overdrawWidgets)
{
auto handle = widget->windowHandle();
if (handle && widget->internalWinId() == (WId)hWnd)
{
return handle->handle();
}
}
return nullptr;
}
void TitleBarOverdrawHandlerWindows::applyOverdrawMargins(QPlatformWindow* window, HWND hWnd, bool maximized)
{
if (auto pni = QGuiApplication::platformNativeInterface())
{
const auto style = GetWindowLongPtr(hWnd, GWL_STYLE);
if (!(style & WS_CHILD))
{
const auto exStyle = GetWindowLongPtr(hWnd, GWL_EXSTYLE);
const auto monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL);
UINT dpi = 0;
if (m_getDpiForWindowFn)
{
dpi = m_getDpiForWindowFn(hWnd);
}
const auto margins = customTitlebarMargins(monitor, static_cast<int>(style), static_cast<int>(exStyle), maximized, dpi);
RECT rect;
GetWindowRect(hWnd, &rect);
pni->setWindowProperty(window, QStringLiteral("WindowsCustomMargins"), QVariant::fromValue(margins));
const auto width = rect.right - rect.left;
const auto height = rect.bottom - rect.top;
SetWindowPos(hWnd, 0, rect.left, rect.top, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
}
}
}
} // namespace AzQtComponents
@@ -0,0 +1,68 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/PlatformIncl.h>
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/Components/TitleBarOverdrawHandler.h>
#include <QVector>
#include <qnamespace.h>
#endif
class QWindow;
class QPlatformWindow;
class QScreen;
namespace AzQtComponents
{
class TitleBarOverdrawScreenHandler;
class TitleBarOverdrawHandlerWindows
: public TitleBarOverdrawHandler
{
Q_OBJECT // AUTOMOC
public:
TitleBarOverdrawHandlerWindows(QApplication* application, QObject* parent);
~TitleBarOverdrawHandlerWindows() override;
QMargins customTitlebarMargins(HMONITOR monitor, unsigned style, unsigned exStyle, bool maximized, int dpi);
void applyOverdrawMargins(QWindow* window);
void polish(QWidget* widget) override;
void addTitleBarOverdrawWidget(QWidget* widget) override;
private:
friend class TitleBarOverdrawScreenHandler;
QPlatformWindow* overdrawWindow(const QVector<QWidget*>& overdrawWidgets, HWND hWnd);
void applyOverdrawMargins(QPlatformWindow* window, HWND hWnd, bool maximized);
QVector<QWidget*> m_overdrawWidgets;
QVector<TitleBarOverdrawScreenHandler*> m_screenHandlers;
// The below DPI related methods exist in the user32.dll only on Windows 10 version 1607 and beyond and not windows server.
// AZQtComopnents is a dependency of AzToolsFramework and linking to them directly will cause a failure at runtime on windows
// server when loading user32.dll. We can't base this code on anything at compile time for cases of packaged builds which need to
// compile executables on windows server for use on windows 10 clients, but also need to process assets themselves for the packaged build
// The better answer will be to resolve this dependency between aztoolsframework and azqtcomponents.
HMODULE m_user32Module;
typedef UINT(WINAPI * PFNGetDpiForWindow) (HWND hwnd);
PFNGetDpiForWindow m_getDpiForWindowFn{ nullptr };
typedef BOOL(WINAPI * PFNAdjustWindowRectExForDpi) (LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi);
PFNAdjustWindowRectExForDpi m_adjustWindowRectExForDpiFn{ nullptr };
};
} // namespace AzQtComponents
@@ -0,0 +1,133 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/TitleBarOverdrawScreenHandler_win.h>
#include <AzQtComponents/Components/TitleBarOverdrawHandler_win.h>
#include <AzQtComponents/Components/StyledDockWidget.h>
#include <QWindow>
#include <QScreen>
#include <QDockWidget>
namespace AzQtComponents
{
TitleBarOverdrawScreenHandler::TitleBarOverdrawScreenHandler(QWindow* window, QObject* parent)
: QObject(parent)
, m_dockWidget(nullptr)
, m_window(window)
{
registerWindow(window);
}
TitleBarOverdrawScreenHandler::TitleBarOverdrawScreenHandler(QDockWidget* dockWidget, QObject* parent)
: QObject(parent)
, m_dockWidget(dockWidget)
, m_window(nullptr)
{
m_dockWidget->installEventFilter(this);
}
void TitleBarOverdrawScreenHandler::registerWindow(QWindow* window)
{
if (!window)
{
return;
}
m_window = window;
connect(m_window, &QObject::destroyed, this, [this] {
m_window = nullptr;
});
connect(window, &QWindow::screenChanged, this, &TitleBarOverdrawScreenHandler::applyOverdrawMargins);
window->installEventFilter(this);
}
bool TitleBarOverdrawScreenHandler::eventFilter(QObject *watched, QEvent *event)
{
switch (event->type())
{
case QEvent::WindowStateChange: // Handles minimize/maximize
{
// Don't reapply margins on window maximization for dockwidgets,
// otherwise the native titlebar would become visible
if (m_dockWidget)
{
return QObject::eventFilter(watched, event);
}
}
// Intentional fall-through
case QEvent::Show:
{
if (QWindow* window = qobject_cast<QWindow*>(watched))
{
applyOverdrawMargins();
}
// Floating dockwidgets' own window becomes available only show.
// Listen for this event on floating widgets and then proceed as usual with titlebar overdraw
else if (QDockWidget* dockWidget = qobject_cast<QDockWidget*>(watched))
{
if (dockWidget != m_dockWidget)
{
break;
}
if (!dockWidget->isFloating())
{
break;
}
if (m_window)
{
// Window is already registered. This happens when a minimized window is restored from the
// taskbar and a Show event is sent by QWidget before the WindowStateChange event is sent
break;
}
if (QWindow* dockWindow = dockWidget->windowHandle())
{
registerWindow(dockWindow);
applyOverdrawMargins();
}
}
break;
}
}
return QObject::eventFilter(watched, event);
}
void TitleBarOverdrawScreenHandler::applyOverdrawMargins()
{
if (!m_window)
{
return;
}
if (!m_window->handle())
{
return;
}
if (TitleBarOverdrawHandlerWindows* tbhandle = qobject_cast<TitleBarOverdrawHandlerWindows*>(parent()))
{
QPlatformWindow* platformWindow = m_window->handle();
auto hWnd = (HWND)m_window->winId();
WINDOWPLACEMENT placement;
placement.length = sizeof(WINDOWPLACEMENT);
const bool maximized = GetWindowPlacement(hWnd, &placement) && placement.showCmd == SW_SHOWMAXIMIZED;
tbhandle->applyOverdrawMargins(platformWindow, hWnd, maximized);
}
}
} // namespace AzQtComponents
@@ -0,0 +1,47 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/Components/TitleBarOverdrawHandler.h>
#include <QObject>
#endif
class QWindow;
class QScreen;
class QDockWidget;
namespace AzQtComponents
{
class TitleBarOverdrawScreenHandler : public QObject
{
Q_OBJECT // AUTOMOC
public:
explicit TitleBarOverdrawScreenHandler(QWindow* window, QObject* parent = nullptr);
explicit TitleBarOverdrawScreenHandler(QDockWidget* dockWidget, QObject* parent = nullptr);
bool eventFilter(QObject *watched, QEvent *event) override;
private:
QWindow* m_window = nullptr;
QDockWidget* m_dockWidget = nullptr;
QScreen* m_screen = nullptr;
void applyOverdrawMargins();
void registerWindow(QWindow* window);
void handleFloatingDockWidget();
};
} // namespace AzQtComponents
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,284 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <AzQtComponents/Components/DockBarButton.h>
#include <AzQtComponents/Components/InteractiveWindowGeometryChanger.h>
#include <AzQtComponents/Components/Widgets/TabWidget.h>
#include <QFrame>
#include <QPoint>
#include <QPointer>
#include <QTimer>
#endif
class QMouseEvent;
class QMenu;
class QDockWidget;
class QLabel;
class QHBoxLayout;
class QSettings;
class QStyleOption;
class QStackedLayout;
namespace AzQtComponents
{
class Style;
class DockTabBar;
class ElidingLabel;
/* TitleBar style is now applied from Qt Style Sheets.
*
* This can be found in Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TitleBar.qss
*/
class AZ_QT_COMPONENTS_API TitleBar
: public QFrame
{
Q_OBJECT
Q_PROPERTY(bool drawSideBorders READ drawSideBorders WRITE setDrawSideBorders NOTIFY drawSideBordersChanged)
Q_PROPERTY(bool drawSimple READ drawSimple WRITE setDrawSimple NOTIFY drawSimpleChanged)
Q_PROPERTY(bool forceInactive READ forceInactive WRITE setForceInactive NOTIFY forceInactiveChanged)
Q_PROPERTY(bool tearEnabled READ tearEnabled WRITE setTearEnabled NOTIFY tearEnabledChanged)
Q_PROPERTY(bool drawAsTabBar READ drawAsTabBar WRITE setDrawAsTabBar NOTIFY drawAsTabBarChanged)
Q_PROPERTY(QString windowTitleOverride READ windowTitleOverride WRITE setWindowTitleOverride NOTIFY windowTitleOverrideChanged)
/**
* Expose the title using a QT property so that test automation can read it
*/
Q_PROPERTY(QString title READ title)
public:
typedef QList<DockBarButton::WindowDecorationButton> WindowDecorationButtons;
struct Config
{
struct TitleBar
{
int height = -1;
int simpleHeight = -1;
bool appearAsTabBar = false;
};
struct Icon
{
bool visible = false;
};
struct Title
{
int indent = -1;
bool visibleWhenSimple = false;
};
struct Buttons
{
bool showDividerButtons = false;
int spacing = -1;
};
TitleBar titleBar;
Icon icon;
Title title;
Buttons buttons;
};
/*!
* Loads the button config data from a settings object.
*/
static Config loadConfig(QSettings& settings);
/*!
* Returns default button config data.
*/
static Config defaultConfig();
enum TitleBarDrawMode
{
Main = 0,
Simple,
Hidden
};
explicit TitleBar(QWidget* parent = nullptr);
~TitleBar();
bool drawSideBorders() const { return m_drawSideBorders; }
void setDrawSideBorders(bool);
bool drawSimple() const { return m_drawMode == TitleBarDrawMode::Simple; }
bool drawHidden() const { return m_drawMode == TitleBarDrawMode::Hidden; }
TitleBarDrawMode drawMode() const { return m_drawMode; }
void setDrawSimple(bool enable);
void setDrawMode(TitleBarDrawMode drawMode);
void setDragEnabled(bool);
void setIsShowingWindowControls(bool show);
bool tearEnabled() const { return m_tearEnabled; }
void setTearEnabled(bool);
bool drawAsTabBar() const { return m_appearAsTabBar; }
void setDrawAsTabBar(bool);
QSize sizeHint() const override;
const QString& windowTitleOverride() const { return m_titleOverride; }
void setWindowTitleOverride(const QString&);
/**
* Sets the titlebar buttons to show.
* By default shows: | Minimize | Maximize | Close
*
* Example:
*
* setButtons({ DockBarButton::DividerButton, DockBarButton::MinimizeButton,
* DockBarButton::DividerButton, DockBarButton::MaximizeButton,
* DockBarButton::DividerButton, DockBarButton::CloseButton});
*/
void setButtons(WindowDecorationButtons);
void handleClose();
void handleMaximize();
void handleMinimize();
bool hasButton(DockBarButton::WindowDecorationButton buttonType) const;
bool buttonIsEnabled(DockBarButton::WindowDecorationButton buttonType) const;
void handleMoveRequest();
void handleSizeRequest();
int numButtons() const;
bool forceInactive() const { return m_forceInactive; }
void setForceInactive(bool);
/**
* For left,right,bottom we use the native Windows border, but for top it's required we add
* the margin ourselves.
*/
bool isTopResizeArea(const QPoint& globalPos) const;
/**
* These will only return true ever for macOS.
*/
bool isLeftResizeArea(const QPoint& globalPos) const;
bool isRightResizeArea(const QPoint& globalPos) const;
/**
* The title rect width minus the buttons rect.
* In local coords.
*/
QRect draggableRect() const;
bool event(QEvent* event) override;
void disableButton(DockBarButton::WindowDecorationButton buttonType);
void enableButton(DockBarButton::WindowDecorationButton buttonType);
Q_SIGNALS:
void undockAction();
void drawSideBordersChanged(bool drawSideBorders);
void drawSimpleChanged(bool drawSimple);
void forceInactiveChanged(bool forceInactive);
void tearEnabledChanged(bool tearEnabled);
void drawAsTabBarChanged(bool drawAsTabBar);
void windowTitleOverrideChanged(const QString& windowTitleOverride);
protected:
void mousePressEvent(QMouseEvent* ev) override;
void mouseReleaseEvent(QMouseEvent* ev) override;
void mouseMoveEvent(QMouseEvent* ev) override;
void mouseDoubleClickEvent(QMouseEvent *ev) override;
void timerEvent(QTimerEvent* ev) override;
void contextMenuEvent(QContextMenuEvent* ev) override;
bool eventFilter(QObject* watched, QEvent* event) override;
protected Q_SLOTS:
void handleButtonClicked(const DockBarButton::WindowDecorationButton type);
private:
friend class Style;
static bool polish(Style* style, QWidget* widget, const Config& config);
static bool unpolish(Style* style, QWidget* widget, const Config& config);
static int titleBarHeight(const Style* style, const QStyleOption* option, const QWidget* widget, const Config& config, const TabWidget::Config& tabConfig);
bool usesCustomTopBorderResizing() const;
void checkEnableMouseTracking();
QWidget* dockWidget() const;
bool isInDockWidget() const;
bool isInFloatingDockWidget() const;
bool isInDockWidgetWindowGroup() const;
void updateStandardContextMenu();
void updateDockedContextMenu();
void fixEnabled();
bool isMaximized() const;
QString title() const;
void updateTitle();
void updateTitleBar();
void setupButtons(bool useDividerButtons = true);
void setupButtonsHelper(QFrame* container, QHBoxLayout* layout, bool useDividerButtons);
bool isDragging() const;
bool isLeftButtonDown() const;
bool canDragWindow() const;
bool isResizingWindow() const;
bool isDraggingWindow() const;
void resizeWindow(const QPoint& globalPos);
void dragWindow(const QPoint& globalPos);
bool isTitleBarForDockWidget() const;
DockBarButton* findButton(DockBarButton::WindowDecorationButton buttonType) const;
QStackedLayout* m_stackedLayout = nullptr;
DockTabBar* m_tabBar = nullptr;
QWidget* m_firstButton = nullptr;
QLabel* m_icon = nullptr;
ElidingLabel* m_label = nullptr;
bool m_showLabelWhenSimple = true;
bool m_appearAsTabBar = false;
bool m_isShowingWindowControls = false;
QFrame* m_buttonsContainer = nullptr;
QFrame* m_tabButtonsContainer = nullptr;
QHBoxLayout* m_buttonsLayout = nullptr;
QHBoxLayout* m_tabButtonsLayout = nullptr;
QString m_titleOverride;
bool m_drawSideBorders = true;
TitleBarDrawMode m_drawMode = TitleBarDrawMode::Main;
bool m_dragEnabled = false;
bool m_tearEnabled = false;
QPoint m_dragPos;
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'AzQtComponents::TitleBar::m_buttons': class 'QList<AzQtComponents::DockBarButton::WindowDecorationButton>' needs to have dll-interface to be used by clients of class 'AzQtComponents::TitleBar'
WindowDecorationButtons m_buttons;
AZ_POP_DISABLE_WARNING
bool m_forceInactive = false; // So we can show it inactive in the gallery, for demo purposes
bool m_autoButtons = false;
bool m_pendingRepositioning = false;
bool m_resizingTop = false;
bool m_resizingRight = false;
bool m_resizingLeft = false;
qreal m_relativeDragPos = 0.0;
qreal m_lastLocalPosX = 0.0;
QMenu* m_tabsContextMenu = nullptr;
QMenu* m_windowContextMenu = nullptr;
QAction* m_restoreMenuAction = nullptr;
QAction* m_sizeMenuAction = nullptr;
QAction* m_moveMenuAction = nullptr;
QAction* m_minimizeMenuAction = nullptr;
QAction* m_maximizeMenuAction = nullptr;
QAction* m_closeMenuAction = nullptr;
QAction* m_closeTabMenuAction = nullptr;
QAction* m_closeGroupMenuAction = nullptr;
QAction* m_undockMenuAction = nullptr;
QAction* m_undockGroupMenuAction = nullptr;
QWindow* topLevelWindow() const;
void updateMouseCursor(const QPoint& globalPos);
bool canResize() const;
Qt::CursorShape m_originalCursor = Qt::ArrowCursor;
QTimer m_enableMouseTrackingTimer;
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'AzQtComponents::TitleBar::m_interactiveWindowGeometryChanger': class 'QPointer<AzQtComponents::InteractiveWindowGeometryChanger>' needs to have dll-interface to be used by clients of class 'AzQtComponents::TitleBar'
QPointer<InteractiveWindowGeometryChanger> m_interactiveWindowGeometryChanger;
AZ_POP_DISABLE_WARNING
};
} // namespace AzQtComponents
@@ -0,0 +1,81 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/ToolBarArea.h>
#include <QToolBar>
#include <QVBoxLayout>
namespace AzQtComponents
{
ToolBarArea::ToolBarArea(QWidget *parent)
: QMainWindow(parent)
{
// this will prevent this toolbar area from being temporarily styled with a custom title bar
setProperty("HasNoWindowDecorations", true);
// this will prevent this toolbar area from being created as a full window
setWindowFlags(windowFlags() & ~Qt::Window);
}
QToolBar* ToolBarArea::CreateToolBarFromWidget(QWidget* sourceWidget, Qt::ToolBarArea area, QString title)
{
QToolBar* toolbar = new QToolBar(title, this);
addToolBar(area, toolbar);
QLayout* layout = sourceWidget->layout();
while (QLayoutItem* item = layout->takeAt(0))
{
if (QWidget* widget = item->widget())
{
toolbar->addWidget(widget);
}
else if (item->spacerItem())
{
// Old default behavior - insert expanding spacer widget where separators are.
if (item->sizeHint().width() > 1)
{
QWidget* spacerWidget = new QWidget;
spacerWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
toolbar->addWidget(spacerWidget);
}
// If the spacer is sized 1 px, honor that and insert a regular separator.
else
{
toolbar->addSeparator();
}
}
delete item;
}
sourceWidget->hide();
return toolbar;
}
void ToolBarArea::SetMainWidget(QWidget *widget)
{
if (!m_mainLayout)
{
QWidget* mainWidget = new QWidget;
m_mainLayout = new QVBoxLayout(mainWidget);
m_mainLayout->setContentsMargins(QMargins());
setCentralWidget(mainWidget);
}
// We only have one main widget, clear the others.
// We don't assume responsibility for deleting any widgets left behind, however.
while (QLayoutItem* item = m_mainLayout->takeAt(0))
delete item;
m_mainLayout->addWidget(widget);
m_mainWidget = widget;
}
}
@@ -0,0 +1,45 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QMainWindow>
class QToolBar;
class QVBoxLayout;
namespace AzQtComponents
{
// ToolBarArea inherits from QMainWindow to offer a docking area for QToolBars
// in which they can expand when they lack the horizontal space to be fully shown
class AZ_QT_COMPONENTS_API ToolBarArea : public QMainWindow
{
public:
ToolBarArea(QWidget* parent);
protected:
// Helper method to turn a source QWidget with a layout into a QToolBar
// Grabs all widgets from the source widget's layout and adds spacers for any stretch in the layout
// Does NOT delete sourceWidget
QToolBar* CreateToolBarFromWidget(QWidget* sourceWidget, Qt::ToolBarArea area = Qt::TopToolBarArea, QString title = {});
QWidget* GetMainWidget() { return m_mainWidget; }
// Sets the primary widget in the center of the ToolBarArea
// Differs from QMainWindow::setCentralWidget in that it will not delete a previous main widget
void SetMainWidget(QWidget* widget);
private:
QVBoxLayout* m_mainLayout = nullptr;
QWidget* m_mainWidget = nullptr;
};
}
@@ -0,0 +1,31 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/ToolButtonComboBox.h>
#include <QComboBox>
namespace AzQtComponents
{
ToolButtonComboBox::ToolButtonComboBox(QWidget* parent)
: ToolButtonWithWidget(new QComboBox(), parent)
, m_combo(static_cast<QComboBox*>(widget()))
{
m_combo->setEditable(true);
}
QComboBox* ToolButtonComboBox::comboBox() const
{
return m_combo;
}
} // namespace AzQtComponents
#include "Components/moc_ToolButtonComboBox.cpp"
@@ -0,0 +1,36 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <AzQtComponents/Components/ToolButtonWithWidget.h>
#endif
class QComboBox;
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API ToolButtonComboBox
: public ToolButtonWithWidget
{
Q_OBJECT
public:
explicit ToolButtonComboBox(QWidget* parent = nullptr);
QComboBox* comboBox() const;
private:
QComboBox* const m_combo;
};
} // namespace AzQtComponents
@@ -0,0 +1,54 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/ToolButtonLineEdit.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QLineEdit>
AZ_POP_DISABLE_WARNING
namespace AzQtComponents
{
ToolButtonLineEdit::ToolButtonLineEdit(QWidget* parent)
: ToolButtonWithWidget(new QLineEdit(), parent)
, m_lineEdit(static_cast<QLineEdit*>(widget()))
{
m_lineEdit->setProperty("class", "ToolButtonLineEdit");
}
void ToolButtonLineEdit::clear()
{
m_lineEdit->clear();
}
QString ToolButtonLineEdit::text() const
{
return m_lineEdit->text();
}
void ToolButtonLineEdit::setText(const QString& text)
{
m_lineEdit->setText(text);
}
void ToolButtonLineEdit::setPlaceholderText(const QString& text)
{
m_lineEdit->setPlaceholderText(text);
}
QLineEdit* ToolButtonLineEdit::lineEdit() const
{
return m_lineEdit;
}
} // namespace AzQtComponents
#include "Components/moc_ToolButtonLineEdit.cpp"
@@ -0,0 +1,40 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <AzQtComponents/Components/ToolButtonWithWidget.h>
#endif
class QLineEdit;
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API ToolButtonLineEdit
: public ToolButtonWithWidget
{
Q_OBJECT
public:
explicit ToolButtonLineEdit(QWidget* parent = nullptr);
void clear();
QString text() const;
void setText(const QString&);
void setPlaceholderText(const QString&);
QLineEdit* lineEdit() const;
private:
QLineEdit* const m_lineEdit;
};
} // namespace AzQtComponents
@@ -0,0 +1,110 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/ToolButtonWithWidget.h>
#include <QHBoxLayout>
#include <QSizePolicy>
#include <QToolButton>
#include <QToolBar>
#include <QEvent>
namespace AzQtComponents
{
enum
{
ButtonHeightMin = 20,
ButtonIconMargin = 4,
WidthProportionButton = 3
};
ToolButtonWithWidget::ToolButtonWithWidget(QWidget* widget, QWidget* parent)
: QWidget(parent)
, m_button(new QToolButton(this))
, m_widget(widget)
{
auto layout = new QHBoxLayout(this);
layout->setSpacing(0);
layout->setMargin(0);
layout->addWidget(m_button);
layout->addWidget(m_widget);
m_button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
setFixedWidth(m_iconSize.height()* WidthProportionButton);
layout->addSpacing(4);
connect(m_button, &QAbstractButton::clicked, this, &ToolButtonWithWidget::clicked);
connectToParentToolBar();
}
QSize ToolButtonWithWidget::sizeHint() const
{
return QSize(120, m_iconSize.height() + ButtonIconMargin);
}
void ToolButtonWithWidget::setIcon(const QIcon& icon)
{
m_button->setIcon(icon);
setIconSize(m_iconSize);
}
void ToolButtonWithWidget::setIconSize(const QSize &iconSize)
{
if (!iconSize.isValid())
{
return;
}
m_iconSize = iconSize.height() + ButtonIconMargin < ButtonHeightMin
? QSize(ButtonHeightMin, ButtonHeightMin)
: iconSize;
const int newHeight = m_iconSize.height() + ButtonIconMargin;
setFixedHeight(newHeight);
m_widget->setMinimumHeight(newHeight);
m_button->setFixedSize(QSize(newHeight, newHeight));
m_button->setIconSize(m_iconSize);
setFixedWidth(newHeight * WidthProportionButton);
}
QToolButton* ToolButtonWithWidget::button() const
{
return m_button;
}
QWidget* ToolButtonWithWidget::widget()
{
return m_widget;
}
bool ToolButtonWithWidget::event(QEvent *event)
{
if (event->type() == QEvent::ParentChange)
{
connectToParentToolBar();
}
return QWidget::event(event);
}
void ToolButtonWithWidget::connectToParentToolBar()
{
if (auto toolbar = qobject_cast<QToolBar*>(parent()))
{
connect(toolbar, &QToolBar::iconSizeChanged, this, &ToolButtonWithWidget::setIconSize, Qt::UniqueConnection);
setIconSize(toolbar->iconSize());
}
}
} // namespace AzQtComponents
#include "Components/moc_ToolButtonWithWidget.cpp"
@@ -0,0 +1,52 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QWidget>
#endif
class QIcon;
class QToolButton;
class QEvent;
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API ToolButtonWithWidget
: public QWidget
{
Q_OBJECT
public:
QSize sizeHint() const override;
void setIcon(const QIcon&);
void setIconSize(const QSize &iconSize);
QToolButton* button() const;
protected:
explicit ToolButtonWithWidget(QWidget* widget, QWidget* parent = nullptr);
bool event(QEvent *event) override;
QWidget* widget();
Q_SIGNALS:
void clicked();
private:
void connectToParentToolBar();
QSize m_iconSize = QSize(16, 16);
QToolButton* const m_button;
QWidget* const m_widget;
};
} // namespace AzQtComponents
@@ -0,0 +1,307 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/VectorEdit.h>
#include <QHBoxLayout>
#include <QDoubleValidator>
#include <QSignalBlocker>
#include <QIcon>
namespace AzQtComponents
{
// An individual line edit with label
VectorEditElement::VectorEditElement(const QString& label, QWidget* parent)
: QWidget(parent)
, m_lineEdit(new QLineEdit())
, m_label(new QLabel(label))
, m_color(Qt::white)
, m_flavor(VectorEditElement::Plain)
{
m_lineEdit->setFixedWidth(33);
auto layout = new QHBoxLayout(this);
layout->addWidget(m_label);
layout->addWidget(m_lineEdit);
layout->setMargin(0);
layout->setSpacing(3);
m_lineEdit->setValidator(new QDoubleValidator(this));
connect(m_lineEdit, &QLineEdit::textChanged,
this, &VectorEditElement::valueChanged);
m_label->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
}
float VectorEditElement::value() const
{
return m_lineEdit->text().toFloat();
}
QString VectorEditElement::label() const
{
return m_label->text();
}
void VectorEditElement::setLabel(const QString& text)
{
m_label->setText(text);
}
QColor VectorEditElement::color() const
{
return m_color;
}
void VectorEditElement::setColor(const QColor& color)
{
if (color != m_color)
{
m_color = color;
updateColor();
}
}
void VectorEditElement::setFlavor(VectorEditElement::Flavor flavor)
{
if (flavor != m_flavor)
{
m_flavor = flavor;
updateColor();
}
}
void VectorEditElement::updateColor()
{
if (m_flavor == Plain)
{
if (m_color.isValid())
{
m_lineEdit->setStyleSheet(QStringLiteral("QLineEdit:enabled { border: 1px solid \"%1\"; }").arg(m_color.name()));
m_label->setStyleSheet(QStringLiteral("QLabel { color: \"%1\"; }").arg(m_color.name()));
}
else
{
m_lineEdit->setStyleSheet({});
}
}
else
{
m_lineEdit->setStyleSheet({});
}
m_label->update();
m_lineEdit->update();
}
void VectorEditElement::setValue(float value)
{
m_lineEdit->setText(QString::number(value));
}
VectorEdit::VectorEdit(QWidget* parent)
: QWidget(parent)
, m_flavor(VectorEditElement::Plain)
, m_iconLabel(new QLabel())
{
m_iconLabel->setFixedWidth(16);
// Two layouts so we can have smaller spacing between icon label and line edits
auto outterLayout = new QHBoxLayout(this);
outterLayout->setMargin(0);
outterLayout->setSpacing(5);
auto container = new QWidget();
auto layout = new QHBoxLayout(container);
layout->setMargin(0);
layout->setSpacing(15);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
const QStringList labels = { tr("X"), tr("Y"), tr("Z") };
m_editElements.reserve(labels.size());
for (const QString& label : labels)
{
auto element = new VectorEditElement(label, this);
layout->addWidget(element);
m_editElements.append(element);
connect(element, &VectorEditElement::valueChanged,
this, &VectorEdit::vectorChanged);
}
outterLayout->addSpacing(4);
outterLayout->addWidget(m_iconLabel);
outterLayout->addWidget(container);
}
QVector3D VectorEdit::vector() const
{
return {
x(), y(), z()
};
}
float VectorEdit::x() const
{
return m_editElements.at(0)->value();
}
float VectorEdit::y() const
{
return m_editElements.at(1)->value();
}
float VectorEdit::z() const
{
return m_editElements.at(2)->value();
}
QString VectorEdit::xLabel() const
{
return m_editElements.at(0)->label();
}
QString VectorEdit::yLabel() const
{
return m_editElements.at(1)->label();
}
QString VectorEdit::zLabel() const
{
return m_editElements.at(2)->label();
}
void VectorEdit::setLabels(const QString& xLabel, const QString& yLabel, const QString& zLabel)
{
setLabels({ xLabel, yLabel, zLabel });
}
void VectorEdit::setLabels(const QStringList& labels)
{
for (int i = 0; i < qMin(labels.size(), 3); ++i)
{
m_editElements.at(i)->setLabel(labels.at(i));
}
}
QColor VectorEdit::xColor() const
{
return m_editElements.at(0)->color();
}
QColor VectorEdit::yColor() const
{
return m_editElements.at(1)->color();
}
QColor VectorEdit::zColor() const
{
return m_editElements.at(2)->color();
}
void VectorEdit::setColors(const QColor& xColor, const QColor& yColor, const QColor& zColor)
{
setXColor(xColor);
setYColor(yColor);
setZColor(zColor);
}
void VectorEdit::setXColor(const QColor& color)
{
m_editElements.at(0)->setColor(color);
}
void VectorEdit::setYColor(const QColor& color)
{
m_editElements.at(1)->setColor(color);
}
void VectorEdit::setZColor(const QColor& color)
{
m_editElements.at(2)->setColor(color);
}
VectorEditElement::Flavor VectorEdit::flavor() const
{
return m_flavor;
}
void VectorEdit::setFlavor(VectorEditElement::Flavor flavor)
{
if (flavor == m_flavor)
{
return;
}
m_flavor = flavor;
foreach(auto edit, m_editElements)
{
edit->setFlavor(flavor);
}
if (flavor == VectorEditElement::Invalid)
{
setPixmap(QPixmap(":/stylesheet/img/lineedit-invalid.png"));
}
else
{
setPixmap(QPixmap());
}
emit flavorChanged();
}
void VectorEdit::setPixmap(const QPixmap& icon)
{
m_iconLabel->setPixmap(icon);
}
void VectorEdit::setVector(QVector3D vec)
{
setVector(vec.x(), vec.y(), vec.z());
}
void VectorEdit::setVector(float xValue, float yValue, float zValue)
{
if (qFuzzyCompare(xValue, x()) && qFuzzyCompare(yValue, y()) && qFuzzyCompare(zValue, z()))
{
return;
}
QSignalBlocker sb0(m_editElements.at(0));
QSignalBlocker sb1(m_editElements.at(1));
QSignalBlocker sb2(m_editElements.at(2));
setX(xValue);
setY(yValue);
setZ(zValue);
emit vectorChanged();
}
void VectorEdit::setX(float v)
{
m_editElements.at(0)->setValue(v);
// Signal is emitted automatically
}
void VectorEdit::setY(float v)
{
m_editElements.at(1)->setValue(v);
// Signal is emitted automatically
}
void VectorEdit::setZ(float v)
{
m_editElements.at(2)->setValue(v);
// Signal is emitted automatically
}
}
#include "Components/moc_VectorEdit.cpp"
@@ -0,0 +1,122 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <QWidget>
AZ_PUSH_DISABLE_WARNING(4244, "-Wunknown-warning-option") // 4251: conversion from 'int' to 'float', possible loss of data
#include <QVector3D>
AZ_POP_DISABLE_WARNING
#include <QLabel>
#include <QLineEdit>
#include <QList>
#include <QStringList>
#include <QColor>
#endif
class QPixmap;
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API VectorEditElement
: public QWidget
{
Q_OBJECT
public:
// TODO: maybe share them somewhere (in a gadget)
enum Flavor
{
Plain = 0,
Information,
Question,
Invalid,
Valid
};
Q_ENUM(Flavor)
explicit VectorEditElement(const QString& label, QWidget* parent = nullptr);
float value() const;
void setValue(float);
QString label() const;
void setLabel(const QString&);
QColor color() const;
void setColor(const QColor&);
void setFlavor(VectorEditElement::Flavor flavor);
Q_SIGNALS:
void valueChanged();
private:
void updateColor();
QLineEdit* const m_lineEdit;
QLabel* const m_label;
QColor m_color;
Flavor m_flavor;
};
class AZ_QT_COMPONENTS_API VectorEdit
: public QWidget
{
Q_OBJECT
Q_PROPERTY(QVector3D vector READ vector WRITE setVector NOTIFY vectorChanged)
Q_PROPERTY(AzQtComponents::VectorEditElement::Flavor flavor READ flavor WRITE setFlavor NOTIFY flavorChanged)
public:
explicit VectorEdit(QWidget* parent = 0);
QVector3D vector() const;
float x() const;
float y() const;
float z() const;
QString xLabel() const;
QString yLabel() const;
QString zLabel() const;
void setLabels(const QString& xLabel, const QString& yLabel, const QString& zLabel);
void setLabels(const QStringList& labels);
QColor xColor() const;
QColor yColor() const;
QColor zColor() const;
void setColors(const QColor& xColor, const QColor& yColor, const QColor& zColor);
void setXColor(const QColor& color);
void setYColor(const QColor& color);
void setZColor(const QColor& color);
VectorEditElement::Flavor flavor() const;
void setFlavor(VectorEditElement::Flavor flavor);
void setPixmap(const QPixmap&);
public Q_SLOTS:
void setVector(QVector3D vec);
void setVector(float xValue, float yValue, float zValue);
void setX(float xValue);
void setY(float yValue);
void setZ(float zValue);
Q_SIGNALS:
void vectorChanged();
void flavorChanged();
private:
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'AzQtComponents::VectorEdit::m_editElements': class 'QList<AzQtComponents::VectorEditElement *>' needs to have dll-interface to be used by clients of class 'AzQtComponents::VectorEdit'
QList<VectorEditElement*> m_editElements;
AZ_POP_DISABLE_WARNING
VectorEditElement::Flavor m_flavor;
QLabel* const m_iconLabel;
};
} // namespace AzQtComponents
@@ -0,0 +1,39 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/Widgets/AssetFolderListView.h>
AZ_PUSH_DISABLE_WARNING(4244, "-Wunknown-warning-option")
#include <QHeaderView>
AZ_POP_DISABLE_WARNING
namespace AzQtComponents
{
AssetFolderListView::AssetFolderListView(QWidget* parent)
: TableView(parent)
{
setRootIsDecorated(true);
setUniformRowHeights(true);
setSortingEnabled(true);
header()->setStretchLastSection(false);
}
void AssetFolderListView::setModel(QAbstractItemModel* model)
{
QTreeView::setModel(model);
for (int i = 0; i < header()->count(); ++i)
{
header()->setSectionResizeMode(i, i == 0 ? QHeaderView::Stretch : QHeaderView::ResizeToContents);
}
}
}
#include "Components/Widgets/moc_AssetFolderListView.cpp"
@@ -0,0 +1,32 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <AzQtComponents/Components/Widgets/TableView.h>
#endif
class QAbstractItemModel;
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API AssetFolderListView
: public TableView
{
Q_OBJECT
public:
explicit AssetFolderListView(QWidget* parent = nullptr);
void setModel(QAbstractItemModel* model) override;
};
}
@@ -0,0 +1,28 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
AzQtComponents--AssetFolderListView
{
icon-size: 16px;
}
AzQtComponents--AssetFolderListView::branch:has-children:!has-siblings:closed,
AzQtComponents--AssetFolderListView::branch:closed:has-children:has-siblings {
border-image: none;
image: url(:/stylesheet/img/branch_closed.png);
}
AzQtComponents--AssetFolderListView::branch:open:has-children:!has-siblings,
AzQtComponents--AssetFolderListView::branch:open:has-children:has-siblings {
border-image: none;
image: url(:/stylesheet/img/branch_open.png);
}

Some files were not shown because too many files have changed in this diff Show More