View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0014648 | Scribus | User Interface | public | 2017-02-27 10:57 | 2026-09-12 17:31 |
| Reporter | JLuc | Assigned To | |||
| Priority | normal | Severity | feature | Reproducibility | N/A |
| Status | new | Resolution | open | ||
| Platform | Linux | OS | Ubuntu | OS Version | 14.04 |
| Summary | 0014648: Allow to delete multiple points in curve | ||||
| Description | When in shape edit mode, its possible to select many points at once (using lasso for example) and it's possible to move them all at once (using the mouse or the keyboard arrow keys) BUT it's not possible to delete them all at once. It's not an issue on rectangular shapes where there is only few points but it becomes a pain on curve shapes, which can sometime contain hundreds of points depending on their source. This request is to allow the use of DEL key so as to delete ALL selected points. | ||||
| Tags | #please_test | ||||
| Patch | No | ||||
|
|
Here is the patch that fixes this a more closely related fixes. * Deletes all selected anchor nodes in one operation. * Excludes Bézier control handles from node Nexus deletion. * Handles duplicated anchors and compound subpaths safely. * Rejects deletions that would leave an invalid open or closed path. * Supports both shape and contour editing. * Records the complete deletion as one Undo action. * Immediately refreshes contour-node rendering. * Keeps modified Delete shortcuts, such as Clear Contents, separate from node deletion. nodeedit-multiple-node-deletion-v1.01.patch (8,470 bytes)
Index: scribus/canvasmode_nodeedit.cpp
===================================================================
--- scribus/canvasmode_nodeedit.cpp (revision 27829)
+++ scribus/canvasmode_nodeedit.cpp (working copy)
@@ -14,9 +14,13 @@
***************************************************************************/
+#include <algorithm>
+#include <functional>
+
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
+#include <QSet>
#include "canvas.h"
#include "canvasgesture_pan.h"
@@ -581,19 +585,21 @@
void CanvasMode_NodeEdit::keyPressEvent(QKeyEvent *e)
{
- commonkeyPressEvent_NormalNodeEdit(e);
-
int kk = e->key();
PageItem* currItem = m_doc->m_Selection->itemAt(0);
- if (kk == Qt::Key_Delete || kk == Qt::Key_Backspace)
+ const auto commandModifiers = e->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier);
+ if ((kk == Qt::Key_Delete || kk == Qt::Key_Backspace) && commandModifiers == Qt::NoModifier)
{
if (currItem && m_doc->nodeEdit.hasNodeSelected())
{
- // delete Nodes
+ e->accept();
refreshPath(deleteNodes());
+ return;
}
}
+
+ commonkeyPressEvent_NormalNodeEdit(e);
}
void CanvasMode_NodeEdit::keyReleaseEvent(QKeyEvent *e)
@@ -1349,96 +1355,154 @@
bool CanvasMode_NodeEdit::deleteNodes()
{
PageItem* currItem = m_doc->m_Selection->itemAt(0);
- FPointArray Clip = m_doc->nodeEdit.beginTransaction(currItem);
+ if (!currItem)
+ return false;
- if (!currItem || Clip.size() == 0)
+ FPointArray clip = m_doc->nodeEdit.clipFromItem(currItem);
+ if (clip.empty())
return false;
- FPointArray cli;
- int EndInd = Clip.size();
- int StartInd = 0;
- if (m_doc->nodeEdit.clre() > 0)
+ struct Subpath
{
- for (int n = m_doc->nodeEdit.clre(); n < Clip.size(); ++n)
- {
- if (Clip.isMarker(n))
- {
- EndInd = n;
- break;
- }
+ int start { 0 };
+ int end { 0 };
+ bool closed { false };
+ QSet<int> anchors;
+ };
+
+ QList<Subpath> subpaths;
+ const bool pathsAreClosed = m_doc->nodeEdit.isContourLine()
+ || (!currItem->isLine() && !currItem->isPathText() && !currItem->isPolyLine());
+ for (int start = 0; start < clip.size();)
+ {
+ while (start < clip.size() && clip.isMarker(start))
+ ++start;
+ if (start >= clip.size())
+ break;
+
+ int end = start;
+ while (end < clip.size() && !clip.isMarker(end))
+ ++end;
+ if (end > start)
+ {
+ Subpath subpath;
+ subpath.start = start;
+ subpath.end = end;
+ subpath.closed = pathsAreClosed;
+ subpaths.append(subpath);
}
- for (int n2 = m_doc->nodeEdit.clre(); n2 > 0; n2--)
+ start = end;
+ }
+
+ // Map both internal representations of a shared anchor to one index.
+ for (int selected : m_doc->nodeEdit.selNode())
+ {
+ if (!NodeEditContext::isNode(selected) || selected < 0 || selected >= clip.size() || clip.isMarker(selected))
+ continue;
+
+ for (Subpath& subpath : subpaths)
{
- if (Clip.isMarker(n2))
- {
- StartInd = n2 + 1;
- break;
- }
+ if (selected < subpath.start || selected >= subpath.end)
+ continue;
+
+ int anchor = selected;
+ if (subpath.closed && selected == subpath.end - 2)
+ anchor = subpath.start;
+ else if (selected > subpath.start && ((selected - subpath.start) % 4) == 0)
+ anchor = selected - 2;
+
+ if (anchor == subpath.start || ((anchor - subpath.start - 2) % 4) == 0)
+ subpath.anchors.insert(anchor);
+ break;
}
}
- if (m_doc->nodeEdit.hasNodeSelected())
+ int affectedSubpaths = 0;
+ int removedSubpaths = 0;
+ for (const Subpath& subpath : subpaths)
{
- int minClipSize = 4;
- if (!currItem->isLine() && !currItem->isPathText() && !currItem->isPolyLine())
- minClipSize = 12;
+ if (subpath.anchors.isEmpty())
+ continue;
+ ++affectedSubpaths;
- if (Clip.size() <= minClipSize)
+ const int segments = (subpath.end - subpath.start) / 4;
+ const int anchorCount = segments + (subpath.closed ? 0 : 1);
+ const int remaining = anchorCount - subpath.anchors.size();
+ if (remaining == 0)
{
- m_doc->nodeEdit.deselect();
- return false;
+ ++removedSubpaths;
+ continue;
}
- int numPoints = (StartInd != 0) ? (StartInd-4) : 0;
- numPoints += (Clip.size() - EndInd);
- if ((currItem->Segments.count() > 0) && ((EndInd - StartInd) <= minClipSize) && (numPoints > 0))
- {
- if (StartInd != 0)
- cli.putPoints(0, StartInd - 4, Clip);
- cli.putPoints(cli.size(), Clip.size() - EndInd, Clip, EndInd);
- }
- else if (m_doc->nodeEdit.clre() == StartInd)
- {
- if (!currItem->isLine() && !currItem->isPathText() && !currItem->isPolyLine())
- {
- const FPoint& kp(Clip.point(EndInd - 3));
- cli.putPoints(0, StartInd, Clip);
- cli.putPoints(cli.size(), EndInd - StartInd - 4, Clip, StartInd);
- cli.setPoint(StartInd, cli.point(cli.size() - 2));
- cli.setPoint(StartInd + 1, kp);
- cli.putPoints(cli.size(), Clip.size() - EndInd, Clip, EndInd);
- }
- else
+ const int minimum = subpath.closed ? 3 : 2;
+ if (remaining < minimum)
+ return false;
+ }
+
+ if (affectedSubpaths == 0 || removedSubpaths == subpaths.size())
+ return false;
+
+ // Work on a copy and commit only after every selected deletion is valid.
+ FPointArray result = clip.copy();
+ for (int subpathIndex = subpaths.size() - 1; subpathIndex >= 0; --subpathIndex)
+ {
+ const Subpath& subpath = subpaths.at(subpathIndex);
+ if (subpath.anchors.isEmpty())
+ continue;
+
+ const int segments = (subpath.end - subpath.start) / 4;
+ const int anchorCount = segments + (subpath.closed ? 0 : 1);
+ if (subpath.anchors.size() == anchorCount)
+ {
+ int removeStart = subpath.start;
+ int removeCount = subpath.end - subpath.start;
+ if (subpath.end < result.size() && result.isMarker(subpath.end))
+ removeCount += 4;
+ else if (removeStart >= 4 && result.isMarker(removeStart - 1))
{
- cli.putPoints(0, StartInd, Clip);
- cli.putPoints(cli.size(), EndInd - StartInd - 4, Clip, StartInd+4);
- cli.putPoints(cli.size(), Clip.size() - EndInd, Clip, EndInd);
+ removeStart -= 4;
+ removeCount += 4;
}
+ result.remove(removeStart, removeCount);
+ continue;
}
- else if (m_doc->nodeEdit.clre() == (EndInd - 2))
- {
- Clip.remove(m_doc->nodeEdit.clre() - 2, 4);
- cli = Clip.copy();
- }
- else
+
+ QList<int> anchors = subpath.anchors.values();
+ std::sort(anchors.begin(), anchors.end(), std::greater<int>());
+ for (int anchor : anchors)
{
- if (m_doc->nodeEdit.clre() != 0)
- cli.putPoints(0, m_doc->nodeEdit.clre(), Clip);
- cli.putPoints(cli.size(), Clip.size() - (m_doc->nodeEdit.clre() + 4), Clip, m_doc->nodeEdit.clre() + 4);
+ int end = subpath.end;
+ // Higher-index removals in this subpath have already shortened it.
+ for (int higher : anchors)
+ {
+ if (higher > anchor)
+ end -= 4;
+ }
+
+ if (anchor == subpath.start && subpath.closed)
+ {
+ const FPoint incomingControl = result.point(end - 3);
+ result.remove(end - 4, 4);
+ result.setPoint(subpath.start, result.point(end - 6));
+ result.setPoint(subpath.start + 1, incomingControl);
+ }
+ else if (anchor == subpath.start)
+ result.remove(subpath.start, 4);
+ else if (anchor == end - 2)
+ result.remove(anchor - 2, 4);
+ else
+ result.remove(anchor, 4);
}
- while (cli.isMarker(0))
- cli.remove(0, 4);
- if (m_doc->nodeEdit.isContourLine())
- currItem->ContourLine = cli.copy();
- else
- currItem->PoLine = cli.copy();
- m_doc->nodeEdit.deselect();
- currItem->ClipEdited = true;
- return true;
}
-
- return false;
+ m_doc->nodeEdit.beginTransaction(currItem);
+ if (m_doc->nodeEdit.isContourLine())
+ currItem->ContourLine = result.copy();
+ else
+ currItem->PoLine = result.copy();
+ m_doc->nodeEdit.deselect();
+ currItem->ClipEdited = true;
+ return true;
}
void CanvasMode_NodeEdit::refreshPath(bool edited)
@@ -1451,6 +1515,14 @@
if (edited)
{
currItem->FrameType = 3;
+ if (m_doc->nodeEdit.isContourLine())
+ {
+ currItem->update();
+ m_view->DrawNew();
+ emit m_view->PolyStatus(currItem->itemType(), currItem->ContourLine.size());
+ return;
+ }
+
double xp = currItem->xPos();
double yp = currItem->yPos();
double w = currItem->width();
|