Merge remote-tracking branch 'upstream/master' into wiring-manager

Conflicts resolved in 24 files. Notable decisions:

- ico/icon-themes.qrc: upstream's icon folder restructure kept; the 36
  cable icon entries re-added under the new qet/qet-dark path scheme
  (XML valid, every referenced file exists).
- Toolbar system: upstream's DiagramToolbarSettings now owns the
  toolbar filling. The cable tool is registered as
  diagrameditor.add_cable (generic group loop) and listed on the
  "diagram" toolbar right after the auto-break conductor action,
  separated by a divider, exactly like before the merge.
- Paste: the cable lines of a copy are handed to
  PasteNumberingImport::push() as extra commands, so paste + cable
  lines + the numbering question stay ONE undo step on both paste
  paths (DiagramView::paste and DiagramEventAddPaste::commit).
- PDF links: upstream's deterministic link sorting kept together with
  the cable cross-reference injector (injectLinkTo -> links list).
- Element renumbering: upstream's ElementAutoNumSchemeCommand::renumber
  replaces the old formula-based body of
  QETProject::renumberElementsBySchemeTitle; the cable numbering rule
  API (cableAutoNum, axis priority, rule name) is kept in full.
- Auto-numbering pages: upstream's scheme management kept, the Câbles
  tab added as index 4 (changeToCableTab), both entry points
  changeToCable() and changeToElement() kept side by side.
- SelectAutonumW: dirty tracking and the contextEdited signal moved
  into upstream's insertPartRow() row API, so single-rule mode, the
  rule-removable button and "confirm without change leaves the project
  alone" keep working.
- xrefpropertieswidget: upstream's offset clamping kept; the cable
  branch (Texte field, Police button, hidden display options) kept;
  the stack-overlapping checkbox is hidden for cables.

Verified after the merge: build green (515/515, no errors), ctest
85/86 (only the pre-existing headless tst_menubarkeyboard failure),
offscreen smoke exit 124.
This commit is contained in:
Kellermorph
2026-10-09 14:09:08 +02:00
1767 changed files with 194544 additions and 72846 deletions
+12
View File
@@ -46,6 +46,9 @@ BOMExportDialog::BOMExportDialog(QETProject *project, QWidget *parent) :
m_query_widget = new ElementQueryWidget(this);
ui->m_main_layout->insertWidget(0, m_query_widget);
m_query_widget->setQuery(BomExport::defaultQuery());
//The contact blocks are parts of their own, as in the command line
//export; unchecking "Contacts esclaves" leaves them out.
m_query_widget->setSlavesIncluded(true);
on_m_format_as_bom_clicked(false);
m_preview_model = new QSqlQueryModel(this);
@@ -145,6 +148,15 @@ void BOMExportDialog::on_m_format_as_bom_clicked(bool checked) {
m_query_widget->setCount("COUNT(*) AS designation_qty", checked);
}
/**
@brief BOMExportDialog::on_m_no_junctions_clicked
@param checked : true leaves out the junctions, see
BomExport::junctionFilter()
*/
void BOMExportDialog::on_m_no_junctions_clicked(bool checked) {
m_query_widget->setExtraFilter(BomExport::junctionFilter(), checked);
}
/**
@brief BOMExportDialog::on_m_preview_pb_clicked
Run the current query and show its result live, without going through
+1
View File
@@ -45,6 +45,7 @@ class BOMExportDialog : public QDialog
private slots:
void on_m_format_as_bom_clicked(bool checked);
void on_m_no_junctions_clicked(bool checked);
void on_m_preview_pb_clicked();
private:
+13
View File
@@ -40,6 +40,19 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="m_no_junctions">
<property name="toolTip">
<string>Bornes sans repère, désignation, fabricant ni référence fabricant</string>
</property>
<property name="text">
<string>Laisser de côté les jonctions</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
+229
View File
@@ -18,8 +18,89 @@
#include "borderpropertieswidget.h"
#include "../diagram.h"
#include "../qetapp.h"
#include "../titleblocktemplate.h"
#include "ui_borderpropertieswidget.h"
#include <iterator>
#include <tuple>
namespace {
/// ISO 216 A sizes in mm, landscape
struct PaperSize { const char *name; int width_mm; int height_mm; };
const PaperSize paper_sizes[] = {
{"A0", 1189, 841},
{"A1", 841, 594},
{"A2", 594, 420},
{"A3", 420, 297},
{"A4", 297, 210},
{"A5", 210, 148}
};
/// The PDF export draws one scene unit as one pixel at 96 dpi
int mmToPx(int mm) { return qRound(mm * 96.0 / 25.4); }
/// The PDF export rounds a page within 3 pt of a paper size to that
/// size, and 4 px left over already rounds to 3.0 pt and misses
const int exact_fit = 3;
/// Smallest column width or row height a paper size may give
const int min_cell = 10;
/// Fewest columns or rows a folio may have: BorderTitleBlock raises
/// any lower count to 3 (MIN_COLUMN_COUNT, MIN_ROW_COUNT)
const int min_count = 3;
/**
@brief fitCells
Split @p available pixels into cells close to @p current pixels.
First choice: cells within 15 %, then 30 %, of the current size
that leave at most exact_fit pixels, the closest to the current
size. Failing that, cells within 15 % that leave the fewest pixels.
@return the number of cells and their size
*/
QPair<int, int> fitCells(int available, int current, int min_size, int max_count)
{
current = qMax(current, min_size);
auto size_for = [&](int count, double spread) {
const int size = available / count;
return (size < min_size || qAbs(size - current) > current * spread)
? 0 : size;
};
for (double spread : {0.15, 0.30})
{
QPair<int, int> best(0, 0);
for (int count = min_count ; count <= max_count ; ++count)
{
const int size = size_for(count, spread);
if (size && available - count * size <= exact_fit &&
(!best.first || qAbs(size - current) < qAbs(best.second - current)))
best = qMakePair(count, size);
}
if (best.first) return best;
}
QPair<int, int> best(0, 0);
std::tuple<int, int> best_score(available, available);
for (int count = min_count ; count <= max_count ; ++count)
{
const int size = size_for(count, 0.15);
const std::tuple<int, int> score(available - count * size,
qAbs(size - current));
if (size && score < best_score) {
best_score = score;
best = qMakePair(count, size);
}
}
if (best.first) return best;
//No size near the current one when the cell count is capped
const int count = qBound(min_count, qRound(double(available) / current), max_count);
return qMakePair(count, available / count);
}
}
/**
@brief BorderPropertiesWidget::BorderPropertiesWidget
default constructor
@@ -31,6 +112,30 @@ BorderPropertiesWidget::BorderPropertiesWidget(const BorderProperties &bp, QWidg
ui(new Ui::BorderPropertiesWidget)
{
ui->setupUi(this);
ui->m_paper_cb->addItem(tr("Personnalisé"));
for (const PaperSize &paper : paper_sizes)
ui->m_paper_cb->addItem(QString::fromLatin1(paper.name));
ui->m_orientation_cb->addItem(tr("Paysage"));
ui->m_orientation_cb->addItem(tr("Portrait"));
if (TitleBlockTemplate *tbt = QETApp::defaultTitleBlockTemplate())
m_titleblock_height = tbt->height();
//activated() is only emitted for a choice the user makes
auto paper_picked = [this]() {
m_paper_picked = ui->m_paper_cb->currentIndex() > 0;
applyPaperSize();
};
connect(ui->m_paper_cb, qOverload<int>(&QComboBox::activated),
this, paper_picked);
connect(ui->m_orientation_cb, qOverload<int>(&QComboBox::activated),
this, paper_picked);
for (QSpinBox *sb : {ui->m_colums_count_sp, ui->m_columns_width_sp,
ui->m_rows_count_sp, ui->m_rows_height_sp})
connect(sb, qOverload<int>(&QSpinBox::valueChanged),
this, &BorderPropertiesWidget::showMatchingPaperSize);
setProperties(bp);
}
@@ -57,6 +162,7 @@ void BorderPropertiesWidget::setProperties(const BorderProperties &bp)
ui -> m_rows_count_sp ->setValue (m_properties.rows_count);
ui -> m_rows_height_sp ->setValue (m_properties.rows_height);
ui -> m_display_rows_cb ->setChecked (m_properties.display_rows);
showMatchingPaperSize();
}
/**
@@ -83,3 +189,126 @@ void BorderPropertiesWidget::setReadOnly(const bool &ro)
{
ui->border_gb->setDisabled(ro);
}
/**
@brief BorderPropertiesWidget::setTitleBlockSize
Tell the widget the title block the folio uses: its height counts in
the size of the page. If the user picked a paper size in this widget,
the columns and rows are fitted again so the folio keeps that size;
otherwise they are left alone.
@param height : height of the title block template
@param edge : edge of the folio the title block sits on
*/
void BorderPropertiesWidget::setTitleBlockSize(int height, Qt::Edge edge)
{
m_titleblock_height = height;
m_titleblock_edge = edge;
if (m_paper_picked)
applyPaperSize();
else
showMatchingPaperSize();
}
/**
@brief BorderPropertiesWidget::applyPaperSize
Set the columns and rows so the folio, title block included, has the
size of the chosen paper. The column width and row height stay close
to the current ones.
*/
void BorderPropertiesWidget::applyPaperSize()
{
const int index = ui->m_paper_cb->currentIndex() - 1;
ui->m_orientation_cb->setEnabled(index >= 0);
if (index < 0) return;
int columns, column_width, rows, row_height;
if (!fitPaperSize(index, ui->m_orientation_cb->currentIndex() == 0,
columns, column_width, rows, row_height))
{
//The title block leaves no room on this paper
showMatchingPaperSize();
return;
}
m_updating = true;
ui->m_colums_count_sp->setValue(columns);
ui->m_columns_width_sp->setValue(column_width);
ui->m_rows_count_sp->setValue(rows);
ui->m_rows_height_sp->setValue(row_height);
m_updating = false;
}
/**
@brief BorderPropertiesWidget::fitPaperSize
Compute the columns and rows that give the folio, title block
included, the size of a paper, with cells close to the current ones.
@param index : index of the paper in paper_sizes
@param landscape : true for landscape, false for portrait
@return false if the paper is too small for the title block
*/
bool BorderPropertiesWidget::fitPaperSize(int index, bool landscape,
int &columns, int &column_width,
int &rows, int &row_height) const
{
const PaperSize &paper = paper_sizes[index];
const int page_width = mmToPx(landscape ? paper.width_mm : paper.height_mm);
const int page_height = mmToPx(landscape ? paper.height_mm : paper.width_mm);
//The page is one pixel larger than the border, for its line
int width = page_width - 1 - qRound(m_properties.rows_header_width);
int height = page_height - 1 - qRound(m_properties.columns_header_height);
if (m_titleblock_edge == Qt::BottomEdge)
height -= m_titleblock_height;
else
width -= m_titleblock_height;
if (width < min_cell * min_count || height < min_cell * min_count)
return false;
const QPair<int, int> c = fitCells(width,
ui->m_columns_width_sp->value(),
min_cell,
ui->m_colums_count_sp->maximum());
const QPair<int, int> r = fitCells(height,
ui->m_rows_height_sp->value(),
min_cell,
ui->m_rows_count_sp->maximum());
columns = c.first;
column_width = c.second;
rows = r.first;
row_height = r.second;
return true;
}
/**
@brief BorderPropertiesWidget::showMatchingPaperSize
Show the paper size the folio has, or "Custom" if it has none: the
folio has a paper size when choosing it would change nothing.
*/
void BorderPropertiesWidget::showMatchingPaperSize()
{
if (m_updating) return;
m_paper_picked = false;
int found = 0;
int orientation = ui->m_orientation_cb->currentIndex();
for (int i = 0 ; i < int(std::size(paper_sizes)) && !found ; ++i)
{
for (int o = 0 ; o < 2 ; ++o)
{
int columns, column_width, rows, row_height;
if (fitPaperSize(i, o == 0, columns, column_width, rows, row_height) &&
columns == ui->m_colums_count_sp->value() &&
column_width == ui->m_columns_width_sp->value() &&
rows == ui->m_rows_count_sp->value() &&
row_height == ui->m_rows_height_sp->value())
{
found = i + 1;
orientation = o;
break;
}
}
}
ui->m_paper_cb->setCurrentIndex(found);
ui->m_orientation_cb->setCurrentIndex(orientation);
ui->m_orientation_cb->setEnabled(found > 0);
}
+16 -2
View File
@@ -41,12 +41,26 @@ class BorderPropertiesWidget : public QWidget
void setProperties(const BorderProperties &bp);
const BorderProperties &properties();
void setReadOnly (const bool &ro);
private slots:
void setTitleBlockSize(int height, Qt::Edge edge);
private:
void applyPaperSize();
void showMatchingPaperSize();
bool fitPaperSize(int index, bool landscape,
int &columns, int &column_width,
int &rows, int &row_height) const;
Ui::BorderPropertiesWidget *ui;
BorderProperties m_properties;
/// height of the title block the folio is drawn with, and
/// the edge it sits on: it counts in the size of the page
int m_titleblock_height = 0;
Qt::Edge m_titleblock_edge = Qt::BottomEdge;
/// true while the widget itself changes the spin boxes
bool m_updating = false;
/// true once the user picked a paper size, until the columns or
/// rows are edited by hand
bool m_paper_picked = false;
};
#endif // BORDERPROPERTIESWIDGET_H
+19
View File
@@ -80,7 +80,24 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="m_paper_label">
<property name="text">
<string>Format :</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="m_paper_cb">
<property name="toolTip">
<string>Règle les colonnes et les lignes pour que le folio, cartouche compris, ait la taille de ce format de papier, à 96 points par pouce comme l'export PDF.</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QComboBox" name="m_orientation_cb"/>
</item>
<item row="3" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -105,6 +122,8 @@
<tabstop>m_rows_height_sp</tabstop>
<tabstop>m_display_columns_cb</tabstop>
<tabstop>m_display_rows_cb</tabstop>
<tabstop>m_paper_cb</tabstop>
<tabstop>m_orientation_cb</tabstop>
</tabstops>
<resources/>
<connections/>
+2 -2
View File
@@ -78,9 +78,9 @@ void CompositeTextEditDialog::setUpComboBox(bool is_report)
}
}
void CompositeTextEditDialog::on_m_info_cb_activated(const QString &arg1)
void CompositeTextEditDialog::on_m_info_cb_activated(int index)
{
Q_UNUSED(arg1)
Q_UNUSED(index)
ui->m_plain_text_edit->insertPlainText(ui->m_info_cb->currentData().toString());
}
+1 -1
View File
@@ -28,7 +28,7 @@ class CompositeTextEditDialog : public QDialog
QString plainText() const;
private slots:
void on_m_info_cb_activated(const QString &arg1);
void on_m_info_cb_activated(int index);
protected:
void focusInEvent(QFocusEvent *event) override;
+1
View File
@@ -220,6 +220,7 @@ void ConductorColorToolButton::chooseOtherColor()
{
QColorDialog dialog(m_current, this);
dialog.setWindowTitle(tr("Choisir une couleur de conducteur"));
dialog.setOption(QColorDialog::DontUseNativeDialog);
ColorDialogDoubleClick::install(&dialog);
if (dialog.exec() == QDialog::Accepted && dialog.selectedColor().isValid()) {
applyColor(dialog.selectedColor());
+116 -64
View File
@@ -17,7 +17,7 @@
*/
#include "conductorpropertieseditorwidget.h"
#include "../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../conductormultiedit.h"
#include "../diagram.h"
#include "../qetgraphicsitem/conductor.h"
#include "conductorpropertieswidget.h"
@@ -35,6 +35,7 @@
#include <QComboBox>
#include <QGroupBox>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
#include <QScrollArea>
#include <QSettings>
@@ -44,11 +45,11 @@
/**
@brief ConductorPropertiesEditorWidget::ConductorPropertiesEditorWidget
@param conductor : conductor to edit
@param conductors : conductors to edit
@param parent : parent widget
*/
ConductorPropertiesEditorWidget::ConductorPropertiesEditorWidget(
Conductor *conductor, QWidget *parent) :
const QList<Conductor *> &conductors, QWidget *parent) :
PropertiesEditorWidget(parent),
m_cpw(new ConductorPropertiesWidget(this))
{
@@ -76,12 +77,19 @@ ConductorPropertiesEditorWidget::ConductorPropertiesEditorWidget(
QStringLiteral("diagrameditor/conductor_apply_all"), on);
});
//Shown only when several conductors are selected: the fields hold
//the first one's values, and only the edited ones reach the others.
m_count_label = new QLabel(this);
m_count_label->setWordWrap(true);
m_count_label->hide();
auto *scroll = new QScrollArea(this);
scroll->setWidgetResizable(true);
scroll->setFrameShape(QFrame::NoFrame);
scroll->setWidget(m_cpw);
auto *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(m_count_label);
layout->addWidget(m_apply_all_cb);
layout->addWidget(scroll);
setMinimumWidth(120);
@@ -100,7 +108,7 @@ ConductorPropertiesEditorWidget::ConductorPropertiesEditorWidget(
gb->installEventFilter(this);
setDisabled(true);
setConductor(conductor);
setConductors(conductors);
}
/**
@@ -123,26 +131,64 @@ ConductorPropertiesEditorWidget::~ConductorPropertiesEditorWidget()
{}
/**
@brief ConductorPropertiesEditorWidget::setConductor
Set (or change) the conductor whose properties are edited.
@param conductor
@brief ConductorPropertiesEditorWidget::setConductors
Set (or change) the conductors whose properties are edited.
@param conductors
*/
void ConductorPropertiesEditorWidget::setConductor(Conductor *conductor)
void ConductorPropertiesEditorWidget::setConductors(
const QList<Conductor *> &conductors)
{
if (!conductor) return;
if (m_conductor && m_conductor != conductor)
disconnect(m_conductor, &Conductor::propertiesChange,
this, &ConductorPropertiesEditorWidget::updateUi);
m_conductor = conductor;
//Keep the dock in sync when the conductor is edited elsewhere (e.g. the
//modal "Edit conductor" dialog); otherwise a stale snapshot would be
//written back on the next apply() and overwrite that change (issue #500).
connect(m_conductor, &Conductor::propertiesChange,
this, &ConductorPropertiesEditorWidget::updateUi, Qt::UniqueConnection);
if (conductors.isEmpty()) return;
for (const QPointer<Conductor> &c : std::as_const(m_conductors))
if (c)
disconnect(c, &Conductor::propertiesChange,
this, &ConductorPropertiesEditorWidget::scheduleUpdateUi);
m_conductors.clear();
//The scene gives no order for its selection: sort, so the same
//wires always show the same "first" one.
QList<Conductor *> sorted = conductors;
ConductorMultiEdit::sortByPosition(sorted, [](Conductor *c) {
return c->sceneBoundingRect().topLeft();
});
for (Conductor *c : std::as_const(sorted))
{
m_conductors << c;
//Keep the dock in sync when a conductor is edited elsewhere (e.g.
//the modal "Edit conductor" dialog); otherwise a stale snapshot
//would be written back on the next apply() and overwrite that
//change (issue #500). One edit of N conductors emits N signals:
//they are gathered into one reload.
connect(c, &Conductor::propertiesChange,
this, &ConductorPropertiesEditorWidget::scheduleUpdateUi,
Qt::UniqueConnection);
}
const int count = conductors.size();
m_count_label->setText(
tr("%n conducteurs sélectionnés : seuls les champs modifiés leur sont appliqués.",
"selection properties panel", count));
m_count_label->setVisible(count > 1);
setEnabled(true);
updateUi();
}
/**
@brief ConductorPropertiesEditorWidget::firstConductor
@return the first edited conductor still alive, its values are the ones
shown, or nullptr
*/
Conductor *ConductorPropertiesEditorWidget::firstConductor() const
{
for (const QPointer<Conductor> &c : m_conductors)
if (c)
return c;
return nullptr;
}
/**
@brief ConductorPropertiesEditorWidget::apply
Push the edit onto the diagram's undo stack.
@@ -153,10 +199,25 @@ void ConductorPropertiesEditorWidget::apply()
// programmatically (updateUi/reset): mid-load the widget holds a partial
// state that must not be committed onto the conductor.
if (m_updating) return;
if (!m_conductor || !m_conductor->diagram()) return;
Conductor *first = firstConductor();
if (!first || !first->diagram()) return;
if (QUndoCommand *undo = associatedUndo())
m_conductor->diagram()->undoStack().push(undo);
m_shown = m_cpw->properties();
first->diagram()->undoStack().push(undo);
updateUi();
}
/**
@brief ConductorPropertiesEditorWidget::scheduleUpdateUi
Reload the widget once the current event is done, however many
conductors changed in it.
*/
void ConductorPropertiesEditorWidget::scheduleUpdateUi()
{
if (m_update_pending) return;
m_update_pending = true;
QMetaObject::invokeMethod(this, [this]() {
if (m_update_pending) updateUi();
}, Qt::QueuedConnection);
}
/**
@@ -242,7 +303,7 @@ void ConductorPropertiesEditorWidget::disconnectChangeSignals()
*/
void ConductorPropertiesEditorWidget::reset()
{
if (!m_conductor) return;
if (!firstConductor()) return;
m_updating = true;
m_cpw->setProperties(m_shown);
m_updating = false;
@@ -250,13 +311,24 @@ void ConductorPropertiesEditorWidget::reset()
/**
@brief ConductorPropertiesEditorWidget::updateUi
Reload the widget from the conductor (e.g. when the selection changes).
Reload the widget from the conductors (e.g. when the selection
changes): the first one's values, with the text fields they do not
agree on left blank.
*/
void ConductorPropertiesEditorWidget::updateUi()
{
if (!m_conductor) return;
m_update_pending = false;
QList<ConductorProperties> list;
for (const QPointer<Conductor> &c : std::as_const(m_conductors))
if (c)
list << c->properties();
if (list.isEmpty()) return;
const auto mixed = ConductorMultiEdit::mixedTextFields(list);
m_updating = true;
m_cpw->setProperties(m_conductor->properties());
m_cpw->setProperties(ConductorMultiEdit::shown(list, mixed));
m_cpw->setMixedTextFields(mixed);
m_cpw->setTextLocked(list.size() > 1);
//Read back rather than keep the conductor's own values: a value the
//widget cannot show exactly must not count as an edit.
m_shown = m_cpw->properties();
@@ -267,51 +339,31 @@ void ConductorPropertiesEditorWidget::updateUi()
@brief ConductorPropertiesEditorWidget::associatedUndo
@return the edit as one undo step, or nullptr if nothing changes.
Only the fields the user changed are written: the conductor keeps every
other value, including one the widget cannot show exactly.
Only the fields the user changed are applied, to each edited conductor:
with several conductors selected, each keeps its own text, function,
cable... unless that is the field being edited.
When "apply to all" is ticked, every conductor on the same potential is
updated too, in the same undo step (one undo reverts them all), as the
modal dialog does (ConductorPropertiesDialog::PropertiesDialog).
When "apply to all" is ticked, every conductor on the same potential as
an edited one is updated too, in the same undo step (one undo reverts
them all), as the modal dialog does
(ConductorPropertiesDialog::PropertiesDialog).
*/
QUndoCommand *ConductorPropertiesEditorWidget::associatedUndo() const
{
if (!m_conductor) return nullptr;
//Most calls are a field losing focus with nothing edited: answer
//before walking the potentials.
const ConductorProperties edited = m_cpw->properties();
if (edited == m_shown) return nullptr;
const ConductorProperties new_properties = m_cpw->properties();
if (new_properties == m_shown) return nullptr;
QList<Conductor *> selected;
for (const QPointer<Conductor> &c : m_conductors)
if (c)
selected << c.data();
QList<Conductor *> targets {m_conductor};
if (m_apply_all_cb && m_apply_all_cb->isChecked())
for (Conductor *potential_conductor : m_conductor->relatedPotentialConductors())
if (!targets.contains(potential_conductor))
targets << potential_conductor;
auto *undo = new QUndoCommand();
int changed = 0;
for (Conductor *conductor : std::as_const(targets))
{
const ConductorProperties old_properties = conductor->properties();
ConductorProperties properties = old_properties;
properties.applyChanges(m_shown, new_properties);
if (properties == old_properties) continue;
QVariant old_value, new_value;
old_value.setValue(old_properties);
new_value.setValue(properties);
new QPropertyUndoCommand(conductor, "properties", old_value, new_value, undo);
++changed;
}
if (!changed)
{
delete undo;
return nullptr;
}
undo->setText(changed == 1
? tr("Modifier les propriétés d'un conducteur", "undo caption")
: tr("Modifier les propriétés de plusieurs conducteurs", "undo caption"));
return undo;
return ConductorMultiEdit::undo(
ConductorMultiEdit::targets(
selected, m_apply_all_cb && m_apply_all_cb->isChecked()),
m_shown, edited);
}
/**
+13 -4
View File
@@ -23,18 +23,22 @@
#include <QList>
#include <QMetaObject>
#include <QPointer>
class Conductor;
class ConductorPropertiesWidget;
class QCheckBox;
class QLabel;
/**
@brief The ConductorPropertiesEditorWidget class
Hosts the existing ConductorPropertiesWidget in the dockable selection-
properties panel, so a selected conductor can be edited in place like the
properties panel, so selected conductors can be edited in place like the
other item types, instead of only through the modal dialog (issue #500).
A pinned "apply to all conductors of the potential" checkbox (persisted)
mirrors the modal dialog's option to propagate edits to the whole potential.
When several conductors are selected, the panel shows the first one and
applies to each only the fields the user changed.
*/
class ConductorPropertiesEditorWidget : public PropertiesEditorWidget
{
@@ -42,10 +46,11 @@ class ConductorPropertiesEditorWidget : public PropertiesEditorWidget
public:
explicit ConductorPropertiesEditorWidget(
Conductor *conductor = nullptr, QWidget *parent = nullptr);
const QList<Conductor *> &conductors = {},
QWidget *parent = nullptr);
~ConductorPropertiesEditorWidget() override;
void setConductor(Conductor *conductor);
void setConductors(const QList<Conductor *> &conductors);
void apply() override;
void reset() override;
@@ -60,16 +65,20 @@ class ConductorPropertiesEditorWidget : public PropertiesEditorWidget
private:
void connectChangeSignals();
void disconnectChangeSignals();
void scheduleUpdateUi();
Conductor *firstConductor() const;
private:
ConductorPropertiesWidget *m_cpw = nullptr;
QCheckBox *m_apply_all_cb = nullptr;
Conductor *m_conductor = nullptr;
QLabel *m_count_label = nullptr;
QList<QPointer<Conductor>> m_conductors;
//What the widget showed before the edit: the fields that
//differ from it are the ones the user changed.
ConductorProperties m_shown;
QList<QMetaObject::Connection> m_live_connections;
bool m_updating = false;
bool m_update_pending = false;
};
#endif // CONDUCTORPROPERTIESEDITORWIDGET_H
+55 -1
View File
@@ -126,6 +126,12 @@ ConductorProperties ConductorPropertiesWidget::properties() const
properties_.m_bicolor = ui->m_color_2_gb->isChecked();
properties_.m_color_2 = ui->m_color_2_kpb->color();
properties_.m_dash_size = ui->m_dash_size_sb->value();
//The box starts at 2, so the default size 1 shows as 2: keep the
//stored size unless the box shows something else.
if (properties_.m_dash_size == qBound(ui->m_dash_size_sb->minimum(),
m_properties.m_dash_size,
ui->m_dash_size_sb->maximum()))
properties_.m_dash_size = m_properties.m_dash_size;
properties_.style = ui -> m_line_style_cb->itemData(ui->m_line_style_cb->currentIndex()).value<QPen>().style();
properties_.m_formula = ui->m_formula_le->text();
properties_.text = ui -> m_text_le -> text();
@@ -191,12 +197,60 @@ void ConductorPropertiesWidget::setHiddenOneTextPerFolio(const bool &hide) {
ui -> m_one_text_per_folio_cb -> setHidden(hide);
}
/**
@brief ConductorPropertiesWidget::setMixedTextFields
Mark the text fields whose value differs between several edited
conductors: they are shown blank, with a hint saying why.
@param mixed
*/
void ConductorPropertiesWidget::setMixedTextFields(
const QList<ConductorMultiEdit::TextField> &mixed)
{
using namespace ConductorMultiEdit;
const QList<QPair<TextField, QLineEdit *>> edits {
{Text, ui->m_text_le},
{Formula, ui->m_formula_le},
{Function, ui->m_function_le},
{TensionProtocol, ui->m_tension_protocol_le},
{WireColor, ui->m_wire_color_le},
{WireSection, ui->m_wire_section_le},
{Cable, ui->m_cable_le},
{Bus, ui->m_bus_le}};
for (const auto &edit : edits)
edit.second->setPlaceholderText(mixed.contains(edit.first)
? tr("Plusieurs valeurs", "several conductors, different values")
: QString());
}
/**
@brief ConductorPropertiesWidget::setTextLocked
Lock the conductor text while several conductors are edited at once:
the same number on all of them would make duplicates.
@param locked
*/
void ConductorPropertiesWidget::setTextLocked(bool locked)
{
m_text_locked = locked;
ui->m_text_le->setEnabled(!locked && ui->m_formula_le->text().isEmpty());
ui->m_text_le->setToolTip(locked
? tr("Plusieurs conducteurs sélectionnés : le texte se modifie "
"sur un seul conducteur à la fois.")
: tr("Texte"));
}
/**
@brief ConductorPropertiesWidget::setDisabledShowText
@param disable
*/
void ConductorPropertiesWidget::setDisabledShowText(const bool &disable) {
ui->m_show_text_cb->setDisabled(disable==true? true : false);
//Say why the box is locked: the setting that locks it is not in this dialog
ui->m_show_text_cb->setToolTip(disable
? tr("Texte visible\n"
"Verrouillé par l'option « Afficher un texte de potentiel par "
"folio » de ce folio, dans Propriétés du folio.")
: tr("Texte visible"));
}
/**
@@ -239,7 +293,7 @@ void ConductorPropertiesWidget::initWidget()
connect(ui->m_multiwires_gb, &QGroupBox::toggled, [this](bool toggle) {this->ui->m_singlewire_gb->setChecked(!toggle);});
connect(ui->m_singlewire_gb, &QGroupBox::toggled, [this](bool toggle) {this->ui->m_multiwires_gb->setChecked(!toggle);});
connect(ui->m_formula_le, &QLineEdit::textChanged, [this](QString text) {this->ui->m_text_le->setEnabled(text.isEmpty());});
connect(ui->m_formula_le, &QLineEdit::textChanged, [this](QString text) {this->ui->m_text_le->setEnabled(!m_text_locked && text.isEmpty());});
ui->m_multiwires_gb->setChecked(true);
ui->m_singlewire_gb->setChecked(true);
#if TODO_LIST
+4
View File
@@ -18,6 +18,7 @@
#ifndef CONDUCTORPROPERTIESWIDGET_H
#define CONDUCTORPROPERTIESWIDGET_H
#include "../conductormultiedit.h"
#include "../conductorproperties.h"
#include <QWidget>
@@ -49,6 +50,8 @@ class ConductorPropertiesWidget : public QWidget
void setHiddenOneTextPerFolio (const bool &hide);
void setDisabledShowText (const bool &disable = true);
void setHiddenAvailableAutonum (const bool &hide);
void setMixedTextFields(const QList<ConductorMultiEdit::TextField> &mixed);
void setTextLocked(bool locked);
QComboBox *autonumComboBox() const;
QPushButton *editAutonumPushButton() const;
@@ -72,6 +75,7 @@ class ConductorPropertiesWidget : public QWidget
Ui::ConductorPropertiesWidget *ui;
ConductorProperties m_properties;
QTextOrientationSpinBoxWidget *m_verti_select, *m_horiz_select;
bool m_text_locked = false;
};
#endif // CONDUCTORPROPERTIESWIDGET_H
+1 -2
View File
@@ -233,8 +233,7 @@
<string/>
</property>
<property name="icon">
<iconset theme="configure" resource="../../qelectrotech.qrc">
<normaloff>:/ico/16x16/configure.png</normaloff>:/ico/16x16/configure.png</iconset>
<iconset theme="configure"/>
</property>
</widget>
</item>
+8 -2
View File
@@ -146,6 +146,10 @@ NewDiagramPage::NewDiagramPage(QETProject *project,
}
connect(ipw, &TitleBlockPropertiesWidget::openAutoNumFolioEditor, this, &NewDiagramPage::changeToAutoFolioTab);
bpw -> setTitleBlockSize(ipw -> currentTitleBlockHeight(),
ipw -> currentTitleBlockEdge());
connect(ipw, &TitleBlockPropertiesWidget::titleBlockSizeChanged,
bpw, &BorderPropertiesWidget::setTitleBlockSize);
// main tab widget
QTabWidget *tab_widget = new QTabWidget(this);
@@ -241,8 +245,10 @@ void NewDiagramPage::applyConf()
pg.color = g.color;
proj_guides.append(pg);
}
m_project->setDefaultGuides(proj_guides);
modified_project = true;
if (m_project->defaultGuides() != proj_guides) {
m_project->setDefaultGuides(proj_guides);
modified_project = true;
}
if (modified_project) {
m_project -> setModified(modified_project);
@@ -16,6 +16,8 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "generalconfigurationpage.h"
#include "../../scripting/liveserver.h"
#include "../../scripting/assistantinfo.h"
#include "../../qetapp.h"
#include "../../qeticons.h"
@@ -26,6 +28,8 @@
#include "../../utils/qetutils.h"
#include "../../qetmessagebox.h"
#include "../../textgrid.h"
#include "../../wiringrules.h"
#include "../wiringruleswarning.h"
#include "../../editor/terminalnamecheck.h"
#include "../../ElementsCollection/qetlabelsfile.h"
#include "../prefixconfigurationdialog.h"
@@ -142,8 +146,23 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) :
ui->m_use_windows_mode_rb->setChecked(true);
ui->m_zoom_out_beyond_folio->setChecked(settings.value("diagrameditor/zoom-out-beyond-of-folio", false).toBool());
ui->m_conductor_properties_panel->setChecked(settings.value("diagrameditor/conductor_properties_panel", false).toBool());
ui->m_wiring_rules_cb->setChecked(WiringRules::masterEnabled());
{
//The rules every project follows unless it sets its own (#1158)
const WiringRules::Settings rules = WiringRules::applicationSettings();
ui->m_wiring_max_wires_sb->setValue(rules.max_wires);
ui->m_wiring_one_wire_per_report_cb->setChecked(rules.one_wire_per_report);
auto enable = [this](bool on) {
ui->m_wiring_max_wires_label->setEnabled(on);
ui->m_wiring_max_wires_sb->setEnabled(on);
ui->m_wiring_one_wire_per_report_cb->setEnabled(on);
};
enable(ui->m_wiring_rules_cb->isChecked());
connect(ui->m_wiring_rules_cb, &QCheckBox::toggled, this, enable);
}
ui->m_use_gesture_trackpad->setChecked(settings.value("diagramview/gestures", false).toBool());
ui->m_save_label_paste->setChecked(settings.value("diagramcommands/erase-label-on-copy", true).toBool());
ui->m_autonumber_pasted->setChecked(settings.value("diagramcommands/autonumber-pasted-elements", true).toBool());
ui->m_enable_scripting->setChecked(QetSettings::scriptingEnabled());
#ifdef QET_HAS_SCRIPTING
if (QetSettings::scriptingForcedByEnvironment()) {
@@ -162,6 +181,11 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) :
//quietly clear a preference set on a build that does have Qml.
ui->m_enable_scripting->setVisible(false);
ui->m_enable_scripting->setEnabled(false);
#endif
ui->m_live_assistant->setChecked(QetSettings::liveAssistantEnabled());
#ifndef QET_HAS_SCRIPTING
ui->m_live_assistant->setVisible(false);
ui->m_live_assistant->setEnabled(false);
#endif
ui->m_use_folio_label->setChecked(settings.value("genericpanel/folio", true).toBool());
ui->m_border_0->setChecked(settings.value("border-columns_0", false).toBool());
@@ -352,6 +376,7 @@ void GeneralConfigurationPage::applyConf()
//DIAGRAM COMMAND
settings.setValue("diagramcommands/erase-label-on-copy", ui->m_save_label_paste->isChecked());
settings.setValue("diagramcommands/autonumber-pasted-elements", ui->m_autonumber_pasted->isChecked());
//SCRIPTING
//Left alone while the environment forces it on: the box is disabled
@@ -360,6 +385,15 @@ void GeneralConfigurationPage::applyConf()
if (ui->m_enable_scripting->isEnabled()) {
QetSettings::setScriptingEnabled(ui->m_enable_scripting->isChecked());
}
if (ui->m_live_assistant->isEnabled()) {
QetSettings::setLiveAssistantEnabled(ui->m_live_assistant->isChecked());
#ifdef QET_HAS_SCRIPTING
//Switching it off closes the door now, not at the next start
if (!ui->m_live_assistant->isChecked()) LiveServer::instance().stop();
#endif
}
//What an assistant reads about this QElectroTech follows the change
AssistantInfo::write();
//GENERIC PANEL
settings.setValue("genericpanel/folio",ui->m_use_folio_label->isChecked());
@@ -371,6 +405,17 @@ void GeneralConfigurationPage::applyConf()
settings.setValue("diagrameditor/highlight-integrated-elements", ui->m_highlight_integrated_elements->isChecked());
settings.setValue("diagrameditor/zoom-out-beyond-of-folio", ui->m_zoom_out_beyond_folio->isChecked());
settings.setValue("diagrameditor/conductor_properties_panel", ui->m_conductor_properties_panel->isChecked());
WiringRules::setMasterEnabled(ui->m_wiring_rules_cb->isChecked());
{
const WiringRules::Settings before = WiringRules::applicationSettings();
WiringRules::Settings rules = before;
rules.max_wires = ui->m_wiring_max_wires_sb->value();
rules.one_wire_per_report = ui->m_wiring_one_wire_per_report_cb->isChecked();
WiringRules::setApplicationSettings(rules);
if (WiringRules::masterEnabled() && WiringRules::turnsRuleOn(before, rules)) {
WiringRulesWarning::show(this);
}
}
settings.setValue("diagrameditor/autosave-interval", ui->m_autosave_sb->value());
settings.setValue("diagrameditor/grid_display_startup", ui->grid_startup_cb->isChecked());
@@ -70,6 +70,76 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="m_wiring_rules_cb">
<property name="toolTip">
<string>Décoché, aucune règle de conducteurs par borne n'est appliquée, quels que soient les réglages des projets.</string>
</property>
<property name="text">
<string>Appliquer les règles de conducteurs par borne des projets</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="m_wiring_max_wires_layout">
<property name="leftMargin">
<number>20</number>
</property>
<item>
<widget class="QLabel" name="m_wiring_max_wires_label">
<property name="text">
<string>Nombre maximal de conducteurs par borne :</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="m_wiring_max_wires_sb">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Pour tous les projets, sauf ceux qui ont leurs propres réglages. Un nouveau conducteur qui dépasserait ce nombre sur une borne est refusé ; les conducteurs déjà dessinés ne sont pas modifiés. 4 correspond à deux embouts doubles, un de chaque côté de la vis.</string>
</property>
<property name="specialValueText">
<string>Sans limite</string>
</property>
<property name="maximum">
<number>99</number>
</property>
</widget>
</item>
<item>
<spacer name="m_wiring_max_wires_spacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="m_wiring_report_layout">
<property name="leftMargin">
<number>20</number>
</property>
<item>
<widget class="QCheckBox" name="m_wiring_one_wire_per_report_cb">
<property name="toolTip">
<string>Un renvoi de folio est un point virtuel : il ne reçoit qu'un conducteur, celui qui continue sur l'autre folio.</string>
</property>
<property name="text">
<string>Un seul conducteur par renvoi de folio</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
@@ -257,6 +327,16 @@
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="m_autonumber_pasted">
<property name="text">
<string>Numéroter automatiquement les éléments collés ou dupliqués qui suivent une numérotation</string>
</property>
<property name="toolTip">
<string>Un élément collé reçoit le numéro suivant de sa numérotation au lieu du nom de l'élément copié, et la numérotation avance.</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="m_save_label_paste">
<property name="text">
@@ -275,6 +355,16 @@
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="m_live_assistant">
<property name="text">
<string>Autoriser un assistant IA à agir sur le projet ouvert (mode direct)</string>
</property>
<property name="toolTip">
<string>Un assistant connecté par le serveur MCP peut alors exécuter des scripts sur le projet ouvert, sous vos yeux. Un avertissement est affiché à chaque démarrage tant que ce réglage est activé. Prend effet au prochain démarrage ; le décocher coupe la connexion tout de suite.</string>
</property>
</widget>
</item>
<item row="7" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -1435,9 +1525,13 @@ Vous pouvez spécifier ici la valeur par défaut de ce champ pour les éléments
<tabstop>m_use_gesture_trackpad</tabstop>
<tabstop>m_zoom_out_beyond_folio</tabstop>
<tabstop>m_conductor_properties_panel</tabstop>
<tabstop>m_wiring_rules_cb</tabstop>
<tabstop>m_wiring_max_wires_sb</tabstop>
<tabstop>m_wiring_one_wire_per_report_cb</tabstop>
<tabstop>m_use_windows_mode_rb</tabstop>
<tabstop>m_use_tab_mode_rb</tabstop>
<tabstop>m_save_label_paste</tabstop>
<tabstop>m_autonumber_pasted</tabstop>
<tabstop>m_use_folio_label</tabstop>
<tabstop>m_border_0</tabstop>
<tabstop>m_autosave_sb</tabstop>
@@ -0,0 +1,475 @@
/*
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 "gesturesconfigpage.h"
#include "../../gesturesettings.h"
#include "../../qeticons.h"
#include "../../shortcutmanager.h"
#include <QAction>
#include <QComboBox>
#include <QDataStream>
#include <QDragEnterEvent>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
#include <QMimeData>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QPainter>
#include <QPainterPath>
#include <QPushButton>
#include <QVBoxLayout>
#include <QtMath>
namespace {
const int ring_radius = 100;
const int ring_inner = 26;
const int slot_radius = 68;
const int slot_icon = 24;
/// What QListWidget puts on a drag
const QString list_mime = QStringLiteral("application/x-qabstractitemmodeldatalist");
/**
@return the command id carried by a drag from the command list, or an
empty string
*/
QString droppedId(const QMimeData *mime)
{
if (!mime || !mime->hasFormat(list_mime)) {
return QString();
}
QByteArray data = mime->data(list_mime);
QDataStream stream(&data, QIODevice::ReadOnly);
int row, column;
QMap<int, QVariant> roles;
stream >> row >> column >> roles;
return roles.value(Qt::UserRole).toString();
}
}
GestureRingEditor::GestureRingEditor(QWidget *parent) :
QWidget(parent)
{
setAcceptDrops(true);
setFocusPolicy(Qt::StrongFocus);
setMinimumSize(sizeHint());
}
QSize GestureRingEditor::sizeHint() const
{
return QSize(2 * ring_radius + 8, 2 * ring_radius + 8);
}
void GestureRingEditor::setIds(const QStringList &ids)
{
m_ids = ids;
update();
}
void GestureRingEditor::setDirections(int directions)
{
m_directions = directions == 4 ? 4 : 8;
setCurrentSlot(qMin(m_current, m_directions - 1));
update();
}
void GestureRingEditor::setDescriptions(const QHash<QString, QString> &descriptions)
{
m_descriptions = descriptions;
update();
}
void GestureRingEditor::setCurrentSlot(int slot)
{
if (slot < 0 || slot >= m_directions) {
return;
}
const bool changed = slot != m_current;
m_current = slot;
update();
if (changed) {
emit currentSlotChanged(slot);
}
}
/**
@return the slot in the direction of @a pos from the centre, 0 at the
top then clockwise, or -1 at the centre or outside the ring
*/
int GestureRingEditor::slotAt(const QPoint &pos) const
{
const QPointF d = QPointF(pos) - QPointF(rect().center());
const qreal distance = qHypot(d.x(), d.y());
if (distance < ring_inner || distance > ring_radius) {
return -1;
}
qreal angle = qRadiansToDegrees(qAtan2(d.x(), -d.y()));
if (angle < 0) {
angle += 360;
}
return int(qRound(angle / (360.0 / m_directions))) % m_directions;
}
/**
@brief GestureRingEditor::place
Put @a id in the current slot. A command already elsewhere on the ring
moves here, so a command has one direction.
*/
void GestureRingEditor::place(const QString &id)
{
if (id.isEmpty()) {
return;
}
const int old = m_ids.indexOf(id);
if (old >= 0) {
m_ids[old].clear();
}
while (m_ids.count() <= m_current) {
m_ids << QString();
}
m_ids[m_current] = id;
update();
emit edited();
}
void GestureRingEditor::clearCurrent()
{
if (m_current < m_ids.count() && !m_ids.at(m_current).isEmpty()) {
m_ids[m_current].clear();
update();
emit edited();
}
}
QString GestureRingEditor::idAt(int slot) const
{
return slot < m_ids.count() ? m_ids.at(slot) : QString();
}
QPointF GestureRingEditor::slotCenter(int slot) const
{
const qreal a = qDegreesToRadians(slot * 360.0 / m_directions);
const QPointF c = QRectF(rect()).center();
return QPointF(c.x() + slot_radius * qSin(a), c.y() - slot_radius * qCos(a));
}
void GestureRingEditor::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
const QPointF c = QRectF(rect()).center();
const qreal step = 360.0 / m_directions;
const QRectF outer(c.x() - ring_radius, c.y() - ring_radius,
2 * ring_radius, 2 * ring_radius);
QColor line = palette().color(QPalette::Text);
line.setAlpha(110);
painter.setPen(line);
painter.setBrush(palette().color(QPalette::Base));
painter.drawEllipse(outer);
//The chosen slot
QPainterPath wedge;
wedge.moveTo(c);
wedge.arcTo(outer, 90 - m_current * step - step / 2, step);
wedge.closeSubpath();
painter.setPen(Qt::NoPen);
QColor highlight = palette().color(QPalette::Highlight);
if (!hasFocus()) {
highlight.setAlpha(140);
}
painter.setBrush(highlight);
painter.drawPath(wedge);
//Lines between the slots
painter.setPen(line);
for (int i = 0 ; i < m_directions ; ++i) {
const qreal a = qDegreesToRadians(i * step + step / 2);
painter.drawLine(QPointF(c.x() + ring_inner * qSin(a), c.y() - ring_inner * qCos(a)),
QPointF(c.x() + ring_radius * qSin(a), c.y() - ring_radius * qCos(a)));
}
painter.setBrush(palette().color(QPalette::Window));
painter.drawEllipse(c, ring_inner, ring_inner);
for (int i = 0 ; i < m_directions ; ++i)
{
const QString id = idAt(i);
if (id.isEmpty()) {
continue;
}
const QPointF p = slotCenter(i);
const QRect r(int(p.x()) - slot_icon / 2, int(p.y()) - slot_icon / 2,
slot_icon, slot_icon);
QAction *action = ShortcutManager::instance().action(id, nullptr);
if (action && !action->icon().isNull()) {
action->icon().paint(&painter, r);
} else {
painter.setPen(palette().color(i == m_current ? QPalette::HighlightedText
: QPalette::Text));
painter.drawText(r.adjusted(-12, 0, 12, 0), Qt::AlignCenter,
m_descriptions.value(id, id).left(3));
}
}
}
void GestureRingEditor::mousePressEvent(QMouseEvent *event)
{
setCurrentSlot(slotAt(event->position().toPoint()));
event->accept();
}
void GestureRingEditor::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) {
clearCurrent();
} else if (event->key() == Qt::Key_Right || event->key() == Qt::Key_Down) {
setCurrentSlot((m_current + 1) % m_directions);
} else if (event->key() == Qt::Key_Left || event->key() == Qt::Key_Up) {
setCurrentSlot((m_current + m_directions - 1) % m_directions);
} else {
QWidget::keyPressEvent(event);
}
}
void GestureRingEditor::dragEnterEvent(QDragEnterEvent *event)
{
if (!droppedId(event->mimeData()).isEmpty()) {
event->acceptProposedAction();
}
}
void GestureRingEditor::dragMoveEvent(QDragMoveEvent *event)
{
const int slot = slotAt(event->position().toPoint());
if (slot < 0) {
event->ignore();
return;
}
setCurrentSlot(slot);
event->acceptProposedAction();
}
void GestureRingEditor::dropEvent(QDropEvent *event)
{
const int slot = slotAt(event->position().toPoint());
const QString id = droppedId(event->mimeData());
if (slot < 0 || id.isEmpty()) {
event->ignore();
return;
}
setCurrentSlot(slot);
place(id);
event->acceptProposedAction();
}
/**
@brief GesturesConfigPage::GesturesConfigPage
@param parent
*/
GesturesConfigPage::GesturesConfigPage(QWidget *parent) :
ConfigPage(parent)
{
const QStringList available = ShortcutBarSettings::availableIds();
for (const ShortcutManager::ShortcutInfo &info :
ShortcutManager::instance().allShortcuts()) {
if (available.contains(info.id)) {
m_descriptions.insert(info.id, info.description);
}
}
for (const ShortcutBarSettings::Context c : ShortcutBarSettings::contexts()) {
m_pending.insert(c, GestureSettings::ids(c));
m_custom.insert(c, GestureSettings::isCustom(c));
}
auto *explanation = new QLabel(
tr("Faites glisser avec le bouton droit sur un folio : un anneau "
"de commandes s'ouvre, relâchez vers celle à lancer. Tant que "
"vous ne changez pas l'anneau d'un contexte, il montre les "
"commandes de la barre de raccourcis. Les gestes s'activent "
"ou se désactivent dans la page Général."), this);
explanation->setWordWrap(true);
m_directions = new QComboBox(this);
m_directions->setObjectName(QStringLiteral("directionsCombo"));
m_directions->addItem(tr("8 directions"), 8);
m_directions->addItem(tr("4 directions"), 4);
m_directions->setCurrentIndex(m_directions->findData(GestureSettings::directions()));
m_context = new QComboBox(this);
m_context->setObjectName(QStringLiteral("contextCombo"));
for (const ShortcutBarSettings::Context c : ShortcutBarSettings::contexts()) {
m_context->addItem(ShortcutBarSettings::title(c), c);
}
m_available = new QListWidget(this);
m_available->setObjectName(QStringLiteral("availableList"));
m_available->setSortingEnabled(true);
m_available->setDragEnabled(true);
m_available->setDragDropMode(QAbstractItemView::DragOnly);
for (auto it = m_descriptions.cbegin(); it != m_descriptions.cend(); ++it) {
QIcon icon;
if (QAction *action = ShortcutManager::instance().action(it.key(), nullptr)) {
icon = action->icon();
}
auto *item = new QListWidgetItem(icon, it.value(), m_available);
item->setData(Qt::UserRole, it.key());
}
m_ring = new GestureRingEditor(this);
m_ring->setObjectName(QStringLiteral("ringEditor"));
m_ring->setDescriptions(m_descriptions);
m_ring->setDirections(GestureSettings::directions());
m_slot_name = new QLabel(this);
m_slot_name->setAlignment(Qt::AlignCenter);
m_slot_name->setWordWrap(true);
m_state = new QLabel(this);
m_state->setWordWrap(true);
auto *place = new QPushButton(tr("Placer →"), this);
place->setObjectName(QStringLiteral("placeButton"));
auto *clear = new QPushButton(tr("Vider la direction"), this);
auto *follow = new QPushButton(tr("Comme la barre de raccourcis"), this);
follow->setObjectName(QStringLiteral("followBarButton"));
auto *ring_column = new QVBoxLayout();
ring_column->addWidget(m_ring, 0, Qt::AlignHCenter);
ring_column->addWidget(m_slot_name);
ring_column->addWidget(clear);
ring_column->addStretch();
auto *grid = new QGridLayout();
grid->addWidget(new QLabel(tr("Commandes disponibles"), this), 0, 0);
grid->addWidget(new QLabel(tr("Anneau, la direction choisie en surbrillance"), this), 0, 2);
grid->addWidget(m_available, 1, 0);
grid->addWidget(place, 1, 1, Qt::AlignVCenter);
grid->addLayout(ring_column, 1, 2);
grid->setColumnStretch(0, 1);
grid->setColumnStretch(2, 1);
auto *top_row = new QHBoxLayout();
top_row->addWidget(new QLabel(tr("Directions :"), this));
top_row->addWidget(m_directions);
top_row->addSpacing(12);
top_row->addWidget(new QLabel(tr("Contexte :"), this));
top_row->addWidget(m_context, 1);
auto *state_row = new QHBoxLayout();
state_row->addWidget(m_state, 1);
state_row->addWidget(follow);
auto *layout = new QVBoxLayout(this);
layout->addWidget(explanation);
layout->addLayout(top_row);
layout->addLayout(state_row);
layout->addLayout(grid);
connect(m_directions, qOverload<int>(&QComboBox::currentIndexChanged), this, [this]() {
m_ring->setDirections(m_directions->currentData().toInt());
updateState();
});
connect(m_context, qOverload<int>(&QComboBox::currentIndexChanged),
this, &GesturesConfigPage::showContext);
connect(m_ring, &GestureRingEditor::edited, this, [this]() {
m_pending.insert(m_shown, m_ring->ids());
m_custom.insert(m_shown, true);
updateState();
});
connect(m_ring, &GestureRingEditor::currentSlotChanged,
this, &GesturesConfigPage::updateState);
connect(place, &QPushButton::clicked, this, &GesturesConfigPage::placeSelected);
connect(m_available, &QListWidget::itemDoubleClicked,
this, &GesturesConfigPage::placeSelected);
connect(clear, &QPushButton::clicked, m_ring, &GestureRingEditor::clearCurrent);
connect(follow, &QPushButton::clicked, this, &GesturesConfigPage::followBar);
showContext();
}
/**
@brief GesturesConfigPage::applyConf
Save the directions and every context's ring.
*/
void GesturesConfigPage::applyConf()
{
GestureSettings::setDirections(m_directions->currentData().toInt());
for (const ShortcutBarSettings::Context c : ShortcutBarSettings::contexts()) {
if (m_custom.value(c)) {
GestureSettings::setIds(c, m_pending.value(c));
} else {
GestureSettings::reset(c);
}
}
}
QString GesturesConfigPage::title() const
{
return tr("Gestes de la souris", "configuration page title");
}
QIcon GesturesConfigPage::icon() const
{
return QET::Icons::ConfigureShortcuts;
}
void GesturesConfigPage::showContext()
{
m_shown = static_cast<ShortcutBarSettings::Context>(
m_context->currentData().toInt());
m_ring->setIds(m_pending.value(m_shown));
updateState();
}
void GesturesConfigPage::placeSelected()
{
if (QListWidgetItem *item = m_available->currentItem()) {
m_ring->place(item->data(Qt::UserRole).toString());
}
}
/**
@brief GesturesConfigPage::followBar
Make the shown context's ring follow the shortcut bar again.
*/
void GesturesConfigPage::followBar()
{
m_custom.insert(m_shown, false);
m_pending.insert(m_shown, GestureSettings::defaultIds(m_shown));
m_ring->setIds(m_pending.value(m_shown));
updateState();
}
/**
@brief GesturesConfigPage::updateState
Name the chosen direction's command, and say whether the shown ring is
the user's or the shortcut bar's.
*/
void GesturesConfigPage::updateState()
{
const int slot = m_ring->currentSlot();
const QStringList ids = m_ring->ids();
const QString id = slot < ids.count() ? ids.at(slot) : QString();
m_slot_name->setText(id.isEmpty() ? tr("(direction vide)")
: m_descriptions.value(id, id));
m_state->setText(m_custom.value(m_shown)
? tr("Anneau personnalisé pour ce contexte.")
: tr("Cet anneau suit la barre de raccourcis."));
}
+112
View File
@@ -0,0 +1,112 @@
/*
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 GESTURESCONFIGPAGE_H
#define GESTURESCONFIGPAGE_H
#include "configpage.h"
#include "../../shortcutbarsettings.h"
#include <QHash>
#include <QStringList>
class QComboBox;
class QLabel;
class QListWidget;
/**
@brief The gesture ring as an editor: one slot per direction, clockwise
from the top. Click a slot to choose it; drop a command from the
command list on a slot to put it there; Delete empties the chosen slot.
*/
class GestureRingEditor : public QWidget
{
Q_OBJECT
public:
explicit GestureRingEditor(QWidget *parent = nullptr);
void setIds(const QStringList &ids);
QStringList ids() const { return m_ids; }
void setDirections(int directions);
void setDescriptions(const QHash<QString, QString> &descriptions);
int currentSlot() const { return m_current; }
void setCurrentSlot(int slot);
int slotAt(const QPoint &pos) const;
void place(const QString &id);
void clearCurrent();
QSize sizeHint() const override;
signals:
void edited();
void currentSlotChanged(int slot);
protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dropEvent(QDropEvent *event) override;
private:
QString idAt(int slot) const;
QPointF slotCenter(int slot) const;
QStringList m_ids;
QHash<QString, QString> m_descriptions;
int m_directions = 8;
int m_current = 0;
};
/**
@brief The GesturesConfigPage class
Choose 4 or 8 directions for the mouse gesture ring, and which command
sits in each direction for each selection context. A context the user
does not edit keeps following the shortcut bar. Saved by applyConf().
*/
class GesturesConfigPage : public ConfigPage
{
Q_OBJECT
public:
explicit GesturesConfigPage(QWidget *parent = nullptr);
void applyConf() override;
QString title() const override;
QIcon icon() const override;
private:
void showContext();
void placeSelected();
void followBar();
void updateState();
QComboBox *m_directions;
QComboBox *m_context;
QListWidget *m_available;
GestureRingEditor *m_ring;
QLabel *m_slot_name;
QLabel *m_state;
ShortcutBarSettings::Context m_shown = ShortcutBarSettings::Canvas;
QHash<int, QStringList> m_pending;
QHash<int, bool> m_custom;
QHash<QString, QString> m_descriptions;
};
#endif // GESTURESCONFIGPAGE_H
@@ -96,7 +96,8 @@ void GuidesPropertiesWidget::addGuide() {
colorBtn->setStyleSheet(QString("background-color: %1; color: white; font-weight: bold;").arg(defaultColor.name()));
connect(colorBtn, &QPushButton::clicked, [this, colorBtn]() {
QColor c = QColorDialog::getColor(colorBtn->property("color").value<QColor>(), this);
QColor c = QColorDialog::getColor(colorBtn->property("color").value<QColor>(), this,
QString(), QColorDialog::DontUseNativeDialog);
if (c.isValid()) {
colorBtn->setProperty("color", c);
colorBtn->setStyleSheet(QString("background-color: %1; color: white; font-weight: bold;").arg(c.name()));
File diff suppressed because it is too large Load Diff
+53 -3
View File
@@ -18,13 +18,17 @@
#ifndef PROJECTCONFIGPAGES_H
#define PROJECTCONFIGPAGES_H
#include "configpage.h"
#include <QPointer>
#include <QVector>
#include "../../autoNum/autonumschemecommand.h"
class QLabel;
class QComboBox;
class QLineEdit;
class QCheckBox;
class QPushButton;
class QTabWidget;
class QSpinBox;
class QGroupBox;
class QETProject;
class BorderPropertiesWidget;
class ConductorPropertiesWidget;
@@ -32,6 +36,11 @@ class DiagramContextWidget;
class ReportPropertieWidget;
class XRefPropertiesWidget;
class SelectAutonumW;
class QTabWidget;
class QGroupBox;
class Element;
class QTableWidget;
class ElementAutoNumSchemeCommand;
class FolioAutonumberingW;
class FormulaAutonumberingW;
class AutoNumberingManagementW;
@@ -115,6 +124,7 @@ class ProjectMainConfigPage : public ProjectConfigPage {
private slots:
void resetUsageTracker();
void updateWiringRulesWidgets();
// attributes
protected:
@@ -129,6 +139,13 @@ class ProjectMainConfigPage : public ProjectConfigPage {
QPushButton *usage_reset_pb_;
QLabel *wire_hops_label_;
QComboBox *wire_hops_cb_;
QCheckBox *upright_symbol_texts_cb_;
QGroupBox *wiring_rules_gb_;
QCheckBox *use_application_rules_cb_;
QLabel *max_wires_label_;
QSpinBox *max_wires_sb_;
QCheckBox *one_wire_per_report_cb_;
QLabel *wiring_rules_off_label_;
};
class ProjectAutoNumConfigPage : public ProjectConfigPage {
@@ -165,11 +182,37 @@ class ProjectAutoNumConfigPage : public ProjectConfigPage {
void removeContextElement();
void saveContextCable(); //cable: one rule, no name to pick
void removeContextCable();
void newContextElement();
void renameContextElement();
void refreshElementSchemes(const QString &selected);
void refreshElementUsers();
void assignFreeNumber();
void updateAssignNumberButton();
void newContextConductor();
void renameContextConductor();
void newContextFolio();
void renameContextFolio();
QString askElementSchemeName(const QString &title, QString name,
const QString &ignored_title);
bool pushElementSchemeCommand(ElementAutoNumSchemeCommand *cmd);
void importFromProject();
void applyAutoNum();
void applyManagement();
private:
//Conductor and folio numberings
using SchemeKind = AutoNumSchemeCommand::Kind;
SelectAutonumW *sawFor(SchemeKind kind) const;
void refreshSchemes(SchemeKind kind, const QString &selected);
void refreshSchemeUsers(SchemeKind kind);
QString askSchemeName(SchemeKind kind, const QString &title, QString name,
const QString &ignored_title);
void newScheme(SchemeKind kind);
void renameScheme(SchemeKind kind);
void saveScheme(SchemeKind kind);
void removeScheme(SchemeKind kind);
signals:
void setAutoNum(QString);
void setAutoNum(int,int);
@@ -182,11 +225,18 @@ class ProjectAutoNumConfigPage : public ProjectConfigPage {
SelectAutonumW *m_saw_folio;
SelectAutonumW *m_saw_element;
SelectAutonumW *m_saw_cable;
QTabWidget *m_tab_widget = nullptr;
QGroupBox *m_conductor_users_box = nullptr;
QTableWidget *m_conductor_users = nullptr;
QGroupBox *m_folio_users_box = nullptr;
QTableWidget *m_folio_users = nullptr;
QGroupBox *m_element_users_box = nullptr;
QTableWidget *m_element_users = nullptr;
QPushButton *m_assign_number_pb = nullptr;
QVector<QPointer<Element>> m_element_rows; ///< the element of each row of the table, none for a gap
FolioAutonumberingW *m_faw;
AutoNumberingManagementW *m_amw;
QPushButton *m_import_pb = nullptr;
/// The tabs of this page, kept to switch between them
QTabWidget *m_tab_widget = nullptr;
};
+149 -14
View File
@@ -20,6 +20,9 @@
#include "../../qeticons.h"
#include "../../shortcutmanager.h"
#include <QAction>
#include <QApplication>
#include <QClipboard>
#include <QComboBox>
#include <QFrame>
#include <QHBoxLayout>
@@ -27,6 +30,8 @@
#include <QKeySequenceEdit>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QMenuBar>
#include <QPushButton>
#include <QRegularExpression>
#include <QToolButton>
@@ -43,6 +48,10 @@
this, so "general" matches "Général" and "Ctrl+S" matches "ctrl+s"
regardless of the keyboard layout the query was typed on.
*/
namespace {
enum Column { ActionColumn, MenuColumn, SequenceColumn, ResetColumn };
}
static QString normalizedForSearch(const QString &text)
{
const QString decomposed = text.normalized(QString::NormalizationForm_D);
@@ -85,20 +94,51 @@ ShortcutsConfigPage::ShortcutsConfigPage(QWidget *parent) :
connect(m_quick_filter, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &ShortcutsConfigPage::quickFilterChanged);
m_category_filter = new QComboBox(this);
m_category_filter->setObjectName(QStringLiteral("categoryFilterCombo"));
m_category_filter->addItem(tr("Toutes les catégories"));
connect(m_category_filter, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &ShortcutsConfigPage::quickFilterChanged);
m_count_label = new QLabel(this);
m_count_label->setObjectName(QStringLiteral("shortcutCountLabel"));
auto *filter_layout = new QHBoxLayout();
filter_layout->addWidget(m_filter_edit, 1);
filter_layout->addWidget(m_category_filter);
filter_layout->addWidget(m_quick_filter);
filter_layout->addWidget(m_count_label);
vlayout->addLayout(filter_layout);
//Press a key combination to list what uses it, without having to
//know how QElectroTech spells it ("Ctrl+Maj+S", "Ctrl+Shift+S"…)
m_key_search = new QKeySequenceEdit(this);
m_key_search->setObjectName(QStringLiteral("keySearchEdit"));
m_key_search->setToolTip(tr("Appuyez sur une combinaison de touches pour voir quelle commande l'utilise"));
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
m_key_search->setMaximumSequenceLength(1);
#endif
connect(m_key_search, &QKeySequenceEdit::keySequenceChanged, this, [this]() { applyFilter(); });
auto *clear_key_button = new QToolButton(this);
clear_key_button->setIcon(QET::Icons::EditClear);
clear_key_button->setToolTip(tr("Effacer la touche recherchée"));
clear_key_button->setAutoRaise(true);
connect(clear_key_button, &QToolButton::clicked, m_key_search, &QKeySequenceEdit::clear);
auto *key_layout = new QHBoxLayout();
key_layout->addWidget(new QLabel(tr("Rechercher par touche :"), this));
key_layout->addWidget(m_key_search);
key_layout->addWidget(clear_key_button);
key_layout->addStretch();
vlayout->addLayout(key_layout);
m_tree = new QTreeWidget(this);
m_tree->setHeaderLabels({tr("Action"), tr("Raccourci"), QString()});
m_tree->header()->setSectionResizeMode(0, QHeaderView::Stretch);
m_tree->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
m_tree->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
m_tree->setHeaderLabels({tr("Action"), tr("Menu"), tr("Raccourci"), QString()});
m_tree->header()->setSectionResizeMode(ActionColumn, QHeaderView::Stretch);
m_tree->header()->setSectionResizeMode(MenuColumn, QHeaderView::ResizeToContents);
m_tree->header()->setSectionResizeMode(SequenceColumn, QHeaderView::ResizeToContents);
m_tree->header()->setSectionResizeMode(ResetColumn, QHeaderView::ResizeToContents);
m_tree->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_tree->setSelectionMode(QAbstractItemView::NoSelection);
vlayout->addWidget(m_tree);
@@ -106,7 +146,12 @@ ShortcutsConfigPage::ShortcutsConfigPage(QWidget *parent) :
auto *reset_all_button = new QPushButton(tr("Tout réinitialiser"), this);
connect(reset_all_button, &QPushButton::clicked, this, &ShortcutsConfigPage::resetAllRows);
auto *copy_button = new QPushButton(QET::Icons::EditCopy, tr("Copier la liste"), this);
copy_button->setToolTip(tr("Copie les raccourcis affichés, à coller dans un tableur ou un document"));
connect(copy_button, &QPushButton::clicked, this, &ShortcutsConfigPage::copyList);
auto *bottom_layout = new QHBoxLayout();
bottom_layout->addWidget(copy_button);
bottom_layout->addStretch();
bottom_layout->addWidget(reset_all_button);
vlayout->addLayout(bottom_layout);
@@ -139,6 +184,9 @@ void ShortcutsConfigPage::populateTable()
m_tree->clear();
m_rows.clear();
while (m_category_filter->count() > 1) {
m_category_filter->removeItem(1);
}
m_rows.reserve(shortcuts.size());
QHash<QString, QTreeWidgetItem *> category_nodes;
@@ -147,18 +195,22 @@ void ShortcutsConfigPage::populateTable()
QTreeWidgetItem *category_item = category_nodes.value(info.category, nullptr);
if (!category_item) {
category_item = new QTreeWidgetItem(m_tree);
category_item->setText(0, info.category);
category_item->setText(ActionColumn, info.category);
category_item->setFlags(category_item->flags() & ~Qt::ItemIsEditable);
category_nodes.insert(info.category, category_item);
m_category_filter->addItem(info.category);
}
auto *child = new QTreeWidgetItem(category_item);
child->setText(0, info.description);
const QString menu_path = menuPath(info.action);
child->setText(ActionColumn, info.description);
child->setIcon(ActionColumn, info.icon);
child->setText(MenuColumn, menu_path);
child->setFlags(child->flags() & ~Qt::ItemIsEditable);
auto *edit = new QKeySequenceEdit(info.current_sequence, m_tree);
connect(edit, &QKeySequenceEdit::editingFinished, this, &ShortcutsConfigPage::checkConflicts);
m_tree->setItemWidget(child, 1, edit);
m_tree->setItemWidget(child, SequenceColumn, edit);
auto *reset_button = new QToolButton(m_tree);
reset_button->setIcon(QET::Icons::EditUndo);
@@ -166,9 +218,10 @@ void ShortcutsConfigPage::populateTable()
reset_button->setAutoRaise(true);
const int row_index = m_rows.size();
connect(reset_button, &QToolButton::clicked, this, [this, row_index]() { resetRow(row_index); });
m_tree->setItemWidget(child, 2, reset_button);
m_tree->setItemWidget(child, ResetColumn, reset_button);
m_rows << Row{info.id, info.category, info.description, info.default_sequence, edit, child, false};
m_rows << Row{info.id, info.category, info.description, menu_path,
info.default_sequence, edit, child, false};
}
checkConflicts();
@@ -210,6 +263,9 @@ void ShortcutsConfigPage::applyFilter()
: needle.split(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts);
const int quick_filter = m_quick_filter->currentIndex();
const QString category = m_category_filter->currentIndex() > 0
? m_category_filter->currentText() : QString();
const QKeySequence key = m_key_search->keySequence();
int visible_actions = 0;
for (const Row &row : std::as_const(m_rows)) {
@@ -218,7 +274,8 @@ void ShortcutsConfigPage::applyFilter()
// of "Shift"), which is precisely the kind of false positive that hides
// the one binding the user is looking for.
const QString text_haystack = normalizedForSearch(
row.category + QLatin1Char(' ') + row.description);
row.category + QLatin1Char(' ') + row.description
+ QLatin1Char(' ') + row.menu_path);
const QString sequence_text = normalizedForSearch(row.edit->keySequence().toString());
bool matches = true;
@@ -230,6 +287,16 @@ void ShortcutsConfigPage::applyFilter()
}
}
if (matches && !category.isEmpty()) {
matches = row.category == category;
}
if (matches && !key.isEmpty()) {
//A pressed key also finds the sequences it starts, such
//as a two-key shortcut whose first key it is
const QKeySequence sequence = row.edit->keySequence();
matches = !sequence.isEmpty() && key.matches(sequence) != QKeySequence::NoMatch;
}
if (matches) {
switch (quick_filter) {
case BoundOnly:
@@ -252,7 +319,8 @@ void ShortcutsConfigPage::applyFilter()
}
}
const bool filtering = !needle.isEmpty() || quick_filter != ShowAll;
const bool filtering = !needle.isEmpty() || quick_filter != ShowAll
|| !category.isEmpty() || !key.isEmpty();
for (int i = 0; i < m_tree->topLevelItemCount(); ++i) {
QTreeWidgetItem *top = m_tree->topLevelItem(i);
bool any_visible = false;
@@ -322,11 +390,11 @@ void ShortcutsConfigPage::checkConflicts()
other_descriptions << m_rows.at(other_row).description;
}
}
current_row.item->setBackground(0, QColor(255, 205, 205));
current_row.item->setBackground(ActionColumn, QColor(255, 205, 205));
current_row.edit->setToolTip(
tr("Ce raccourci est aussi utilisé par : %1").arg(other_descriptions.join(QStringLiteral(", "))));
} else {
current_row.item->setBackground(0, QBrush());
current_row.item->setBackground(ActionColumn, QBrush());
current_row.edit->setToolTip(QString());
}
}
@@ -335,7 +403,9 @@ void ShortcutsConfigPage::checkConflicts()
// conflicts-only quick filter); refresh the visible set so the list doesn't
// show stale results.
const bool filtering = !m_filter_edit->text().trimmed().isEmpty()
|| m_quick_filter->currentIndex() != ShowAll;
|| m_quick_filter->currentIndex() != ShowAll
|| m_category_filter->currentIndex() > 0
|| !m_key_search->keySequence().isEmpty();
if (filtering) {
applyFilter();
}
@@ -374,6 +444,71 @@ void ShortcutsConfigPage::applyConf()
}
}
/**
@brief ShortcutsConfigPage::menuPath
@param action
@return where \a action is in the menu bar, such as "Projet › Scripts",
or an empty string for a command that is in no menu of the menu bar
(a toolbar or shortcut bar only command, or one with no live action)
*/
QString ShortcutsConfigPage::menuPath(const QAction *action)
{
if (!action) {
return QString();
}
for (QObject *object : action->associatedObjects()) {
QStringList titles;
for (auto *menu = qobject_cast<QMenu *>(object); menu; ) {
titles.prepend(menu->title().remove(QLatin1Char('&')));
QMenu *parent_menu = nullptr;
bool in_menu_bar = false;
for (QObject *owner : menu->menuAction()->associatedObjects()) {
if (qobject_cast<QMenuBar *>(owner)) {
in_menu_bar = true;
} else if (!parent_menu) {
parent_menu = qobject_cast<QMenu *>(owner);
}
}
if (in_menu_bar) {
return titles.join(QStringLiteral(" › "));
}
menu = parent_menu;
}
}
return QString();
}
/**
@brief ShortcutsConfigPage::listAsText
@return the shortcuts shown, as the edits currently hold them, one per
line with tab-separated columns and a header line: pasted into a
spreadsheet it fills one cell per column.
*/
QString ShortcutsConfigPage::listAsText() const
{
QStringList lines;
lines << QStringList{tr("Catégorie"), tr("Menu"), tr("Action"), tr("Raccourci")}
.join(QLatin1Char('\t'));
for (const Row &row : std::as_const(m_rows)) {
if (row.item->isHidden()) {
continue;
}
lines << QStringList{row.category, row.menu_path, row.description,
row.edit->keySequence().toString(QKeySequence::NativeText)}
.join(QLatin1Char('\t'));
}
return lines.join(QLatin1Char('\n')) + QLatin1Char('\n');
}
/**
@brief ShortcutsConfigPage::copyList
Put listAsText() on the clipboard.
*/
void ShortcutsConfigPage::copyList()
{
QApplication::clipboard()->setText(listAsText());
}
QString ShortcutsConfigPage::title() const
{
return tr("Raccourcis", "configuration page title");
@@ -22,6 +22,7 @@
#include <QKeySequence>
class QAction;
class QComboBox;
class QKeySequenceEdit;
class QLabel;
@@ -48,11 +49,15 @@ class ShortcutsConfigPage : public ConfigPage
QString title() const override;
QIcon icon() const override;
static QString menuPath(const QAction *action);
QString listAsText() const;
private slots:
void filterRows(const QString &filter_text);
void quickFilterChanged(int index);
void checkConflicts();
void resetAllRows();
void copyList();
private:
enum QuickFilter {
@@ -66,6 +71,7 @@ class ShortcutsConfigPage : public ConfigPage
QString id;
QString category;
QString description;
QString menu_path;
QKeySequence default_sequence;
QKeySequenceEdit *edit;
QTreeWidgetItem *item;
@@ -78,6 +84,8 @@ class ShortcutsConfigPage : public ConfigPage
QLineEdit *m_filter_edit;
QComboBox *m_quick_filter;
QComboBox *m_category_filter;
QKeySequenceEdit *m_key_search;
QLabel *m_count_label;
QTreeWidget *m_tree;
QList<Row> m_rows;
@@ -0,0 +1,423 @@
/*
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 "toolbarcommandsconfigpage.h"
#include "../../qeticons.h"
#include "../../shortcutmanager.h"
#include <QAction>
#include <QComboBox>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QInputDialog>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QPushButton>
#include <QTimer>
#include <QVBoxLayout>
ToolbarCommandsConfigPage::ToolbarCommandsConfigPage(QWidget *parent) :
ConfigPage(parent)
{
const QStringList available = DiagramToolbarSettings::availableCommandIds();
for (const ShortcutManager::ShortcutInfo &info :
ShortcutManager::instance().allShortcuts()) {
if (available.contains(info.id)) {
m_descriptions.insert(info.id, info.description);
}
}
m_custom = DiagramToolbarSettings::customToolbars();
for (const DiagramToolbarSettings::Toolbar &toolbar : DiagramToolbarSettings::toolbars()) {
m_pending.insert(toolbar.name, DiagramToolbarSettings::ids(toolbar.name));
}
auto *explanation = new QLabel(
tr("Choisissez les commandes de chaque barre d'outils de l'éditeur "
"de schémas. Glissez une commande de la liste de gauche vers la "
"barre, ou double-cliquez dessus ; Suppr la retire."), this);
explanation->setWordWrap(true);
m_toolbar = new QComboBox(this);
m_toolbar->setObjectName(QStringLiteral("toolbarCombo"));
auto *new_toolbar = new QPushButton(tr("Nouvelle barre…"), this);
new_toolbar->setObjectName(QStringLiteral("newToolbarButton"));
m_rename = new QPushButton(tr("Renommer…"), this);
m_delete = new QPushButton(tr("Supprimer la barre"), this);
m_delete->setObjectName(QStringLiteral("deleteToolbarButton"));
auto *reset = new QPushButton(tr("Valeurs par défaut"), this);
reset->setObjectName(QStringLiteral("resetButton"));
m_filter = new QLineEdit(this);
m_filter->setPlaceholderText(tr("Rechercher une commande"));
m_filter->setClearButtonEnabled(true);
m_available = new QListWidget(this);
m_available->setObjectName(QStringLiteral("availableList"));
m_available->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_available->setSortingEnabled(true);
m_available->setDragDropMode(QAbstractItemView::DragOnly);
m_available->setDefaultDropAction(Qt::MoveAction);
m_chosen = new QListWidget(this);
m_chosen->setObjectName(QStringLiteral("chosenList"));
m_chosen->setSelectionMode(QAbstractItemView::ExtendedSelection);
//Takes commands dragged from the list on the left, and reorders
m_chosen->setDragDropMode(QAbstractItemView::DragDrop);
m_chosen->setDefaultDropAction(Qt::MoveAction);
auto *add = new QPushButton(tr("Ajouter →"), this);
auto *separator = new QPushButton(tr("Séparateur"), this);
separator->setObjectName(QStringLiteral("separatorButton"));
auto *remove = new QPushButton(tr("← Retirer"), this);
auto *up = new QPushButton(tr("Monter"), this);
auto *down = new QPushButton(tr("Descendre"), this);
auto *buttons = new QVBoxLayout();
buttons->addStretch();
buttons->addWidget(add);
buttons->addWidget(separator);
buttons->addWidget(remove);
buttons->addSpacing(12);
buttons->addWidget(up);
buttons->addWidget(down);
buttons->addStretch();
auto *grid = new QGridLayout();
grid->addWidget(new QLabel(tr("Commandes disponibles"), this), 0, 0);
grid->addWidget(new QLabel(tr("Dans la barre, dans l'ordre"), this), 0, 2);
grid->addWidget(m_filter, 1, 0);
grid->addWidget(m_available, 2, 0);
grid->addLayout(buttons, 2, 1);
grid->addWidget(m_chosen, 1, 2, 2, 1);
auto *toolbar_row = new QHBoxLayout();
toolbar_row->addWidget(new QLabel(tr("Barre d'outils :"), this));
toolbar_row->addWidget(m_toolbar, 1);
toolbar_row->addWidget(new_toolbar);
toolbar_row->addWidget(m_rename);
toolbar_row->addWidget(m_delete);
toolbar_row->addWidget(reset);
auto *layout = new QVBoxLayout(this);
layout->addWidget(explanation);
layout->addLayout(toolbar_row);
layout->addLayout(grid);
connect(m_toolbar, qOverload<int>(&QComboBox::currentIndexChanged), this, [this]() {
storeToolbar();
showToolbar();
});
connect(m_filter, &QLineEdit::textChanged, this, &ToolbarCommandsConfigPage::fillAvailable);
connect(add, &QPushButton::clicked, this, &ToolbarCommandsConfigPage::addSelected);
connect(separator, &QPushButton::clicked, this, &ToolbarCommandsConfigPage::addSeparator);
connect(remove, &QPushButton::clicked, this, &ToolbarCommandsConfigPage::removeSelected);
connect(up, &QPushButton::clicked, this, [this]() { moveSelected(-1); });
connect(down, &QPushButton::clicked, this, [this]() { moveSelected(1); });
connect(reset, &QPushButton::clicked, this, &ToolbarCommandsConfigPage::resetToolbar);
connect(new_toolbar, &QPushButton::clicked, this, &ToolbarCommandsConfigPage::newToolbar);
connect(m_rename, &QPushButton::clicked, this, &ToolbarCommandsConfigPage::renameToolbar);
connect(m_delete, &QPushButton::clicked, this, &ToolbarCommandsConfigPage::deleteToolbar);
connect(m_available, &QListWidget::itemDoubleClicked, this, &ToolbarCommandsConfigPage::addSelected);
connect(m_chosen, &QListWidget::itemDoubleClicked, this, &ToolbarCommandsConfigPage::removeSelected);
//A command dropped from the left is no longer available. Later,
//not now: a dropped row is inserted before its data is set.
connect(m_chosen->model(), &QAbstractItemModel::rowsInserted, this, [this]() {
if (m_chosen->property("filling").toBool()) return;
QTimer::singleShot(0, this, [this]() {
storeToolbar();
fillAvailable();
});
});
auto *delete_key = new QAction(m_chosen);
delete_key->setShortcut(QKeySequence::Delete);
delete_key->setShortcutContext(Qt::WidgetShortcut);
m_chosen->addAction(delete_key);
connect(delete_key, &QAction::triggered, this, &ToolbarCommandsConfigPage::removeSelected);
fillToolbarCombo(DiagramToolbarSettings::builtInNames().constFirst());
}
/**
@brief ToolbarCommandsConfigPage::applyConf
Save the user's own toolbars and every toolbar's contents, then
rebuild the toolbars of the open diagram editors.
*/
void ToolbarCommandsConfigPage::applyConf()
{
storeToolbar();
DiagramToolbarSettings::setCustomToolbars(m_custom);
for (const DiagramToolbarSettings::Toolbar &toolbar : DiagramToolbarSettings::toolbars()) {
DiagramToolbarSettings::setIds(toolbar.name, m_pending.value(toolbar.name));
}
DiagramToolbarSettings::applyToAll();
}
QString ToolbarCommandsConfigPage::title() const
{
return tr("Contenu des barres", "configuration page title: what each toolbar holds");
}
QIcon ToolbarCommandsConfigPage::icon() const
{
return QET::Icons::ConfigureToolbars;
}
/**
@brief ToolbarCommandsConfigPage::fillToolbarCombo
List the built-in toolbars and the user's own, and show @a current.
*/
void ToolbarCommandsConfigPage::fillToolbarCombo(const QString &current)
{
const QSignalBlocker blocker(m_toolbar);
m_toolbar->clear();
for (const QString &name : DiagramToolbarSettings::builtInNames()) {
m_toolbar->addItem(DiagramToolbarSettings::builtInTitle(name), name);
}
for (const DiagramToolbarSettings::Toolbar &toolbar : std::as_const(m_custom)) {
m_toolbar->addItem(toolbar.title, toolbar.name);
}
const int index = m_toolbar->findData(current);
m_toolbar->setCurrentIndex(index < 0 ? 0 : index);
showToolbar();
}
/**
@brief ToolbarCommandsConfigPage::showToolbar
Fill both lists for the toolbar chosen in the combo box.
*/
void ToolbarCommandsConfigPage::showToolbar()
{
m_shown = m_toolbar->currentData().toString();
const bool custom = !DiagramToolbarSettings::builtInNames().contains(m_shown);
m_rename->setEnabled(custom);
m_delete->setEnabled(custom);
m_chosen->setProperty("filling", true);
m_chosen->clear();
for (const QString &id : m_pending.value(m_shown)) {
m_chosen->addItem(makeItem(id));
}
m_chosen->setProperty("filling", false);
fillAvailable();
}
/**
@brief ToolbarCommandsConfigPage::storeToolbar
Keep the shown toolbar's list, in the order on screen.
*/
void ToolbarCommandsConfigPage::storeToolbar()
{
if (m_shown.isEmpty()) {
return;
}
QStringList ids;
for (int i = 0 ; i < m_chosen->count() ; ++i) {
ids << m_chosen->item(i)->data(Qt::UserRole).toString();
}
m_pending.insert(m_shown, ids);
}
/**
@brief ToolbarCommandsConfigPage::fillAvailable
List what can still go on the shown toolbar and matches the search: a
command not already on it, and a widget button on no toolbar at all
(a widget can only be in one place).
*/
void ToolbarCommandsConfigPage::fillAvailable()
{
QStringList on_shown;
for (int i = 0 ; i < m_chosen->count() ; ++i) {
on_shown << m_chosen->item(i)->data(Qt::UserRole).toString();
}
QStringList on_any = on_shown;
for (auto it = m_pending.cbegin() ; it != m_pending.cend() ; ++it) {
if (it.key() != m_shown) on_any << it.value();
}
QStringList ids;
for (auto it = m_descriptions.cbegin() ; it != m_descriptions.cend() ; ++it) {
if (!on_shown.contains(it.key())) ids << it.key();
}
for (const QString &id : DiagramToolbarSettings::widgetIds()) {
if (!on_any.contains(id)) ids << id;
}
m_available->clear();
const QString filter = m_filter->text().trimmed();
for (const QString &id : std::as_const(ids)) {
QListWidgetItem *item = makeItem(id);
if (filter.isEmpty() || item->text().contains(filter, Qt::CaseInsensitive)) {
m_available->addItem(item);
} else {
delete item;
}
}
}
void ToolbarCommandsConfigPage::addSelected()
{
const QList<QListWidgetItem *> selected = m_available->selectedItems();
if (selected.isEmpty()) {
return;
}
m_chosen->setProperty("filling", true);
for (QListWidgetItem *item : selected) {
m_chosen->addItem(m_available->takeItem(m_available->row(item)));
}
m_chosen->setProperty("filling", false);
storeToolbar();
fillAvailable();
}
/**
@brief ToolbarCommandsConfigPage::addSeparator
Insert a separator after the selected row, or at the end.
*/
void ToolbarCommandsConfigPage::addSeparator()
{
const int row = m_chosen->currentRow();
m_chosen->setProperty("filling", true);
m_chosen->insertItem(row < 0 ? m_chosen->count() : row + 1,
makeItem(DiagramToolbarSettings::separatorId()));
m_chosen->setProperty("filling", false);
storeToolbar();
}
void ToolbarCommandsConfigPage::removeSelected()
{
for (QListWidgetItem *item : m_chosen->selectedItems()) {
delete m_chosen->takeItem(m_chosen->row(item));
}
storeToolbar();
fillAvailable();
}
/**
@brief ToolbarCommandsConfigPage::moveSelected
Move the selected command @a step rows, keeping it selected.
*/
void ToolbarCommandsConfigPage::moveSelected(int step)
{
const int row = m_chosen->currentRow();
const int target = row + step;
if (row < 0 || target < 0 || target >= m_chosen->count()) {
return;
}
m_chosen->setProperty("filling", true);
QListWidgetItem *item = m_chosen->takeItem(row);
m_chosen->insertItem(target, item);
m_chosen->setProperty("filling", false);
m_chosen->setCurrentRow(target);
storeToolbar();
}
/**
@brief ToolbarCommandsConfigPage::resetToolbar
Give the shown toolbar its defaults back; the user's own toolbar is
emptied.
*/
void ToolbarCommandsConfigPage::resetToolbar()
{
m_pending.insert(m_shown, DiagramToolbarSettings::defaultIds(m_shown));
//A widget the defaults put here leaves the toolbar it was moved to
for (const QString &id : DiagramToolbarSettings::defaultIds(m_shown)) {
if (!DiagramToolbarSettings::isWidget(id)) continue;
for (auto it = m_pending.begin() ; it != m_pending.end() ; ++it) {
if (it.key() != m_shown) it.value().removeAll(id);
}
}
showToolbar();
}
void ToolbarCommandsConfigPage::newToolbar()
{
bool ok = false;
const QString title = QInputDialog::getText(this, tr("Nouvelle barre d'outils"),
tr("Nom de la barre d'outils :"),
QLineEdit::Normal, QString(), &ok).trimmed();
if (!ok || title.isEmpty()) {
return;
}
storeToolbar();
DiagramToolbarSettings::Toolbar toolbar;
toolbar.name = DiagramToolbarSettings::newCustomName(m_custom);
toolbar.title = title;
toolbar.custom = true;
m_custom << toolbar;
m_pending.insert(toolbar.name, QStringList());
fillToolbarCombo(toolbar.name);
}
void ToolbarCommandsConfigPage::renameToolbar()
{
for (DiagramToolbarSettings::Toolbar &toolbar : m_custom) {
if (toolbar.name != m_shown) continue;
bool ok = false;
const QString title = QInputDialog::getText(this, tr("Renommer la barre d'outils"),
tr("Nom de la barre d'outils :"),
QLineEdit::Normal, toolbar.title, &ok).trimmed();
if (ok && !title.isEmpty()) {
storeToolbar();
toolbar.title = title;
fillToolbarCombo(m_shown);
}
return;
}
}
void ToolbarCommandsConfigPage::deleteToolbar()
{
const QString name = m_shown;
for (int i = 0 ; i < m_custom.size() ; ++i) {
if (m_custom.at(i).name == name) {
m_custom.removeAt(i);
m_pending.remove(name);
m_shown.clear();
fillToolbarCombo(DiagramToolbarSettings::builtInNames().constFirst());
return;
}
}
}
/**
@brief ToolbarCommandsConfigPage::makeItem
@return a list item for @a id with the command's text and icon. An id
no live action carries (a command from a build without it, or a script
since removed) is still listed, by its id, so saving does not silently
drop it.
*/
QListWidgetItem *ToolbarCommandsConfigPage::makeItem(const QString &id) const
{
QString text = m_descriptions.value(id, id);
QString tip;
QIcon icon;
if (id == DiagramToolbarSettings::separatorId()) {
text = tr("─── Séparateur ───");
} else if (DiagramToolbarSettings::isWidget(id)) {
text = DiagramToolbarSettings::widgetTitle(id);
} else if (QAction *action = ShortcutManager::instance().action(id, nullptr)) {
icon = action->icon();
tip = action->statusTip();
}
auto *item = new QListWidgetItem(icon, text);
item->setData(Qt::UserRole, id);
//Tells apart commands with the same name ("Ajouter une ligne")
item->setToolTip(tip);
return item;
}
@@ -0,0 +1,78 @@
/*
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 TOOLBARCOMMANDSCONFIGPAGE_H
#define TOOLBARCOMMANDSCONFIGPAGE_H
#include "configpage.h"
#include "../../diagramtoolbarsettings.h"
#include <QHash>
class QComboBox;
class QLineEdit;
class QListWidget;
class QListWidgetItem;
class QPushButton;
/**
@brief The ToolbarCommandsConfigPage class
Choose what each of the diagram editor's toolbars holds, and add,
rename or delete toolbars of one's own. Commands are dragged or added
from the list on the left. Changes are kept per toolbar while the
dialog is open, then saved and applied to the open windows by
applyConf().
*/
class ToolbarCommandsConfigPage : public ConfigPage
{
Q_OBJECT
public:
explicit ToolbarCommandsConfigPage(QWidget *parent = nullptr);
void applyConf() override;
QString title() const override;
QIcon icon() const override;
private:
void fillToolbarCombo(const QString &current);
void showToolbar();
void storeToolbar();
void fillAvailable();
void addSelected();
void addSeparator();
void removeSelected();
void moveSelected(int step);
void resetToolbar();
void newToolbar();
void renameToolbar();
void deleteToolbar();
QListWidgetItem *makeItem(const QString &id) const;
QComboBox *m_toolbar;
QLineEdit *m_filter;
QListWidget *m_available;
QListWidget *m_chosen;
QPushButton *m_rename;
QPushButton *m_delete;
QString m_shown;
QList<DiagramToolbarSettings::Toolbar> m_custom;
QHash<QString, QStringList> m_pending;
QHash<QString, QString> m_descriptions;
};
#endif // TOOLBARCOMMANDSCONFIGPAGE_H
@@ -0,0 +1,88 @@
/*
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 "toolbarsconfigpage.h"
#include "../../qeticons.h"
#include "../../toolbarsettings.h"
#include <QCheckBox>
#include <QComboBox>
#include <QFormLayout>
#include <QFrame>
#include <QLabel>
#include <QVBoxLayout>
ToolbarsConfigPage::ToolbarsConfigPage(QWidget *parent) :
ConfigPage(parent)
{
auto *vlayout = new QVBoxLayout(this);
vlayout->addWidget(new QLabel(title(), this));
auto *line = new QFrame(this);
line->setFrameShape(QFrame::HLine);
vlayout->addWidget(line);
m_icon_size = new QComboBox(this);
m_icon_size->setObjectName(QStringLiteral("iconSizeCombo"));
m_icon_size->addItem(tr("Par défaut"), 0);
m_icon_size->addItem(tr("Petites (16 px)"), 16);
m_icon_size->addItem(tr("Moyennes (24 px)"), 24);
m_icon_size->addItem(tr("Grandes (32 px)"), 32);
m_icon_size->addItem(tr("Très grandes (48 px)"), 48);
const int size_index = m_icon_size->findData(ToolbarSettings::iconSize());
m_icon_size->setCurrentIndex(size_index < 0 ? 0 : size_index);
m_button_style = new QComboBox(this);
m_button_style->setObjectName(QStringLiteral("buttonStyleCombo"));
m_button_style->addItem(tr("Icône seule"), int(Qt::ToolButtonIconOnly));
m_button_style->addItem(tr("Texte à côté de l'icône"), int(Qt::ToolButtonTextBesideIcon));
m_button_style->addItem(tr("Texte sous l'icône"), int(Qt::ToolButtonTextUnderIcon));
m_button_style->setCurrentIndex(m_button_style->findData(int(ToolbarSettings::buttonStyle())));
m_locked = new QCheckBox(tr("Verrouiller les barres d'outils (elles ne peuvent plus être déplacées)"), this);
m_locked->setObjectName(QStringLiteral("lockedCheck"));
m_locked->setChecked(ToolbarSettings::locked());
auto *form = new QFormLayout();
form->addRow(tr("Taille des icônes :"), m_icon_size);
form->addRow(tr("Boutons :"), m_button_style);
form->addRow(m_locked);
vlayout->addLayout(form);
auto *hint = new QLabel(tr("Pour afficher ou masquer une barre d'outils, faites un clic droit sur une barre d'outils."), this);
hint->setWordWrap(true);
vlayout->addWidget(hint);
vlayout->addStretch();
}
void ToolbarsConfigPage::applyConf()
{
ToolbarSettings::save(m_icon_size->currentData().toInt(),
Qt::ToolButtonStyle(m_button_style->currentData().toInt()),
m_locked->isChecked());
ToolbarSettings::applyToAll();
}
QString ToolbarsConfigPage::title() const
{
return tr("Barres d'outils", "configuration page title");
}
QIcon ToolbarsConfigPage::icon() const
{
return QET::Icons::ConfigureToolbars;
}
@@ -0,0 +1,49 @@
/*
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 TOOLBARSCONFIGPAGE_H
#define TOOLBARSCONFIGPAGE_H
#include "configpage.h"
class QCheckBox;
class QComboBox;
/**
@brief The ToolbarsConfigPage class
Toolbar icon size, text with the icons, and locking the toolbars in
place, for every window. Saved and applied to the open windows by
applyConf().
*/
class ToolbarsConfigPage : public ConfigPage
{
Q_OBJECT
public:
explicit ToolbarsConfigPage(QWidget *parent = nullptr);
void applyConf() override;
QString title() const override;
QIcon icon() const override;
private:
QComboBox *m_icon_size;
QComboBox *m_button_style;
QCheckBox *m_locked;
};
#endif // TOOLBARSCONFIGPAGE_H
+2 -4
View File
@@ -29,8 +29,7 @@
<string/>
</property>
<property name="icon">
<iconset theme="folder-open" resource="../../qelectrotech.qrc">
<normaloff>:/ico/16x16/folder-open.png</normaloff>:/ico/16x16/folder-open.png</iconset>
<iconset theme="folder-open"/>
</property>
</widget>
</item>
@@ -40,8 +39,7 @@
<string/>
</property>
<property name="icon">
<iconset theme="document-save" resource="../../qelectrotech.qrc">
<normaloff>:/ico/16x16/document-save.png</normaloff>:/ico/16x16/document-save.png</iconset>
<iconset theme="document-save"/>
</property>
</widget>
</item>
+77
View File
@@ -0,0 +1,77 @@
/*
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 "customizedialog.h"
#include "configpage/configpage.h"
#include <QDialogButtonBox>
#include <QIcon>
#include <QTabWidget>
#include <QVBoxLayout>
CustomizeDialog::CustomizeDialog(QWidget *parent) :
QDialog(parent)
{
setWindowTitle(tr("Personnaliser", "window title"));
m_tabs = new QTabWidget(this);
m_tabs->setObjectName(QStringLiteral("customizeTabs"));
m_tabs->setDocumentMode(true);
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttons, &QDialogButtonBox::accepted, this, [this]() {
applyConf();
accept();
});
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
auto *layout = new QVBoxLayout(this);
layout->addWidget(m_tabs);
layout->addWidget(buttons);
resize(1000, 700);
}
/**
@brief CustomizeDialog::addPage
Add @a page as a tab, named after its title. The dialog takes it.
*/
void CustomizeDialog::addPage(ConfigPage *page)
{
if (!page || m_pages.contains(page)) {
return;
}
m_pages << page;
m_tabs->addTab(page, page->icon(), page->title());
}
QList<ConfigPage *> CustomizeDialog::pages() const
{
return m_pages;
}
/**
@brief CustomizeDialog::applyConf
Apply every page, in tab order.
*/
void CustomizeDialog::applyConf()
{
for (ConfigPage *page : std::as_const(m_pages)) {
page->applyConf();
}
}
+51
View File
@@ -0,0 +1,51 @@
/*
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 CUSTOMIZEDIALOG_H
#define CUSTOMIZEDIALOG_H
#include <QDialog>
#include <QList>
class ConfigPage;
class QTabWidget;
/**
@brief The CustomizeDialog class
Everything about how the user works in one window, one tab per
configuration page: toolbars, what they hold, the shortcut bar, the
keyboard and the mouse gestures, like SolidWorks' Tools > Customize.
The pages are the ones of the configuration dialog, which still shows
them too. OK applies every page, Cancel none.
*/
class CustomizeDialog : public QDialog
{
Q_OBJECT
public:
explicit CustomizeDialog(QWidget *parent = nullptr);
void addPage(ConfigPage *page);
QList<ConfigPage *> pages() const;
void applyConf();
private:
QTabWidget *m_tabs;
QList<ConfigPage *> m_pages;
};
#endif // CUSTOMIZEDIALOG_H
+3 -2
View File
@@ -85,7 +85,7 @@ DiagramBgColorToolButton::DiagramBgColorToolButton(QETDiagramEditor *editor, QWi
m_recent.removeLast();
}
setMenu(new QMenu(this));
setMenu(new QMenu(tr("Couleur de fond du folio"), this));
rebuildMenu();
setSwatch(m_current);
}
@@ -186,7 +186,8 @@ void DiagramBgColorToolButton::applySystemColor()
void DiagramBgColorToolButton::chooseOtherColor()
{
const QColor c = QColorDialog::getColor(m_current, this,
tr("Choisir une couleur de fond"));
tr("Choisir une couleur de fond"),
QColorDialog::DontUseNativeDialog);
if (c.isValid()) {
applyColor(c);
}
+5 -4
View File
@@ -65,6 +65,10 @@ DiagramPropertiesDialog::DiagramPropertiesDialog(Diagram *diagram, QWidget *pare
titleblock_infos -> setReadOnly(diagram_is_read_only);
connect(titleblock_infos, &TitleBlockPropertiesWidget::openAutoNumFolioEditor, this, &DiagramPropertiesDialog::editAutoFolioNum);
border_infos -> setTitleBlockSize(titleblock_infos -> currentTitleBlockHeight(),
titleblock_infos -> currentTitleBlockEdge());
connect(titleblock_infos, &TitleBlockPropertiesWidget::titleBlockSizeChanged,
border_infos, &BorderPropertiesWidget::setTitleBlockSize);
//titleblock_infos->setMinimumSize(590,480); //Minimum Size needed for correct display
//Conductor widget
@@ -124,10 +128,7 @@ DiagramPropertiesDialog::DiagramPropertiesDialog(Diagram *diagram, QWidget *pare
{
const QList<Conductor *> conductor_list = diagram -> conductors();
for (Conductor *c : conductor_list)
{
const ConductorProperties cp = c -> properties();
c -> textItem() -> setVisible(cp.type == ConductorProperties::Multi && cp.m_show_text);
}
c -> updateTextVisibility();
for (Conductor *c : conductor_list)
c -> calculateTextItemPosition();
}
+3
View File
@@ -19,6 +19,7 @@
#include "dialogwaiting.h"
#include "ui_dialogwaiting.h"
#include "../qet.h"
#include <QIcon>
#include <QPushButton>
@@ -32,6 +33,8 @@ DialogWaiting::DialogWaiting(QWidget *parent) :
ui(new Ui::DialogWaiting)
{
ui->setupUi(this);
ui->label->setPixmap(QIcon::fromTheme("user-away-extended")
.pixmap(ui->label->maximumSize()));
QET::trackDialogGeometry(this);
setTitle( "..." );
setDetail( "..." );
-3
View File
@@ -62,9 +62,6 @@
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../../qelectrotech.qrc">:/ico/48x48/user-away-extended.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
+4 -8
View File
@@ -25,8 +25,7 @@
<string>...</string>
</property>
<property name="icon">
<iconset theme="document-save" resource="../../qelectrotech.qrc">
<normaloff>:/ico/16x16/document-save.png</normaloff>:/ico/16x16/document-save.png</iconset>
<iconset theme="document-save"/>
</property>
</widget>
</item>
@@ -39,8 +38,7 @@
<string>...</string>
</property>
<property name="icon">
<iconset theme="folder-open" resource="../../qelectrotech.qrc">
<normaloff>:/ico/16x16/folder-open.png</normaloff>:/ico/16x16/folder-open.png</iconset>
<iconset theme="folder-open"/>
</property>
</widget>
</item>
@@ -79,8 +77,7 @@
<string>...</string>
</property>
<property name="icon">
<iconset theme="object-group" resource="../../qelectrotech.qrc">
<normaloff>:/ico/16x16/object-group.png</normaloff>:/ico/16x16/object-group.png</iconset>
<iconset theme="object-group"/>
</property>
</widget>
</item>
@@ -93,8 +90,7 @@
<string>...</string>
</property>
<property name="icon">
<iconset theme="list-remove" resource="../../qelectrotech.qrc">
<normaloff>:/ico/16x16/list-remove.png</normaloff>:/ico/16x16/list-remove.png</iconset>
<iconset theme="list-remove"/>
</property>
</widget>
</item>
+2 -1
View File
@@ -1729,7 +1729,8 @@ QWidget *DynamicTextItemDelegate::createEditor(
* handling, so it didn't crash). Resolving the dialog
* before returning removes the second, competing teardown
* path entirely. */
QColor color = QColorDialog::getColor(index.data(Qt::EditRole).value<QColor>(), parent);
QColor color = QColorDialog::getColor(index.data(Qt::EditRole).value<QColor>(), parent,
QString(), QColorDialog::DontUseNativeDialog);
QWidget *w = new QWidget(parent);
if (color.isValid())
{
+451 -5
View File
@@ -18,7 +18,13 @@
#include "elementinfowidget.h"
#include "../qet.h"
#include <QCheckBox>
#include <QComboBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
#include "../autoNum/elementautonumschemecommand.h"
#include "../undocommand/freezeelementlabelcommand.h"
#include "../diagram.h"
#include "../materiallist/materiallist.h"
#include "../materiallist/materialselectiondialog.h"
@@ -28,6 +34,7 @@
#include "../dataBase/projectdatabase.h"
#include "../qetinformation.h"
#include "../qetproject.h"
#include "../ui/projectpropertiesdialog.h"
#include "../ui_elementinfowidget.h"
#include "../undocommand/changeelementinformationcommand.h"
#include "customelementinfopartwidget.h"
@@ -113,13 +120,76 @@ void ElementInfoWidget::apply()
*/
QUndoCommand* ElementInfoWidget::associatedUndo() const
{
const auto new_info = currentInfo();
auto new_info = currentInfo();
const auto old_info = m_element -> elementInformations();
if (old_info != new_info)
return (new ChangeElementInformationCommand(m_element, old_info, new_info));
const bool was_frozen = m_element->isFreezeLabel();
const bool want_frozen = m_freeze_cb ? m_freeze_cb->isChecked() : was_frozen;
return nullptr;
//A numbering chosen in the list is given to the element the way
//a placed one gets it: its formula, and the next number of the
//numbering, which moves on. That is a command of its own, first,
//so the other edited fields are changed on top of its result.
const QString chosen = chosenScheme();
const bool assign_wanted = !chosen.isEmpty()
&& chosen != followedScheme()
&& m_element->diagram();
//A number picked among the free ones, for an element which keeps its numbering
const bool number_wanted = !assign_wanted && m_number_cb && m_number_row
&& !m_number_row->isHidden() && m_number_cb->isEnabled()
&& m_number_cb->currentData().toInt() > 0
&& m_number_cb->currentData().toInt() != m_number_current
&& m_element->diagram();
if (!assign_wanted && !number_wanted && was_frozen == want_frozen)
{
if (old_info != new_info)
return (new ChangeElementInformationCommand(m_element, old_info, new_info));
return nullptr;
}
auto *macro = new QUndoCommand(
QObject::tr("Modifier les informations de l'élément : %1")
.arg(m_element->name()));
DiagramContext base_info = old_info;
bool state_frozen = was_frozen;
if (assign_wanted || number_wanted)
{
RenumberElementsCommand *assign = assign_wanted
? ElementAutoNumSchemeCommand::assign(
m_element->diagram()->project(), chosen,
{m_element.data()}, true, nullptr, macro)
: ElementAutoNumSchemeCommand::assignNumber(
m_element->diagram()->project(), m_element.data(),
m_number_cb->currentData().toInt(), nullptr, macro);
if (assign && !assign->changes().isEmpty())
{
const auto &change = assign->changes().first();
base_info = change.new_infos;
state_frozen = change.new_frozen;
for (const QString &key : {QETInformation::ELMT_FORMULA,
QETInformation::ELMT_FORMULA_ID,
QETInformation::ELMT_LABEL}) {
if (base_info.contains(key)) {
new_info.addValue(key, base_info.value(key), key != QETInformation::ELMT_FORMULA_ID);
}
}
}
}
if (base_info != new_info) {
new ChangeElementInformationCommand(m_element, base_info, new_info, macro);
}
if (state_frozen != want_frozen) {
new FreezeElementLabelCommand(m_element, state_frozen, want_frozen, macro);
}
if (macro->childCount() == 0) {
delete macro;
return nullptr;
}
return macro;
}
/**
@@ -177,6 +247,12 @@ void ElementInfoWidget::enableLiveEdit()
if (m_exclude_from_bom_cb) {
connect(m_exclude_from_bom_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
}
if (m_freeze_cb) {
connect(m_freeze_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
}
if (m_number_cb) {
connect(m_number_cb, QOverload<int>::of(&QComboBox::activated), this, &ElementInfoWidget::apply);
}
}
/**
@@ -192,6 +268,12 @@ void ElementInfoWidget::disableLiveEdit()
if (m_potential_isolating_cb) {
disconnect(m_potential_isolating_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
}
if (m_freeze_cb) {
disconnect(m_freeze_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
}
if (m_number_cb) {
disconnect(m_number_cb, QOverload<int>::of(&QComboBox::activated), this, &ElementInfoWidget::apply);
}
if (m_exclude_from_bom_cb) {
disconnect(m_exclude_from_bom_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
}
@@ -226,6 +308,9 @@ void ElementInfoWidget::buildInterface()
}
setupMaterialButtons();
setupSchemeRow();
setupNumberRow();
setupFreezeRow();
m_add_custom_property_btn = new QPushButton(tr("Ajouter une propriété personnalisée"), this);
connect(m_add_custom_property_btn, &QPushButton::clicked, this, [this]() { addCustomProperty(); });
@@ -273,7 +358,8 @@ QStringList ElementInfoWidget::predefinedKeys() const
? QETInformation::terminalElementInfoKeys()
: QETInformation::elementInfoKeys();
keys << QStringLiteral("auto_num_locked")
keys << QETInformation::ELMT_FORMULA_ID
<< QStringLiteral("auto_num_locked")
<< QStringLiteral("potential_isolating")
<< QStringLiteral("exclude_from_bom");
@@ -573,6 +659,12 @@ void ElementInfoWidget::updateUi()
for (ElementInfoPartWidget *eipw : m_eipw_list) {
eipw -> setText (element_info[eipw->key()].toString());
}
refreshSchemeRow();
if (m_freeze_cb) {
m_freeze_cb->setChecked(m_element->isFreezeLabel());
updateFreezeRow();
}
refreshNumberRow();
updateSuggestions();
// Rebuild the custom-property rows to match whatever
@@ -658,8 +750,362 @@ DiagramContext ElementInfoWidget::currentInfo() const
if (m_exclude_from_bom_cb) {
info_.addValue(QStringLiteral("exclude_from_bom"), m_exclude_from_bom_cb->isChecked() ? QStringLiteral("true") : QStringLiteral("false"));
}
//The element keeps following its numbering scheme as long as its
//formula is left as it is; a formula edited by hand is its own.
const DiagramContext &elmt_info = m_element->elementInformations();
if (elmt_info.contains(QETInformation::ELMT_FORMULA_ID)
&& !info_.value(QETInformation::ELMT_FORMULA).toString().isEmpty()
&& info_.value(QETInformation::ELMT_FORMULA) == elmt_info.value(QETInformation::ELMT_FORMULA)) {
info_.addValue(QETInformation::ELMT_FORMULA_ID,
elmt_info.value(QETInformation::ELMT_FORMULA_ID), false);
}
return info_;
}
/**
@brief ElementInfoWidget::setupSchemeRow
Add above the formula the list of the numberings of the project, the
formula of the element being the one of the numbering picked there:
no formula to type, a numbering to choose, and a button to open the
numberings without leaving this window.
*/
void ElementInfoWidget::setupSchemeRow()
{
ElementInfoPartWidget *formula = infoPartWidgetForKey(QETInformation::ELMT_FORMULA);
if (!formula || !m_element || !m_element->diagram()) {
return;
}
m_scheme_row = new QWidget(this);
auto *layout = new QHBoxLayout(m_scheme_row);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(new QLabel(tr("Numérotation automatique"), m_scheme_row));
m_scheme_cb = new QComboBox(m_scheme_row);
m_scheme_cb->setObjectName(QStringLiteral("m_scheme_cb"));
m_scheme_cb->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
layout->addWidget(m_scheme_cb, 1);
auto *open = new QPushButton(tr("…"), m_scheme_row);
open->setToolTip(tr("Ouvrir les numérotations d'éléments du projet"));
open->setMaximumWidth(32);
layout->addWidget(open);
ui->scroll_vlayout->insertWidget(ui->scroll_vlayout->indexOf(formula), m_scheme_row);
//The formula is shown, not typed: it comes from the numbering
formula->setDisabled(true);
connect(m_scheme_cb, QOverload<int>::of(&QComboBox::activated),
this, &ElementInfoWidget::schemeChosen);
connect(m_scheme_cb, QOverload<int>::of(&QComboBox::activated),
this, &ElementInfoWidget::updateNumberRow);
connect(open, &QPushButton::clicked, this, &ElementInfoWidget::openSchemePage);
}
/**
@brief ElementInfoWidget::setupFreezeRow
Add below the label a check box which freezes it: a frozen label is
not touched by the numbering of elements, and its number is not given
to another element.
*/
void ElementInfoWidget::setupFreezeRow()
{
ElementInfoPartWidget *label = infoPartWidgetForKey(QETInformation::ELMT_LABEL);
if (!label || !m_element) {
return;
}
m_freeze_cb = new QCheckBox(tr("Figer le nom"), this);
m_freeze_cb->setToolTip(tr("Un nom figé n'est pas changé par la numérotation automatique, "
"et son numéro n'est pas donné à un autre élément."));
ui->scroll_vlayout->insertWidget(ui->scroll_vlayout->indexOf(label) + 1, m_freeze_cb);
connect(m_freeze_cb, &QCheckBox::toggled, this, &ElementInfoWidget::updateNumberRow);
}
/**
@brief ElementInfoWidget::updateFreezeRow
Freezing means something for a label a formula gives: the box can be
ticked when there is one, and always unticked, so that a frozen
element can be given back.
*/
void ElementInfoWidget::updateFreezeRow()
{
ElementInfoPartWidget *formula = infoPartWidgetForKey(QETInformation::ELMT_FORMULA);
if (!m_freeze_cb || !formula) {
return;
}
m_freeze_cb->setEnabled(!formula->text().isEmpty() || m_freeze_cb->isChecked());
updateNumberRow();
}
/**
@brief ElementInfoWidget::setupNumberRow
Add under the numbering a list of the numbers the element may be given
by hand: its own, then the free ones, each with the label it would give.
For an element which follows a numbering whose numbers are one sequence.
*/
void ElementInfoWidget::setupNumberRow()
{
if (!m_scheme_row || !m_element || !m_element->diagram()) {
return;
}
m_number_row = new QWidget(this);
auto *layout = new QHBoxLayout(m_number_row);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(new QLabel(tr("Numéro"), m_number_row));
m_number_cb = new QComboBox(m_number_row);
m_number_cb->setObjectName(QStringLiteral("m_number_cb"));
m_number_cb->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
m_number_cb->setToolTip(tr("Seuls les numéros libres sont proposés : un élément qui doit garder son "
"numéro réel peut le retrouver si personne ne l'a."));
layout->addWidget(m_number_cb, 1);
ui->scroll_vlayout->insertWidget(ui->scroll_vlayout->indexOf(m_scheme_row) + 1, m_number_row);
m_number_row->hide();
connect(m_number_cb, QOverload<int>::of(&QComboBox::activated), this, &ElementInfoWidget::numberChosen);
}
/**
@brief ElementInfoWidget::refreshNumberRow
Fill the list of numbers: the element's own, then the free ones. Hidden
when the element follows no numbering, or one whose numbers are not one
sequence.
*/
void ElementInfoWidget::refreshNumberRow()
{
if (!m_number_cb || !m_element || !m_element->diagram()) {
return;
}
QETProject *project = m_element->diagram()->project();
const QString title = followedScheme();
const auto support = title.isEmpty()
? ElementAutoNumSchemeCommand::NumberSupport()
: ElementAutoNumSchemeCommand::numberSupport(project->elementAutoNum().value(title));
const QSignalBlocker blocker(m_number_cb);
m_number_cb->clear();
m_number_current = -1;
if (!support.supported) {
m_number_row->hide();
return;
}
if (const auto own = ElementAutoNumSchemeCommand::numberOf(support, m_element)) {
m_number_current = *own;
m_number_cb->addItem(tr("%1 (actuel)").arg(*own), *own);
} else {
m_number_cb->addItem(tr("— (numéro inconnu)"), 0);
}
for (int n : ElementAutoNumSchemeCommand::freeNumbers(project, title, m_element)) {
if (n == m_number_current) continue;
m_number_cb->addItem(tr("%1 → %2").arg(n).arg(
ElementAutoNumSchemeCommand::labelForNumber(project, title, m_element, n)), n);
}
m_number_cb->setCurrentIndex(0);
m_number_row->show();
updateNumberRow();
}
/**
@brief ElementInfoWidget::updateNumberRow
A number is chosen only for an element which keeps its numbering and
whose name is not frozen.
*/
void ElementInfoWidget::updateNumberRow()
{
if (!m_number_cb || !m_number_row || m_number_row->isHidden()) {
return;
}
const bool keeps = chosenScheme() == followedScheme() && !chosenScheme().isEmpty();
const bool frozen = m_freeze_cb ? m_freeze_cb->isChecked() : m_element->isFreezeLabel();
m_number_cb->setEnabled(keeps && !frozen);
m_number_cb->setToolTip(
frozen ? tr("Le nom est figé : dégelez-le pour changer son numéro.")
: !keeps ? tr("Le numéro se choisit quand l'élément garde sa numérotation.")
: tr("Seuls les numéros libres sont proposés : un élément qui doit garder son "
"numéro réel peut le retrouver si personne ne l'a."));
}
/**
@brief ElementInfoWidget::numberChosen
Show the label the number picked gives, in the field of the label.
*/
void ElementInfoWidget::numberChosen()
{
ElementInfoPartWidget *label = infoPartWidgetForKey(QETInformation::ELMT_LABEL);
if (!label || !m_element || !m_element->diagram()) {
return;
}
const int number = m_number_cb->currentData().toInt();
if (number <= 0 || number == m_number_current) {
label->setText(m_element->elementInformations().value(QETInformation::ELMT_LABEL).toString());
return;
}
label->setText(ElementAutoNumSchemeCommand::labelForNumber(
m_element->diagram()->project(), followedScheme(), m_element, number));
}
/**
@brief ElementInfoWidget::followedScheme
@return the title of the numbering the element follows, empty if none
*/
QString ElementInfoWidget::followedScheme() const
{
if (!m_element || !m_element->diagram()) {
return QString();
}
return m_element->diagram()->project()->elementAutoNumTitle(
QUuid(m_element->elementInformations()
.value(QETInformation::ELMT_FORMULA_ID).toString()));
}
/**
@brief ElementInfoWidget::chosenScheme
@return the title of the numbering picked in the list, empty if the
element is to follow none
*/
QString ElementInfoWidget::chosenScheme() const
{
const QString data = m_scheme_cb ? m_scheme_cb->currentData().toString() : QString();
return data == QLatin1String("\x01") ? QString() : data;
}
/**
@brief ElementInfoWidget::refreshSchemeRow
Fill the list with the numberings of the project and show the one the
element follows. An element with a formula of its own, followed from
no numbering, gets an entry for it, so that it is neither hidden nor
lost by looking at it.
*/
void ElementInfoWidget::refreshSchemeRow()
{
if (!m_scheme_cb || !m_element || !m_element->diagram()) {
return;
}
QETProject *project = m_element->diagram()->project();
const QSignalBlocker blocker(m_scheme_cb);
m_scheme_cb->clear();
m_scheme_cb->addItem(tr("Aucune (nom saisi à la main)"), QString());
QStringList titles(project->elementAutoNum().keys());
titles.sort(Qt::CaseInsensitive);
for (const QString &title : titles) {
m_scheme_cb->addItem(title, title);
}
const QString followed = followedScheme();
if (!followed.isEmpty()) {
m_scheme_cb->setCurrentIndex(m_scheme_cb->findData(followed));
m_scheme_index = m_scheme_cb->currentIndex();
return;
}
const QString formula = m_element->elementInformations()
.value(QETInformation::ELMT_FORMULA).toString();
if (!formula.isEmpty()) {
//Same data as "none" would clash: the entry is for display only
m_scheme_cb->insertItem(1, tr("Formule propre : %1").arg(formula), QStringLiteral("\x01"));
m_scheme_cb->setCurrentIndex(1);
}
m_scheme_index = m_scheme_cb->currentIndex();
}
/**
@brief ElementInfoWidget::schemeChosen
The user picked an entry of the list.
The formula shown is the one of the numbering picked; the label is
typed only when there is no formula. Leaving a numbering, or the
element's own formula, for "none" also empties the label: what stays
would pass for a number the numbering gave, and could be given again
to another element. Going back to what the element has at present
gives its label back.
An element whose label is frozen is changed only if the user agrees.
*/
void ElementInfoWidget::schemeChosen()
{
ElementInfoPartWidget *formula = infoPartWidgetForKey(QETInformation::ELMT_FORMULA);
ElementInfoPartWidget *label = infoPartWidgetForKey(QETInformation::ELMT_LABEL);
if (!formula || !label || !m_element || !m_element->diagram()) {
return;
}
const DiagramContext info = m_element->elementInformations();
const QString own_formula = info.value(QETInformation::ELMT_FORMULA).toString();
const bool own_entry = m_scheme_cb->currentData().toString() == QLatin1String("\x01");
const QString chosen = chosenScheme();
const bool unchanged = own_entry
|| (!chosen.isEmpty() && chosen == followedScheme())
|| (chosen.isEmpty() && own_formula.isEmpty());
const bool frozen_now = m_freeze_cb ? m_freeze_cb->isChecked() : m_element->isFreezeLabel();
if (!unchanged && frozen_now)
{
const auto answer = QET::QetMessageBox::question(
this,
tr("Nom figé"),
tr("Le nom de cet élément est figé.\n"
"Changer sa numérotation le remplacera ou l'effacera.\n\nContinuer ?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
if (answer != QMessageBox::Yes) {
m_scheme_cb->setCurrentIndex(m_scheme_index);
return;
}
}
m_scheme_index = m_scheme_cb->currentIndex();
//The user agreed: the element is no longer protected
if (!unchanged && m_freeze_cb) {
m_freeze_cb->setChecked(false);
}
//Setting the fields one by one must not apply each step
const bool live = m_live_edit;
if (live) disableLiveEdit();
if (unchanged) {
formula->setText(own_formula);
label->setText(info.value(QETInformation::ELMT_LABEL).toString());
} else if (chosen.isEmpty()) {
formula->setText(QString());
if (!own_formula.isEmpty()) {
label->setText(QString());
}
} else {
formula->setText(m_element->diagram()->project()->elementAutoNumFormula(chosen));
}
updateFreezeRow();
if (live) {
enableLiveEdit();
apply();
}
}
/**
@brief ElementInfoWidget::openSchemePage
Open the numberings of the project, on the one of elements, then show
again the list, which may have changed.
*/
void ElementInfoWidget::openSchemePage()
{
if (!m_element || !m_element->diagram()) {
return;
}
const QString before = chosenScheme();
ProjectPropertiesDialog ppd(m_element->diagram()->project(), this);
ppd.setCurrentPage(ProjectPropertiesDialog::Autonum);
ppd.changeToElement();
ppd.exec();
refreshSchemeRow();
const int index = m_scheme_cb->findData(before);
if (index >= 0) {
m_scheme_cb->setCurrentIndex(index);
}
}
/**
@brief ElementInfoWidget::firstActivated
Slot activated when this widget is show.
+21
View File
@@ -29,7 +29,9 @@ class ElementInfoPartWidget;
class CustomElementInfoPartWidget;
class ChangeElementInformationCommand;
class QCheckBox;
class QComboBox;
class QPushButton;
class QWidget;
struct MaterialRecord;
namespace Ui {
@@ -71,6 +73,18 @@ class ElementInfoWidget : public AbstractElementPropertiesEditorWidget
ElementInfoPartWidget *infoPartWidgetForKey(const QString &key) const;
QStringList predefinedKeys() const;
void updateSuggestions();
void setupSchemeRow();
void setupFreezeRow();
void setupNumberRow();
void refreshNumberRow();
void updateNumberRow();
void numberChosen();
void updateFreezeRow();
void refreshSchemeRow();
void schemeChosen();
void openSchemePage();
QString chosenScheme() const;
QString followedScheme() const;
private slots:
void firstActivated();
@@ -86,6 +100,13 @@ class ElementInfoWidget : public AbstractElementPropertiesEditorWidget
QPushButton *m_add_custom_property_btn = nullptr;
QCheckBox *m_potential_isolating_cb = nullptr;
QCheckBox *m_exclude_from_bom_cb = nullptr;
QComboBox *m_scheme_cb = nullptr;
QCheckBox *m_freeze_cb = nullptr;
QWidget *m_number_row = nullptr;
QComboBox *m_number_cb = nullptr;
int m_number_current = -1; ///< the number the element has, -1 if unknown
int m_scheme_index = 0;
QWidget *m_scheme_row = nullptr;
bool m_first_activation;
bool m_ui_builded = false;
};
+1 -1
View File
@@ -367,7 +367,7 @@ void ElementPropertiesWidget::addGeneralWidget()
*/
QWidget *ElementPropertiesWidget::generalWidget()
{
QString description_string(tr("Élement\n"));
QString description_string(tr("Élément\n"));
// some element characteristic
description_string += QString(tr("Nom : %1\n")).arg(m_element -> name());
+49 -4
View File
@@ -22,6 +22,9 @@
#include "../qetgraphicsitem/diagramimageitem.h"
#include "../qeticons.h"
#include "../ui_imagepropertieswidget.h"
#include <QSignalBlocker>
#include <QDialog>
#include <QEvent>
/**
@brief ImagePropertiesWidget::ImagePropertiesWidget
@@ -53,6 +56,19 @@ ImagePropertiesWidget::~ImagePropertiesWidget()
delete ui;
}
bool ImagePropertiesWidget::event(QEvent *event)
{
// The generic properties dialog resets on its Cancel button, but its
// title-bar close and Escape reach reject() directly. Roll back the
// same preview in all rejection paths, without changing other editors.
if (event->type() == QEvent::ParentChange) {
if (auto *dialog = qobject_cast<QDialog *>(parentWidget())) {
connect(dialog, &QDialog::rejected, this, &ImagePropertiesWidget::reset, Qt::UniqueConnection);
}
}
return PropertiesEditorWidget::event(event);
}
/**
@brief ImagePropertiesWidget::setImageItem
Set the image to edit properties
@@ -67,12 +83,15 @@ void ImagePropertiesWidget::setImageItem(DiagramImageItem *image)
{
disconnect(m_image, &DiagramImageItem::transformChanged, this, &ImagePropertiesWidget::updateUi);
disconnect(m_image, &DiagramImageItem::labelChanged, this, &ImagePropertiesWidget::updateUi);
disconnect(m_image, &DiagramImageItem::adaptToDarkThemeChanged, this, &ImagePropertiesWidget::updateUi);
}
m_image = image;
connect(m_image, &DiagramImageItem::transformChanged, this, &ImagePropertiesWidget::updateUi);
connect(m_image, &DiagramImageItem::labelChanged, this, &ImagePropertiesWidget::updateUi);
connect(m_image, &DiagramImageItem::adaptToDarkThemeChanged, this, &ImagePropertiesWidget::updateUi);
m_movable = image->isMovable();
m_adapt_to_dark_theme = image->adaptToDarkTheme();
m_scaleX = m_image->scaleFactorX();
m_scaleY = m_image->scaleFactorY();
m_rotation = m_image->rotationAngle();
@@ -107,6 +126,7 @@ void ImagePropertiesWidget::apply()
m_skewX = m_image->skewX();
m_skewY = m_image->skewY();
m_label = m_image->label();
m_adapt_to_dark_theme = m_image->adaptToDarkTheme();
}
/**
@@ -124,6 +144,9 @@ void ImagePropertiesWidget::reset()
m_image->setSkewY(m_skewY);
m_image->setLabel(m_label);
m_image->setMovable(m_movable);
m_preview_dark_theme = true;
m_image->setAdaptToDarkTheme(m_adapt_to_dark_theme);
m_preview_dark_theme = false;
updateUi();
}
@@ -146,6 +169,7 @@ bool ImagePropertiesWidget::setLiveEdit(bool live_edit)
connect (ui->m_skew_x_sb, &QDoubleSpinBox::editingFinished, this, &ImagePropertiesWidget::apply);
connect (ui->m_skew_y_sb, &QDoubleSpinBox::editingFinished, this, &ImagePropertiesWidget::apply);
connect (ui->m_label_le, &QLineEdit::editingFinished, this, &ImagePropertiesWidget::apply);
connect (ui->m_adapt_to_dark_theme_cb, &QCheckBox::clicked, this, &ImagePropertiesWidget::apply);
}
else
{
@@ -155,6 +179,7 @@ bool ImagePropertiesWidget::setLiveEdit(bool live_edit)
disconnect (ui->m_skew_x_sb, &QDoubleSpinBox::editingFinished, this, &ImagePropertiesWidget::apply);
disconnect (ui->m_skew_y_sb, &QDoubleSpinBox::editingFinished, this, &ImagePropertiesWidget::apply);
disconnect (ui->m_label_le, &QLineEdit::editingFinished, this, &ImagePropertiesWidget::apply);
disconnect (ui->m_adapt_to_dark_theme_cb, &QCheckBox::clicked, this, &ImagePropertiesWidget::apply);
}
return true;
@@ -179,7 +204,7 @@ QUndoCommand* ImagePropertiesWidget::associatedUndo() const
const qreal newSkewX = ui->m_skew_x_sb->value();
const qreal newSkewY = ui->m_skew_y_sb->value();
QPropertyUndoCommand *undo = nullptr;
QUndoCommand *undo = nullptr;
auto chain = [&](const char *property, qreal oldValue, qreal newValue, const QString &text)
{
if (qFuzzyCompare(oldValue, newValue))
@@ -188,9 +213,10 @@ QUndoCommand* ImagePropertiesWidget::associatedUndo() const
new QPropertyUndoCommand(m_image, property, oldValue, newValue, undo);
else
{
undo = new QPropertyUndoCommand(m_image, property, oldValue, newValue);
undo->enableAnimation();
undo->setText(text);
auto *change = new QPropertyUndoCommand(m_image, property, oldValue, newValue);
change->enableAnimation();
change->setText(text);
undo = change;
}
};
@@ -213,6 +239,14 @@ QUndoCommand* ImagePropertiesWidget::associatedUndo() const
}
}
const bool adapt = ui->m_adapt_to_dark_theme_cb->isChecked();
if (adapt != m_adapt_to_dark_theme) {
if (undo) new QPropertyUndoCommand(m_image, "adaptToDarkTheme", m_adapt_to_dark_theme, adapt, undo);
else {
undo = new QUndoCommand(tr("Modifier l'adaptation d'une image au thème sombre"));
new QPropertyUndoCommand(m_image, "adaptToDarkTheme", m_adapt_to_dark_theme, adapt, undo);
}
}
return undo;
}
@@ -233,6 +267,9 @@ void ImagePropertiesWidget::updateUi()
// signal from a resize handle used directly on the canvas while
// this dialog is open).
if (!m_image || m_updating_ratio) return;
if (m_live_edit && !m_preview_dark_theme) m_adapt_to_dark_theme = m_image->adaptToDarkTheme();
const QSignalBlocker darkThemeBlocker(ui->m_adapt_to_dark_theme_cb);
ui->m_adapt_to_dark_theme_cb->setChecked(m_image->adaptToDarkTheme());
m_updating_ratio = true;
ui->m_width_sb->setValue(m_image->scaleFactorX() * 100.0);
@@ -395,3 +432,11 @@ void ImagePropertiesWidget::on_m_lock_pos_cb_clicked()
{
m_image->setMovable(!ui->m_lock_pos_cb->isChecked());
}
void ImagePropertiesWidget::on_m_adapt_to_dark_theme_cb_toggled(bool checked)
{
if (!m_image) return;
m_preview_dark_theme = true;
m_image->setAdaptToDarkTheme(checked);
m_preview_dark_theme = false;
}
+5
View File
@@ -43,6 +43,8 @@ class ImagePropertiesWidget : public PropertiesEditorWidget
void reset() override;
bool setLiveEdit(bool live_edit) override;
QUndoCommand* associatedUndo() const override;
protected:
bool event(QEvent *event) override;
private:
void updateUi() override;
@@ -56,11 +58,14 @@ class ImagePropertiesWidget : public PropertiesEditorWidget
void on_m_lock_ratio_tb_toggled(bool checked);
void on_m_restore_ratio_pb_clicked();
void on_m_lock_pos_cb_clicked();
void on_m_adapt_to_dark_theme_cb_toggled(bool checked);
private:
Ui::ImagePropertiesWidget *ui;
DiagramImageItem *m_image;
bool m_movable;
bool m_adapt_to_dark_theme = false;
bool m_preview_dark_theme = false;
// All tracked independently -- the image may already be
// non-uniformly scaled, rotated, and/or skewed via the resize/
// rotate/skew handles before this dialog is even opened, and
+11 -1
View File
@@ -173,7 +173,17 @@
</property>
</widget>
</item>
<item row="8" column="0">
<item row="8" column="0" colspan="3">
<widget class="QCheckBox" name="m_adapt_to_dark_theme_cb">
<property name="text">
<string>Adapter l’image au thème sombre</string>
</property>
<property name="toolTip">
<string>Adapte les couleurs à l’affichage sombre uniquement. L’image originale, les exports et les impressions restent inchangés.</string>
</property>
</widget>
</item>
<item row="9" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
+47
View File
@@ -27,6 +27,17 @@
#include <QLineEdit>
#include <QtGlobal>
/**
@return width as shown in the width spin box: whole pixels, 0 for the
automatic width ("Auto"). The box starts at 0, so no number means Auto
and the first step up is 1 px; IndependentTextItem::setTextWidth()
treats 0 as -1, the automatic width, so the two never disagree.
*/
static int spinBoxWidth(qreal width)
{
return width > 0 ? qRound(width) : 0;
}
/**
@brief IndiTextPropertiesWidget::IndiTextPropertiesWidget
@param text : the text to edit
@@ -84,6 +95,7 @@ void IndiTextPropertiesWidget::setText(IndependentTextItem *text)
m_connect_list << connect(m_text.data(), &IndependentTextItem::rotationChanged, this, &IndiTextPropertiesWidget::updateUi);
m_connect_list << connect(m_text.data(), &IndependentTextItem::fontChanged, this, &IndiTextPropertiesWidget::updateUi);
m_connect_list << connect(m_text.data(), &IndependentTextItem::textEdited, this, &IndiTextPropertiesWidget::updateUi);
m_connect_list << connect(m_text.data(), &IndependentTextItem::textWidthChanged, this, &IndiTextPropertiesWidget::updateUi);
updateUi();
}
@@ -209,6 +221,10 @@ QUndoCommand *IndiTextPropertiesWidget::associatedUndo() const
undo = new QPropertyUndoCommand(m_text.data(), "font", m_text->font(), m_selected_font);
undo->setText(tr("Modifier la police d'un champ texte"));
}
if (ui->m_width_sb->value() != spinBoxWidth(m_text->textWidth())) {
undo = new QPropertyUndoCommand(m_text.data(), "textWidth", m_text->textWidth(), qreal(ui->m_width_sb->value()));
undo->setText(tr("Modifier la largeur d'un champ texte"));
}
return undo;
}
@@ -218,9 +234,11 @@ QUndoCommand *IndiTextPropertiesWidget::associatedUndo() const
bool size_equal = true;
bool angle_equal = true;
bool font_equal = true;
bool width_equal = true;
qreal rotation_ = m_text_list.first()->rotation();
int size_ = m_text_list.first()->font().pointSize();
QFont font_ = m_text_list.first()->font();
int width_ = spinBoxWidth(m_text_list.first()->textWidth());
for (QPointer<IndependentTextItem> piti : m_text_list)
{
if (piti->rotation() != rotation_) {
@@ -232,6 +250,9 @@ QUndoCommand *IndiTextPropertiesWidget::associatedUndo() const
if (piti->font() != font_) {
font_equal = false;
}
if (spinBoxWidth(piti->textWidth()) != width_) {
width_equal = false;
}
}
if ((angle_equal && (ui->m_angle_sb->value() != rotation_)) ||
@@ -279,6 +300,20 @@ QUndoCommand *IndiTextPropertiesWidget::associatedUndo() const
}
}
}
else if ((width_equal && (ui->m_width_sb->value() != width_)) ||
(!width_equal && (ui->m_width_sb->value() != ui->m_width_sb->minimum())))
{
for (QPointer<IndependentTextItem> piti : m_text_list)
{
if (piti)
{
if (!parent_undo) {
parent_undo = new QUndoCommand(tr("Modifier la largeur de plusieurs champs texte"));
}
new QPropertyUndoCommand(piti.data(), "textWidth", piti->textWidth(), qreal(ui->m_width_sb->value()), parent_undo);
}
}
}
return parent_undo;
}
}
@@ -307,6 +342,9 @@ QUndoCommand *IndiTextPropertiesWidget::associatedUndo() const
if (m_font_is_selected && m_selected_font != m_text->font()) {
new QPropertyUndoCommand(m_text.data(), "font", m_text->font(), m_selected_font, undo);
}
if (ui->m_width_sb->value() != spinBoxWidth(m_text->textWidth())) {
new QPropertyUndoCommand(m_text.data(), "textWidth", m_text->textWidth(), qreal(ui->m_width_sb->value()), undo);
}
if (undo->childCount()) {
return undo;
@@ -337,6 +375,7 @@ void IndiTextPropertiesWidget::setUpEditConnection()
m_edit_connection << connect(ui->m_line_edit, &QLineEdit::textEdited, this, &IndiTextPropertiesWidget::apply);
}
m_edit_connection << connect(ui->m_angle_sb, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &IndiTextPropertiesWidget::apply);
m_edit_connection << connect(ui->m_width_sb, QOverload<int>::of(&QSpinBox::valueChanged), this, &IndiTextPropertiesWidget::apply);
m_edit_connection << connect(ui->m_size_sb, QOverload<int>::of(&QSpinBox::valueChanged), [this]()
{
this->m_selected_font.setPointSize(ui->m_size_sb->value());
@@ -372,6 +411,7 @@ void IndiTextPropertiesWidget::updateUi()
ui->m_line_edit->setText(m_text->toPlainText());
ui->m_angle_sb->setValue(m_text->rotation());
ui->m_size_sb->setValue(m_text->font().pointSize());
ui->m_width_sb->setValue(spinBoxWidth(m_text->textWidth()));
ui->m_line_edit->setDisabled(m_text->isHtml() ? true : false);
ui->m_size_sb->setDisabled(m_text->isHtml() ? true : false);
@@ -385,9 +425,11 @@ void IndiTextPropertiesWidget::updateUi()
bool size_equal = true;
bool angle_equal = true;
bool font_equal = true;
bool width_equal = true;
qreal rotation_ = m_text_list.first()->rotation();
int size_ = m_text_list.first()->font().pointSize();
QFont font_ = m_text_list.first()->font();
int width_ = spinBoxWidth(m_text_list.first()->textWidth());
for (QPointer<IndependentTextItem> piti : m_text_list)
{
@@ -400,8 +442,13 @@ void IndiTextPropertiesWidget::updateUi()
if (piti->font() != font_) {
font_equal = false;
}
if (spinBoxWidth(piti->textWidth()) != width_) {
width_equal = false;
}
}
ui->m_angle_sb->setValue(angle_equal ? rotation_ : 0);
//Different widths show the minimum, as the other boxes do
ui->m_width_sb->setValue(width_equal ? width_ : ui->m_width_sb->minimum());
bool valid_ = true;
for (QPointer<IndependentTextItem> piti : m_text_list) {
+36 -3
View File
@@ -64,7 +64,7 @@
</property>
</widget>
</item>
<item row="6" column="0">
<item row="7" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -126,7 +126,39 @@
</property>
</widget>
</item>
<item row="4" column="0" colspan="4">
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Largeur :</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="m_width_sb">
<property name="toolTip">
<string>Largeur du texte, le texte passe à la ligne. Auto : pas de retour à la ligne automatique</string>
</property>
<property name="specialValueText">
<string>Auto</string>
</property>
<property name="suffix">
<string>px</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="5" column="0" colspan="4">
<widget class="QLabel" name="m_label">
<property name="text">
<string>Le contenu, la taille et la police du texte ne peuvent être modifié car formaté en html.
@@ -140,7 +172,7 @@ Veuillez utiliser l'éditeur avancé pour cela.</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="4">
<item row="6" column="0" colspan="4">
<widget class="QPushButton" name="m_break_html_pb">
<property name="text">
<string>Cliquez ici pour annuler le formatage html</string>
@@ -171,6 +203,7 @@ Veuillez utiliser l'éditeur avancé pour cela.</string>
<tabstop>m_y_sb</tabstop>
<tabstop>m_angle_sb</tabstop>
<tabstop>m_size_sb</tabstop>
<tabstop>m_width_sb</tabstop>
<tabstop>m_break_html_pb</tabstop>
</tabstops>
<resources/>
+5
View File
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "jumptoelementdialog.h"
#include "../qetinformation.h"
#include "../diagram.h"
#include "../diagramview.h"
@@ -99,6 +100,10 @@ void JumpToElementDialog::buildCandidates()
search_parts << label << name;
const DiagramContext infos = element->elementInformations();
for (const QString &key : infos.keys()) {
//An internal id, not something the user can read or type
if (key == QETInformation::ELMT_FORMULA_ID) {
continue;
}
search_parts << infos.value(key).toString();
}
candidate.search_text = search_parts.join(QLatin1Char(' ')).toLower();
+2 -4
View File
@@ -136,8 +136,7 @@
<string/>
</property>
<property name="icon">
<iconset theme="go-up" resource="../../qelectrotech.qrc">
<normaloff>:/ico/16x16/go-up.png</normaloff>:/ico/16x16/go-up.png</iconset>
<iconset theme="go-up"/>
</property>
</widget>
</item>
@@ -150,8 +149,7 @@
<string/>
</property>
<property name="icon">
<iconset theme="go-down" resource="../../qelectrotech.qrc">
<normaloff>:/ico/16x16/go-down.png</normaloff>:/ico/16x16/go-down.png</iconset>
<iconset theme="go-down"/>
</property>
</widget>
</item>
+31 -36
View File
@@ -17,6 +17,7 @@
*/
#include "multipastedialog.h"
#include "../autoNum/elementautonumschemecommand.h"
#include "../qetproject.h"
#include "../conductorautonumerotation.h"
#include "../diagram.h"
@@ -105,22 +106,28 @@ void MultiPasteDialog::on_m_button_box_accepted()
{
m_diagram->undoStack().beginMacro(tr("Multi-collage"));
QSettings settings;
bool erase_label = settings.value("diagramcommands/erase-label-on-copy", true).toBool();
//Ensure when 'auto_num' is checked, the settings 'save_label' is to true.
//Because in the class PasteDiagramCommand, if the settings 'save_label' is to false,
//the function redo of PasteDiagramCommand, clear the formula and the label of the pasted element
//and so the auto_num below do nothing (there is not a formula to compare)
if(ui->m_auto_num_cb->isChecked())
settings.setValue("diagramcommands/erase-label-on-copy", false);
QETProject *project = m_diagram->project();
//The element numberings the copies follow, read now: the paste
//erases the formulas of its elements, unless the preference
//keeps them, and the numbering below is meant to work either way
QList<QMap<QString, QVector<Element *>>> copy_schemes;
for(const DiagramContent &dc : std::as_const(m_pasted_content_list))
{
copy_schemes << (ui->m_auto_num_cb->isChecked()
? ElementAutoNumSchemeCommand::pastedSchemes(project, dc.m_elements)
: QMap<QString, QVector<Element *>>());
}
m_diagram->clearSelection();
m_diagram->undoStack().push(new PasteDiagramCommand(m_diagram, m_pasted_content));
//The copies are numbered by this dialog, copy by copy, below
auto *paste = new PasteDiagramCommand(m_diagram, m_pasted_content);
paste->setAutoNumbering(false);
m_diagram->undoStack().push(paste);
for(DiagramContent dc : m_pasted_content_list)
for(int copy = 0 ; copy < m_pasted_content_list.size() ; ++copy)
{
const DiagramContent &dc = m_pasted_content_list.at(copy);
QList<Element *> pasted_elements = dc.m_elements;
//Sort the list element by there pos (top -> bottom)
std::sort(pasted_elements.begin(), pasted_elements.end(), [](Element *a, Element *b){return (a->pos().y() < b->pos().y());});
@@ -147,32 +154,21 @@ void MultiPasteDialog::on_m_button_box_accepted()
}
}
//Set up the label of element
//Instead of use the current autonum of project,
//we try to fetch the same formula of the pasted element, in the several autonum of the project
//for apply the good formula for each elements
if(ui->m_auto_num_cb->isChecked())
//Number the elements of this copy with the numberings they
//follow: the next numbers, in the order of the Renumber
//button, the counters moving on, so that the next copy goes
//on from there. The project's current numbering is not
//changed, and labels which other elements keep are not given.
if(!copy_schemes.at(copy).isEmpty())
{
for(Element *elmt : pasted_elements)
{
QString formula = elmt->elementInformations()["formula"].toString();
if(!formula.isEmpty())
{
QHash <QString, NumerotationContext> autonums = m_diagram->project()->elementAutoNum();
QHashIterator<QString, NumerotationContext> hash_iterator(autonums);
while(hash_iterator.hasNext())
{
hash_iterator.next();
if(autonum::numerotationContextToFormula(hash_iterator.value()) == formula)
{
m_diagram->project()->setCurrrentElementAutonum(hash_iterator.key());
elmt->setUpFormula();
}
}
}
}
auto *numbering = new QUndoCommand(tr("Numéroter les éléments collés"));
ElementAutoNumSchemeCommand::numberPasted(project, copy_schemes.at(copy), numbering);
if(numbering->childCount())
m_diagram->undoStack().push(numbering);
else
delete numbering;
}
//Like elements, we compare formula of pasted conductor with the autonums available in the project.
if(ui->m_auto_num_cond_cb->isChecked())
{
@@ -216,7 +212,6 @@ void MultiPasteDialog::on_m_button_box_accepted()
m_diagram->adjustSceneRect();
m_accept = true;
settings.setValue("diagramcommands/erase-label-on-copy", erase_label);
m_diagram->undoStack().endMacro();
}
}
+2 -1
View File
@@ -48,7 +48,8 @@ void KColorButton::setColor(const QColor &color)
void KColorButton::chooseColor()
{
const auto selected = QColorDialog::getColor(m_color, this);
const auto selected = QColorDialog::getColor(m_color, this, QString(),
QColorDialog::DontUseNativeDialog);
if (!selected.isValid() || selected == m_color) {
return;
}
+12
View File
@@ -107,3 +107,15 @@ void ProjectPropertiesDialog::changeToCable()
m_properties_dialog->pages.at(2));
autoNumPage->changeToCableTab();
}
/**
@brief ProjectPropertiesDialog::changeToElement
Change the current displayed tab to the element numberings tab.
*/
void ProjectPropertiesDialog::changeToElement()
{
ProjectAutoNumConfigPage *autoNumPage =
static_cast <ProjectAutoNumConfigPage*>(
m_properties_dialog->pages.at(2));
autoNumPage->changeToTab(2);
}
+1
View File
@@ -43,6 +43,7 @@ class ProjectPropertiesDialog : public QObject {
void changeToFolio();
///Show the automatic numbering page with the cable tab on top
void changeToCable();
void changeToElement();
private:
ConfigDialog *m_properties_dialog;
+7 -8
View File
@@ -4,6 +4,7 @@
#include "../qetproject.h"
#include "../diagram.h"
#include "../qetgraphicsitem/element.h"
#include "../positionorder.h"
#include "../undocommand/changeelementinformationcommand.h"
#include "../qet.h"
#include <QUndoCommand>
@@ -210,14 +211,12 @@ QUndoCommand* TerminalNumberingDialog::getUndoCommand(QETProject *project) const
// Then sort by folio (page) index
if (a.folioIndex != b.folioIndex) return a.folioIndex < b.folioIndex;
// Finally sort by coordinates (with a 1.0px tolerance to handle slight misalignments)
if (axisX) {
if (qAbs(a.x - b.x) > 1.0) return a.x < b.x;
return a.y < b.y;
} else {
if (qAbs(a.y - b.y) > 1.0) return a.y < b.y;
return a.x < b.x;
}
// Finally sort by coordinates, rounded to whole pixels so that slight
// misalignments count as aligned (a tolerance test is not transitive,
// which std::sort requires)
const QPointF pa(a.x, a.y), pb(b.x, b.y);
return axisX ? PositionOrder::roundedXThenY(pa, pb)
: PositionOrder::roundedYThenX(pa, pb);
});
// 4. Generate new numbering and create the undo command macro
+2 -4
View File
@@ -33,8 +33,7 @@
<string>Télechargement</string>
</property>
<property name="icon">
<iconset theme="edit-download" resource="../../qelectrotech.qrc">
<normaloff>:/ico/16x16/edit-download.png</normaloff>:/ico/16x16/edit-download.png</iconset>
<iconset theme="edit-download"/>
</property>
</widget>
</item>
@@ -44,8 +43,7 @@
<string>Dossier installation</string>
</property>
<property name="icon">
<iconset theme="folder-open" resource="../../qelectrotech.qrc">
<normaloff>:/ico/16x16/folder-open.png</normaloff>:/ico/16x16/folder-open.png</iconset>
<iconset theme="folder-open"/>
</property>
</widget>
</item>
+54 -1
View File
@@ -123,6 +123,8 @@ void TitleBlockPropertiesWidget::setProperties(
ui -> m_folio_le -> setText (properties.folio);
ui -> m_display_at_cb -> setCurrentIndex(properties.display_at == Qt::BottomEdge ? 0 : 1);
ui->auto_page_cb->setCurrentText(properties.auto_page_num);
m_auto_page_num = properties.auto_page_num;
m_auto_page_num_picked = false;
//About date
ui -> m_date_now_pb -> setDisabled(true);
@@ -166,6 +168,7 @@ void TitleBlockPropertiesWidget::setProperties(
// Show the saved custom values, plus any of the template's custom variables
// that aren't defined yet, so the user only fills in the missing ones (#271).
DiagramContext context = properties.context;
m_context_keys = context.keys();
addTemplateVariables(context, index);
m_dcw -> setContext(context);
}
@@ -215,8 +218,22 @@ TitleBlockProperties TitleBlockPropertiesWidget::properties() const
}
prop.context = m_dcw -> context();
//The template's custom variables are only offered for filling in
//(#271): one left empty is not part of the properties.
const QList<QString> keys = prop.context.keys();
for (const QString &key : keys) {
if (!m_context_keys.contains(key)
&& prop.context.value(key).toString().isEmpty())
prop.context.remove(key);
}
prop.auto_page_num = ui->auto_page_cb->currentText();
//The combo box cannot show "no folio numbering": it shows its
//placeholder entry, or the project's first numbering. Return what
//was set unless the user picked something else.
if (!m_auto_page_num_picked)
prop.auto_page_num = m_auto_page_num;
else if (ui->auto_page_cb->currentText() != tr("Créer un Folio Numérotation Auto"))
prop.auto_page_num = ui->auto_page_cb->currentText();
return prop;
}
@@ -319,6 +336,30 @@ QString TitleBlockPropertiesWidget::currentTitleBlockTemplateName() const
return QString();
}
/**
@brief TitleBlockPropertiesWidget::currentTitleBlockHeight
@return the height of the current title block template
*/
int TitleBlockPropertiesWidget::currentTitleBlockHeight() const
{
const TitleBlockTemplate *tbt = nullptr;
if (!currentTitleBlockTemplateName().isEmpty())
tbt = templateForIndex(ui -> m_tbt_cb -> currentIndex());
if (!tbt)
tbt = QETApp::defaultTitleBlockTemplate();
return tbt ? tbt -> height() : 0;
}
/**
@brief TitleBlockPropertiesWidget::currentTitleBlockEdge
@return the edge of the folio the title block is displayed at
*/
Qt::Edge TitleBlockPropertiesWidget::currentTitleBlockEdge() const
{
return ui -> m_display_at_cb -> currentIndex() == 0 ? Qt::BottomEdge
: Qt::RightEdge;
}
/**
@brief TitleBlockPropertiesWidget::addCollection
add a collection of title block available in the combo box
@@ -371,11 +412,23 @@ void TitleBlockPropertiesWidget::initDialog(
this,
&TitleBlockPropertiesWidget::changeCurrentTitleBlockTemplate);
auto size_changed = [this]() {
emit titleBlockSizeChanged(currentTitleBlockHeight(),
currentTitleBlockEdge());
};
connect(ui->m_tbt_cb, qOverload<int>(&QComboBox::currentIndexChanged),
this, size_changed);
connect(ui->m_display_at_cb, qOverload<int>(&QComboBox::currentIndexChanged),
this, size_changed);
if (project!= nullptr){
keys_2 = project -> folioAutoNum().keys();
foreach (QString str, keys_2) { ui -> auto_page_cb -> addItem(str); }
if (ui->auto_page_cb->currentText()==nullptr)
ui->auto_page_cb->addItem(tr("Créer un Folio Numérotation Auto"));
//activated() is only emitted for a choice the user makes
connect(ui->auto_page_cb, qOverload<int>(&QComboBox::activated),
this, [this]() { m_auto_page_num_picked = true; });
}
else{
ui->auto_page_cb->hide();
+9
View File
@@ -70,6 +70,8 @@ class TitleBlockPropertiesWidget : public QWidget
void setTitleBlockTemplatesVisible(const bool &visible);
void setReadOnly (const bool &ro);
int currentTitleBlockHeight() const;
Qt::Edge currentTitleBlockEdge() const;
static void addTemplateVariables(
DiagramContext &context,
@@ -96,15 +98,22 @@ class TitleBlockPropertiesWidget : public QWidget
signals:
void set_auto_page_num() const;
void openAutoNumFolioEditor (QString);
void titleBlockSizeChanged(int height, Qt::Edge edge);
private:
Ui::TitleBlockPropertiesWidget *ui;
DiagramContextWidget *m_dcw;
/// keys of the context given to setProperties()
QStringList m_context_keys;
QAction *m_tbt_edit, *m_tbt_duplicate;
QMenu *m_tbt_menu;
QList <TitleBlockTemplatesCollection *> m_tbt_collection_list;
QList <QET::QetCollection> m_map_index_to_collection_type;
QList <QString> keys_2;
/// auto_page_num as given to setProperties(), returned
/// unchanged unless the user picks another folio numbering
QString m_auto_page_num;
bool m_auto_page_num_picked = false;
};
#endif // TITLEBLOCKPROPERTIESWIDGET_H
+3 -6
View File
@@ -119,8 +119,7 @@
<string/>
</property>
<property name="icon">
<iconset theme="edit-rename" resource="../../qelectrotech.qrc">
<normaloff>:/ico/22x22/edit-rename.png</normaloff>:/ico/22x22/edit-rename.png</iconset>
<iconset theme="edit-rename"/>
</property>
<property name="iconSize">
<size>
@@ -208,8 +207,7 @@
<string/>
</property>
<property name="icon">
<iconset theme="configure" resource="../../qelectrotech.qrc">
<normaloff>:/ico/16x16/configure.png</normaloff>:/ico/16x16/configure.png</iconset>
<iconset theme="configure"/>
</property>
</widget>
</item>
@@ -303,8 +301,7 @@
<string/>
</property>
<property name="icon">
<iconset theme="start" resource="../../qelectrotech.qrc">
<normaloff>:/ico/22x22/start.png</normaloff>:/ico/22x22/start.png</iconset>
<iconset theme="start"/>
</property>
</widget>
</item>
+47
View File
@@ -0,0 +1,47 @@
/*
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/>.
*/
// SPDX-License-Identifier: GPL-2.0-or-later
#include "wiringruleswarning.h"
#include <QApplication>
#include <QMessageBox>
/**
@brief WiringRulesWarning::show
Warn, when a wires-per-terminal rule is turned on (discussion #1158,
review of #1272), that the rules are experimental: they count wires on
a terminal as QElectroTech models them today, and would have to be
redesigned if wires and conductors become separate objects. A plain
warning, as the element editor gives for unnamed terminals: OK goes on.
@param parent
*/
void WiringRulesWarning::show(QWidget *parent)
{
QMessageBox::warning(
parent,
QApplication::translate("WiringRulesWarning", "Avertissement"),
QApplication::translate(
"WiringRulesWarning",
"<b>Les règles de conducteurs par borne sont une fonction expérimentale.</b>"
"<br><br>Elles comptent les conducteurs tels que QElectroTech les représente "
"aujourd'hui. Elles pourraient changer, et vos réglages devoir être refaits, "
"si les fils et les conducteurs deviennent des objets distincts dans une "
"version future."
"<br><br>Toutes ces règles peuvent être désactivées dans Configurer "
"QElectroTech > Général."));
}
+29
View File
@@ -0,0 +1,29 @@
/*
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/>.
*/
// SPDX-License-Identifier: GPL-2.0-or-later
#ifndef WIRINGRULESWARNING_H
#define WIRINGRULESWARNING_H
class QWidget;
namespace WiringRulesWarning
{
void show(QWidget *parent);
}
#endif // WIRINGRULESWARNING_H
+16 -2
View File
@@ -182,14 +182,22 @@ void XRefPropertiesWidget::saveProperties(int index) {
xrp.setShowPowerContac(ui->m_show_power_cb->isChecked());
xrp.setShowTerminalName(ui->m_show_terminal_name_cb->isChecked());
xrp.setShowAllConfiguredSlaves(ui->m_show_all_slaves_cb->isChecked());
xrp.setStackOverlapping(ui->m_stack_overlapping_cb->isChecked());
xrp.setPrefix("power", ui->m_power_prefix_le->text());
xrp.setPrefix("delay", ui->m_delay_prefix_le->text());
xrp.setPrefix("switch", ui->m_switch_prefix_le->text());
xrp.setMasterLabel(ui->m_master_le->text());
xrp.setSlaveLabel(ui->m_slave_le->text());
xrp.setFont(m_current_font);
xrp.setOffset(ui->m_offset_sb->value());
xrp.setSlaveOffset(ui->m_slave_offset_sb->value());
//The boxes cannot show a value below their minimum (the offset's
//minimum is its "Default" entry, standing for the stored 0): keep
//the stored value unless the box shows something else.
if (ui->m_offset_sb->value() != qBound(ui->m_offset_sb->minimum(), xrp.offset(),
ui->m_offset_sb->maximum()))
xrp.setOffset(ui->m_offset_sb->value());
if (ui->m_slave_offset_sb->value() != qBound(ui->m_slave_offset_sb->minimum(), xrp.slaveOffset(),
ui->m_slave_offset_sb->maximum()))
xrp.setSlaveOffset(ui->m_slave_offset_sb->value());
m_properties.insert(type, xrp);
}
@@ -235,6 +243,8 @@ void XRefPropertiesWidget::updateDisplay()
ui->m_snap_to_cb->setCurrentIndex(ui->m_snap_to_cb->findData("label"));
ui->m_offset_sb->setEnabled(false);
}
ui->m_stack_overlapping_cb->setChecked(xrp.stackOverlapping());
ui->m_stack_overlapping_cb->setEnabled(ui->m_offset_sb->isEnabled());
if(xrp.getXrefPos() == Qt::AlignTop) ui->m_xrefpos_cb->setCurrentIndex(ui->m_xrefpos_cb->findData("top"));
else if(xrp.getXrefPos() == Qt::AlignLeft) ui->m_xrefpos_cb->setCurrentIndex(ui->m_xrefpos_cb->findData("left"));
@@ -297,6 +307,9 @@ void XRefPropertiesWidget::updateDisplay()
}
}
//A cable stacks the lines of its references by itself, so the
//option is only worth showing for the elements.
ui->m_stack_overlapping_cb->setVisible(!is_plc && !is_cable);
ui->m_cross_properties_gb->setVisible(!is_plc && !is_cable);
}
@@ -325,6 +338,7 @@ void XRefPropertiesWidget::enableOffsetSB(int i){
ui->m_offset_sb->setEnabled(false);
else
ui->m_offset_sb->setEnabled(true);
ui->m_stack_overlapping_cb->setEnabled(ui->m_offset_sb->isEnabled());
}
/**
+59 -48
View File
@@ -91,55 +91,65 @@
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="label_10">
<property name="text">
<string>XRef slave position</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="m_xrefpos_cb"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QLabel" name="label_11">
<property name="text">
<string>Distance label - slave :</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="m_slave_offset_sb">
<property name="toolTip">
<string>Distance in pixels between the label and the slave cross reference</string>
</property>
<property name="suffix">
<string notr="true">px</string>
</property>
<property name="correctionMode">
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
</property>
<property name="minimum">
<number>-50</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="singleStep">
<number>1</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
<widget class="QCheckBox" name="m_stack_overlapping_cb">
<property name="toolTip">
<string>Quand les textes de deux références croisées se chevauchent, celle de l'élément le plus haut est placée au-dessus de l'autre.</string>
</property>
<property name="text">
<string>Empiler les références dont les textes se chevauchent</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="label_10">
<property name="text">
<string>XRef slave position</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="m_xrefpos_cb"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QLabel" name="label_11">
<property name="text">
<string>Distance label - slave :</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="m_slave_offset_sb">
<property name="toolTip">
<string>Distance in pixels between the label and the slave cross reference</string>
</property>
<property name="suffix">
<string notr="true">px</string>
</property>
<property name="correctionMode">
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
</property>
<property name="minimum">
<number>-50</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="singleStep">
<number>1</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
@@ -340,6 +350,7 @@
<tabstop>m_type_cb</tabstop>
<tabstop>m_snap_to_cb</tabstop>
<tabstop>m_offset_sb</tabstop>
<tabstop>m_stack_overlapping_cb</tabstop>
<tabstop>m_xrefpos_cb</tabstop>
<tabstop>m_display_has_contacts_rb</tabstop>
<tabstop>m_display_has_cross_rb</tabstop>