View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0017922 | Scribus | User Interface | public | 2026-08-14 02:41 | 2026-08-16 07:46 |
| Reporter | qirat | Assigned To | |||
| Priority | normal | Severity | feature | Reproducibility | N/A |
| Status | new | Resolution | open | ||
| Platform | Linux | OS | Fedora Workstation | OS Version | 44 |
| Product Version | 1.7.4.svn | ||||
| Summary | 0017922: [PATCH] Preview paragraph/character styles with their own font and colour in PP combos | ||||
| Description | ParaStyleComboBox/CharStyleComboBox go from plain name lists to per-row previews. * styleComboPreviewFont() — builds each row's QFont from the style's own family/bold/italic/underline/strikethrough/caps, via Qt::FontRole. Italic checks both ScFace::isItalic() and the face's style-string (catches Scribus's faked-slant fonts too). * styleComboResolveColor() — resolves font/background color+shade via ScColorEngine::getDisplayColor(), Qt::ForegroundRole/BackgroundRole. Paragraph rows prefer the paragraph's own background over the character style's. * styleComboPreviewGlyph() — Character Style only. 14×14 glyph via QFontDatabase::writingSystems(); non-Latin scripts get their own sample char, Latin (and Greek/Cyrillic/Vietnamese/Symbol, excluded as unreliable secondary-script noise) get a fixed "A". All wired through one shared styleComboSetPreviewData(), called per-item instead of the old bulk addItems(). Read-only display logic — no document model or file-format changes. | ||||
| Additional Information | Written against r27771. It is a small patch to my surprise. (AI helped a lot.) | ||||
| Tags | No tags attached. | ||||
| Attached Files | r27771-p1-stylecombo-font-preview-v1.06.patch (9,748 bytes)
Index: scribus/ui/stylecombos.cpp
===================================================================
--- scribus/ui/stylecombos.cpp (revision 27771)
+++ scribus/ui/stylecombos.cpp (working copy)
@@ -22,14 +22,161 @@
***************************************************************************/
#include "commonstrings.h"
+#include "sccolorengine.h"
#include "scribusdoc.h"
#include "stylecombos.h"
+#include "fonts/scface.h"
+#include <QFont>
+#include <QFontDatabase>
#include <QListView>
+#include <QPainter>
+#include <QPixmap>
#include "scpage.h"
#include "util.h"
+namespace {
+// Builds a QFont that reflects a style's own character formatting, so a
+// combo row previews roughly what the style looks like instead of just
+// naming it. Deliberately partial: point size is left at the combo's own
+// size (a heading-sized style shouldn't grow its own row, or the whole
+// list); stroke and shadow effects have no equivalent in QFont or Qt's
+// item-data roles and would need custom painting to show at all.
+QFont styleComboPreviewFont(const CharStyle& cstyle)
+{
+ const ScFace& face = cstyle.font();
+ QFont font;
+ font.setFamily(face.family());
+ font.setBold(face.isBold());
+ // A font with no real italic file can still be marked italic via its
+ // Style field (e.g. "EB Garamond" + "Italic"), which Scribus renders as
+ // a faked slant rather than the font's own design. isItalic() alone
+ // only catches the case where the chosen face itself is truly italic,
+ // so check the style name too - either source counts as "wants italic"
+ // for preview purposes, matching what actually renders on canvas.
+ bool italicByStyleName = face.style().contains(QLatin1String("Italic"), Qt::CaseInsensitive)
+ || face.style().contains(QLatin1String("Oblique"), Qt::CaseInsensitive);
+ font.setItalic(face.isItalic() || italicByStyleName);
+ font.setUnderline(cstyle.features().contains(CharStyle::UNDERLINE));
+ font.setStrikeOut(cstyle.features().contains(CharStyle::STRIKETHROUGH));
+ if (cstyle.features().contains(CharStyle::ALLCAPS))
+ font.setCapitalization(QFont::AllUppercase);
+ else if (cstyle.features().contains(CharStyle::SMALLCAPS))
+ font.setCapitalization(QFont::SmallCaps);
+ return font;
+}
+
+// Resolves a named document color (as stored on a style) to a QColor ready
+// for display, at the given shade - "None" or a name no longer in the
+// document's color list both mean "nothing to show", not black.
+QColor styleComboResolveColor(const QString& colorName, double shade, const ScribusDoc* doc)
+{
+ if (colorName.isEmpty() || colorName == CommonStrings::None || !doc->PageColors.contains(colorName))
+ return QColor();
+ return ScColorEngine::getDisplayColor(doc->PageColors[colorName], doc, shade);
+}
+
+// A single glyph identifying the row's own font, sized to match the
+// paragraph-style badges used elsewhere in Style Scope. Checks the font's
+// full list of supported writing systems (not just its single primary
+// one, which can be Latin even for a font whose real purpose is another
+// script - several "Noto Sans <Script>" fonts are like this) and prefers
+// the first genuinely non-Latin one found there. A few systems are
+// excluded even when present: Vietnamese and Symbol commonly turn up as
+// incidental secondary coverage on an ordinary Latin font (extended accent
+// support, a handful of dingbats) rather than a real second script; Greek
+// and Cyrillic are excluded for a different reason - several well-known
+// Latin serif families (Garamond-derived ones among them) deliberately
+// ship a matching classical Greek design alongside their Latin one, which
+// is a real script but not the one worth showing here. None of these
+// exclusions are reversible from the data Qt exposes without misreading
+// genuinely Greek/Cyrillic/Vietnamese-purposed fonts as Latin instead, so
+// this is a practical trade-off, not a perfect classification method. When
+// a non-excluded system is found, the glyph is Qt's own representative
+// sample character for it - a font's family name is often Latin-
+// transliterated and gives no visual sense of the script itself, so this
+// fills that gap. Otherwise, the glyph is a fixed "A": the point of this
+// preview is comparing a font's shape against others at a glance, which
+// needs the same letter every time, not a different one per style name.
+QIcon styleComboPreviewGlyph(const QFont& font)
+{
+ QString glyph = QStringLiteral("A");
+ const QList<QFontDatabase::WritingSystem> systems = QFontDatabase::writingSystems(font.family());
+ for (QFontDatabase::WritingSystem system : systems)
+ {
+ if (system == QFontDatabase::Latin || system == QFontDatabase::Any
+ || system == QFontDatabase::Vietnamese || system == QFontDatabase::Symbol
+ || system == QFontDatabase::Greek || system == QFontDatabase::Cyrillic)
+ continue;
+ QString sample = QFontDatabase::writingSystemSample(system);
+ if (!sample.isEmpty())
+ {
+ glyph = sample.left(1);
+ break;
+ }
+ }
+
+ // Family only, at neutral weight/slant - the glyph identifies the font,
+ // it isn't meant to also carry the style's own bold/italic/etc (the row
+ // text itself already does that).
+ QFont glyphFont(font.family());
+
+ const int size = 14;
+ QPixmap pixmap(size, size);
+ pixmap.fill(Qt::transparent);
+ QPainter painter(&pixmap);
+ painter.setFont(glyphFont);
+ painter.drawText(QRect(0, 0, size, size), Qt::AlignCenter, glyph);
+ painter.end();
+
+ // Qt auto-generates a dimmed "Selected" variant of an icon unless both
+ // modes are given the same pixmap explicitly - without this, the glyph
+ // would wash out on a selected row.
+ QIcon icon;
+ icon.addPixmap(pixmap, QIcon::Normal);
+ icon.addPixmap(pixmap, QIcon::Selected);
+ return icon;
+}
+
+// Applies a style's preview (font, colors, and - for the character style
+// combo only - a per-row glyph) to one combo item. showPreviewGlyph is
+// false for the paragraph style combo: a plain list reads better there,
+// since paragraph styles are usually distinguished by more than font
+// alone, and every row already carries the font/color preview regardless.
+void styleComboSetPreviewData(QComboBox* combo, int itemIndex, const CharStyle& cstyle, const ScribusDoc* doc,
+ const QString& paraBackColorName = QString(), double paraBackShade = 100.0,
+ bool showPreviewGlyph = false)
+{
+ QFont font = styleComboPreviewFont(cstyle);
+ combo->setItemData(itemIndex, font, Qt::FontRole);
+
+ if (showPreviewGlyph)
+ {
+ QIcon glyph = styleComboPreviewGlyph(font);
+ if (!glyph.isNull())
+ combo->setItemData(itemIndex, glyph, Qt::DecorationRole);
+ }
+
+ QColor fg = styleComboResolveColor(cstyle.fillColor(), cstyle.fillShade(), doc);
+ if (fg.isValid())
+ combo->setItemData(itemIndex, fg, Qt::ForegroundRole);
+
+ // A paragraph's own background (set on the Properties tab, shading the
+ // whole paragraph) takes priority over its default character style's
+ // background here - the two are layered separately on canvas, but a
+ // combo row only has room for one swatch, and the paragraph-wide one is
+ // the more paragraph-style-appropriate signal. Character style rows
+ // never pass paraBackColorName, so they always fall through to their
+ // own backColor.
+ QColor bg = styleComboResolveColor(paraBackColorName, paraBackShade, doc);
+ if (!bg.isValid())
+ bg = styleComboResolveColor(cstyle.backColor(), cstyle.backShade(), doc);
+ if (bg.isValid())
+ combo->setItemData(itemIndex, bg, Qt::BackgroundRole);
+}
+}
+
ParaStyleComboBox::ParaStyleComboBox(QWidget* parent) : QComboBox(parent)
{
setEditable(false);
@@ -124,9 +271,15 @@
clear();
if (m_doc != nullptr)
{
- QStringList st;
addItem( firstItemString() );
+
+ int defaultIndex = m_doc->paragraphStyles().find(CommonStrings::DefaultParagraphStyle);
addItem( CommonStrings::trDefaultParagraphStyle );
+ if (defaultIndex >= 0)
+ styleComboSetPreviewData(this, count() - 1, m_doc->paragraphStyles()[defaultIndex].charStyle(), m_doc,
+ m_doc->paragraphStyles()[defaultIndex].backgroundColor(), m_doc->paragraphStyles()[defaultIndex].backgroundShade());
+
+ QStringList st;
for (int i = 0; i < m_doc->paragraphStyles().count(); ++i)
{
const ParagraphStyle& paraStyle = m_doc->paragraphStyles()[i];
@@ -134,7 +287,14 @@
st.append(paraStyle.name());
}
st.sort();
- addItems(st);
+ for (const QString& styleName : st)
+ {
+ addItem(styleName);
+ int idx = m_doc->paragraphStyles().find(styleName);
+ if (idx >= 0)
+ styleComboSetPreviewData(this, count() - 1, m_doc->paragraphStyles()[idx].charStyle(), m_doc,
+ m_doc->paragraphStyles()[idx].backgroundColor(), m_doc->paragraphStyles()[idx].backgroundShade());
+ }
if (oldStyleName.length() > 0)
{
@@ -261,9 +421,14 @@
clear();
if (m_doc != nullptr)
{
- QStringList st;
addItem( firstItemString() );
+
+ int defaultIndex = m_doc->charStyles().find(CommonStrings::DefaultCharacterStyle);
addItem( CommonStrings::trDefaultCharacterStyle );
+ if (defaultIndex >= 0)
+ styleComboSetPreviewData(this, count() - 1, m_doc->charStyles()[defaultIndex], m_doc, QString(), 100.0, true);
+
+ QStringList st;
for (int i = 0; i < m_doc->charStyles().count(); ++i)
{
const CharStyle& charStyle = m_doc->charStyles()[i];
@@ -271,7 +436,13 @@
st.append(charStyle.name());
}
st.sort();
- addItems(st);
+ for (const QString& styleName : st)
+ {
+ addItem(styleName);
+ int idx = m_doc->charStyles().find(styleName);
+ if (idx >= 0)
+ styleComboSetPreviewData(this, count() - 1, m_doc->charStyles()[idx], m_doc, QString(), 100.0, true);
+ }
if (oldStyleName.length() > 0)
{
| ||||
| Patch | Yes | ||||
|
|
Preview of what it does. |
|
|
It can do more: the font/BG color in right shade, Small/All-caps etc. Para Style's BG color too. |
|
|
|
|
|
Not my cup of tea, but: that won't probably work for dark themes... At first, I thought about the need of a setting, if people want a more calm UI, but then I realized that people who want a calm UI, are also people who will refrain from going crazy with the colors and fonts of their styles : - ) Mixed feelings about this... But I admit that I tend to apply the styles by name, not by look. |
|
|
Yes, I agree @ale. I am not big on colours either. I just exaggerated things to show the example, what it can do. Also, I have no issue with a little preview of the style showing in the combo. I would like that. (the first two screenshots). And, thanks a lot for you valuable comment. Look forward to hear more from you on other patches as well. When you find time, of course. |
|
|
Fixed, show as per theme. stylecombo-font-preview-v1.15.patch (18,887 bytes)
Index: scribus/ui/stylecombos.h
===================================================================
--- scribus/ui/stylecombos.h (revision 27773)
+++ scribus/ui/stylecombos.h (working copy)
@@ -29,6 +29,7 @@
#include "scribusapi.h"
class QEvent;
+class QPaintEvent;
class ScribusDoc;
/**
@@ -95,6 +96,7 @@
void changeEvent(QEvent *e) override;
void languageChange();
+ void paintEvent(QPaintEvent *e) override;
protected slots:
void selectedStyle(int e);
Index: scribus/ui/stylecombos.cpp
===================================================================
--- scribus/ui/stylecombos.cpp (revision 27773)
+++ scribus/ui/stylecombos.cpp (working copy)
@@ -22,14 +22,292 @@
***************************************************************************/
#include "commonstrings.h"
+#include "sccolorengine.h"
#include "scribusdoc.h"
#include "stylecombos.h"
+#include "fonts/scface.h"
+#include <QApplication>
+#include <QFont>
+#include <QFontDatabase>
+#include <QFontMetrics>
#include <QListView>
+#include <QPainter>
+#include <QPixmap>
+#include <QStyleOptionComboBox>
+#include <QStyledItemDelegate>
#include "scpage.h"
#include "util.h"
+namespace {
+// Custom item-data roles used only by the character-style combo's glyph
+// preview - see StyleComboGlyphDelegate.
+constexpr int StyleComboGlyphCharRole = Qt::UserRole + 1;
+
+// Builds a QFont that reflects a style's own character formatting, so a
+// combo row previews roughly what the style looks like instead of just
+// naming it. Deliberately partial: point size is left at the combo's own
+// size (a heading-sized style shouldn't grow its own row, or the whole
+// list); stroke and shadow effects have no equivalent in QFont or Qt's
+// item-data roles and would need custom painting to show at all.
+QFont styleComboPreviewFont(const CharStyle& cstyle)
+{
+ const ScFace& face = cstyle.font();
+ QFont font;
+ font.setFamily(face.family());
+ font.setBold(face.isBold());
+ // A font with no real italic file can still be marked italic via its
+ // Style field (e.g. "EB Garamond" + "Italic"), which Scribus renders as
+ // a faked slant rather than the font's own design. isItalic() alone
+ // only catches the case where the chosen face itself is truly italic,
+ // so check the style name too - either source counts as "wants italic"
+ // for preview purposes, matching what actually renders on canvas.
+ bool italicByStyleName = face.style().contains(QLatin1String("Italic"), Qt::CaseInsensitive)
+ || face.style().contains(QLatin1String("Oblique"), Qt::CaseInsensitive);
+ font.setItalic(face.isItalic() || italicByStyleName);
+ font.setUnderline(cstyle.features().contains(CharStyle::UNDERLINE));
+ font.setStrikeOut(cstyle.features().contains(CharStyle::STRIKETHROUGH));
+ if (cstyle.features().contains(CharStyle::ALLCAPS))
+ font.setCapitalization(QFont::AllUppercase);
+ else if (cstyle.features().contains(CharStyle::SMALLCAPS))
+ font.setCapitalization(QFont::SmallCaps);
+ return font;
+}
+
+// Resolves a named document color (as stored on a style) to a QColor ready
+// for display, at the given shade - "None" or a name no longer in the
+// document's color list both mean "nothing to show", not black.
+QColor styleComboResolveColor(const QString& colorName, double shade, const ScribusDoc* doc)
+{
+ if (colorName.isEmpty() || colorName == CommonStrings::None || !doc->PageColors.contains(colorName))
+ return QColor();
+ return ScColorEngine::getDisplayColor(doc->PageColors[colorName], doc, shade);
+}
+
+// Quick perceived-brightness estimate (0..255), good enough for a contrast
+// check - not a color-managed luminance calculation.
+double styleComboPerceivedBrightness(const QColor& c)
+{
+ return 0.299 * c.red() + 0.587 * c.green() + 0.114 * c.blue();
+}
+
+// A style's own ink color is shown as-is whenever it reads fine - a black
+// heading style should still look black. It only becomes a problem where
+// the row it lands on doesn't share the document's white-paper assumption,
+// namely the app's own (possibly dark-themed) list background behind rows
+// with no explicit paragraph/character background of their own. There,
+// black-on-near-black (or the light-theme mirror, a pale style on white)
+// can go effectively invisible. Rather than hide the color or replace it
+// with a generic default, step its HSL lightness away from the background
+// in small increments, stopping as soon as contrast is enough - not
+// jumping straight to a fixed near-white/near-black lightness, which
+// would wash any saturated hue (magenta, blue) out to where it's barely
+// distinguishable from any other adjusted color. Never touches the
+// style's actual stored color - this is preview-only.
+QColor styleComboEnsureContrast(const QColor& fg, const QColor& bg)
+{
+ if (!fg.isValid() || !bg.isValid())
+ return fg;
+
+ const double minBrightnessDiff = 110.0;
+ const double bgBrightness = styleComboPerceivedBrightness(bg);
+ if (qAbs(styleComboPerceivedBrightness(fg) - bgBrightness) >= minBrightnessDiff)
+ return fg;
+
+ int h, s, l, a;
+ fg.getHsl(&h, &s, &l, &a);
+ const bool lighten = bgBrightness <= 128.0;
+
+ QColor adjusted = fg;
+ for (int step = l; lighten ? step <= 255 : step >= 0; step += lighten ? 5 : -5)
+ {
+ QColor candidate;
+ candidate.setHsl(h, s, step, a);
+ adjusted = candidate;
+ if (qAbs(styleComboPerceivedBrightness(candidate) - bgBrightness) >= minBrightnessDiff)
+ break;
+ }
+ return adjusted;
+}
+
+// A single glyph identifying the row's own font, sized to match the
+// paragraph-style badges used elsewhere in Style Scope. Checks the font's
+// full list of supported writing systems (not just its single primary
+// one, which can be Latin even for a font whose real purpose is another
+// script - several "Noto Sans <Script>" fonts are like this) and prefers
+// the first genuinely non-Latin one found there. A few systems are
+// excluded even when present: Vietnamese and Symbol commonly turn up as
+// incidental secondary coverage on an ordinary Latin font (extended accent
+// support, a handful of dingbats) rather than a real second script; Greek
+// and Cyrillic are excluded for a different reason - several well-known
+// Latin serif families (Garamond-derived ones among them) deliberately
+// ship a matching classical Greek design alongside their Latin one, which
+// is a real script but not the one worth showing here. None of these
+// exclusions are reversible from the data Qt exposes without misreading
+// genuinely Greek/Cyrillic/Vietnamese-purposed fonts as Latin instead, so
+// this is a practical trade-off, not a perfect classification method. When
+// a non-excluded system is found, the glyph is Qt's own representative
+// sample character for it - a font's family name is often Latin-
+// transliterated and gives no visual sense of the script itself, so this
+// fills that gap. Otherwise, the glyph is a fixed "A": the point of this
+// preview is comparing a font's shape against others at a glance, which
+// needs the same letter every time, not a different one per style name.
+// Returns just the character - StyleComboGlyphDelegate does the actual
+// painting, at paint time, so it can always use the palette that's
+// current right then rather than one baked in at populate time.
+QString styleComboPreviewGlyphChar(const QFont& font)
+{
+ QString glyph = QStringLiteral("A");
+ const QList<QFontDatabase::WritingSystem> systems = QFontDatabase::writingSystems(font.family());
+ for (QFontDatabase::WritingSystem system : systems)
+ {
+ if (system == QFontDatabase::Latin || system == QFontDatabase::Any
+ || system == QFontDatabase::Vietnamese || system == QFontDatabase::Symbol
+ || system == QFontDatabase::Greek || system == QFontDatabase::Cyrillic)
+ continue;
+ QString sample = QFontDatabase::writingSystemSample(system);
+ if (!sample.isEmpty())
+ {
+ glyph = sample.left(1);
+ break;
+ }
+ }
+ return glyph;
+}
+
+// Family only, at neutral weight/slant - the glyph identifies the font,
+// it isn't meant to also carry the style's own bold/italic/etc (the row
+// text itself already does that). Kept at its natural size, only shrunk
+// if that would overflow the space available to it.
+QFont styleComboFittedGlyphFont(const QString& family, int availableHeight)
+{
+ QFont font(family);
+ if (QFontMetrics(font).height() > availableHeight)
+ font.setPixelSize(qMax(8, availableHeight - 2));
+ return font;
+}
+
+// Paints the character-style combo's font-identifying glyph directly, on
+// every row paint, rather than baking a colored pixmap once at populate
+// time. The row's font/color/background preview data uses plain
+// Qt::FontRole/ForegroundRole/BackgroundRole values, which Qt itself
+// re-resolves fresh on every paint - this delegate gives the glyph that
+// same always-current guarantee. Wraps the combo's own popup delegate
+// (rather than replacing it) so the native row painting and sizing -
+// including spacing Qt's combo-specific delegate applies that a plain
+// QStyledItemDelegate does not - stays intact; the glyph is drawn as an
+// addition on top of that, not a substitute for it.
+class StyleComboGlyphDelegate : public QStyledItemDelegate
+{
+public:
+ StyleComboGlyphDelegate(QAbstractItemDelegate* baseDelegate, QObject* parent)
+ : QStyledItemDelegate(parent), m_baseDelegate(baseDelegate)
+ {
+ }
+
+ void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override
+ {
+ if (m_baseDelegate)
+ m_baseDelegate->paint(painter, option, index);
+ else
+ QStyledItemDelegate::paint(painter, option, index);
+
+ const QString glyph = index.data(StyleComboGlyphCharRole).toString();
+ if (glyph.isEmpty())
+ return;
+
+ QStyleOptionViewItem opt(option);
+ initStyleOption(&opt, index);
+
+ QStyle* style = opt.widget ? opt.widget->style() : QApplication::style();
+ QRect glyphRect = style->subElementRect(QStyle::SE_ItemViewItemDecoration, &opt, opt.widget);
+ if (glyphRect.isEmpty())
+ return;
+
+ QFont glyphFont = styleComboFittedGlyphFont(index.data(Qt::FontRole).value<QFont>().family(), glyphRect.height());
+
+ const bool selected = opt.state & QStyle::State_Selected;
+ const QPalette::ColorGroup group = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled;
+
+ painter->save();
+ painter->setFont(glyphFont);
+ painter->setPen(opt.palette.color(group, selected ? QPalette::HighlightedText : QPalette::Text));
+ painter->drawText(glyphRect, Qt::AlignCenter, glyph);
+ painter->restore();
+ }
+
+ QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override
+ {
+ QSize hint = m_baseDelegate ? m_baseDelegate->sizeHint(option, index) : QStyledItemDelegate::sizeHint(option, index);
+
+ // Every row renders in its own preview font, but that font varying
+ // by family (not just point size) also varies its natural line
+ // metrics, which otherwise carries straight into the row's height -
+ // a family swap reads as uneven list spacing rather than as
+ // anything meaningful about the styles. Re-based here to the
+ // view's own font, the single height every row in an ordinary
+ // (non-previewing) style list already gets.
+ const QFont rowFont = index.data(Qt::FontRole).value<QFont>();
+ const QFont referenceFont = option.widget ? option.widget->font() : rowFont;
+ const int delta = QFontMetrics(referenceFont).height() - QFontMetrics(rowFont).height();
+ hint.setHeight(hint.height() + delta);
+ return hint;
+ }
+
+private:
+ QAbstractItemDelegate* m_baseDelegate;
+};
+
+// Applies a style's preview (font, colors, and - for the character style
+// combo only - a per-row glyph) to one combo item. showPreviewGlyph is
+// false for the paragraph style combo: a plain list reads better there,
+// since paragraph styles are usually distinguished by more than font
+// alone, and every row already carries the font/color preview regardless.
+void styleComboSetPreviewData(QComboBox* combo, int itemIndex, const CharStyle& cstyle, const ScribusDoc* doc,
+ const QString& paraBackColorName = QString(), double paraBackShade = 100.0,
+ bool showPreviewGlyph = false)
+{
+ QFont font = styleComboPreviewFont(cstyle);
+ combo->setItemData(itemIndex, font, Qt::FontRole);
+
+ if (showPreviewGlyph)
+ {
+ combo->setItemData(itemIndex, styleComboPreviewGlyphChar(font), StyleComboGlyphCharRole);
+
+ // A transparent placeholder only reserves the decoration column's
+ // layout space - StyleComboGlyphDelegate paints the real,
+ // currently-colored glyph into that space itself.
+ QPixmap placeholder(14, 14);
+ placeholder.fill(Qt::transparent);
+ combo->setItemData(itemIndex, QIcon(placeholder), Qt::DecorationRole);
+ }
+
+ // A paragraph's own background (set on the Properties tab, shading the
+ // whole paragraph) takes priority over its default character style's
+ // background here - the two are layered separately on canvas, but a
+ // combo row only has room for one swatch, and the paragraph-wide one is
+ // the more paragraph-style-appropriate signal. Character style rows
+ // never pass paraBackColorName, so they always fall through to their
+ // own backColor.
+ QColor bg = styleComboResolveColor(paraBackColorName, paraBackShade, doc);
+ if (!bg.isValid())
+ bg = styleComboResolveColor(cstyle.backColor(), cstyle.backShade(), doc);
+ if (bg.isValid())
+ combo->setItemData(itemIndex, bg, Qt::BackgroundRole);
+
+ // Resolved first so the foreground contrast check below has a real
+ // background to check against: the row's own background when set,
+ // otherwise the app's current (possibly dark-themed) list background.
+ QColor fg = styleComboResolveColor(cstyle.fillColor(), cstyle.fillShade(), doc);
+ if (fg.isValid())
+ {
+ QColor effectiveBg = bg.isValid() ? bg : combo->palette().color(QPalette::Base);
+ combo->setItemData(itemIndex, styleComboEnsureContrast(fg, effectiveBg), Qt::ForegroundRole);
+ }
+}
+}
+
ParaStyleComboBox::ParaStyleComboBox(QWidget* parent) : QComboBox(parent)
{
setEditable(false);
@@ -74,6 +352,13 @@
languageChange();
return;
}
+ if (e->type() == QEvent::PaletteChange || e->type() == QEvent::ApplicationPaletteChange)
+ {
+ // Re-derives the font/color preview data (including the glyph
+ // icon) against the now-current palette, queued so it runs after
+ // the posted ApplicationPaletteChange event has actually landed.
+ QMetaObject::invokeMethod(this, [this]() { updateStyleList(); }, Qt::QueuedConnection);
+ }
QComboBox::changeEvent(e);
}
@@ -124,9 +409,15 @@
clear();
if (m_doc != nullptr)
{
- QStringList st;
addItem( firstItemString() );
+
+ int defaultIndex = m_doc->paragraphStyles().find(CommonStrings::DefaultParagraphStyle);
addItem( CommonStrings::trDefaultParagraphStyle );
+ if (defaultIndex >= 0)
+ styleComboSetPreviewData(this, count() - 1, m_doc->paragraphStyles()[defaultIndex].charStyle(), m_doc,
+ m_doc->paragraphStyles()[defaultIndex].backgroundColor(), m_doc->paragraphStyles()[defaultIndex].backgroundShade());
+
+ QStringList st;
for (int i = 0; i < m_doc->paragraphStyles().count(); ++i)
{
const ParagraphStyle& paraStyle = m_doc->paragraphStyles()[i];
@@ -134,7 +425,14 @@
st.append(paraStyle.name());
}
st.sort();
- addItems(st);
+ for (const QString& styleName : st)
+ {
+ addItem(styleName);
+ int idx = m_doc->paragraphStyles().find(styleName);
+ if (idx >= 0)
+ styleComboSetPreviewData(this, count() - 1, m_doc->paragraphStyles()[idx].charStyle(), m_doc,
+ m_doc->paragraphStyles()[idx].backgroundColor(), m_doc->paragraphStyles()[idx].backgroundShade());
+ }
if (oldStyleName.length() > 0)
{
@@ -172,6 +470,10 @@
setEditable(false);
m_defaultStyle = CommonStrings::DefaultCharacterStyle;
+ // Paints the font-identifying glyph fresh on every row paint - see
+ // StyleComboGlyphDelegate.
+ view()->setItemDelegate(new StyleComboGlyphDelegate(view()->itemDelegate(), this));
+
addItem( firstItemString() );
addItem( CommonStrings::trDefaultCharacterStyle );
connect(this, SIGNAL(activated(int)), this, SLOT(selectedStyle(int)));
@@ -211,6 +513,11 @@
languageChange();
return;
}
+ if (e->type() == QEvent::PaletteChange || e->type() == QEvent::ApplicationPaletteChange)
+ {
+ // See ParaStyleComboBox::changeEvent() for why this is queued.
+ QMetaObject::invokeMethod(this, [this]() { updateStyleList(); }, Qt::QueuedConnection);
+ }
QComboBox::changeEvent(e);
}
@@ -226,6 +533,33 @@
this->blockSignals(sigBlocked);
}
+// The popup list's glyph is painted by StyleComboGlyphDelegate, which only
+// covers the popup - the closed box's own current-selection display is
+// drawn by QComboBox's own paintEvent() instead, reading Qt::DecorationRole
+// directly. That role is a transparent placeholder (see
+// styleComboSetPreviewData()), so without this override the closed box
+// would just show the empty reserved space and no glyph.
+void CharStyleComboBox::paintEvent(QPaintEvent *e)
+{
+ QComboBox::paintEvent(e);
+
+ const QString glyph = itemData(currentIndex(), StyleComboGlyphCharRole).toString();
+ if (glyph.isEmpty())
+ return;
+
+ QStyleOptionComboBox opt;
+ initStyleOption(&opt);
+ QRect editRect = style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this);
+ QRect glyphRect(editRect.left(), editRect.top(), iconSize().width(), editRect.height());
+
+ QFont glyphFont = styleComboFittedGlyphFont(itemData(currentIndex(), Qt::FontRole).value<QFont>().family(), glyphRect.height());
+
+ QPainter painter(this);
+ painter.setFont(glyphFont);
+ painter.setPen(palette().color(isEnabled() ? QPalette::Active : QPalette::Disabled, QPalette::Text));
+ painter.drawText(glyphRect, Qt::AlignCenter, glyph);
+}
+
void CharStyleComboBox::setDoc(ScribusDoc *newCurrentDoc)
{
m_doc = newCurrentDoc;
@@ -261,9 +595,14 @@
clear();
if (m_doc != nullptr)
{
- QStringList st;
addItem( firstItemString() );
+
+ int defaultIndex = m_doc->charStyles().find(CommonStrings::DefaultCharacterStyle);
addItem( CommonStrings::trDefaultCharacterStyle );
+ if (defaultIndex >= 0)
+ styleComboSetPreviewData(this, count() - 1, m_doc->charStyles()[defaultIndex], m_doc, QString(), 100.0, true);
+
+ QStringList st;
for (int i = 0; i < m_doc->charStyles().count(); ++i)
{
const CharStyle& charStyle = m_doc->charStyles()[i];
@@ -271,7 +610,13 @@
st.append(charStyle.name());
}
st.sort();
- addItems(st);
+ for (const QString& styleName : st)
+ {
+ addItem(styleName);
+ int idx = m_doc->charStyles().find(styleName);
+ if (idx >= 0)
+ styleComboSetPreviewData(this, count() - 1, m_doc->charStyles()[idx], m_doc, QString(), 100.0, true);
+ }
if (oldStyleName.length() > 0)
{
|
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-08-14 02:41 | qirat | New Issue | |
| 2026-08-14 02:41 | qirat | File Added: r27771-p1-stylecombo-font-preview-v1.06.patch | |
| 2026-08-14 02:47 | qirat | Note Added: 0054203 | |
| 2026-08-14 02:47 | qirat | File Added: pp-char-style-combo-preview.png | |
| 2026-08-14 02:47 | qirat | File Added: pp-para-style-combo-preview.png | |
| 2026-08-14 04:05 | qirat | Note Added: 0054204 | |
| 2026-08-14 04:05 | qirat | File Added: pp-char-style-combo-preview_2.png | |
| 2026-08-14 06:39 | qirat | Note Added: 0054205 | |
| 2026-08-14 06:39 | qirat | File Added: pp-char-style-combo-preview_3.png | |
| 2026-08-15 07:28 | ale | Note Added: 0054206 | |
| 2026-08-15 07:47 | qirat | Note Added: 0054207 | |
| 2026-08-16 07:46 | qirat | Note Added: 0054211 | |
| 2026-08-16 07:46 | qirat | File Added: stylecombo-font-preview-v1.15.patch |