Merge pull request #593 from ispyisail/feature-wrap-and-carry-autonum

Add wrap-and-carry (cyclic/modulo) auto-numbering
This commit is contained in:
Laurent Trinques
2026-08-01 11:09:58 +02:00
committed by GitHub
7 changed files with 247 additions and 11 deletions
+18 -10
View File
@@ -51,12 +51,14 @@ void NumerotationContext::clear ()
@param value the value itself
@param increase the increase number of value
@param initialvalue
@param modulus wrap-and-carry modulus (0 means "not a wrapping part")
@return true if value is append
*/
bool NumerotationContext::addValue(const QString &type,
const QVariant &value,
const int increase,
const int initialvalue) {
const int initialvalue,
const int modulus) {
if (!keyIsAcceptable(type) && !value.canConvert<QString>())
return false;
if (keyIsNumber(type) && !value.canConvert<int>())
@@ -70,7 +72,9 @@ bool NumerotationContext::addValue(const QString &type,
+ "|"
+ QString::number(increase)
+ "|"
+ QString::number(initialvalue);
+ QString::number(initialvalue)
+ "|"
+ QString::number(modulus);
return true;
}
@@ -125,7 +129,7 @@ QStringList NumerotationContext::itemAt(const int i) const
*/
QString NumerotationContext::validRegExpNum () const
{
return ("unit|unitfolio|ten|tenfolio|hundred|hundredfolio|string|idfolio|folio|plant|locmach|elementline|elementcolumn|elementprefix");
return ("unit|unitfolio|ten|tenfolio|hundred|hundredfolio|wrap|string|idfolio|folio|plant|locmach|elementline|elementcolumn|elementprefix");
}
/**
@@ -134,7 +138,7 @@ QString NumerotationContext::validRegExpNum () const
*/
QString NumerotationContext::validRegExpNumber() const
{
return ("unit|unitfolio|ten|tenfolio|hundred|hundredfolio");
return ("unit|unitfolio|ten|tenfolio|hundred|hundredfolio|wrap");
}
/**
@@ -172,6 +176,9 @@ QDomElement NumerotationContext::toXml(QDomDocument &d, const QString& str) {
strl.at(0) == ("hundredfolio")) {
part.setAttribute("initialvalue", strl.at(3));
}
if (strl.at(0) == ("wrap") && strl.size() > 4) {
part.setAttribute("modulus", strl.at(4));
}
num_auto.appendChild(part);
}
return num_auto;
@@ -183,7 +190,7 @@ QDomElement NumerotationContext::toXml(QDomDocument &d, const QString& str) {
*/
void NumerotationContext::fromXml(QDomElement &e) {
clear();
foreach(QDomElement qde, QET::findInDomElement(e, "part")) addValue(qde.attribute("type"), qde.attribute("value"), qde.attribute("increase").toInt(), qde.attribute("initialvalue").toInt());
foreach(QDomElement qde, QET::findInDomElement(e, "part")) addValue(qde.attribute("type"), qde.attribute("value"), qde.attribute("increase").toInt(), qde.attribute("initialvalue").toInt(), qde.attribute("modulus").toInt());
}
/**
@@ -193,10 +200,11 @@ void NumerotationContext::fromXml(QDomElement &e) {
@param content to replace current value
*/
void NumerotationContext::replaceValue(int index, QString content) {
QString sep = "|";
QString type = content_[index].split("|").at(0);
QStringList strl = content_[index].split("|");
QString type = strl.at(0);
const QString& value = std::move(content);
QString increase = content_[index].split("|").at(2);
QString initvalue = content_[index].split("|").at(3);
content_[index].replace(content_[index], type + "|" + value + "|" + increase + "|" + initvalue);
QString increase = strl.at(2);
QString initvalue = strl.at(3);
QString modulus = strl.size() > 4 ? strl.at(4) : QStringLiteral("0");
content_[index] = type + "|" + value + "|" + increase + "|" + initvalue + "|" + modulus;
}
+1
View File
@@ -36,6 +36,7 @@ class NumerotationContext
bool addValue(const QString &,
const QVariant & = QVariant(1),
const int = 1,
const int = 0,
const int = 0);
QString operator[] (const int &) const;
void operator << (const NumerotationContext &);
@@ -48,6 +48,15 @@ NumerotationContext NumerotationContextCommands::next()
QStringList str = context_.itemAt(i);
setNumStrategy(str.at(0));
contextnum << strategy_ -> next(context_, i);
//Wrap-and-carry: str still holds the pre-increment value, so
//this checks the same condition WrapNum::next() used to decide
//whether to wrap its own value back to 0.
if (str.at(0) == "wrap" && str.size() > 4) {
int modulus = str.at(4).toInt();
if (modulus > 0 && (str.at(1).toInt() + str.at(2).toInt()) >= modulus)
carry(contextnum, i - 1);
}
}
return contextnum;
}
@@ -64,10 +73,80 @@ NumerotationContext NumerotationContextCommands::previous()
QStringList str = context_.itemAt(i);
setNumStrategy(str.at(0));
contextnum << strategy_ -> previous(context_, i);
if (str.at(0) == "wrap" && str.size() > 4) {
int modulus = str.at(4).toInt();
if (modulus > 0 && (str.at(1).toInt() - str.at(2).toInt()) < 0)
borrow(contextnum, i - 1);
}
}
return contextnum;
}
/**
@brief NumerotationContextCommands::carry
Add one unit to the nearest numeric part at or before from_index in
contextnum, skipping non-numeric parts (e.g. a "." string separator)
along the way. If that part is itself a wrap part and this pushes it
to (or past) its own modulus, it wraps back to 0 and the carry
cascades further back -- so wrap parts can be chained (e.g. seconds
wrapping into minutes wrapping into hours).
@param contextnum the context being built by next(); already contains
entries for every index <= from_index
@param from_index index to start looking from, going backwards
*/
void NumerotationContextCommands::carry(NumerotationContext &contextnum, int from_index)
{
for (int j = from_index; j >= 0; --j) {
QStringList strl = contextnum.itemAt(j);
if (!contextnum.keyIsNumber(strl.at(0)))
continue;
int value = strl.at(1).toInt() + 1;
if (strl.at(0) == "wrap" && strl.size() > 4) {
int modulus = strl.at(4).toInt();
if (modulus > 0 && value >= modulus) {
contextnum.replaceValue(j, QString::number(value - modulus));
carry(contextnum, j - 1);
return;
}
}
contextnum.replaceValue(j, QString::number(value));
return;
}
//No preceding numeric part: the carry has nowhere to go and is dropped,
//same as any other counter in this engine has no overflow tracking
//beyond the parts the user actually configured.
}
/**
@brief NumerotationContextCommands::borrow
Inverse of carry(): subtract one unit from the nearest numeric part at
or before from_index. If that part is itself a wrap part and this
takes it below 0, it wraps to (modulus - 1) and the borrow cascades
further back.
*/
void NumerotationContextCommands::borrow(NumerotationContext &contextnum, int from_index)
{
for (int j = from_index; j >= 0; --j) {
QStringList strl = contextnum.itemAt(j);
if (!contextnum.keyIsNumber(strl.at(0)))
continue;
int value = strl.at(1).toInt() - 1;
if (strl.at(0) == "wrap" && strl.size() > 4) {
int modulus = strl.at(4).toInt();
if (modulus > 0 && value < 0) {
contextnum.replaceValue(j, QString::number(value + modulus));
borrow(contextnum, j - 1);
return;
}
}
contextnum.replaceValue(j, QString::number(value));
return;
}
}
/**
@brief NumerotationContextCommands::toFinalString
@return the string represented by the numerotation context
@@ -117,6 +196,10 @@ void NumerotationContextCommands::setNumStrategy(const QString &str) {
strategy_ = new HundredFNum (diagram_);
return;
}
else if (str == "wrap") {
strategy_ = new WrapNum (diagram_);
return;
}
else if (str == "string") {
strategy_ = new StringNum (diagram_);
return;
@@ -431,6 +514,62 @@ NumerotationContext HundredFNum::previous(const NumerotationContext &nc, const i
return (previousNumber(nc, i));
}
/**
Constructor
*/
WrapNum::WrapNum (Diagram *d):
NumStrategy (d)
{}
/**
@brief WrapNum::toRepresentedString
@return the represented string of num
*/
QString WrapNum::toRepresentedString(const QString num) const
{
return (num);
}
/**
@brief WrapNum::next
Wraps this part's own value back to 0 every `modulus` values (carrying
into the adjacent part is handled by NumerotationContextCommands::next(),
which has visibility into the other parts).
@return the next NumerotationContext nc at position i
*/
NumerotationContext WrapNum::next (const NumerotationContext &nc, const int i) const
{
QStringList strl = nc.itemAt(i);
NumerotationContext newnc;
int increase = strl.at(2).toInt();
int modulus = strl.size() > 4 ? strl.at(4).toInt() : 0;
int new_value = strl.at(1).toInt() + increase;
if (modulus > 0)
new_value %= modulus;
newnc.addValue(strl.at(0), QString::number(new_value), increase, strl.at(3).toInt(), modulus);
return (newnc);
}
/**
@brief WrapNum::previous
@return the previous NumerotationContext nc at posiiton i
*/
NumerotationContext WrapNum::previous(const NumerotationContext &nc, const int i) const
{
QStringList strl = nc.itemAt(i);
NumerotationContext newnc;
int increase = strl.at(2).toInt();
int modulus = strl.size() > 4 ? strl.at(4).toInt() : 0;
int new_value = strl.at(1).toInt() - increase;
if (modulus > 0) {
new_value %= modulus;
if (new_value < 0)
new_value += modulus;
}
newnc.addValue(strl.at(0), QString::number(new_value), increase, strl.at(3).toInt(), modulus);
return (newnc);
}
/**
Constructor
*/
@@ -38,6 +38,8 @@ class NumerotationContextCommands
private:
void setNumStrategy (const QString &);
static void carry(NumerotationContext &contextnum, int from_index);
static void borrow(NumerotationContext &contextnum, int from_index);
Diagram *diagram_;
NumerotationContext context_;
@@ -115,6 +117,24 @@ class HundredFNum: public NumStrategy
NumerotationContext previous (const NumerotationContext &, const int) const override;
};
/**
@brief The WrapNum class
A counter that wraps back to 0 (borrowing from initialvalue on the
way down) every `modulus` values, instead of counting up forever like
UnitNum/TenNum/HundredNum. Its own next()/previous() only computes its
own wrapped value; carrying into (or borrowing from) the preceding
numeric part is handled by NumerotationContextCommands::next()/
previous(), since only the composition loop can see adjacent parts.
*/
class WrapNum: public NumStrategy
{
public:
WrapNum (Diagram *);
QString toRepresentedString(const QString) const override;
NumerotationContext next (const NumerotationContext &, const int) const override;
NumerotationContext previous (const NumerotationContext &, const int) const override;
};
class StringNum: public NumStrategy
{
public:
+32
View File
@@ -71,6 +71,8 @@ NumPartEditorW::NumPartEditorW (NumerotationContext &context,
setType(NumPartEditorW::hundred, true);
else if (strl.at(0)=="hundredfolio")
setType(NumPartEditorW::hundredfolio, true);
else if (strl.at(0)=="wrap")
setType(NumPartEditorW::wrap, true);
else if (strl.at(0)=="string")
setType(NumPartEditorW::string);
else if (strl.at(0)=="idfolio")
@@ -89,6 +91,8 @@ NumPartEditorW::NumPartEditorW (NumerotationContext &context,
setType(NumPartEditorW::elementprefix);
ui -> value_field -> setText(strl.at(1));
ui -> increase_spinBox -> setValue(strl.at(2).toInt());
if (strl.at(0)=="wrap" && strl.size() > 4)
ui -> modulus_spinBox -> setValue(strl.at(4).toInt());
}
}
@@ -110,6 +114,7 @@ void NumPartEditorW::setVisibleItems()
items << tr("Chiffre 1")
<< tr("Chiffre 01")
<< tr("Chiffre 001")
<< tr("Cyclique (modulo)")
<< tr("Texte");
}
else if (m_edited_type == 1)
@@ -120,6 +125,7 @@ void NumPartEditorW::setVisibleItems()
<< tr("Chiffre 01 - Folio")
<< tr("Chiffre 001")
<< tr("Chiffre 001 - Folio")
<< tr("Cyclique (modulo)")
<< tr("Texte")
<< tr("N° folio")
<< tr("Folio")
@@ -133,6 +139,7 @@ void NumPartEditorW::setVisibleItems()
<< tr("Chiffre 01 - Folio")
<< tr("Chiffre 001")
<< tr("Chiffre 001 - Folio")
<< tr("Cyclique (modulo)")
<< tr("Texte")
<< tr("N° folio")
<< tr("Folio")
@@ -195,6 +202,9 @@ NumerotationContext NumPartEditorW::toNumContext()
case elementprefix:
type_str = "elementprefix";
break;
case wrap:
type_str = "wrap";
break;
}
if (type_str == "unitfolio"
|| type_str == "tenfolio"
@@ -203,6 +213,12 @@ NumerotationContext NumPartEditorW::toNumContext()
ui -> value_field -> displayText(),
ui -> increase_spinBox -> value(),
ui->value_field->displayText().toInt());
else if (type_str == "wrap")
nc.addValue(type_str,
ui -> value_field -> displayText(),
ui -> increase_spinBox -> value(),
0,
ui -> modulus_spinBox -> value());
else
nc.addValue(type_str,
ui -> value_field -> displayText(),
@@ -260,6 +276,8 @@ void NumPartEditorW::on_type_cb_activated(int) {
setType(elementcolumn);
else if (ui->type_cb->currentText() == tr("Element Prefix"))
setType(elementprefix);
else if (ui->type_cb->currentText() == tr("Cyclique (modulo)"))
setType(wrap);
emit changed();
}
@@ -280,6 +298,14 @@ void NumPartEditorW::on_increase_spinBox_valueChanged(int) {
if (!ui -> value_field -> text().isEmpty()) emit changed();
}
/**
@brief NumPartEditorW::on_modulus_spinBox_valueChanged
emit changed when modulus_spinBox value changed
*/
void NumPartEditorW::on_modulus_spinBox_valueChanged(int) {
if (!ui -> value_field -> text().isEmpty()) emit changed();
}
/**
@brief NumPartEditorW::setType
Set good behavior by type t
@@ -299,6 +325,7 @@ void NumPartEditorW::setType(NumPartEditorW::type t, bool fnum) {
|| t==tenfolio
|| t==hundred
|| t==hundredfolio
|| t==wrap
)
&& (type_==string
|| type_==folio
@@ -317,6 +344,8 @@ void NumPartEditorW::setType(NumPartEditorW::type t, bool fnum) {
ui -> value_field -> setValidator(intValidator);
ui -> increase_spinBox -> setEnabled(true);
ui -> increase_spinBox -> setValue(1);
if (t == wrap)
ui -> modulus_spinBox -> setValue(8);
}
//@t isn't a numeric type
else if (t == string
@@ -362,6 +391,7 @@ void NumPartEditorW::setType(NumPartEditorW::type t, bool fnum) {
ui -> increase_spinBox -> setDisabled(true);
}
}
ui -> modulus_spinBox -> setEnabled(t == wrap);
type_= t;
}
@@ -400,5 +430,7 @@ void NumPartEditorW::setCurrentIndex(NumPartEditorW::type t) {
i = ui->type_cb->findText(tr("Element Column"));
else if (t == elementprefix)
i = ui->type_cb->findText(tr("Element Prefix"));
else if (t == wrap)
i = ui->type_cb->findText(tr("Cyclique (modulo)"));
ui->type_cb->setCurrentIndex(i);
}
+2
View File
@@ -49,6 +49,7 @@ class NumPartEditorW : public QWidget
enum type {unit,unitfolio,ten,tenfolio, hundred, hundredfolio,
string,idfolio,folio,plant,locmach,
elementline,elementcolumn,elementprefix,
wrap,
};
NumerotationContext toNumContext();
bool isValid ();
@@ -63,6 +64,7 @@ class NumPartEditorW : public QWidget
void on_type_cb_activated(int);
void on_value_field_textEdited();
void on_increase_spinBox_valueChanged(int);
void on_modulus_spinBox_valueChanged(int);
void setType (NumPartEditorW::type t, bool=false);
signals:
+35 -1
View File
@@ -75,6 +75,9 @@
<property name="wrapping">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Mettre à 0 pour un chiffre qui n'avance que par le report d'un chiffre cyclique suivant (ex: le "0" de "0.7")</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
@@ -88,7 +91,38 @@
<string/>
</property>
<property name="minimum">
<number>1</number>
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="modulus_spinBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Valeur à laquelle ce chiffre revient à 0 en incrémentant le chiffre précédent (0 = pas de cycle)</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="accelerated">
<bool>true</bool>
</property>
<property name="prefix">
<string>mod. </string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>99999</number>
</property>
</widget>
</item>