View Issue Details

IDProjectCategoryView StatusLast Update
0015724ScribusText Frames / Story Editorpublic2026-09-09 14:49
Reporterale Assigned To 
PrioritynormalSeverityfeatureReproducibilityN/A
Status newResolutionopen 
Product Version1.5.5.svn 
Summary0015724: make the unlink tool a single click one
Descriptioncurrently, the unlink tool is only active when at least a text frame is selected.
for the action to take place, one need to click on a second frame.

i did not test the behavior when multiple text frame are selected but it's a rather awkward situation.

i suggest that the behavior to be changed in the following way:

- the tool is inactive when no text frame is selected
- when one single text frame is selected, the link to the next frame is removed (if any)
- when multiple text frames are selected, the link between the selected frames are removed
Tags#patch_to_be_reviewed, #please_test
PatchNo

Relationships

related to 0015721 new Better label for last "Object > Text frame links > Cut" options 
related to 0015519 new unlinking text frames should work in "both direction". 
parent of 0015954 new Improve link/unlink tools usability 
Not all the children of this issue are yet resolved or closed.

Activities

ale

2019-11-16 09:30

manager   ~0047088

we could even keep it enabled when no item is selected and it will unlink the next tool you click on from its next item.

ale

2019-12-03 17:45

manager   ~0047194

if multiple text frame are seleted, enabling the tool could simply link all the selected items, starting from top left.

pygmee

2025-03-26 19:38

developer   ~0052342

Having several behavior for the same tool is cool for powerusers, but less for the other that may not understand when a behavior is applied and when another, giving the feeling of random unlinking. And since nobody reads the docs anymore… Wouldn’t it be better to use keyboard modifiers on click, for example :
select the unlink tool
click on a frame to unlink this frame and the previous
click + Ctrl to unlink with the next
Shift could apply the same with cut text functionnality

buttar

2026-08-26 01:55

reporter   ~0054300

Attached patch changes Unlink Text Frames from the current two-step canvas tool into a direct selection-based command.

* With one selected text frame, the link to its next frame is removed.
* With multiple selected text frames, only links where both frames are selected are removed.

Example:

`A → B → C → D`

* Select B → removes `B → C`
* Select B + C → removes `B → C`
* Select B + C + D → removes `B → C` and `C → D`
* Select A + C → nothing is removed, since the selected frames are not directly linked

The patch reuses the existing `PageItem::unlink()` logic for the actual chain/text handling rather than duplicating unlink behavior.

For multiple selected links, the unlink operations are grouped into a single undo transaction:

`A → B → C → D`

Select B + C + D and unlink:

`A → B C D`

* Undo restores the complete chain in one step:
  `A → B → C → D`
* Redo removes the selected links again in one step.

It also removes the obsolete canvas-mode handling used by the old click-to-unlink workflow and cleans up the related action setup, while keeping the existing mode enum slot to avoid unnecessary renumbering.
unlink-selected-text-frames-v1.02.patch (13,101 bytes)   
Index: scribus/actionmanager.cpp
===================================================================
--- scribus/actionmanager.cpp	(revision 27777)
+++ scribus/actionmanager.cpp	(working copy)
@@ -925,13 +925,13 @@
 	name = "toolsLinkTextFrame";
 	scrActions->insert(name, new ScrAction(ScrAction::DataInt, "tool-link-text-frame", "tool-link-text-frame", "", defaultKey(name), mainWindow, modeLinkFrames));
 	name = "toolsUnlinkTextFrame";
-	scrActions->insert(name, new ScrAction(ScrAction::DataInt, "tool-unlink-text-frame", "tool-unlink-text-frame", "", defaultKey(name), mainWindow, modeUnlinkFrames));
+	scrActions->insert(name, new ScrAction("tool-unlink-text-frame", "tool-unlink-text-frame", "", defaultKey(name), mainWindow));
 	name = "toolsEyeDropper";
 	scrActions->insert(name, new ScrAction(ScrAction::DataInt, "tool-color-picker", "tool-color-picker", "", defaultKey(name), mainWindow, modeEyeDropper));
 	name = "toolsCopyProperties";
 	scrActions->insert(name, new ScrAction(ScrAction::DataInt, "tool-copy-style", "tool-copy-style", "", defaultKey(name), mainWindow, modeCopyProperties));
 	name = "toolsUnlinkTextFrameAndCutText";
-	scrActions->insert(name, new ScrAction(ScrAction::DataInt, QString(), QString(), "", defaultKey(name), mainWindow, modeUnlinkFrames));
+	scrActions->insert(name, new ScrAction(QString(), QString(), "", defaultKey(name), mainWindow));
 
 	//PDF toolbar
 	name = "toolsPDFPushButton";
@@ -982,7 +982,7 @@
 	*modeActionNames << "toolsSelect" << "toolsInsertTextFrame" << "toolsInsertImageFrame" << "toolsInsertTable";
 	*modeActionNames << "toolsInsertShape" << "toolsInsertPolygon" << "toolsInsertArc" << "toolsInsertSpiral" << "toolsInsertLine" << "toolsInsertBezier";
 	*modeActionNames << "toolsInsertFreehandLine" << "toolsInsertCalligraphicLine" << "toolsInsertRenderFrame" << "toolsRotate" << "toolsZoom" << "toolsEditContents";
-	*modeActionNames << "toolsEditWithStoryEditor" << "toolsLinkTextFrame" << "toolsUnlinkTextFrame"; //<< "toolsUnlinkTextFrameAndCutText";
+	*modeActionNames << "toolsEditWithStoryEditor" << "toolsLinkTextFrame";
 	*modeActionNames << "toolsEyeDropper" << "toolsCopyProperties";
 	*modeActionNames << "toolsPDFPushButton" << "toolsPDFRadioButton" << "toolsPDFTextField" << "toolsPDFCheckBox" << "toolsPDFComboBox" << "toolsPDFListBox" << "toolsPDFAnnotText" << "toolsPDFAnnotLink";
 #ifdef HAVE_OSG
@@ -995,7 +995,7 @@
 	}
 
 
-	*nonEditActionNames << "itemLowerToBottom" << "itemRaiseToTop" << "itemRaise" << "itemLower";
+	*nonEditActionNames << "itemLowerToBottom" << "itemRaiseToTop" << "itemRaise" << "itemLower" << "toolsUnlinkTextFrame";
 
 	connect( (*scrActions)["toolsResources"], SIGNAL(triggered()), mainWindow, SLOT(slotResourceManager()) );
 	connectModeActions();
@@ -1309,6 +1309,7 @@
 	(*scrActions)["itemRaiseToTop"]->disconnect();
 	(*scrActions)["itemLower"]->disconnect();
 	(*scrActions)["itemRaise"]->disconnect();
+	(*scrActions)["toolsUnlinkTextFrame"]->disconnect();
 	(*scrActions)["toolsUnlinkTextFrameAndCutText"]->disconnect();
 
 	(*scrActions)["itemTextTransformLowercase"]->disconnect(SIGNAL(triggeredData(int)));
@@ -1363,6 +1364,7 @@
 	connect( (*scrActions)["itemRaiseToTop"], SIGNAL(triggered()), currDoc, SLOT(bringItemSelectionToFront()) );
 	connect( (*scrActions)["itemLower"], SIGNAL(triggered()), currDoc, SLOT(itemSelection_LowerItem()) );
 	connect( (*scrActions)["itemRaise"], SIGNAL(triggered()), currDoc, SLOT(itemSelection_RaiseItem()) );
+	connect( (*scrActions)["toolsUnlinkTextFrame"], SIGNAL(triggered()), currDoc, SLOT(itemSelection_UnlinkTextFrames()) );
 	connect( (*scrActions)["toolsUnlinkTextFrameAndCutText"], SIGNAL(triggered()), currDoc, SLOT(itemSelection_UnlinkTextFrameAndCutText()) );
 	connect( (*scrActions)["itemTextTransformLowercase"], SIGNAL(triggeredData(int)), currDoc, SLOT(itemSelection_SetItemTextCaseTransform(int)) );
 	connect( (*scrActions)["itemTextTransformUppercase"], SIGNAL(triggeredData(int)), currDoc, SLOT(itemSelection_SetItemTextCaseTransform(int)) );
Index: scribus/appmodehelper.cpp
===================================================================
--- scribus/appmodehelper.cpp	(revision 27777)
+++ scribus/appmodehelper.cpp	(working copy)
@@ -16,6 +16,8 @@
 
 #include <algorithm>
 
+#include <QList>
+
 #include "appmodehelper.h"
 #include "appmodes.h"
 #include "canvasmode.h"
@@ -200,9 +202,6 @@
 		case modeLinkFrames:
 			doc->regionsChanged()->update(QRect());
 			break;
-		case modeUnlinkFrames:
-			doc->regionsChanged()->update(QRect());
-			break;
 		default:
 			//No doc open?
 			break;
@@ -408,9 +407,6 @@
 				scmw->slotSelect();
 			}
 			break;
-		case modeUnlinkFrames:
-			doc->regionsChanged()->update(QRect());
-			break;
 		default:
 			//No doc open?
 			break;
@@ -493,6 +489,27 @@
 	}
 	(*a_scrActions)["itemSelectChain"]->setEnabled(anyChained);
 
+	QList<PageItem*> selectedTextFrames;
+	for (int i = 0; i < docSelectionCount; ++i)
+	{
+		PageItem* item = doc->m_Selection->itemAt(i);
+		if (item && item->isTextFrame())
+			selectedTextFrames.append(item);
+	}
+
+	bool canUnlinkTextFrames = selectedTextFrames.count() == 1 && selectedTextFrames.first()->nextInChain();
+	if (selectedTextFrames.count() > 1)
+	{
+		for (PageItem* item : selectedTextFrames)
+		{
+			if (item->nextInChain() && doc->m_Selection->containsItem(item->nextInChain()))
+			{
+				canUnlinkTextFrames = true;
+				break;
+			}
+		}
+	}
+
 	switch (SelectedType)
 	{
 		case -1: // None
@@ -844,6 +861,8 @@
 			break;
 	}
 
+	(*a_scrActions)["toolsUnlinkTextFrame"]->setEnabled(canUnlinkTextFrames && !inAnEditMode);
+
 	if (docSelectionCount == 1)
 	{
 		(*a_scrActions)["itemsUnWeld"]->setEnabled(currItem->isWelded());
@@ -1016,8 +1035,6 @@
 	(*a_scrActions)["toolsEditContents"]->setChecked(newMode == modeEdit);
 	(*a_scrActions)["toolsEditWithStoryEditor"]->setChecked(newMode == modeStoryEditor);
 	(*a_scrActions)["toolsLinkTextFrame"]->setChecked(newMode == modeLinkFrames);
-	(*a_scrActions)["toolsUnlinkTextFrame"]->setChecked(newMode == modeUnlinkFrames);
-	//	(*a_scrActions)["toolsUnlinkTextFrameAndCutText"]->setChecked(newMode == modeUnlinkFrames);
 	(*a_scrActions)["toolsEyeDropper"]->setChecked(newMode == modeEyeDropper);
 	(*a_scrActions)["toolsMeasurements"]->setChecked(newMode == modeMeasurementTool);
 	(*a_scrActions)["toolsCopyProperties"]->setChecked(newMode == modeCopyProperties);
Index: scribus/canvas.cpp
===================================================================
--- scribus/canvas.cpp	(revision 27777)
+++ scribus/canvas.cpp	(working copy)
@@ -2103,7 +2103,7 @@
 void Canvas::drawFrameLinks(ScPainter* painter)
 {
 	PageItem *currItem = nullptr;
-	if ((m_doc->appMode == modeLinkFrames) || (m_doc->appMode == modeUnlinkFrames))
+	if (m_doc->appMode == modeLinkFrames)
 	{
 		if (m_doc->m_Selection->count() > 0)
 			currItem = m_doc->m_Selection->itemAt(0);
@@ -2123,8 +2123,7 @@
 			drawChainLinks(painter, startItem);
 		}
 	}
-	else if (((m_doc->appMode == modeLinkFrames) || (m_doc->appMode == modeUnlinkFrames))
-			 && (currItem != nullptr))
+	else if (currItem != nullptr)
 	{
 		drawChainLinks(painter, currItem->firstInChain());
 	}
Index: scribus/canvasmode.cpp
===================================================================
--- scribus/canvasmode.cpp	(revision 27777)
+++ scribus/canvasmode.cpp	(working copy)
@@ -151,7 +151,6 @@
 			result = new CanvasMode_ObjImport(view);
 			break;
 		case modeLinkFrames:
-		case modeUnlinkFrames:
 			result = new CanvasMode_FrameLinks(view);
 			break;
 		case modeImportImage:
@@ -874,7 +873,6 @@
 					/* fall through */
 					[[fallthrough]];
 				case modeLinkFrames:
-				case modeUnlinkFrames:
 				case modeEditWeldPoint:
 				case modeEyeDropper:
 				case modeImportObject:
Index: scribus/canvasmode_copyproperties.cpp
===================================================================
--- scribus/canvasmode_copyproperties.cpp	(revision 27777)
+++ scribus/canvasmode_copyproperties.cpp	(working copy)
@@ -306,7 +306,7 @@
 			}
 		}
 	}
-	else if ( (m->modifiers() & SELECT_MULTIPLE) == Qt::NoModifier || (m_doc->appMode == modeLinkFrames) || (m_doc->appMode == modeUnlinkFrames) )
+	else if ( (m->modifiers() & SELECT_MULTIPLE) == Qt::NoModifier || (m_doc->appMode == modeLinkFrames) )
 	{
 		m_view->deselectItems(false);
 	}
@@ -358,7 +358,7 @@
 		return true;
 	}
 	m_doc->m_Selection->connectItemToGUI();
-	if ( !(m->modifiers() & SELECT_MULTIPLE) || (m_doc->appMode == modeLinkFrames) || (m_doc->appMode == modeUnlinkFrames))
+	if ( !(m->modifiers() & SELECT_MULTIPLE) || (m_doc->appMode == modeLinkFrames))
 		m_view->deselectItems(true);
 	return false;
 }
Index: scribus/canvasmode_framelinks.cpp
===================================================================
--- scribus/canvasmode_framelinks.cpp	(revision 27777)
+++ scribus/canvasmode_framelinks.cpp	(working copy)
@@ -296,23 +296,6 @@
 				m_doc->ElemToLink = nullptr;
 			m_doc->m_Selection->delaySignalsOff();
 			break;
-		case modeUnlinkFrames:
-			if (m->button() != Qt::LeftButton)
-				break;
-			// #14334: delay selection signals so that (un)link actions get properly enabled/disabled
-			m_doc->m_Selection->delaySignalsOn();
-			SeleItem(m);
-			if (GetItem(&currItem) && (currItem->isTextFrame()))
-			{
-				if (currItem->prevInChain() != nullptr)
-				{
-					currItem->prevInChain()->unlink();
-				}
-				// unlink calls PageItem::update				emit DocChanged();
-				// m_view->updateContents();
-			}
-			m_doc->m_Selection->delaySignalsOff();
-			break;
 	}
 }
 
@@ -333,7 +316,7 @@
 		createContextMenu(currItem, mousePointDoc.x(), mousePointDoc.y());
 		return;
 	}
-	if ((m_doc->appMode == modeLinkFrames) || (m_doc->appMode == modeUnlinkFrames))
+	if (m_doc->appMode == modeLinkFrames)
 	{
 		m_view->updateContents();
 		if (!PrefsManager::instance().appPrefs.uiPrefs.stickyTools || m_doc->ElemToLink == nullptr)
@@ -456,7 +439,7 @@
 			}
 		}
 	}
-	else if ( (m->modifiers() & SELECT_MULTIPLE) == Qt::NoModifier || (m_doc->appMode == modeLinkFrames) || (m_doc->appMode == modeUnlinkFrames) )
+	else if ( (m->modifiers() & SELECT_MULTIPLE) == Qt::NoModifier || (m_doc->appMode == modeLinkFrames) )
 	{
 		m_view->deselectItems(false);
 	}
@@ -508,7 +491,7 @@
 		return true;
 	}
 	m_doc->m_Selection->connectItemToGUI();
-	if ( !(m->modifiers() & SELECT_MULTIPLE) || (m_doc->appMode == modeLinkFrames) || (m_doc->appMode == modeUnlinkFrames))
+	if ( !(m->modifiers() & SELECT_MULTIPLE) || (m_doc->appMode == modeLinkFrames))
 		m_view->deselectItems(true);
 	return false;
 }
Index: scribus/scribusdoc.cpp
===================================================================
--- scribus/scribusdoc.cpp	(revision 27777)
+++ scribus/scribusdoc.cpp	(working copy)
@@ -17440,6 +17440,55 @@
 	m_documentFileName = documentFileName;
 }
 
+void ScribusDoc::itemSelection_UnlinkTextFrames(Selection* customSelection)
+{
+	Selection* itemSelection = (customSelection != nullptr) ? customSelection : m_Selection;
+	assert(itemSelection != nullptr);
+	const int selectedItemCount = itemSelection->count();
+	if (selectedItemCount == 0)
+		return;
+
+	QList<PageItem*> selectedTextFrames;
+	for (int i = 0; i < selectedItemCount; ++i)
+	{
+		PageItem* item = itemSelection->itemAt(i);
+		if (item && item->isTextFrame())
+			selectedTextFrames.append(item);
+	}
+
+	QList<PageItem*> itemsToUnlink;
+	if (selectedTextFrames.count() == 1)
+	{
+		if (selectedTextFrames.first()->nextInChain())
+			itemsToUnlink.append(selectedTextFrames.first());
+	}
+	else
+	{
+		for (PageItem* item : selectedTextFrames)
+		{
+			if (item->nextInChain() && itemSelection->containsItem(item->nextInChain()))
+				itemsToUnlink.append(item);
+		}
+	}
+
+	if (itemsToUnlink.isEmpty())
+		return;
+
+	UndoTransaction transaction;
+	if (UndoManager::undoEnabled() && itemsToUnlink.count() > 1)
+		transaction = m_undoManager->beginTransaction(Um::Selection, Um::ITextFrame, Um::UnlinkTextFrame, "", Um::ITextFrame);
+
+	for (PageItem* item : itemsToUnlink)
+		item->unlink();
+
+	if (transaction)
+		transaction.commit();
+
+	regionsChanged()->update(QRectF());
+	changed();
+	itemSelection->itemAt(0)->emitAllToGUI();
+}
+
 void ScribusDoc::itemSelection_UnlinkTextFrameAndCutText( Selection *customSelection)
 {
 	Selection* itemSelection = (customSelection != nullptr) ? customSelection : m_Selection;
Index: scribus/scribusdoc.h
===================================================================
--- scribus/scribusdoc.h	(revision 27777)
+++ scribus/scribusdoc.h	(working copy)
@@ -1620,6 +1620,7 @@
 		void itemSelection_Rotate(double angle, Selection* customSelection = nullptr);
 		void itemSelection_DoHyphenate();
 		void itemSelection_DoDeHyphenate();
+		void itemSelection_UnlinkTextFrames(Selection* customSelection = nullptr);
 		void itemSelection_UnlinkTextFrameAndCutText(Selection* customSelection = nullptr);
 		void itemSelection_SendToLayer(int layerID);
 		void itemSelection_SetImageOffset(double x, double y, Selection* customSelection = nullptr);

buttar

2026-09-09 14:49

reporter   ~0054447

Here is another version (see which one is best).

* Depends on `tc1-text-chain-ui-terminology-v1.01.patch` (from 0015507) for terminology changes.

* Also resolves the selection-order issues described in 0015519 and the redundant second-click case in 0010872.
* Makes Break Text Chain a direct selection-driven command instead of entering an interactive unlink mode.
  -- Single selected text frame ('A'): `[A] → B → C` → Break Text Chain → `A B → C`
  -- Multiple selected text frames: `A → [B] → [C] → D` → Break Text Chain → `A → B C → D`
  -- Selection order does not affect the result: select `C`, then `B` in `A → B → C → D` → Break Text Chain → `A → B C → D`
  -- Non-adjacent selected frames do not cause unrelated chain breaks: `[A] → B → [C] → D` → Break Text Chain → unchanged
  -- Mixed selections are supported; non-text items are ignored.
  -- Preserves the current selection after the operation.
  -- Groups multi-frame breaks into a single Undo step: `[A] → [B] [C] → [D]` → Break Text Chain → `A B C D` → Undo → both connections restored
  -- Uses the same eligibility logic for command enablement and execution.
  -- Keeps text-edit shortcut suppression intact, so the `U` shortcut cannot trigger while typing.
  -- Uses `QSet<PageItem*>` for efficient selected-frame membership checks.
  -- Leaves Break Text Chain and Split Text, existing chain serialization, and underlying `PageItem::unlink()` behaviour unchanged.
* Also fully addresses 0010872 and substantially addresses 0015519. (relates to #0015954)
tcu1-selection-driven-unchain-v1.01.patch (9,088 bytes)   
Index: scribus/actionmanager.cpp
===================================================================
--- scribus/actionmanager.cpp	(revision 27827)
+++ scribus/actionmanager.cpp	(working copy)
@@ -925,7 +925,8 @@
 	name = "toolsLinkTextFrame";
 	scrActions->insert(name, new ScrAction(ScrAction::DataInt, "tool-link-text-frame", "tool-link-text-frame", "", defaultKey(name), mainWindow, modeLinkFrames));
 	name = "toolsUnlinkTextFrame";
-	scrActions->insert(name, new ScrAction(ScrAction::DataInt, "tool-unlink-text-frame", "tool-unlink-text-frame", "", defaultKey(name), mainWindow, modeUnlinkFrames));
+	scrActions->insert(name, new ScrAction("tool-unlink-text-frame", "tool-unlink-text-frame", "", defaultKey(name), mainWindow));
+	(*scrActions)[name]->setEnabled(false);
 	name = "toolsEyeDropper";
 	scrActions->insert(name, new ScrAction(ScrAction::DataInt, "tool-color-picker", "tool-color-picker", "", defaultKey(name), mainWindow, modeEyeDropper));
 	name = "toolsCopyProperties";
@@ -982,7 +983,7 @@
 	*modeActionNames << "toolsSelect" << "toolsInsertTextFrame" << "toolsInsertImageFrame" << "toolsInsertTable";
 	*modeActionNames << "toolsInsertShape" << "toolsInsertPolygon" << "toolsInsertArc" << "toolsInsertSpiral" << "toolsInsertLine" << "toolsInsertBezier";
 	*modeActionNames << "toolsInsertFreehandLine" << "toolsInsertCalligraphicLine" << "toolsInsertRenderFrame" << "toolsRotate" << "toolsZoom" << "toolsEditContents";
-	*modeActionNames << "toolsEditWithStoryEditor" << "toolsLinkTextFrame" << "toolsUnlinkTextFrame"; //<< "toolsUnlinkTextFrameAndCutText";
+	*modeActionNames << "toolsEditWithStoryEditor" << "toolsLinkTextFrame"; //<< "toolsUnlinkTextFrameAndCutText";
 	*modeActionNames << "toolsEyeDropper" << "toolsCopyProperties";
 	*modeActionNames << "toolsPDFPushButton" << "toolsPDFRadioButton" << "toolsPDFTextField" << "toolsPDFCheckBox" << "toolsPDFComboBox" << "toolsPDFListBox" << "toolsPDFAnnotText" << "toolsPDFAnnotLink";
 #ifdef HAVE_OSG
@@ -995,7 +996,7 @@
 	}
 
 
-	*nonEditActionNames << "itemLowerToBottom" << "itemRaiseToTop" << "itemRaise" << "itemLower";
+	*nonEditActionNames << "itemLowerToBottom" << "itemRaiseToTop" << "itemRaise" << "itemLower" << "toolsUnlinkTextFrame";
 
 	connect( (*scrActions)["toolsResources"], SIGNAL(triggered()), mainWindow, SLOT(slotResourceManager()) );
 	connectModeActions();
@@ -1309,6 +1310,7 @@
 	(*scrActions)["itemRaiseToTop"]->disconnect();
 	(*scrActions)["itemLower"]->disconnect();
 	(*scrActions)["itemRaise"]->disconnect();
+	(*scrActions)["toolsUnlinkTextFrame"]->disconnect();
 	(*scrActions)["toolsUnlinkTextFrameAndCutText"]->disconnect();
 
 	(*scrActions)["itemTextTransformLowercase"]->disconnect(SIGNAL(triggeredData(int)));
@@ -1363,6 +1365,7 @@
 	connect( (*scrActions)["itemRaiseToTop"], SIGNAL(triggered()), currDoc, SLOT(bringItemSelectionToFront()) );
 	connect( (*scrActions)["itemLower"], SIGNAL(triggered()), currDoc, SLOT(itemSelection_LowerItem()) );
 	connect( (*scrActions)["itemRaise"], SIGNAL(triggered()), currDoc, SLOT(itemSelection_RaiseItem()) );
+	connect( (*scrActions)["toolsUnlinkTextFrame"], SIGNAL(triggered()), currDoc, SLOT(itemSelection_UnlinkTextFrames()) );
 	connect( (*scrActions)["toolsUnlinkTextFrameAndCutText"], SIGNAL(triggered()), currDoc, SLOT(itemSelection_UnlinkTextFrameAndCutText()) );
 	connect( (*scrActions)["itemTextTransformLowercase"], SIGNAL(triggeredData(int)), currDoc, SLOT(itemSelection_SetItemTextCaseTransform(int)) );
 	connect( (*scrActions)["itemTextTransformUppercase"], SIGNAL(triggeredData(int)), currDoc, SLOT(itemSelection_SetItemTextCaseTransform(int)) );
@@ -1914,7 +1917,7 @@
 	(*scrActions)["toolsDocumentLog"]->setStatusTextAndShortcut( tr("Display logged warnings and errors from editing activities"));
 	(*scrActions)["toolsRotate"]->setStatusTextAndShortcut( tr("Rotate an item"));
 	(*scrActions)["toolsSelect"]->setStatusTextAndShortcut( tr("Select an item"));
-	(*scrActions)["toolsUnlinkTextFrame"]->setStatusTextAndShortcut( tr("Break a text chain before the clicked frame"));
+	(*scrActions)["toolsUnlinkTextFrame"]->setStatusTextAndShortcut( tr("Break the outgoing chain connection from a selected text frame, or chain connections between selected text frames"));
 	(*scrActions)["toolsUnlinkTextFrameAndCutText"]->setStatusTextAndShortcut( tr("Break the text chain at the selected frame and split its text between the resulting chains"));
 	(*scrActions)["toolsZoom"]->setStatusTextAndShortcut( tr("Zoom in or out"));
 	(*scrActions)["viewPreviewMode"]->setStatusTextAndShortcut( tr("Enable preview mode"));
Index: scribus/appmodehelper.cpp
===================================================================
--- scribus/appmodehelper.cpp	(revision 27827)
+++ scribus/appmodehelper.cpp	(working copy)
@@ -993,6 +993,9 @@
 		scmw->layerMenu->setEnabled(!inAnEditMode);
 	}
 
+	// Ordinary chain breaking is selection-driven: enable it only when the
+	// current selection identifies at least one connection to break.
+	(*a_scrActions)["toolsUnlinkTextFrame"]->setEnabled(doc->itemSelection_CanUnlinkTextFrames());
 }
 
 void AppModeHelper::setModeActionsPerMode(int newMode)
@@ -1016,7 +1019,6 @@
 	(*a_scrActions)["toolsEditContents"]->setChecked(newMode == modeEdit);
 	(*a_scrActions)["toolsEditWithStoryEditor"]->setChecked(newMode == modeStoryEditor);
 	(*a_scrActions)["toolsLinkTextFrame"]->setChecked(newMode == modeLinkFrames);
-	(*a_scrActions)["toolsUnlinkTextFrame"]->setChecked(newMode == modeUnlinkFrames);
 	//	(*a_scrActions)["toolsUnlinkTextFrameAndCutText"]->setChecked(newMode == modeUnlinkFrames);
 	(*a_scrActions)["toolsEyeDropper"]->setChecked(newMode == modeEyeDropper);
 	(*a_scrActions)["toolsMeasurements"]->setChecked(newMode == modeMeasurementTool);
Index: scribus/scribusdoc.cpp
===================================================================
--- scribus/scribusdoc.cpp	(revision 27827)
+++ scribus/scribusdoc.cpp	(working copy)
@@ -40,6 +40,7 @@
 #include <QRandomGenerator>
 #include <QRegularExpression>
 #include <QScopedValueRollback>
+#include <QSet>
 #include <QStringList>
 #include <QtAlgorithms>
 #include <QTime>
@@ -17440,6 +17441,74 @@
 	m_documentFileName = documentFileName;
 }
 
+static QList<PageItem*> unlinkableTextFrameSources(Selection* itemSelection)
+{
+	QList<PageItem*> textFrames;
+	QSet<PageItem*> selectedTextFrames;
+	textFrames.reserve(itemSelection->count());
+	selectedTextFrames.reserve(itemSelection->count());
+	for (int i = 0; i < itemSelection->count(); ++i)
+	{
+		PageItem* item = itemSelection->itemAt(i);
+		if (item && item->isTextFrame())
+		{
+			textFrames.append(item);
+			selectedTextFrames.insert(item);
+		}
+	}
+
+	QList<PageItem*> sources;
+	if (textFrames.count() == 1)
+	{
+		PageItem* item = textFrames.first();
+		if (item->nextInChain())
+			sources.append(item);
+		return sources;
+	}
+
+	for (PageItem* item : textFrames)
+	{
+		PageItem* next = item->nextInChain();
+		if (next && selectedTextFrames.contains(next))
+			sources.append(item);
+	}
+	return sources;
+}
+
+bool ScribusDoc::itemSelection_CanUnlinkTextFrames(Selection* customSelection) const
+{
+	Selection* itemSelection = (customSelection != nullptr) ? customSelection : m_Selection;
+	return itemSelection && !unlinkableTextFrameSources(itemSelection).isEmpty();
+}
+
+void ScribusDoc::itemSelection_UnlinkTextFrames(Selection* customSelection)
+{
+	Selection* itemSelection = (customSelection != nullptr) ? customSelection : m_Selection;
+	if (!itemSelection)
+		return;
+
+	const QList<PageItem*> sources = unlinkableTextFrameSources(itemSelection);
+	if (sources.isEmpty())
+		return;
+
+	UndoTransaction transaction;
+	if (UndoManager::undoEnabled() && sources.count() > 1)
+		transaction = m_undoManager->beginTransaction(Um::SelectionGroup, Um::IGroup, Um::UnlinkTextFrame, "", Um::ITextFrame);
+
+	for (PageItem* source : sources)
+		source->unlink();
+
+	if (transaction)
+		transaction.commit();
+
+	regionsChanged()->update(QRectF());
+	changed();
+	if (itemSelection->count() > 0)
+		itemSelection->itemAt(0)->emitAllToGUI();
+	if (customSelection == nullptr && m_ScMW)
+		m_ScMW->HaveNewSel();
+}
+
 void ScribusDoc::itemSelection_UnlinkTextFrameAndCutText( Selection *customSelection)
 {
 	Selection* itemSelection = (customSelection != nullptr) ? customSelection : m_Selection;
Index: scribus/scribusdoc.h
===================================================================
--- scribus/scribusdoc.h	(revision 27827)
+++ scribus/scribusdoc.h	(working copy)
@@ -1620,6 +1620,8 @@
 		void itemSelection_Rotate(double angle, Selection* customSelection = nullptr);
 		void itemSelection_DoHyphenate();
 		void itemSelection_DoDeHyphenate();
+		bool itemSelection_CanUnlinkTextFrames(Selection* customSelection = nullptr) const;
+		void itemSelection_UnlinkTextFrames(Selection* customSelection = nullptr);
 		void itemSelection_UnlinkTextFrameAndCutText(Selection* customSelection = nullptr);
 		void itemSelection_SendToLayer(int layerID);
 		void itemSelection_SetImageOffset(double x, double y, Selection* customSelection = nullptr);

Issue History

Date Modified Username Field Change
2019-06-26 13:49 ale New Issue
2019-06-26 13:49 ale Relationship added related to 0015721
2019-06-26 13:50 ale Relationship added related to 0015519
2019-11-16 09:30 ale Note Added: 0047088
2019-11-16 17:08 ale Relationship added parent of 0015954
2019-12-03 17:45 ale Note Added: 0047194
2025-03-26 19:38 pygmee Note Added: 0052342
2025-04-27 19:16 cbradney Category Story Editor / Text Frames => Text Frames / Story Editor
2026-08-26 01:55 buttar Note Added: 0054300
2026-08-26 01:55 buttar File Added: unlink-selected-text-frames-v1.02.patch
2026-08-26 01:56 buttar Tag Attached: #please_test
2026-08-26 01:56 buttar Tag Attached: #patchtobereviewed
2026-08-27 08:05 ale Tag Renamed #patchtobereviewed => #patch_to_be_reviewed
2026-09-09 14:49 buttar Note Added: 0054447
2026-09-09 14:49 buttar File Added: tcu1-selection-driven-unchain-v1.01.patch