mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-08-13 10:04:13 +02:00
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:
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 &,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user