Sort terminal strips into the plan's own order

The strip sorted by label: X1:1, X1:2 ... with X1:N and X1:PE at the
end whatever the plan looks like, so a strip drawn across pages came
out in an order no cable follows. Sort it the way the plan is drawn
instead -- folio by folio, then left to right -- in both places that
order a strip: the auto import's own sort of the terminals it groups,
and SortTerminalStripCommand, which the import pushes for a strip it
just creates and which the editor's sort button and
qet.sortTerminalStrip() push as well. The two share one comparator
(SortTerminalStripCommand::planOrderLessThan), so they cannot fight
each other. A spare stands on no folio, has no place in the plan order
and goes last, by label; the label breaks the remaining ties, so two
terminals at the very same spot keep a stable order.
This commit is contained in:
Kellermorph
2026-10-19 20:48:06 +02:00
parent a12fb43bcb
commit 123c56bb13
6 changed files with 381 additions and 14 deletions
@@ -19,6 +19,9 @@
#include "../terminalstrip.h"
#include "../physicalterminal.h"
#include "../realterminal.h"
#include "../../diagram.h"
#include "../../positionorder.h"
#include "../../qetgraphicsitem/element.h"
#include "../../utils/qetutils.h"
SortTerminalStripCommand::SortTerminalStripCommand(TerminalStrip *strip, QUndoCommand *parent) :
@@ -47,16 +50,73 @@ void SortTerminalStripCommand::redo()
void SortTerminalStripCommand::sort()
{
auto label_of = [](const QSharedPointer<PhysicalTerminal> &t) -> QString
//The plan position of a physical terminal is the position of the
//level that sits on a folio; a terminal whose levels are all
//spares has no position and is ordered by label only.
auto position_of = [](const QSharedPointer<PhysicalTerminal> &t_) -> QSharedPointer<RealTerminal>
{
return t->realTerminalCount() ? t->realTerminals().constLast()->label()
: QString();
if (!t_) {
return {};
}
for (const auto &real_ : t_->realTerminals()) {
if (real_ && real_->element()) {
return real_;
}
}
return t_->realTerminalCount() ? t_->realTerminals().constFirst()
: QSharedPointer<RealTerminal>();
};
std::stable_sort(m_new_order.begin(), m_new_order.end(),
[&label_of](const QSharedPointer<PhysicalTerminal> &arg1,
[&position_of](const QSharedPointer<PhysicalTerminal> &arg1,
const QSharedPointer<PhysicalTerminal> &arg2)
{
return QETUtils::naturalLessThan(label_of(arg1), label_of(arg2));
return planOrderLessThan(position_of(arg1), position_of(arg2));
});
}
/**
@brief SortTerminalStripCommand::planOrderLessThan
Order two real terminals the way the plan is drawn : folio by folio,
then left to right. Terminals with no folio position (spares) come
last, and the label only breaks ties, so the result stays stable and
predictable whatever the labels look like ("X1:N" and "X1:PE" stand
where they are drawn, not at the end).
Both the auto import and sort() use this, so the two orders of the
same strip can never fight each other.
@a a_ terminal to compare
@a b_ other terminal to compare
@return true if @a a_ comes before @a b_
*/
bool SortTerminalStripCommand::planOrderLessThan(
const QSharedPointer<RealTerminal> &a_,
const QSharedPointer<RealTerminal> &b_)
{
if (!a_ || !b_) {
return a_ && !b_; //a missing terminal goes last
}
const Element *ea = a_->element();
const Element *eb = b_->element();
auto folio_of = [](const Element *e_) -> int
{
return (e_ && e_->diagram()) ? e_->diagram()->folioIndex() : -1;
};
const int fa = folio_of(ea);
const int fb = folio_of(eb);
if (fa != fb) {
//A terminal with no folio has no place in the plan order
if (fa < 0) {
return false;
}
if (fb < 0) {
return true;
}
return fa < fb;
}
if (fa >= 0 && ea->pos() != eb->pos()) {
return PositionOrder::roundedXThenY(ea->pos(), eb->pos());
}
return QETUtils::naturalLessThan(a_->label(), b_->label());
}
@@ -20,10 +20,12 @@
#include <QUndoCommand>
#include <QPointer>
#include <QSharedPointer>
#include <QVector>
class TerminalStrip;
class PhysicalTerminal;
class RealTerminal;
/**
* @brief The SortTerminalStripCommand class
@@ -38,6 +40,25 @@ class SortTerminalStripCommand : public QUndoCommand
void undo() override;
void redo() override;
/**
* @brief planOrderLessThan
* Order two real terminals the way the plan is drawn : folio by
* folio, then left to right (top to bottom only breaks ties
* within a column). A spare has no folio and comes last, by
* label, and the label breaks every remaining tie (two
* terminals at the very same spot), which keeps the result
* stable.
*
* The auto import sorts with it too, so the order it works out
* cannot be rewritten by the SortTerminalStripCommand it then
* pushes for the strip it just created.
* @param a_ : first terminal
* @param b_ : second terminal
* @return true if @a a_ comes before @a b_
*/
static bool planOrderLessThan(const QSharedPointer<RealTerminal> &a_,
const QSharedPointer<RealTerminal> &b_);
private:
void sort();
@@ -128,7 +128,9 @@ namespace
* whose label prefix or plant and location no longer match its strip.
*
* In both cases the order the user gave to a strip is never changed,
* only a strip created by the run itself is sorted.
* only a strip created by the run itself is sorted -- the way the plan
* is drawn : folio by folio, then left to right (see
* SortTerminalStripCommand::planOrderLessThan).
*/
TerminalStripAutoImport::ImportResult TerminalStripAutoImport::importFreeTerminals(
QETProject *project, bool move_placed)
@@ -187,16 +189,21 @@ TerminalStripAutoImport::ImportResult TerminalStripAutoImport::importFreeTermina
for (auto it_ = groups_.begin(); it_ != groups_.end(); ++it_)
{
auto by_label_ = [](const QSharedPointer<RealTerminal> &a_,
const QSharedPointer<RealTerminal> &b_) {
return QETUtils::naturalLessThan(a_->label(), b_->label());
//The plan order : folio by folio, then left to right, so the
//strip comes out the way the plan is drawn instead of label
//by label ("N" and "PE" stay where they are on the sheet).
//SortTerminalStripCommand, pushed below for a strip this run
//creates, sorts with the same rule and cannot undo it.
auto by_plan_ = [](const QSharedPointer<RealTerminal> &a_,
const QSharedPointer<RealTerminal> &b_) {
return SortTerminalStripCommand::planOrderLessThan(a_, b_);
};
std::sort(it_.value().free_terminals.begin(),
it_.value().free_terminals.end(), by_label_);
it_.value().free_terminals.end(), by_plan_);
std::sort(it_.value().placed_terminals.begin(),
it_.value().placed_terminals.end(),
[&by_label_](const auto &a_, const auto &b_) {
return by_label_(a_.first, b_.first);
[&by_plan_](const auto &a_, const auto &b_) {
return by_plan_(a_.first, b_.first);
});
}
+3 -2
View File
@@ -3314,8 +3314,9 @@ bool QetScriptApi::bridgeTerminals(int stripIndex, const QVariantList &realTermi
/**
@brief QetScriptApi::sortTerminalStrip
Reorder a strip's physical positions into the canonical order the
editor's own "sort" button computes, through SortTerminalStripCommand.
Reorder a strip's physical positions into the order the plan is
drawn in -- folio by folio, then left to right -- the same
SortTerminalStripCommand the editor's own "sort" button pushes.
*/
bool QetScriptApi::sortTerminalStrip(int stripIndex)
{