View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0017980 | Scribus | Master Pages | public | 2026-09-18 14:27 | 2026-09-23 16:41 |
| Reporter | buttar | 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 | 0017980: Allow Delete Unused for master pages as well | ||||
| Description | It will be a really nice quality of life improvement. With many master pages, it is confusing knowing which are actually used in the document and which are just sitting there causing clutter. | ||||
| Tags | #please_test | ||||
| Patch | No | ||||
|
|
This 2-patch series adds a safer, more complete master-page deletion work-flow, including reliable deletion/undo behaviour, protected default masters, Delete Unused, Select Unused, and explicit replacement choices for masters that are still in use. The series depends on `mid-foundation-v1.0.patch` (from 0017984) for stable master-page identity, so that deletion, protection, replacement, and undo/redo operate on the actual master rather than its editable name. (mdu = master delete unused) ## `mdu1-deletion-hardening-v1.0.patch` - Protects built-in/default masters by identity. - Unifies deletion behaviour across Edit Master Pages, Arrange Pages, and scripting. - Makes used-master replacement part of the same undoable deletion operation. - Restores the deleted master’s essential state correctly through undo/redo. ## `mdu2-delete-unused-v1.33.patch` - Adds Delete Unused... and Select Unused for custom master pages. - Adds one confirmation dialog showing usage and replacement behaviour before deletion. - Supports side-aware per-master replacement choices and optional bulk Left/Right replacement. - Keeps protected defaults out of the deletion set and reports that they are retained. - Performs confirmed multi-master deletion as one undoable operation. mdu1-deletion-hardening-v1.0.patch (27,227 bytes)
Index: scribus/fileloader.cpp
===================================================================
--- scribus/fileloader.cpp (revision 27861)
+++ scribus/fileloader.cpp (working copy)
@@ -261,6 +261,8 @@
break;
}
}
+ if (ret)
+ currDoc->ensureDefaultMasterPages();
return ret;
}
Index: scribus/plugins/fileloader/scribus171format/scribus171format.cpp
===================================================================
--- scribus/plugins/fileloader/scribus171format/scribus171format.cpp (revision 27861)
+++ scribus/plugins/fileloader/scribus171format/scribus171format.cpp (working copy)
@@ -4816,6 +4816,8 @@
m_Doc->setMasterPageMode(!pageName.isEmpty());
const QUuid storedMasterId(attrs.valueAsString("MasterPageId"));
ScPage* newPage = pageName.isEmpty() ? doc->addPage(pageNum) : doc->addMasterPage(pageNum, pageName, storedMasterId);
+ if (!pageName.isEmpty() && attrs.valueAsBool("DefaultMaster", false))
+ doc->setDefaultMasterPage(newPage->masterPageId());
//Remove uppercase in 1.8
if (attrs.hasAttribute("LEFT"))
Index: scribus/plugins/fileloader/scribus171format/scribus171format_save.cpp
===================================================================
--- scribus/plugins/fileloader/scribus171format/scribus171format_save.cpp (revision 27861)
+++ scribus/plugins/fileloader/scribus171format/scribus171format_save.cpp (working copy)
@@ -1798,6 +1798,8 @@
docu.writeAttribute("MasterPageName",page->masterPageName());
if (!page->masterPageId().isNull())
docu.writeAttribute("MasterPageId", page->masterPageId().toString(QUuid::WithoutBraces));
+ if (master && doc->isDefaultMasterPage(page->masterPageId()))
+ docu.writeAttribute("DefaultMaster", 1);
if (!page->appliedMasterPageId().isNull())
docu.writeAttribute("AppliedMasterPageId", page->appliedMasterPageId().toString(QUuid::WithoutBraces));
PageSize ps(page->width(), page->height());
Index: scribus/plugins/scriptplugin/cmddoc.cpp
===================================================================
--- scribus/plugins/scriptplugin/cmddoc.cpp (revision 27861)
+++ scribus/plugins/scriptplugin/cmddoc.cpp (working copy)
@@ -502,14 +502,15 @@
PyErr_SetString(PyExc_ValueError, "Master page does not exist");
return nullptr;
}
- if (masterPageName == "Normal")
+ const QUuid masterId = currentDoc->masterPageId(masterPageName);
+ if (!currentDoc->canDeleteMasterPage(masterId))
{
- PyErr_SetString(PyExc_ValueError, "Can not delete the Normal master page");
+ PyErr_SetString(PyExc_ValueError, "Can not delete a default master page");
return nullptr;
}
bool oldMode = currentDoc->masterPageMode();
currentDoc->setMasterPageMode(true);
- ScCore->primaryMainWindow()->deletePage2(currentDoc->MasterNames[masterPageName]);
+ ScCore->primaryMainWindow()->deletePage2(currentDoc->masterPageIndex(masterId));
currentDoc->setMasterPageMode(oldMode);
Py_RETURN_NONE;
Index: scribus/scribus.cpp
===================================================================
--- scribus/scribus.cpp (revision 27861)
+++ scribus/scribus.cpp (working copy)
@@ -6099,6 +6099,13 @@
if (HaveDoc && doc->appMode == modeEditClip)
view->requestMode(submodeEndNodeEdit);
view->deselectItems(true);
+ if (doc->masterPageMode())
+ {
+ if (pg < 0 || pg >= doc->Pages->count())
+ return;
+ if (!doc->canDeleteMasterPage(doc->Pages->at(pg)->masterPageId()))
+ return;
+ }
if (doc->Pages->count() == 1)
return;
deletePage(pg + 1, pg + 1);
@@ -6127,6 +6134,15 @@
activeTransaction = m_undoManager->beginTransaction(doc->documentFileName(), Um::IDocument,
(from - to == 0) ? Um::DeletePage : Um::DeletePages, QString(),
Um::IDelete);
+ if (doc->masterPageMode())
+ {
+ for (int a = to - 1; a >= from - 1; --a)
+ {
+ const QUuid masterId = doc->Pages->at(a)->masterPageId();
+ if (doc->canDeleteMasterPage(masterId))
+ doc->replaceMasterPage(masterId);
+ }
+ }
PageItem* ite;
doc->m_Selection->clear();
Selection tmpSelection(this, false);
@@ -6162,14 +6178,34 @@
{
if (UndoManager::undoEnabled())
{
+ ScPage* page = doc->Pages->at(a);
auto *ss = new SimpleState(Um::DeletePage, QString(), Um::ICreate);
ss->set("DELETE_PAGE");
ss->set("PAGENR", a + 1);
- ss->set("PAGENAME", doc->Pages->at(a)->pageName());
- ss->set("MASTERPAGE", doc->Pages->at(a)->masterPageName());
- ss->set("MASTER_PAGE_ID", doc->Pages->at(a)->masterPageId().toString(QUuid::WithoutBraces));
- ss->set("APPLIED_MASTER_PAGE_ID", doc->Pages->at(a)->appliedMasterPageId().toString(QUuid::WithoutBraces));
+ ss->set("PAGENAME", page->pageName());
+ ss->set("MASTERPAGE", page->masterPageName());
+ ss->set("MASTER_PAGE_ID", page->masterPageId().toString(QUuid::WithoutBraces));
+ ss->set("APPLIED_MASTER_PAGE_ID", page->appliedMasterPageId().toString(QUuid::WithoutBraces));
ss->set("MASTER_PAGE_MODE", doc->masterPageMode());
+ if (doc->masterPageMode())
+ {
+ ss->set("PAGE_WIDTH", page->width());
+ ss->set("PAGE_HEIGHT", page->height());
+ ss->set("PAGE_INITIAL_WIDTH", page->initialWidth());
+ ss->set("PAGE_INITIAL_HEIGHT", page->initialHeight());
+ ss->set("PAGE_ORIENTATION", page->orientation());
+ ss->set("PAGE_SIZE", page->size());
+ ss->set("PAGE_LEFT", page->LeftPg);
+ ss->set("PAGE_MARGIN_PRESET", page->marginPreset);
+ ss->set("PAGE_MARGIN_TOP", page->Margins.top());
+ ss->set("PAGE_MARGIN_BOTTOM", page->Margins.bottom());
+ ss->set("PAGE_MARGIN_LEFT", page->Margins.left());
+ ss->set("PAGE_MARGIN_RIGHT", page->Margins.right());
+ ss->set("PAGE_INITIAL_MARGIN_TOP", page->initialMargins.top());
+ ss->set("PAGE_INITIAL_MARGIN_BOTTOM", page->initialMargins.bottom());
+ ss->set("PAGE_INITIAL_MARGIN_LEFT", page->initialMargins.left());
+ ss->set("PAGE_INITIAL_MARGIN_RIGHT", page->initialMargins.right());
+ }
// replace the deleted page in the undostack by a dummy object that will
// replaced with the "undone" page if user choose to undo the action
auto *duo = new DummyUndoObject();
@@ -7961,8 +7997,28 @@
{
slotNewMasterPage(wo, pageName);
const QUuid restoredId(state->get("MASTER_PAGE_ID"));
+ ScPage* restoredPage = doc->Pages->at(pagenr - 1);
if (!restoredId.isNull())
- doc->Pages->at(pagenr - 1)->setMasterPageId(restoredId);
+ restoredPage->setMasterPageId(restoredId);
+ if (state->contains("PAGE_WIDTH"))
+ {
+ restoredPage->setInitialWidth(state->getDouble("PAGE_INITIAL_WIDTH"));
+ restoredPage->setInitialHeight(state->getDouble("PAGE_INITIAL_HEIGHT"));
+ restoredPage->setWidth(state->getDouble("PAGE_WIDTH"));
+ restoredPage->setHeight(state->getDouble("PAGE_HEIGHT"));
+ restoredPage->setOrientation(state->getInt("PAGE_ORIENTATION"));
+ restoredPage->setSize(state->get("PAGE_SIZE"));
+ restoredPage->LeftPg = state->getInt("PAGE_LEFT");
+ restoredPage->marginPreset = state->getInt("PAGE_MARGIN_PRESET");
+ restoredPage->Margins.setTop(state->getDouble("PAGE_MARGIN_TOP"));
+ restoredPage->Margins.setBottom(state->getDouble("PAGE_MARGIN_BOTTOM"));
+ restoredPage->Margins.setLeft(state->getDouble("PAGE_MARGIN_LEFT"));
+ restoredPage->Margins.setRight(state->getDouble("PAGE_MARGIN_RIGHT"));
+ restoredPage->initialMargins.setTop(state->getDouble("PAGE_INITIAL_MARGIN_TOP"));
+ restoredPage->initialMargins.setBottom(state->getDouble("PAGE_INITIAL_MARGIN_BOTTOM"));
+ restoredPage->initialMargins.setLeft(state->getDouble("PAGE_INITIAL_MARGIN_LEFT"));
+ restoredPage->initialMargins.setRight(state->getDouble("PAGE_INITIAL_MARGIN_RIGHT"));
+ }
}
else
{
@@ -7986,6 +8042,8 @@
if (currMasterPageMode != savedMasterPageMode)
doc->setMasterPageMode(currMasterPageMode);
doc->rebuildMasterNames();
+ if (savedMasterPageMode)
+ view->reformPages();
pagePalette->updateMasterPageList();
pagePalette->rebuildPages();
if (outlinePalette->isVisible())
Index: scribus/scribusdoc.cpp
===================================================================
--- scribus/scribusdoc.cpp (revision 27861)
+++ scribus/scribusdoc.cpp (working copy)
@@ -273,6 +273,7 @@
Q_CHECK_PTR(autoSaveTimer);
m_uuid = QUuid::createUuid();
+ m_defaultMasterPageIds.clear();
m_docPrefsData.colorPrefs.DCMSset.CMSinUse = false;
@@ -2797,7 +2798,7 @@
bool ScribusDoc::renameMasterPage(const QUuid& masterId, const QString& newPageName)
{
const int number = masterPageIndex(masterId);
- if (number < 0 || MasterNames.contains(newPageName))
+ if (number < 0 || isDefaultMasterPage(masterId) || MasterNames.contains(newPageName))
return false;
const QString oldPageName = MasterPages.at(number)->pageName();
@@ -2837,6 +2838,7 @@
assert( Pages->count() > 1 && Pages->count() > pageNumber );
setCurrentPage(Pages->at(0));
ScPage* page = Pages->takeAt(pageNumber);
+ m_defaultMasterPageIds.remove(page->masterPageId());
delete page;
// #10658 : renumber masterpages and masterpage objects
// in order to avoid crash after masterpage deletion
@@ -2886,48 +2888,115 @@
return it == MasterPageIds.constEnd() ? -1 : it.value();
}
+bool ScribusDoc::isDefaultMasterPage(const QUuid& masterId) const
+{
+ return !masterId.isNull() && m_defaultMasterPageIds.contains(masterId);
+}
-void ScribusDoc::replaceMasterPage(const QString& oldMasterPage)
+bool ScribusDoc::canDeleteMasterPage(const QUuid& masterId) const
{
- uint pageIndex = 0;
- QMap<QString,int>::Iterator it = MasterNames.begin();
- QListIterator<ScPage *> dpIt(DocPages);
- ScPage* docPage = nullptr;
- while (dpIt.hasNext())
- {
- docPage = dpIt.next();
- if (docPage->masterPageName() == oldMasterPage)
- {
- PageLocation pageLoc = locationOfPage(pageIndex);
- if (pageLoc == LeftPage)
- {
- if (MasterNames.contains(CommonStrings::trMasterPageNormalLeft))
- docPage->setMasterPageName(CommonStrings::trMasterPageNormalLeft);
- else if (MasterNames.contains(CommonStrings::trMasterPageNormal))
- docPage->setMasterPageNameNormal();
- else
- docPage->setMasterPageName(it.key());
- }
- else if (pageLoc == RightPage)
- {
- if (MasterNames.contains(CommonStrings::trMasterPageNormalRight))
- docPage->setMasterPageName(CommonStrings::trMasterPageNormalRight);
- else if (MasterNames.contains(CommonStrings::trMasterPageNormal))
- docPage->setMasterPageNameNormal();
- else
- docPage->setMasterPageName(it.key());
- }
- else
- {
- if (MasterNames.contains(CommonStrings::trMasterPageNormalMiddle))
- docPage->setMasterPageName(CommonStrings::trMasterPageNormalMiddle);
- else if (MasterNames.contains(CommonStrings::trMasterPageNormal))
- docPage->setMasterPageNameNormal();
- else
- docPage->setMasterPageName(it.key());
- }
+ return MasterPages.count() > 1 && masterPageIndex(masterId) >= 0 && !isDefaultMasterPage(masterId);
+}
+
+void ScribusDoc::setDefaultMasterPage(const QUuid& masterId, bool isDefault)
+{
+ if (masterId.isNull())
+ return;
+ if (isDefault)
+ m_defaultMasterPageIds.insert(masterId);
+ else
+ m_defaultMasterPageIds.remove(masterId);
+}
+
+void ScribusDoc::ensureDefaultMasterPages()
+{
+ if (!m_defaultMasterPageIds.isEmpty())
+ return;
+
+ const int columns = pageSets()[m_docPrefsData.docSetupPrefs.pagePositioning].Columns;
+ for (ScPage* master : std::as_const(MasterPages))
+ {
+ const QString& name = master->pageName();
+ const bool normal = name == CommonStrings::masterPageNormal || name == CommonStrings::trMasterPageNormal;
+ const bool normalLeft = name == CommonStrings::masterPageNormalLeft || name == CommonStrings::trMasterPageNormalLeft;
+ const bool normalMiddle = name == CommonStrings::masterPageNormalMiddle || name == CommonStrings::trMasterPageNormalMiddle;
+ const bool normalRight = name == CommonStrings::masterPageNormalRight || name == CommonStrings::trMasterPageNormalRight;
+ bool legacyDefault = columns == 1 && normal;
+ legacyDefault |= columns > 1 && normalLeft && master->LeftPg == 1;
+ legacyDefault |= columns > 1 && normalRight && master->LeftPg == 0;
+ legacyDefault |= columns > 2 && normalMiddle && master->LeftPg > 1;
+ if (legacyDefault)
+ m_defaultMasterPageIds.insert(master->masterPageId());
+ }
+
+ // Older files may contain translated default names from another UI language.
+ // Default masters have historically been created first, so use that ordering
+ // only when name-based migration found nothing.
+ if (m_defaultMasterPageIds.isEmpty() && !MasterPages.isEmpty())
+ {
+ const int expectedDefaults = columns == 1 ? 1 : (columns == 2 ? 2 : 3);
+ for (int i = 0; i < qMin(expectedDefaults, MasterPages.count()); ++i)
+ m_defaultMasterPageIds.insert(MasterPages.at(i)->masterPageId());
+ }
+}
+
+QUuid ScribusDoc::defaultMasterPageId(int pageIndex) const
+{
+ if (m_defaultMasterPageIds.isEmpty())
+ return QUuid();
+
+ if (pagePositioning() == singlePage)
+ {
+ for (ScPage* master : MasterPages)
+ {
+ if (isDefaultMasterPage(master->masterPageId()))
+ return master->masterPageId();
}
- pageIndex++;
+ return QUuid();
+ }
+
+ const PageLocation pageLoc = locationOfPage(pageIndex);
+ for (ScPage* master : MasterPages)
+ {
+ if (!isDefaultMasterPage(master->masterPageId()))
+ continue;
+ if (pageLoc == LeftPage && master->LeftPg == 1)
+ return master->masterPageId();
+ if (pageLoc == RightPage && master->LeftPg == 0)
+ return master->masterPageId();
+ if (pageLoc == MiddlePage && master->LeftPg > 1)
+ return master->masterPageId();
+ }
+
+ for (ScPage* master : MasterPages)
+ {
+ if (isDefaultMasterPage(master->masterPageId()))
+ return master->masterPageId();
+ }
+ return QUuid();
+}
+
+
+void ScribusDoc::replaceMasterPage(const QString& oldMasterPage)
+{
+ replaceMasterPage(masterPageId(oldMasterPage));
+}
+
+void ScribusDoc::replaceMasterPage(const QUuid& oldMasterPageId)
+{
+ if (oldMasterPageId.isNull())
+ return;
+ const QString oldMasterPageName = masterPageName(oldMasterPageId);
+
+ for (int pageIndex = 0; pageIndex < DocPages.count(); ++pageIndex)
+ {
+ ScPage* docPage = DocPages.at(pageIndex);
+ if (docPage->appliedMasterPageId() != oldMasterPageId && docPage->masterPageName() != oldMasterPageName)
+ continue;
+
+ const QUuid replacementId = defaultMasterPageId(pageIndex);
+ if (!replacementId.isNull() && replacementId != oldMasterPageId)
+ applyMasterPage(replacementId, pageIndex);
}
}
@@ -14710,15 +14779,18 @@
int setcol = pageSets()[m_docPrefsData.docSetupPrefs.pagePositioning].Columns;
if (setcol == 1)
{
- addMasterPage(0, CommonStrings::trMasterPageNormal);
+ ScPage* mp = addMasterPage(0, CommonStrings::trMasterPageNormal);
+ setDefaultMasterPage(mp->masterPageId());
}
else if (setcol == 2)
{
ScPage *lp = addMasterPage(0, CommonStrings::trMasterPageNormalLeft);
+ setDefaultMasterPage(lp->masterPageId());
lp->LeftPg = 1;
lp->Margins.setLeft(lp->initialMargins.right());
lp->Margins.setRight(lp->initialMargins.left());
lp = addMasterPage(1, CommonStrings::trMasterPageNormalRight);
+ setDefaultMasterPage(lp->masterPageId());
lp->LeftPg = 0;
lp->Margins.setRight(lp->initialMargins.right());
lp->Margins.setLeft(lp->initialMargins.left());
@@ -14726,14 +14798,17 @@
else if ((setcol == 3) || (setcol == 4))
{
ScPage *lp = addMasterPage(0, CommonStrings::trMasterPageNormalLeft);
+ setDefaultMasterPage(lp->masterPageId());
lp->LeftPg = 1;
lp->Margins.setLeft(lp->initialMargins.right());
lp->Margins.setRight(lp->initialMargins.left());
lp = addMasterPage(1, CommonStrings::trMasterPageNormalRight);
+ setDefaultMasterPage(lp->masterPageId());
lp->LeftPg = 0;
lp->Margins.setRight(lp->initialMargins.right());
lp->Margins.setLeft(lp->initialMargins.left());
lp = addMasterPage(2, CommonStrings::trMasterPageNormalMiddle);
+ setDefaultMasterPage(lp->masterPageId());
lp->LeftPg = 2;
lp->Margins.setLeft(lp->initialMargins.left());
lp->Margins.setRight(lp->initialMargins.left());
Index: scribus/scribusdoc.h
===================================================================
--- scribus/scribusdoc.h (revision 27861)
+++ scribus/scribusdoc.h (working copy)
@@ -36,6 +36,7 @@
#include <QObject>
#include <QPixmap>
#include <QRectF>
+#include <QSet>
#include <QStringList>
#include <QTimer>
#include <QUuid>
@@ -268,8 +269,14 @@
QUuid masterPageId(const QString& pageName) const;
QString masterPageName(const QUuid& masterId) const;
int masterPageIndex(const QUuid& masterId) const;
+ bool isDefaultMasterPage(const QUuid& masterId) const;
+ bool canDeleteMasterPage(const QUuid& masterId) const;
+ void setDefaultMasterPage(const QUuid& masterId, bool isDefault = true);
+ void ensureDefaultMasterPages();
+ QUuid defaultMasterPageId(int pageIndex) const;
//! @brief Replace a master page by default one
void replaceMasterPage(const QString& oldMasterPage);
+ void replaceMasterPage(const QUuid& oldMasterPageId);
//! @brief Rename a master page
bool renameMasterPage(const QString& oldPageName, const QString& newPageName);
bool renameMasterPage(const QUuid& masterId, const QString& newPageName);
@@ -1553,6 +1560,7 @@
void itemResizeToMargin(PageItem* item, int direction); //direction reflect enum numbers from Canvas::FrameHandle
private:
+ QSet<QUuid> m_defaultMasterPageIds;
UndoTransaction m_itemCreationTransaction;
UndoTransaction m_alignTransaction;
Index: scribus/ui/pagepalette_masterpages.cpp
===================================================================
--- scribus/ui/pagepalette_masterpages.cpp (revision 27861)
+++ scribus/ui/pagepalette_masterpages.cpp (working copy)
@@ -105,7 +105,9 @@
connect(deleteButton , SIGNAL(clicked()), this, SLOT(deleteMasterPage()));
connect(newButton , SIGNAL(clicked()), this, SLOT(newMasterPage()));
connect(importButton , SIGNAL(clicked()), this, SLOT(importPage()));
- connect(masterPageListBox, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(selectMasterPage(QListWidgetItem*)));
+ connect(masterPageListBox, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
+ this, SLOT(currentMasterPageChanged(QListWidgetItem*,QListWidgetItem*)));
+ connect(masterPageListBox, SIGNAL(itemSelectionChanged()), this, SLOT(updateDeleteButton()));
connect(masterPageListBox, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(renameMasterPage(QListWidgetItem*)));
connect(finishButton , SIGNAL(released()), this, SIGNAL(finished()));
}
@@ -116,8 +118,9 @@
disconnect(deleteButton , SIGNAL(clicked()), this, SLOT(deleteMasterPage()));
disconnect(newButton , SIGNAL(clicked()), this, SLOT(newMasterPage()));
disconnect(importButton , SIGNAL(clicked()), this, SLOT(importPage()));
- disconnect(masterPageListBox, SIGNAL(itemClicked(QListWidgetItem*)),
- this, SLOT(selectMasterPage(QListWidgetItem*)));
+ disconnect(masterPageListBox, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
+ this, SLOT(currentMasterPageChanged(QListWidgetItem*,QListWidgetItem*)));
+ disconnect(masterPageListBox, SIGNAL(itemSelectionChanged()), this, SLOT(updateDeleteButton()));
disconnect(masterPageListBox, SIGNAL(itemChanged(QListWidgetItem*)),
this, SLOT(renameMasterPage( QListWidgetItem*)));
disconnect(finishButton , SIGNAL(released()), this, SIGNAL(finished()));
@@ -154,14 +157,22 @@
void PagePalette_MasterPages::deleteMasterPage()
{
bool forceDelete = false;
+ const QUuid displayedMasterId = m_doc->masterPageId(m_currentPage);
+ QList<QUuid> selectedMasterIds;
+ for (QListWidgetItem* item : masterPageListBox->selectedItems())
+ {
+ const QUuid masterId = m_doc->masterPageId(item->data(Qt::UserRole).toString());
+ if (m_doc->canDeleteMasterPage(masterId))
+ selectedMasterIds.append(masterId);
+ }
// allow to delete multiple pages in one step
- foreach (QListWidgetItem * delItem, masterPageListBox->selectedItems())
+ for (const QUuid& masterId : selectedMasterIds)
{
- m_currentPage = delItem->text();
-
- if (isReservedName(m_currentPage))
+ const QString masterPageName = m_doc->masterPageName(masterId);
+ if (masterPageName.isEmpty() || !m_doc->canDeleteMasterPage(masterId))
continue;
+ m_currentPage = masterPageName;
QString extraWarn;
@@ -189,17 +200,18 @@
if (m_doc->appMode == modeEditClip)
m_view->requestMode(submodeEndNodeEdit);
- m_doc->scMW()->deletePage2(m_doc->MasterNames[m_currentPage]);
- //<<CB TODO Move back into ScribusDoc::deleteMasterPage();
- //This must happen after the pages have been reformed (view/doc)
- m_doc->rebuildMasterNames();
- // Fix up any pages that refer to the deleted master page
- m_doc->replaceMasterPage(m_currentPage);
+ m_doc->scMW()->deletePage2(m_doc->masterPageIndex(masterId));
}
- // set the 1st MP for the other slots
- auto it = m_doc->MasterNames.cbegin();
- m_currentPage = it.key();
+ if (m_doc->masterPageIndex(displayedMasterId) >= 0)
+ m_currentPage = m_doc->masterPageName(displayedMasterId);
+ else
+ {
+ const QUuid defaultId = m_doc->defaultMasterPageId(0);
+ m_currentPage = m_doc->masterPageName(defaultId);
+ if (m_currentPage.isEmpty() && !m_doc->MasterPages.isEmpty())
+ m_currentPage = m_doc->MasterPages.first()->pageName();
+ }
updateMasterPageList(m_currentPage);
selectMasterPage(m_currentPage);
}
@@ -388,13 +400,10 @@
void PagePalette_MasterPages::selectMasterPage(QListWidgetItem *item)
{
+ if (!item)
+ return;
m_currentPage = item->data(Qt::UserRole).toString();
-
- bool deleteEnabled = true;
- deleteEnabled &= (m_doc->MasterNames.count() > 1);
- deleteEnabled &= (m_currentPage != CommonStrings::masterPageNormal);
- deleteEnabled &= (m_currentPage != CommonStrings::trMasterPageNormal);
- deleteButton->setEnabled(deleteEnabled);
+ updateDeleteButton();
if (m_doc->appMode == modeEditClip)
m_view->requestMode(submodeEndNodeEdit);
@@ -405,40 +414,63 @@
this->setEnabled(true);
}
+void PagePalette_MasterPages::currentMasterPageChanged(QListWidgetItem *current, QListWidgetItem *previous)
+{
+ Q_UNUSED(previous);
+ selectMasterPage(current);
+}
+
+void PagePalette_MasterPages::updateDeleteButton()
+{
+ bool deleteEnabled = false;
+ if (m_doc)
+ {
+ for (QListWidgetItem* item : masterPageListBox->selectedItems())
+ {
+ const QUuid masterId = m_doc->masterPageId(item->data(Qt::UserRole).toString());
+ if (m_doc->canDeleteMasterPage(masterId))
+ {
+ deleteEnabled = true;
+ break;
+ }
+ }
+ }
+ deleteButton->setEnabled(deleteEnabled);
+}
+
void PagePalette_MasterPages::selectMasterPage(const QString& name)
{
if (!m_doc || !m_view)
return;
m_currentPage.clear();
+ QListWidgetItem* targetItem = nullptr;
for (int i = 0; i < masterPageListBox->count(); ++i)
{
QListWidgetItem* item = masterPageListBox->item(i);
QString realPageName = item->data(Qt::UserRole).toString();
- if (item->text() == name)
+ if (item->text() == name || realPageName == name)
{
m_currentPage = realPageName;
- break;
- }
- if (realPageName == name)
- {
- m_currentPage = name;
+ targetItem = item;
break;
}
}
-
+
if (m_currentPage.isEmpty())
m_currentPage = m_doc->MasterPages.at(0)->pageName();
- bool deleteEnabled = true;
- deleteEnabled &= (m_doc->MasterNames.count() > 1);
- deleteEnabled &= (m_currentPage != CommonStrings::masterPageNormal);
- deleteEnabled &= (m_currentPage != CommonStrings::trMasterPageNormal);
- deleteButton->setEnabled(deleteEnabled);
+ if (targetItem)
+ {
+ masterPageListBox->clearSelection();
+ masterPageListBox->setCurrentItem(targetItem);
+ targetItem->setSelected(true);
+ }
if (m_doc->appMode == modeEditClip)
m_view->requestMode(submodeEndNodeEdit);
m_view->showMasterPage(m_doc->MasterNames[m_currentPage]);
+ updateDeleteButton();
this->setEnabled(true);
}
@@ -463,7 +495,7 @@
mpName = CommonStrings::trMasterPageNormal;
QListWidgetItem* mpItem = new QListWidgetItem(mpName);
- if (!isReservedName(mpName))
+ if (!m_doc->isDefaultMasterPage(m_doc->masterPageId(it.key())))
mpItem->setFlags(mpItem->flags() |= Qt::ItemIsEditable);
else
mpItem->setFlags(mpItem->flags() &= ~Qt::ItemIsEditable);
@@ -471,15 +503,15 @@
mpItem->setData(Qt::UserRole, it.key());
masterPageListBox->addItem(mpItem);
}
- deleteButton->setEnabled(m_doc->MasterNames.count() != 1);
if (masterPageName == CommonStrings::masterPageNormal)
- {
masterPageName = CommonStrings::trMasterPageNormal;
- deleteButton->setEnabled(false);
- }
QList<QListWidgetItem *> itL = masterPageListBox->findItems(masterPageName, Qt::MatchExactly);
if (itL.count() != 0)
+ {
+ masterPageListBox->setCurrentItem(itL.at(0));
itL.at(0)->setSelected(true);
+ }
+ updateDeleteButton();
}
void PagePalette_MasterPages::renameMasterPage(QListWidgetItem * item)
Index: scribus/ui/pagepalette_masterpages.h
===================================================================
--- scribus/ui/pagepalette_masterpages.h (revision 27861)
+++ scribus/ui/pagepalette_masterpages.h (working copy)
@@ -68,6 +68,8 @@
void renameMasterPage(QListWidgetItem * item);
void importPage();
void selectMasterPage(QListWidgetItem *item);
+ void currentMasterPageChanged(QListWidgetItem *current, QListWidgetItem *previous);
+ void updateDeleteButton();
signals:
void removePage(int);
Index: scribus/ui/pagepalette_pages.cpp
===================================================================
--- scribus/ui/pagepalette_pages.cpp (revision 27861)
+++ scribus/ui/pagepalette_pages.cpp (working copy)
@@ -86,7 +86,8 @@
void PagePalette_Pages::deleteMasterPage(const QString &tmp)
{
- if (tmp == CommonStrings::trMasterPageNormal)
+ const QUuid masterId = currView->m_doc->masterPageId(tmp);
+ if (!currView->m_doc->canDeleteMasterPage(masterId))
return;
QString extraWarn;
for (int i = 0; i < currView->m_doc->DocPages.count(); ++i)
@@ -109,12 +110,7 @@
int storedViewYCoor = currView->verticalScrollBar()->value();
currView->m_doc->setMasterPageMode(true);
- currView->m_doc->scMW()->deletePage2(currView->m_doc->MasterNames[tmp]);
- //<<CB TODO Move back into ScribusDoc::deleteMasterPage();
- //This must happen after the pages have been reformed (view/doc)
- currView->m_doc->rebuildMasterNames();
- // Fix up any pages that refer to the deleted master page
- currView->m_doc->replaceMasterPage(tmp);
+ currView->m_doc->scMW()->deletePage2(currView->m_doc->masterPageIndex(masterId));
currView->m_doc->setMasterPageMode(oldMPMode);
currView->m_doc->setModified(true);
Index: scribus/ui/pagepalette_widgets.cpp
===================================================================
--- scribus/ui/pagepalette_widgets.cpp (revision 27861)
+++ scribus/ui/pagepalette_widgets.cpp (working copy)
@@ -117,7 +117,7 @@
if (currentItem())
{
e->accept();
- emit delMasterRequest(currentItem()->text());
+ emit delMasterRequest(currentItem()->data(Qt::UserRole).toString());
return;
}
}
mdu2-delete-unused-v1.33.patch (47,161 bytes)
Index: scribus/CMakeLists_Sources.txt
===================================================================
--- scribus/CMakeLists_Sources.txt (revision 27861)
+++ scribus/CMakeLists_Sources.txt (working copy)
@@ -412,6 +412,7 @@
ui/pageitemattributes.cpp
ui/pagepalette.cpp
ui/pagepalette_pages.cpp
+ ui/masterpagedeletedialog.cpp
ui/pagepalette_masterpages.cpp
ui/pagepalette_widgets.cpp
ui/pagepropertiesdialog.cpp
Index: scribus/scribus.cpp
===================================================================
--- scribus/scribus.cpp (revision 27861)
+++ scribus/scribus.cpp (working copy)
@@ -6111,6 +6111,67 @@
deletePage(pg + 1, pg + 1);
}
+void ScribusMainWindow::deleteMasterPages(const QList<QUuid>& masterIds, const QMap<int, QUuid>& replacementMasterIds)
+{
+ if (!HaveDoc || !doc->masterPageMode())
+ return;
+
+ QList<QUuid> deletableMasterIds;
+ for (const QUuid& masterId : masterIds)
+ {
+ if (!deletableMasterIds.contains(masterId) && doc->canDeleteMasterPage(masterId))
+ deletableMasterIds.append(masterId);
+ }
+ if (deletableMasterIds.isEmpty())
+ return;
+
+ QMap<int, QUuid> validReplacements;
+ for (auto it = replacementMasterIds.constBegin(); it != replacementMasterIds.constEnd(); ++it)
+ {
+ const int pageIndex = it.key();
+ const QUuid replacementId = it.value();
+ if (pageIndex < 0 || pageIndex >= doc->DocPages.count())
+ continue;
+ if (replacementId.isNull() || deletableMasterIds.contains(replacementId) || doc->masterPageIndex(replacementId) < 0)
+ continue;
+
+ const ScPage* page = doc->DocPages.at(pageIndex);
+ const QUuid currentMasterId = page->appliedMasterPageId();
+ if (!deletableMasterIds.contains(currentMasterId))
+ {
+ bool deletingLegacyAssignment = false;
+ for (const QUuid& masterId : deletableMasterIds)
+ {
+ if (page->masterPageName() == doc->masterPageName(masterId))
+ {
+ deletingLegacyAssignment = true;
+ break;
+ }
+ }
+ if (!deletingLegacyAssignment)
+ continue;
+ }
+ validReplacements.insert(pageIndex, replacementId);
+ }
+
+ UndoTransaction transaction;
+ if (UndoManager::undoEnabled() && (deletableMasterIds.count() > 1 || !validReplacements.isEmpty()))
+ transaction = m_undoManager->beginTransaction(doc->documentFileName(), Um::IDocument, Um::DeletePages, QString(), Um::IDelete);
+
+ for (auto it = validReplacements.constBegin(); it != validReplacements.constEnd(); ++it)
+ doc->applyMasterPage(it.value(), it.key());
+
+ for (const QUuid& masterId : deletableMasterIds)
+ {
+ const int masterIndex = doc->masterPageIndex(masterId);
+ if (masterIndex >= 0 && doc->canDeleteMasterPage(masterId))
+ deletePage2(masterIndex);
+ }
+
+ if (transaction)
+ transaction.commit();
+}
+
void ScribusMainWindow::deletePage()
{
if (HaveDoc && doc->appMode == modeEditClip)
Index: scribus/scribus.h
===================================================================
--- scribus/scribus.h (revision 27861)
+++ scribus/scribus.h (working copy)
@@ -450,6 +450,7 @@
*/
void deletePage(int from, int to);
void deletePage2(int pg);
+ void deleteMasterPages(const QList<QUuid>& masterIds, const QMap<int, QUuid>& replacementMasterIds = QMap<int, QUuid>());
/** \brief Verschiebt Seiten */
void movePage();
void copyPage();
Index: scribus/scribusdoc.cpp
===================================================================
--- scribus/scribusdoc.cpp (revision 27861)
+++ scribus/scribusdoc.cpp (working copy)
@@ -2898,6 +2898,33 @@
return MasterPages.count() > 1 && masterPageIndex(masterId) >= 0 && !isDefaultMasterPage(masterId);
}
+QList<int> ScribusDoc::masterPageUsage(const QUuid& masterId) const
+{
+ QList<int> pages;
+ if (masterId.isNull())
+ return pages;
+ const QString masterName = masterPageName(masterId);
+ for (int pageIndex = 0; pageIndex < DocPages.count(); ++pageIndex)
+ {
+ const ScPage* page = DocPages.at(pageIndex);
+ if (page->appliedMasterPageId() == masterId || (!masterName.isEmpty() && page->masterPageName() == masterName))
+ pages.append(pageIndex);
+ }
+ return pages;
+}
+
+QList<QUuid> ScribusDoc::unusedCustomMasterPageIds() const
+{
+ QList<QUuid> masterIds;
+ for (const ScPage* master : MasterPages)
+ {
+ const QUuid masterId = master->masterPageId();
+ if (canDeleteMasterPage(masterId) && masterPageUsage(masterId).isEmpty())
+ masterIds.append(masterId);
+ }
+ return masterIds;
+}
+
void ScribusDoc::setDefaultMasterPage(const QUuid& masterId, bool isDefault)
{
if (masterId.isNull())
Index: scribus/scribusdoc.h
===================================================================
--- scribus/scribusdoc.h (revision 27861)
+++ scribus/scribusdoc.h (working copy)
@@ -271,6 +271,8 @@
int masterPageIndex(const QUuid& masterId) const;
bool isDefaultMasterPage(const QUuid& masterId) const;
bool canDeleteMasterPage(const QUuid& masterId) const;
+ QList<int> masterPageUsage(const QUuid& masterId) const;
+ QList<QUuid> unusedCustomMasterPageIds() const;
void setDefaultMasterPage(const QUuid& masterId, bool isDefault = true);
void ensureDefaultMasterPages();
QUuid defaultMasterPageId(int pageIndex) const;
Index: scribus/ui/masterpagedeletedialog.cpp
===================================================================
--- scribus/ui/masterpagedeletedialog.cpp (nonexistent)
+++ scribus/ui/masterpagedeletedialog.cpp (working copy)
@@ -0,0 +1,814 @@
+/*
+For general Scribus (>=1.3.2) copyright and licensing information please refer
+to the COPYING file provided with the program. Following this notice may exist
+a copyright and/or license notice that predates the release of Scribus 1.3.2
+for which a new license (GPL+exception) is in place.
+*/
+#include "masterpagedeletedialog.h"
+
+#include <algorithm>
+
+#include <QStringList>
+
+#include <QAbstractScrollArea>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QDialogButtonBox>
+#include <QHBoxLayout>
+#include <QHeaderView>
+#include <QIcon>
+#include <QLabel>
+#include <QPushButton>
+#include <QResizeEvent>
+#include <QShowEvent>
+#include <QSignalBlocker>
+#include <QStyle>
+#include <QTableWidget>
+#include <QVBoxLayout>
+
+#include "commonstrings.h"
+#include "iconmanager.h"
+#include "pagestructs.h"
+#include "scpage.h"
+#include "scribusdoc.h"
+
+MasterPageDeleteDialog::MasterPageDeleteDialog(QWidget* parent, ScribusDoc* doc, const QList<QUuid>& masterIds)
+ : QDialog(parent),
+ m_doc(doc)
+{
+ setWindowTitle(tr("Delete Master Pages"));
+ setModal(true);
+
+ int protectedCount = 0;
+ QList<QUuid> seenIds;
+ for (const QUuid& masterId : masterIds)
+ {
+ if (!m_doc || masterId.isNull() || seenIds.contains(masterId) || m_doc->masterPageIndex(masterId) < 0)
+ continue;
+ seenIds.append(masterId);
+ if (m_doc->isDefaultMasterPage(masterId))
+ {
+ ++protectedCount;
+ continue;
+ }
+ if (!m_doc->canDeleteMasterPage(masterId))
+ continue;
+
+ MasterInfo info;
+ info.id = masterId;
+ info.name = m_doc->masterPageName(masterId);
+ info.pages = m_doc->masterPageUsage(masterId);
+ m_masterInfo.append(info);
+ m_deleteIds.append(masterId);
+ }
+
+ for (MasterInfo& info : m_masterInfo)
+ {
+ if (m_doc->pagePositioning() == singlePage)
+ info.allPages = info.pages;
+ else
+ {
+ for (int pageIndex : info.pages)
+ {
+ switch (m_doc->locationOfPage(pageIndex))
+ {
+ case LeftPage:
+ info.leftPages.append(pageIndex);
+ break;
+ case RightPage:
+ info.rightPages.append(pageIndex);
+ break;
+ case MiddlePage:
+ info.middlePages.append(pageIndex);
+ break;
+ }
+ }
+ }
+
+ if (!info.allPages.isEmpty())
+ info.allReplacementId = m_doc->defaultMasterPageId(info.allPages.first());
+ if (!info.leftPages.isEmpty())
+ info.leftReplacementId = m_doc->defaultMasterPageId(info.leftPages.first());
+ if (!info.rightPages.isEmpty())
+ info.rightReplacementId = m_doc->defaultMasterPageId(info.rightPages.first());
+ }
+
+ int usedCount = 0;
+ int totalUsage = 0;
+ for (const MasterInfo& info : m_masterInfo)
+ {
+ if (!info.pages.isEmpty())
+ ++usedCount;
+ totalUsage += info.pages.count();
+ }
+ const int unusedCount = m_masterInfo.count() - usedCount;
+
+ auto* layout = new QVBoxLayout(this);
+
+ const bool showReplacementColumn = usedCount > 0;
+ m_table = new QTableWidget(m_masterInfo.count(), showReplacementColumn ? 3 : 2, this);
+ if (showReplacementColumn)
+ m_table->setHorizontalHeaderLabels({ tr("Master page"), tr("Usage"), tr("Replacement") });
+ else
+ m_table->setHorizontalHeaderLabels({ tr("Master page"), tr("Usage") });
+ m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
+ m_table->setSelectionMode(QAbstractItemView::NoSelection);
+ m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ m_table->setAlternatingRowColors(true);
+ m_table->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow);
+ m_table->verticalHeader()->hide();
+ m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
+ m_table->horizontalHeader()->setStretchLastSection(false);
+ connect(m_table->horizontalHeader(), &QHeaderView::sectionResized, this, [this](int, int, int)
+ {
+ if (!m_adjustingColumns)
+ m_columnsUserAdjusted = true;
+ updateElidedToolTips();
+ for (const ReplacementControl& control : m_replacementControls)
+ updateComboToolTip(control.combo);
+ });
+
+ for (int row = 0; row < m_masterInfo.count(); ++row)
+ {
+ const MasterInfo& info = m_masterInfo.at(row);
+ auto* nameItem = new QTableWidgetItem(displayMasterName(info.name));
+ nameItem->setData(Qt::UserRole, info.id.toString(QUuid::WithoutBraces));
+ m_table->setItem(row, 0, nameItem);
+
+ QString usage;
+ if (info.pages.isEmpty())
+ usage = tr("Unused");
+ else if (info.pages.count() == 1)
+ usage = tr("1 page");
+ else
+ usage = tr("%1 pages").arg(info.pages.count());
+ auto* usageItem = new QTableWidgetItem(usage);
+ if (!info.pages.isEmpty())
+ usageItem->setToolTip(tr("Applied to pages: %1").arg(formatPageRanges(info.pages)));
+ m_table->setItem(row, 1, usageItem);
+
+ if (showReplacementColumn)
+ {
+ if (info.pages.isEmpty())
+ m_table->setItem(row, 2, new QTableWidgetItem(QString(QChar(0x2014))));
+ else
+ {
+ QWidget* replacementCell = createReplacementCell(row);
+ if (replacementCell)
+ m_table->setCellWidget(row, 2, replacementCell);
+ else
+ m_table->setItem(row, 2, new QTableWidgetItem(tr("Matching default")));
+ }
+ }
+ }
+ m_adjustingColumns = true;
+ m_table->resizeRowsToContents();
+ int uniformRowHeight = 0;
+ for (int row = 0; row < m_table->rowCount(); ++row)
+ uniformRowHeight = qMax(uniformRowHeight, m_table->rowHeight(row));
+ for (int row = 0; row < m_table->rowCount(); ++row)
+ m_table->setRowHeight(row, uniformRowHeight);
+ m_table->resizeColumnsToContents();
+ m_adjustingColumns = false;
+ layout->addWidget(m_table);
+
+ if (QWidget* bulkControls = createBulkReplacementControls(usedCount))
+ layout->addWidget(bulkControls);
+
+ if (protectedCount > 0)
+ {
+ auto* protectedLayout = new QHBoxLayout;
+ auto* infoIcon = new QLabel(this);
+ infoIcon->setPixmap(IconManager::instance().loadPixmap("information", 16));
+ infoIcon->setAlignment(Qt::AlignTop);
+ protectedLayout->addWidget(infoIcon);
+ auto* protectedLabel = new QLabel(this);
+ protectedLabel->setWordWrap(true);
+ if (protectedCount == 1)
+ protectedLabel->setText(tr("1 protected default master page will be kept."));
+ else
+ protectedLabel->setText(tr("%1 protected default master pages will be kept.").arg(protectedCount));
+ protectedLayout->addWidget(protectedLabel, 1);
+ layout->addLayout(protectedLayout);
+ }
+
+ QString summary;
+ if (m_masterInfo.count() == 1 && usedCount == 0)
+ summary = tr("Delete this unused master page?");
+ else if (m_masterInfo.count() == 1)
+ {
+ if (totalUsage == 1)
+ summary = tr("Delete this master page? Used on 1 document page.");
+ else
+ summary = tr("Delete this master page? Used on %1 document pages.").arg(totalUsage);
+ }
+ else if (usedCount == 0)
+ summary = tr("Delete %1 unused master pages?").arg(m_masterInfo.count());
+ else if (unusedCount == 0)
+ summary = tr("Delete %1 master pages? All are used.").arg(m_masterInfo.count());
+ else
+ {
+ const QString usedText = usedCount == 1 ? tr("1 used") : tr("%1 used").arg(usedCount);
+ const QString unusedText = unusedCount == 1 ? tr("1 unused") : tr("%1 unused").arg(unusedCount);
+ summary = tr("Delete %1 master pages? %2, %3.").arg(m_masterInfo.count()).arg(usedText).arg(unusedText);
+ }
+
+ layout->addSpacing(12);
+ auto* actionLayout = new QHBoxLayout;
+ auto* summaryLabel = new QLabel(summary, this);
+ summaryLabel->setWordWrap(true);
+ actionLayout->addWidget(summaryLabel, 1, Qt::AlignVCenter);
+
+ auto* buttonBox = new QDialogButtonBox(this);
+ QPushButton* deleteButton = buttonBox->addButton(tr("Delete"), QDialogButtonBox::DestructiveRole);
+ deleteButton->setIcon(IconManager::instance().loadIcon("delete"));
+ QPushButton* cancelButton = buttonBox->addButton(QDialogButtonBox::Cancel);
+ cancelButton->setIcon(QIcon());
+ cancelButton->setDefault(true);
+ deleteButton->setAutoDefault(false);
+ connect(deleteButton, &QPushButton::clicked, this, &QDialog::accept);
+ connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
+ actionLayout->addWidget(buttonBox, 0, Qt::AlignBottom);
+ layout->addLayout(actionLayout);
+
+ setMinimumSize(500, 220);
+ QSize initialSize = sizeHint();
+ initialSize.setWidth(qBound(showReplacementColumn ? 600 : 500, initialSize.width(), 760));
+ initialSize.setHeight(qBound(240, initialSize.height(), 560));
+ resize(initialSize);
+ layout->activate();
+ m_columnsUserAdjusted = false;
+ fitTableColumns();
+ updateElidedToolTips();
+}
+
+QList<QUuid> MasterPageDeleteDialog::masterPageIds() const
+{
+ QList<QUuid> ids;
+ for (const MasterInfo& info : m_masterInfo)
+ ids.append(info.id);
+ return ids;
+}
+
+QMap<int, QUuid> MasterPageDeleteDialog::replacementMasterIds() const
+{
+ QMap<int, QUuid> replacements;
+ for (const MasterInfo& info : m_masterInfo)
+ {
+ for (int pageIndex : info.allPages)
+ {
+ if (!info.allReplacementId.isNull())
+ replacements.insert(pageIndex, info.allReplacementId);
+ }
+ for (int pageIndex : info.leftPages)
+ {
+ if (!info.leftReplacementId.isNull())
+ replacements.insert(pageIndex, info.leftReplacementId);
+ }
+ for (int pageIndex : info.rightPages)
+ {
+ if (!info.rightReplacementId.isNull())
+ replacements.insert(pageIndex, info.rightReplacementId);
+ }
+ }
+ return replacements;
+}
+
+void MasterPageDeleteDialog::showEvent(QShowEvent* event)
+{
+ QDialog::showEvent(event);
+ if (!m_columnsUserAdjusted)
+ fitTableColumns();
+}
+
+void MasterPageDeleteDialog::resizeEvent(QResizeEvent* event)
+{
+ QDialog::resizeEvent(event);
+ if (!m_columnsUserAdjusted)
+ fitTableColumns();
+ updateElidedToolTips();
+ for (const ReplacementControl& control : m_replacementControls)
+ updateComboToolTip(control.combo);
+ updateComboToolTip(m_bulkAllCombo);
+ updateComboToolTip(m_bulkLeftCombo);
+ updateComboToolTip(m_bulkRightCombo);
+}
+
+QString MasterPageDeleteDialog::displayMasterName(const QString& name) const
+{
+ if (name == CommonStrings::masterPageNormal)
+ return CommonStrings::trMasterPageNormal;
+ if (name == CommonStrings::masterPageNormalLeft)
+ return CommonStrings::trMasterPageNormalLeft;
+ if (name == CommonStrings::masterPageNormalMiddle)
+ return CommonStrings::trMasterPageNormalMiddle;
+ if (name == CommonStrings::masterPageNormalRight)
+ return CommonStrings::trMasterPageNormalRight;
+ return name;
+}
+
+QString MasterPageDeleteDialog::formatPageRanges(const QList<int>& pages) const
+{
+ if (pages.isEmpty())
+ return QString();
+ QList<int> sortedPages = pages;
+ std::sort(sortedPages.begin(), sortedPages.end());
+ QStringList ranges;
+ int start = sortedPages.first();
+ int previous = start;
+ for (int i = 1; i < sortedPages.count(); ++i)
+ {
+ const int current = sortedPages.at(i);
+ if (current == previous + 1)
+ {
+ previous = current;
+ continue;
+ }
+ ranges.append(start == previous ? QString::number(start + 1) : tr("%1-%2").arg(start + 1).arg(previous + 1));
+ start = previous = current;
+ }
+ ranges.append(start == previous ? QString::number(start + 1) : tr("%1-%2").arg(start + 1).arg(previous + 1));
+ return ranges.join(tr(", "));
+}
+
+QList<QUuid> MasterPageDeleteDialog::replacementCandidates(ReplacementSide side) const
+{
+ QList<QUuid> candidates;
+ if (!m_doc)
+ return candidates;
+
+ for (const ScPage* master : m_doc->MasterPages)
+ {
+ const QUuid masterId = master->masterPageId();
+ if (masterId.isNull() || m_deleteIds.contains(masterId))
+ continue;
+ if (side == ReplacementSide::Left && master->LeftPg != 1)
+ continue;
+ if (side == ReplacementSide::Right && master->LeftPg != 0)
+ continue;
+ candidates.append(masterId);
+ }
+ return candidates;
+}
+
+QUuid MasterPageDeleteDialog::replacementId(const MasterInfo& info, ReplacementSide side) const
+{
+ switch (side)
+ {
+ case ReplacementSide::All:
+ return info.allReplacementId;
+ case ReplacementSide::Left:
+ return info.leftReplacementId;
+ case ReplacementSide::Right:
+ return info.rightReplacementId;
+ case ReplacementSide::None:
+ break;
+ }
+ return QUuid();
+}
+
+void MasterPageDeleteDialog::setReplacementId(MasterInfo& info, ReplacementSide side, const QUuid& masterId)
+{
+ switch (side)
+ {
+ case ReplacementSide::All:
+ info.allReplacementId = masterId;
+ break;
+ case ReplacementSide::Left:
+ info.leftReplacementId = masterId;
+ break;
+ case ReplacementSide::Right:
+ info.rightReplacementId = masterId;
+ break;
+ case ReplacementSide::None:
+ break;
+ }
+}
+
+bool MasterPageDeleteDialog::usesReplacementSide(const MasterInfo& info, ReplacementSide side) const
+{
+ switch (side)
+ {
+ case ReplacementSide::All:
+ return !info.allPages.isEmpty();
+ case ReplacementSide::Left:
+ return !info.leftPages.isEmpty();
+ case ReplacementSide::Right:
+ return !info.rightPages.isEmpty();
+ case ReplacementSide::None:
+ break;
+ }
+ return false;
+}
+
+QString MasterPageDeleteDialog::sideLabel(ReplacementSide side) const
+{
+ switch (side)
+ {
+ case ReplacementSide::All:
+ return tr("Pages");
+ case ReplacementSide::Left:
+ return tr("Left pages");
+ case ReplacementSide::Right:
+ return tr("Right pages");
+ case ReplacementSide::None:
+ break;
+ }
+ return QString();
+}
+
+QComboBox* MasterPageDeleteDialog::createReplacementCombo(int row, ReplacementSide side, QWidget* parent)
+{
+ auto* combo = new QComboBox(parent);
+ combo->setMinimumWidth(135);
+ populateReplacementCombo(combo, side, replacementId(m_masterInfo[row], side));
+ if (combo->currentIndex() >= 0)
+ setReplacementId(m_masterInfo[row], side, QUuid(combo->currentData().toString()));
+ connect(combo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this, row, side, combo](int)
+ {
+ rowReplacementChanged(row, side, combo);
+ });
+ ReplacementControl control;
+ control.row = row;
+ control.side = side;
+ control.combo = combo;
+ m_replacementControls.append(control);
+ updateComboToolTip(combo);
+ return combo;
+}
+
+QWidget* MasterPageDeleteDialog::createReplacementControl(int row, ReplacementSide side, QWidget* parent)
+{
+ auto* control = new QWidget(parent);
+ auto* controlLayout = new QHBoxLayout(control);
+ controlLayout->setContentsMargins(0, 0, 0, 0);
+ controlLayout->setSpacing(0);
+
+ QComboBox* combo = createReplacementCombo(row, side, control);
+ combo->setAccessibleName(sideLabel(side));
+ controlLayout->addWidget(combo);
+ return control;
+}
+
+QWidget* MasterPageDeleteDialog::createReplacementCell(int row)
+{
+ if (row < 0 || row >= m_masterInfo.count())
+ return nullptr;
+
+ const MasterInfo& info = m_masterInfo.at(row);
+ QList<ReplacementSide> sides;
+ if (!info.allPages.isEmpty())
+ sides.append(ReplacementSide::All);
+ else
+ {
+ if (!info.leftPages.isEmpty())
+ sides.append(ReplacementSide::Left);
+ if (!info.rightPages.isEmpty())
+ sides.append(ReplacementSide::Right);
+ }
+ if (sides.isEmpty())
+ return nullptr;
+
+ auto* cell = new QWidget(m_table);
+ auto* cellLayout = new QHBoxLayout(cell);
+ cellLayout->setContentsMargins(4, 2, 4, 2);
+ cellLayout->setSpacing(8);
+ for (ReplacementSide side : sides)
+ cellLayout->addWidget(createReplacementControl(row, side, cell));
+ cellLayout->addStretch(1);
+ return cell;
+}
+
+QWidget* MasterPageDeleteDialog::createBulkReplacementControls(int usedCount)
+{
+ if (usedCount < 2)
+ return nullptr;
+
+ QList<ReplacementSide> sides;
+ if (m_doc->pagePositioning() == singlePage)
+ {
+ for (const MasterInfo& info : m_masterInfo)
+ {
+ if (!info.allPages.isEmpty())
+ {
+ sides.append(ReplacementSide::All);
+ break;
+ }
+ }
+ }
+ else
+ {
+ bool hasLeft = false;
+ bool hasRight = false;
+ for (const MasterInfo& info : m_masterInfo)
+ {
+ hasLeft |= !info.leftPages.isEmpty();
+ hasRight |= !info.rightPages.isEmpty();
+ }
+ if (hasLeft)
+ sides.append(ReplacementSide::Left);
+ if (hasRight)
+ sides.append(ReplacementSide::Right);
+ }
+ if (sides.isEmpty())
+ return nullptr;
+
+ auto* widget = new QWidget(this);
+ auto* widgetLayout = new QHBoxLayout(widget);
+ widgetLayout->setContentsMargins(0, 4, 0, 0);
+ widgetLayout->setSpacing(8);
+
+ m_bulkReplacementCheckBox = new QCheckBox(tr("Set all replacements"), widget);
+ widgetLayout->addWidget(m_bulkReplacementCheckBox, 0, Qt::AlignTop);
+
+ m_bulkReplacementControls = new QWidget(widget);
+ auto* controlsLayout = new QHBoxLayout(m_bulkReplacementControls);
+ controlsLayout->setContentsMargins(0, 0, 0, 0);
+ controlsLayout->setSpacing(8);
+ for (ReplacementSide side : sides)
+ controlsLayout->addWidget(createBulkReplacementControl(side, m_bulkReplacementControls));
+ m_bulkReplacementControls->setEnabled(false);
+ widgetLayout->addWidget(m_bulkReplacementControls);
+ widgetLayout->addStretch(1);
+
+ connect(m_bulkReplacementCheckBox, &QCheckBox::toggled, this, &MasterPageDeleteDialog::setBulkReplacementEnabled);
+ return widget;
+}
+
+QWidget* MasterPageDeleteDialog::createBulkReplacementControl(ReplacementSide side, QWidget* parent)
+{
+ auto* control = new QWidget(parent);
+ auto* controlLayout = new QHBoxLayout(control);
+ controlLayout->setContentsMargins(0, 0, 0, 0);
+ controlLayout->setSpacing(4);
+
+ QString labelText;
+ switch (side)
+ {
+ case ReplacementSide::All:
+ labelText = tr("Pages:");
+ break;
+ case ReplacementSide::Left:
+ labelText = tr("Left:");
+ break;
+ case ReplacementSide::Right:
+ labelText = tr("Right:");
+ break;
+ case ReplacementSide::None:
+ break;
+ }
+ controlLayout->addWidget(new QLabel(labelText, control));
+
+ auto* combo = new QComboBox(control);
+ combo->setMinimumWidth(135);
+ populateBulkReplacementCombo(combo, side);
+ connect(combo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this, side, combo](int)
+ {
+ bulkReplacementChanged(side, combo);
+ });
+ controlLayout->addWidget(combo);
+
+ switch (side)
+ {
+ case ReplacementSide::All:
+ m_bulkAllCombo = combo;
+ break;
+ case ReplacementSide::Left:
+ m_bulkLeftCombo = combo;
+ break;
+ case ReplacementSide::Right:
+ m_bulkRightCombo = combo;
+ break;
+ case ReplacementSide::None:
+ break;
+ }
+ updateComboToolTip(combo);
+ return control;
+}
+
+void MasterPageDeleteDialog::populateReplacementCombo(QComboBox* combo, ReplacementSide side, const QUuid& selectedId)
+{
+ combo->clear();
+ const QList<QUuid> candidates = replacementCandidates(side);
+ for (const QUuid& masterId : candidates)
+ {
+ const QString name = displayMasterName(m_doc->masterPageName(masterId));
+ combo->addItem(name, masterId.toString(QUuid::WithoutBraces));
+ }
+
+ int selectedIndex = combo->findData(selectedId.toString(QUuid::WithoutBraces));
+ if (selectedIndex < 0 && combo->count() > 0)
+ selectedIndex = 0;
+ combo->setCurrentIndex(selectedIndex);
+ combo->setEnabled(combo->count() > 0);
+}
+
+void MasterPageDeleteDialog::populateBulkReplacementCombo(QComboBox* combo, ReplacementSide side)
+{
+ if (!combo)
+ return;
+
+ m_updatingBulkControls = true;
+ combo->clear();
+
+ QUuid selectedId;
+ for (const MasterInfo& info : m_masterInfo)
+ {
+ if (!usesReplacementSide(info, side))
+ continue;
+ selectedId = replacementId(info, side);
+ if (!selectedId.isNull())
+ break;
+ }
+
+ const QList<QUuid> candidates = replacementCandidates(side);
+ for (const QUuid& masterId : candidates)
+ combo->addItem(displayMasterName(m_doc->masterPageName(masterId)), masterId.toString(QUuid::WithoutBraces));
+
+ if (!selectedId.isNull())
+ setComboMasterId(combo, selectedId);
+ if (combo->currentIndex() < 0 && combo->count() > 0)
+ combo->setCurrentIndex(0);
+ combo->setEnabled(!candidates.isEmpty());
+ m_updatingBulkControls = false;
+ updateComboToolTip(combo);
+}
+
+void MasterPageDeleteDialog::rowReplacementChanged(int row, ReplacementSide side, QComboBox* combo)
+{
+ if (row < 0 || row >= m_masterInfo.count() || !combo || combo->currentIndex() < 0)
+ return;
+ const QUuid masterId(combo->currentData().toString());
+ if (masterId.isNull())
+ return;
+ setReplacementId(m_masterInfo[row], side, masterId);
+ updateComboToolTip(combo);
+ updateBulkReplacementCombo(side);
+}
+
+void MasterPageDeleteDialog::bulkReplacementChanged(ReplacementSide side, QComboBox* combo)
+{
+ if (!m_bulkReplacementEnabled || m_updatingBulkControls || !combo || combo->currentIndex() < 0)
+ return;
+ const QUuid masterId(combo->currentData().toString());
+ if (masterId.isNull())
+ return;
+
+ for (int row = 0; row < m_masterInfo.count(); ++row)
+ {
+ if (usesReplacementSide(m_masterInfo[row], side))
+ setReplacementId(m_masterInfo[row], side, masterId);
+ }
+ for (const ReplacementControl& control : m_replacementControls)
+ {
+ if (control.side != side || !control.combo)
+ continue;
+ QSignalBlocker blocker(control.combo);
+ setComboMasterId(control.combo, masterId);
+ updateComboToolTip(control.combo);
+ }
+}
+
+void MasterPageDeleteDialog::setBulkReplacementEnabled(bool enabled)
+{
+ if (enabled == m_bulkReplacementEnabled)
+ return;
+
+ if (enabled)
+ {
+ m_individualReplacementSnapshot = m_masterInfo;
+ m_bulkReplacementEnabled = true;
+ if (m_bulkReplacementControls)
+ m_bulkReplacementControls->setEnabled(true);
+ setRowReplacementControlsEnabled(false);
+ if (m_bulkAllCombo)
+ bulkReplacementChanged(ReplacementSide::All, m_bulkAllCombo);
+ if (m_bulkLeftCombo)
+ bulkReplacementChanged(ReplacementSide::Left, m_bulkLeftCombo);
+ if (m_bulkRightCombo)
+ bulkReplacementChanged(ReplacementSide::Right, m_bulkRightCombo);
+ }
+ else
+ {
+ m_bulkReplacementEnabled = false;
+ if (m_bulkReplacementControls)
+ m_bulkReplacementControls->setEnabled(false);
+ restoreIndividualReplacements();
+ setRowReplacementControlsEnabled(true);
+ }
+}
+
+void MasterPageDeleteDialog::setRowReplacementControlsEnabled(bool enabled)
+{
+ for (const ReplacementControl& control : m_replacementControls)
+ {
+ if (control.combo)
+ control.combo->setEnabled(enabled && control.combo->count() > 0);
+ }
+}
+
+void MasterPageDeleteDialog::restoreIndividualReplacements()
+{
+ if (m_individualReplacementSnapshot.count() != m_masterInfo.count())
+ {
+ m_individualReplacementSnapshot.clear();
+ return;
+ }
+
+ for (int row = 0; row < m_masterInfo.count(); ++row)
+ {
+ m_masterInfo[row].allReplacementId = m_individualReplacementSnapshot.at(row).allReplacementId;
+ m_masterInfo[row].leftReplacementId = m_individualReplacementSnapshot.at(row).leftReplacementId;
+ m_masterInfo[row].rightReplacementId = m_individualReplacementSnapshot.at(row).rightReplacementId;
+ }
+ for (const ReplacementControl& control : m_replacementControls)
+ {
+ if (!control.combo || control.row < 0 || control.row >= m_masterInfo.count())
+ continue;
+ QSignalBlocker blocker(control.combo);
+ setComboMasterId(control.combo, replacementId(m_masterInfo.at(control.row), control.side));
+ updateComboToolTip(control.combo);
+ }
+ m_individualReplacementSnapshot.clear();
+ updateBulkReplacementCombo(ReplacementSide::All);
+ updateBulkReplacementCombo(ReplacementSide::Left);
+ updateBulkReplacementCombo(ReplacementSide::Right);
+}
+
+void MasterPageDeleteDialog::updateBulkReplacementCombo(ReplacementSide side)
+{
+ QComboBox* combo = bulkReplacementCombo(side);
+ if (!combo)
+ return;
+ populateBulkReplacementCombo(combo, side);
+}
+
+QComboBox* MasterPageDeleteDialog::bulkReplacementCombo(ReplacementSide side) const
+{
+ switch (side)
+ {
+ case ReplacementSide::All:
+ return m_bulkAllCombo;
+ case ReplacementSide::Left:
+ return m_bulkLeftCombo;
+ case ReplacementSide::Right:
+ return m_bulkRightCombo;
+ case ReplacementSide::None:
+ break;
+ }
+ return nullptr;
+}
+
+void MasterPageDeleteDialog::setComboMasterId(QComboBox* combo, const QUuid& masterId)
+{
+ if (!combo)
+ return;
+ const int index = combo->findData(masterId.toString(QUuid::WithoutBraces));
+ if (index >= 0)
+ combo->setCurrentIndex(index);
+}
+
+void MasterPageDeleteDialog::fitTableColumns()
+{
+ if (!m_table || m_table->columnCount() < 2)
+ return;
+
+ m_adjustingColumns = true;
+ const int availableWidth = qMax(0, m_table->horizontalHeader()->width());
+ const int usageWidth = qBound(90, m_table->horizontalHeader()->sectionSizeHint(1), 140);
+ int replacementWidth = 0;
+ if (m_table->columnCount() > 2)
+ replacementWidth = qBound(150, m_table->horizontalHeader()->sectionSizeHint(2), 300);
+ const int minimumNameWidth = qBound(170, m_table->horizontalHeader()->sectionSizeHint(0), 300);
+ const int nameWidth = qMax(minimumNameWidth, availableWidth - usageWidth - replacementWidth);
+
+ m_table->setColumnWidth(0, nameWidth);
+ m_table->setColumnWidth(1, usageWidth);
+ if (m_table->columnCount() > 2)
+ m_table->setColumnWidth(2, replacementWidth);
+ m_adjustingColumns = false;
+}
+
+void MasterPageDeleteDialog::updateElidedToolTips()
+{
+ if (!m_table)
+ return;
+ const int availableWidth = qMax(0, m_table->columnWidth(0) - 12);
+ for (int row = 0; row < m_table->rowCount(); ++row)
+ {
+ QTableWidgetItem* item = m_table->item(row, 0);
+ if (!item)
+ continue;
+ const bool elided = m_table->fontMetrics().horizontalAdvance(item->text()) > availableWidth;
+ item->setToolTip(elided ? item->text() : QString());
+ }
+}
+
+void MasterPageDeleteDialog::updateComboToolTip(QComboBox* combo)
+{
+ if (!combo || !combo->isVisible() || combo->currentIndex() < 0)
+ return;
+ const int availableWidth = qMax(0, combo->width() - combo->style()->pixelMetric(QStyle::PM_ComboBoxFrameWidth, nullptr, combo) * 2 - 28);
+ const bool elided = combo->fontMetrics().horizontalAdvance(combo->currentText()) > availableWidth;
+ combo->setToolTip(elided ? combo->currentText() : QString());
+}
Index: scribus/ui/masterpagedeletedialog.h
===================================================================
--- scribus/ui/masterpagedeletedialog.h (nonexistent)
+++ scribus/ui/masterpagedeletedialog.h (working copy)
@@ -0,0 +1,115 @@
+/*
+For general Scribus (>=1.3.2) copyright and licensing information please refer
+to the COPYING file provided with the program. Following this notice may exist
+a copyright and/or license notice that predates the release of Scribus 1.3.2
+for which a new license (GPL+exception) is in place.
+*/
+#ifndef MASTERPAGEDELETEDIALOG_H
+#define MASTERPAGEDELETEDIALOG_H
+
+#include <QDialog>
+#include <QList>
+#include <QMap>
+#include <QString>
+#include <QUuid>
+
+#include "scribusapi.h"
+
+class QCheckBox;
+class QComboBox;
+class QLabel;
+class QResizeEvent;
+class QShowEvent;
+class QTableWidget;
+class QWidget;
+class ScribusDoc;
+
+class SCRIBUS_API MasterPageDeleteDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ MasterPageDeleteDialog(QWidget* parent, ScribusDoc* doc, const QList<QUuid>& masterIds);
+
+ QList<QUuid> masterPageIds() const;
+ QMap<int, QUuid> replacementMasterIds() const;
+
+protected:
+ void resizeEvent(QResizeEvent* event) override;
+ void showEvent(QShowEvent* event) override;
+
+private:
+ enum class ReplacementSide
+ {
+ None,
+ All,
+ Left,
+ Right
+ };
+
+ struct MasterInfo
+ {
+ QUuid id;
+ QString name;
+ QList<int> pages;
+ QList<int> allPages;
+ QList<int> leftPages;
+ QList<int> rightPages;
+ QList<int> middlePages;
+ QUuid allReplacementId;
+ QUuid leftReplacementId;
+ QUuid rightReplacementId;
+ };
+
+ struct ReplacementControl
+ {
+ int row { -1 };
+ ReplacementSide side { ReplacementSide::None };
+ QComboBox* combo { nullptr };
+ };
+
+ QString displayMasterName(const QString& name) const;
+ QString formatPageRanges(const QList<int>& pages) const;
+ QList<QUuid> replacementCandidates(ReplacementSide side) const;
+ QUuid replacementId(const MasterInfo& info, ReplacementSide side) const;
+ void setReplacementId(MasterInfo& info, ReplacementSide side, const QUuid& masterId);
+ bool usesReplacementSide(const MasterInfo& info, ReplacementSide side) const;
+ QString sideLabel(ReplacementSide side) const;
+
+ QComboBox* createReplacementCombo(int row, ReplacementSide side, QWidget* parent);
+ QWidget* createReplacementControl(int row, ReplacementSide side, QWidget* parent);
+ QWidget* createReplacementCell(int row);
+ QWidget* createBulkReplacementControls(int usedCount);
+ QWidget* createBulkReplacementControl(ReplacementSide side, QWidget* parent);
+ void populateReplacementCombo(QComboBox* combo, ReplacementSide side, const QUuid& selectedId);
+ void populateBulkReplacementCombo(QComboBox* combo, ReplacementSide side);
+ void rowReplacementChanged(int row, ReplacementSide side, QComboBox* combo);
+ void bulkReplacementChanged(ReplacementSide side, QComboBox* combo);
+ void setBulkReplacementEnabled(bool enabled);
+ void setRowReplacementControlsEnabled(bool enabled);
+ void restoreIndividualReplacements();
+ void updateBulkReplacementCombo(ReplacementSide side);
+ QComboBox* bulkReplacementCombo(ReplacementSide side) const;
+ void setComboMasterId(QComboBox* combo, const QUuid& masterId);
+ void fitTableColumns();
+ void updateElidedToolTips();
+ void updateComboToolTip(QComboBox* combo);
+
+ ScribusDoc* m_doc { nullptr };
+ QList<MasterInfo> m_masterInfo;
+ QList<QUuid> m_deleteIds;
+ QTableWidget* m_table { nullptr };
+ QList<ReplacementControl> m_replacementControls;
+ QList<MasterInfo> m_individualReplacementSnapshot;
+ QCheckBox* m_bulkReplacementCheckBox { nullptr };
+ QWidget* m_bulkReplacementControls { nullptr };
+ QComboBox* m_bulkAllCombo { nullptr };
+ QComboBox* m_bulkLeftCombo { nullptr };
+ QComboBox* m_bulkRightCombo { nullptr };
+ bool m_updatingBulkControls { false };
+ bool m_bulkReplacementEnabled { false };
+ bool m_adjustingColumns { false };
+ bool m_columnsUserAdjusted { false };
+};
+
+#endif
Index: scribus/ui/pagepalette_masterpages.cpp
===================================================================
--- scribus/ui/pagepalette_masterpages.cpp (revision 27861)
+++ scribus/ui/pagepalette_masterpages.cpp (working copy)
@@ -6,6 +6,7 @@
*/
#include "pagepalette_masterpages.h"
+#include <QAction>
#include <QApplication>
#include <QCloseEvent>
#include <QCursor>
@@ -13,6 +14,7 @@
#include <QInputDialog>
#include <QListWidget>
#include <QListWidgetItem>
+#include <QMenu>
#include <QMessageBox>
#include <QScopedPointer>
#include <QSpacerItem>
@@ -26,6 +28,7 @@
#include "canvasmode.h"
#include "commonstrings.h"
#include "iconmanager.h"
+#include "masterpagedeletedialog.h"
#include "mergedoc.h"
#include "newtemp.h"
#include "pagestructs.h"
@@ -47,6 +50,11 @@
m_view = pCurrentView;
setupUi(this);
+ m_deleteMenu = new QMenu(deleteOptionsButton);
+ m_deleteUnusedAction = m_deleteMenu->addAction(QString());
+ m_selectUnusedAction = m_deleteMenu->addAction(QString());
+ deleteOptionsButton->setMenu(m_deleteMenu);
+ deleteOptionsButton->setPopupMode(QToolButton::InstantPopup);
iconSetChange();
masterPageListBox->setSelectionMode(QAbstractItemView::ExtendedSelection);
@@ -103,6 +111,8 @@
{
connect(duplicateButton, SIGNAL(clicked()), this, SLOT(duplicateMasterPage()));
connect(deleteButton , SIGNAL(clicked()), this, SLOT(deleteMasterPage()));
+ connect(m_deleteUnusedAction, SIGNAL(triggered()), this, SLOT(deleteUnusedMasterPages()));
+ connect(m_selectUnusedAction, SIGNAL(triggered()), this, SLOT(selectUnusedMasterPages()));
connect(newButton , SIGNAL(clicked()), this, SLOT(newMasterPage()));
connect(importButton , SIGNAL(clicked()), this, SLOT(importPage()));
connect(masterPageListBox, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
@@ -116,6 +126,8 @@
{
disconnect(duplicateButton, SIGNAL(clicked()), this, SLOT(duplicateMasterPage()));
disconnect(deleteButton , SIGNAL(clicked()), this, SLOT(deleteMasterPage()));
+ disconnect(m_deleteUnusedAction, SIGNAL(triggered()), this, SLOT(deleteUnusedMasterPages()));
+ disconnect(m_selectUnusedAction, SIGNAL(triggered()), this, SLOT(selectUnusedMasterPages()));
disconnect(newButton , SIGNAL(clicked()), this, SLOT(newMasterPage()));
disconnect(importButton , SIGNAL(clicked()), this, SLOT(importPage()));
disconnect(masterPageListBox, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
@@ -140,7 +152,10 @@
{
setWindowTitle( tr( "Edit Master Pages" ) );
duplicateButton->setToolTip( tr( "Duplicate the selected master page" ) );
- deleteButton->setToolTip( tr( "Delete the selected master page" ) );
+ deleteButton->setToolTip( tr( "Delete the selected master pages" ) );
+ deleteOptionsButton->setToolTip(tr("Master page deletion options"));
+ m_deleteUnusedAction->setText(tr("Delete Unused..."));
+ m_selectUnusedAction->setText(tr("Select Unused"));
newButton->setToolTip( tr( "Add a new master page" ) );
importButton->setToolTip( tr( "Import master pages from another document" ) );
}
@@ -156,53 +171,55 @@
void PagePalette_MasterPages::deleteMasterPage()
{
- bool forceDelete = false;
const QUuid displayedMasterId = m_doc->masterPageId(m_currentPage);
QList<QUuid> selectedMasterIds;
for (QListWidgetItem* item : masterPageListBox->selectedItems())
{
const QUuid masterId = m_doc->masterPageId(item->data(Qt::UserRole).toString());
- if (m_doc->canDeleteMasterPage(masterId))
+ if (!masterId.isNull() && !selectedMasterIds.contains(masterId))
selectedMasterIds.append(masterId);
}
- // allow to delete multiple pages in one step
- for (const QUuid& masterId : selectedMasterIds)
- {
- const QString masterPageName = m_doc->masterPageName(masterId);
- if (masterPageName.isEmpty() || !m_doc->canDeleteMasterPage(masterId))
- continue;
- m_currentPage = masterPageName;
+ MasterPageDeleteDialog dialog(this, m_doc, selectedMasterIds);
+ if (dialog.masterPageIds().isEmpty() || dialog.exec() != QDialog::Accepted)
+ return;
- QString extraWarn;
+ if (m_doc->appMode == modeEditClip)
+ m_view->requestMode(submodeEndNodeEdit);
+ m_doc->scMW()->deleteMasterPages(dialog.masterPageIds(), dialog.replacementMasterIds());
- if (!forceDelete)
- {
- for (int i=0; i < m_doc->DocPages.count(); ++i )
- {
- if (m_doc->DocPages[i]->masterPageName() == m_currentPage)
- extraWarn = tr("This master page is used at least once in the document.");
- }
- int exit = ScMessageBox::warning(this,
- CommonStrings::trWarning,
- tr("Do you really want to delete master page \"%1\"?").arg(m_currentPage)+"\n"+extraWarn,
- QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::Cancel,
- QMessageBox::NoButton, // GUI default
- QMessageBox::YesToAll); // batch default
-
- if (exit == QMessageBox::YesToAll)
- forceDelete = true;
- if (exit == QMessageBox::No)
- continue;
- if (exit == QMessageBox::Cancel)
- break;
- }
+ if (m_doc->masterPageIndex(displayedMasterId) >= 0)
+ m_currentPage = m_doc->masterPageName(displayedMasterId);
+ else
+ {
+ const QUuid defaultId = m_doc->defaultMasterPageId(0);
+ m_currentPage = m_doc->masterPageName(defaultId);
+ if (m_currentPage.isEmpty() && !m_doc->MasterPages.isEmpty())
+ m_currentPage = m_doc->MasterPages.first()->pageName();
+ }
+ updateMasterPageList(m_currentPage);
+ selectMasterPage(m_currentPage);
+}
- if (m_doc->appMode == modeEditClip)
- m_view->requestMode(submodeEndNodeEdit);
- m_doc->scMW()->deletePage2(m_doc->masterPageIndex(masterId));
+void PagePalette_MasterPages::deleteUnusedMasterPages()
+{
+ const QList<QUuid> unusedMasterIds = m_doc->unusedCustomMasterPageIds();
+ if (unusedMasterIds.isEmpty())
+ {
+ QMessageBox::information(this, tr("Delete Unused Master Pages"),
+ tr("No unused custom master pages found. Default master pages are kept."));
+ return;
}
+ const QUuid displayedMasterId = m_doc->masterPageId(m_currentPage);
+ MasterPageDeleteDialog dialog(this, m_doc, unusedMasterIds);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ if (m_doc->appMode == modeEditClip)
+ m_view->requestMode(submodeEndNodeEdit);
+ m_doc->scMW()->deleteMasterPages(dialog.masterPageIds(), dialog.replacementMasterIds());
+
if (m_doc->masterPageIndex(displayedMasterId) >= 0)
m_currentPage = m_doc->masterPageName(displayedMasterId);
else
@@ -216,6 +233,27 @@
selectMasterPage(m_currentPage);
}
+void PagePalette_MasterPages::selectUnusedMasterPages()
+{
+ const QList<QUuid> unusedMasterIds = m_doc->unusedCustomMasterPageIds();
+ if (unusedMasterIds.isEmpty())
+ {
+ QMessageBox::information(this, tr("Select Unused Master Pages"),
+ tr("No unused custom master pages found. Default master pages are kept."));
+ return;
+ }
+
+ masterPageListBox->clearSelection();
+ for (int row = 0; row < masterPageListBox->count(); ++row)
+ {
+ QListWidgetItem* item = masterPageListBox->item(row);
+ const QUuid masterId = m_doc->masterPageId(item->data(Qt::UserRole).toString());
+ if (unusedMasterIds.contains(masterId))
+ item->setSelected(true);
+ }
+ updateDeleteButton();
+}
+
void PagePalette_MasterPages::duplicateMasterPage()
{
QString name = getUniqueName(m_currentPage, m_doc->MasterNames);
@@ -436,6 +474,7 @@
}
}
deleteButton->setEnabled(deleteEnabled);
+ deleteOptionsButton->setEnabled(m_doc != nullptr);
}
void PagePalette_MasterPages::selectMasterPage(const QString& name)
Index: scribus/ui/pagepalette_masterpages.h
===================================================================
--- scribus/ui/pagepalette_masterpages.h (revision 27861)
+++ scribus/ui/pagepalette_masterpages.h (working copy)
@@ -7,10 +7,12 @@
#ifndef PAGEPALETTE_MASTERPAGES_H
#define PAGEPALETTE_MASTERPAGES_H
+class QAction;
class QCloseEvent;
class QHBoxLayout;
class QListWidget;
class QListWidgetItem;
+class QMenu;
class QToolButton;
class QVBoxLayout;
@@ -47,6 +49,9 @@
QString m_currentPage;
ScribusDoc *m_doc;
ScribusView *m_view;
+ QMenu *m_deleteMenu { nullptr };
+ QAction *m_deleteUnusedAction { nullptr };
+ QAction *m_selectUnusedAction { nullptr };
protected:
void connectSignals();
@@ -64,6 +69,8 @@
private slots:
void duplicateMasterPage();
void deleteMasterPage();
+ void deleteUnusedMasterPages();
+ void selectUnusedMasterPages();
void newMasterPage();
void renameMasterPage(QListWidgetItem * item);
void importPage();
Index: scribus/ui/pagepalette_masterpagesbase.ui
===================================================================
--- scribus/ui/pagepalette_masterpagesbase.ui (revision 27861)
+++ scribus/ui/pagepalette_masterpagesbase.ui (working copy)
@@ -69,6 +69,16 @@
</widget>
</item>
<item>
+ <widget class="QToolButton" name="deleteOptionsButton">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="arrowType">
+ <enum>Qt::ArrowType::DownArrow</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
Index: scribus/ui/pagepalette_pages.cpp
===================================================================
--- scribus/ui/pagepalette_pages.cpp (revision 27861)
+++ scribus/ui/pagepalette_pages.cpp (working copy)
@@ -22,6 +22,7 @@
#include "commonstrings.h"
#include "iconmanager.h"
+#include "masterpagedeletedialog.h"
#include "ui/widgets/pagelayout.h"
#include "pagepalette_pages.h"
#include "pagepalette_widgets.h"
@@ -89,36 +90,26 @@
const QUuid masterId = currView->m_doc->masterPageId(tmp);
if (!currView->m_doc->canDeleteMasterPage(masterId))
return;
- QString extraWarn;
- for (int i = 0; i < currView->m_doc->DocPages.count(); ++i)
- {
- if (currView->m_doc->DocPages[i]->masterPageName() == tmp)
- extraWarn = tr("This master page is used at least once in the document.");
- }
- int exit = ScMessageBox::warning(this,
- CommonStrings::trWarning,
- tr("Do you really want to delete this master page?") + "\n"
- + extraWarn,
- QMessageBox::Yes | QMessageBox::No,
- QMessageBox::NoButton, // GUI default
- QMessageBox::Yes); // batch default
- if (exit == QMessageBox::Yes)
- {
- bool oldMPMode = currView->m_doc->masterPageMode();
- int storedPageNum = currView->m_doc->currentPageNumber();
- int storedViewXCoor = currView->horizontalScrollBar()->value();
- int storedViewYCoor = currView->verticalScrollBar()->value();
-
- currView->m_doc->setMasterPageMode(true);
- currView->m_doc->scMW()->deletePage2(currView->m_doc->masterPageIndex(masterId));
- currView->m_doc->setMasterPageMode(oldMPMode);
- currView->m_doc->setModified(true);
-
- currView->m_doc->setCurrentPage(currView->m_doc->DocPages.at(storedPageNum));
- currView->reformPages(false);
- currView->setContentsPos(storedViewXCoor, storedViewYCoor);
- currView->DrawNew();
- }
+
+ MasterPageDeleteDialog dialog(this, currView->m_doc, { masterId });
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ bool oldMPMode = currView->m_doc->masterPageMode();
+ int storedPageNum = currView->m_doc->currentPageNumber();
+ int storedViewXCoor = currView->horizontalScrollBar()->value();
+ int storedViewYCoor = currView->verticalScrollBar()->value();
+
+ currView->m_doc->setMasterPageMode(true);
+ currView->m_doc->scMW()->deleteMasterPages(dialog.masterPageIds(), dialog.replacementMasterIds());
+ currView->m_doc->setMasterPageMode(oldMPMode);
+ currView->m_doc->setModified(true);
+
+ if (!currView->m_doc->DocPages.isEmpty())
+ currView->m_doc->setCurrentPage(currView->m_doc->DocPages.at(qBound(0, storedPageNum, currView->m_doc->DocPages.count() - 1)));
+ currView->reformPages(false);
+ currView->setContentsPos(storedViewXCoor, storedViewYCoor);
+ currView->DrawNew();
}
void PagePalette_Pages::pageView_applyMasterPage(const QString &masterpageName, int pageIndex)
|
|
|
A few screenshots showing the proposed feature/changes. |
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-09-18 14:27 | buttar | New Issue | |
| 2026-09-23 12:50 | buttar | Note Added: 0054490 | |
| 2026-09-23 12:50 | buttar | File Added: mdu1-deletion-hardening-v1.0.patch | |
| 2026-09-23 12:50 | buttar | File Added: mdu2-delete-unused-v1.33.patch | |
| 2026-09-23 12:52 | buttar | Note Added: 0054491 | |
| 2026-09-23 12:52 | buttar | File Added: delete0unused-PROPOSED.png | |
| 2026-09-23 12:52 | buttar | File Added: delete-mixed-PROPOSED.png | |
| 2026-09-23 12:52 | buttar | File Added: delete-or-select-unused-PROPOSED.png | |
| 2026-09-23 16:41 | buttar | Tag Attached: #please_test |