Files
qelectrotech-source-mirror/sources/autoNum/ui/numparteditorw.cpp
T
ispyisail f7a79e75af Add alphabetical auto-numbering (a, b, ... z, aa, ab, ...) (#579)
Adds a real base-26 incrementing part type to the autonumbering engine,
alongside the 14 existing NumStrategy leaves. Unlike StringNum (a fixed,
non-incrementing text segment), AlphaNum::next()/previous() carry/borrow
entirely within the part's own value -- the composition loop in
NumerotationContextCommands doesn't need to change, since (unlike #578's
wrap-and-carry) nothing here needs to signal an adjacent part.

- incrementAlpha()/decrementAlpha() implement the spreadsheet-column-name
  algorithm: increment carries right-to-left on 'z'/'Z' overflow,
  prepending a new leading letter if the whole value overflows (z -> aa,
  az -> ba). decrement is the exact inverse, including the symmetric
  shrink case (aa -> z) once every position has borrowed. A single letter
  already at "a"/"A" has no representable predecessor and is clamped
  rather than turned into "z" -- caught via manual testing, since the
  initial implementation mutated the string in the borrow loop before
  checking whether to clamp, silently discarding the original value.
- Registered in NumerotationContext::validRegExpNum() but deliberately
  not in validRegExpNumber(), so addValue() doesn't force alphabetic
  values through int conversion.
- New "Cyclique"-adjacent "Alphabétique" entry in numparteditorw's type
  dropdown, with its own letters-only QRegularExpressionValidator; the
  increase spinbox is disabled since the step is always exactly one
  letter, not a configurable amount.

Also wires the new part type through to actual element/conductor labels,
which turned out to be required for the feature to do anything visible
beyond folio numbering (which applies a NumerotationContext's
represented string directly). Element and conductor numbering instead
go through a separate formula-substitution layer
(autonum::sequentialNumbers + %sequ_/%seqt_/%seqh_-style placeholders in
AssignVariables::assignSequence()) that numerotationContextToFormula()
auto-populates. Without a matching placeholder, an "alpha" part would
silently vanish from the generated formula and never reach the label,
even though the underlying counter was advancing correctly:
- sequentialNumbers gained an `alpha` QStringList member (copy ctor,
  operator=, operator==, toXml/fromXml, clear()).
- numerotationContextToFormula() emits a new %seqa_N placeholder for
  alpha parts, the same way %sequ_N is emitted for unit parts.
- setSequential()/setSequentialToList() populate seqStruct.alpha,
  passing the raw string through as-is rather than the .toInt()-based
  formatting used for the numeric part types.
- AssignVariables::assignSequence() substitutes %seqa_N from
  seqStruct.alpha, mirroring the existing %sequ_N/%seqt_N/%seqh_N
  substitutions.
No "alphafolio" variant was added, matching the discussion's scope (only
unit/ten/hundred have folio-anchored variants).

Verified against production code via the numbering config dialog's own
Suivant/Précédent buttons: from "a", 25 clicks reached "z"; one more
produced "aa"; 25 more reached "az"; one more produced "ba" (carry).
Reversed: "ba"->"az"->(25 clicks)->"aa"->"z" (shrink)->(25 clicks)->"a".
One more "previous" at "a" correctly stayed at "a" after the clamp fix.
Also confirmed the Formule field auto-updates to "%seqa_1" the instant
the type is switched to "Alphabétique", confirming the formula-generation
wiring works live in the UI, not just at the engine level.
2026-08-01 22:45:49 +12:00

462 lines
12 KiB
C++

/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "numparteditorw.h"
#include "ui_numparteditorw.h"
#include <QRegularExpressionValidator>
/**
@brief NumPartEditorW::NumPartEditorW
Constructor
@param type
@param parent
*/
NumPartEditorW::NumPartEditorW(int type, QWidget *parent) :
QWidget(parent),
ui(new Ui::NumPartEditorW),
intValidator (new QIntValidator(0,99999,this)),
alphaValidator (new QRegularExpressionValidator(QRegularExpression("[A-Za-z]+"), this)),
m_edited_type(type)
{
ui -> setupUi(this);
setVisibleItems();
setType(NumPartEditorW::unit, true);
}
/**
@brief NumPartEditorW::NumPartEditorW
Constructor
Build with value of context at position i
@param context
@param i
@param type
@param parent
*/
NumPartEditorW::NumPartEditorW (NumerotationContext &context,
int i,
int type,
QWidget *parent):
QWidget(parent),
ui(new Ui::NumPartEditorW),
intValidator (new QIntValidator(0,99999,this)),
alphaValidator (new QRegularExpressionValidator(QRegularExpression("[A-Za-z]+"), this)),
m_edited_type(type)
{
ui -> setupUi(this);
setVisibleItems();
if(context.size()==0) setType(NumPartEditorW::unit, true);
else {
QStringList strl = context.itemAt(i);
if (strl.at(0)=="unit")
setType(NumPartEditorW::unit, true);
else if (strl.at(0)=="unitfolio")
setType(NumPartEditorW::unitfolio, true);
else if (strl.at(0)=="ten")
setType(NumPartEditorW::ten, true);
else if (strl.at(0)=="tenfolio")
setType(NumPartEditorW::tenfolio, true);
else if (strl.at(0)=="hundred")
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)=="alpha")
setType(NumPartEditorW::alpha);
else if (strl.at(0)=="string")
setType(NumPartEditorW::string);
else if (strl.at(0)=="idfolio")
setType(NumPartEditorW::idfolio);
else if (strl.at(0)=="folio")
setType(NumPartEditorW::folio);
else if (strl.at(0)=="plant")
setType(NumPartEditorW::plant);
else if (strl.at(0)=="locmach")
setType(NumPartEditorW::locmach);
else if (strl.at(0)=="elementline")
setType(NumPartEditorW::elementline);
else if (strl.at(0)=="elementcolumn")
setType(NumPartEditorW::elementcolumn);
else if (strl.at(0)=="elementprefix")
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());
}
}
/**
Destructor
*/
NumPartEditorW::~NumPartEditorW()
{
delete intValidator;
delete alphaValidator;
delete ui;
}
void NumPartEditorW::setVisibleItems()
{
ui->type_cb->setInsertPolicy(QComboBox::InsertAtBottom);
QStringList items;
if (m_edited_type == 2)
{
items << tr("Chiffre 1")
<< tr("Chiffre 01")
<< tr("Chiffre 001")
<< tr("Cyclique (modulo)")
<< tr("Alphabétique")
<< tr("Texte");
}
else if (m_edited_type == 1)
{
items << tr("Chiffre 1")
<< tr("Chiffre 1 - Folio")
<< tr("Chiffre 01")
<< tr("Chiffre 01 - Folio")
<< tr("Chiffre 001")
<< tr("Chiffre 001 - Folio")
<< tr("Cyclique (modulo)")
<< tr("Alphabétique")
<< tr("Texte")
<< tr("N° folio")
<< tr("Folio")
<< tr("Installation")
<< tr("Locmach");
}
else
items << tr("Chiffre 1")
<< tr("Chiffre 1 - Folio")
<< tr("Chiffre 01")
<< tr("Chiffre 01 - Folio")
<< tr("Chiffre 001")
<< tr("Chiffre 001 - Folio")
<< tr("Cyclique (modulo)")
<< tr("Alphabétique")
<< tr("Texte")
<< tr("N° folio")
<< tr("Folio")
<< tr("Installation")
<< tr("Locmach")
<< tr("Element Line")
<< tr("Element Column")
<< tr("Element Prefix");
ui->type_cb->insertItems(0,items);
}
/**
@brief NumPartEditorW::toNumContext
@return the display to NumerotationContext
*/
NumerotationContext NumPartEditorW::toNumContext()
{
NumerotationContext nc;
QString type_str;
switch (type_) {
case unit:
type_str = "unit";
break;
case unitfolio:
type_str = "unitfolio";
break;
case ten:
type_str = "ten";
break;
case tenfolio:
type_str = "tenfolio";
break;
case hundred:
type_str = "hundred";
break;
case hundredfolio:
type_str = "hundredfolio";
break;
case string:
type_str = "string";
break;
case idfolio:
type_str = "idfolio";
break;
case folio:
type_str = "folio";
break;
case plant:
type_str = "plant";
break;
case locmach:
type_str = "locmach";
break;
case elementline:
type_str = "elementline";
break;
case elementcolumn:
type_str = "elementcolumn";
break;
case elementprefix:
type_str = "elementprefix";
break;
case wrap:
type_str = "wrap";
break;
case alpha:
type_str = "alpha";
break;
}
if (type_str == "unitfolio"
|| type_str == "tenfolio"
|| type_str == "hundredfolio")
nc.addValue(type_str,
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(),
ui -> increase_spinBox -> value());
return nc;
}
/**
@brief NumPartEditorW::isValid
@return true if value field isn't empty or if type is folio
*/
bool NumPartEditorW::isValid()
{
if (type_ == folio
|| type_ == idfolio
|| type_ == elementline
|| type_ == plant
|| type_ == locmach
|| type_ == elementcolumn
|| type_ == elementprefix) {return true;}
else if(ui -> value_field -> text().isEmpty()) {return false;}
else return true;
}
/**
@brief NumPartEditorW::on_type_cb_activated
Action when user change the type comboBox
*/
void NumPartEditorW::on_type_cb_activated(int) {
if (ui->type_cb->currentText() == tr("Chiffre 1"))
setType(unit);
else if (ui->type_cb->currentText() == tr("Chiffre 1 - Folio"))
setType(unitfolio);
else if (ui->type_cb->currentText() == tr("Chiffre 01"))
setType(ten);
else if (ui->type_cb->currentText() == tr("Chiffre 01 - Folio"))
setType(tenfolio);
else if (ui->type_cb->currentText() == tr("Chiffre 001"))
setType(hundred);
else if (ui->type_cb->currentText() == tr("Chiffre 001 - Folio"))
setType(hundredfolio);
else if (ui->type_cb->currentText() == tr("Texte"))
setType(string);
else if (ui->type_cb->currentText() == tr("N° folio"))
setType(idfolio);
else if (ui->type_cb->currentText() == tr("Folio"))
setType(folio);
else if (ui->type_cb->currentText() == tr("Installation"))
setType(plant);
else if (ui->type_cb->currentText() == tr("Locmach"))
setType(locmach);
else if (ui->type_cb->currentText() == tr("Element Line"))
setType(elementline);
else if (ui->type_cb->currentText() == tr("Element Column"))
setType(elementcolumn);
else if (ui->type_cb->currentText() == tr("Element Prefix"))
setType(elementprefix);
else if (ui->type_cb->currentText() == tr("Cyclique (modulo)"))
setType(wrap);
else if (ui->type_cb->currentText() == tr("Alphabétique"))
setType(alpha);
emit changed();
}
/**
@brief NumPartEditorW::on_value_field_textChanged
emit changed when value_field text changed
*/
void NumPartEditorW::on_value_field_textEdited()
{
emit changed();
}
/**
@brief NumPartEditorW::on_increase_spinBox_valueChanged
emit changed when increase_spinBox value changed
*/
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
@param t : type used
@param fnum : force the behavior of numeric type
*/
void NumPartEditorW::setType(NumPartEditorW::type t, bool fnum) {
setCurrentIndex(t);
//if @t is a numeric type and preview type @type_ isn't a numeric type
//or @fnum is true, we set numeric behavior
if (
(
(t==unit
|| t==unitfolio
|| t==ten
|| t==tenfolio
|| t==hundred
|| t==hundredfolio
|| t==wrap
)
&& (type_==string
|| type_==folio
|| type_==plant
|| type_==locmach
|| type_==idfolio
|| type_==elementcolumn
|| type_==elementline
|| type_==elementprefix)
)
|| fnum
)
{
ui -> value_field -> clear();
ui -> value_field -> setEnabled(true);
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
|| t == alpha
|| t == folio
|| t == idfolio
|| t == elementline
|| t == plant
|| t == locmach
|| t == elementcolumn
|| t == elementprefix) {
ui -> value_field -> clear();
ui -> increase_spinBox -> setDisabled(true);
if (t==string) {
ui -> value_field -> setValidator(nullptr);
ui -> value_field -> setEnabled(true);
}
else if (t==alpha) {
//Alphabetic step is always exactly one letter (a, b, ...);
//there is no numeric "increase" to configure, unlike the
//digit-based part types.
ui -> value_field -> setValidator(alphaValidator);
ui -> value_field -> setEnabled(true);
}
else if (t==folio) {
ui -> value_field -> setDisabled(true);
ui -> increase_spinBox -> setDisabled(true);
}
else if (t==plant) {
ui -> value_field -> setDisabled(true);
ui -> increase_spinBox -> setDisabled(true);
}
else if (t==locmach) {
ui -> value_field -> setDisabled(true);
ui -> increase_spinBox -> setDisabled(true);
}
else if (t==idfolio) {
ui -> value_field -> setDisabled(true);
ui -> increase_spinBox -> setDisabled(true);
}
else if (t==elementcolumn) {
ui -> value_field -> setDisabled(true);
ui -> increase_spinBox -> setDisabled(true);
}
else if (t==elementline) {
ui -> value_field -> setDisabled(true);
ui -> increase_spinBox -> setDisabled(true);
}
else if (t==elementprefix) {
ui -> value_field -> setDisabled(true);
ui -> increase_spinBox -> setDisabled(true);
}
}
ui -> modulus_spinBox -> setEnabled(t == wrap);
type_= t;
}
/**
@brief NumPartEditorW::setCurrentIndex
Set Current Index of type_cb
@param t : type used
*/
void NumPartEditorW::setCurrentIndex(NumPartEditorW::type t) {
int i=-1;
if (t == unit)
i = ui->type_cb->findText(tr("Chiffre 1"));
else if (t == unitfolio)
i = ui->type_cb->findText(tr("Chiffre 1 - Folio"));
else if (t == ten)
i = ui->type_cb->findText(tr("Chiffre 01"));
else if (t == tenfolio)
i = ui->type_cb->findText(tr("Chiffre 01 - Folio"));
else if (t == hundred)
i = ui->type_cb->findText(tr("Chiffre 001"));
else if (t == hundredfolio)
i = ui->type_cb->findText(tr("Chiffre 001 - Folio"));
else if (t == alpha)
i = ui->type_cb->findText(tr("Alphabétique"));
else if (t == string)
i = ui->type_cb->findText(tr("Texte"));
else if (t == idfolio)
i = ui->type_cb->findText(tr("N° folio"));
else if (t == folio)
i = ui->type_cb->findText(tr("Folio"));
else if (t == plant)
i = ui->type_cb->findText(tr("Installation"));
else if (t == locmach)
i = ui->type_cb->findText(tr("Locmach"));
else if (t == elementline)
i = ui->type_cb->findText(tr("Element Line"));
else if (t == elementcolumn)
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);
}