--- scribus/ui/stylemanager.cpp +++ scribus/ui/stylemanager.cpp @@ -5,14 +5,29 @@ for which a new license (GPL+exception) is in place. */ +#include #include +#include +#include #include +#include +#include +#include +#include +#include #include -#include #include +#include +#include +#include +#include +#include +#include +#include #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 unusedStyles; + + struct UnusedStyleType + { + QString typeName; + QString iconName; + int count; + }; + QList unusedStyleTypes; struct ItemDeletion { @@ -552,17 +615,33 @@ // Match StyleItem subclass to the right ResourceCollection map const QMap* usedMap = nullptr; + QString iconName; if (qobject_cast(styleitem)) + { usedMap = &usedResources.styles(); + iconName = "paragraph-style"; + } else if (qobject_cast(styleitem)) + { usedMap = &usedResources.charStyles(); + iconName = "character-style"; + } else if (qobject_cast(styleitem)) + { usedMap = &usedResources.lineStyles(); + iconName = "stroke-style"; + } else if (qobject_cast(styleitem)) + { usedMap = &usedResources.tableStyles(); + iconName = "table-style"; + } else if (qobject_cast(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)