From 828a4571a0ed5a0632c20f767e3d8c156fd58a28 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Wed, 15 Jul 2026 19:15:09 +0900 Subject: [PATCH] Fix remaining color mismatches in Qt renderer As the first step towards polishing the Qt renderer to match the Win32 renderer (#1550), this commit works on the remaining color mismatches in Qt-based mozc_renderer windows. With this commit, window border and footer color gradient start being rendered as defined in renderer_style.textproto. --- src/renderer/qt/qt_window_manager.cc | 77 +++++++++++++++++++++++----- src/renderer/qt/qt_window_manager.h | 38 ++++++++++++-- 2 files changed, 100 insertions(+), 15 deletions(-) diff --git a/src/renderer/qt/qt_window_manager.cc b/src/renderer/qt/qt_window_manager.cc index ee7808106d..e74fc58384 100644 --- a/src/renderer/qt/qt_window_manager.cc +++ b/src/renderer/qt/qt_window_manager.cc @@ -33,6 +33,7 @@ #include #include #include +#include #include "absl/log/check.h" #include "absl/log/log.h" @@ -42,6 +43,7 @@ #include "client/client_interface.h" #include "protocol/candidate_window.pb.h" #include "protocol/commands.pb.h" +#include "protocol/renderer_style.pb.h" #include "renderer/renderer_style_handler.h" #include "renderer/window_util.h" @@ -71,6 +73,30 @@ QBrush QBrushFromColor(const RendererStyle::RGBAColor& rgba) { } // namespace +void FooterBackgroundDelegate::paint(QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const { + // The footer is the last row of the candidate window. + if (index.row() == index.model()->rowCount() - 1) { + QRect rect = option.rect; + + // Separator lines between the main content area and the footer. + for (int i = 0; i < separator_colors_.size(); ++i) { + painter->fillRect(QRect(rect.left(), rect.top() + i, rect.width(), 1), + separator_colors_[i]); + } + rect.adjust(0, separator_colors_.size(), 0, 0); + + // The gradient is anchored to the remaining cell rectangle, which spans + // the full height of the footer row. + QLinearGradient gradient(rect.topLeft(), rect.bottomLeft()); + gradient.setColorAt(0, top_); + gradient.setColorAt(1, bottom_); + painter->fillRect(rect, gradient); + } + QStyledItemDelegate::paint(painter, option, index); +} + QtWindowManager::QtWindowManager() { RendererStyleHandler::GetRendererStyle(&style_); } @@ -113,6 +139,8 @@ void QtWindowManager::Initialize() { candidates_ = new QTableWidget(); initialize_table(candidates_); + footer_delegate_ = new FooterBackgroundDelegate(candidates_); + candidates_->setItemDelegate(footer_delegate_); QObject::connect(candidates_, &QTableWidget::cellClicked, [&](int row, int col) { OnClicked(row, col); }); @@ -130,17 +158,39 @@ void QtWindowManager::ApplyStyleToWidgets() { QColorFromColor(style_.candidate_style().background_color()); const QColor foreground = QColorFromColor(style_.candidate_style().foreground_color()); + const QColor border = QColorFromColor(style_.border_color()); + + // By default the QTableWidget frame and viewport are drawn by the widget + // style from the application palette, which does not necessarily match the + // renderer style. Override them with a style sheet. Note that these colors + // are deliberately not applied via QWidget::setPalette: palette changes are + // not reliably honored on widgets that have a style sheet, which would leave + // stale colors behind when the theme changes at runtime. + const QString sheet = QString( + "QTableWidget { border: %1px solid %2;" + " background-color: %3; color: %4; }") + .arg(style_.window_border()) + .arg(border.name()) + .arg(background.name()) + .arg(foreground.name()); for (QTableWidget* table : {candidates_, infolist_}) { if (table == nullptr) { continue; } - QPalette palette = table->palette(); - palette.setColor(QPalette::Base, background); - palette.setColor(QPalette::Window, background); - palette.setColor(QPalette::Text, foreground); - palette.setColor(QPalette::WindowText, foreground); - table->setPalette(palette); + table->setStyleSheet(sheet); + } + + if (footer_delegate_ != nullptr) { + footer_delegate_->SetGradientColors( + QColorFromColor(style_.footer_top_color()), + QColorFromColor(style_.footer_bottom_color())); + QList separator_colors; + for (const RendererStyle::RGBAColor& color : + style_.footer_border_colors()) { + separator_colors.append(QColorFromColor(color)); + } + footer_delegate_->SetSeparatorColors(std::move(separator_colors)); } } @@ -325,8 +375,6 @@ void FillCandidateWindow(const commands::CandidateWindow& candidate_window, QBrushFromColor(style.candidate_style().foreground_color()); const QBrush description_brush = QBrushFromColor(style.description_style().foreground_color()); - const QBrush footer_bg_brush = QBrushFromColor(style.footer_bottom_color()); - // Fill the candidates std::string shortcut, value, description; for (size_t i = 0; i < cands_size; ++i) { @@ -362,17 +410,22 @@ void FillCandidateWindow(const commands::CandidateWindow& candidate_window, total_height += height; } - // Footer + // Footer. The background is painted by FooterBackgroundDelegate. for (int i = 0; i < table->columnCount(); ++i) { auto footer_item = new QTableWidgetItem(); - footer_item->setBackground(footer_bg_brush); table->setItem(cands_size, i, footer_item); } QTableWidgetItem* footer2 = table->item(cands_size, 2); footer2->setText(QStr(GetIndexGuideString(candidate_window))); - footer2->setTextAlignment(Qt::AlignRight); + footer2->setForeground( + QBrushFromColor(style.footer_style().foreground_color())); + footer2->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); max_width2 = std::max(max_width2, GetItemWidth(*footer2)); - const int footer_height = GetItemHeight(*footer2); + // The separator lines drawn by FooterBackgroundDelegate consume the top + // pixels of the footer cell; enlarge the row so that the visible footer + // content area keeps its height. + const int footer_height = + GetItemHeight(*footer2) + style.footer_border_colors_size(); table->setRowHeight(cands_size, footer_height); total_height += footer_height; diff --git a/src/renderer/qt/qt_window_manager.h b/src/renderer/qt/qt_window_manager.h index b990d9af0f..5ba2672bf8 100644 --- a/src/renderer/qt/qt_window_manager.h +++ b/src/renderer/qt/qt_window_manager.h @@ -31,6 +31,7 @@ #define MOZC_RENDERER_QT_QT_WINDOW_MANAGER_H_ #include +#include #include "base/coordinates.h" #include "client/client_interface.h" @@ -40,6 +41,33 @@ namespace mozc { namespace renderer { +// The gradient needs to be drawn by the delegate rather than set as the items' +// background brush, because gradient QBrush coordinate mapping is not reliable +// in the QTableWidget cell-background path. +class FooterBackgroundDelegate : public QStyledItemDelegate { + public: + explicit FooterBackgroundDelegate(QObject* parent = nullptr) + : QStyledItemDelegate(parent) {} + + void SetGradientColors(const QColor& top, const QColor& bottom) { + top_ = top; + bottom_ = bottom; + } + + // One 1-pixel separator line is drawn per color, from top to bottom. + void SetSeparatorColors(QList colors) { + separator_colors_ = std::move(colors); + } + + void paint(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const override; + + private: + QColor top_; + QColor bottom_; + QList separator_colors_; +}; + class QtWindowManager { public: QtWindowManager(); @@ -83,15 +111,19 @@ class QtWindowManager { void OnClicked(int row, int column); - // Applies the foreground / background colors of the current style_ to the - // candidate and infolist widgets' palettes, so that areas not covered by - // individual cells (e.g. the viewport) also follow the theme. + // Applies the colors of the current style_ to the candidate and infolist + // widgets via a style sheet, so that areas not covered by individual cells + // (e.g. the window border and the viewport) also follow the style. void ApplyStyleToWidgets(); private: QTableWidget *candidates_ = nullptr; QTableWidget *infolist_ = nullptr; + // Does not have the ownership. `footer_delegate_` is a child QObject of + // `candidates_`, which is responsible for destroying it. + FooterBackgroundDelegate* footer_delegate_ = nullptr; + RendererStyle style_; commands::RendererCommand prev_command_; client::SendCommandInterface *send_command_interface_ = nullptr;