Merge remote-tracking branch 'upstream/master' into revive/654-rotating-backups

# Conflicts:
#	sources/qetapp.cpp
This commit is contained in:
ispyisail
2026-09-30 11:44:49 +13:00
66 changed files with 879 additions and 15 deletions
+1 -1
View File
@@ -1936,7 +1936,7 @@ Note: these options DO NOT allow or block auto numberings, only their update pol
<source>
&lt;Shift&gt; to move</source>
<translation>
&lt;Shift&gt; to move</translation>
Hold Shift and drag to move this text on its own</translation>
</message>
</context>
<context>
+1 -1
View File
@@ -1935,7 +1935,7 @@ Remarque: Ces options n&apos;autorisent ou bloquent l&apos;auto numérotation, s
<source>
&lt;Shift&gt; to move</source>
<translation>
&lt;Shift&gt; pour déplacer</translation>
Maintenir Maj et faire glisser pour déplacer ce texte seul</translation>
</message>
</context>
<context>
@@ -18,6 +18,9 @@
#include "elementcollectionitem.h"
#include <QCoreApplication>
#include <QThread>
/**
@brief ElementCollectionItem::ElementCollectionItem
Constructor
@@ -247,6 +250,73 @@ QList<ElementCollectionItem *> ElementCollectionItem::items() const
return list;
}
/**
@brief onGuiThread
@return true when called from the thread the application lives in
*/
static bool onGuiThread()
{
return QThread::currentThread() == QCoreApplication::instance()->thread();
}
/**
@brief ElementCollectionItem::setData
ElementsCollectionModel::loadCollections() runs setUpData() for every
item on worker threads (QtConcurrent::map), while the items are
already in the model. QStandardItem::setData() updates the model and
emits its dataChanged() signal, which is not safe from a worker
thread. So a value set from a worker thread is kept on the item, and
the model applies it on the GUI thread with applyDeferredData() once
every item is set up. setText(), setFlags(), setToolTip() and setIcon()
all end up here.
@param value
@param role
*/
void ElementCollectionItem::setData(const QVariant &value, int role)
{
if (role == Qt::EditRole)
role = Qt::DisplayRole;
if (onGuiThread())
QStandardItem::setData(value, role);
else
m_deferred_data.insert(role, value);
}
/**
@brief ElementCollectionItem::data
On a worker thread, a value set by setData() but not applied yet is
returned, so setUpData() reads back what it has just set
(localName() tests text() for example).
@param role
@return
*/
QVariant ElementCollectionItem::data(int role) const
{
if (role == Qt::EditRole)
role = Qt::DisplayRole;
if (!onGuiThread())
{
const auto it = m_deferred_data.constFind(role);
if (it != m_deferred_data.constEnd())
return it.value();
}
return QStandardItem::data(role);
}
/**
@brief ElementCollectionItem::applyDeferredData
Apply the values set from a worker thread, see setData().
Must be called on the GUI thread, after the worker is done.
*/
void ElementCollectionItem::applyDeferredData()
{
for (auto it = m_deferred_data.constBegin() ; it != m_deferred_data.constEnd() ; ++it)
QStandardItem::setData(it.value(), it.key());
m_deferred_data.clear();
}
void setUpData(ElementCollectionItem *eci) {
eci->setUpData();
}
@@ -18,6 +18,7 @@
#ifndef ELEMENTCOLLECTIONITEM2_H
#define ELEMENTCOLLECTIONITEM2_H
#include <QHash>
#include <QStandardItem>
/**
@@ -56,6 +57,16 @@ class ElementCollectionItem : public QStandardItem
QList<ElementCollectionItem *> elementsChild() const;
QList<ElementCollectionItem *> directoriesChild() const;
QList<ElementCollectionItem *> items() const;
QVariant data(int role = Qt::UserRole + 1) const override;
void setData(const QVariant &value, int role = Qt::UserRole + 1) override;
void applyDeferredData();
private:
/// Values set by setUpData() while it runs on a worker thread,
/// see setData(). Only ever touched by that one worker thread,
/// until applyDeferredData() empties it on the GUI thread.
QHash<int, QVariant> m_deferred_data;
};
void setUpData(ElementCollectionItem *eci);
@@ -312,8 +312,15 @@ void ElementsCollectionModel::loadCollections(bool common_collection,
this, &ElementsCollectionModel::loadingProgressValueChanged);
connect(watcher, &QFutureWatcher<void>::progressRangeChanged,
this, &ElementsCollectionModel::loadingProgressRangeChanged);
connect(watcher, &QFutureWatcher<void>::finished,
this, &ElementsCollectionModel::loadingFinished);
//setUpData() ran on worker threads, which only kept the values on
//the items (ElementCollectionItem::setData()): apply them here, on
//the GUI thread, before anyone is told the loading is finished.
connect(watcher, &QFutureWatcher<void>::finished, this, [this]()
{
for (ElementCollectionItem *eci : std::as_const(m_items_list_to_setUp))
eci->applyDeferredData();
emit loadingFinished();
});
connect(watcher, &QFutureWatcher<void>::finished, watcher, &QFutureWatcher<void>::deleteLater);
@@ -20,12 +20,14 @@
#include <QDate>
#include "ui_renamedialog.h"
#include "../../qet.h"
RenameDialog::RenameDialog(QString path, QWidget *parent) :
QDialog(parent),
ui(new Ui::RenameDialog),
m_path(std::move(path))
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
m_name = m_path.split("/").last();
if (m_name.endsWith(".elmt")) m_name.remove(".elmt");
ui->m_label->setText(tr("L'élément « %1 » existe déjà. Que souhaitez-vous faire ?").arg(m_path));
+2
View File
@@ -19,6 +19,7 @@
#include "ui_namelistdialog.h"
#include "namelistwidget.h"
#include "../../qet.h"
#include <QPushButton>
#include <QMessageBox>
@@ -27,6 +28,7 @@ NameListDialog::NameListDialog(QWidget *parent) :
ui(new Ui::NameListDialog)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
m_namelist_widget = new NameListWidget(this);
ui->m_main_layout->insertWidget(1, m_namelist_widget);
@@ -18,6 +18,8 @@
#ifndef PROPERTIESEDITORDIALOG_H
#define PROPERTIESEDITORDIALOG_H
#include "../qet.h"
#include <QDialog>
#include <QDialogButtonBox>
#include <QVBoxLayout>
@@ -74,6 +76,14 @@ class PropertiesEditorDialog : public QDialog
this);
vlayout->addWidget(button_box);
// Keyed on the wrapped editor's own class, not this
// dialog's -- every instantiation of this constructor
// template is the same PropertiesEditorDialog class,
// so using its class name here would have every kind
// of wrapped editor (conductor properties, element
// properties, ...) fight over one saved size.
QET::trackDialogGeometry(this, editor->metaObject()->className());
//Setup connection between button box and the editor
connect(button_box,
&QDialogButtonBox::clicked,
@@ -21,6 +21,7 @@
#include "../../qetinformation.h"
#include "ui_replaceadvanceddialog.h"
#include "../../qet.h"
#include <QAbstractButton>
/**
@@ -33,6 +34,7 @@ replaceAdvancedDialog::replaceAdvancedDialog(advancedReplaceStruct advanced, QWi
ui(new Ui::replaceAdvancedDialog)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
connect(ui->m_button_box, &QDialogButtonBox::clicked, [this](QAbstractButton *button_) {
this->done(ui->m_button_box->buttonRole(button_));
@@ -20,6 +20,7 @@
#include "../searchandreplaceworker.h"
#include "ui_replaceconductordialog.h"
#include "../../qet.h"
#include <QColorDialog>
#include <QPainter>
@@ -37,6 +38,7 @@ ReplaceConductorDialog::ReplaceConductorDialog(
ui(new Ui::ReplaceConductorDialog)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
initWidget();
setProperties(properties);
}
@@ -24,6 +24,7 @@
#include "replaceelementdialog.h"
#include "ui_replaceelementdialog.h"
#include "../../qet.h"
#include <QAbstractButton>
ReplaceElementDialog::ReplaceElementDialog(DiagramContext context, QWidget *parent) :
@@ -31,6 +32,7 @@ ReplaceElementDialog::ReplaceElementDialog(DiagramContext context, QWidget *pare
ui(new Ui::ReplaceElementDialog)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
buildWidget();
setContext(context);
}
@@ -18,6 +18,7 @@
#include "replacefoliowidget.h"
#include "../../ui/diagramcontextwidget.h"
#include "../../qet.h"
#include "../searchandreplaceworker.h"
#include "ui_replacefoliowidget.h"
@@ -132,6 +133,8 @@ ReplaceFolioDialog::ReplaceFolioDialog(QWidget *parent) :
{
this->done(m_button_box->buttonRole(button_));
});
QET::trackDialogGeometry(this);
}
ReplaceFolioDialog::~ReplaceFolioDialog()
@@ -23,6 +23,7 @@
#include "../GraphicsItem/terminalstripitem.h"
#include "../../diagram.h"
#include "../../qet.h"
void AddTerminalStripItemDialog::openDialog(Diagram *diagram, QWidget *parent)
{
AddTerminalStripItemDialog d(diagram->project(), parent);
@@ -46,6 +47,7 @@ AddTerminalStripItemDialog::AddTerminalStripItemDialog(QETProject *project, QWid
ui{new Ui::AddTerminalStripItemDialog}
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
fillComboBox();
}
@@ -20,6 +20,7 @@
#include "../terminalstrip.h"
#include "../../qetproject.h"
#include "../../qet.h"
/**
* @brief TerminalStripCreatorDialog::TerminalStripCreatorDialog
* @param project : Project to add a new terminal strip
@@ -31,6 +32,7 @@ TerminalStripCreatorDialog::TerminalStripCreatorDialog(QETProject *project, QWid
m_project(project)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
}
/**
@@ -62,7 +62,9 @@ void TerminalStripTreeDockWidget::setProject(QETProject *project)
}
m_project = project;
if (m_project) {
m_project_destroy_connection = connect(m_project, &QObject::destroyed, [this](){
//`this` as context: this dock can be deleted before the project
//(with the editor window that owns it), and the connection must go with it
m_project_destroy_connection = connect(m_project, &QObject::destroyed, this, [this](){
this->m_current_strip.clear();
this->reload();
});
@@ -17,6 +17,7 @@
*/
#include "renumberelementsdialog.h"
#include "../../qet.h"
#include <QComboBox>
#include <QDialogButtonBox>
@@ -60,6 +61,8 @@ RenumberElementsDialog::RenumberElementsDialog(const QStringList &scheme_titles,
connect(m_all_rb, &QRadioButton::toggled, this, &RenumberElementsDialog::updateUi);
connect(m_one_rb, &QRadioButton::toggled, this, &RenumberElementsDialog::updateUi);
QET::trackDialogGeometry(this);
}
QString RenumberElementsDialog::selectedSchemeTitle() const
+3
View File
@@ -19,6 +19,7 @@
#include "configdialog.h"
#include "ui/configpage/configpages.h"
#include "qetapp.h"
#include "qet.h"
#include "machine_info.h"
@@ -89,6 +90,8 @@ ConfigDialog::ConfigDialog(QWidget *parent) : QDialog(parent) {
setWindowFlags(Qt::Sheet);
}
#endif
QET::trackDialogGeometry(this);
}
/// Destructeur
+182 -1
View File
@@ -45,13 +45,20 @@
#include "ElementsCollection/xmlelementcollection.h"
#include "NameList/nameslist.h"
#include "elementdialog.h"
#include "qetapp.h"
#include "qetgraphicsitem/element.h"
#include "qetinformation.h"
#include "qetversion.h"
#include <QApplication>
#include <QDropEvent>
#include <QPainter>
#include <QPointer>
#include <QSet>
#include <QUuid>
#include <algorithm>
static bool hasCabinetInfo(const DiagramContext &infos);
/**
Constructeur
@param diagram Schema a afficher ; si diagram vaut 0, un nouveau Diagram est utilise
@@ -103,6 +110,10 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
m_create_template = new QAction(tr("Créer un template", "context menu action"), this);
connect(m_create_template, &QAction::triggered, this, &DiagramView::createTemplateFromSelection);
//Setup the action to generate cabinet placement thumbnails (discussion #602)
m_generate_cabinet_thumbnail = new QAction(tr("Générer une vignette d'armoire", "context menu action"), this);
connect(m_generate_cabinet_thumbnail, &QAction::triggered, this, &DiagramView::generateCabinetThumbnails);
//Filled each time the context menu opens, see updateFolioReportMenu()
m_folio_report_menu = new QMenu(tr("Renvoi de folio"), this);
@@ -1765,7 +1776,8 @@ QList<QAction *> DiagramView::contextMenuActions() const
QList<QAction *> list;
if (QETDiagramEditor *qde = diagramEditor())
{
if (m_diagram->selectedItems().isEmpty())
const QList<QGraphicsItem *> selected_items = m_diagram->selectedItems();
if (selected_items.isEmpty())
{
//Drawing comes first. The row and column actions change
//the folio's layout and are rarely wanted, so they sit one
@@ -1786,6 +1798,15 @@ QList<QAction *> DiagramView::contextMenuActions() const
list << m_multi_paste;
list << m_separators.at(0);
list << m_create_template; // Add the create template action
//Disabled, and so left out below, unless a selected element
//has both a manufacturer and a manufacturer reference.
m_generate_cabinet_thumbnail->setEnabled(std::any_of(
selected_items.cbegin(), selected_items.cend(),
[](QGraphicsItem *qgi) {
const Element *element = qgraphicsitem_cast<Element *>(qgi);
return element && hasCabinetInfo(element->elementInformations());
}));
list << m_generate_cabinet_thumbnail;
list << qde->m_conductor_reset;
list << m_separators.at(1);
list << qde->m_selection_actions_group.actions();
@@ -2073,6 +2094,166 @@ void DiagramView::createTemplateFromSelection()
}
}
/**
@brief hasCabinetInfo
@return true if @a infos has both a manufacturer and a manufacturer
reference, the two values a cabinet thumbnail shows.
*/
static bool hasCabinetInfo(const DiagramContext &infos)
{
return !infos[QETInformation::ELMT_MANUFACTURER].toString().trimmed().isEmpty()
&& !infos[QETInformation::ELMT_MANUFACTURER_REF].toString().trimmed().isEmpty();
}
/**
@brief cabinetThumbnailDefinition
@return the definition of a thumbnail element, a 120x30 frame showing
the manufacturer and the manufacturer reference in a dynamic text.
The two values are also stored in the definition's element
information, so the thumbnail shows them as soon as it is placed.
*/
static QDomElement cabinetThumbnailDefinition(QDomDocument &doc,
const QString &display_name,
const QString &manufacturer,
const QString &reference)
{
QDomElement definition = doc.createElement(QStringLiteral("definition"));
definition.setAttribute(QStringLiteral("version"), QetVersion::currentVersion().toString());
definition.setAttribute(QStringLiteral("type"), QStringLiteral("element"));
definition.setAttribute(QStringLiteral("link_type"), QStringLiteral("thumbnail"));
definition.setAttribute(QStringLiteral("width"), 120);
definition.setAttribute(QStringLiteral("height"), 30);
definition.setAttribute(QStringLiteral("hotspot_x"), 60);
definition.setAttribute(QStringLiteral("hotspot_y"), 15);
doc.appendChild(definition);
QDomElement uuid = doc.createElement(QStringLiteral("uuid"));
uuid.setAttribute(QStringLiteral("uuid"), QUuid::createUuid().toString());
definition.appendChild(uuid);
NamesList names;
names.addName(QStringLiteral("en"), display_name);
definition.appendChild(names.toXml(doc));
QDomElement informations = doc.createElement(QStringLiteral("elementInformations"));
DiagramContext infos;
infos.addValue(QETInformation::ELMT_MANUFACTURER, manufacturer);
infos.addValue(QETInformation::ELMT_MANUFACTURER_REF, reference);
infos.toXml(informations, QStringLiteral("elementInformation"));
definition.appendChild(informations);
QDomElement description = doc.createElement(QStringLiteral("description"));
QDomElement rect = doc.createElement(QStringLiteral("rect"));
rect.setAttribute(QStringLiteral("x"), -60);
rect.setAttribute(QStringLiteral("y"), -15);
rect.setAttribute(QStringLiteral("width"), 120);
rect.setAttribute(QStringLiteral("height"), 30);
rect.setAttribute(QStringLiteral("rx"), 0);
rect.setAttribute(QStringLiteral("ry"), 0);
rect.setAttribute(QStringLiteral("style"),
QStringLiteral("line-style:normal;line-weight:normal;filling:none;color:black"));
rect.setAttribute(QStringLiteral("antialias"), QStringLiteral("false"));
description.appendChild(rect);
QDomElement text = doc.createElement(QStringLiteral("dynamic_text"));
text.setAttribute(QStringLiteral("x"), -57);
text.setAttribute(QStringLiteral("y"), -14);
text.setAttribute(QStringLiteral("z"), 1);
text.setAttribute(QStringLiteral("text_width"), 114);
text.setAttribute(QStringLiteral("Halignment"), QStringLiteral("AlignLeft"));
text.setAttribute(QStringLiteral("Valignment"), QStringLiteral("AlignTop"));
text.setAttribute(QStringLiteral("frame"), QStringLiteral("false"));
text.setAttribute(QStringLiteral("rotation"), 0);
text.setAttribute(QStringLiteral("keep_visual_rotation"), QStringLiteral("false"));
text.setAttribute(QStringLiteral("text_from"), QStringLiteral("CompositeText"));
text.setAttribute(QStringLiteral("uuid"), QUuid::createUuid().toString());
text.setAttribute(QStringLiteral("font"), QETApp::dynamicTextsItemFont(7).toString());
QDomElement text_cache = doc.createElement(QStringLiteral("text"));
text_cache.appendChild(doc.createTextNode(manufacturer + QLatin1Char('\n') + reference));
text.appendChild(text_cache);
QDomElement composite_text = doc.createElement(QStringLiteral("composite_text"));
composite_text.appendChild(doc.createTextNode(QStringLiteral("%{manufacturer}\n%{manufacturer_reference}")));
text.appendChild(composite_text);
description.appendChild(text);
definition.appendChild(description);
return definition;
}
/**
@brief DiagramView::generateCabinetThumbnails
Context menu action (discussion #602). For every selected element with
both a manufacturer and a manufacturer reference, add a thumbnail
element showing them to the folder "Cabinet thumbnails" of this
project's embedded collection, from where it can be placed on a
cabinet layout folio like any other element. A device that already
has a thumbnail there is skipped, so placing it several times does
not pile up copies.
*/
void DiagramView::generateCabinetThumbnails()
{
QETProject *project = m_diagram->project();
if (!project || project->isReadOnly()) {
return;
}
XmlElementCollection *collection = project->embeddedElementCollection();
if (!collection) {
return;
}
const QString dir_name = QStringLiteral("Cabinet thumbnails");
const QString dir_path = QStringLiteral("import/") + dir_name;
if (!collection->exist(dir_path))
{
NamesList dir_names;
dir_names.addName(QStringLiteral("en"), dir_name);
//Stored per language, like an element's names: not tr(), which
//would store the interface's language under "fr"
dir_names.addName(QStringLiteral("fr"), QStringLiteral("Vignettes d'armoire"));
if (!collection->createDir(QStringLiteral("import"), dir_name, dir_names)) {
return;
}
}
QStringList existing = collection->elementsNames(collection->directory(dir_path));
int created = 0;
const QList<QGraphicsItem *> selected_items = m_diagram->selectedItems();
for (QGraphicsItem *qgi : selected_items)
{
const Element *element = qgraphicsitem_cast<Element *>(qgi);
if (!element || !hasCabinetInfo(element->elementInformations())) {
continue;
}
const DiagramContext infos = element->elementInformations();
const QString manufacturer = infos[QETInformation::ELMT_MANUFACTURER].toString().trimmed();
const QString reference = infos[QETInformation::ELMT_MANUFACTURER_REF].toString().trimmed();
//The display name keeps the values as typed; the file name
//cannot hold characters such as '/', which would be read as
//a folder.
const QString display_name = manufacturer + QLatin1Char(' ') + reference;
const QString file_name = QET::stringToFileName(display_name) + QStringLiteral(".elmt");
if (existing.contains(file_name)) {
continue;
}
QDomDocument doc;
const QDomElement definition = cabinetThumbnailDefinition(doc, display_name, manufacturer, reference);
if (collection->addElementDefinition(dir_path, file_name, definition))
{
existing << file_name;
++created;
}
}
if (created) {
project->setModified(true);
}
}
/**
@return l'editeur de schemas parent ou 0
*/
+2
View File
@@ -58,6 +58,7 @@ class DiagramView : public PaletteGraphicsView
QAction *m_paste_here = nullptr;
QAction *m_multi_paste = nullptr;
QAction *m_create_template = nullptr;
QAction *m_generate_cabinet_thumbnail = nullptr;
QMenu *m_folio_report_menu = nullptr;
QPoint m_paste_here_pos;
QPoint m_press_pos;
@@ -197,5 +198,6 @@ class DiagramView : public PaletteGraphicsView
void adjustGridToZoom();
void applyReadOnly();
void createTemplateFromSelection();
void generateCabinetThumbnails();
};
#endif
+44
View File
@@ -18,6 +18,7 @@
#include "elementscene.h"
#include "../ElementsCollection/terminaluuids.h"
#include "../borderproperties.h"
#include "../NameList/ui/namelistdialog.h"
#include "../NameList/ui/namelistwidget.h"
#include "../QPropertyUndoCommand/qpropertyundocommand.h"
@@ -72,6 +73,49 @@ ElementScene::ElementScene(QETElementEditor *editor, QObject *parent) :
m_decorator_lock = new QMutex();
connect(&m_undo_stack, &QUndoStack::indexChanged, this, &ElementScene::managePrimitivesGroups);
connect(this, &ElementScene::selectionChanged, this, &ElementScene::managePrimitivesGroups);
QSettings settings;
m_background_frame_visible = settings.value(
QStringLiteral("elementeditor/background_frame_visible"), false).toBool();
BorderProperties bp = BorderProperties::defaultProperties();
m_background_frame_size = QSizeF(
settings.value(QStringLiteral("elementeditor/background_frame_width"),
bp.columns_count * bp.columns_width).toReal(),
settings.value(QStringLiteral("elementeditor/background_frame_height"),
bp.rows_count * bp.rows_height).toReal());
}
/**
@brief ElementScene::setBackgroundFrameVisible
Toggle the visual-only background frame used to proportion this
element's drawing against a representative folio surface. Like the
hotspot indicator, this frame is never written to the saved .elmt file.
@param visible
*/
void ElementScene::setBackgroundFrameVisible(bool visible)
{
if (m_background_frame_visible == visible) {
return;
}
m_background_frame_visible = visible;
QSettings().setValue(QStringLiteral("elementeditor/background_frame_visible"), visible);
update();
}
/**
@brief ElementScene::setBackgroundFrameSize
@param size the new size (in scene/grid units) of the background frame
*/
void ElementScene::setBackgroundFrameSize(const QSizeF &size)
{
if (m_background_frame_size == size) {
return;
}
m_background_frame_size = size;
QSettings settings;
settings.setValue(QStringLiteral("elementeditor/background_frame_width"), size.width());
settings.setValue(QStringLiteral("elementeditor/background_frame_height"), size.height());
update();
}
/**
+7
View File
@@ -92,6 +92,8 @@ class ElementScene : public QGraphicsScene
QPointer<CustomElementGraphicPart> m_single_selected_item;
bool m_hotspot_visible = true;
bool m_background_frame_visible = false;
QSizeF m_background_frame_size;
// methods
public:
@@ -142,6 +144,11 @@ class ElementScene : public QGraphicsScene
/// not part of the drawn symbol.
void setHotspotVisible(bool visible) {m_hotspot_visible = visible;}
bool hotspotVisible() const {return m_hotspot_visible;}
bool backgroundFrameVisible() const {return m_background_frame_visible;}
void setBackgroundFrameVisible(bool visible);
QSizeF backgroundFrameSize() const {return m_background_frame_size;}
void setBackgroundFrameSize(const QSizeF &size);
protected:
void mouseMoveEvent (QGraphicsSceneMouseEvent *) override;
+13
View File
@@ -606,6 +606,19 @@ void ElementView::drawBackground(QPainter *p, const QRectF &r) {
}
}
}
if (m_scene->backgroundFrameVisible()) {
const QSizeF frame_size = m_scene->backgroundFrameSize();
const QRectF frame_rect(-frame_size.width() / 2.0, -frame_size.height() / 2.0,
frame_size.width(), frame_size.height());
QPen frame_pen(Qt::blue);
frame_pen.setCosmetic(true);
frame_pen.setStyle(Qt::DashLine);
p -> setPen(frame_pen);
p -> setBrush(Qt::NoBrush);
p -> drawRect(frame_rect);
}
p -> restore();
}
@@ -22,6 +22,7 @@
#include "../../qetinformation.h"
#include "ui_elementpropertieseditorwidget.h"
#include "../../qet.h"
#include <QItemDelegate>
#include <QComboBox>
#include <QSpinBox>
@@ -95,6 +96,7 @@ ElementPropertiesEditorWidget::ElementPropertiesEditorWidget(ElementData data, Q
m_data(data)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
setUpInterface();
upDateInterface();
}
+45
View File
@@ -59,6 +59,10 @@
#include <QFileDialog>
#include <QSvgGenerator>
#include <QHBoxLayout>
#include <QDialog>
#include <QDialogButtonBox>
#include <QDoubleSpinBox>
#include <QFormLayout>
/**
* @brief QETElementEditor::QETElementEditor
@@ -1138,6 +1142,47 @@ void QETElementEditor::setupActions()
parts_toolbar -> setObjectName("parts");
parts_toolbar -> addActions(m_add_part_action_grp -> actions());
addToolBar(Qt::LeftToolBarArea, parts_toolbar);
//Background frame action: a visual-only reference rectangle, never
//written to the saved .elmt file, to help proportion the drawing
//against a representative folio surface.
auto *toggle_background_frame_action = new QAction(QET::Icons::DocumentPrintFrame, tr("Afficher le cadre de fond"), this);
toggle_background_frame_action -> setCheckable(true);
toggle_background_frame_action -> setChecked(m_elmt_scene -> backgroundFrameVisible());
connect(toggle_background_frame_action, &QAction::toggled, m_elmt_scene, &ElementScene::setBackgroundFrameVisible);
ShortcutManager::instance().registerAction(toggle_background_frame_action, "elementeditor.toggle_background_frame", tr("Éditeur d'élément"), QKeySequence());
ui->m_display_menu->addAction(toggle_background_frame_action);
ui->m_view_toolbar->addAction(toggle_background_frame_action);
auto *configure_background_frame_action = new QAction(tr("Taille du cadre de fond..."), this);
connect(configure_background_frame_action, &QAction::triggered, this, [this]() {
QDialog dialog(this);
dialog.setWindowTitle(tr("Taille du cadre de fond"));
auto *layout = new QFormLayout(&dialog);
auto *width_spin = new QDoubleSpinBox(&dialog);
width_spin -> setRange(1.0, 100000.0);
width_spin -> setSuffix(tr(" px"));
width_spin -> setValue(m_elmt_scene -> backgroundFrameSize().width());
layout -> addRow(tr("Largeur"), width_spin);
auto *height_spin = new QDoubleSpinBox(&dialog);
height_spin -> setRange(1.0, 100000.0);
height_spin -> setSuffix(tr(" px"));
height_spin -> setValue(m_elmt_scene -> backgroundFrameSize().height());
layout -> addRow(tr("Hauteur"), height_spin);
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog);
connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
layout -> addRow(buttons);
if (dialog.exec() == QDialog::Accepted) {
m_elmt_scene -> setBackgroundFrameSize(QSizeF(width_spin -> value(), height_spin -> value()));
}
});
ShortcutManager::instance().registerAction(configure_background_frame_action, "elementeditor.configure_background_frame", tr("Éditeur d'élément"), QKeySequence());
ui->m_display_menu->addAction(configure_background_frame_action);
}
/**
+2
View File
@@ -20,6 +20,7 @@
#include "ElementsCollection/elementcollectionitem.h"
#include "ElementsCollection/elementscollectionmodel.h"
#include "ElementsCollection/elementstreeview.h"
#include "qet.h"
#include "qetapp.h"
#include "qetmessagebox.h"
#include "qfilenameedit.h"
@@ -41,6 +42,7 @@ ElementDialog::ElementDialog(uint mode, QWidget *parent) :
{
setUpWidget();
setUpConnection();
QET::trackDialogGeometry(this);
}
/**
+1
View File
@@ -41,6 +41,7 @@ ElementsCategoryEditor::ElementsCategoryEditor(const ElementsLocation &location,
m_location(location)
{
setUpWidget();
QET::trackDialogGeometry(this);
if (m_location.isElement()) {
QET::QetMessageBox::warning(this,
+3
View File
@@ -22,6 +22,7 @@
#include "dxfexport.h"
#include "exportpropertieswidget.h"
#include "factory/elementpicturefactory.h"
#include "qet.h"
#include "qetgraphicsitem/ViewItem/qetgraphicstableitem.h"
#include "dxfpaintdevice.h"
#include "qetgraphicsitem/conductor.h"
@@ -108,6 +109,8 @@ ExportDialog::ExportDialog(
// ajustement des extensions des fichiers
slot_changeFilesExtension(true);
QET::trackDialogGeometry(this);
}
/**
+2
View File
@@ -22,6 +22,7 @@
#include "../../utils/qetutils.h"
#include "ui_addtabledialog.h"
#include "../../qet.h"
#include <QFontDialog>
/**
@@ -35,6 +36,7 @@ AddTableDialog::AddTableDialog(QWidget *content_widget, QWidget *parent) :
ui(new Ui::AddTableDialog)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
ui->m_header_font_pb->setText(m_header_font.family());
ui->m_table_font_pb->setText(m_table_font.family());
m_content_widget = content_widget;
@@ -17,6 +17,7 @@
*/
#include "diagnosticsreportdialog.h"
#include "../../qet.h"
#include "../../qetmessagebox.h"
#include <QDialogButtonBox>
@@ -63,6 +64,8 @@ DiagnosticsReportDialog::DiagnosticsReportDialog(
// holds a QString copy but we save the original UTF-8 bytes to avoid
// any round-trip surprises.
setProperty("qet_report_content", content);
QET::trackDialogGeometry(this);
}
void DiagnosticsReportDialog::saveToFile()
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "materialentrydialog.h"
#include "../qet.h"
#include "../qetmessagebox.h"
@@ -92,6 +93,8 @@ MaterialEntryDialog::MaterialEntryDialog(const QStringList &columns, QWidget *pa
if (!m_edits.isEmpty()) {
m_edits.first()->setFocus();
}
QET::trackDialogGeometry(this);
}
/**
+22
View File
@@ -26,6 +26,7 @@
#include <QColorDialog>
#include <QGraphicsSceneContextMenuEvent>
#include <QAction>
#include <QDialog>
#include <QFileInfo>
#include <QSaveFile>
#include <QSettings>
@@ -272,6 +273,27 @@ bool QET::infoFlagIsTrue(const QString &value)
|| v == QLatin1String("on");
}
/**
@brief QET::trackDialogGeometry
@see the declaration in qet.h for the rationale.
*/
void QET::trackDialogGeometry(QDialog *dialog, const QString &key)
{
const QString settings_key = QStringLiteral("dialoggeometry/%1").arg(
key.isEmpty() ? QString::fromLatin1(dialog->metaObject()->className()) : key);
QSettings settings;
const QVariant geometry = settings.value(settings_key);
if (geometry.isValid()) {
dialog->restoreGeometry(geometry.toByteArray());
}
QObject::connect(dialog, &QDialog::finished, dialog, [dialog, settings_key]() {
QSettings settings;
settings.setValue(settings_key, dialog->saveGeometry());
});
}
/**
@brief QET::ElementsAndConductorsSentence
Permet de composer rapidement la proposition "x elements et y conducteurs"
+13
View File
@@ -24,6 +24,7 @@
#include <QObject>
class QActionGroup;
class QDialog;
/**
This file provides useful functions and enums that may be used from
anywhere else within the QElectroTech application.
@@ -204,6 +205,18 @@ namespace QET {
QActionGroup *depthActionGroup(QObject *parent = nullptr);
void saveCustomColors();
void loadCustomColors();
/**
Restore a dialog's last-used size/position from QSettings, and
save it back whenever the dialog closes (accepted, rejected, or
via the window's close button). @a key identifies the dialog in
QSettings; defaults to the dialog's class name, which is enough
to distinguish dialogs one-to-one -- pass an explicit @a key for
a dialog class reused for several different kinds of content
(e.g. a generic properties-editor wrapper), so each kind gets
its own remembered size instead of fighting over one entry.
*/
void trackDialogGeometry(QDialog *dialog, const QString &key = QString());
}
Q_DECLARE_METATYPE(QET::DepthOption)
+106
View File
@@ -30,6 +30,7 @@
#include "qetpalette.h"
#include "qetstyle.h"
#include "utils/qetutils.h"
#include "utils/configprofile.h"
#include "qetmessagebox.h"
#include "qetproject.h"
#include "qtextorientationspinboxwidget.h"
@@ -55,10 +56,12 @@
#include <iostream>
#define QUOTE(x) STRINGIFY(x)
#define STRINGIFY(x) #x
#include <QFileDialog>
#include <QFileInfo>
#include <QFontDatabase>
#include <QProcessEnvironment>
#include <QRegularExpression>
#include <QSettings>
#include <QStyleFactory>
#include <QStyleHints>
#ifdef BUILD_WITHOUT_KF
@@ -2246,6 +2249,109 @@ void QETApp::configureQET()
#endif
}
/**
@brief QETApp::exportConfiguration
Save the settings of QElectroTech to a file the user chooses, to keep
them as a named profile or copy them to another computer (discussion
#610). The file is always written in the ini format, whatever the
platform stores its live settings in, so a profile saved on Windows
loads on Linux and macOS. See ConfigProfile for the keys left out.
*/
void QETApp::exportConfiguration()
{
QWidget *parent_widget = qApp->activeWindow();
QString path = QFileDialog::getSaveFileName(
parent_widget,
tr("Enregistrer la configuration sous...", "dialog title"),
QString(),
tr("Configurations QElectroTech (*.conf)", "file dialog filter"));
if (path.isEmpty()) {
return;
}
if (!path.endsWith(QLatin1String(".conf"), Qt::CaseInsensitive)) {
path += QLatin1String(".conf");
}
QSettings live_settings;
QSettings file_settings(path, QSettings::IniFormat);
ConfigProfile::exportTo(live_settings, file_settings);
if (file_settings.status() != QSettings::NoError) {
QET::QetMessageBox::critical(
parent_widget,
tr("Erreur", "message box title"),
tr("Impossible d'enregistrer la configuration dans « %1 ».").arg(path));
}
}
/**
@brief QETApp::importConfiguration
Replace the settings of QElectroTech with a file saved by
exportConfiguration(), then close QElectroTech.
The settings are read by each part of QElectroTech when it starts, and
there is no signal telling all of them that a setting changed, so the
new settings are applied by starting QElectroTech again. That restart
is left to the user: a second copy started from here would find this
one still running, hand its arguments over to it and exit (main.cpp).
*/
void QETApp::importConfiguration()
{
QWidget *parent_widget = qApp->activeWindow();
const QString path = QFileDialog::getOpenFileName(
parent_widget,
tr("Charger une configuration...", "dialog title"),
QString(),
tr("Configurations QElectroTech (*.conf)", "file dialog filter"));
if (path.isEmpty()) {
return;
}
QSettings file_settings(path, QSettings::IniFormat);
if (file_settings.status() != QSettings::NoError
|| !ConfigProfile::isProfile(file_settings))
{
QET::QetMessageBox::critical(
parent_widget,
tr("Erreur", "message box title"),
tr("« %1 » n'est pas une configuration enregistrée par QElectroTech.").arg(path));
return;
}
//Said before anything closes: once the last window is closed,
//QElectroTech quits by itself (checkRemainingWindows()).
const auto answer = QET::QetMessageBox::question(
parent_widget,
tr("Charger une configuration", "message box title"),
tr("Cette configuration va remplacer vos réglages actuels, sauf la "
"disposition des fenêtres et la liste des fichiers récents.\n\n"
"QElectroTech va ensuite se fermer. Relancez-le pour utiliser "
"la nouvelle configuration.\n\n"
"Voulez-vous continuer ?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
if (answer != QMessageBox::Yes) {
return;
}
//Asks to save any modified project, as quitQET() does. The settings
//are only replaced once every editor is closed, so that the editors
//saving their own settings on close cannot overwrite them, and a
//cancelled close leaves them untouched.
if (!closeEveryEditor()) {
return;
}
QSettings live_settings;
ConfigProfile::importFrom(file_settings, live_settings);
//~QETApp() saves the colour dialog's custom colours on exit: load
//the profile's into it first, or the old ones overwrite them
QET::loadCustomColors();
qApp->quit();
}
/**
@brief QETApp::aboutQET
Open the dialog about qet.
+4
View File
@@ -291,6 +291,10 @@ class QETApp : public QObject
void openTitleBlockTemplate(const QString &);
void openTitleBlockTemplateFiles(const QStringList &);
void configureQET();
/// Save the settings to a file (discussion #610)
void exportConfiguration();
/// Replace the settings with a saved file, then close QElectroTech
void importConfiguration();
void aboutQET();
void showDiagnosticsReport();
void receiveMessage(int instanceId, QByteArray message);
+8 -3
View File
@@ -1099,8 +1099,10 @@ bool Conductor::fromXml(QDomElement &dom_element)
m_uuid = QUuid::createUuid();
}
setPos(dom_element.attribute("x", nullptr).toDouble(),
dom_element.attribute("y", nullptr).toDouble());
//"nan" and "inf" parse as numbers; a non-finite position is kept at 0
const qreal x = dom_element.attribute("x", nullptr).toDouble();
const qreal y = dom_element.attribute("y", nullptr).toDouble();
setPos(qIsFinite(x) ? x : 0, qIsFinite(y) ? y : 0);
bool retval = pathFromXml(dom_element);
@@ -1229,7 +1231,10 @@ bool Conductor::pathFromXml(const QDomElement &e) {
// cette longueur doit etre un reel
bool ok;
qreal segment_length = current_segment.attribute("length").toDouble(&ok);
if (!ok) continue;
// toDouble() accepts "nan" and "inf". A NaN length would also pass
// the coherence check below (every comparison with NaN is false)
// and turn the whole path into NaN.
if (!ok || !qIsFinite(segment_length)) continue;
if (current_segment.attribute("orientation") == "horizontal") {
segments_x << segment_length;
@@ -69,13 +69,17 @@ Conductor *ConductorTextItem::parentConductor() const
@param e
*/
void ConductorTextItem::fromXml(const QDomElement &e) {
if (e.hasAttribute("userx")) {
setPos(e.attribute("userx").toDouble(),
e.attribute("usery").toDouble());
//A non-finite ("nan", "inf") position or rotation is ignored,
//as if the text had never been moved or rotated
const qreal x = e.attribute("userx").toDouble();
const qreal y = e.attribute("usery").toDouble();
if (e.hasAttribute("userx") && qIsFinite(x) && qIsFinite(y)) {
setPos(x, y);
moved_by_user_ = true;
}
if (e.hasAttribute("rotation")) {
setRotation(e.attribute("rotation").toDouble());
const qreal rotation = e.attribute("rotation").toDouble();
if (e.hasAttribute("rotation") && qIsFinite(rotation)) {
setRotation(rotation);
rotate_by_user_ = true;
}
}
+13
View File
@@ -92,6 +92,16 @@ void QETMainWindow::initCommonActions()
}
});
export_config_action_ = new QAction(QET::Icons::DocumentExport, tr("Enregistrer la configuration sous..."), this);
export_config_action_ -> setStatusTip(tr("Enregistre les réglages de QElectroTech dans un fichier", "status bar tip"));
connect(export_config_action_, &QAction::triggered, qet_app, &QETApp::exportConfiguration);
ShortcutManager::instance().registerAction(export_config_action_, "mainwindow.export_configuration", tr("Général"), QKeySequence());
import_config_action_ = new QAction(QET::Icons::DocumentImport, tr("Charger une configuration..."), this);
import_config_action_ -> setStatusTip(tr("Remplace les réglages de QElectroTech par ceux d'un fichier, puis ferme QElectroTech", "status bar tip"));
connect(import_config_action_, &QAction::triggered, qet_app, &QETApp::importConfiguration);
ShortcutManager::instance().registerAction(import_config_action_, "mainwindow.import_configuration", tr("Général"), QKeySequence());
fullscreen_action_ = new QAction(this);
updateFullScreenAction();
connect(fullscreen_action_, &QAction::triggered, this, &QETMainWindow::toggleFullScreen);
@@ -170,6 +180,9 @@ void QETMainWindow::initCommonMenus()
settings_menu_ = new QMenu(tr("&Configuration", "window menu"), this);
settings_menu_ -> addAction(fullscreen_action_);
settings_menu_ -> addAction(configure_action_);
settings_menu_ -> addSeparator();
settings_menu_ -> addAction(export_config_action_);
settings_menu_ -> addAction(import_config_action_);
connect(settings_menu_, &QMenu::aboutToShow, this, &QETMainWindow::checkToolbarsmenu);
help_menu_ = new QMenu(tr("&Aide", "window menu"), this);
+2
View File
@@ -56,6 +56,8 @@ class QETMainWindow : public QMainWindow {
// attributes
protected:
QAction *configure_action_; ///< Launch the QElectroTech configuration dialog
QAction *export_config_action_; ///< Save the settings to a file
QAction *import_config_action_; ///< Replace the settings with a saved file
QAction *fullscreen_action_; ///< Toggle full screen
QAction *whatsthis_action_; ///< Toggle "What's this" mode
QAction *about_qet_; ///< Launch the "About QElectroTech" dialog
+3
View File
@@ -48,6 +48,7 @@
#include "richtexteditor_p.h"
#include "ui_addlinkdialog.h"
#include "../shortcutmanager.h"
#include "../qet.h"
//#include <QtDesigner/QDesignerFormEditorInterface>
@@ -242,6 +243,7 @@ AddLinkDialog::AddLinkDialog(RichTextEditor *editor, QWidget *parent) :
m_ui(new Ui::AddLinkDialog)
{
m_ui->setupUi(this);
QET::trackDialogGeometry(this);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
@@ -862,6 +864,7 @@ RichTextEditorDialog::RichTextEditorDialog(QWidget *parent) :
m_editor->setFocus();
resize(600, 400);
QET::trackDialogGeometry(this);
}
RichTextEditorDialog::~RichTextEditorDialog()
+3
View File
@@ -17,6 +17,8 @@
*/
#include "dimensionwidget.h"
#include "../qet.h"
/**
Constructor
@param complete True for this dialog to show the radio buttons that allow
@@ -31,6 +33,7 @@ TitleBlockDimensionWidget::TitleBlockDimensionWidget(bool complete, QWidget *par
{
initWidgets();
initLayouts();
QET::trackDialogGeometry(this);
}
/**
+1
View File
@@ -34,6 +34,7 @@ AboutQETDialog::AboutQETDialog(QWidget *parent) :
ui(new Ui::AboutQETDialog)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
setAbout();
setAuthors();
setTranslators();
+3
View File
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aiassistantdialog.h"
#include "../qet.h"
#include <QApplication>
#include <QCheckBox>
@@ -157,6 +158,8 @@ AiAssistantDialog::AiAssistantDialog(QWidget *parent) :
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
refresh();
QET::trackDialogGeometry(this);
}
void AiAssistantDialog::refresh()
+2
View File
@@ -20,11 +20,13 @@
#include "../qetgraphicsitem/dynamicelementtextitem.h"
#include "ui_alignmenttextdialog.h"
#include "../qet.h"
AlignmentTextDialog::AlignmentTextDialog(Qt::Alignment alignment, QWidget *parent) :
QDialog(parent),
ui(new Ui::AlignmentTextDialog)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
if(alignment == (Qt::AlignTop|Qt::AlignLeft))
ui->top_left->setChecked(true);
+2
View File
@@ -24,6 +24,7 @@
#include "../qetproject.h"
#include "ui_bomexportdialog.h"
#include "../qet.h"
#include <QMessageBox>
#include <QSqlError>
#include <QSqlQueryModel>
@@ -40,6 +41,7 @@ BOMExportDialog::BOMExportDialog(QETProject *project, QWidget *parent) :
m_project(project)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
m_query_widget = new ElementQueryWidget(this);
ui->m_main_layout->insertWidget(0, m_query_widget);
+3
View File
@@ -5,6 +5,7 @@
#include "../qetgraphicsitem/dynamicelementtextitem.h"
#include "../qetgraphicsitem/element.h"
#include "../qetinformation.h"
#include "../qet.h"
#include "ui_compositetexteditdialog.h"
#include <utility>
@@ -15,6 +16,7 @@ CompositeTextEditDialog::CompositeTextEditDialog(DynamicElementTextItem *text, Q
m_text(text)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
m_default_text = m_text->compositeText();
ui->m_plain_text_edit->setPlainText(m_default_text);
ui->m_plain_text_edit->setPlaceholderText(tr("Entrée votre texte composé ici, en vous aidant des variables disponible"));
@@ -31,6 +33,7 @@ CompositeTextEditDialog::CompositeTextEditDialog(QString text, bool report, QWid
ui(new Ui::CompositeTextEditDialog)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
m_default_text = std::move(text);
ui->m_plain_text_edit->setPlainText(m_default_text);
ui->m_plain_text_edit->setPlaceholderText(tr("Entrée votre texte composé ici, en vous aidant des variables disponible"));
+2
View File
@@ -24,6 +24,7 @@
#include "conductorpropertieswidget.h"
#include "ui_conductorpropertiesdialog.h"
#include "../qet.h"
/**
@brief ConductorPropertiesDialog::ConductorPropertiesDialog
Constructor
@@ -36,6 +37,7 @@ ConductorPropertiesDialog::ConductorPropertiesDialog(
ui(new Ui::ConductorPropertiesDialog)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
m_cpw = new ConductorPropertiesWidget(conductor->properties());
m_cpw -> setHiddenOneTextPerFolio(true);
m_cpw->setHiddenAvailableAutonum(true);
@@ -17,6 +17,8 @@
*/
#include "contactgroupselectiondialog.h"
#include "../qet.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QTableWidget>
@@ -207,6 +209,8 @@ ContactGroupSelectionDialog::ContactGroupSelectionDialog(
accept();
}
});
QET::trackDialogGeometry(this);
}
int ContactGroupSelectionDialog::selectedIndex() const
+2
View File
@@ -89,6 +89,8 @@ DiagramPropertiesDialog::DiagramPropertiesDialog(Diagram *diagram, QWidget *pare
vlayout.addLayout(glayout);
vlayout.addWidget(&boutons);
QET::trackDialogGeometry(this);
// if dialog is accepted
if (this -> exec() == QDialog::Accepted && !diagram_is_read_only)
{
+2
View File
@@ -18,6 +18,7 @@
#include "dialogwaiting.h"
#include "ui_dialogwaiting.h"
#include "../qet.h"
#include <QPushButton>
@@ -31,6 +32,7 @@ DialogWaiting::DialogWaiting(QWidget *parent) :
ui(new Ui::DialogWaiting)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
setTitle( "..." );
setDetail( "..." );
}
+3
View File
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "duplicateoffsetdialog.h"
#include "../qet.h"
#include <QComboBox>
#include <QDialogButtonBox>
@@ -76,6 +77,8 @@ DuplicateOffsetDialog::DuplicateOffsetDialog(QWidget *parent) :
auto *layout = new QVBoxLayout(this);
layout->addLayout(form);
layout->addWidget(buttons);
QET::trackDialogGeometry(this);
}
QPoint DuplicateOffsetDialog::stepOffset() const
+2
View File
@@ -18,6 +18,7 @@
#include "formulaassistantdialog.h"
#include "ui_formulaassistantdialog.h"
#include "../qet.h"
#include <QPushButton>
FormulaAssistantDialog::FormulaAssistantDialog(QWidget *parent) :
@@ -25,6 +26,7 @@ FormulaAssistantDialog::FormulaAssistantDialog(QWidget *parent) :
ui(new Ui::FormulaAssistantDialog)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
}
FormulaAssistantDialog::~FormulaAssistantDialog()
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "imagetransparentcolordialog.h"
#include "../qet.h"
#include <QDialogButtonBox>
#include <QGridLayout>
@@ -141,6 +142,8 @@ ImageTransparentColorDialog::ImageTransparentColorDialog(const QPixmap &basePixm
rebuildSwatches(); // shows existingColors immediately, if any
updatePreview(); // and the preview already reflects them too, rather than only appearing after the next pick
QET::trackDialogGeometry(this);
}
/**
+2
View File
@@ -17,6 +17,7 @@
*/
#include "importelementdialog.h"
#include "ui_importelementdialog.h"
#include "../qet.h"
#include <QButtonGroup>
ImportElementDialog::ImportElementDialog(QWidget *parent) :
@@ -24,6 +25,7 @@ ImportElementDialog::ImportElementDialog(QWidget *parent) :
ui(new Ui::ImportElementDialog)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
setUpWidget();
}
@@ -18,10 +18,12 @@
#include "importelementtextpatterndialog.h"
#include "ui_importelementtextpatterndialog.h"
#include "../qet.h"
ImportElementTextPatternDialog::ImportElementTextPatternDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::ImportElementTextPatternDialog) {
ui->setupUi(this);
QET::trackDialogGeometry(this);
}
ImportElementTextPatternDialog::~ImportElementTextPatternDialog()
+3
View File
@@ -20,6 +20,7 @@
#include "../diagram.h"
#include "../diagramview.h"
#include "../qetproject.h"
#include "../qet.h"
#include "../qetgraphicsitem/element.h"
#include <QEvent>
@@ -59,6 +60,8 @@ JumpToElementDialog::JumpToElementDialog(Diagram *diagram, QWidget *parent) :
buildCandidates();
updateFilteredList(QString());
m_filter_edit->setFocus();
QET::trackDialogGeometry(this);
}
JumpToElementDialog::~JumpToElementDialog()
+2
View File
@@ -18,6 +18,7 @@
#include "marginseditdialog.h"
#include "ui_marginseditdialog.h"
#include "../qet.h"
#include <QScopedPointer>
MarginsEditDialog::MarginsEditDialog(QMargins margins, QWidget *parent) :
@@ -25,6 +26,7 @@ MarginsEditDialog::MarginsEditDialog(QMargins margins, QWidget *parent) :
ui(new Ui::MarginsEditDialog)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
ui->m_top_sb->setValue(margins.top());
ui->m_left_sb->setValue(margins.left());
ui->m_right_sb->setValue(margins.right());
-1
View File
@@ -605,7 +605,6 @@ void MasterPropertiesWidget::updateUi()
// Type combo (read-only — type is defined in the editor)
auto *type_cb = new QComboBox(m_plc_table);
type_cb->setEnabled(false);
type_cb->setStyleSheet("QComboBox { background-color: #f0f0f0; }");
QStringList plc_types = ElementData::plcIOTypeList();
for (int t = 0; t < plc_types.size(); ++t) {
type_cb->addItem(plc_types.at(t), t);
+2
View File
@@ -26,6 +26,7 @@
#include "../qetgraphicsitem/conductor.h"
#include "../ui_multipastedialog.h"
#include "../qet.h"
#include <QHash>
#include <QSettings>
@@ -35,6 +36,7 @@ MultiPasteDialog::MultiPasteDialog(Diagram *diagram, QWidget *parent) :
m_diagram(diagram)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
connect(ui->m_x_sb, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &MultiPasteDialog::updatePreview);
connect(ui->m_y_sb, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &MultiPasteDialog::updatePreview);
+3
View File
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "pdfpagesdialog.h"
#include "../qet.h"
// Whole file is a no-op unless QtPdf is available (see pdfpagesdialog.h).
#ifdef QET_HAS_QTPDF
@@ -125,6 +126,8 @@ PdfPagesDialog::PdfPagesDialog(QPdfDocument &document, QWidget *parent)
// Render initial preview
updatePreview();
QET::trackDialogGeometry(this);
}
/**
+3
View File
@@ -19,6 +19,7 @@
#include "potentialselectordialog.h"
#include "../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../qet.h"
#include "../autoNum/assignvariables.h"
#include "../diagram.h"
#include "../qetgraphicsitem/conductor.h"
@@ -270,6 +271,7 @@ PotentialSelectorDialog::PotentialSelectorDialog(Conductor *conductor,
m_parent_undo(parent_undo)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
m_potential_selector = new NewConductorPotentialSelector(conductor);
buildWidget();
}
@@ -292,6 +294,7 @@ PotentialSelectorDialog::PotentialSelectorDialog(Element *report,
m_parent_undo(parent_undo)
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
m_potential_selector = new LinkReportPotentialSelector(report);
buildWidget();
}
+3
View File
@@ -5,6 +5,7 @@
#include "../diagram.h"
#include "../qetgraphicsitem/element.h"
#include "../undocommand/changeelementinformationcommand.h"
#include "../qet.h"
#include <QUndoCommand>
#include <QCheckBox>
#include <QVBoxLayout>
@@ -67,6 +68,8 @@ TerminalNumberingDialog::TerminalNumberingDialog(QWidget *parent, QETProject *pr
m_stripCheckboxes.insert(prefix, cb);
}
}
QET::trackDialogGeometry(this);
}
/**
@@ -18,6 +18,7 @@
#include "thirdpartybinaryinstalldialog.h"
#include "ui_thirdpartybinaryinstalldialog.h"
#include "../qet.h"
#include <QDialogButtonBox>
#include <QPushButton>
#include <QDesktopServices>
@@ -32,6 +33,7 @@ ThirdPartyBinaryInstallDialog::ThirdPartyBinaryInstallDialog(const QString &text
ui{new Ui::ThirdPartyBinaryInstallDialog}
{
ui->setupUi(this);
QET::trackDialogGeometry(this);
ui->m_label->setText(text);
connect(ui->m_download_pb, &QPushButton::clicked, [downloadLink](){
+3
View File
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "wiringlistdialog.h"
#include "../qet.h"
#include "../dataBase/projectdatabase.h"
#include "../qetproject.h"
@@ -111,4 +112,6 @@ WiringListDialog::WiringListDialog(QETProject *project, QWidget *parent) :
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Close, this);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
layout->addWidget(buttons);
QET::trackDialogGeometry(this);
}
+98
View File
@@ -0,0 +1,98 @@
/*
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 CONFIGPROFILE_H
#define CONFIGPROFILE_H
#include <QSettings>
#include <QString>
#include <QStringList>
/**
Configuration profiles (discussion #610): the settings of QElectroTech
saved to a file, and loaded back from one.
Two kinds of keys stay out of a profile, because they describe this
computer rather than how the user wants QElectroTech to behave: window
sizes, positions and dock layouts (".../geometry", ".../state"), and
the recent-files lists ("...-recentfiles/..."). They are not written to
the file, and loading a profile keeps the current ones.
*/
namespace ConfigProfile
{
/// Key written to every exported file, so that loading refuses a
/// file that was not exported by QElectroTech. Never copied into
/// the live settings.
inline const QString marker_key{QStringLiteral("qelectrotech-configuration/format")};
/// True for a key that stays out of a profile (see above).
inline bool isLocalKey(const QString &key)
{
return key.endsWith(QLatin1String("/geometry"))
|| key.endsWith(QLatin1String("/state"))
|| key.contains(QLatin1String("-recentfiles/"))
|| key == marker_key;
}
/// True if @a file was exported by exportTo().
inline bool isProfile(const QSettings &file)
{
return file.value(marker_key).toInt() == 1;
}
/// Replace the contents of @a file with every key of @a live,
/// except the local ones. Returns the number of keys written.
inline int exportTo(const QSettings &live, QSettings &file)
{
file.clear();
int written = 0;
const QStringList keys = live.allKeys();
for (const QString &key : keys) {
if (isLocalKey(key)) {
continue;
}
file.setValue(key, live.value(key));
++written;
}
file.setValue(marker_key, 1);
file.sync();
return written;
}
/// Replace every key of @a live, except the local ones, with the
/// keys of @a file. A key that is not in @a file is removed rather
/// than kept: loading a profile must not leave behind a setting
/// the previous profile made. Local keys in @a file are ignored.
inline void importFrom(const QSettings &file, QSettings &live)
{
const QStringList live_keys = live.allKeys();
for (const QString &key : live_keys) {
if (!isLocalKey(key)) {
live.remove(key);
}
}
const QStringList file_keys = file.allKeys();
for (const QString &key : file_keys) {
if (!isLocalKey(key)) {
live.setValue(key, file.value(key));
}
}
live.sync();
}
}
#endif // CONFIGPROFILE_H
+7
View File
@@ -111,6 +111,13 @@ add_test(NAME tst_alignment COMMAND tst_alignment)
target_include_directories(tst_alignment PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_alignment PRIVATE Qt::Test)
# configprofile.h is header-only: which settings a saved configuration
# profile carries, and how loading one replaces the live settings.
add_executable(tst_configprofile tst_configprofile.cpp)
add_test(NAME tst_configprofile COMMAND tst_configprofile)
target_include_directories(tst_configprofile PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_configprofile PRIVATE Qt::Test Qt::Gui)
# itemgroups.cpp keeps group membership and the selection rule apart from
# Diagram, so it is tested here on a plain scene of rectangles.
add_executable(tst_itemgroups tst_itemgroups.cpp ${QET_DIR}/sources/itemgroups.cpp)
+100
View File
@@ -0,0 +1,100 @@
#include <QtTest>
#include <QTemporaryDir>
#include "utils/configprofile.h"
class tst_configprofile : public QObject
{
Q_OBJECT
QTemporaryDir m_dir;
QString path(const QString &name) const { return m_dir.filePath(name); }
private slots:
void localKeys()
{
QVERIFY(ConfigProfile::isLocalKey("diagrameditor/geometry"));
QVERIFY(ConfigProfile::isLocalKey("elementeditor/state"));
QVERIFY(ConfigProfile::isLocalKey("projects-recentfiles/file1"));
QVERIFY(ConfigProfile::isLocalKey(ConfigProfile::marker_key));
QVERIFY(!ConfigProfile::isLocalKey("diagrameditor/Xgrid"));
QVERIFY(!ConfigProfile::isLocalKey("shortcuts/mainwindow.fullscreen"));
QVERIFY(!ConfigProfile::isLocalKey("masterpropertieswidget/plc-table-header-state"));
}
// Export leaves out window layout and recent files, and marks the file.
void exportLeavesOutLocalKeys()
{
QSettings live(path("live1.conf"), QSettings::IniFormat);
live.setValue("diagrameditor/Xgrid", 7);
live.setValue("lang", "de");
live.setValue("diagrameditor/geometry", QByteArray("xyz"));
live.setValue("projects-recentfiles/file1", "/home/a/b.qet");
QSettings file(path("profile1.conf"), QSettings::IniFormat);
QCOMPARE(ConfigProfile::exportTo(live, file), 2);
QVERIFY(ConfigProfile::isProfile(file));
QCOMPARE(file.value("diagrameditor/Xgrid").toInt(), 7);
QVERIFY(!file.contains("diagrameditor/geometry"));
QVERIFY(!file.contains("projects-recentfiles/file1"));
}
// Import replaces every setting, removes the ones the profile does not
// have, keeps window layout and recent files, and never copies the marker.
void importReplacesButKeepsLocal()
{
QSettings file(path("profile2.conf"), QSettings::IniFormat);
file.setValue("diagrameditor/Xgrid", 5);
file.setValue("diagrameditor/geometry", QByteArray("from-file"));
file.setValue(ConfigProfile::marker_key, 1);
QSettings live(path("live2.conf"), QSettings::IniFormat);
live.setValue("diagrameditor/Xgrid", 10);
live.setValue("lang", "fr");
live.setValue("diagrameditor/geometry", QByteArray("mine"));
live.setValue("projects-recentfiles/file1", "/home/a/b.qet");
ConfigProfile::importFrom(file, live);
QCOMPARE(live.value("diagrameditor/Xgrid").toInt(), 5);
QVERIFY(!live.contains("lang"));
QCOMPARE(live.value("diagrameditor/geometry").toByteArray(), QByteArray("mine"));
QCOMPARE(live.value("projects-recentfiles/file1").toString(), QString("/home/a/b.qet"));
QVERIFY(!live.contains(ConfigProfile::marker_key));
}
// Values survive the round trip with their types, including arrays.
void roundTrip()
{
QSettings a(path("a.conf"), QSettings::IniFormat);
a.setValue("print/default/fitinpage", true);
a.setValue("diagrameditor/sheet_background_color", QColor(12, 34, 56));
a.beginWriteArray("diagrameditor/defaultguides", 2);
a.setArrayIndex(0); a.setValue("pos", 100);
a.setArrayIndex(1); a.setValue("pos", 250);
a.endArray();
QSettings file(path("profile3.conf"), QSettings::IniFormat);
ConfigProfile::exportTo(a, file);
QSettings b(path("b.conf"), QSettings::IniFormat);
b.setValue("stray", 1);
ConfigProfile::importFrom(file, b);
QStringList ka = a.allKeys(), kb = b.allKeys();
ka.sort(); kb.sort();
QCOMPARE(kb, ka);
for (const QString &k : std::as_const(ka))
QCOMPARE(b.value(k), a.value(k));
}
void plainFileIsNotAProfile()
{
QSettings other(path("other.conf"), QSettings::IniFormat);
other.setValue("some/key", 1);
QVERIFY(!ConfigProfile::isProfile(other));
}
};
QTEST_GUILESS_MAIN(tst_configprofile)
#include "tst_configprofile.moc"