Merge remote-tracking branch 'origin/master' into feature/center-rotation-option-for-textfields

# Conflicts:
#	lang/qet_de.ts
#	lang/qet_en.ts
This commit is contained in:
Levi Jetzer
2026-08-09 15:31:52 +02:00
74 changed files with 16542 additions and 6574 deletions
+45
View File
@@ -220,6 +220,24 @@ void NumerotationContext::replaceValue(int index, QString content) {
content_[index] = type + "|" + value + "|" + increase + "|" + initvalue + "|" + modulus + "|" + format;
}
/**
@brief NumerotationContext::replaceIncrease
Change how much this part advances per step, leaving its current value,
initial value, modulus and format untouched. Sibling to replaceValue(),
which deliberately never touches this field.
@param index of NC item
@param increase new increase for that item
*/
void NumerotationContext::replaceIncrease(int index, int increase) {
QStringList strl = content_[index].split("|");
QString type = strl.at(0);
QString value = strl.at(1);
QString initvalue = strl.at(3);
QString modulus = strl.size() > 4 ? strl.at(4) : QStringLiteral("0");
QString format = strl.size() > 5 ? strl.at(5) : QString();
content_[index] = type + "|" + value + "|" + QString::number(increase) + "|" + initvalue + "|" + modulus + "|" + format;
}
/**
@brief NumerotationContext::formatOf
@param item : a context item as returned by itemAt()
@@ -230,3 +248,30 @@ QString NumerotationContext::formatOf(const QStringList &item)
{
return item.size() > 5 ? item.at(5) : QString();
}
/**
@brief NumerotationContext::formatValue
@param item : a context item as returned by itemAt()
@return the part's value, zero-padded exactly as
autonum::setSequentialToList() pads it when composing a real label: an
explicit format mask wins, then "ten"/"hundred" parts get their
implicit 2/3-digit width, "alpha" is used as-is, everything else is a
plain number. Kept in step with that function by hand since the two
cannot share code without exposing an assignvariables.cpp-local helper.
*/
QString NumerotationContext::formatValue(const QStringList &item)
{
const QString &type = item.at(0);
const QString &value = item.at(1);
if (type == QLatin1String("alpha"))
return value;
const QString mask = formatOf(item);
if (!mask.isEmpty())
return QString("%1").arg(value.toInt(), mask.length(), 10, QChar('0'));
if (type == QLatin1String("ten") || type == QLatin1String("tenfolio"))
return QString("%1").arg(value.toInt(), 2, 10, QChar('0'));
if (type == QLatin1String("hundred") || type == QLatin1String("hundredfolio"))
return QString("%1").arg(value.toInt(), 3, 10, QChar('0'));
return QString::number(value.toInt());
}
+5
View File
@@ -54,6 +54,11 @@ class NumerotationContext
QDomElement toXml(QDomDocument &, const QString&);
void fromXml(QDomElement &);
void replaceValue(int, QString);
void replaceIncrease(int, int);
/// Zero-pad a part's value the same way the real numbering engine
/// does (autonum::setSequentialToList in assignvariables.cpp), so a
/// UI preview of a part's value matches what actually gets rendered.
static QString formatValue(const QStringList &item);
private:
QStringList content_;
+178 -26
View File
@@ -24,10 +24,14 @@
#include "../../titleblockproperties.h"
#include "../../ui/projectpropertiesdialog.h"
#include "../numerotationcontext.h"
#include "../numerotationcontextcommands.h"
#include "ui_autonumberingdockwidget.h"
#include "../../undocommand/changetitleblockcommand.h"
#include <QComboBox>
#include <QLineEdit>
#include <QSignalBlocker>
#include <QSpinBox>
/**
@brief AutoNumberingDockWidget::AutoNumberingDockWidget
@@ -64,6 +68,29 @@ void AutoNumberingDockWidget::clear()
ui->m_conductor_value_le->clear();
ui->m_element_value_le->clear();
ui->m_folio_value_le->clear();
ui->m_conductor_next_le->clear();
ui->m_element_next_le->clear();
ui->m_folio_next_le->clear();
}
/**
@brief AutoNumberingDockWidget::rowFor
@return the combo/value/increase/next widgets that make up category's row.
*/
AutoNumberingDockWidget::Row AutoNumberingDockWidget::rowFor(AutoNumCategory category) const
{
switch (category) {
case AutoNumCategory::Conductor:
return {ui->m_conductor_cb, ui->m_conductor_value_le,
ui->m_conductor_increase_sb, ui->m_conductor_next_le};
case AutoNumCategory::Element:
return {ui->m_element_cb, ui->m_element_value_le,
ui->m_element_increase_sb, ui->m_element_next_le};
case AutoNumCategory::Folio:
return {ui->m_folio_cb, ui->m_folio_value_le,
ui->m_folio_increase_sb, ui->m_folio_next_le};
}
return {nullptr, nullptr, nullptr, nullptr};
}
void AutoNumberingDockWidget::projectClosed()
@@ -201,9 +228,9 @@ void AutoNumberingDockWidget::setContext()
//The combo boxes have just been repopulated, so the value fields next
//to them are showing whatever the previous project left there.
refreshValueField(ui->m_conductor_cb, ui->m_conductor_value_le, AutoNumCategory::Conductor);
refreshValueField(ui->m_element_cb, ui->m_element_value_le, AutoNumCategory::Element);
refreshValueField(ui->m_folio_cb, ui->m_folio_value_le, AutoNumCategory::Folio);
refreshRow(AutoNumCategory::Conductor);
refreshRow(AutoNumCategory::Element);
refreshRow(AutoNumCategory::Folio);
this->setActive();
}
@@ -279,7 +306,7 @@ void AutoNumberingDockWidget::on_m_conductor_cb_activated(int)
m_project->setCurrentConductorAutoNum(current_autonum);
m_project_view->currentDiagram()->diagram()->setConductorsAutonumName(current_autonum);
m_project_view->currentDiagram()->diagram()->loadCndFolioSeq();
refreshValueField(ui->m_conductor_cb, ui->m_conductor_value_le, AutoNumCategory::Conductor);
refreshRow(AutoNumCategory::Conductor);
}
/**
@@ -308,7 +335,7 @@ void AutoNumberingDockWidget::on_m_element_cb_activated(int)
{
m_project->setCurrrentElementAutonum(ui->m_element_cb->currentText());
m_project_view->currentDiagram()->diagram()->loadElmtFolioSeq();
refreshValueField(ui->m_element_cb, ui->m_element_value_le, AutoNumCategory::Element);
refreshRow(AutoNumCategory::Element);
}
/**
@@ -345,8 +372,18 @@ void AutoNumberingDockWidget::on_m_folio_cb_activated(int) {
ip.folio = "%id/%total";
m_project->setDefaultTitleBlockProperties(ip);
}
emit(folioAutoNumChanged(current_autonum));
refreshValueField(ui->m_folio_cb, ui->m_folio_value_le, AutoNumCategory::Folio);
if (m_project_view && m_project_view->currentDiagram()) {
Diagram *diagram = m_project_view->currentDiagram()->diagram();
TitleBlockProperties old_properties = diagram->border_and_titleblock.exportTitleBlock();
TitleBlockProperties new_properties = old_properties;
new_properties.auto_page_num = ip.auto_page_num;
new_properties.folio = ip.folio;
if (new_properties != old_properties)
diagram->undoStack().push(new ChangeTitleBlockCommand(diagram, old_properties, new_properties));
}
emit(folioAutoNumChanged(current_autonum));
refreshRow(AutoNumCategory::Folio);
}
void AutoNumberingDockWidget::on_m_configure_pb_clicked()
@@ -389,6 +426,21 @@ void AutoNumberingDockWidget::on_m_folio_value_le_editingFinished()
applyValueField(ui->m_folio_cb, ui->m_folio_value_le, AutoNumCategory::Folio);
}
void AutoNumberingDockWidget::on_m_conductor_increase_sb_valueChanged(int)
{
applyIncreaseField(ui->m_conductor_cb, ui->m_conductor_increase_sb, AutoNumCategory::Conductor);
}
void AutoNumberingDockWidget::on_m_element_increase_sb_valueChanged(int)
{
applyIncreaseField(ui->m_element_cb, ui->m_element_increase_sb, AutoNumCategory::Element);
}
void AutoNumberingDockWidget::on_m_folio_increase_sb_valueChanged(int)
{
applyIncreaseField(ui->m_folio_cb, ui->m_folio_increase_sb, AutoNumCategory::Folio);
}
/**
@brief AutoNumberingDockWidget::contextFor
@return the numerotation context named by combo_box, for category
@@ -455,17 +507,21 @@ int AutoNumberingDockWidget::counterIndex(const NumerotationContext &context)
*/
void AutoNumberingDockWidget::refreshValueFields()
{
//Leave alone a field the user is typing in: numbering an element
//refreshes all three, and overwriting a half-typed value under the
//cursor is worse than showing it a moment out of date. Only this
//automatic path skips; an explicit refresh after a reset or an edit
//still writes, so the field always ends up canonical.
if (!ui->m_conductor_value_le->hasFocus())
refreshValueField(ui->m_conductor_cb, ui->m_conductor_value_le, AutoNumCategory::Conductor);
if (!ui->m_element_value_le->hasFocus())
refreshValueField(ui->m_element_cb, ui->m_element_value_le, AutoNumCategory::Element);
if (!ui->m_folio_value_le->hasFocus())
refreshValueField(ui->m_folio_cb, ui->m_folio_value_le, AutoNumCategory::Folio);
//Leave alone a row the user is typing in: numbering an element
//refreshes all three rows, and overwriting a half-typed value or
//increment under the cursor is worse than showing it a moment out
//of date. Only this automatic path skips; an explicit refresh after
//a reset or an edit still writes, so the row always ends up
//canonical. The next-value preview has no such guard: it is
//read-only, so there is nothing a refresh could clobber.
for (AutoNumCategory category : {AutoNumCategory::Conductor,
AutoNumCategory::Element,
AutoNumCategory::Folio})
{
const Row row = rowFor(category);
if (!row.value->hasFocus() && !row.increase->hasFocus())
refreshRow(category);
}
}
/**
@@ -507,13 +563,114 @@ void AutoNumberingDockWidget::applyValueField(QComboBox *combo_box, QLineEdit *l
const QString typed = line_edit->text();
if (typed.isEmpty() || typed == context.itemAt(index).at(1))
{
refreshValueField(combo_box, line_edit, category);
refreshRow(category);
return;
}
context.replaceValue(index, typed);
storeContext(combo_box, category, context);
refreshValueField(combo_box, line_edit, category);
refreshRow(category);
}
/**
@brief AutoNumberingDockWidget::refreshIncreaseField
Show the counter's current step size (bug #331: previously only
reachable from the full configuration dialog, via "Configurer").
*/
void AutoNumberingDockWidget::refreshIncreaseField(QComboBox *combo_box, QSpinBox *increase_sb, AutoNumCategory category)
{
//QSpinBox::setValue() emits valueChanged() even when called
//programmatically. Without blocking it, this refresh would
//immediately re-trigger on_..._increase_sb_valueChanged() ->
//applyIncreaseField() -> storeContext() -> the project's
//autoNumContextUpdated signal -> refreshValueFields() -> back here.
const QSignalBlocker blocker(increase_sb);
if (!m_project || combo_box->currentText().isEmpty())
{
increase_sb->setEnabled(false);
increase_sb->setValue(increase_sb->minimum());
return;
}
const NumerotationContext context = contextFor(combo_box, category);
const int index = counterIndex(context);
increase_sb->setEnabled(index >= 0);
increase_sb->setValue(index >= 0 ? context.itemAt(index).at(2).toInt()
: increase_sb->minimum());
}
/**
@brief AutoNumberingDockWidget::applyIncreaseField
Write the spin box's step size to the counter it displays (bug #331).
*/
void AutoNumberingDockWidget::applyIncreaseField(QComboBox *combo_box, QSpinBox *increase_sb, AutoNumCategory category)
{
if (!m_project || combo_box->currentText().isEmpty())
return;
NumerotationContext context = contextFor(combo_box, category);
const int index = counterIndex(context);
if (index < 0)
return;
if (increase_sb->value() == context.itemAt(index).at(2).toInt())
return;
context.replaceIncrease(index, increase_sb->value());
storeContext(combo_box, category, context);
refreshRow(category);
}
/**
@brief AutoNumberingDockWidget::refreshNextField
Show what this counter will read after one more step (bug #331: "visualiser
la prochaine numérotation qui sera appliquée"). Advances a copy of the
whole context through NumerotationContextCommands -- the same engine the
Suivant button in the full configuration dialog uses to step a context --
so wrap-and-carry into this part from a following part, or out of it into
a preceding one, comes out identical to what will actually happen when the
number is next consumed.
*/
void AutoNumberingDockWidget::refreshNextField(QComboBox *combo_box, QLineEdit *next_edit, AutoNumCategory category)
{
if (!m_project || combo_box->currentText().isEmpty())
{
next_edit->clear();
next_edit->setEnabled(false);
return;
}
const NumerotationContext context = contextFor(combo_box, category);
const int index = counterIndex(context);
if (index < 0)
{
next_edit->clear();
next_edit->setEnabled(false);
return;
}
Diagram *diagram = (m_project_view && m_project_view->currentDiagram())
? m_project_view->currentDiagram()->diagram()
: nullptr;
NumerotationContextCommands ncc(context, diagram);
const NumerotationContext next_context = ncc.next();
next_edit->setEnabled(true);
next_edit->setText(NumerotationContext::formatValue(next_context.itemAt(index)));
}
/**
@brief AutoNumberingDockWidget::refreshRow
Refresh a category's value, increment and next-value preview together --
every call site that used to refresh just the value field needs the
other two kept in step with it as well.
*/
void AutoNumberingDockWidget::refreshRow(AutoNumCategory category)
{
const Row row = rowFor(category);
refreshValueField(row.combo, row.value, category);
refreshIncreaseField(row.combo, row.increase, category);
refreshNextField(row.combo, row.next, category);
}
/**
@@ -557,10 +714,5 @@ void AutoNumberingDockWidget::resetAutoNum(QComboBox *combo_box, AutoNumCategory
}
storeContext(combo_box, category, context);
switch (category) {
case AutoNumCategory::Conductor: refreshValueField(combo_box, ui->m_conductor_value_le, category); break;
case AutoNumCategory::Element: refreshValueField(combo_box, ui->m_element_value_le, category); break;
case AutoNumCategory::Folio: refreshValueField(combo_box, ui->m_folio_value_le, category); break;
}
refreshRow(category);
}
@@ -25,6 +25,7 @@
class QComboBox;
class QLineEdit;
class QSpinBox;
namespace Ui {
class AutoNumberingDockWidget;
@@ -66,12 +67,28 @@ class AutoNumberingDockWidget : public QDockWidget
void on_m_element_value_le_editingFinished();
void on_m_folio_value_le_editingFinished();
void on_m_conductor_increase_sb_valueChanged(int);
void on_m_element_increase_sb_valueChanged(int);
void on_m_folio_increase_sb_valueChanged(int);
signals:
void folioAutoNumChanged(QString);
private:
enum class AutoNumCategory { Conductor, Element, Folio };
/// The four widgets that make up one category's row, bundled so
/// refreshRow() can be called with just a category instead of
/// four pointers that must always be passed in matching sets.
struct Row
{
QComboBox *combo;
QLineEdit *value;
QSpinBox *increase;
QLineEdit *next;
};
Row rowFor(AutoNumCategory category) const;
/**
@brief resetAutoNum
Reset the numerotation context currently selected in combo_box
@@ -93,6 +110,23 @@ class AutoNumberingDockWidget : public QDockWidget
void refreshValueField(QComboBox *combo_box, QLineEdit *line_edit, AutoNumCategory category);
void applyValueField(QComboBox *combo_box, QLineEdit *line_edit, AutoNumCategory category);
/// Refresh/apply the increment spin box the same way.
void refreshIncreaseField(QComboBox *combo_box, QSpinBox *increase_sb, AutoNumCategory category);
void applyIncreaseField(QComboBox *combo_box, QSpinBox *increase_sb, AutoNumCategory category);
/// Show what the counter will read after one more step, using
/// the same NumerotationContextCommands engine the Suivant
/// button in the full configuration dialog already advances
/// the whole context with -- so the preview can never disagree
/// with what actually happens when the number is next consumed.
void refreshNextField(QComboBox *combo_box, QLineEdit *next_edit, AutoNumCategory category);
/// Refresh a whole row -- value, increment and next-value
/// preview -- in one call. Every refresh call site needs the
/// increment and preview kept in step with the value now, so
/// this replaces refreshValueField() at each of them.
void refreshRow(AutoNumCategory category);
Ui::AutoNumberingDockWidget *ui;
QETProject* m_project = nullptr;
ProjectView* m_project_view = nullptr;
@@ -15,6 +15,36 @@
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="3">
<widget class="QLabel" name="value_header_label">
<property name="text">
<string>Valeur</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLabel" name="increase_header_label">
<property name="text">
<string>Incrément</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="5">
<widget class="QLabel" name="next_header_label">
<property name="text">
<string>Suivant</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="m_element_cb"/>
</item>
@@ -61,6 +91,47 @@
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="QSpinBox" name="m_conductor_increase_sb">
<property name="maximumSize">
<size>
<width>55</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Incrément : valeur ajoutée au compteur à chaque nouvelle numérotation</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="accelerated">
<bool>true</bool>
</property>
<property name="minimum">
<number>0</number>
</property>
</widget>
</item>
<item row="2" column="5">
<widget class="QLineEdit" name="m_conductor_next_le">
<property name="maximumSize">
<size>
<width>70</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Prochaine valeur qui sera appliquée avec cet incrément</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label">
<property name="text">
@@ -108,6 +179,47 @@
</property>
</widget>
</item>
<item row="3" column="4">
<widget class="QSpinBox" name="m_element_increase_sb">
<property name="maximumSize">
<size>
<width>55</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Incrément : valeur ajoutée au compteur à chaque nouvelle numérotation</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="accelerated">
<bool>true</bool>
</property>
<property name="minimum">
<number>0</number>
</property>
</widget>
</item>
<item row="3" column="5">
<widget class="QLineEdit" name="m_element_next_le">
<property name="maximumSize">
<size>
<width>70</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Prochaine valeur qui sera appliquée avec cet incrément</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="m_folio_cb"/>
</item>
@@ -144,6 +256,47 @@
</property>
</widget>
</item>
<item row="4" column="4">
<widget class="QSpinBox" name="m_folio_increase_sb">
<property name="maximumSize">
<size>
<width>55</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Incrément : valeur ajoutée au compteur à chaque nouvelle numérotation</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="accelerated">
<bool>true</bool>
</property>
<property name="minimum">
<number>0</number>
</property>
</widget>
</item>
<item row="4" column="5">
<widget class="QLineEdit" name="m_folio_next_le">
<property name="maximumSize">
<size>
<width>70</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Prochaine valeur qui sera appliquée avec cet incrément</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="6" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
+19 -1
View File
@@ -67,6 +67,7 @@ Diagram::Diagram(QETProject *project) :
m_project (project),
use_border_ (true),
draw_terminals_ (true),
draw_terminal_names_ (true),
draw_colored_conductors_ (true),
m_event_interface (nullptr),
m_freeze_new_elements (false),
@@ -2319,6 +2320,7 @@ ExportProperties Diagram::applyProperties(
old_properties.draw_border = border_and_titleblock.borderIsDisplayed();
old_properties.draw_titleblock = border_and_titleblock.titleBlockIsDisplayed();
old_properties.draw_terminals = drawTerminals();
old_properties.draw_terminal_names = drawTerminalNames();
old_properties.draw_colored_conductors = drawColoredConductors();
old_properties.exported_area = useBorder() ? QET::BorderArea
: QET::ElementsArea;
@@ -2327,6 +2329,7 @@ ExportProperties Diagram::applyProperties(
// applique les nouvelles options de rendu
setUseBorder (new_properties.exported_area == QET::BorderArea);
setDrawTerminals (new_properties.draw_terminals);
setDrawTerminalNames (new_properties.draw_terminal_names);
setDrawColoredConductors (new_properties.draw_colored_conductors);
setDisplayGrid (new_properties.draw_grid);
setDisplayGuides (new_properties.draw_guides);
@@ -2397,9 +2400,24 @@ QPointF Diagram::snapToGrid(const QPointF &p)
\~French true pour afficher les bornes, false sinon
*/
void Diagram::setDrawTerminals(bool dt) {
draw_terminals_ = dt;
foreach(QGraphicsItem *qgi, items()) {
if (Terminal *t = qgraphicsitem_cast<Terminal *>(qgi)) {
t -> setVisible(dt);
t -> update();
}
}
}
/**
@brief Diagram::setDrawTerminalNames
Defines whether or not to display the terminal names/labels
@param dt : true to display the terminal names, false otherwise
*/
void Diagram::setDrawTerminalNames(bool dt) {
draw_terminal_names_ = dt;
foreach(QGraphicsItem *qgi, items()) {
if (Terminal *t = qgraphicsitem_cast<Terminal *>(qgi)) {
t -> update();
}
}
}
+12
View File
@@ -127,6 +127,7 @@ class Diagram : public QGraphicsScene
bool draw_guides_;
QList<Diagram::Guide> m_guides_list;
bool draw_terminals_;
bool draw_terminal_names_;
bool draw_colored_conductors_;
QString m_conductors_autonum_name;
@@ -226,6 +227,8 @@ class Diagram : public QGraphicsScene
bool drawTerminals() const;
void setDrawTerminals(bool);
bool drawTerminalNames() const;
void setDrawTerminalNames(bool);
bool drawColoredConductors() const;
void setDrawColoredConductors(bool);
@@ -426,6 +429,15 @@ inline bool Diagram::drawTerminals() const
return(draw_terminals_);
}
/**
@brief Diagram::drawTerminalNames
@return true if terminal names are rendered, false otherwise
*/
inline bool Diagram::drawTerminalNames() const
{
return(draw_terminal_names_);
}
/**
@brief Diagram::drawColoredConductors
@return true if conductors colors are rendered, false otherwise.
@@ -18,6 +18,7 @@
#include "diagrameventaddshape.h"
#include "../diagram.h"
#include "../lastusedstyle.h"
#include "../undocommand/addgraphicsobjectcommand.h"
/**
@@ -77,6 +78,14 @@ void DiagramEventAddShape::mousePressEvent(QGraphicsSceneMouseEvent *event)
if (!m_shape_item)
{
m_shape_item = new QetShapeItem(pos, pos, m_shape_type);
//Start from whatever pen/brush was last applied this
//session, rather than always the hardcoded default.
if (LastUsedStyle::hasShapePen()) {
m_shape_item->setPen(LastUsedStyle::shapePen());
}
if (LastUsedStyle::hasShapeBrush()) {
m_shape_item->setBrush(LastUsedStyle::shapeBrush());
}
m_diagram->addItem (m_shape_item);
event->setAccepted(true);
return;
+1 -1
View File
@@ -100,7 +100,7 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
connect(m_diagram, SIGNAL(showDiagram(Diagram*)), this, SIGNAL(showDiagram(Diagram*)));
connect(m_diagram, SIGNAL(sceneRectChanged(QRectF)), this, SLOT(adjustSceneRect()));
connect(&(m_diagram -> border_and_titleblock), SIGNAL(diagramTitleChanged(const QString &)), this, SLOT(updateWindowTitle()));
connect(&(m_diagram -> border_and_titleblock), &BorderTitleBlock::informationChanged, this, &DiagramView::updateWindowTitle);
connect(diagram, SIGNAL(findElementRequired(ElementsLocation)), this, SIGNAL(findElementRequired(ElementsLocation)));
QShortcut *edit_conductor_color_shortcut = new QShortcut(QKeySequence(Qt::Key_F2), this);
+2 -1
View File
@@ -19,6 +19,7 @@
#include "ElementsCollection/elementcollectionitem.h"
#include "ElementsCollection/elementscollectionmodel.h"
#include "ElementsCollection/elementstreeview.h"
#include "qetapp.h"
#include "qetmessagebox.h"
#include "qfilenameedit.h"
@@ -88,7 +89,7 @@ void ElementDialog::setUpWidget()
layout->addWidget(new QLabel(label_));
m_tree_view = new QTreeView(this);
m_tree_view = new ElementsTreeView(this);
m_model = new ElementsCollectionModel(m_tree_view);
+14 -5
View File
@@ -140,12 +140,21 @@ QWidget *ExportDialog::initDiagramsListPart()
reset_mapper_ = new QSignalMapper(this);
clipboard_mapper_ = new QSignalMapper(this);
connect(preview_mapper_, SIGNAL(mapped(int)), this, SLOT(slot_previewDiagram(int)));
connect(width_mapper_, SIGNAL(mapped(int)), this, SLOT(slot_correctHeight(int)));
connect(height_mapper_, SIGNAL(mapped(int)), this, SLOT(slot_correctWidth(int)));
connect(ratio_mapper_, SIGNAL(mapped(int)), this, SLOT(slot_keepRatioChanged(int)));
connect(reset_mapper_, SIGNAL(mapped(int)), this, SLOT(slot_resetSize(int)));
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) // TODO Qt6 only: remove, mappedInt() always available
connect(preview_mapper_, SIGNAL(mapped(int)), this, SLOT(slot_previewDiagram(int)));
connect(width_mapper_, SIGNAL(mapped(int)), this, SLOT(slot_correctHeight(int)));
connect(height_mapper_, SIGNAL(mapped(int)), this, SLOT(slot_correctWidth(int)));
connect(ratio_mapper_, SIGNAL(mapped(int)), this, SLOT(slot_keepRatioChanged(int)));
connect(reset_mapper_, SIGNAL(mapped(int)), this, SLOT(slot_resetSize(int)));
connect(clipboard_mapper_, SIGNAL(mapped(int)), this, SLOT(slot_exportToClipBoard(int)));
#else
connect(preview_mapper_, &QSignalMapper::mappedInt, this, &ExportDialog::slot_previewDiagram);
connect(width_mapper_, &QSignalMapper::mappedInt, this, &ExportDialog::slot_correctHeight);
connect(height_mapper_, &QSignalMapper::mappedInt, this, &ExportDialog::slot_correctWidth);
connect(ratio_mapper_, &QSignalMapper::mappedInt, this, &ExportDialog::slot_keepRatioChanged);
connect(reset_mapper_, &QSignalMapper::mappedInt, this, &ExportDialog::slot_resetSize);
connect(clipboard_mapper_, &QSignalMapper::mappedInt, this, &ExportDialog::slot_exportToClipBoard);
#endif
diagrams_list_layout_ = new QGridLayout();
+5
View File
@@ -34,6 +34,7 @@ ExportProperties::ExportProperties() :
draw_border(true),
draw_titleblock(true),
draw_terminals(false),
draw_terminal_names(false),
draw_bg_transparent(false),
draw_colored_conductors(true),
exported_area(QET::BorderArea)
@@ -70,6 +71,8 @@ void ExportProperties::toSettings(QSettings &settings,
draw_titleblock);
settings.setValue(prefix % "drawterminals",
draw_terminals);
settings.setValue(prefix % "drawterminalnames",
draw_terminal_names);
settings.setValue(prefix % "drawbgtransparent",
draw_bg_transparent);
settings.setValue(prefix % "drawcoloredconductors",
@@ -105,6 +108,8 @@ void ExportProperties::fromSettings(QSettings &settings,
true ).toBool();
draw_terminals = settings.value(prefix % "drawterminals",
false).toBool();
draw_terminal_names = settings.value(prefix % "drawterminalnames",
false).toBool();
draw_bg_transparent = settings.value(prefix % "drawbgtransparent",
false).toBool();
draw_colored_conductors = settings.value(
+1
View File
@@ -47,6 +47,7 @@ class ExportProperties {
bool draw_border; ///< Whether to render the border (along with rows/columns headers)
bool draw_titleblock; ///< Whether to render the title block
bool draw_terminals; ///< Whether to render terminals
bool draw_terminal_names; ///< Whether to render terminal names/labels
bool draw_bg_transparent; ///< Whether to use transparency for SVG-Export
bool draw_colored_conductors; ///< Whether to render conductors colors
QET::DiagramArea exported_area; ///< Area of diagrams to be rendered
+9 -2
View File
@@ -63,6 +63,7 @@ ExportProperties ExportPropertiesWidget::exportProperties() const
export_properties.draw_border = draw_border -> isChecked();
export_properties.draw_titleblock = draw_titleblock -> isChecked();
export_properties.draw_terminals = draw_terminals -> isChecked();
export_properties.draw_terminal_names = draw_terminal_names -> isChecked();
export_properties.draw_bg_transparent = draw_bg_transparent -> isChecked();
export_properties.draw_colored_conductors = draw_colored_conductors -> isChecked();
export_properties.exported_area = export_border -> isChecked() ? QET::BorderArea : QET::ElementsArea;
@@ -85,6 +86,7 @@ void ExportPropertiesWidget::setExportProperties(const ExportProperties &export_
draw_border -> setChecked(export_properties.draw_border);
draw_titleblock -> setChecked(export_properties.draw_titleblock);
draw_terminals -> setChecked(export_properties.draw_terminals);
draw_terminal_names -> setChecked(export_properties.draw_terminal_names);
draw_bg_transparent -> setChecked(export_properties.draw_bg_transparent);
draw_colored_conductors -> setChecked(export_properties.draw_colored_conductors);
@@ -206,13 +208,17 @@ void ExportPropertiesWidget::build()
draw_terminals = new QCheckBox(tr("Dessiner les bornes"), groupbox_options);
optionshlayout -> addWidget(draw_terminals, 2, 1);
// dessiner les noms des bornes
draw_terminal_names = new QCheckBox(tr("Dessiner les noms des bornes"), groupbox_options);
optionshlayout -> addWidget(draw_terminal_names, 3, 0);
// conserver les couleurs des conducteurs
draw_colored_conductors = new QCheckBox(tr("Conserver les couleurs des conducteurs"), groupbox_options);
optionshlayout -> addWidget(draw_colored_conductors, 3, 0);
optionshlayout -> addWidget(draw_colored_conductors, 3, 1);
// use transparent background for SVG-Export
draw_bg_transparent = new QCheckBox(tr("SVG: fond transparent"), groupbox_options);
optionshlayout -> addWidget(draw_bg_transparent, 3, 1);
optionshlayout -> addWidget(draw_bg_transparent, 4, 0);
vboxLayout -> addWidget(groupbox_options);
@@ -239,6 +245,7 @@ void ExportPropertiesWidget::build()
connect(draw_border, SIGNAL(stateChanged(int)), this, SIGNAL(optionChanged()));
connect(draw_titleblock, SIGNAL(stateChanged(int)), this, SIGNAL(optionChanged()));
connect(draw_terminals, SIGNAL(stateChanged(int)), this, SIGNAL(optionChanged()));
connect(draw_terminal_names, SIGNAL(stateChanged(int)), this, SIGNAL(optionChanged()));
connect(draw_bg_transparent, SIGNAL(stateChanged(int)), this, SIGNAL(optionChanged()));
connect(draw_colored_conductors, SIGNAL(stateChanged(int)), this, SIGNAL(optionChanged()));
}
+1
View File
@@ -62,6 +62,7 @@ class ExportPropertiesWidget : public QWidget {
QCheckBox *draw_border;
QCheckBox *draw_titleblock;
QCheckBox *draw_terminals;
QCheckBox *draw_terminal_names;
QCheckBox *draw_bg_transparent;
QCheckBox *draw_colored_conductors;
QRadioButton *export_border;
+106
View File
@@ -0,0 +1,106 @@
/*
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 "lastusedstyle.h"
QPen LastUsedStyle::m_shape_pen;
bool LastUsedStyle::m_has_shape_pen = false;
QBrush LastUsedStyle::m_shape_brush;
bool LastUsedStyle::m_has_shape_brush = false;
QFont LastUsedStyle::m_text_font;
bool LastUsedStyle::m_has_text_font = false;
/**
@return true if a shape pen was set this session
*/
bool LastUsedStyle::hasShapePen()
{
return m_has_shape_pen;
}
/**
@return the last pen applied to a shape this session
*/
QPen LastUsedStyle::shapePen()
{
return m_shape_pen;
}
/**
@brief LastUsedStyle::setShapePen
Record @a pen as the last-used shape pen for this session
@param pen
*/
void LastUsedStyle::setShapePen(const QPen &pen)
{
m_shape_pen = pen;
m_has_shape_pen = true;
}
/**
@return true if a shape brush was set this session
*/
bool LastUsedStyle::hasShapeBrush()
{
return m_has_shape_brush;
}
/**
@return the last brush applied to a shape this session
*/
QBrush LastUsedStyle::shapeBrush()
{
return m_shape_brush;
}
/**
@brief LastUsedStyle::setShapeBrush
Record @a brush as the last-used shape brush for this session
@param brush
*/
void LastUsedStyle::setShapeBrush(const QBrush &brush)
{
m_shape_brush = brush;
m_has_shape_brush = true;
}
/**
@return true if a text font was set this session
*/
bool LastUsedStyle::hasTextFont()
{
return m_has_text_font;
}
/**
@return the last font applied to a free text item this session
*/
QFont LastUsedStyle::textFont()
{
return m_text_font;
}
/**
@brief LastUsedStyle::setTextFont
Record @a font as the last-used free text font for this session
@param font
*/
void LastUsedStyle::setTextFont(const QFont &font)
{
m_text_font = font;
m_has_text_font = true;
}
+63
View File
@@ -0,0 +1,63 @@
/*
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/>.
*/
#ifndef LAST_USED_STYLE_H
#define LAST_USED_STYLE_H
#include <QBrush>
#include <QFont>
#include <QPen>
/**
@brief The LastUsedStyle class
Session-scoped "last used" style for new shapes and free text created
on the diagram canvas: whatever pen/brush/font was last applied through
the properties editors becomes the starting point for the next new
item of that type, the way most drawing tools behave.
Deliberately in-memory only, not QSettings-backed: this is a live
"what did I just use" value for the current editing session, not an
app-wide default (that's already covered by the Preferences dialog's
font setting, read as the fallback when nothing has been set yet).
*/
class LastUsedStyle
{
public:
static bool hasShapePen();
static QPen shapePen();
static void setShapePen(const QPen &pen);
static bool hasShapeBrush();
static QBrush shapeBrush();
static void setShapeBrush(const QBrush &brush);
static bool hasTextFont();
static QFont textFont();
static void setTextFont(const QFont &font);
private:
LastUsedStyle() = delete;
static QPen m_shape_pen;
static bool m_has_shape_pen;
static QBrush m_shape_brush;
static bool m_has_shape_brush;
static QFont m_text_font;
static bool m_has_text_font;
};
#endif // LAST_USED_STYLE_H
+2 -1
View File
@@ -19,6 +19,7 @@
#include "ElementsCollection/elementcollectionitem.h"
#include "ElementsCollection/elementscollectionmodel.h"
#include "ElementsCollection/elementstreeview.h"
#include "NameList/ui/namelistwidget.h"
#include "editor/ui/qetelementeditor.h"
#include "qetmessagebox.h"
@@ -85,7 +86,7 @@ QWizardPage *NewElementWizard::buildStep1()
page -> setSubTitle(tr("Sélectionnez une catégorie dans laquelle enregistrer le nouvel élément.", "wizard page subtitle"));
QVBoxLayout *layout = new QVBoxLayout();
m_tree_view = new QTreeView(this);
m_tree_view = new ElementsTreeView(this);
m_model = new ElementsCollectionModel(m_tree_view);
m_model->hideElement();
+3
View File
@@ -161,6 +161,7 @@ ProjectPrintWindow::ProjectPrintWindow(QETProject *project, QPrinter *printer, Q
ui->m_draw_border_cb->setChecked(exp.draw_border);
ui->m_draw_titleblock_cb->setChecked(exp.draw_titleblock);
ui->m_draw_terminal_cb->setChecked(exp.draw_terminals);
ui->m_draw_terminal_names_cb->setChecked(exp.draw_terminal_names);
ui->m_keep_conductor_color_cb->setChecked(exp.draw_colored_conductors);
ui->m_date_cb->blockSignals(true);
@@ -523,6 +524,7 @@ ExportProperties ProjectPrintWindow::exportProperties() const
exp.draw_border = ui->m_draw_border_cb->isChecked();
exp.draw_titleblock = ui->m_draw_titleblock_cb->isChecked();
exp.draw_terminals = ui->m_draw_terminal_cb->isChecked();
exp.draw_terminal_names = ui->m_draw_terminal_names_cb->isChecked();
exp.draw_colored_conductors = ui->m_keep_conductor_color_cb->isChecked();
exp.draw_grid = false;
exp.draw_guides = false;
@@ -796,6 +798,7 @@ void ProjectPrintWindow::on_m_draw_border_cb_clicked() { m_preview->upd
void ProjectPrintWindow::on_m_draw_titleblock_cb_clicked() { m_preview->updatePreview(); }
void ProjectPrintWindow::on_m_keep_conductor_color_cb_clicked() { m_preview->updatePreview(); }
void ProjectPrintWindow::on_m_draw_terminal_cb_clicked() { m_preview->updatePreview(); }
void ProjectPrintWindow::on_m_draw_terminal_names_cb_clicked() { m_preview->updatePreview(); }
void ProjectPrintWindow::on_m_fit_in_page_cb_clicked() { m_preview->updatePreview(); }
void ProjectPrintWindow::on_m_use_full_page_cb_clicked()
{
+1
View File
@@ -55,6 +55,7 @@ class ProjectPrintWindow : public QMainWindow
void on_m_draw_titleblock_cb_clicked();
void on_m_keep_conductor_color_cb_clicked();
void on_m_draw_terminal_cb_clicked();
void on_m_draw_terminal_names_cb_clicked();
void on_m_fit_in_page_cb_clicked();
void on_m_use_full_page_cb_clicked();
void on_m_zoom_out_action_triggered();
+10
View File
@@ -183,6 +183,16 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="m_draw_terminal_names_cb">
<property name="text">
<string>Dessiner les noms des bornes</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
+5 -2
View File
@@ -124,8 +124,11 @@ QETApp::QETApp() :
initSplashScreen();
initSystemTray();
connect(&signal_map, SIGNAL(mapped(QWidget *)),
this, SLOT(invertMainWindowVisibility(QWidget *)));
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) // TODO Qt6 only: remove, mappedObject() always available
connect(&signal_map, SIGNAL(mapped(QWidget *)), this, SLOT(invertMainWindowVisibility(QWidget *)));
#else
connect(&signal_map, &QSignalMapper::mappedObject, this, [this](QObject *object) { invertMainWindowVisibility(qobject_cast<QWidget *>(object)); });
#endif
qApp->setQuitOnLastWindowClosed(false);
connect(qApp, &QApplication::lastWindowClosed,
this, &QETApp::checkRemainingWindows);
+49
View File
@@ -107,7 +107,11 @@ QETDiagramEditor::QETDiagramEditor(const QStringList &files, QWidget *parent) :
m_workspace.setTabsClosable(true);
//Set the signal mapper
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) // TODO Qt6 only: remove, mappedObject() always available
connect(&windowMapper, SIGNAL(mapped(QWidget *)), this, SLOT(activateWidget(QWidget *)));
#else
connect(&windowMapper, &QSignalMapper::mappedObject, this, [this](QObject *object) { activateWidget(qobject_cast<QWidget *>(object)); });
#endif
setWindowTitle(tr("QElectroTech", "window title"));
setWindowIcon(QET::Icons::QETLogo);
@@ -644,6 +648,7 @@ void QETDiagramEditor::setUpActions()
//Selections Actions (related to a selected item)
m_delete_selection = m_selection_actions_group.addAction( QET::Icons::EditDelete, tr("Supprimer") );
m_rotate_selection = m_selection_actions_group.addAction( QET::Icons::TransformRotate, tr("Pivoter") );
m_rotate_group_selection = m_selection_actions_group.addAction( QET::Icons::TransformRotate, tr("Pivoter le groupe") );
m_rotate_texts = m_selection_actions_group.addAction( QET::Icons::ObjectRotateRight, tr("Orienter les textes") );
m_find_element = m_selection_actions_group.addAction( QET::Icons::ZoomDraw, tr("Retrouver dans le panel") );
m_edit_selection = m_selection_actions_group.addAction( QET::Icons::ElementEdit, tr("Éditer l'item sélectionné") );
@@ -651,16 +656,19 @@ void QETDiagramEditor::setUpActions()
ShortcutManager::instance().registerAction(m_delete_selection, "diagrameditor.delete_selection", tr("Éditeur de schémas"), Qt::Key_Delete);
ShortcutManager::instance().registerAction(m_rotate_selection, "diagrameditor.rotate_selection", tr("Éditeur de schémas"), Qt::Key_Space);
ShortcutManager::instance().registerAction(m_rotate_group_selection, "diagrameditor.rotate_group_selection", tr("Éditeur de schémas"), Qt::SHIFT | Qt::Key_Space);
ShortcutManager::instance().registerAction(m_rotate_texts, "diagrameditor.rotate_texts", tr("Éditeur de schémas"), Qt::CTRL | Qt::Key_Space);
ShortcutManager::instance().registerAction(m_edit_selection, "diagrameditor.edit_selection", tr("Éditeur de schémas"), Qt::CTRL | Qt::Key_E);
m_delete_selection->setStatusTip( tr("Enlève les éléments sélectionnés du folio", "status bar tip"));
m_rotate_selection->setStatusTip( tr("Pivote les éléments et textes sélectionnés", "status bar tip"));
m_rotate_group_selection->setStatusTip( tr("Pivote la sélection comme un groupe autour de son centre, au lieu de chaque élément sur place", "status bar tip"));
m_rotate_texts ->setStatusTip( tr("Pivote les textes sélectionnés à un angle précis", "status bar tip"));
m_find_element ->setStatusTip( tr("Retrouve l'élément sélectionné dans le panel", "status bar tip"));
m_delete_selection ->setData("delete_selection");
m_rotate_selection ->setData("rotate_selection");
m_rotate_group_selection->setData("rotate_group_selection");
m_rotate_texts ->setData("rotate_selected_text");
m_find_element ->setData("find_selected_element");
m_edit_selection ->setData("edit_selected_element");
@@ -1303,6 +1311,12 @@ bool QETDiagramEditor::addProject(QETProject *project, bool update_panel)
undo_group.addStack(project -> undoStack());
connect(project, &QETProject::projectModified, this, [this](QETProject *modified_project, bool) {
if (modified_project == currentProject()) {
updateWindowModifiedState();
}
});
m_element_collection_widget->addProject(project);
// met a jour le panel d'elements
@@ -1621,6 +1635,12 @@ void QETDiagramEditor::selectionGroupTriggered(QAction *action)
if(c->isValid())
diagram->undoStack().push(c);
}
else if (value == "rotate_group_selection")
{
RotateSelectionCommand *c = new RotateSelectionCommand(diagram, 90, nullptr, true);
if(c->isValid())
diagram->undoStack().push(c);
}
else if (value == "rotate_selected_text")
diagram->undoStack().push(new RotateTextsCommand(diagram));
else if (value == "find_selected_element" && currentElement())
@@ -1755,6 +1775,7 @@ void QETDiagramEditor::slot_updateComplexActions()
<< m_copy
<< m_delete_selection
<< m_rotate_selection
<< m_rotate_group_selection
<< m_edit_selection
<< m_group_selected_texts;
for(QAction *action : action_list)
@@ -1783,6 +1804,7 @@ void QETDiagramEditor::slot_updateComplexActions()
m_copy -> setEnabled(copiable_items);
m_delete_selection -> setEnabled(!ro && deletable_items);
m_rotate_selection -> setEnabled(!ro && diagram_->canRotateSelection());
m_rotate_group_selection -> setEnabled(!ro && diagram_->canRotateSelection());
//Action that need selected texts or texts group
QList<DiagramTextItem *> texts = DiagramContent(diagram_).selectedTexts();
@@ -2597,6 +2619,7 @@ void QETDiagramEditor::subWindowActivated(QMdiSubWindow *subWindows)
slot_updateWindowsMenu();
emit syncElementsPanel();
updateUsageTrackersActiveState();
updateWindowModifiedState();
}
/**
@@ -2622,6 +2645,32 @@ void QETDiagramEditor::updateUsageTrackersActiveState()
}
}
/**
@brief QETDiagramEditor::updateWindowModifiedState
Reflect the currently active project's unsaved-changes state in the
main window's title and native "document modified" indicator (e.g.
the dot in the close button on macOS). Called whenever the active
project changes, or whenever the active project's own modified state
changes.
The window title's "[*]" placeholder is Qt's own convention: combined
with setWindowModified(), it lets each platform render the modified
indicator its own way (or not at all, on platforms without one)
without QET having to draw anything itself.
*/
void QETDiagramEditor::updateWindowModifiedState()
{
if (QETProject *project = currentProject()) {
setWindowTitle(QString("%1[*] - %2").arg(
project->pathNameTitle(),
tr("QElectroTech", "window title")));
setWindowModified(project->projectOptionsWereModified());
} else {
setWindowTitle(tr("QElectroTech", "window title"));
setWindowModified(false);
}
}
/**
@brief QETDiagramEditor::selectionChanged
This slot is called when a diagram selection was changed.
+2
View File
@@ -98,6 +98,7 @@ class QETDiagramEditor : public QETMainWindow
ProjectView *findProject(const QString &) const;
QMdiSubWindow *subWindowForWidget(QWidget *) const;
void updateUsageTrackersActiveState();
void updateWindowModifiedState();
signals:
void syncElementsPanel();
@@ -216,6 +217,7 @@ class QETDiagramEditor : public QETMainWindow
*m_edit_selection, ///< To edit selected item
*m_delete_selection, ///< Delete selection
*m_rotate_selection, ///< Rotate selected elements and text items by 90 degrees
*m_rotate_group_selection = nullptr, ///< Rotate the selection as a whole around its shared center, instead of each item in place
*m_rotate_texts, ///< Direct selected text items to a specific angle
*m_find_element, ///< Find the selected element in the panel
*m_group_selected_texts = nullptr,
@@ -19,6 +19,7 @@
#include "../diagram.h"
#include "../diagramcommands.h"
#include "../lastusedstyle.h"
#include "../qet.h"
#include "../qetapp.h"
#include "../utils/qetutils.h"
@@ -33,7 +34,10 @@
IndependentTextItem::IndependentTextItem() :
DiagramTextItem(nullptr)
{
setFont(QETApp::indiTextsItemFont());
//Start from the font last applied to a text item this session,
//falling back to the app-wide Preferences default otherwise.
setFont(LastUsedStyle::hasTextFont() ? LastUsedStyle::textFont()
: QETApp::indiTextsItemFont());
QSettings settings;
setRotation(settings.value("diagrameditor/independent_text_rotation", 0).toInt());
}
+17 -14
View File
@@ -199,19 +199,21 @@ void Terminal::paint(
// dessin de la borne en rouge
// draw the terminal in red
t.setColor(Qt::red);
painter -> setPen(t);
painter -> drawLine(c, e);
if (!diagram() || diagram()->drawTerminals()) {
t.setColor(Qt::red);
painter -> setPen(t);
painter -> drawLine(c, e);
// dessin du point d'amarrage au conducteur en bleu
// draw the docking point to the conductor in blue
t.setColor(m_hovered_color);
painter -> setPen(t);
painter -> setBrush(m_hovered_color);
if (m_hovered) {
painter -> setRenderHint(QPainter::Antialiasing, true);
painter -> drawEllipse(QRectF(c.x() - 2.5, c.y() - 2.5, 5.0, 5.0));
} else painter -> drawPoint(c);
// dessin du point d'amarrage au conducteur en bleu
// draw the docking point to the conductor in blue
t.setColor(m_hovered_color);
painter -> setPen(t);
painter -> setBrush(m_hovered_color);
if (m_hovered) {
painter -> setRenderHint(QPainter::Antialiasing, true);
painter -> drawEllipse(QRectF(c.x() - 2.5, c.y() - 2.5, 5.0, 5.0));
} else painter -> drawPoint(c);
}
//Draw help line if needed,
if (diagram() && m_draw_help_line)
@@ -273,9 +275,10 @@ void Terminal::paint(
m_help_line_a -> setLine(line);
}
// Draw label if show_name is enabled
// Draw label if show_name is enabled and terminal names are allowed
const QString display_name = name();
if (d->m_show_name && !display_name.isEmpty()) {
if (d->m_show_name && !display_name.isEmpty()
&& (!diagram() || diagram()->drawTerminalNames())) {
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setRenderHint(QPainter::TextAntialiasing, true);
painter->setFont(d->m_label_font);
-1
View File
@@ -244,7 +244,6 @@ class QETProject : public QObject
/// rebuild their rule lists; this one just says "re-read me".
void autoNumContextUpdated();
void folioAutoNumRemoved();
void folioAutoNumChanged(QString);
void defaultTitleBlockPropertiesChanged();
void conductorAutoNumChanged();
@@ -217,7 +217,11 @@ QString TitleBlockTemplateLogoManager::confirmLogoName(const QString &initial_na
connect(replace_button, SIGNAL(clicked()), signal_mapper, SLOT(map()));
connect(rename_button, SIGNAL(clicked()), signal_mapper, SLOT(map()));
connect(cancel_button, SIGNAL(clicked()), signal_mapper, SLOT(map()));
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) // TODO Qt6 only: remove, mappedInt() always available
connect(signal_mapper, SIGNAL(mapped(int)), rename_dialog, SLOT(done(int)));
#else
connect(signal_mapper, &QSignalMapper::mappedInt, rename_dialog, &QDialog::done);
#endif
}
rd_label -> setText(
QString(tr(
+41 -14
View File
@@ -999,20 +999,47 @@ void TitleBlockTemplateView::updateDisplayedMinMaxWidth()
int max_width = tbtemplate_ -> maximumWidth();
QString min_max_width_sentence;
if (max_width != -1) {
min_max_width_sentence = QString(
tr(
"Longueur minimale : %1px\nLongueur maximale : %2px\n",
"tooltip showing the minimum and/or maximum width of the edited template"
)
).arg(min_width).arg(max_width);
} else {
min_max_width_sentence = QString(
tr(
"Longueur minimale : %1px\n",
"tooltip showing the minimum width of the edited template"
)
).arg(min_width);
switch (static_cast<TitleBlockTemplate::WidthConstraintCase>(min_width)) {
case TitleBlockTemplate::WidthConstraintCase::RelativeWidthExceeds100Percent:
// minimumWidth() and maximumWidth() both check infeasibility
// first, identically, so max_width reports the same case
// here -- no need to check it separately.
min_max_width_sentence = tr(
"Attention : la somme des largeurs relatives dépasse 100%% de la largeur totale, ce modèle de cartouche ne peut être satisfait par aucune largeur.\n",
"tooltip warning shown when a template's relative-to-total-length columns alone already exceed 100%% of the total width"
);
break;
case TitleBlockTemplate::WidthConstraintCase::AbsoluteColumnsExceedRemainingWidth:
min_max_width_sentence = tr(
"Attention : les colonnes de largeur fixe ne peuvent pas tenir dans la largeur restante, ce modèle de cartouche ne peut être satisfait par aucune largeur.\n",
"tooltip warning shown when a template's relative-to-total-length columns already consume all available width, leaving no room for its fixed-width columns"
);
break;
default:
if (min_width != static_cast<int>(TitleBlockTemplate::WidthConstraintCase::Unconstrained)) {
min_max_width_sentence += QString(
tr(
"Longueur minimale : %1px\n",
"tooltip showing the minimum width of the edited template"
)
).arg(min_width);
}
if (max_width != static_cast<int>(TitleBlockTemplate::WidthConstraintCase::Unconstrained)) {
min_max_width_sentence += QString(
tr(
"Longueur maximale : %1px\n",
"tooltip showing the maximum width of the edited template"
)
).arg(max_width);
}
if (min_max_width_sentence.isEmpty()) {
min_max_width_sentence = tr(
"Longueur non contrainte.\n",
"tooltip shown when the edited template has neither a minimum nor a maximum width constraint"
);
}
break;
}
// the tooltip may also display the split label for readability purpose
+110 -22
View File
@@ -960,42 +960,130 @@ int TitleBlockTemplate::columnTypeTotal(QET::TitleBlockColumnLength type) {
}
/**
@return the minimum width for this template
@brief TitleBlockTemplate::classifyWidthConstraint
Classifies this template's absolute-width (ABS) and
relative-to-total-length (RTT) columns, independently of whether a
minimum or a maximum width is being computed.
The classification is derived from the same inequality
minimumWidth() solves for a minimum width:
@code
TOT >= ((sum(RTT)/100)*TOT) + sum(ABS)
@endcode
Regardless of TOT, the RTT term above scales linearly with TOT. If
sum(RTT) alone already accounts for 100% or more of TOT, no choice
of TOT -- however large -- can make room for it (and for any ABS
columns on top of it): making the template wider grows the RTT
columns' pixel footprint by the same proportion, so the shortfall
never resolves. This function exists so both minimumWidth() and
maximumWidth() report that consistently, instead of maximumWidth()
incorrectly treating a misconfigured template as "no upper bound".
@param[out] abs_total set to columnTypeTotal(QET::Absolute).
@param[out] remaining_width_fraction set to the fraction of the
total template width left over once the RTT columns have taken
their share: (100.0 - sum(RTT)) / 100.0. Zero means the RTT
columns claim the entire width, leaving nothing for ABS columns;
negative means they claim more than the entire width, which is
unsatisfiable regardless of any ABS columns. Only meaningful when
this function returns std::nullopt.
@return WidthConstraintCase::RelativeWidthExceeds100Percent if
sum(RTT) exceeds 100% (unsatisfiable at any width, with or without
ABS columns), WidthConstraintCase::AbsoluteColumnsExceedRemainingWidth
if sum(RTT) equals exactly 100% and at least one ABS column is also
present (unsatisfiable at any width, since the RTT columns leave no
room for it), WidthConstraintCase::Unconstrained if sum(RTT) equals
exactly 100% with no ABS columns (every term vanishes, any width
holds -- an ordinary, valid template), or std::nullopt if this
template's columns admit a genuine finite width.
*/
std::optional<TitleBlockTemplate::WidthConstraintCase> TitleBlockTemplate::classifyWidthConstraint(int &abs_total, qreal &remaining_width_fraction)
{
abs_total = columnTypeTotal(QET::Absolute);
remaining_width_fraction = (100.0 - columnTypeTotal(QET::RelativeToTotalLength)) / 100.0;
if (remaining_width_fraction < 0.0) {
return WidthConstraintCase::RelativeWidthExceeds100Percent;
}
if (remaining_width_fraction == 0.0) {
return abs_total == 0
? WidthConstraintCase::Unconstrained
: WidthConstraintCase::AbsoluteColumnsExceedRemainingWidth;
}
return std::nullopt; // a finite width exists: remaining_width_fraction > 0
}
/**
@brief TitleBlockTemplate::minimumWidth
@return the minimum width, in pixels, required for this template's
absolute-width (ABS) and relative-to-total-length (RTT) columns to
all fit.
Derivation: writing TOT for the (variable) total template width,
the minimum size enforced by the ABS and RTT columns is:
@code
TOT >= ((sum(RTT)/100)*TOT) + sum(ABS)
=> (1 - (sum(RTT)/100))*TOT >= sum(ABS)
=> TOT >= sum(ABS) / (1 - (sum(RTT)/100))
=> TOT >= sum(ABS) / ((100 - sum(RTT))/100)
@endcode
relative-to-remaining-length (RTR) columns do not constrain the
minimum width, since by definition they only ever claim a share of
whatever space is left over after the ABS and RTT columns are laid
out.
If no finite minimum width applies, returns one of
WidthConstraintCase::Unconstrained,
WidthConstraintCase::RelativeWidthExceeds100Percent, or
WidthConstraintCase::AbsoluteColumnsExceedRemainingWidth (cast to
int) instead -- see classifyWidthConstraint() and that enum's
documentation for when each case applies.
*/
int TitleBlockTemplate::minimumWidth()
{
// Abbreviations: ABS: absolute, RTT: relative to total, RTR:
// relative to remaining,
// TOT: total diagram/TBT width (variable).
// Minimum size may be enforced by ABS and RTT widths:
// TOT >= ((sum(REL)/100)*TOT)+sum(ABS)
// => (1 - (sum(REL)/100))TOT >= sum(ABS)
// => TOT >= sum(ABS) / (1 - (sum(REL)/100))
// => TOT >= sum(ABS) / ((100 - sum(REL))/100))
return(
qRound(
columnTypeTotal(QET::Absolute)
/
((100.0 - columnTypeTotal(QET::RelativeToTotalLength))
/ 100.0)
)
);
int abs_total;
qreal remaining_width_fraction;
if (auto width_case = classifyWidthConstraint(abs_total, remaining_width_fraction)) {
return static_cast<int>(*width_case);
}
return qRound(abs_total / remaining_width_fraction);
}
/**
@brief TitleBlockTemplate::maximumWidth
@return the maximum width for this template,
or -1 if it does not have any.
@return the maximum width, in pixels, this template may be
rendered at.
If this template is composed entirely of absolute-width (ABS)
columns, the maximum is fixed: the template cannot extend beyond
their sum, since nothing in it scales with the template's total
width. Otherwise, at least one column scales with the total width
(relative-to-total-length or relative-to-remaining-length), so
there is ordinarily no upper bound: returns
WidthConstraintCase::Unconstrained (cast to int) -- the common case
for most templates.
The exception is when this template's columns cannot be satisfied
by any width at all (see classifyWidthConstraint()) -- in that case
there is no width, however large, that works, and this returns
WidthConstraintCase::RelativeWidthExceeds100Percent or
WidthConstraintCase::AbsoluteColumnsExceedRemainingWidth (cast to
int) instead of Unconstrained, matching minimumWidth()'s handling
of the same cases.
*/
int TitleBlockTemplate::maximumWidth()
{
int abs_total;
qreal remaining_width_fraction;
if (auto width_case = classifyWidthConstraint(abs_total, remaining_width_fraction)) {
return static_cast<int>(*width_case);
}
if (columnTypeCount(QET::Absolute) == columns_width_.count()) {
// The template is composed of absolute widths only,
// therefore it may not extend beyond their sum.
return(columnTypeTotal(QET::Absolute));
return abs_total; // already computed by classifyWidthConstraint()
}
return(-1);
return static_cast<int>(WidthConstraintCase::Unconstrained);
}
/**
+80 -1
View File
@@ -24,6 +24,7 @@
#include <QtSvg>
#include <QtXml>
#include <optional>
/**
@brief The TitleBlockTemplate class
@@ -36,9 +37,73 @@
*/
class TitleBlockTemplate : public QObject {
Q_OBJECT
public:
/**
@brief The TitleBlockTemplate::WidthConstraintCase enum Distinguishes
the possible outcomes of minimumWidth() or maximumWidth() for a
template's column layout: either a genuine finite pixel width, or one
of the cases below where no single finite width applies -- some of which are
perfectly ordinary (Unconstrained), and some of which indicate the
template's columns cannot be laid out at any width.
Both functions return one of the non-Bounded* cases below,
cast to int, in place of a genuine width whenever no finite
width applies. (*minimumWidth()/maximumWidth() never return
a literal "Bounded" value -- when a finite width exists,
they return that width directly. Bounded only appears as a
concept in classifyWidthConstraint()'s std::nullopt return.)
The int-based encoding exists because this project
currently targets C++17.
@todo Once this project's minimum supported C++ standard
reaches C++23, migrate minimumWidth() and maximumWidth() to
return std::expected<int, WidthConstraintCase> instead of
encoding these cases as negative int sentinels. That removes
the possibility of a caller silently misinterpreting a
sentinel as a real pixel width -- something the current
int-based encoding cannot prevent at compile time.
*/
enum class WidthConstraintCase : int {
/**
There is no finite width constraint in this direction:
any width is valid. This is a perfectly ordinary case,
not a problem with the template -- for minimumWidth(),
it happens when the relative-to-total-length (RTT)
columns account for exactly 100% of the total width and
there are no absolute-width (ABS) columns competing for
space -- every term in the minimum-width inequality
vanishes (0 >= 0), which holds for any width. For
maximumWidth(), it happens whenever at least one column
scales with the total width, so nothing caps how wide
the template may grow -- the common case for most
templates.
*/
Unconstrained = -1,
/**
The RTT columns alone already exceed 100% of the total
width (sum(RTT) > 100), independently of whether any ABS
columns exist. The minimum-width inequality then only
holds for a non-positive width, which cannot represent a
real template: this indicates a genuinely misconfigured
template that cannot be laid out at any width.
*/
RelativeWidthExceeds100Percent = -2,
/**
The RTT columns account for exactly 100% of the total
width, but at least one ABS column also needs a nonzero,
fixed amount of space on top of that. The minimum-width
inequality reduces to "0 >= (a positive number)", which
never holds: this also indicates a genuinely
misconfigured template that cannot be laid out at any
width.
*/
AbsoluteColumnsExceedRemainingWidth = -3
};
// constructors, destructor
public:
TitleBlockTemplate(QObject * = nullptr);
~TitleBlockTemplate() override;
private:
@@ -146,6 +211,20 @@ class TitleBlockTemplate : public QObject {
bool checkCell(const QDomElement &, TitleBlockCell ** = nullptr);
void flushCells();
void initCells();
/**
@brief TitleBlockTemplate::classifyWidthConstraint Classifies this template's absolute-width (ABS) and
relative-to-total-length (RTT) columns, independently of whether a minimum or a maximum width is being computed -- see
minimumWidth() for the derivation this is based on.
@param[out] abs_total set to columnTypeTotal(QET::Absolute).
@param[out] remaining_width_fraction set to the fraction of the total template width left over once the RTT columns have taken
their share: (100.0 - sum(RTT)) / 100.0. Zero means the RTT columns claim the entire width, leaving nothing for ABS
columns; negative means they claim more than the entire width, which is unsatisfiable regardless of any ABS columns. Only
meaningful when this function returns std::nullopt.
@return WidthConstraintCase::RelativeWidthExceeds100Percent, WidthConstraintCase::AbsoluteColumnsExceedRemainingWidth, or
WidthConstraintCase::Unconstrained if no finite width exists for this template's columns, or std::nullopt if a finite width
does exist (computable as qRound(abs_total / remaining_width_fraction)).
*/
std::optional<WidthConstraintCase> classifyWidthConstraint(int &abs_total, qreal &remaining_width_fraction);
int lengthRange(int, int, const QList<int> &) const;
QString finalTextForCell(
const TitleBlockCell &,
+27 -3
View File
@@ -396,17 +396,29 @@ void ProjectAutoNumConfigPage::buildConnections()
//Conductor Tab
connect(m_saw_conductor, &SelectAutonumW::applyPressed, this, &ProjectAutoNumConfigPage::saveContextConductor);
connect(m_saw_conductor, &SelectAutonumW::removeClicked, this, &ProjectAutoNumConfigPage::removeContextConductor);
connect(m_saw_conductor->contextComboBox(), SIGNAL(currentIndexChanged(QString)), this, SLOT(updateContextConductor(QString)));
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // TODO Qt6 only: remove, textActivated() always available
connect(m_saw_conductor->contextComboBox(), SIGNAL(activated(QString)), this, SLOT(updateContextConductor(QString)));
#else
connect(m_saw_conductor->contextComboBox(), &QComboBox::textActivated, this, &ProjectAutoNumConfigPage::updateContextConductor);
#endif
//Element Tab
connect(m_saw_element, &SelectAutonumW::applyPressed, this, &ProjectAutoNumConfigPage::saveContextElement);
connect(m_saw_element, &SelectAutonumW::removeClicked, this, &ProjectAutoNumConfigPage::removeContextElement);
connect(m_saw_element->contextComboBox(), SIGNAL(currentIndexChanged(QString)), this, SLOT(updateContextElement(QString)));
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // TODO Qt6 only: remove, textActivated() always available
connect(m_saw_element->contextComboBox(), SIGNAL(activated(QString)), this, SLOT(updateContextElement(QString)));
#else
connect(m_saw_element->contextComboBox(), &QComboBox::textActivated, this, &ProjectAutoNumConfigPage::updateContextElement);
#endif
//Folio Tab
connect(m_saw_folio, &SelectAutonumW::applyPressed, this, &ProjectAutoNumConfigPage::saveContextFolio);
connect(m_saw_folio, &SelectAutonumW::removeClicked, this, &ProjectAutoNumConfigPage::removeContextFolio);
connect(m_saw_folio->contextComboBox(), SIGNAL(currentIndexChanged(QString)), this, SLOT(updateContextFolio(QString)));
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // TODO Qt6 only: remove, textActivated() always available
connect(m_saw_folio->contextComboBox(), SIGNAL(activated(QString)), this, SLOT(updateContextFolio(QString)));
#else
connect(m_saw_folio->contextComboBox(), &QComboBox::textActivated, this, &ProjectAutoNumConfigPage::updateContextFolio);
#endif
// Auto Folio Numbering
connect (m_faw, SIGNAL (applyPressed()), this, SLOT (applyAutoNum()));
@@ -491,6 +503,10 @@ void ProjectAutoNumConfigPage::removeContextElement()
return;
m_project->removeElementAutoNum (m_saw_element->contextComboBox()->currentText());
m_saw_element->contextComboBox()->removeItem (m_saw_element->contextComboBox()->currentIndex());
// removeItem() removes the current selection programmatically but
// textActivated() does not react to (by design, see buildConnections()).
// Refresh the displayed pattern explicitly so it matches the new selection.
updateContextElement(m_saw_element->contextComboBox()->currentText());
}
/**
@@ -678,6 +694,10 @@ void ProjectAutoNumConfigPage::removeContextConductor()
if ( m_saw_conductor->contextComboBox()-> currentText() == tr("Nom de la nouvelle numérotation") ) return;
m_project -> removeConductorAutoNum (m_saw_conductor->contextComboBox()-> currentText() );
m_saw_conductor->contextComboBox()-> removeItem (m_saw_conductor->contextComboBox()-> currentIndex() );
// removeItem() removes the current selection programmatically but
// textActivated() does not react to (by design, see buildConnections()).
// Refresh the displayed pattern explicitly so it matches the new selection.
updateContextConductor(m_saw_conductor->contextComboBox()->currentText());
project()->conductorAutoNumRemoved();
}
@@ -691,6 +711,10 @@ void ProjectAutoNumConfigPage::removeContextFolio()
if ( m_saw_folio->contextComboBox() -> currentText() == tr("Nom de la nouvelle numérotation") ) return;
m_project -> removeFolioAutoNum (m_saw_folio->contextComboBox() -> currentText() );
m_saw_folio->contextComboBox() -> removeItem (m_saw_folio->contextComboBox() -> currentIndex() );
// removeItem() removes the current selection programmatically but
// textActivated() does not react to (by design, see buildConnections()).
// Refresh the displayed pattern explicitly so it matches the new selection.
updateContextFolio(m_saw_folio->contextComboBox()->currentText());
project()->folioAutoNumRemoved();
}
+2
View File
@@ -20,6 +20,7 @@
#include "../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../diagram.h"
#include "../diagramcommands.h"
#include "../lastusedstyle.h"
#include "../qetgraphicsitem/independenttextitem.h"
#include "../ui_inditextpropertieswidget.h"
@@ -455,6 +456,7 @@ void IndiTextPropertiesWidget::on_m_font_pb_clicked()
m_font_is_selected = true;
ui->m_font_pb->setText(font.family());
ui->m_size_sb->setValue(font.pointSize());
LastUsedStyle::setTextFont(m_selected_font);
apply();
} else {
ui->m_font_pb->setText(tr("Police"));
@@ -20,6 +20,7 @@
#include "../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../diagram.h"
#include "../lastusedstyle.h"
#include "../qetgraphicsitem/qetshapeitem.h"
#include "../ui_shapegraphicsitempropertieswidget.h"
@@ -180,6 +181,7 @@ QUndoCommand* ShapeGraphicsItemPropertiesWidget::associatedUndo() const
{
undo = new QPropertyUndoCommand(m_shape, "pen", old_pen, new_pen);
undo->setText(tr("Modifier le trait d'une forme"));
LastUsedStyle::setShapePen(new_pen);
}
QBrush old_brush = m_shape->brush();
@@ -196,6 +198,7 @@ QUndoCommand* ShapeGraphicsItemPropertiesWidget::associatedUndo() const
undo = new QPropertyUndoCommand(m_shape, "brush", old_brush, new_brush);
undo->setText(tr("Modifier le remplissage d'une forme"));
}
LastUsedStyle::setShapeBrush(new_brush);
}
if (ui->m_close_polygon->isChecked() != m_shape->isClosed())
@@ -321,6 +324,7 @@ QUndoCommand* ShapeGraphicsItemPropertiesWidget::associatedUndo() const
if (new_pen != old_pen) {
new QPropertyUndoCommand(m_shape, "pen", old_pen, new_pen, undo);
LastUsedStyle::setShapePen(new_pen);
}
QBrush old_brush = m_shape->brush();
@@ -330,6 +334,7 @@ QUndoCommand* ShapeGraphicsItemPropertiesWidget::associatedUndo() const
if (new_brush != old_brush) {
new QPropertyUndoCommand(m_shape, "brush", old_brush, new_brush, undo);
LastUsedStyle::setShapeBrush(new_brush);
}
if (ui->m_close_polygon->isChecked() != m_shape->isClosed()) {
+20 -2
View File
@@ -194,7 +194,16 @@ TitleBlockProperties TitleBlockPropertiesWidget::properties() const
prop.useDate = TitleBlockProperties::UseDateValue;
prop.date = ui->m_date_edit->date();
}
else if (ui->m_current_date_rb->isVisible() && ui->m_current_date_rb->isChecked()) {
else if (!ui->m_current_date_rb->isHidden() && ui->m_current_date_rb->isChecked()) {
/* isVisible() (unlike isHidden()) also depends on the whole
* ancestor chain being visible, not just this widget's own
* state -- inside a QTabWidget page (both the New Project and
* Project Properties dialogs embed this widget in one), it's
* false whenever this isn't the active tab, silently dropping
* "current date" back to the useDate/date default (no date)
* even though the radio button is still checked underneath.
* isHidden() mirrors the read side's own check in initDialog(),
* and isn't affected by an ancestor tab switch. */
prop.useDate = TitleBlockProperties::CurrentDate;
prop.date = QDate::currentDate();
}
@@ -237,7 +246,16 @@ TitleBlockProperties TitleBlockPropertiesWidget::propertiesAutoNum(
prop.useDate = TitleBlockProperties::UseDateValue;
prop.date = ui->m_date_edit->date();
}
else if (ui->m_current_date_rb->isVisible() && ui->m_current_date_rb->isChecked()) {
else if (!ui->m_current_date_rb->isHidden() && ui->m_current_date_rb->isChecked()) {
/* isVisible() (unlike isHidden()) also depends on the whole
* ancestor chain being visible, not just this widget's own
* state -- inside a QTabWidget page (both the New Project and
* Project Properties dialogs embed this widget in one), it's
* false whenever this isn't the active tab, silently dropping
* "current date" back to the useDate/date default (no date)
* even though the radio button is still checked underneath.
* isHidden() mirrors the read side's own check in initDialog(),
* and isn't affected by an ancestor tab switch. */
prop.useDate = TitleBlockProperties::CurrentDate;
prop.date = QDate::currentDate();
}
+128 -4
View File
@@ -29,21 +29,52 @@
#include "../qetgraphicsitem/independenttextitem.h"
#include <QGraphicsItem>
#include <QtMath>
RotateSelectionCommand::RotateSelectionCommand(Diagram *diagram, qreal angle, QUndoCommand *parent) :
RotateSelectionCommand::RotateSelectionCommand(Diagram *diagram, qreal angle, QUndoCommand *parent, bool rotate_as_group) :
QUndoCommand(parent),
m_diagram(diagram)
{
setText(QObject::tr("Pivoter la selection"));
setText(rotate_as_group ? QObject::tr("Pivoter le groupe") : QObject::tr("Pivoter la selection"));
if(!m_diagram->isReadOnly())
{
/* Shared pivot for group rotation: the bounding-box centre of
* the whole selection, computed once up front (not just from
* the items that end up being individually repositioned
* below), then snapped to the grid.
*
* The snap is not cosmetic. sceneBoundingRect() is derived
* from font metrics and pen widths, so the raw centre is
* almost never a round number, and rotating a grid-aligned
* element around a fractional pivot moves it off the grid for
* good -- an element at x=100 lands at x=133.78, and no
* further rotation brings it back. Positions are saved with
* QString::number() (%.6g), which hides the floating-point
* noise but preserves the offset, so the diagram is left
* subtly misaligned with no way to repair it from the UI.
* Reported by plc-user from the same problem in the Element
* Editor, discussion #618.
*
* snapToGrid() follows the user's configured X/Y grid rather
* than assuming the 10 px default. */
QPointF pivot;
if (rotate_as_group)
{
QRectF bounding_rect;
for (QGraphicsItem *item : m_diagram->selectedItems())
bounding_rect |= item->sceneBoundingRect();
pivot = Diagram::snapToGrid(bounding_rect.center());
}
for (QGraphicsItem *item : m_diagram->selectedItems())
{
switch (item->type())
{
case Element::Type:
m_undo << new QPropertyUndoCommand(item->toGraphicsObject(), "rotation", QVariant(item->rotation()), QVariant(item->rotation()+angle), this);
if (rotate_as_group)
addGroupPositionUndo(item, pivot, angle);
break;
case ConductorTextItem::Type:
{
@@ -53,9 +84,19 @@ m_diagram(diagram)
break;
case IndependentTextItem::Type:
m_undo << new QPropertyUndoCommand(item->toGraphicsObject(), "rotation", QVariant(item->rotation()), QVariant(item->rotation()+angle), this);
if (rotate_as_group)
addGroupPositionUndo(item, pivot, angle);
break;
case DynamicElementTextItem::Type:
{
//No pos() undo here even in group mode: this item is
//only rotated in place when its parent Element isn't
//also selected (guard below), and its pos() is
//parent-local, not scene coordinates -- when the
//parent Element *is* selected and gets its own pos()
//rotated around the shared pivot above, this child
//text item is carried along for free by Qt's normal
//parent/child transform propagation.
if(item->parentItem() && !item->parentItem()->isSelected())
m_undo << new QPropertyUndoCommand(item->toGraphicsObject(), "rotation", QVariant(item->rotation()), QVariant(item->rotation()+angle), this);
}
@@ -69,17 +110,100 @@ m_diagram(diagram)
break;
case DiagramImageItem::Type:
m_undo << new QPropertyUndoCommand(item->toGraphicsObject(), "rotation", QVariant(item->rotation()), QVariant(item->rotation()+angle), this);
if (rotate_as_group)
addGroupPositionUndo(item, pivot, angle);
break;
default:
break;
}
}
for (QPropertyUndoCommand *undo : m_undo)
undo->setAnimated(true, false);
}
}
/**
@brief RotateSelectionCommand::addGroupPositionUndo
Queue a "pos" QPropertyUndoCommand that rotates @a item's position
around @a pivot by @a angle degrees (Qt's clockwise-positive
convention, matching QGraphicsItem::setRotation() so a group
rotation turns the same direction as each item's own spin).
Only meaningful for items whose pos() is in scene coordinates
(Element, IndependentTextItem, DiagramImageItem) -- never call this
for a child item positioned relative to its own parent.
@param item : item to reposition, its own rotation undo already queued
@param pivot : shared pivot point, in scene coordinates
@param angle : rotation angle in degrees
*/
void RotateSelectionCommand::addGroupPositionUndo(QGraphicsItem *item, const QPointF &pivot, qreal angle)
{
const QPointF old_pos = item->pos();
const QPointF delta = old_pos - pivot;
/* Exact arithmetic for the right angles instead of qCos()/qSin().
* The rotate actions only ever pass multiples of 90 degrees, and
* at 90 qCos() returns 6.12e-17 rather than 0, so the generic trig
* path introduces error for no benefit: rotating a point through
* four 90 degree steps would not return it to where it started.
* A quadrant is just an axis swap, which is exact. */
QPointF offset;
const int quadrant = qRound(angle / 90.0);
bool exact_quadrant = qFuzzyCompare(angle, quadrant * 90.0);
if (exact_quadrant)
{
switch (((quadrant % 4) + 4) % 4)
{
case 1: offset = QPointF(-delta.y(), delta.x()); break;
case 2: offset = QPointF(-delta.x(), -delta.y()); break;
case 3: offset = QPointF( delta.y(), -delta.x()); break;
default: offset = delta; break;
}
}
else
{
const qreal radians = qDegreesToRadians(angle);
offset = QPointF(
delta.x() * qCos(radians) - delta.y() * qSin(radians),
delta.x() * qSin(radians) + delta.y() * qCos(radians));
}
QPointF new_pos = pivot + offset;
if (exact_quadrant)
{
/* Swapping X/Y deltas for a 90/270 turn only stays on the
* user's configured grid if xGrid == yGrid. With an
* asymmetric grid (both independently configurable, 1-30 px,
* in Settings) a delta that was a clean multiple of xGrid
* lands on the Y axis after the swap, where the grid unit is
* yGrid -- and 10 is not a multiple of 7. Verified this
* drifts a grid-aligned point off-grid without this snap
* (e.g. xGrid=10/yGrid=7: (100,210) rotates to (225,295),
* x%10==5), and that adding it corrects exactly that case on
* a real build (same inputs land on x%10==0, y%7==0).
*
* For xGrid == yGrid, snapping an already-on-grid point is a
* no-op, so this leaves that case's arithmetic unchanged.
* It does NOT, on its own, guarantee that four consecutive
* 90-degree turns return a selection to its exact starting
* position even on a symmetric grid: each RotateSelectionCommand
* recomputes the pivot fresh from the selection's CURRENT
* sceneBoundingRect(), and an item whose bounding box isn't
* rotationally symmetric (e.g. a wide text label) reports a
* different box, and therefore a different box centre, at 0
* and 90 degrees. That drift is pre-existing -- verified
* identical with and without this change -- and a separate
* problem from the one this fixes: staying on-grid after
* every individual turn is the property that matters day to
* day; bit-exact round-tripping through several consecutive
* rotations is a different, harder guarantee this change
* does not attempt. */
new_pos = Diagram::snapToGrid(new_pos);
}
m_undo << new QPropertyUndoCommand(item->toGraphicsObject(), "pos", QVariant(old_pos), QVariant(new_pos), this);
}
/**
@brief RotateSelectionCommand::undo
*/
+5 -1
View File
@@ -21,10 +21,12 @@
#include <QUndoCommand>
#include <QPointer>
#include <QHash>
#include <QPointF>
class Diagram;
class ConductorTextItem;
class QPropertyUndoCommand;
class QGraphicsItem;
/**
@brief The RotateSelectionCommand class
@@ -33,13 +35,15 @@ class QPropertyUndoCommand;
class RotateSelectionCommand : public QUndoCommand
{
public:
RotateSelectionCommand(Diagram *diagram, qreal angle=90, QUndoCommand *parent=nullptr);
RotateSelectionCommand(Diagram *diagram, qreal angle=90, QUndoCommand *parent=nullptr, bool rotate_as_group=false);
void undo() override;
void redo() override;
bool isValid();
private:
void addGroupPositionUndo(QGraphicsItem *item, const QPointF &pivot, qreal angle);
Diagram *m_diagram =nullptr;
QList<QPointer<ConductorTextItem>> m_cond_text;