2020-10-16 23:05:00 -06:00
|
|
|
// Copyright 2020 Dolphin Emulator Project
|
2021-07-04 19:22:19 -06:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2020-10-16 23:05:00 -06:00
|
|
|
|
|
|
|
#include "DolphinQt/Config/Graphics/BalloonTip.h"
|
|
|
|
|
|
|
|
#include <memory>
|
|
|
|
|
|
|
|
#include <QBitmap>
|
|
|
|
#include <QGraphicsEffect>
|
|
|
|
#include <QGraphicsView>
|
|
|
|
#include <QGridLayout>
|
|
|
|
#include <QGuiApplication>
|
|
|
|
#include <QLabel>
|
|
|
|
#include <QPainter>
|
|
|
|
#include <QPainterPath>
|
|
|
|
#include <QPushButton>
|
|
|
|
#include <QStyle>
|
|
|
|
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
|
|
#include <QScreen>
|
|
|
|
#else
|
|
|
|
#include <QApplication>
|
|
|
|
#include <QDesktopWidget>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(__APPLE__)
|
|
|
|
#include <QToolTip>
|
|
|
|
#endif
|
|
|
|
|
2020-10-24 22:30:14 -06:00
|
|
|
#include "Core/Config/MainSettings.h"
|
|
|
|
|
2021-04-28 10:15:53 -06:00
|
|
|
#include "DolphinQt/Settings.h"
|
|
|
|
|
2020-10-16 23:05:00 -06:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
std::unique_ptr<BalloonTip> s_the_balloon_tip = nullptr;
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
void BalloonTip::ShowBalloon(const QIcon& icon, const QString& title, const QString& message,
|
|
|
|
const QPoint& pos, QWidget* parent, ShowArrow show_arrow)
|
|
|
|
{
|
|
|
|
HideBalloon();
|
|
|
|
if (message.isEmpty() && title.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
#if defined(__APPLE__)
|
|
|
|
QString the_message = message;
|
|
|
|
the_message.replace(QStringLiteral("<dolphin_emphasis>"), QStringLiteral("<b>"));
|
|
|
|
the_message.replace(QStringLiteral("</dolphin_emphasis>"), QStringLiteral("</b>"));
|
|
|
|
QToolTip::showText(pos, the_message, parent);
|
|
|
|
#else
|
|
|
|
s_the_balloon_tip = std::make_unique<BalloonTip>(PrivateTag{}, icon, title, message, parent);
|
|
|
|
s_the_balloon_tip->UpdateBoundsAndRedraw(pos, show_arrow);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void BalloonTip::HideBalloon()
|
|
|
|
{
|
|
|
|
#if defined(__APPLE__)
|
|
|
|
QToolTip::hideText();
|
|
|
|
#else
|
|
|
|
if (!s_the_balloon_tip)
|
|
|
|
return;
|
|
|
|
s_the_balloon_tip->hide();
|
|
|
|
s_the_balloon_tip.reset();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
BalloonTip::BalloonTip(PrivateTag, const QIcon& icon, QString title, QString message,
|
|
|
|
QWidget* parent)
|
|
|
|
: QWidget(nullptr, Qt::ToolTip)
|
|
|
|
{
|
|
|
|
setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
setAutoFillBackground(true);
|
|
|
|
|
|
|
|
QColor window_color;
|
|
|
|
QColor text_color;
|
|
|
|
QColor dolphin_emphasis;
|
2021-04-28 10:15:53 -06:00
|
|
|
Settings::Instance().GetToolTipStyle(window_color, text_color, dolphin_emphasis, m_border_color,
|
|
|
|
parent->palette(), palette());
|
2020-10-16 23:05:00 -06:00
|
|
|
const auto style_sheet = QStringLiteral("background-color: #%1; color: #%2;")
|
|
|
|
.arg(window_color.rgba(), 0, 16)
|
|
|
|
.arg(text_color.rgba(), 0, 16);
|
|
|
|
setStyleSheet(style_sheet);
|
|
|
|
|
|
|
|
// Replace text in our our message
|
|
|
|
// if specific "tags" are used
|
|
|
|
message.replace(QStringLiteral("<dolphin_emphasis>"),
|
|
|
|
QStringLiteral("<font color=\"#%1\"><b>").arg(dolphin_emphasis.rgba(), 0, 16));
|
|
|
|
message.replace(QStringLiteral("</dolphin_emphasis>"), QStringLiteral("</b></font>"));
|
|
|
|
|
|
|
|
auto* title_label = new QLabel;
|
|
|
|
title_label->installEventFilter(this);
|
|
|
|
title_label->setText(title);
|
|
|
|
QFont f = title_label->font();
|
|
|
|
f.setBold(true);
|
|
|
|
title_label->setFont(f);
|
|
|
|
title_label->setTextFormat(Qt::RichText);
|
|
|
|
title_label->setSizePolicy(QSizePolicy::Policy::MinimumExpanding,
|
|
|
|
QSizePolicy::Policy::MinimumExpanding);
|
|
|
|
|
|
|
|
auto* message_label = new QLabel;
|
|
|
|
message_label->installEventFilter(this);
|
|
|
|
message_label->setText(message);
|
|
|
|
message_label->setTextFormat(Qt::RichText);
|
|
|
|
message_label->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
|
|
|
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
|
|
|
const int limit = QApplication::desktop()->availableGeometry(message_label).width() / 3;
|
|
|
|
#else
|
|
|
|
const int limit = message_label->screen()->availableGeometry().width() / 3;
|
|
|
|
#endif
|
|
|
|
message_label->setMaximumWidth(limit);
|
|
|
|
message_label->setSizePolicy(QSizePolicy::Policy::MinimumExpanding,
|
|
|
|
QSizePolicy::Policy::MinimumExpanding);
|
|
|
|
if (message_label->sizeHint().width() > limit)
|
|
|
|
{
|
|
|
|
message_label->setWordWrap(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto* layout = new QGridLayout;
|
|
|
|
layout->addWidget(title_label, 0, 0, 1, 2);
|
|
|
|
|
|
|
|
layout->addWidget(message_label, 1, 0, 1, 3);
|
|
|
|
layout->setSizeConstraint(QLayout::SetMinimumSize);
|
|
|
|
setLayout(layout);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BalloonTip::paintEvent(QPaintEvent*)
|
|
|
|
{
|
|
|
|
QPainter painter(this);
|
|
|
|
painter.drawPixmap(rect(), m_pixmap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BalloonTip::UpdateBoundsAndRedraw(const QPoint& pos, ShowArrow show_arrow)
|
|
|
|
{
|
|
|
|
m_show_arrow = show_arrow == ShowArrow::Yes;
|
|
|
|
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
|
|
|
const QRect screen_rect = QApplication::desktop()->screenGeometry(pos);
|
|
|
|
#else
|
|
|
|
QScreen* screen = QGuiApplication::screenAt(pos);
|
|
|
|
if (!screen)
|
|
|
|
screen = QGuiApplication::primaryScreen();
|
|
|
|
const QRect screen_rect = screen->geometry();
|
|
|
|
#endif
|
|
|
|
QSize sh = sizeHint();
|
2021-04-28 10:15:53 -06:00
|
|
|
// The look should resemble the default tooltip style set in Settings::SetCurrentUserStyle()
|
2020-10-16 23:05:00 -06:00
|
|
|
const int border = 1;
|
|
|
|
const int arrow_height = 18;
|
|
|
|
const int arrow_width = 18;
|
|
|
|
const int arrow_offset = 52;
|
|
|
|
const int rect_center = 7;
|
|
|
|
const bool arrow_at_bottom = (pos.y() - sh.height() - arrow_height > 0);
|
|
|
|
const bool arrow_at_left = (pos.x() + sh.width() - arrow_width < screen_rect.width());
|
|
|
|
const int default_padding = 10;
|
|
|
|
layout()->setContentsMargins(border + 3 + default_padding,
|
|
|
|
border + (arrow_at_bottom ? 0 : arrow_height) + 2 + default_padding,
|
|
|
|
border + 3 + default_padding,
|
|
|
|
border + (arrow_at_bottom ? arrow_height : 0) + 2 + default_padding);
|
|
|
|
updateGeometry();
|
|
|
|
sh = sizeHint();
|
|
|
|
|
|
|
|
int ml, mr, mt, mb;
|
|
|
|
QSize sz = sizeHint();
|
|
|
|
if (arrow_at_bottom)
|
|
|
|
{
|
|
|
|
ml = mt = 0;
|
|
|
|
mr = sz.width() - 1;
|
|
|
|
mb = sz.height() - arrow_height - 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ml = 0;
|
|
|
|
mt = arrow_height;
|
|
|
|
mr = sz.width() - 1;
|
|
|
|
mb = sz.height() - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
QPainterPath path;
|
|
|
|
path.moveTo(ml + rect_center, mt);
|
|
|
|
if (!arrow_at_bottom && arrow_at_left)
|
|
|
|
{
|
|
|
|
if (m_show_arrow)
|
|
|
|
{
|
|
|
|
path.lineTo(ml + arrow_offset - arrow_width, mt);
|
|
|
|
path.lineTo(ml + arrow_offset, mt - arrow_height);
|
|
|
|
path.lineTo(ml + arrow_offset + arrow_width, mt);
|
|
|
|
}
|
|
|
|
move(qMax(pos.x() - arrow_offset, screen_rect.left() + 2), pos.y());
|
|
|
|
}
|
|
|
|
else if (!arrow_at_bottom && !arrow_at_left)
|
|
|
|
{
|
|
|
|
if (m_show_arrow)
|
|
|
|
{
|
|
|
|
path.lineTo(mr - arrow_offset - arrow_width, mt);
|
|
|
|
path.lineTo(mr - arrow_offset, mt - arrow_height);
|
|
|
|
path.lineTo(mr - arrow_offset + arrow_width, mt);
|
|
|
|
}
|
|
|
|
move(qMin(pos.x() - sh.width() + arrow_offset, screen_rect.right() - sh.width() - 2), pos.y());
|
|
|
|
}
|
|
|
|
path.lineTo(mr - rect_center, mt);
|
|
|
|
path.arcTo(QRect(mr - rect_center * 2, mt, rect_center * 2, rect_center * 2), 90, -90);
|
|
|
|
path.lineTo(mr, mb - rect_center);
|
|
|
|
path.arcTo(QRect(mr - rect_center * 2, mb - rect_center * 2, rect_center * 2, rect_center * 2), 0,
|
|
|
|
-90);
|
|
|
|
if (arrow_at_bottom && !arrow_at_left)
|
|
|
|
{
|
|
|
|
if (m_show_arrow)
|
|
|
|
{
|
|
|
|
path.lineTo(mr - arrow_offset + arrow_width, mb);
|
|
|
|
path.lineTo(mr - arrow_offset, mb + arrow_height);
|
|
|
|
path.lineTo(mr - arrow_offset - arrow_width, mb);
|
|
|
|
}
|
|
|
|
move(qMin(pos.x() - sh.width() + arrow_offset, screen_rect.right() - sh.width() - 2),
|
|
|
|
pos.y() - sh.height());
|
|
|
|
}
|
|
|
|
else if (arrow_at_bottom && arrow_at_left)
|
|
|
|
{
|
|
|
|
if (m_show_arrow)
|
|
|
|
{
|
|
|
|
path.lineTo(arrow_offset + arrow_width, mb);
|
|
|
|
path.lineTo(arrow_offset, mb + arrow_height);
|
|
|
|
path.lineTo(arrow_offset - arrow_width, mb);
|
|
|
|
}
|
|
|
|
move(qMax(pos.x() - arrow_offset, screen_rect.x() + 2), pos.y() - sh.height());
|
|
|
|
}
|
|
|
|
path.lineTo(ml + rect_center, mb);
|
|
|
|
path.arcTo(QRect(ml, mb - rect_center * 2, rect_center * 2, rect_center * 2), -90, -90);
|
|
|
|
path.lineTo(ml, mt + rect_center);
|
|
|
|
path.arcTo(QRect(ml, mt, rect_center * 2, rect_center * 2), 180, -90);
|
|
|
|
|
|
|
|
// Set the mask
|
|
|
|
QBitmap bitmap(sizeHint());
|
|
|
|
bitmap.fill(Qt::color0);
|
|
|
|
QPainter painter1(&bitmap);
|
|
|
|
painter1.setPen(QPen(Qt::color1, border));
|
|
|
|
painter1.setBrush(QBrush(Qt::color1));
|
|
|
|
painter1.drawPath(path);
|
|
|
|
setMask(bitmap);
|
|
|
|
|
|
|
|
// Draw the border
|
|
|
|
m_pixmap = QPixmap(sz);
|
|
|
|
QPainter painter2(&m_pixmap);
|
|
|
|
painter2.setPen(QPen(m_border_color));
|
|
|
|
painter2.setBrush(palette().color(QPalette::Window));
|
|
|
|
painter2.drawPath(path);
|
|
|
|
|
|
|
|
show();
|
|
|
|
}
|