fcitx5-qt/qt5/platforminputcontext/fcitxcandidatewindow.cpp

777 lines
28 KiB
C++

/*
* SPDX-FileCopyrightText: 2021~2021 CSSlayer <wengxt@gmail.com>
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include "fcitxcandidatewindow.h"
#include "fcitxflags.h"
#include "fcitxtheme.h"
#include "qfcitxplatforminputcontext.h"
#include <QDebug>
#include <QExposeEvent>
#include <QFont>
#include <QGuiApplication>
#include <QMouseEvent>
#include <QPalette>
#include <QResizeEvent>
#include <QScreen>
#include <QTextLayout>
#include <QVariant>
#include <QtMath>
#include <utility>
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <QtWaylandClient/private/qwayland-xdg-shell.h>
#include <QtWaylandClient/private/qwaylanddisplay_p.h>
#include <QtWaylandClient/private/qwaylandintegration_p.h>
#include <QtWaylandClient/private/qwaylandwindow_p.h>
#include <QtWaylandClient/private/wayland-xdg-shell-client-protocol.h>
#include <qpa/qplatformnativeinterface.h>
#endif
namespace fcitx {
namespace {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
class XdgWmBase : public QtWayland::xdg_wm_base {
public:
using xdg_wm_base::xdg_wm_base;
protected:
// This is required for all xdg_wm_base bind.
void xdg_wm_base_ping(uint32_t serial) override { pong(serial); }
};
#endif
void doLayout(QTextLayout &layout) {
QFontMetrics fontMetrics(layout.font());
auto minH = fontMetrics.ascent() + fontMetrics.descent();
layout.setCacheEnabled(true);
layout.beginLayout();
int height = 0;
while (1) {
QTextLine line = layout.createLine();
if (!line.isValid())
break;
line.setLineWidth(INT_MAX);
line.setLeadingIncluded(true);
line.setPosition(
QPoint(0, height - line.ascent() + fontMetrics.ascent()));
height += minH;
}
layout.endLayout();
}
} // namespace
class MultilineText {
public:
MultilineText(const QFont &font, const QString &text) {
QStringList lines = text.split("\n");
int width = 0;
QFontMetrics fontMetrics(font);
fontHeight_ = fontMetrics.ascent() + fontMetrics.descent();
for (const auto &line : lines) {
layouts_.emplace_back(std::make_unique<QTextLayout>(line));
layouts_.back()->setFont(font);
doLayout(*layouts_.back());
width = std::max(width,
layouts_.back()->boundingRect().toRect().width());
}
boundingRect_.setTopLeft(QPoint(0, 0));
boundingRect_.setSize(QSize(width, lines.size() * fontHeight_));
}
bool isEmpty() const { return layouts_.empty(); }
void draw(QPainter *painter, QColor color, QPoint position) {
painter->save();
painter->setPen(color);
int currentY = 0;
for (const auto &layout : layouts_) {
layout->draw(painter, position + QPoint(0, currentY));
currentY += fontHeight_;
}
painter->restore();
}
QRect boundingRect() { return boundingRect_; }
private:
std::vector<std::unique_ptr<QTextLayout>> layouts_;
int fontHeight_;
QRect boundingRect_;
};
FcitxCandidateWindow::FcitxCandidateWindow(QWindow *window,
QFcitxPlatformInputContext *context)
: QRasterWindow(), context_(context), theme_(context->theme()),
parent_(window) {
constexpr Qt::WindowFlags commonFlags = Qt::FramelessWindowHint |
Qt::WindowDoesNotAcceptFocus |
Qt::NoDropShadowWindowHint;
if (isWayland_) {
// Qt::ToolTip ensures wayland doesn't grab focus.
// Not using Qt::BypassWindowManagerHint ensures wayland handle
// fractional scale.
setFlags(Qt::ToolTip | commonFlags);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (auto instance = QtWaylandClient::QWaylandIntegration::instance()) {
for (QtWaylandClient::QWaylandDisplay::RegistryGlobal global :
instance->display()->globals()) {
if (global.interface == QLatin1String("xdg_wm_base")) {
xdgWmBase_.reset(
new XdgWmBase(instance->display()->wl_registry(),
global.id, global.version));
break;
}
}
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
setProperty("_q_waylandPopupAnchor",
QVariant::fromValue(Qt::BottomEdge | Qt::LeftEdge));
setProperty("_q_waylandPopupGravity",
QVariant::fromValue(Qt::BottomEdge | Qt::RightEdge));
setProperty(
"_q_waylandPopupConstraintAdjustment",
static_cast<unsigned int>(
QtWayland::xdg_positioner::constraint_adjustment_slide_x |
QtWayland::xdg_positioner::constraint_adjustment_flip_y));
#endif
#endif
} else {
// Qt::Popup ensures X11 doesn't apply tooltip animation under kwin.
setFlags(Qt::Popup | Qt::BypassWindowManagerHint | commonFlags);
}
if (isWayland_) {
setTransientParent(parent_);
}
QSurfaceFormat surfaceFormat = format();
surfaceFormat.setAlphaBufferSize(8);
setFormat(surfaceFormat);
connect(this, &QWindow::visibleChanged, this, [this] { hoverIndex_ = -1; });
}
FcitxCandidateWindow::~FcitxCandidateWindow() {}
bool FcitxCandidateWindow::event(QEvent *event) {
if (event->type() == QEvent::Leave) {
auto oldHighlight = highlight();
hoverIndex_ = -1;
if (highlight() != oldHighlight) {
requestUpdate();
}
}
return QRasterWindow::event(event);
}
void FcitxCandidateWindow::render(QPainter *painter) {
theme_->paint(painter, theme_->background(),
QRect(QPoint(0, 0), actualSize_));
auto contentMargin = theme_->contentMargin();
const QPoint topLeft(contentMargin.left(), contentMargin.top());
painter->setPen(theme_->normalColor());
auto minH =
theme_->fontMetrics().ascent() + theme_->fontMetrics().descent();
auto textMargin = theme_->textMargin();
auto extraW = textMargin.left() + textMargin.right();
auto extraH = textMargin.top() + textMargin.bottom();
size_t currentHeight = 0;
if (!upperLayout_.text().isEmpty()) {
upperLayout_.draw(
painter, topLeft + QPoint(textMargin.left(), textMargin.top()));
// Draw cursor
currentHeight += minH + extraH;
if (cursor_ >= 0) {
auto line = upperLayout_.lineForTextPosition(cursor_);
if (line.isValid()) {
int cursorX = line.cursorToX(cursor_);
line.lineNumber();
painter->save();
QPen pen = painter->pen();
pen.setWidth(2);
painter->setPen(pen);
QPoint start = topLeft + QPoint(textMargin.left() + cursorX + 1,
textMargin.top() +
line.lineNumber() * minH);
painter->drawLine(start, start + QPoint(0, minH));
painter->restore();
}
}
}
if (!lowerLayout_.text().isEmpty()) {
lowerLayout_.draw(painter,
topLeft + QPoint(textMargin.left(),
textMargin.top() + currentHeight));
currentHeight += minH + extraH;
}
bool vertical = theme_->vertical();
if (layoutHint_ == FcitxCandidateLayoutHint::Vertical) {
vertical = true;
} else if (layoutHint_ == FcitxCandidateLayoutHint::Horizontal) {
vertical = false;
}
candidateRegions_.clear();
candidateRegions_.reserve(labelLayouts_.size());
size_t wholeW = 0, wholeH = 0;
// size of text = textMargin + actual text size.
// HighLight = HighLight margin + TEXT.
// Click region = HighLight - click
for (size_t i = 0; i < labelLayouts_.size(); i++) {
int x, y;
if (vertical) {
x = 0;
y = currentHeight + wholeH;
} else {
x = wholeW;
y = currentHeight;
}
x += textMargin.left();
y += textMargin.top();
int labelW = 0, labelH = 0, candidateW = 0, candidateH = 0;
if (!labelLayouts_[i]->isEmpty()) {
auto size = labelLayouts_[i]->boundingRect();
labelW = size.width();
labelH = size.height();
}
if (!candidateLayouts_[i]->isEmpty()) {
auto size = candidateLayouts_[i]->boundingRect();
candidateW = size.width();
candidateH = size.height();
}
int vheight;
if (vertical) {
vheight = std::max({minH, labelH, candidateH});
wholeH += vheight + extraH;
} else {
vheight = candidatesHeight_ - extraH;
wholeW += candidateW + labelW + extraW;
}
QMargins highlightMargin = theme_->highlightMargin();
QMargins clickMargin = theme_->highlightClickMargin();
auto highlightWidth = labelW + candidateW;
bool fullWidthHighlight = true;
if (fullWidthHighlight && vertical) {
// Last candidate, fill.
highlightWidth = actualSize_.width() - contentMargin.left() -
contentMargin.right() - textMargin.left() -
textMargin.right();
}
const int highlightIndex = highlight();
QColor color = theme_->normalColor();
if (highlightIndex >= 0 && i == static_cast<size_t>(highlightIndex)) {
// Paint highlight
theme_->paint(
painter, theme_->highlight(),
QRect(topLeft + QPoint(x, y) -
QPoint(highlightMargin.left(), highlightMargin.top()),
QSize(highlightWidth + highlightMargin.left() +
highlightMargin.right(),
vheight + highlightMargin.top() +
highlightMargin.bottom())));
color = theme_->highlightCandidateColor();
}
QRect candidateRegion(
topLeft + QPoint(x, y) -
QPoint(highlightMargin.left(), highlightMargin.right()) +
QPoint(clickMargin.left(), clickMargin.right()),
QSize(highlightWidth + highlightMargin.left() +
highlightMargin.right() - clickMargin.left() -
clickMargin.right(),
vheight + highlightMargin.top() + highlightMargin.bottom() -
clickMargin.top() - clickMargin.bottom()));
candidateRegions_.push_back(candidateRegion);
if (!labelLayouts_[i]->isEmpty()) {
labelLayouts_[i]->draw(painter, color, topLeft + QPoint(x, y));
}
if (!candidateLayouts_[i]->isEmpty()) {
candidateLayouts_[i]->draw(painter, color,
topLeft + QPoint(x + labelW, y));
}
}
prevRegion_ = QRect();
nextRegion_ = QRect();
if (labelLayouts_.size() && (hasPrev_ || hasNext_)) {
if (theme_->prev().valid() && theme_->next().valid()) {
int prevY = 0, nextY = 0;
if (theme_->buttonAlignment() == "Top") {
prevY = contentMargin.top();
nextY = contentMargin.top();
} else if (theme_->buttonAlignment() == "First Candidate") {
prevY = candidateRegions_.front().top() +
(candidateRegions_.front().height() -
theme_->prev().height()) /
2.0;
nextY = candidateRegions_.front().top() +
(candidateRegions_.front().height() -
theme_->next().height()) /
2.0;
} else if (theme_->buttonAlignment() == "Center") {
prevY = contentMargin.top() +
(actualSize_.height() - contentMargin.top() -
contentMargin.bottom() - theme_->prev().height()) /
2.0;
nextY = contentMargin.top() +
(actualSize_.height() - contentMargin.top() -
contentMargin.bottom() - theme_->next().height()) /
2.0;
} else if (theme_->buttonAlignment() == "Last Candidate") {
prevY = candidateRegions_.back().top() +
(candidateRegions_.back().height() -
theme_->prev().height()) /
2.0;
nextY = candidateRegions_.back().top() +
(candidateRegions_.back().height() -
theme_->next().height()) /
2.0;
} else {
prevY = actualSize_.height() - contentMargin.bottom() -
theme_->prev().height();
nextY = actualSize_.height() - contentMargin.bottom() -
theme_->next().height();
}
nextRegion_ =
QRect(QPoint(actualSize_.width() - contentMargin.right() -
theme_->prev().width(),
nextY),
theme_->next().size());
double alpha = 1.0;
if (!hasNext_) {
alpha = 0.3;
} else if (nextHovered_) {
alpha = 0.7;
}
theme_->paint(painter, theme_->next(), nextRegion_.topLeft(),
alpha);
nextRegion_ = nextRegion_.marginsRemoved(theme_->next().margin());
prevRegion_ = QRect(
QPoint(actualSize_.width() - contentMargin.right() -
theme_->next().width() - theme_->prev().width(),
prevY),
theme_->prev().size());
alpha = 1.0;
if (!hasPrev_) {
alpha = 0.3;
} else if (prevHovered_) {
alpha = 0.7;
}
theme_->paint(painter, theme_->prev(), prevRegion_.topLeft(),
alpha);
prevRegion_ = prevRegion_.marginsRemoved(theme_->prev().margin());
}
}
}
void UpdateLayout(QTextLayout &layout, const FcitxTheme &theme,
std::initializer_list<
std::reference_wrapper<const FcitxQtFormattedPreeditList>>
texts) {
layout.clearFormats();
layout.setFont(theme.font());
QVector<QTextLayout::FormatRange> formats;
QString str;
int pos = 0;
for (const auto &text : texts) {
for (const auto &preedit : text.get()) {
str += preedit.string();
QTextCharFormat format;
if (preedit.format() & FcitxTextFormatFlag_Underline) {
format.setUnderlineStyle(QTextCharFormat::DashUnderline);
}
if (preedit.format() & FcitxTextFormatFlag_Strike) {
format.setFontStrikeOut(true);
}
if (preedit.format() & FcitxTextFormatFlag_Bold) {
format.setFontWeight(QFont::Bold);
}
if (preedit.format() & FcitxTextFormatFlag_Italic) {
format.setFontItalic(true);
}
if (preedit.format() & FcitxTextFormatFlag_HighLight) {
format.setBackground(theme.highlightBackgroundColor());
format.setForeground(theme.highlightColor());
}
formats.append(QTextLayout::FormatRange{
pos, static_cast<int>(preedit.string().length()), format});
pos += preedit.string().length();
}
}
layout.setText(str);
layout.setFormats(formats);
}
void FcitxCandidateWindow::updateClientSideUI(
const FcitxQtFormattedPreeditList &preedit, int cursorpos,
const FcitxQtFormattedPreeditList &auxUp,
const FcitxQtFormattedPreeditList &auxDown,
const FcitxQtStringKeyValueList &candidates, int candidateIndex,
int layoutHint, bool hasPrev, bool hasNext) {
bool preeditVisible = !preedit.isEmpty();
bool auxUpVisbile = !auxUp.isEmpty();
bool auxDownVisible = !auxDown.isEmpty();
bool candidatesVisible = !candidates.isEmpty();
bool visible =
preeditVisible || auxUpVisbile || auxDownVisible || candidatesVisible;
auto window = context_->focusWindowWrapper();
if (!theme_ || !visible || !window || window != parent_) {
hide();
return;
}
UpdateLayout(upperLayout_, *theme_, {auxUp, preedit});
if (cursorpos >= 0) {
int auxUpLength = 0;
for (const auto &auxUpText : auxUp) {
auxUpLength += auxUpText.string().length();
}
// Get the preedit part
auto preeditString = upperLayout_.text().mid(auxUpLength).toUtf8();
preeditString = preeditString.mid(0, cursorpos);
cursor_ = auxUpLength + QString::fromUtf8(preeditString).length();
} else {
cursor_ = -1;
}
doLayout(upperLayout_);
UpdateLayout(lowerLayout_, *theme_, {auxDown});
doLayout(lowerLayout_);
labelLayouts_.clear();
candidateLayouts_.clear();
for (int i = 0; i < candidates.size(); i++) {
labelLayouts_.emplace_back(std::make_unique<MultilineText>(
theme_->font(), candidates[i].key()));
candidateLayouts_.emplace_back(std::make_unique<MultilineText>(
theme_->font(), candidates[i].value()));
}
highlight_ = candidateIndex;
hasPrev_ = hasPrev;
hasNext_ = hasNext;
layoutHint_ = static_cast<FcitxCandidateLayoutHint>(layoutHint);
actualSize_ = sizeHint();
if (actualSize_.width() <= 0 || actualSize_.height() <= 0) {
hide();
return;
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QSize sizeWithoutShadow = actualSize_.shrunkBy(theme_->shadowMargin());
#else
QSize sizeWithoutShadow =
actualSize_ -
QSize(theme_->shadowMargin().left() + theme_->shadowMargin().right(),
theme_->shadowMargin().top() + theme_->shadowMargin().bottom());
#endif
if (sizeWithoutShadow.width() < 0) {
sizeWithoutShadow.setWidth(0);
}
if (sizeWithoutShadow.height() < 0) {
sizeWithoutShadow.setHeight(0);
}
resize(actualSize_);
QRect cursorRect = context_->cursorRectangleWrapper();
QRect screenGeometry;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (isWayland_) {
auto waylandWindow =
static_cast<QtWaylandClient::QWaylandWindow *>(window->handle());
const auto windowMargins = waylandWindow->windowContentMargins() -
waylandWindow->clientSideMargins();
auto windowGeometry = window->frameGeometry();
windowGeometry = windowGeometry.marginsAdded(-windowMargins);
if (!cursorRect.isValid()) {
if (cursorRect.width() <= 0) {
cursorRect.setWidth(1);
}
if (cursorRect.height() <= 0) {
cursorRect.setHeight(1);
}
}
// valid the anchor rect.
if (!cursorRect.intersects(windowGeometry)) {
if (cursorRect.right() < windowGeometry.left()) {
cursorRect.setLeft(windowGeometry.left());
cursorRect.setWidth(1);
}
if (cursorRect.left() > windowGeometry.right()) {
cursorRect.setLeft(windowGeometry.right());
cursorRect.setWidth(1);
}
if (cursorRect.bottom() < windowGeometry.top()) {
cursorRect.setTop(windowGeometry.top());
cursorRect.setWidth(1);
}
if (cursorRect.top() > windowGeometry.bottom()) {
cursorRect.setTop(windowGeometry.bottom());
cursorRect.setWidth(1);
}
}
bool wasVisible = isVisible();
bool cursorRectChanged = false;
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
if (property("_q_waylandPopupAnchorRect") != cursorRect) {
cursorRectChanged = true;
setProperty("_q_waylandPopupAnchorRect", cursorRect);
}
#endif
// This try to ensure xdg_popup is available.
show();
xdg_popup *xdgPopup = static_cast<xdg_popup *>(
QGuiApplication::platformNativeInterface()->nativeResourceForWindow(
"xdg_popup", this));
if (xdgWmBase_ && xdgPopup &&
xdg_popup_get_version(xdgPopup) >=
XDG_POPUP_REPOSITION_SINCE_VERSION) {
cursorRect.translate(-windowMargins.left(), -windowMargins.top());
auto positioner =
new QtWayland::xdg_positioner(xdgWmBase_->create_positioner());
positioner->set_anchor_rect(cursorRect.x(), cursorRect.y(),
cursorRect.width(),
cursorRect.height());
positioner->set_anchor(
QtWayland::xdg_positioner::anchor_bottom_left);
positioner->set_gravity(
QtWayland::xdg_positioner::gravity_bottom_right);
positioner->set_size(width(), height());
positioner->set_constraint_adjustment(
QtWayland::xdg_positioner::constraint_adjustment_slide_x |
QtWayland::xdg_positioner::constraint_adjustment_flip_y);
xdg_popup_reposition(xdgPopup, positioner->object(),
repositionToken_++);
positioner->destroy();
return;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
// Check if we need remap.
// If it was invisible, nothing need to be done.
// If cursor rect changed, the window must be remapped.
// If adjustment is already happening (flip/slide), then remap.
// If we predict adjustment may be happening, then remap.
const auto predictGeometry =
QRect(QPoint(cursorRect.x(), cursorRect.y() + cursorRect.height()),
actualSize_);
if (wasVisible &&
(cursorRectChanged || position() != predictGeometry.topLeft() ||
!windowGeometry.contains(predictGeometry))) {
hide();
show();
}
return;
#endif
}
#endif
// Try to apply the screen edge detection over the window, because if we
// intent to use this with wayland. It we have no information above screen
// edge.
if (isWayland_) {
screenGeometry = window->frameGeometry();
cursorRect.translate(window->framePosition());
auto margins = window->frameMargins();
cursorRect.translate(margins.left(), margins.top());
} else {
screenGeometry = window->screen()->geometry();
auto pos = window->mapToGlobal(cursorRect.topLeft());
cursorRect.moveTo(pos);
}
int x = cursorRect.left(), y = cursorRect.bottom();
if (cursorRect.left() + sizeWithoutShadow.width() >
screenGeometry.right()) {
x = screenGeometry.right() - sizeWithoutShadow.width() + 1;
}
if (x < screenGeometry.left()) {
x = screenGeometry.left();
}
if (y + sizeWithoutShadow.height() > screenGeometry.bottom()) {
if (y > screenGeometry.bottom()) {
y = screenGeometry.bottom() - sizeWithoutShadow.height() - 40;
} else { /* better position the window */
y = y - sizeWithoutShadow.height() -
((cursorRect.height() == 0) ? 40 : cursorRect.height());
}
}
if (y < screenGeometry.top()) {
y = screenGeometry.top();
}
QPoint newPosition(x, y);
newPosition -=
QPoint(theme_->shadowMargin().left(), theme_->shadowMargin().top());
if (newPosition != position()) {
if (isWayland_ && isVisible()) {
hide();
}
setPosition(newPosition);
}
show();
}
void FcitxCandidateWindow::mouseMoveEvent(QMouseEvent *event) {
bool needRepaint = false;
bool prevHovered = false;
bool nextHovered = false;
auto oldHighlight = highlight();
hoverIndex_ = -1;
prevHovered = prevRegion_.contains(event->pos());
if (!prevHovered) {
nextHovered = nextRegion_.contains(event->pos());
if (!nextHovered) {
for (int idx = 0, e = candidateRegions_.size(); idx < e; idx++) {
if (candidateRegions_[idx].contains(event->pos())) {
hoverIndex_ = idx;
break;
}
}
}
}
needRepaint = needRepaint || prevHovered_ != prevHovered;
prevHovered_ = prevHovered;
needRepaint = needRepaint || nextHovered_ != nextHovered;
nextHovered_ = nextHovered;
needRepaint = needRepaint || oldHighlight != highlight();
if (needRepaint) {
requestUpdate();
}
}
void FcitxCandidateWindow::mouseReleaseEvent(QMouseEvent *event) {
if (event->button() != Qt::LeftButton) {
return;
}
if (prevRegion_.contains(event->pos())) {
Q_EMIT prevClicked();
return;
}
if (nextRegion_.contains(event->pos())) {
Q_EMIT nextClicked();
return;
}
for (int idx = 0, e = candidateRegions_.size(); idx < e; idx++) {
if (candidateRegions_[idx].contains(event->pos())) {
Q_EMIT candidateSelected(idx);
return;
}
}
}
QSize FcitxCandidateWindow::sizeHint() {
auto minH =
theme_->fontMetrics().ascent() + theme_->fontMetrics().descent();
size_t width = 0;
size_t height = 0;
auto updateIfLarger = [](size_t &m, size_t n) {
if (n > m) {
m = n;
}
};
auto textMargin = theme_->textMargin();
auto extraW = textMargin.left() + textMargin.right();
auto extraH = textMargin.top() + textMargin.bottom();
if (!upperLayout_.text().isEmpty()) {
auto size = upperLayout_.boundingRect();
height += minH + extraH;
updateIfLarger(width, size.width() + extraW);
}
if (!lowerLayout_.text().isEmpty()) {
auto size = lowerLayout_.boundingRect();
height += minH + extraH;
updateIfLarger(width, size.width() + extraW);
}
bool vertical = theme_->vertical();
if (layoutHint_ == FcitxCandidateLayoutHint::Vertical) {
vertical = true;
} else if (layoutHint_ == FcitxCandidateLayoutHint::Horizontal) {
vertical = false;
}
size_t wholeH = 0, wholeW = 0;
for (size_t i = 0; i < labelLayouts_.size(); i++) {
size_t candidateW = 0, candidateH = 0;
if (!labelLayouts_[i]->isEmpty()) {
auto size = labelLayouts_[i]->boundingRect();
candidateW += size.width();
updateIfLarger(candidateH,
std::max(minH, qCeil(size.height())) + extraH);
}
if (!candidateLayouts_[i]->isEmpty()) {
auto size = candidateLayouts_[i]->boundingRect();
candidateW += size.width();
updateIfLarger(candidateH,
std::max(minH, qCeil(size.height())) + extraH);
}
candidateW += extraW;
if (vertical) {
wholeH += candidateH;
updateIfLarger(wholeW, candidateW);
} else {
wholeW += candidateW;
updateIfLarger(wholeH, candidateH);
}
}
updateIfLarger(width, wholeW);
candidatesHeight_ = wholeH;
height += wholeH;
auto contentMargin = theme_->contentMargin();
width += contentMargin.left() + contentMargin.right();
height += contentMargin.top() + contentMargin.bottom();
if (!labelLayouts_.empty() && (hasPrev_ || hasNext_)) {
if (theme_->prev().valid() && theme_->next().valid()) {
width += theme_->prev().width() + theme_->next().width();
}
}
return {static_cast<int>(width), static_cast<int>(height)};
}
void FcitxCandidateWindow::wheelEvent(QWheelEvent *event) {
if (!theme_ || !theme_->wheelForPaging()) {
return;
}
accAngle_ += event->angleDelta().y();
auto angleForClick = 120;
while (accAngle_ >= angleForClick) {
accAngle_ -= angleForClick;
Q_EMIT prevClicked();
}
while (accAngle_ <= -angleForClick) {
accAngle_ += angleForClick;
Q_EMIT nextClicked();
}
}
void FcitxCandidateWindow::paintEvent(QPaintEvent *) {
QPainter p(this);
render(&p);
}
} // namespace fcitx