View Issue Details

IDProjectCategoryView StatusLast Update
0017828ScribusUser Interfacepublic2026-07-13 04:30
Reporterqirat Assigned Tocbradney  
PrioritynormalSeverityminorReproducibilityhave not tried
Status assignedResolutionopen 
PlatformLinuxOSFedora WorkstationOS Version44
Summary0017828: [PATCH] Deleting many unused styles at once hides the buttons
DescriptionIf the list is too long the buttons go off screen below (maybe not on bigger screens but it happens on my 15" laptop, 1080p)
Steps To ReproduceHave many styles unused and try removing them
Additional InformationHow about fixing the dialog box's height with the buttons always visible, and making the style list scrollable?

Anyway, you know better.
TagsNo tags attached.
Attached Files
PatchYes

Activities

qirat

2026-07-11 02:38

reporter   ~0054009

(Created with AI), this fixes it for me. Makes the list scrollable.
delete-unused-dialog-resizable-v4-r27707.patch (4,467 bytes)   
--- scribus/ui/stylemanager.cpp
+++ scribus/ui/stylemanager.cpp
@@ -5,11 +5,21 @@
 for which a new license (GPL+exception) is in place.
 */
 
+#include <QAbstractItemView>
 #include <QDebug>
+#include <QDialog>
+#include <QDialogButtonBox>
 #include <QEvent>
+#include <QHeaderView>
+#include <QLabel>
 #include <QMenu>
-#include <QToolTip>
 #include <QMessageBox>
+#include <QPushButton>
+#include <QScreen>
+#include <QSizePolicy>
+#include <QToolTip>
+#include <QTreeWidget>
+#include <QVBoxLayout>
 
 #include "commonstrings.h"
 #include "fileloader.h"
@@ -537,7 +547,13 @@
 		slotOk();
 
 	int totalUnused = 0;
-	QStringList unusedSummary;
+
+	struct UnusedStyleEntry
+	{
+		QString typeName;
+		QString styleName;
+	};
+	QList<UnusedStyleEntry> unusedStyles;
 
 	struct ItemDeletion
 	{
@@ -579,7 +595,7 @@
 			if (!usedMap->contains(styleName))
 			{
 				removeList.append(RemoveItem(styleName, QString()));
-				unusedSummary << QString("  %1: %2").arg(styleitem->typeName(), styleName);
+				unusedStyles.append({styleitem->typeName(), styleName});
 			}
 		}
 
@@ -592,20 +608,70 @@
 
 	if (totalUnused == 0)
 	{
-		ScMessageBox::information(this, tr("Remove Unused Styles"),
-			tr("All styles in this document are in use. Nothing to remove."));
+		ScMessageBox::information(this, tr("Delete Unused Styles"),
+			tr("All styles in this document are in use. Nothing to delete."));
 		return;
 	}
 
-	// Confirmation dialog
-	QString message = tr("Found %1 unused style(s):\n\n").arg(totalUnused);
-	message += unusedSummary.join("\n");
-	message += "\n\n";
-	message += tr("Remove all unused styles?");
-
-	int result = ScMessageBox::question(this, tr("Remove Unused Styles"), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
+	QDialog dialog(this);
+	dialog.setWindowTitle(tr("Delete Unused Styles"));
+	dialog.setSizeGripEnabled(true);
+
+	QVBoxLayout* layout = new QVBoxLayout(&dialog);
+
+	QLabel* summaryLabel = new QLabel(tr("%n unused style(s) found.", nullptr, totalUnused), &dialog);
+	layout->addWidget(summaryLabel);
+
+	QLabel* detailLabel = new QLabel(tr("The following styles are not used by any object in the document:"), &dialog);
+	detailLabel->setWordWrap(true);
+	layout->addWidget(detailLabel);
+
+	QTreeWidget* unusedStyleList = new QTreeWidget(&dialog);
+	unusedStyleList->setHeaderLabels(QStringList() << tr("Type") << tr("Style"));
+	unusedStyleList->setRootIsDecorated(false);
+	unusedStyleList->setAlternatingRowColors(true);
+	unusedStyleList->setSelectionMode(QAbstractItemView::NoSelection);
+	unusedStyleList->setUniformRowHeights(true);
+	unusedStyleList->setMinimumHeight(180);
+	unusedStyleList->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+	for (int i = 0; i < unusedStyles.count(); ++i)
+	{
+		QTreeWidgetItem* row = new QTreeWidgetItem(unusedStyleList);
+		row->setText(0, unusedStyles.at(i).typeName);
+		row->setText(1, unusedStyles.at(i).styleName);
+	}
+
+	QHeaderView* unusedStyleListHeader = unusedStyleList->header();
+	unusedStyleListHeader->setSectionResizeMode(QHeaderView::Interactive);
+	unusedStyleListHeader->setStretchLastSection(false);
+	unusedStyleList->resizeColumnToContents(0);
+	unusedStyleList->setColumnWidth(0, qMax(120, unusedStyleList->columnWidth(0)));
+	unusedStyleList->setColumnWidth(1, qMax(320, unusedStyleList->columnWidth(1)));
+	layout->addWidget(unusedStyleList);
+
+	QLabel* warningLabel = new QLabel(tr("Delete all listed styles?"), &dialog);
+	warningLabel->setWordWrap(true);
+	layout->addWidget(warningLabel);
+
+	QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog);
+	buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Delete Styles"));
+	connect(buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
+	connect(buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
+	layout->addWidget(buttonBox);
+
+	const QScreen* dialogScreen = screen();
+	if (dialogScreen)
+	{
+		const QRect availableGeometry = dialogScreen->availableGeometry();
+		const int dialogWidth = qMin(qMax(560, availableGeometry.width() / 2), qMax(320, availableGeometry.width() - 80));
+		const int dialogHeight = qMin(520, qMax(280, availableGeometry.height() - 120));
+		dialog.resize(dialogWidth, dialogHeight);
+	}
+	else
+		dialog.resize(560, 520);
 
-	if (result != QMessageBox::Yes)
+	if (dialog.exec() != QDialog::Accepted)
 		return;
 
 	// Delete through each StyleItem — modifies temp copies only

qirat

2026-07-11 02:39

reporter   ~0054010

qirat

2026-07-11 02:48

reporter   ~0054011

I cannot set the patch to "Yes". Please see.

ale

2026-07-11 07:29

manager   ~0054017

I would probably use an icon instead of the "Paragraph Styles" text.

At first sight, the patch seems to be OK.
But needs a more careful review, than what I did.

qirat

2026-07-11 07:36

reporter   ~0054018

You mean icons instead of text in the column "Type", right? That is better.

qirat

2026-07-11 09:16

reporter   ~0054019

Here is the improved version:
* icons used
* two part view
** show style by category
--- show total count
--- show count by category
1_delete-unused-dialog-layout-v3-r27707.patch (9,922 bytes)   
--- scribus/ui/stylemanager.cpp
+++ scribus/ui/stylemanager.cpp
@@ -5,14 +5,28 @@
 for which a new license (GPL+exception) is in place.
 */
 
+#include <QAbstractItemView>
 #include <QDebug>
+#include <QDialog>
+#include <QDialogButtonBox>
 #include <QEvent>
+#include <QFont>
+#include <QHeaderView>
+#include <QHBoxLayout>
+#include <QKeyEvent>
+#include <QLabel>
 #include <QMenu>
-#include <QToolTip>
 #include <QMessageBox>
+#include <QMouseEvent>
+#include <QPushButton>
+#include <QSizePolicy>
+#include <QToolTip>
+#include <QTreeWidget>
+#include <QVBoxLayout>
 
 #include "commonstrings.h"
 #include "fileloader.h"
+#include "iconmanager.h"
 #include "prefsfile.h"
 #include "prefsmanager.h"
 #include "scraction.h"
@@ -32,6 +46,39 @@
 #include "ui/scmessagebox.h"
 #include "ui/styleview.h"
 
+namespace
+{
+class ReadOnlyTreeWidget : public QTreeWidget
+{
+public:
+	explicit ReadOnlyTreeWidget(QWidget* parent = nullptr)
+		: QTreeWidget(parent)
+	{
+	}
+
+protected:
+	void mousePressEvent(QMouseEvent* event) override
+	{
+		event->accept();
+	}
+
+	void mouseReleaseEvent(QMouseEvent* event) override
+	{
+		event->accept();
+	}
+
+	void mouseDoubleClickEvent(QMouseEvent* event) override
+	{
+		event->accept();
+	}
+
+	void keyPressEvent(QKeyEvent* event) override
+	{
+		event->accept();
+	}
+};
+}
+
 const QString StyleManager::SEPARATOR = "$$$$"; // dumb but it works
 
 StyleManager::StyleManager(QWidget *parent, const char *name)
@@ -537,7 +584,22 @@
 		slotOk();
 
 	int totalUnused = 0;
-	QStringList unusedSummary;
+
+	struct UnusedStyleEntry
+	{
+		QString typeName;
+		QString iconName;
+		QString styleName;
+	};
+	QList<UnusedStyleEntry> unusedStyles;
+
+	struct UnusedStyleType
+	{
+		QString typeName;
+		QString iconName;
+		int count;
+	};
+	QList<UnusedStyleType> unusedStyleTypes;
 
 	struct ItemDeletion
 	{
@@ -552,17 +614,33 @@
 
 		// Match StyleItem subclass to the right ResourceCollection map
 		const QMap<QString, QString>* usedMap = nullptr;
+		QString iconName;
 
 		if (qobject_cast<SMParagraphStyle*>(styleitem))
+		{
 			usedMap = &usedResources.styles();
+			iconName = "paragraph-style";
+		}
 		else if (qobject_cast<SMCharacterStyle*>(styleitem))
+		{
 			usedMap = &usedResources.charStyles();
+			iconName = "character-style";
+		}
 		else if (qobject_cast<SMLineStyle*>(styleitem))
+		{
 			usedMap = &usedResources.lineStyles();
+			iconName = "stroke-style";
+		}
 		else if (qobject_cast<SMTableStyle*>(styleitem))
+		{
 			usedMap = &usedResources.tableStyles();
+			iconName = "table-style";
+		}
 		else if (qobject_cast<SMCellStyle*>(styleitem))
+		{
 			usedMap = &usedResources.cellStyles();
+			iconName = "table-cell-style";
+		}
 		else
 			continue;
 
@@ -579,10 +657,12 @@
 			if (!usedMap->contains(styleName))
 			{
 				removeList.append(RemoveItem(styleName, QString()));
-				unusedSummary << QString("  %1: %2").arg(styleitem->typeName(), styleName);
+				unusedStyles.append({styleitem->typeName(), iconName, styleName});
 			}
 		}
 
+		unusedStyleTypes.append({styleitem->typeName(), iconName, removeList.count()});
+
 		if (!removeList.isEmpty())
 		{
 			deletions.append({styleitem, removeList});
@@ -592,20 +672,149 @@
 
 	if (totalUnused == 0)
 	{
-		ScMessageBox::information(this, tr("Remove Unused Styles"),
-			tr("All styles in this document are in use. Nothing to remove."));
+		ScMessageBox::information(this, tr("Delete Unused Styles"),
+			tr("All styles in this document are in use. Nothing to delete."));
 		return;
 	}
 
-	// Confirmation dialog
-	QString message = tr("Found %1 unused style(s):\n\n").arg(totalUnused);
-	message += unusedSummary.join("\n");
-	message += "\n\n";
-	message += tr("Remove all unused styles?");
+	QDialog dialog(this);
+	dialog.setWindowTitle(tr("Delete Unused Styles"));
+	dialog.setSizeGripEnabled(false);
+
+	QVBoxLayout* layout = new QVBoxLayout(&dialog);
+	layout->setSpacing(10);
+	layout->setSizeConstraint(QLayout::SetFixedSize);
+
+	const int paneSpacing = 24;
+	const QSize overviewPaneSize(220, 300);
+	const QSize listPaneSize(540, 300);
+	const int listHeight = 230;
+
+	QWidget* contentWidget = new QWidget(&dialog);
+	QHBoxLayout* contentLayout = new QHBoxLayout(contentWidget);
+	contentLayout->setContentsMargins(0, 0, 0, 0);
+	contentLayout->setSpacing(paneSpacing);
+
+	QWidget* overviewPane = new QWidget(contentWidget);
+	overviewPane->setFixedSize(overviewPaneSize);
+	QVBoxLayout* overviewLayout = new QVBoxLayout(overviewPane);
+	overviewLayout->setContentsMargins(0, 0, 0, 0);
+	overviewLayout->setSpacing(8);
+
+	QLabel* overviewTitle = new QLabel(tr("Styles by Type"), overviewPane);
+	QFont overviewTitleFont = overviewTitle->font();
+	overviewTitleFont.setBold(true);
+	overviewTitle->setFont(overviewTitleFont);
+	overviewLayout->addWidget(overviewTitle);
+
+	QLabel* summaryLabel = new QLabel(tr("Total unused styles found: %1").arg(totalUnused), overviewPane);
+	summaryLabel->setWordWrap(true);
+	overviewLayout->addWidget(summaryLabel);
+
+	const QSize typeIconSize(16, 16);
+	for (int i = 0; i < unusedStyleTypes.count(); ++i)
+	{
+		const UnusedStyleType& styleType = unusedStyleTypes.at(i);
+		QHBoxLayout* typeRow = new QHBoxLayout;
+		typeRow->setContentsMargins(0, 0, 0, 0);
+
+		QLabel* typeIcon = new QLabel(overviewPane);
+		typeIcon->setFixedSize(typeIconSize);
+		typeIcon->setAlignment(Qt::AlignCenter);
+		typeIcon->setPixmap(IconManager::instance().loadIcon(styleType.iconName).pixmap(typeIconSize));
+		typeIcon->setToolTip(styleType.typeName);
+		typeRow->addWidget(typeIcon);
+
+		QLabel* typeName = new QLabel(styleType.typeName, overviewPane);
+		typeRow->addWidget(typeName, 1);
+
+		QLabel* typeCount = new QLabel(QString::number(styleType.count), overviewPane);
+		typeCount->setMinimumWidth(24);
+		typeCount->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+		typeRow->addWidget(typeCount);
+
+		overviewLayout->addLayout(typeRow);
+	}
+	overviewLayout->addStretch();
+
+	QWidget* listPane = new QWidget(contentWidget);
+	listPane->setFixedSize(listPaneSize);
+	QVBoxLayout* listLayout = new QVBoxLayout(listPane);
+	listLayout->setContentsMargins(0, 0, 0, 0);
+	listLayout->setSpacing(8);
+
+	QLabel* listTitle = new QLabel(tr("Unused Styles"), listPane);
+	QFont listTitleFont = listTitle->font();
+	listTitleFont.setBold(true);
+	listTitle->setFont(listTitleFont);
+	listLayout->addWidget(listTitle);
+
+	QLabel* detailLabel = new QLabel(tr("The following styles are not used by any object in the document:"), listPane);
+	detailLabel->setWordWrap(true);
+	listLayout->addWidget(detailLabel);
+
+	QTreeWidget* unusedStyleList = new ReadOnlyTreeWidget(listPane);
+	unusedStyleList->setHeaderLabels(QStringList() << tr("Type") << tr("Style Name"));
+	unusedStyleList->headerItem()->setTextAlignment(0, Qt::AlignCenter);
+	unusedStyleList->setRootIsDecorated(false);
+	unusedStyleList->setAlternatingRowColors(true);
+	unusedStyleList->setSelectionMode(QAbstractItemView::NoSelection);
+	unusedStyleList->setFocusPolicy(Qt::NoFocus);
+	unusedStyleList->setEditTriggers(QAbstractItemView::NoEditTriggers);
+	unusedStyleList->setContextMenuPolicy(Qt::NoContextMenu);
+	unusedStyleList->setUniformRowHeights(true);
+	unusedStyleList->setFixedHeight(listHeight);
+	unusedStyleList->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+
+	for (int i = 0; i < unusedStyles.count(); ++i)
+	{
+		const UnusedStyleEntry& style = unusedStyles.at(i);
+		QTreeWidgetItem* row = new QTreeWidgetItem(unusedStyleList);
+		row->setData(0, Qt::AccessibleTextRole, style.typeName);
+		row->setText(1, style.styleName);
+
+		QLabel* typeIcon = new QLabel(unusedStyleList);
+		typeIcon->setAlignment(Qt::AlignCenter);
+		typeIcon->setPixmap(IconManager::instance().loadIcon(style.iconName).pixmap(typeIconSize));
+		typeIcon->setToolTip(style.typeName);
+		typeIcon->setAccessibleName(style.typeName);
+		typeIcon->setAttribute(Qt::WA_TransparentForMouseEvents);
+		unusedStyleList->setItemWidget(row, 0, typeIcon);
+	}
+
+	QHeaderView* unusedStyleListHeader = unusedStyleList->header();
+	unusedStyleListHeader->setSectionResizeMode(QHeaderView::Interactive);
+	unusedStyleListHeader->setStretchLastSection(false);
+	unusedStyleList->setColumnWidth(0, 64);
+	unusedStyleList->resizeColumnToContents(1);
+	unusedStyleList->setColumnWidth(1, qMax(320, unusedStyleList->columnWidth(1)));
+	listLayout->addWidget(unusedStyleList);
+
+	contentLayout->addWidget(overviewPane);
+	contentLayout->addWidget(listPane);
+	layout->addWidget(contentWidget);
+
+	const QString warningText = (totalUnused == 1)
+		? tr("This style will be permanently deleted.")
+		: tr("These styles will be permanently deleted.");
+	QLabel* warningLabel = new QLabel(warningText, &dialog);
+	warningLabel->setWordWrap(true);
+	QHBoxLayout* warningLayout = new QHBoxLayout;
+	warningLayout->setContentsMargins(0, 0, 0, 0);
+	warningLayout->setSpacing(0);
+	warningLayout->addSpacing(overviewPaneSize.width() + paneSpacing);
+	warningLayout->addWidget(warningLabel, 1);
+	layout->addLayout(warningLayout);
+
+	QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog);
+	QPushButton* deleteStylesButton = buttonBox->button(QDialogButtonBox::Ok);
+	deleteStylesButton->setText(tr("Delete Styles"));
+	deleteStylesButton->setIcon(IconManager::instance().loadIcon("edit-delete"));
+	connect(buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
+	connect(buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
+	layout->addWidget(buttonBox);
 
-	int result = ScMessageBox::question(this, tr("Remove Unused Styles"), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
-
-	if (result != QMessageBox::Yes)
+	if (dialog.exec() != QDialog::Accepted)
 		return;
 
 	// Delete through each StyleItem — modifies temp copies only
@@ -619,7 +828,6 @@
 	slotOk();
 }
 
-
 void StyleManager::slotImport()
 {
 	if (!m_doc)

qirat

2026-07-11 09:17

reporter   ~0054020

ale

2026-07-12 06:46

manager   ~0054023

The screenshot looks good

I would probably put the summary below or on the righ side.

In my eyes, the main content is the list of styles..

qirat

2026-07-12 07:10

reporter   ~0054024

Yeah, that crossed my mind too. However, I took it so:
- English is LTR, so summary first, then details (I hope this logic it makes sense).
- the main content should be on the right near the buttons (the main, sensitive operation)
- it is consistence with many other areas of Scribus, like SM, the main work of styling goes on the right.
- the icons in the summary on the left serve like index so that one should wuickly know what each icon means in the right side. Index should come first?

The "total" line in the summary could be moved to the bottom of the summary. However, it makes sense at the top too.

Putting summary below would contradict my 2nd point above: The summary could be ignored if one wants but if it comes just before/above the buttons it kind of ask of an extra attention from the user which I personally beleive it does not need. What do you think?

qirat

2026-07-12 07:13

reporter   ~0054025

I went with the summary because there was an empty space anyway.

And, that "Styles by Type" could be renamed to "Summary", of course.

qirat

2026-07-13 04:06

reporter   ~0054027

Here is v4:
- space optimised
- better polished overall

qirat

2026-07-13 04:30

reporter   ~0054028

Here is v4:
- space optimised
- better polished overall
- smaller size
1_delete-unused-dialog-layout-v4.1-r27708.patch (10,879 bytes)   
--- scribus/ui/stylemanager.cpp
+++ scribus/ui/stylemanager.cpp
@@ -5,14 +5,29 @@
 for which a new license (GPL+exception) is in place.
 */
 
+#include <QAbstractItemView>
 #include <QDebug>
+#include <QDialog>
+#include <QDialogButtonBox>
 #include <QEvent>
+#include <QFont>
+#include <QHeaderView>
+#include <QHBoxLayout>
+#include <QKeyEvent>
+#include <QLabel>
 #include <QMenu>
-#include <QToolTip>
 #include <QMessageBox>
+#include <QMouseEvent>
+#include <QPushButton>
+#include <QSizePolicy>
+#include <QStyle>
+#include <QToolTip>
+#include <QTreeWidget>
+#include <QVBoxLayout>
 
 #include "commonstrings.h"
 #include "fileloader.h"
+#include "iconmanager.h"
 #include "prefsfile.h"
 #include "prefsmanager.h"
 #include "scraction.h"
@@ -32,6 +47,39 @@
 #include "ui/scmessagebox.h"
 #include "ui/styleview.h"
 
+namespace
+{
+class ReadOnlyTreeWidget : public QTreeWidget
+{
+public:
+	explicit ReadOnlyTreeWidget(QWidget* parent = nullptr)
+		: QTreeWidget(parent)
+	{
+	}
+
+protected:
+	void mousePressEvent(QMouseEvent* event) override
+	{
+		event->accept();
+	}
+
+	void mouseReleaseEvent(QMouseEvent* event) override
+	{
+		event->accept();
+	}
+
+	void mouseDoubleClickEvent(QMouseEvent* event) override
+	{
+		event->accept();
+	}
+
+	void keyPressEvent(QKeyEvent* event) override
+	{
+		event->accept();
+	}
+};
+}
+
 const QString StyleManager::SEPARATOR = "$$$$"; // dumb but it works
 
 StyleManager::StyleManager(QWidget *parent, const char *name)
@@ -537,7 +585,22 @@
 		slotOk();
 
 	int totalUnused = 0;
-	QStringList unusedSummary;
+
+	struct UnusedStyleEntry
+	{
+		QString typeName;
+		QString iconName;
+		QString styleName;
+	};
+	QList<UnusedStyleEntry> unusedStyles;
+
+	struct UnusedStyleType
+	{
+		QString typeName;
+		QString iconName;
+		int count;
+	};
+	QList<UnusedStyleType> unusedStyleTypes;
 
 	struct ItemDeletion
 	{
@@ -552,17 +615,33 @@
 
 		// Match StyleItem subclass to the right ResourceCollection map
 		const QMap<QString, QString>* usedMap = nullptr;
+		QString iconName;
 
 		if (qobject_cast<SMParagraphStyle*>(styleitem))
+		{
 			usedMap = &usedResources.styles();
+			iconName = "paragraph-style";
+		}
 		else if (qobject_cast<SMCharacterStyle*>(styleitem))
+		{
 			usedMap = &usedResources.charStyles();
+			iconName = "character-style";
+		}
 		else if (qobject_cast<SMLineStyle*>(styleitem))
+		{
 			usedMap = &usedResources.lineStyles();
+			iconName = "stroke-style";
+		}
 		else if (qobject_cast<SMTableStyle*>(styleitem))
+		{
 			usedMap = &usedResources.tableStyles();
+			iconName = "table-style";
+		}
 		else if (qobject_cast<SMCellStyle*>(styleitem))
+		{
 			usedMap = &usedResources.cellStyles();
+			iconName = "table-cell-style";
+		}
 		else
 			continue;
 
@@ -579,10 +658,12 @@
 			if (!usedMap->contains(styleName))
 			{
 				removeList.append(RemoveItem(styleName, QString()));
-				unusedSummary << QString("  %1: %2").arg(styleitem->typeName(), styleName);
+				unusedStyles.append({styleitem->typeName(), iconName, styleName});
 			}
 		}
 
+		unusedStyleTypes.append({styleitem->typeName(), iconName, removeList.count()});
+
 		if (!removeList.isEmpty())
 		{
 			deletions.append({styleitem, removeList});
@@ -592,20 +673,173 @@
 
 	if (totalUnused == 0)
 	{
-		ScMessageBox::information(this, tr("Remove Unused Styles"),
-			tr("All styles in this document are in use. Nothing to remove."));
+		ScMessageBox::information(this, tr("Delete Unused Styles"),
+			tr("All styles in this document are in use. Nothing to delete."));
 		return;
 	}
 
-	// Confirmation dialog
-	QString message = tr("Found %1 unused style(s):\n\n").arg(totalUnused);
-	message += unusedSummary.join("\n");
-	message += "\n\n";
-	message += tr("Remove all unused styles?");
+	QDialog dialog(this);
+	dialog.setWindowTitle(tr("Delete Unused Styles"));
+	dialog.setSizeGripEnabled(false);
+
+	QVBoxLayout* layout = new QVBoxLayout(&dialog);
+	layout->setSpacing(10);
+	layout->setSizeConstraint(QLayout::SetFixedSize);
+
+	const int paneSpacing = 24;
+	const QSize overviewPaneSize(220, 300);
+	const int listPaneHeight = 300;
+	const int listHeight = 230;
+
+	QWidget* contentWidget = new QWidget(&dialog);
+	QHBoxLayout* contentLayout = new QHBoxLayout(contentWidget);
+	contentLayout->setContentsMargins(0, 0, 0, 0);
+	contentLayout->setSpacing(paneSpacing);
+
+	QWidget* overviewPane = new QWidget(contentWidget);
+	overviewPane->setFixedSize(overviewPaneSize);
+	QVBoxLayout* overviewLayout = new QVBoxLayout(overviewPane);
+	overviewLayout->setContentsMargins(0, 0, 0, 0);
+	overviewLayout->setSpacing(8);
+
+	QLabel* overviewTitle = new QLabel(tr("Summary"), overviewPane);
+	QFont overviewTitleFont = overviewTitle->font();
+	overviewTitleFont.setBold(true);
+	overviewTitle->setFont(overviewTitleFont);
+	overviewLayout->addWidget(overviewTitle);
+
+	const QSize typeIconSize(16, 16);
+	for (int i = 0; i < unusedStyleTypes.count(); ++i)
+	{
+		const UnusedStyleType& styleType = unusedStyleTypes.at(i);
+		QHBoxLayout* typeRow = new QHBoxLayout;
+		typeRow->setContentsMargins(0, 0, 0, 0);
+
+		QLabel* typeIcon = new QLabel(overviewPane);
+		typeIcon->setFixedSize(typeIconSize);
+		typeIcon->setAlignment(Qt::AlignCenter);
+		typeIcon->setPixmap(IconManager::instance().loadIcon(styleType.iconName).pixmap(typeIconSize));
+		typeIcon->setToolTip(styleType.typeName);
+		typeRow->addWidget(typeIcon);
+
+		QLabel* typeName = new QLabel(styleType.typeName, overviewPane);
+		typeRow->addWidget(typeName, 1);
+
+		QLabel* typeCount = new QLabel(QString::number(styleType.count), overviewPane);
+		typeCount->setMinimumWidth(24);
+		typeCount->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+		typeRow->addWidget(typeCount);
+
+		overviewLayout->addLayout(typeRow);
+	}
+
+	overviewLayout->addSpacing(8);
+	QHBoxLayout* totalRow = new QHBoxLayout;
+	totalRow->setContentsMargins(0, 0, 0, 0);
+
+	QLabel* totalIconSpacer = new QLabel(overviewPane);
+	totalIconSpacer->setFixedSize(typeIconSize);
+	totalRow->addWidget(totalIconSpacer);
+
+	QLabel* totalLabel = new QLabel(tr("Total unused styles:"), overviewPane);
+	QFont totalFont = totalLabel->font();
+	totalFont.setBold(true);
+	totalLabel->setFont(totalFont);
+	totalRow->addWidget(totalLabel, 1);
+
+	QLabel* totalCount = new QLabel(QString::number(totalUnused), overviewPane);
+	totalCount->setFont(totalFont);
+	totalCount->setMinimumWidth(24);
+	totalCount->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+	totalRow->addWidget(totalCount);
+
+	overviewLayout->addLayout(totalRow);
+	overviewLayout->addStretch();
+
+	QWidget* listPane = new QWidget(contentWidget);
+	listPane->setFixedHeight(listPaneHeight);
+	QVBoxLayout* listLayout = new QVBoxLayout(listPane);
+	listLayout->setContentsMargins(0, 0, 0, 0);
+	listLayout->setSpacing(8);
+
+	QLabel* listTitle = new QLabel(tr("Unused Styles"), listPane);
+	QFont listTitleFont = listTitle->font();
+	listTitleFont.setBold(true);
+	listTitle->setFont(listTitleFont);
+	listLayout->addWidget(listTitle);
+
+	QLabel* detailLabel = new QLabel(tr("The following styles are not used by any object in the document:"), listPane);
+	detailLabel->setWordWrap(true);
+	listLayout->addWidget(detailLabel);
+
+	QTreeWidget* unusedStyleList = new ReadOnlyTreeWidget(listPane);
+	unusedStyleList->setHeaderLabels(QStringList() << tr("Type") << tr("Style Name"));
+	unusedStyleList->headerItem()->setTextAlignment(0, Qt::AlignCenter);
+	unusedStyleList->setRootIsDecorated(false);
+	unusedStyleList->setAlternatingRowColors(true);
+	unusedStyleList->setSelectionMode(QAbstractItemView::NoSelection);
+	unusedStyleList->setFocusPolicy(Qt::NoFocus);
+	unusedStyleList->setEditTriggers(QAbstractItemView::NoEditTriggers);
+	unusedStyleList->setContextMenuPolicy(Qt::NoContextMenu);
+	unusedStyleList->setUniformRowHeights(true);
+	unusedStyleList->setFixedHeight(listHeight);
+	unusedStyleList->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+
+	for (int i = 0; i < unusedStyles.count(); ++i)
+	{
+		const UnusedStyleEntry& style = unusedStyles.at(i);
+		QTreeWidgetItem* row = new QTreeWidgetItem(unusedStyleList);
+		row->setData(0, Qt::AccessibleTextRole, style.typeName);
+		row->setText(1, style.styleName);
+
+		QLabel* typeIcon = new QLabel(unusedStyleList);
+		typeIcon->setAlignment(Qt::AlignCenter);
+		typeIcon->setPixmap(IconManager::instance().loadIcon(style.iconName).pixmap(typeIconSize));
+		typeIcon->setToolTip(style.typeName);
+		typeIcon->setAccessibleName(style.typeName);
+		typeIcon->setAttribute(Qt::WA_TransparentForMouseEvents);
+		unusedStyleList->setItemWidget(row, 0, typeIcon);
+	}
+
+	QHeaderView* unusedStyleListHeader = unusedStyleList->header();
+	unusedStyleListHeader->setSectionResizeMode(QHeaderView::Interactive);
+	unusedStyleListHeader->setStretchLastSection(false);
+	unusedStyleList->setColumnWidth(0, 64);
+	unusedStyleList->resizeColumnToContents(1);
+	unusedStyleList->setColumnWidth(1, qMax(320, unusedStyleList->columnWidth(1)));
+	unusedStyleList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+	const int listPaneWidth = unusedStyleListHeader->length()
+		+ unusedStyleList->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, unusedStyleList)
+		+ 2 * unusedStyleList->frameWidth();
+	unusedStyleList->setFixedWidth(listPaneWidth);
+	listPane->setFixedWidth(listPaneWidth);
+	listLayout->addWidget(unusedStyleList);
+
+	contentLayout->addWidget(overviewPane);
+	contentLayout->addWidget(listPane);
+	layout->addWidget(contentWidget);
+
+	const QString warningText = (totalUnused == 1)
+		? tr("This style will be permanently deleted.")
+		: tr("These styles will be permanently deleted.");
+	QLabel* warningLabel = new QLabel(warningText, &dialog);
+	warningLabel->setWordWrap(true);
+	QHBoxLayout* warningLayout = new QHBoxLayout;
+	warningLayout->setContentsMargins(0, 0, 0, 0);
+	warningLayout->setSpacing(0);
+	warningLayout->addSpacing(overviewPaneSize.width() + paneSpacing);
+	warningLayout->addWidget(warningLabel, 1);
+	layout->addLayout(warningLayout);
+
+	QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog);
+	QPushButton* deleteStylesButton = buttonBox->button(QDialogButtonBox::Ok);
+	deleteStylesButton->setText(tr("Delete Styles"));
+	deleteStylesButton->setIcon(IconManager::instance().loadIcon("edit-delete"));
+	connect(buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
+	connect(buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
+	layout->addWidget(buttonBox);
 
-	int result = ScMessageBox::question(this, tr("Remove Unused Styles"), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
-
-	if (result != QMessageBox::Yes)
+	if (dialog.exec() != QDialog::Accepted)
 		return;
 
 	// Delete through each StyleItem — modifies temp copies only
@@ -619,7 +853,6 @@
 	slotOk();
 }
 
-
 void StyleManager::slotImport()
 {
 	if (!m_doc)

Issue History

Date Modified Username Field Change
2026-06-02 10:08 qirat New Issue
2026-06-02 10:08 qirat File Added: Screenshot From 2026-06-01 06-14-48.png
2026-06-02 17:55 cbradney Assigned To => cbradney
2026-06-02 17:55 cbradney Status new => assigned
2026-07-11 02:38 qirat Note Added: 0054009
2026-07-11 02:38 qirat File Added: delete-unused-dialog-resizable-v4-r27707.patch
2026-07-11 02:39 qirat Note Added: 0054010
2026-07-11 02:39 qirat File Added: delete_unused_style_scrollable.png
2026-07-11 02:48 qirat Note Added: 0054011
2026-07-11 07:21 ale Summary Deleting many unused styles at once hides the buttons => [PATCH] Deleting many unused styles at once hides the buttons
2026-07-11 07:21 ale Patch No => Yes
2026-07-11 07:29 ale Note Added: 0054017
2026-07-11 07:36 qirat Note Added: 0054018
2026-07-11 09:16 qirat Note Added: 0054019
2026-07-11 09:16 qirat File Added: 1_delete-unused-dialog-layout-v3-r27707.patch
2026-07-11 09:17 qirat Note Added: 0054020
2026-07-11 09:17 qirat File Added: delete-unused-dialog-layout-v3-r27707.png
2026-07-12 06:46 ale Note Added: 0054023
2026-07-12 07:10 qirat Note Added: 0054024
2026-07-12 07:13 qirat Note Added: 0054025
2026-07-13 04:06 qirat Note Added: 0054027
2026-07-13 04:06 qirat File Added: 1_delete-unused-dialog-layout-v4-r27707.patch
2026-07-13 04:06 qirat File Added: delete-unused-dialog-layout-v4-r27707.png
2026-07-13 04:13 qirat File Deleted: delete-unused-dialog-layout-v4-r27707.png
2026-07-13 04:13 qirat File Deleted: 1_delete-unused-dialog-layout-v4-r27707.patch
2026-07-13 04:30 qirat Note Added: 0054028
2026-07-13 04:30 qirat File Added: 1_delete-unused-dialog-layout-v4.1-r27708.patch
2026-07-13 04:30 qirat File Added: delete-unused-dialog-layout-v4.1-r27708.png