From 1a5f96a83b8cbd8a77e67c82d3cd815169a31e7c Mon Sep 17 00:00:00 2001
From: ale rimoldi <ale@graphicslab.org>
Date: Sat, 18 Oct 2025 19:50:03 +0200
Subject: add Save a Copy and improve Save As and better persist the document
 version for the session


diff --git a/scribus/actionmanager.cpp b/scribus/actionmanager.cpp
index 6051d5c46..2b67d5187 100644
--- a/scribus/actionmanager.cpp
+++ b/scribus/actionmanager.cpp
@@ -127,6 +127,8 @@ void ActionManager::initFileMenuActions()
 	scrActions->insert(name, new ScrAction("document-save", "document-save", "", defaultKey(name), mainWindow));
 	name = "fileSaveAs";
 	scrActions->insert(name, new ScrAction("document-save-as", "document-save-as", "", defaultKey(name), mainWindow));
+	name = "fileSaveCopy";
+	scrActions->insert(name, new ScrAction("", defaultKey(name), mainWindow));
 	name = "fileRevert";
 	scrActions->insert(name, new ScrAction("revert", QString(), "", defaultKey(name), mainWindow));
 	name = "fileCollect";
@@ -185,6 +187,7 @@ void ActionManager::initFileMenuActions()
 	connect( (*scrActions)["OutputPreviewPS"], SIGNAL(triggered()), mainWindow, SLOT(outputPreviewPS()) );
 	connect( (*scrActions)["fileSave"], SIGNAL(triggered()), mainWindow, SLOT(slotFileSave()) );
 	connect( (*scrActions)["fileSaveAs"], SIGNAL(triggered()), mainWindow, SLOT(slotFileSaveAs()) );
+	connect( (*scrActions)["fileSaveCopy"], SIGNAL(triggered()), mainWindow, SLOT(slotFileSaveCopy()) );
 	connect( (*scrActions)["fileDocSetup150"], SIGNAL(triggered()), mainWindow, SLOT(slotDocSetup()) );
 	connect( (*scrActions)["filePreferences150"], SIGNAL(triggered()), mainWindow, SLOT(slotPrefsOrg()) );
 	connect( (*scrActions)["fileRevert"], SIGNAL(triggered()), mainWindow, SLOT(slotFileRevert()) );
@@ -1503,6 +1506,7 @@ void ActionManager::languageChange()
 	(*scrActions)["fileClose"]->setTexts( tr("&Close"));
 	(*scrActions)["fileSave"]->setTexts( tr("&Save"));
 	(*scrActions)["fileSaveAs"]->setTexts( tr("Save &As..."));
+	(*scrActions)["fileSaveCopy"]->setTexts( tr("Save a Co&py..."));
 	(*scrActions)["fileRevert"]->setTexts( tr("Re&vert to Saved"));
 	(*scrActions)["fileCollect"]->setTexts( tr("Collect for O&utput..."));
 	(*scrActions)["fileImportText"]->setTexts( tr("Get Text..."));
@@ -2092,6 +2096,7 @@ void ActionManager::createDefaultMenus()
 		<< "fileClose"
 		<< "fileSave"
 		<< "fileSaveAs"
+		<< "fileSaveCopy"
 		<< "fileRevert"
 		<< "fileCollect"
 		<< "fileImportText"
diff --git a/scribus/appmodehelper.cpp b/scribus/appmodehelper.cpp
index 06016c344..1dc5bd487 100644
--- a/scribus/appmodehelper.cpp
+++ b/scribus/appmodehelper.cpp
@@ -1197,6 +1197,7 @@ void AppModeHelper::setSymbolEditMode(bool b, const ScribusDoc* doc)
 	(*a_scrActions)["filePrint"]->setEnabled(b2);
 	(*a_scrActions)["fileCollect"]->setEnabled(b2);
 	(*a_scrActions)["fileSaveAs"]->setEnabled(b2);
+	(*a_scrActions)["fileSaveCopy"]->setEnabled(b2);
 	(*a_scrActions)["fileExportAsEPS"]->setEnabled(b2);
 	(*a_scrActions)["fileExportAsPDF"]->setEnabled(b2);
 	if (ScCore->haveGS() || ScCore->isWinGUI())
@@ -1252,6 +1253,7 @@ void AppModeHelper::setInlineEditMode(bool b, const ScribusDoc *doc)
 	(*a_scrActions)["filePrint"]->setEnabled(b2);
 	(*a_scrActions)["fileCollect"]->setEnabled(b2);
 	(*a_scrActions)["fileSaveAs"]->setEnabled(b2);
+	(*a_scrActions)["fileSaveCopy"]->setEnabled(b2);
 	(*a_scrActions)["fileExportAsEPS"]->setEnabled(b2);
 	(*a_scrActions)["fileExportAsPDF"]->setEnabled(b2);
 	if (ScCore->haveGS() || ScCore->isWinGUI())
@@ -1471,6 +1473,7 @@ void AppModeHelper::mainWindowHasNewDoc(const ScribusDoc *doc, bool clipScrapHav
 	(*a_scrActions)["fileRevert"]->setEnabled(false);
 	(*a_scrActions)["fileCollect"]->setEnabled(true);
 	(*a_scrActions)["fileSaveAs"]->setEnabled(true);
+	(*a_scrActions)["fileSaveCopy"]->setEnabled(true);
 	(*a_scrActions)["fileExportAsEPS"]->setEnabled(true);
 	(*a_scrActions)["fileExportAsPDF"]->setEnabled(true);
 	(*a_scrActions)["fileImportVector"]->setEnabled(true);
@@ -1580,6 +1583,7 @@ void AppModeHelper::mainWindowSwitchWin(const ScribusDoc *doc)
 		{
 			(*a_scrActions)["fileCollect"]->setEnabled(false);
 			(*a_scrActions)["fileSaveAs"]->setEnabled(false);
+			(*a_scrActions)["fileSaveCopy"]->setEnabled(false);
 			(*a_scrActions)["fileExportAsEPS"]->setEnabled(false);
 			(*a_scrActions)["fileExportAsPDF"]->setEnabled(false);
 			(*a_scrActions)["fileSave"]->setEnabled(false);
@@ -1624,6 +1628,7 @@ void AppModeHelper::mainWindowSwitchWin(const ScribusDoc *doc)
 		(*a_scrActions)["pageManageProperties"]->setEnabled(true);
 
 		(*a_scrActions)["fileSaveAs"]->setEnabled(true);
+		(*a_scrActions)["fileSaveCopy"]->setEnabled(true);
 		(*a_scrActions)["fileCollect"]->setEnabled(true);
 		(*a_scrActions)["toolsPDFPushButton"]->setEnabled(true);
 		(*a_scrActions)["toolsPDFRadioButton"]->setEnabled(true);
@@ -1691,6 +1696,7 @@ void AppModeHelper::mainWindowCloseLastDoc()
 	(*a_scrActions)["fileRevert"]->setEnabled(false);
 	(*a_scrActions)["fileSave"]->setEnabled(false);
 	(*a_scrActions)["fileSaveAs"]->setEnabled(false);
+	(*a_scrActions)["fileSaveCopy"]->setEnabled(false);
 	(*a_scrActions)["insertFrame"]->setEnabled(false);
 	(*a_scrActions)["insertSampleText"]->setEnabled(false);
 	(*a_scrActions)["itemAdjustFrameToImage"]->setEnabled(false);
@@ -1865,6 +1871,7 @@ void AppModeHelper::setStartupActionsEnabled(bool enabled)
 	(*a_scrActions)["filePrint"]->setEnabled(false);
 	(*a_scrActions)["fileSave"]->setEnabled(false);
 	(*a_scrActions)["fileSaveAs"]->setEnabled(false);
+	(*a_scrActions)["fileSaveCopy"]->setEnabled(false);
 	(*a_scrActions)["fileRevert"]->setEnabled(false);
 	(*a_scrActions)["fileCollect"]->setEnabled(false);
 	(*a_scrActions)["fileClose"]->setEnabled(false);
diff --git a/scribus/loadsaveplugin.cpp b/scribus/loadsaveplugin.cpp
index 28a6ae415..ca39d9b12 100644
--- a/scribus/loadsaveplugin.cpp
+++ b/scribus/loadsaveplugin.cpp
@@ -67,6 +67,17 @@ QStringList LoadSavePlugin::fileDialogSaveFilter()
 	return getDialogFilter(false, true);
 }
 
+QString LoadSavePlugin::fileDialogSaveFilter(uint filter)
+{
+	return getDialogFilterById(filter, true);
+}
+
+QString LoadSavePlugin::fileDialogSaveFilterName(uint filter)
+{
+	return getDialogFilterNameById(filter, true);
+
+}
+
 QStringList LoadSavePlugin::getExtensionsForColors(const int id)
 {
 	QList<FileFormat>::const_iterator it(findFormat(id));
@@ -214,6 +225,26 @@ const QStringList LoadSavePlugin::getDialogFilter(bool forLoad, bool scribusOnly
 	return scribusList+filterList;
 }
 
+const QString LoadSavePlugin::getDialogFilterById(uint id, bool forLoad)
+{
+	for (const FileFormat& format: formats)
+	{
+		if (format.load == forLoad && format.formatId == id)
+			return format.filter;
+	}
+	return "";
+}
+
+const QString LoadSavePlugin::getDialogFilterNameById(uint id, bool forLoad)
+{
+	for (const FileFormat& format: formats)
+	{
+		if (format.load == forLoad && format.formatId == id)
+			return format.trName;
+	}
+	return "";
+}
+
 bool LoadSavePlugin::saveFile(const QString & /* fileName */,
 							  const FileFormat & /* fmt */)
 {
diff --git a/scribus/loadsaveplugin.h b/scribus/loadsaveplugin.h
index 90db6b4c9..f034e2b07 100644
--- a/scribus/loadsaveplugin.h
+++ b/scribus/loadsaveplugin.h
@@ -69,6 +69,16 @@ class SCRIBUS_API LoadSavePlugin : public ScPlugin
 
 		// Same deal but for save
 		static QStringList fileDialogSaveFilter();
+		/**
+		 * @param filter the index in the list of save filters
+		 * @return empty if the filter is not found
+		 */
+		static QString fileDialogSaveFilter(uint filter);
+		/**
+		 * @param filter the index in the list of save filters
+		 * @return empty if the filter is not found
+		 */
+		static QString fileDialogSaveFilterName(uint filter);
 
 		// Get the highest priority format of a given id, or 0 if
 		// not found / not available.
@@ -183,6 +193,15 @@ class SCRIBUS_API LoadSavePlugin : public ScPlugin
 
 		// Base implementation for fileDialogLoadFilter and fileDialogSaveFilter
 		static const QStringList getDialogFilter(bool forLoad, bool scribusOnly);
+		/**
+		 * @return the filter name  by its ID, as used by the Save As dialog (with the list of extensions);
+		 * returns empty string if the filter is not found.
+		 */
+		static const QString getDialogFilterById(uint id, bool forLoad);
+		/**
+		 * @return the filter name  by its ID, without the list of extensions; empty string if the filter is not found.
+		 */
+		static const QString getDialogFilterNameById(uint id, bool forLoad);
 };
 
 
diff --git a/scribus/scribus.cpp b/scribus/scribus.cpp
index c1457a882..8a271925c 100644
--- a/scribus/scribus.cpp
+++ b/scribus/scribus.cpp
@@ -922,6 +922,7 @@ void ScribusMainWindow::initMenuBar()
 	scrMenuMgr->addMenuItemString("fileClose", "File");
 	scrMenuMgr->addMenuItemString("fileSave", "File");
 	scrMenuMgr->addMenuItemString("fileSaveAs", "File");
+	scrMenuMgr->addMenuItemString("fileSaveCopy", "File");
 	scrMenuMgr->addMenuItemString("fileRevert", "File");
 	scrMenuMgr->addMenuItemString("fileCollect", "File");
 	scrMenuMgr->addMenuItemString("SEPARATOR", "File");
@@ -2006,7 +2007,7 @@ bool ScribusMainWindow::recoverFile(const QStringList& foundFiles)
 			continue;
 		doc->setDocumentFileName(dia->recoverNames[i]);
 		doc->hasName = true;
-		updateActiveWindowCaption(doc->documentFileName());
+		updateActiveWindowCaption();
 		outlinePalette->setDoc(doc);
 		if (outlinePalette->isVisible())
 			outlinePalette->BuildTree();
@@ -2047,7 +2048,7 @@ void ScribusMainWindow::startUpDialog()
 			// Don's disturb user with "save?" dialog just after new doc
 			// doc changing should be rewritten maybe... maybe later...
 			doc->setModified(false);
-			updateActiveWindowCaption(doc->documentFileName());
+			updateActiveWindowCaption();
 		}
 		else if (dia->tabSelected() == NewDocDialog::NewFromTemplateTab)
 		{
@@ -2057,6 +2058,7 @@ void ScribusMainWindow::startUpDialog()
 				doc->hasName = false;
 				UndoManager::instance()->renameStack(dia->nftGui->currentDocumentTemplate->name);
 				doc->setDocumentFileName(dia->nftGui->currentDocumentTemplate->name);
+				// TODO: we should probaby add a field to doc saying that it's a template and use updateWindowCaptionForDocument()
 				updateActiveWindowCaption(QObject::tr("Document Template: ") + dia->nftGui->currentDocumentTemplate->name);
 				QDir::setCurrent(PrefsManager::instance().documentDir());
 				removeRecent(fileName);
@@ -2127,7 +2129,7 @@ bool ScribusMainWindow::slotFileNew()
 		// Don't disturb user with "save?" dialog just after new doc
 		// doc changing should be rewritten maybe... maybe later...
 		doc->setModified(false);
-		updateActiveWindowCaption(doc->documentFileName());
+		updateActiveWindowCaption();
 	}
 
 	if (docSet)
@@ -2296,6 +2298,7 @@ void ScribusMainWindow::newFileFromTemplate()
 			doc->hasName = false;
 			UndoManager::instance()->renameStack(currentTemplate->name);
 			doc->setDocumentFileName(currentTemplate->name);
+			// TODO: we should probaby add a field to doc saying that it's a template and use updateWindowCaptionForDocument()
 			updateActiveWindowCaption(QObject::tr("Document Template: ") + currentTemplate->name);
 			QDir::setCurrent(PrefsManager::instance().documentDir());
 			removeRecent(QDir::cleanPath(currentTemplate->file));
@@ -2563,7 +2566,7 @@ void ScribusMainWindow::windowsMenuActivated(int id)
 
 void ScribusMainWindow::SwitchWin()
 {
-	updateActiveWindowCaption(doc->documentFileName());
+	updateActiveWindowCaption();
 	propertiesPalette->setDoc(doc);
 	contentPalette->setDoc(doc);
 	marksManager->setDoc(doc);
@@ -2612,7 +2615,7 @@ void ScribusMainWindow::HaveNewDoc()
 	appModeHelper->mainWindowHasNewDoc(doc, (ScMimeData::clipboardHasScribusData()) || (scrapbookPalette->tempHasContents()));
 
 	//Update palettes
-	updateActiveWindowCaption(doc->documentFileName());
+	updateActiveWindowCaption();
 	propertiesPalette->setDoc(doc);
 	contentPalette->setDoc(doc);
 	nsEditor->setDoc(doc);
@@ -2756,7 +2759,9 @@ void ScribusMainWindow::slotDocCh(bool /*reb*/)
 {
 	if (!doc->isModified())
 		doc->setModified(true);
-	updateActiveWindowCaption(doc->documentFileName() + "*");
+
+	updateActiveWindowCaption();
+
 	if (!doc->masterPageMode())
 	{
 		if (!doc->symbolEditMode() && !doc->inlineEditMode())
@@ -4054,23 +4059,24 @@ bool ScribusMainWindow::slotFileSave()
 	{
 		QString fn(doc->documentFileName());
 		QString savedFileName;
-		ret = DoFileSave(fn, &savedFileName);
+		ret = DoFileSave(fn, &savedFileName, doc->getSaveAsFormat());
 		if (!ret && !savedFileName.isEmpty())
 			ScMessageBox::warning(this, CommonStrings::trWarning, tr("Your document was saved to a temporary file and could not be moved: \n%1").arg( QDir::toNativeSeparators(savedFileName) ));
 		else if (!ret)
 			ScMessageBox::warning(this, CommonStrings::trWarning, tr("Cannot write the file: \n%1").arg( QDir::toNativeSeparators(fn) ));
+		updateActiveWindowCaption();
 	}
 	else
 		ret = slotFileSaveAs();
 	return ret;
 }
 
-bool ScribusMainWindow::slotFileSaveAs()
+QPair<QString, uint> ScribusMainWindow::getFileNameAndFormatForSaveAs(int fileFormat)
 {
-	bool ret = false;
 	QString fileName;
 	PrefsContext* docContext = m_prefsManager.prefsFile->getContext("docdirs", false);
 	QString wdir;
+	bool currentlyGzipped = false;
 	if (doc->hasName)
 	{
 		QFileInfo fi(doc->documentFileName());
@@ -4078,7 +4084,10 @@ bool ScribusMainWindow::slotFileSaveAs()
 		if (completeBaseName.endsWith(".sla", Qt::CaseInsensitive))
 			completeBaseName.chop(4);
 		else if (completeBaseName.endsWith(".gz", Qt::CaseInsensitive))
+		{
+			currentlyGzipped = false;
 			completeBaseName.chop(3);
+		}
 		wdir = QDir::fromNativeSeparators( fi.path() );
 		fileName  = QDir::fromNativeSeparators( fi.path()+"/"+completeBaseName+".sla" );
 	}
@@ -4094,50 +4103,85 @@ bool ScribusMainWindow::slotFileSaveAs()
 			fileName += "/";
 		fileName += doc->documentFileName() + ".sla";
 	}
-	bool saveCompressed = m_prefsManager.appPrefs.docSetupPrefs.saveCompressed;
-	if (saveCompressed)
+	bool wantsSaveCompressed = currentlyGzipped || m_prefsManager.appPrefs.docSetupPrefs.saveCompressed;
+	if (wantsSaveCompressed)
 		fileName.append(".gz");
 
-	//QString formats = tr("Documents (*.sla *.sla.gz);;All Files (*)");
 	QString formats(FileLoader::getSaveAsFilterString());
 	int optionFlags = fdCompressFile | fdHidePreviewCheckBox;
-	QPair<QString, uint> fileNameVersion { fileName, FORMATID_CURRENTEXPORT };
-	fileNameVersion = CFileDialog( wdir, tr("Save As"), formats, fileName, optionFlags, &saveCompressed);
-	QString fn(fileNameVersion.first);
-	if (!fn.isEmpty())
-	{
-		docContext->set("save_as", fn.left(fn.lastIndexOf("/")));
-		fileName = fn;
-		if (!((fn.endsWith(".sla")) || (fn.endsWith(".sla.gz"))))
-			fileName = fn+".sla";
-		if (overwrite(this, fileName))
-		{
-			QString savedFileName;
-			ret = DoFileSave(fileName, &savedFileName, fileNameVersion.second);
-			if (!ret && !savedFileName.isEmpty())
-				ScMessageBox::warning(this, CommonStrings::trWarning, tr("Your document was saved to a temporary file and could not be moved: \n%1").arg( QDir::toNativeSeparators(savedFileName) ));
-			else if (!ret)
-				ScMessageBox::warning(this, CommonStrings::trWarning, tr("Cannot write the file: \n%1").arg( QDir::toNativeSeparators(fn) ));
-			else
-				doc->pdfOptions().fileName.clear(); // #1482 reset the pdf file name
+	QPair<QString, uint> fileNameAndFormat { fileName, fileFormat };
+	fileNameAndFormat = CFileDialog( wdir, tr("Save As"), formats, fileNameAndFormat, optionFlags, &wantsSaveCompressed);
+
+	fileName = fileNameAndFormat.first;
+
+	if (!fileName.isEmpty())
+	{
+		docContext->set("save_as", fileName.left(fileName.lastIndexOf("/")));
+		fileNameAndFormat.first = fileName;
+		if (!((fileName.endsWith(".sla")) || (fileName.endsWith(".sla.gz"))))
+			fileNameAndFormat.first = fileName+".sla";
+	}
+
+	if (!overwrite(this, fileNameAndFormat.first))
+		fileNameAndFormat = {"", 0};
+
+	return fileNameAndFormat;
+}
+
+bool ScribusMainWindow::slotFileSaveAs()
+{
+	bool ret = false;
+	int saveAsFormat = doc->getSaveAsFormat();
+	QPair<QString, uint> fileNameAndFormat = getFileNameAndFormatForSaveAs(saveAsFormat);
+	if (!fileNameAndFormat.first.isEmpty())
+	{
+		QString savedFileName;
+		ret = DoFileSave(fileNameAndFormat.first, &savedFileName, fileNameAndFormat.second);
+		if (!ret && !savedFileName.isEmpty())
+			ScMessageBox::warning(this, CommonStrings::trWarning, tr("Your document was saved to a temporary file and could not be moved: \n%1").arg( QDir::toNativeSeparators(savedFileName) ));
+		else if (!ret)
+			ScMessageBox::warning(this, CommonStrings::trWarning, tr("Cannot write the file: \n%1").arg( QDir::toNativeSeparators(fileNameAndFormat.first) ));
+		else
+		{
+			doc->pdfOptions().fileName.clear(); // #1482 reset the pdf file name
+			doc->setSaveAsFormat(fileNameAndFormat.second);
 		}
+		updateActiveWindowCaption();
 	}
-	m_mainWindowStatusLabel->setText( tr("Ready"));
+	m_mainWindowStatusLabel->setText(tr("Ready"));
 	return ret;
 }
 
-bool ScribusMainWindow::DoFileSave(const QString& fileName, QString* savedFileName, uint formatID)
+bool ScribusMainWindow::slotFileSaveCopy()
+{
+	bool success = false;
+	QPair<QString, uint> fileNameAndFormat = getFileNameAndFormatForSaveAs();
+	if (!fileNameAndFormat.first.isEmpty())
+	{
+		QString savedFileName;
+		success = DoFileSave(fileNameAndFormat.first, &savedFileName, fileNameAndFormat.second, true);
+		if (!success && !savedFileName.isEmpty())
+			ScMessageBox::warning(this, CommonStrings::trWarning, tr("Your document was saved to a temporary file and could not be moved: \n%1").arg( QDir::toNativeSeparators(savedFileName) ));
+		else if (!success)
+			ScMessageBox::warning(this, CommonStrings::trWarning, tr("Cannot write the file: \n%1").arg( QDir::toNativeSeparators(fileNameAndFormat.first) ));
+		else
+			doc->pdfOptions().fileName.clear(); // #1482 reset the pdf file name
+	}
+	m_mainWindowStatusLabel->setText(tr("Ready"));
+	return success;
+}
+
+bool ScribusMainWindow::DoFileSave(const QString& fileName, QString* savedFileName, uint formatID, bool saveACopy)
 {
 	ScCore->fileWatcher->forceScan();
 	ScCore->fileWatcher->stop();
 	doc->reorganiseFonts();
 	m_mainWindowStatusLabel->setText( tr("Saving..."));
 	mainWindowProgressBar->reset();
-	bool ret = doc->save(fileName, savedFileName, formatID);
+	bool ret = doc->save(fileName, savedFileName, formatID, saveACopy);
 	QApplication::processEvents();
-	if (ret)
+	if (!saveACopy && ret)
 	{
-		updateActiveWindowCaption(fileName);
 		m_undoManager->renameStack(fileName);
 		scrActions["fileRevert"]->setEnabled(false);
 		updateRecent(fileName);
@@ -7553,7 +7597,7 @@ void ScribusMainWindow::editSymbolEnd()
 	layerPalette->setEnabled(true);
 	if (outlinePalette->isVisible())
 		outlinePalette->BuildTree(false);
-	updateActiveWindowCaption(doc->documentFileName());
+	updateActiveWindowCaption();
 }
 
 void ScribusMainWindow::editInlineStart(int id)
@@ -7602,7 +7646,7 @@ void ScribusMainWindow::editInlineEnd()
 	layerPalette->setEnabled(true);
 	if (outlinePalette->isVisible())
 		outlinePalette->BuildTree(false);
-	updateActiveWindowCaption(doc->documentFileName());
+	updateActiveWindowCaption();
 }
 
 void ScribusMainWindow::editMasterPagesStart(const QString& temp)
@@ -7994,20 +8038,19 @@ void ScribusMainWindow::StatusPic()
 	delete dia;
 }
 
-QPair<QString, uint> ScribusMainWindow::CFileDialog(const QString& workingDirectory, const QString& dialogCaption, const QString& fileFilter, const QString& defaultFilename, int optionFlags, bool *useCompression, bool *useFonts, bool *useProfiles)
+QPair<QString, uint> ScribusMainWindow::CFileDialog(const QString& workingDirectory, const QString& dialogCaption, const QString& fileFilter, const QPair<QString, uint>& defaultFilenameVersion, int optionFlags, bool *useCompression, bool *useFonts, bool *useProfiles)
 {
-	// changed from "this" to qApp->activeWindow() to be sure it will be opened
-	// with the current active window as parent. E.g. it won't hide StoryEditor etc. -- PV
 	CustomFDialog *dia = new CustomFDialog(QApplication::activeWindow(), workingDirectory, dialogCaption, fileFilter, optionFlags);
-	if (!defaultFilename.isEmpty())
+	dia->selectNameFilter(defaultFilenameVersion.second);
+	if (!defaultFilenameVersion.first.isEmpty())
 	{
-		QString tmpFileName = defaultFilename;
-		if (tmpFileName.endsWith(".gz", Qt::CaseInsensitive))
-			tmpFileName.chop(3);
-		QFileInfo f(tmpFileName);
+		QString tmpFilename = defaultFilenameVersion.first;
+		if (tmpFilename.endsWith(".gz", Qt::CaseInsensitive))
+			tmpFilename.chop(3);
+		QFileInfo f(tmpFilename);
 		dia->setExtension(f.suffix());
 		dia->setZipExtension(f.suffix() + ".gz");
-		dia->setSelection(defaultFilename);
+		dia->setSelection(defaultFilenameVersion.first);
 		if (useCompression != nullptr)
 			dia->setSaveZipFile(*useCompression);
 	}
@@ -8021,8 +8064,7 @@ QPair<QString, uint> ScribusMainWindow::CFileDialog(const QString& workingDirect
 			dia->setIncludeProfiles(*useProfiles);
 	}
 
-	QPair<QString, uint> fileNameVersion;
-	fileNameVersion.second = FORMATID_CURRENTEXPORT;
+	QPair<QString, uint> filenameVersion;
 	if (dia->exec() == QDialog::Accepted)
 	{
 		if (!(optionFlags & fdDirectoriesOnly))
@@ -8043,18 +8085,25 @@ QPair<QString, uint> ScribusMainWindow::CFileDialog(const QString& workingDirect
 				*useProfiles = dia->includeProfiles();
 		}
 		this->repaint();
-		fileNameVersion.first = dia->selectedFile();
+		filenameVersion.second = FORMATID_CURRENTEXPORT;
+		filenameVersion.first = dia->selectedFile();
 		//Qt 6.8 changes what selectedNameFilter returns.. now includes the file extension list. Remove it.
 		QString formatName(dia->selectedNameFilter());
 		int location = formatName.indexOf("(");
 		if (location > 0)
 			formatName.truncate(location);
 		formatName = formatName.trimmed();
-		fileNameVersion.second=FileLoader::findFormatIDFromDescription(formatName);
+		filenameVersion.second=FileLoader::findFormatIDFromDescription(formatName);
 		QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
 	}
 	delete dia;
-	return fileNameVersion;
+	return filenameVersion;
+}
+
+QPair<QString, uint> ScribusMainWindow::CFileDialog(const QString& workingDirectory, const QString& dialogCaption, const QString& fileFilter, const QString& defaultFilename, int optionFlags, bool *useCompression, bool *useFonts, bool *useProfiles)
+{
+	QPair<QString, uint> filenameVersion{defaultFilename, FORMATID_CURRENTEXPORT};
+	return CFileDialog(workingDirectory, dialogCaption, fileFilter, filenameVersion, optionFlags, useCompression, useFonts, useProfiles);
 }
 
 
@@ -8602,6 +8651,18 @@ void ScribusMainWindow::updateActiveWindowCaption(const QString &newCaption)
 	ActWin->setWindowTitle(QDir::toNativeSeparators(newCaption));
 }
 
+void ScribusMainWindow::updateActiveWindowCaption()
+{
+	if (!HaveDoc)
+		return;
+	QString caption = QDir::toNativeSeparators(doc->documentFileName());
+	if (doc->isModified())
+		caption += QStringLiteral("*");
+	if (!doc->isDefaultSaveAsFormat())
+		caption += QStringLiteral("|") + LoadSavePlugin::fileDialogSaveFilterName(doc->getSaveAsFormat());
+	ActWin->setWindowTitle(caption);
+};
+
 void ScribusMainWindow::dragEnterEvent ( QDragEnterEvent* e)
 {
 	bool accepted = false;
diff --git a/scribus/scribus.h b/scribus/scribus.h
index 7b2a7185b..d5434f0b9 100644
--- a/scribus/scribus.h
+++ b/scribus/scribus.h
@@ -156,7 +156,7 @@ public:
 
 	ScribusDoc* doFileNew(double width, double height, double topMargin, double leftMargin, double rightMargin, double bottomMargin, double columnDistance, double columnCount, bool autoTextFrames, int pageArrangement, int unitIndex, int firstPageLocation, int orientation, int firstPageNumber, const QString& defaultPageSize, bool requiresGUI, int pageCount = 1, bool showView = true, int marginPreset = 0);
 	ScribusDoc* newDoc(double width, double height, double topMargin, double leftMargin, double rightMargin, double bottomMargin, double columnDistance, double columnCount, bool autoTextFrames, int pageArrangement, int unitIndex, int firstPageLocation, int orientation, int firstPageNumber, const QString& defaultPageSize, bool requiresGUI, int pageCount = 1, bool showView = true, int marginPreset = 0);
-	bool DoFileSave(const QString& fileName, QString* savedFileName = nullptr, uint formatID = FORMATID_CURRENTEXPORT);
+	bool DoFileSave(const QString& fileName, QString* savedFileName = nullptr, uint formatID = FORMATID_CURRENTEXPORT, bool saveACopy = false);
 
 	void changeEvent(QEvent *e) override;
 	void closeEvent(QCloseEvent *ce) override;
@@ -174,7 +174,9 @@ public:
 	bool getPDFDriver(const QString & filename, const std::vector<int> & pageNumbers, const QMap<int, QImage> & thumbs, QString& error, bool* cancelled = nullptr);
 	bool DoSaveAsEps(const QString& fn, QString& error);
 	QPair<QString, uint> CFileDialog(const QString& workingDirectory = ".", const QString& dialogCaption = "", const QString& fileFilter = "", const QString& defNa = "",
-						int optionFlags = fdExistingFiles, bool *useCompression = 0, bool *useFonts = 0, bool *useProfiles = 0);
+						int optionFlags = fdExistingFiles, bool *useCompression = nullptr, bool *useFonts = nullptr, bool *useProfiles = nullptr);
+	QPair<QString, uint> CFileDialog(const QString& workingDirectory, const QString& dialogCaption, const QString& fileFilter, const QPair<QString, uint>& filenameVersion,
+		int optionFlags = fdExistingFiles, bool *useCompression = nullptr, bool *useFonts = nullptr, bool *useProfiles = nullptr);
 	/*! \brief Recalculate the colors after changing CMS settings.
 	Call the appropriate document function and then update the GUI elements.
 	\param dia optional progress widget */
@@ -321,7 +323,14 @@ public slots:
 	void extrasMenuAboutToShow();
 	void newActWin(QMdiSubWindow *w);
 	void closeActiveWindowMasterPageEditor();
+	/**
+	 * Use newCaption for the the window title
+	 */
 	void updateActiveWindowCaption(const QString &newCaption);
+	/**
+	 * Use the information about the current document to create the window title
+	 */
+	void updateActiveWindowCaption();
 	void windowsMenuActivated(int id);
 	void PutScrap(int scID);
 	void PutToInline(const QString& buffer);
@@ -377,6 +386,7 @@ public slots:
 	bool slotFileSave();
 	/** \brief save a document under a different filename*/
 	bool slotFileSaveAs();
+	bool slotFileSaveCopy();
 	void slotFileRevert();
 	/** \brief Sichert den Text eines Elements */
 	void SaveText();
@@ -475,10 +485,10 @@ public slots:
 	/** \brief Switch appMode
 	\param mode TODO learn modes*/
 	void setAppModeByToggle(bool isOn, int newMode);
-	/** \brief Neues Dokument erzeugt */
+	/** \brief A new document has been created */
 	void HaveNewDoc();
 	void HaveNewSel();
-	/** Dokument ist geaendert worden */
+	/** The document has been changed */
 	void slotDocCh(bool reb = true);
 	/** Page preview changed */
 	void slotPreviewCh();
@@ -631,6 +641,16 @@ private:
 	void initPalettes();
 	void initScrapbook();
 	void updateColorMenu(QProgressBar* progressBar = nullptr);
+	/**
+	 * Get the file name and format version for a new Scribus document.
+	 *
+	 * Display the "Save As" dialog and sanitize the file name entered by the user.
+	 *
+	 * The file name is empty if the user cancels the process or the file is invalid.
+	 *
+	 * @return A pair of file name (QString) and Scribus File Format Version (uint)
+	 */
+	QPair<QString, uint> getFileNameAndFormatForSaveAs(int fileFormat=FORMATID_CURRENTEXPORT);
 
 	int m_ScriptRunning {0};
 
diff --git a/scribus/scribusdoc.cpp b/scribus/scribusdoc.cpp
index c77efe316..0411e5b50 100644
--- a/scribus/scribusdoc.cpp
+++ b/scribus/scribusdoc.cpp
@@ -2349,6 +2349,23 @@ bool ScribusDoc::isModified() const
 	return m_modified;
 }
 
+void ScribusDoc::setSaveAsFormat(int saveAsFormat)
+{
+	if (m_saveAsFormat != saveAsFormat)
+	{
+		m_saveAsFormat = saveAsFormat;
+	}
+}
+
+int ScribusDoc::getSaveAsFormat() const
+{
+	return m_saveAsFormat;
+}
+
+bool ScribusDoc::isDefaultSaveAsFormat() const {
+	return m_saveAsFormat == FORMATID_CURRENTEXPORT;
+}
+
 bool ScribusDoc::isUndoRedoOngoing() const
 {
 	return (m_undoRedoOngoing != 0);
@@ -4778,7 +4795,7 @@ void ScribusDoc::restoreCopyPage(SimpleState* ss, bool isUndo)
 }
 
 
-bool ScribusDoc::save(const QString& fileName, QString* savedFile, uint formatID)
+bool ScribusDoc::save(const QString& fileName, QString* savedFile, uint formatID, bool saveACopy)
 {
 	QProgressBar* mainWindowProgressBar = nullptr;
 	if (ScCore->usingGUI())
@@ -4790,6 +4807,8 @@ bool ScribusDoc::save(const QString& fileName, QString* savedFile, uint formatID
 	bool ret = fl.saveFile(fileName, this, savedFile, formatID);
 	if (!ret)
 		return false;
+	if (saveACopy)
+		return true;
 	setDocumentFileName(fileName);
 	setModified(false);
 	hasName = true;
diff --git a/scribus/scribusdoc.h b/scribus/scribusdoc.h
index 389113876..0a5676436 100644
--- a/scribus/scribusdoc.h
+++ b/scribus/scribusdoc.h
@@ -112,6 +112,19 @@ public:
 	bool isLoading() const;
 	void setModified(bool);
 	bool isModified() const;
+	/**
+	 * Set the latest format used when saving a document as
+	 *
+	 * @param saveAsFormat one of FORMATID_SLA*EXPORT from plugins/formatidlist.h
+	 */
+	void setSaveAsFormat(int saveAsFormat);
+	/**
+	 * Get the latest format used when saving a document as
+	 *
+	 * @return saveAsFormat one of FORMATID_SLA*EXPORT from plugins/formatidlist.h
+	 */
+	int getSaveAsFormat() const;
+	bool isDefaultSaveAsFormat() const;
 	bool isUndoRedoOngoing() const;
 /** Setzt die Seitenattribute */
 	void setPage(double w, double h, double t, double l, double r, double b, double sp, double ab, bool atf, int fp);
@@ -818,7 +831,7 @@ public:
 	/**
 	 * @brief Save function
 	 */
-	bool save(const QString& fileName, QString* savedFile = nullptr, uint formatID = FORMATID_CURRENTEXPORT);
+	bool save(const QString& fileName, QString* savedFile = nullptr, uint formatID = FORMATID_CURRENTEXPORT, bool saveACopy = false);
 	/**
 	 * @brief Set the page margins. Current code uses current page only, also provide a (currently, TODO) option for this.
 	 */
@@ -1312,6 +1325,7 @@ protected:
 	UndoManager * const m_undoManager;
 	bool m_loading {false};
 	bool m_modified {false};
+	int m_saveAsFormat {FORMATID_CURRENTEXPORT};
 	int  m_undoRedoOngoing {0};
 	int m_ActiveLayer {0};
 	double m_docUnitRatio;
diff --git a/scribus/ui/customfdialog.cpp b/scribus/ui/customfdialog.cpp
index 169d14bc6..6691a49fd 100644
--- a/scribus/ui/customfdialog.cpp
+++ b/scribus/ui/customfdialog.cpp
@@ -39,6 +39,7 @@ for which a new license (GPL+exception) is in place.
 #include <QScreen>
 #include <QTextCodec>
 #include <QVBoxLayout>
 
 #include "customfdialog.h"
 
@@ -296,7 +297,6 @@ CustomFDialog::CustomFDialog(QWidget *parent, const QString &wDir, const QString
 	fileDialog = new ScFileWidget(this, contextFlags);
 	fileDialog->setIconProvider(new ImIconProvider());
 	fileDialog->setNameFilter(filter);
-	fileDialog->selectNameFilter(filter);
 	fileDialog->setDirectory(wDir);
 	hboxLayout->addWidget(fileDialog);
 	vboxLayout1 = new QVBoxLayout;
@@ -570,6 +570,16 @@ void CustomFDialog::togglePreview()
 	update();
 }
 
+void CustomFDialog::selectNameFilter(const int nameFilter)
+{
+	fileDialog->selectNameFilter(LoadSavePlugin::fileDialogSaveFilter(nameFilter));
+}
+
+void CustomFDialog::selectNameFilter(const QString& nameFilter)
+{
+	fileDialog->selectNameFilter(nameFilter);
+}
+
 void CustomFDialog::setSelection(const QString& fileName)
 {
 	fileDialog->selectFile( QFileInfo(fileName).fileName() );
diff --git a/scribus/ui/customfdialog.h b/scribus/ui/customfdialog.h
index 4dfde5add..6229469ae 100644
--- a/scribus/ui/customfdialog.h
+++ b/scribus/ui/customfdialog.h
@@ -102,9 +102,11 @@ public:
 	\param filter a mask/filter. E.g.: *.txt
 	\param flags combination of fdFlags, default to fdExistingFiles
 	*/
-	CustomFDialog(QWidget *parent, const QString& wDir, const QString& caption = "",  const QString& filter = "", int flags = fdExistingFiles, fwContextFlags contextFlags = contextNone);
+	explicit CustomFDialog(QWidget *parent, const QString& wDir, const QString& caption = "",  const QString& filter = "", int flags = fdExistingFiles, fwContextFlags contextFlags = contextNone);
 	~CustomFDialog() = default;
 
+	void selectNameFilter(const int nameFilter);
+	void selectNameFilter(const QString& nameFilter);
 	void setSelection(const QString& fileName);
 	QString selectedFile() const;
 	QStringList selectedFiles() const;
