Merge pull request #12314 from Dentomologist/balloontip_rework
Balloontip drawing rework
This commit is contained in:
commit
b30d6e92db
|
@ -6,24 +6,25 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <QBitmap>
|
#include <QBitmap>
|
||||||
#include <QGraphicsEffect>
|
#include <QBrush>
|
||||||
#include <QGraphicsView>
|
#include <QCursor>
|
||||||
#include <QGridLayout>
|
#include <QFont>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QPainterPath>
|
#include <QPainterPath>
|
||||||
#include <QPushButton>
|
#include <QPen>
|
||||||
#include <QStyle>
|
#include <QPoint>
|
||||||
|
#include <QRect>
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
|
#include <QSize>
|
||||||
|
#include <QString>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
#include <QToolTip>
|
#include <QToolTip>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "Core/Config/MainSettings.h"
|
|
||||||
|
|
||||||
#include "DolphinQt/Settings.h"
|
#include "DolphinQt/Settings.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
|
@ -31,8 +32,9 @@ namespace
|
||||||
std::unique_ptr<BalloonTip> s_the_balloon_tip = nullptr;
|
std::unique_ptr<BalloonTip> s_the_balloon_tip = nullptr;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void BalloonTip::ShowBalloon(const QIcon& icon, const QString& title, const QString& message,
|
void BalloonTip::ShowBalloon(const QString& title, const QString& message,
|
||||||
const QPoint& pos, QWidget* parent, ShowArrow show_arrow)
|
const QPoint& target_arrow_tip_position, QWidget* const parent,
|
||||||
|
const ShowArrow show_arrow, const int border_width)
|
||||||
{
|
{
|
||||||
HideBalloon();
|
HideBalloon();
|
||||||
if (message.isEmpty() && title.isEmpty())
|
if (message.isEmpty() && title.isEmpty())
|
||||||
|
@ -42,10 +44,10 @@ void BalloonTip::ShowBalloon(const QIcon& icon, const QString& title, const QStr
|
||||||
QString the_message = message;
|
QString the_message = message;
|
||||||
the_message.replace(QStringLiteral("<dolphin_emphasis>"), QStringLiteral("<b>"));
|
the_message.replace(QStringLiteral("<dolphin_emphasis>"), QStringLiteral("<b>"));
|
||||||
the_message.replace(QStringLiteral("</dolphin_emphasis>"), QStringLiteral("</b>"));
|
the_message.replace(QStringLiteral("</dolphin_emphasis>"), QStringLiteral("</b>"));
|
||||||
QToolTip::showText(pos, the_message, parent);
|
QToolTip::showText(target_arrow_tip_position, the_message, parent);
|
||||||
#else
|
#else
|
||||||
s_the_balloon_tip = std::make_unique<BalloonTip>(PrivateTag{}, icon, title, message, parent);
|
s_the_balloon_tip = std::make_unique<BalloonTip>(PrivateTag{}, title, message, parent);
|
||||||
s_the_balloon_tip->UpdateBoundsAndRedraw(pos, show_arrow);
|
s_the_balloon_tip->UpdateBoundsAndRedraw(target_arrow_tip_position, show_arrow, border_width);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,20 +56,16 @@ void BalloonTip::HideBalloon()
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
QToolTip::hideText();
|
QToolTip::hideText();
|
||||||
#else
|
#else
|
||||||
if (!s_the_balloon_tip)
|
if (s_the_balloon_tip == nullptr)
|
||||||
return;
|
return;
|
||||||
s_the_balloon_tip->hide();
|
s_the_balloon_tip->hide();
|
||||||
s_the_balloon_tip.reset();
|
s_the_balloon_tip.reset();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
BalloonTip::BalloonTip(PrivateTag, const QIcon& icon, QString title, QString message,
|
BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidget* const parent)
|
||||||
QWidget* parent)
|
|
||||||
: QWidget(nullptr, Qt::ToolTip)
|
: QWidget(nullptr, Qt::ToolTip)
|
||||||
{
|
{
|
||||||
setAttribute(Qt::WA_DeleteOnClose);
|
|
||||||
setAutoFillBackground(true);
|
|
||||||
|
|
||||||
QColor window_color;
|
QColor window_color;
|
||||||
QColor text_color;
|
QColor text_color;
|
||||||
QColor dolphin_emphasis;
|
QColor dolphin_emphasis;
|
||||||
|
@ -78,43 +76,41 @@ BalloonTip::BalloonTip(PrivateTag, const QIcon& icon, QString title, QString mes
|
||||||
.arg(text_color.rgba(), 0, 16);
|
.arg(text_color.rgba(), 0, 16);
|
||||||
setStyleSheet(style_sheet);
|
setStyleSheet(style_sheet);
|
||||||
|
|
||||||
// Replace text in our our message
|
// Replace text in our our message if specific "tags" are used
|
||||||
// if specific "tags" are used
|
|
||||||
message.replace(QStringLiteral("<dolphin_emphasis>"),
|
message.replace(QStringLiteral("<dolphin_emphasis>"),
|
||||||
QStringLiteral("<font color=\"#%1\"><b>").arg(dolphin_emphasis.rgba(), 0, 16));
|
QStringLiteral("<font color=\"#%1\"><b>").arg(dolphin_emphasis.rgba(), 0, 16));
|
||||||
message.replace(QStringLiteral("</dolphin_emphasis>"), QStringLiteral("</b></font>"));
|
message.replace(QStringLiteral("</dolphin_emphasis>"), QStringLiteral("</b></font>"));
|
||||||
|
|
||||||
auto* title_label = new QLabel;
|
auto* const balloontip_layout = new QVBoxLayout;
|
||||||
title_label->installEventFilter(this);
|
balloontip_layout->setSizeConstraint(QLayout::SetFixedSize);
|
||||||
title_label->setText(title);
|
setLayout(balloontip_layout);
|
||||||
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;
|
const auto create_label = [=](const QString& text) {
|
||||||
message_label->installEventFilter(this);
|
QLabel* const label = new QLabel;
|
||||||
message_label->setText(message);
|
balloontip_layout->addWidget(label);
|
||||||
message_label->setTextFormat(Qt::RichText);
|
|
||||||
message_label->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
|
||||||
|
|
||||||
const int limit = message_label->screen()->availableGeometry().width() / 3;
|
label->setText(text);
|
||||||
message_label->setMaximumWidth(limit);
|
label->setTextFormat(Qt::RichText);
|
||||||
message_label->setSizePolicy(QSizePolicy::Policy::MinimumExpanding,
|
|
||||||
QSizePolicy::Policy::MinimumExpanding);
|
const int max_width = label->screen()->availableGeometry().width() / 3;
|
||||||
if (message_label->sizeHint().width() > limit)
|
label->setMaximumWidth(max_width);
|
||||||
|
if (label->sizeHint().width() > max_width)
|
||||||
|
label->setWordWrap(true);
|
||||||
|
|
||||||
|
return label;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!title.isEmpty())
|
||||||
{
|
{
|
||||||
message_label->setWordWrap(true);
|
QLabel* const title_label = create_label(title);
|
||||||
|
|
||||||
|
QFont title_font = title_label->font();
|
||||||
|
title_font.setBold(true);
|
||||||
|
title_label->setFont(title_font);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* layout = new QGridLayout;
|
if (!message.isEmpty())
|
||||||
layout->addWidget(title_label, 0, 0, 1, 2);
|
create_label(message);
|
||||||
|
|
||||||
layout->addWidget(message_label, 1, 0, 1, 3);
|
|
||||||
layout->setSizeConstraint(QLayout::SetMinimumSize);
|
|
||||||
setLayout(layout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BalloonTip::paintEvent(QPaintEvent*)
|
void BalloonTip::paintEvent(QPaintEvent*)
|
||||||
|
@ -123,116 +119,215 @@ void BalloonTip::paintEvent(QPaintEvent*)
|
||||||
painter.drawPixmap(rect(), m_pixmap);
|
painter.drawPixmap(rect(), m_pixmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BalloonTip::UpdateBoundsAndRedraw(const QPoint& pos, ShowArrow show_arrow)
|
void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
||||||
|
const ShowArrow show_arrow, const int border_full_width)
|
||||||
{
|
{
|
||||||
m_show_arrow = show_arrow == ShowArrow::Yes;
|
const float border_half_width = border_full_width / 2.0;
|
||||||
|
|
||||||
QScreen* screen = QGuiApplication::screenAt(pos);
|
// This should be odd so that the arrow tip is a single pixel wide.
|
||||||
if (!screen)
|
const int arrow_full_width = 35;
|
||||||
screen = QGuiApplication::primaryScreen();
|
const float arrow_half_width = arrow_full_width / 2.0;
|
||||||
|
// The y distance between the inner edge of the rectangle border and the inner tip of the arrow
|
||||||
|
// border, and also the distance between the outer edge of the rectangle border and the outer tip
|
||||||
|
// of the arrow border
|
||||||
|
const int arrow_height = (1 + arrow_full_width) / 2;
|
||||||
|
|
||||||
|
// Distance between the label layout and the inner rectangle border edge
|
||||||
|
const int balloon_interior_padding = 12;
|
||||||
|
// Prevent the corners of the label layout from portruding into the rounded rectangle corners at
|
||||||
|
// larger border sizes.
|
||||||
|
const int rounded_corner_margin = border_half_width / 4;
|
||||||
|
const int horizontal_margin =
|
||||||
|
border_full_width + rounded_corner_margin + balloon_interior_padding;
|
||||||
|
const int vertical_margin = horizontal_margin + arrow_height;
|
||||||
|
|
||||||
|
// Create enough space around the layout containing the title and message to draw the balloon and
|
||||||
|
// both arrow tips (at most one of which will be visible)
|
||||||
|
layout()->setContentsMargins(horizontal_margin, vertical_margin, horizontal_margin,
|
||||||
|
vertical_margin);
|
||||||
|
|
||||||
|
QSize size_hint = sizeHint();
|
||||||
|
|
||||||
|
// These positions represent the middle of each edge of the BalloonTip's rounded rectangle
|
||||||
|
const float rect_width = size_hint.width() - border_full_width;
|
||||||
|
const float rect_height = size_hint.height() - border_full_width - 2 * arrow_height;
|
||||||
|
const float rect_top = border_half_width + arrow_height;
|
||||||
|
const float rect_bottom = rect_top + rect_height;
|
||||||
|
const float rect_left = border_half_width;
|
||||||
|
// rect_right isn't used for anything
|
||||||
|
|
||||||
|
// Qt defines the radius of a rounded rectangle as "the radius of the ellipses defining the
|
||||||
|
// corner". Unlike the rectangle's edges this corresponds to the outside of the rounded curve
|
||||||
|
// instead of its middle, so we add the full width to the inner radius instead of the half width
|
||||||
|
const float corner_base_inner_radius = 7.0;
|
||||||
|
const float corner_outer_radius = corner_base_inner_radius + border_full_width;
|
||||||
|
|
||||||
|
// This value is arbitrary but works well.
|
||||||
|
const int base_arrow_x_offset = 34;
|
||||||
|
// Adjust the offset inward to compensate for the border and rounded corner widths. This ensures
|
||||||
|
// the arrow is on the flat part of the top/bottom border.
|
||||||
|
const int adjusted_arrow_x_offset =
|
||||||
|
base_arrow_x_offset + border_full_width + static_cast<int>(border_half_width);
|
||||||
|
// If the border is wide enough (or the BalloonTip small enough) the offset might end up past the
|
||||||
|
// midpoint; if that happens just use the midpoint instead
|
||||||
|
const int centered_arrow_x_offset = (size_hint.width() - arrow_full_width) / 2;
|
||||||
|
// If the arrow is on the left this is the distance between the left edge of the BalloonTip and
|
||||||
|
// the left edge of the arrow interior; otherwise the distance between the right edges.
|
||||||
|
const int arrow_nearest_edge_x_offset =
|
||||||
|
std::min(adjusted_arrow_x_offset, centered_arrow_x_offset);
|
||||||
|
const int arrow_tip_x_offset = arrow_nearest_edge_x_offset + arrow_half_width;
|
||||||
|
|
||||||
|
// The BalloonTip should be contained entirely within the screen that contains the target
|
||||||
|
// position.
|
||||||
|
QScreen* screen = QGuiApplication::screenAt(target_arrow_tip_position);
|
||||||
|
if (screen == nullptr)
|
||||||
|
{
|
||||||
|
// If the target position isn't on any screen (which can happen if the window is partly off the
|
||||||
|
// screen and the user hovers over the label) then use the screen containing the cursor instead.
|
||||||
|
screen = QGuiApplication::screenAt(QCursor::pos());
|
||||||
|
}
|
||||||
const QRect screen_rect = screen->geometry();
|
const QRect screen_rect = screen->geometry();
|
||||||
|
|
||||||
QSize sh = sizeHint();
|
QPainterPath rect_path;
|
||||||
// The look should resemble the default tooltip style set in Settings::ApplyStyle()
|
rect_path.addRoundedRect(rect_left, rect_top, rect_width, rect_height, corner_outer_radius,
|
||||||
const int border = 1;
|
corner_outer_radius);
|
||||||
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;
|
// The default pen cap style Qt::SquareCap extends drawn lines one half width before the starting
|
||||||
QSize sz = sizeHint();
|
// point and one half width after the ending point such that the starting and ending points are
|
||||||
if (arrow_at_bottom)
|
// surrounded by drawn pixels in both dimensions instead of just for the width. This is a
|
||||||
|
// reasonable default but we need to draw lines precisely, and this behavior causes problems when
|
||||||
|
// drawing lines of length and width 1 (which we do for the arrow interior, and also the arrow
|
||||||
|
// border when border_full_width is 1). For those lines to correctly end up as a pixel we would
|
||||||
|
// need to offset the start and end points by 0.5 inward. However, doing that would lead them to
|
||||||
|
// be at the same point, and if the endpoints of a line are the same Qt simply doesn't draw it
|
||||||
|
// regardless of the cap style.
|
||||||
|
//
|
||||||
|
// Using Qt::FlatCap instead fixes the issue.
|
||||||
|
|
||||||
|
m_pixmap = QPixmap(size_hint);
|
||||||
|
|
||||||
|
QPen border_pen(m_border_color, border_full_width);
|
||||||
|
border_pen.setCapStyle(Qt::FlatCap);
|
||||||
|
|
||||||
|
QPainter balloon_painter(&m_pixmap);
|
||||||
|
balloon_painter.setPen(border_pen);
|
||||||
|
balloon_painter.setBrush(palette().color(QPalette::Window));
|
||||||
|
balloon_painter.drawPath(rect_path);
|
||||||
|
|
||||||
|
QBitmap mask_bitmap(size_hint);
|
||||||
|
mask_bitmap.fill(Qt::color0);
|
||||||
|
|
||||||
|
QPen mask_pen(Qt::color1, border_full_width);
|
||||||
|
mask_pen.setCapStyle(Qt::FlatCap);
|
||||||
|
|
||||||
|
QPainter mask_painter(&mask_bitmap);
|
||||||
|
mask_painter.setPen(mask_pen);
|
||||||
|
mask_painter.setBrush(QBrush(Qt::color1));
|
||||||
|
mask_painter.drawPath(rect_path);
|
||||||
|
|
||||||
|
const bool arrow_at_bottom =
|
||||||
|
target_arrow_tip_position.y() - size_hint.height() + arrow_height >= 0;
|
||||||
|
const bool arrow_at_left =
|
||||||
|
target_arrow_tip_position.x() + size_hint.width() - arrow_tip_x_offset < screen_rect.width();
|
||||||
|
|
||||||
|
const float arrow_base_y =
|
||||||
|
arrow_at_bottom ? rect_bottom - border_half_width : rect_top + border_half_width;
|
||||||
|
|
||||||
|
const float arrow_tip_vertical_offset = arrow_at_bottom ? arrow_height : -arrow_height;
|
||||||
|
const float arrow_tip_interior_y = arrow_base_y + arrow_tip_vertical_offset;
|
||||||
|
const float arrow_tip_exterior_y =
|
||||||
|
arrow_tip_interior_y + (arrow_at_bottom ? border_full_width : -border_full_width);
|
||||||
|
const float arrow_base_left_edge_x =
|
||||||
|
arrow_at_left ? arrow_nearest_edge_x_offset :
|
||||||
|
size_hint.width() - arrow_nearest_edge_x_offset - arrow_full_width;
|
||||||
|
const float arrow_base_right_edge_x = arrow_base_left_edge_x + arrow_full_width;
|
||||||
|
const float arrow_tip_x = arrow_base_left_edge_x + arrow_half_width;
|
||||||
|
|
||||||
|
if (show_arrow == ShowArrow::Yes)
|
||||||
{
|
{
|
||||||
ml = mt = 0;
|
// Drawing diagonal lines in Qt is filled with edge cases and inexplicable behavior. Getting it
|
||||||
mr = sz.width() - 1;
|
// to do what you want at one border size is simple enough, but doing so flexibly is an exercise
|
||||||
mb = sz.height() - arrow_height - 1;
|
// in futility. Some examples:
|
||||||
}
|
// * For some values of x, diagonal lines of width x and x+1 are drawn exactly the same.
|
||||||
else
|
// * When drawing a triangle where p1 and p3 have exactly the same y value, they can be drawn at
|
||||||
{
|
// different heights.
|
||||||
ml = 0;
|
// * Lines of width 1 sometimes get drawn one pixel past where they should even with FlatCap,
|
||||||
mt = arrow_height;
|
// but only on the left side (regardless of which direction the stroke was).
|
||||||
mr = sz.width() - 1;
|
//
|
||||||
mb = sz.height() - 1;
|
// Instead of dealing with all that, fake it with vertical lines which are much better behaved.
|
||||||
|
// Draw a bunch of vertical lines with width 1 to form the arrow border and interior.
|
||||||
|
|
||||||
|
QPainterPath arrow_border_path;
|
||||||
|
QPainterPath arrow_interior_fill_path;
|
||||||
|
const float y_end_offset = arrow_at_bottom ? border_full_width : -border_full_width;
|
||||||
|
|
||||||
|
// Draw the arrow border and interior lines from the outside inward. Each loop iteration draws
|
||||||
|
// one pair of border lines and one pair of interior lines.
|
||||||
|
for (int i = 1; i <= arrow_half_width; i++)
|
||||||
|
{
|
||||||
|
const float x_offset_from_arrow_base_edge = i - 0.5;
|
||||||
|
const float border_y_start = arrow_base_y + (arrow_at_bottom ? i : -i);
|
||||||
|
const float border_y_end = border_y_start + y_end_offset;
|
||||||
|
const float interior_y_start = arrow_base_y;
|
||||||
|
const float interior_y_end = border_y_start;
|
||||||
|
const float left_line_x = arrow_base_left_edge_x + x_offset_from_arrow_base_edge;
|
||||||
|
const float right_line_x = arrow_base_right_edge_x - x_offset_from_arrow_base_edge;
|
||||||
|
|
||||||
|
arrow_border_path.moveTo(left_line_x, border_y_start);
|
||||||
|
arrow_border_path.lineTo(left_line_x, border_y_end);
|
||||||
|
|
||||||
|
arrow_border_path.moveTo(right_line_x, border_y_start);
|
||||||
|
arrow_border_path.lineTo(right_line_x, border_y_end);
|
||||||
|
|
||||||
|
arrow_interior_fill_path.moveTo(left_line_x, interior_y_start);
|
||||||
|
arrow_interior_fill_path.lineTo(left_line_x, interior_y_end);
|
||||||
|
|
||||||
|
arrow_interior_fill_path.moveTo(right_line_x, interior_y_start);
|
||||||
|
arrow_interior_fill_path.lineTo(right_line_x, interior_y_end);
|
||||||
|
}
|
||||||
|
// The middle border line
|
||||||
|
arrow_border_path.moveTo(arrow_tip_x, arrow_tip_interior_y);
|
||||||
|
arrow_border_path.lineTo(arrow_tip_x, arrow_tip_interior_y + y_end_offset);
|
||||||
|
|
||||||
|
// The middle interior line
|
||||||
|
arrow_interior_fill_path.moveTo(arrow_tip_x, arrow_base_y);
|
||||||
|
arrow_interior_fill_path.lineTo(arrow_tip_x, arrow_tip_interior_y);
|
||||||
|
|
||||||
|
border_pen.setWidth(1);
|
||||||
|
|
||||||
|
balloon_painter.setPen(border_pen);
|
||||||
|
balloon_painter.drawPath(arrow_border_path);
|
||||||
|
|
||||||
|
QPen arrow_interior_fill_pen(palette().color(QPalette::Window), 1);
|
||||||
|
arrow_interior_fill_pen.setCapStyle(Qt::FlatCap);
|
||||||
|
balloon_painter.setPen(arrow_interior_fill_pen);
|
||||||
|
balloon_painter.drawPath(arrow_interior_fill_path);
|
||||||
|
|
||||||
|
mask_pen.setWidth(1);
|
||||||
|
mask_painter.setPen(mask_pen);
|
||||||
|
|
||||||
|
mask_painter.drawPath(arrow_border_path);
|
||||||
|
mask_painter.drawPath(arrow_interior_fill_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
QPainterPath path;
|
setMask(mask_bitmap);
|
||||||
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
|
// Place the arrow tip at the target position whether the arrow tip is drawn or not
|
||||||
QBitmap bitmap(sizeHint());
|
const int target_balloontip_global_x =
|
||||||
bitmap.fill(Qt::color0);
|
target_arrow_tip_position.x() - static_cast<int>(arrow_tip_x);
|
||||||
QPainter painter1(&bitmap);
|
const int rightmost_valid_balloontip_global_x = screen_rect.width() - size_hint.width();
|
||||||
painter1.setPen(QPen(Qt::color1, border));
|
// If the balloon would extend off the screen, push it left or right until it's not
|
||||||
painter1.setBrush(QBrush(Qt::color1));
|
const int actual_balloontip_global_x =
|
||||||
painter1.drawPath(path);
|
std::max(0, std::min(rightmost_valid_balloontip_global_x, target_balloontip_global_x));
|
||||||
setMask(bitmap);
|
// The tip pixel should be in the middle of the control, and arrow_tip_exterior_y is at the bottom
|
||||||
|
// of that pixel. When arrow_at_bottom is true the arrow is above arrow_tip_exterior_y and so the
|
||||||
|
// tip pixel is in the right place, but when it's false the arrow is below arrow_tip_exterior_y
|
||||||
|
// so the tip pixel would be the one below that. Make this adjustment to fix that.
|
||||||
|
const int tip_pixel_adjustment = arrow_at_bottom ? 0 : 1;
|
||||||
|
const int actual_balloontip_global_y =
|
||||||
|
target_arrow_tip_position.y() - arrow_tip_exterior_y - tip_pixel_adjustment;
|
||||||
|
|
||||||
// Draw the border
|
move(actual_balloontip_global_x, actual_balloontip_global_y);
|
||||||
m_pixmap = QPixmap(sz);
|
|
||||||
QPainter painter2(&m_pixmap);
|
|
||||||
painter2.setPen(QPen(m_border_color));
|
|
||||||
painter2.setBrush(palette().color(QPalette::Window));
|
|
||||||
painter2.drawPath(path);
|
|
||||||
|
|
||||||
show();
|
show();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,14 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QIcon>
|
#include <QColor>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
class QPaintEvent;
|
||||||
|
class QPoint;
|
||||||
|
class QString;
|
||||||
|
|
||||||
class BalloonTip : public QWidget
|
class BalloonTip : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -21,15 +25,16 @@ public:
|
||||||
Yes,
|
Yes,
|
||||||
No
|
No
|
||||||
};
|
};
|
||||||
static void ShowBalloon(const QIcon& icon, const QString& title, const QString& msg,
|
static void ShowBalloon(const QString& title, const QString& message,
|
||||||
const QPoint& pos, QWidget* parent,
|
const QPoint& target_arrow_tip_position, QWidget* parent,
|
||||||
ShowArrow show_arrow = ShowArrow::Yes);
|
ShowArrow show_arrow = ShowArrow::Yes, int border_width = 1);
|
||||||
static void HideBalloon();
|
static void HideBalloon();
|
||||||
|
|
||||||
BalloonTip(PrivateTag, const QIcon& icon, QString title, QString msg, QWidget* parent);
|
BalloonTip(PrivateTag, const QString& title, QString message, QWidget* parent);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void UpdateBoundsAndRedraw(const QPoint&, ShowArrow);
|
void UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position, ShowArrow show_arrow,
|
||||||
|
int border_width);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent*) override;
|
void paintEvent(QPaintEvent*) override;
|
||||||
|
@ -37,5 +42,4 @@ protected:
|
||||||
private:
|
private:
|
||||||
QColor m_border_color;
|
QColor m_border_color;
|
||||||
QPixmap m_pixmap;
|
QPixmap m_pixmap;
|
||||||
bool m_show_arrow = true;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -37,7 +37,7 @@ private:
|
||||||
this->killTimer(*m_timer_id);
|
this->killTimer(*m_timer_id);
|
||||||
m_timer_id.reset();
|
m_timer_id.reset();
|
||||||
|
|
||||||
BalloonTip::ShowBalloon(QIcon(), m_title, m_description,
|
BalloonTip::ShowBalloon(m_title, m_description,
|
||||||
this->parentWidget()->mapToGlobal(GetToolTipPosition()), this);
|
this->parentWidget()->mapToGlobal(GetToolTipPosition()), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue