mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-01 07:14:14 +02:00
Merge upstream/master into prefix-button
Only one conflict: the include block of sources/ui/configpage/generalconfigurationpage.cpp, where upstream adds editor/terminalnamecheck.h next to the two includes of the prefix editor. All three are kept. The file list in cmake/qet_compilation_vars.cmake merged itself this time.
This commit is contained in:
@@ -754,6 +754,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/ui/diagrampropertieseditordockwidget.h
|
||||
${QET_DIR}/sources/ui/backupdialog.cpp
|
||||
${QET_DIR}/sources/ui/backupdialog.h
|
||||
${QET_DIR}/sources/ui/backuprestoredialog.cpp
|
||||
${QET_DIR}/sources/ui/backuprestoredialog.h
|
||||
${QET_DIR}/sources/ui/dialogwaiting.cpp
|
||||
${QET_DIR}/sources/ui/dialogwaiting.h
|
||||
${QET_DIR}/sources/ui/duplicateoffsetdialog.cpp
|
||||
@@ -867,6 +869,8 @@ set(QET_SRC_FILES
|
||||
|
||||
${QET_DIR}/sources/utils/conductorcreator.cpp
|
||||
${QET_DIR}/sources/utils/conductorcreator.h
|
||||
${QET_DIR}/sources/utils/colordialogdoubleclick.cpp
|
||||
${QET_DIR}/sources/utils/colordialogdoubleclick.h
|
||||
${QET_DIR}/sources/utils/macosxopenevent.cpp
|
||||
${QET_DIR}/sources/utils/macosxopenevent.h
|
||||
${QET_DIR}/sources/utils/qetsettings.cpp
|
||||
|
||||
+1
-1
@@ -1936,7 +1936,7 @@ Note: these options DO NOT allow or block auto numberings, only their update pol
|
||||
<source>
|
||||
<Shift> to move</source>
|
||||
<translation>
|
||||
<Shift> to move</translation>
|
||||
Hold Shift and drag to move this text on its own</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
||||
+1
-1
@@ -1935,7 +1935,7 @@ Remarque: Ces options n'autorisent ou bloquent l'auto numérotation, s
|
||||
<source>
|
||||
<Shift> to move</source>
|
||||
<translation>
|
||||
<Shift> 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);
|
||||
|
||||
|
||||
|
||||
@@ -172,6 +172,18 @@ void ElementsCollectionWidget::setCurrentLocation(
|
||||
m_model->indexFromLocation(location));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ElementsCollectionWidget::addTemplate
|
||||
Show a newly saved template in the templates tab, which is otherwise
|
||||
only read when the collections are loaded or reloaded.
|
||||
@param location : the saved .qetmak file
|
||||
*/
|
||||
void ElementsCollectionWidget::addTemplate(const ElementsLocation &location)
|
||||
{
|
||||
if (m_macros_model && location.exist())
|
||||
m_macros_model->addLocation(location);
|
||||
}
|
||||
|
||||
void ElementsCollectionWidget::leaveEvent(QEvent *event)
|
||||
{
|
||||
if (QETDiagramEditor *qde = QETApp::diagramEditorAncestorOf(this))
|
||||
|
||||
@@ -72,6 +72,7 @@ class ElementsCollectionWidget : public QWidget
|
||||
void removeProject (QETProject *project);
|
||||
void highlightUnusedElement();
|
||||
void setCurrentLocation(const ElementsLocation &location);
|
||||
void addTemplate(const ElementsLocation &location);
|
||||
QVector<ElementSearchHit> rankedSearch(const QString &text,
|
||||
const QModelIndex &within = QModelIndex());
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
+24
-1
@@ -24,6 +24,7 @@
|
||||
#include "dataBase/projectdatabase.h"
|
||||
#include "diagram.h"
|
||||
#include "diagramcontext.h"
|
||||
#include "editor/terminalnamecheck.h"
|
||||
#include "dxfexport.h"
|
||||
#include "exportproperties.h"
|
||||
#include "pdf_links.h"
|
||||
@@ -513,7 +514,20 @@ int checkOneElement(const QString &path)
|
||||
return 2;
|
||||
}
|
||||
|
||||
const int terminals = root.elementsByTagName("terminal").count();
|
||||
const QDomNodeList terminal_nodes = root.elementsByTagName("terminal");
|
||||
const int terminals = terminal_nodes.count();
|
||||
QStringList terminal_names;
|
||||
for (int i = 0; i < terminals; ++i)
|
||||
terminal_names << terminal_nodes.at(i).toElement().attribute("name");
|
||||
|
||||
// Two terminals with one name cannot be told apart in a wiring list
|
||||
// (IEC 61666), the same rule the element editor applies on save.
|
||||
const auto repeated = TerminalNameCheck::repeatedNames(terminal_names);
|
||||
if (!repeated.isEmpty()) {
|
||||
out << "FAIL " << path << " (repeated terminal names: "
|
||||
<< TerminalNameCheck::describe(repeated) << ")\n";
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Negative dimensions are malformed but QET still loads them; surface as a
|
||||
// warning rather than a failure so this agrees with QET's own loader.
|
||||
@@ -528,6 +542,15 @@ int checkOneElement(const QString &path)
|
||||
return 1;
|
||||
}
|
||||
|
||||
const QString type = root.attribute("link_type");
|
||||
const int unnamed = TerminalNameCheck::unnamedCount(terminal_names);
|
||||
if (unnamed && !type.endsWith("_report")
|
||||
&& type != "conductor_definition" && type != "thumbnail") {
|
||||
out << "WARN " << path << " (" << unnamed << " of " << terminals
|
||||
<< " terminals have no name)\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
out << "OK " << path << " (" << terminals << " terminals)\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -88,7 +88,12 @@ m_preview_item(nullptr)
|
||||
dummy_diagram->render(&painter, QRectF(QPointF(0,0), scene_rect.size()), scene_rect);
|
||||
|
||||
m_preview_item = new QGraphicsPixmapItem(pixmap);
|
||||
m_preview_item->setOffset(scene_rect.topLeft());
|
||||
// Anchor the preview on the template's own top-left
|
||||
// corner, where addMacro() puts it, not on the origin of
|
||||
// the folio it was saved from: a template saved from the
|
||||
// middle of a folio was otherwise shown, and placed, that
|
||||
// far below and to the right of the cursor.
|
||||
m_preview_item->setOffset(scene_rect.topLeft() - m_items_top_left);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +139,7 @@ void DiagramEventAddMacro::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
m_preview_item->setPos(pos_);
|
||||
|
||||
if (m_status_bar) {
|
||||
m_status_bar->showMessage(QString("x %1 : y %2 (Makro-Anker)").arg(QString::number(pos_.x()), QString::number(pos_.y())));
|
||||
m_status_bar->showMessage(QString("x %1 : y %2").arg(QString::number(pos_.x()), QString::number(pos_.y())));
|
||||
}
|
||||
}
|
||||
event->setAccepted(true);
|
||||
@@ -255,7 +260,14 @@ void DiagramEventAddMacro::addMacro(QPointF final_pos)
|
||||
QDomElement cloned_node = diagram_node.cloneNode(true).toElement();
|
||||
DiagramContent pasted_content;
|
||||
|
||||
m_diagram->fromXml(cloned_node, final_pos + m_items_top_left, false, &pasted_content);
|
||||
// fromXml() puts the template's top-left corner on final_pos,
|
||||
// under the cursor. It skips that for a null position, which
|
||||
// would leave the items where they were saved, so a click on
|
||||
// the folio origin is nudged by less than it snaps away.
|
||||
if (final_pos.isNull()) {
|
||||
final_pos = QPointF(0.1, 0.1);
|
||||
}
|
||||
m_diagram->fromXml(cloned_node, final_pos, false, &pasted_content);
|
||||
m_diagram->refreshContents();
|
||||
|
||||
// Prevent PasteDiagramCommand from erasing labels (BMK)
|
||||
|
||||
+188
-1
@@ -18,6 +18,7 @@
|
||||
#include "diagramview.h"
|
||||
#include "cellruler.h"
|
||||
#include "lastusedstyle.h"
|
||||
#include "utils/colordialogdoubleclick.h"
|
||||
#include "qetproject.h"
|
||||
#include "QPropertyUndoCommand/qpropertyundocommand.h"
|
||||
#include "diagramcommands.h"
|
||||
@@ -45,13 +46,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 +111,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);
|
||||
|
||||
@@ -165,6 +177,7 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
|
||||
color_dialog -> setWindowFlags(Qt::Sheet);
|
||||
#endif
|
||||
color_dialog->setCurrentColor(initial_properties.color);
|
||||
ColorDialogDoubleClick::install(color_dialog);
|
||||
|
||||
// asks the user what color he wishes to apply
|
||||
if (color_dialog->exec() == QDialog::Accepted)
|
||||
@@ -1765,7 +1778,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 +1800,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();
|
||||
@@ -2065,6 +2088,10 @@ void DiagramView::createTemplateFromSelection()
|
||||
file.close();
|
||||
qDebug() << "Template successfully saved to:" << full_path;
|
||||
|
||||
// List it right away, not only after the collections are reloaded
|
||||
for (QETDiagramEditor *qde : QETApp::diagramEditors())
|
||||
qde->templateSaved(template_location);
|
||||
|
||||
QMessageBox::information(this, tr("Modèle enregistré"),
|
||||
tr("Le modèle a été enregistré avec succès sous :\n%1").arg(full_path));
|
||||
} else {
|
||||
@@ -2073,6 +2100,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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
|
||||
#include "../../qetapp.h"
|
||||
#include "../../textanchor.h"
|
||||
#include "../elementscene.h"
|
||||
#include "../../utils/qetutils.h"
|
||||
#include <QApplication>
|
||||
@@ -474,6 +475,41 @@ Qt::Alignment PartDynamicTextField::alignment() const
|
||||
return m_alignment;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief PartDynamicTextField::anchorPos
|
||||
@return the anchor point of this text, in parent coordinates:
|
||||
the point of the text chosen by the alignment (top-left, right edge,
|
||||
centre...). For a top-left aligned text this is pos().
|
||||
Same as DiagramTextItem::anchorPos().
|
||||
*/
|
||||
QPointF PartDynamicTextField::anchorPos() const
|
||||
{
|
||||
return TextAnchor::pos(this, m_alignment);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief PartDynamicTextField::setAnchorPos
|
||||
Move this text so that its anchor point (see anchorPos()) is at anchor.
|
||||
@param anchor : in parent coordinates
|
||||
*/
|
||||
void PartDynamicTextField::setAnchorPos(const QPointF &anchor)
|
||||
{
|
||||
setPos(TextAnchor::itemPosFor(this, m_alignment, anchor));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief PartDynamicTextField::setAlignmentAtAnchor
|
||||
Change the alignment and move the text so that its new anchor point
|
||||
is where the previous one was.
|
||||
@param alignment
|
||||
*/
|
||||
void PartDynamicTextField::setAlignmentAtAnchor(Qt::Alignment alignment)
|
||||
{
|
||||
const QPointF anchor = anchorPos();
|
||||
setAlignment(alignment);
|
||||
setAnchorPos(anchor);
|
||||
}
|
||||
|
||||
void PartDynamicTextField::setFont(const QFont &font) {
|
||||
if (font == this -> font()) {
|
||||
return;
|
||||
|
||||
@@ -42,6 +42,8 @@ class PartDynamicTextField : public QGraphicsTextItem, public CustomElementPart
|
||||
Q_PROPERTY(bool frame READ frame WRITE setFrame NOTIFY frameChanged)
|
||||
Q_PROPERTY(qreal textWidth READ textWidth WRITE setTextWidth NOTIFY textWidthChanged)
|
||||
Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged)
|
||||
Q_PROPERTY(QPointF anchorPos READ anchorPos WRITE setAnchorPos)
|
||||
Q_PROPERTY(Qt::Alignment alignmentAtAnchor READ alignment WRITE setAlignmentAtAnchor NOTIFY alignmentChanged)
|
||||
Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged)
|
||||
Q_PROPERTY(bool keepVisualRotation READ keepVisualRotation WRITE setKeepVisualRotation NOTIFY keepVisualRotationChanged)
|
||||
Q_PROPERTY(bool rotationPointCenter READ rotationPointCenter WRITE setRotationPointCenter NOTIFY rotationPointCenterChanged)
|
||||
@@ -101,6 +103,9 @@ class PartDynamicTextField : public QGraphicsTextItem, public CustomElementPart
|
||||
void setPlainText(const QString &text);
|
||||
void setAlignment(Qt::Alignment alignment);
|
||||
Qt::Alignment alignment() const;
|
||||
QPointF anchorPos() const;
|
||||
void setAnchorPos(const QPointF &anchor);
|
||||
void setAlignmentAtAnchor(Qt::Alignment alignment);
|
||||
void setFont(const QFont &font);
|
||||
void setKeepVisualRotation(const bool &keep);
|
||||
bool keepVisualRotation() const;
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
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 TERMINALNAMECHECK_H
|
||||
#define TERMINALNAMECHECK_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
/**
|
||||
The terminal name check run when an element is saved, and by
|
||||
--check-elements. IEC 61666 requires every terminal to be identified
|
||||
unambiguously within its object: two terminals of one element must not
|
||||
share a name, and a terminal should have one.
|
||||
|
||||
Names are compared after trimming spaces at both ends, and are case
|
||||
sensitive ("n" and "N" are different names).
|
||||
*/
|
||||
namespace TerminalNameCheck
|
||||
{
|
||||
/// QSettings key: false turns the check off in the element editor.
|
||||
inline const QString settings_key{QStringLiteral("elementeditor/check-terminal-names")};
|
||||
|
||||
/// The names used by more than one terminal, with how many terminals
|
||||
/// use each, in the order the names first appear. Unnamed terminals
|
||||
/// are not counted here.
|
||||
inline QList<QPair<QString, int>> repeatedNames(const QStringList &names)
|
||||
{
|
||||
QHash<QString, int> count;
|
||||
QStringList order;
|
||||
for (const QString &raw : names) {
|
||||
const QString name = raw.trimmed();
|
||||
if (name.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (!count.contains(name)) {
|
||||
order << name;
|
||||
}
|
||||
++count[name];
|
||||
}
|
||||
|
||||
QList<QPair<QString, int>> repeated;
|
||||
for (const QString &name : order) {
|
||||
if (count.value(name) > 1) {
|
||||
repeated << qMakePair(name, count.value(name));
|
||||
}
|
||||
}
|
||||
return repeated;
|
||||
}
|
||||
|
||||
/// The number of terminals without a name.
|
||||
inline int unnamedCount(const QStringList &names)
|
||||
{
|
||||
int unnamed = 0;
|
||||
for (const QString &name : names) {
|
||||
if (name.trimmed().isEmpty()) {
|
||||
++unnamed;
|
||||
}
|
||||
}
|
||||
return unnamed;
|
||||
}
|
||||
|
||||
/// "N ×3, L ×2": the repeated names, for messages.
|
||||
inline QString describe(const QList<QPair<QString, int>> &repeated)
|
||||
{
|
||||
QStringList parts;
|
||||
for (const auto &entry : repeated) {
|
||||
parts << QStringLiteral("%1 ×%2").arg(entry.first, QString::number(entry.second));
|
||||
}
|
||||
return parts.join(QStringLiteral(", "));
|
||||
}
|
||||
}
|
||||
|
||||
#endif // TERMINALNAMECHECK_H
|
||||
@@ -134,8 +134,8 @@ QList<CustomElementPart*> DynamicTextFieldEditor::currentParts() const
|
||||
void DynamicTextFieldEditor::updateForm()
|
||||
{
|
||||
if(m_text_field) {
|
||||
ui -> m_x_sb -> setValue(m_text_field.data() -> x());
|
||||
ui -> m_y_sb -> setValue(m_text_field.data() ->y ());
|
||||
ui -> m_x_sb -> setValue(m_text_field.data() -> anchorPos().x());
|
||||
ui -> m_y_sb -> setValue(m_text_field.data() -> anchorPos().y());
|
||||
ui -> m_rotation_sb -> setValue(QET::correctAngle(m_text_field.data() -> rotation()));
|
||||
ui -> m_frame_cb -> setChecked(m_text_field.data() -> frame());
|
||||
ui -> m_user_text_le -> setText(m_text_field.data() -> text());
|
||||
@@ -211,6 +211,9 @@ void DynamicTextFieldEditor::setUpConnections()
|
||||
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::compositeTextChanged,this, [this](){this -> updateForm();});
|
||||
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::keepVisualRotationChanged, this, [this](){this -> updateForm();});
|
||||
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::rotationPointCenterChanged, this, [this](){this -> updateForm();});
|
||||
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::alignmentChanged, this, [this](){this -> updateForm();});
|
||||
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::xChanged, this, [this](){this -> updateForm();});
|
||||
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::yChanged, this, [this](){this -> updateForm();});
|
||||
|
||||
// Refresh info combo when element data changes (e.g. type switched to PLC-Slave)
|
||||
m_connection_list << connect(elementEditor()->elementScene(), &ElementScene::elementInfoChanged,
|
||||
@@ -290,7 +293,8 @@ void DynamicTextFieldEditor::on_m_x_sb_editingFinished()
|
||||
{
|
||||
double value = ui -> m_x_sb -> value();
|
||||
for (int i = 0; i < m_parts.length(); i++) {
|
||||
QPropertyUndoCommand *undo = new QPropertyUndoCommand(m_parts[i], "x", m_parts[i] -> x(), value);
|
||||
const QPointF anchor = m_parts[i] -> anchorPos();
|
||||
QPropertyUndoCommand *undo = new QPropertyUndoCommand(m_parts[i], "anchorPos", anchor, QPointF(value, anchor.y()));
|
||||
undo -> setText(tr("Déplacer un champ texte"));
|
||||
undo -> enableAnimation(true);
|
||||
undoStack().push(undo);
|
||||
@@ -301,7 +305,8 @@ void DynamicTextFieldEditor::on_m_y_sb_editingFinished()
|
||||
{
|
||||
double value = ui -> m_y_sb -> value();
|
||||
for (int i = 0; i < m_parts.length(); i++) {
|
||||
QPropertyUndoCommand *undo = new QPropertyUndoCommand(m_parts[i], "y", m_parts[i] -> y(), value);
|
||||
const QPointF anchor = m_parts[i] -> anchorPos();
|
||||
QPropertyUndoCommand *undo = new QPropertyUndoCommand(m_parts[i], "anchorPos", anchor, QPointF(anchor.x(), value));
|
||||
undo -> setText(tr("Déplacer un champ texte"));
|
||||
undo -> enableAnimation(true);
|
||||
undoStack().push(undo);
|
||||
@@ -459,7 +464,7 @@ void DynamicTextFieldEditor::on_m_alignment_pb_clicked()
|
||||
if(atd.alignment() != m_parts[i] -> alignment()) {
|
||||
QPropertyUndoCommand *undo =\
|
||||
new QPropertyUndoCommand(
|
||||
m_parts[i], "alignment", QVariant(m_parts[i] -> alignment()), QVariant(atd.alignment()));
|
||||
m_parts[i], "alignmentAtAnchor", QVariant(m_parts[i] -> alignment()), QVariant(atd.alignment()));
|
||||
undo -> setText(tr("Modifier l'alignement d'un champ texte"));
|
||||
undoStack().push(undo);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "../esevent/eseventadddynamictextfield.h"
|
||||
#include "../../elementdialog.h"
|
||||
#include "../graphicspart/partterminal.h"
|
||||
#include "../terminalnamecheck.h"
|
||||
#include "../arceditor.h"
|
||||
#include "ellipseeditor.h"
|
||||
#include "lineeditor.h"
|
||||
@@ -59,6 +60,10 @@
|
||||
#include <QFileDialog>
|
||||
#include <QSvgGenerator>
|
||||
#include <QHBoxLayout>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QFormLayout>
|
||||
|
||||
/**
|
||||
* @brief QETElementEditor::QETElementEditor
|
||||
@@ -835,6 +840,53 @@ bool QETElementEditor::checkElement()
|
||||
}
|
||||
}
|
||||
|
||||
// Check terminal names: repeated names are an error, missing names a warning
|
||||
if (QSettings().value(TerminalNameCheck::settings_key, true).toBool())
|
||||
{
|
||||
QList<PartTerminal *> terminals;
|
||||
QStringList names;
|
||||
for (auto qgi : m_elmt_scene -> items()) {
|
||||
if (auto terminal = qgraphicsitem_cast<PartTerminal *>(qgi)) {
|
||||
terminals << terminal;
|
||||
names << terminal -> terminalName();
|
||||
}
|
||||
}
|
||||
|
||||
const auto repeated = TerminalNameCheck::repeatedNames(names);
|
||||
if (!repeated.isEmpty())
|
||||
{
|
||||
errors << qMakePair (tr("Noms de bornes en double"),
|
||||
tr("<br><b>Erreur</b> :"
|
||||
"<br>Plusieurs bornes portent le même nom : %1."
|
||||
"<br><b>Solution</b> :"
|
||||
"<br>Donner un nom unique à chaque borne, par exemple N.1 et N.2."
|
||||
" Les bornes concernées sont sélectionnées.")
|
||||
.arg(TerminalNameCheck::describe(repeated).toHtmlEscaped()));
|
||||
|
||||
m_elmt_scene -> clearSelection();
|
||||
for (auto terminal : terminals) {
|
||||
for (const auto &entry : repeated) {
|
||||
if (terminal -> terminalName().trimmed() == entry.first) {
|
||||
terminal -> setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int unnamed = TerminalNameCheck::unnamedCount(names);
|
||||
if (unnamed &&
|
||||
!(m_elmt_scene->elementData().m_type & ElementData::AllReport) &&
|
||||
m_elmt_scene->elementData().m_type != ElementData::ConductorDefinition &&
|
||||
m_elmt_scene->elementData().m_type != ElementData::Thumbnail)
|
||||
{
|
||||
warnings << qMakePair (tr("Bornes sans nom"),
|
||||
tr("<br>%n borne(s) sans nom. Sans noms de bornes uniques,"
|
||||
" la liste de câblage (qui relie quoi à quoi) ne peut pas"
|
||||
" désigner chaque borne, et ne peut donc pas servir à"
|
||||
" câbler l'armoire en atelier.", "", unnamed));
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.count() && !warnings.count()) {
|
||||
return(true);
|
||||
}
|
||||
@@ -1138,6 +1190,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,6 +41,7 @@ ElementsCategoryEditor::ElementsCategoryEditor(const ElementsLocation &location,
|
||||
m_location(location)
|
||||
{
|
||||
setUpWidget();
|
||||
QET::trackDialogGeometry(this);
|
||||
|
||||
if (m_location.isElement()) {
|
||||
QET::QetMessageBox::warning(this,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
+135
-26
@@ -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,9 +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
|
||||
@@ -65,6 +69,9 @@
|
||||
#else
|
||||
# include <KAutoSaveFile>
|
||||
#endif
|
||||
#include "ui/backuprestoredialog.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef QET_ALLOW_OVERRIDE_CED_OPTION
|
||||
QString QETApp::m_overrided_common_elements_dir = QString();
|
||||
@@ -2242,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.
|
||||
@@ -2839,48 +2949,47 @@ void QETApp::checkBackupFiles()
|
||||
/**
|
||||
@brief QETApp::offerBackupFiles
|
||||
Ask whether to reopen the recovery files left by a previous run, and
|
||||
open or discard them accordingly.
|
||||
open or discard them accordingly. A project can leave several recovery
|
||||
files, one per snapshot (@see QETProject::writeBackup): they are grouped
|
||||
by project, and the user picks which one to reopen, the newest by default.
|
||||
@param stale_files : the recovery files to offer
|
||||
*/
|
||||
void QETApp::offerBackupFiles(const QList<KAutoSaveFile *> &stale_files)
|
||||
{
|
||||
QString text;
|
||||
if(stale_files.size() == 1) {
|
||||
text.append(tr("<b>Le fichier de restauration suivant a été trouvé,<br>"
|
||||
"Voulez-vous l'ouvrir ?</b><br>"));
|
||||
} else {
|
||||
text.append(tr("<b>Les fichiers de restauration suivant on été trouvé,<br>"
|
||||
"Voulez-vous les ouvrir ?</b><br>"));
|
||||
//Group the snapshots by the project they recover, newest first.
|
||||
QHash<QString, QList<KAutoSaveFile *>> groups;
|
||||
for (KAutoSaveFile *kasf : stale_files) {
|
||||
groups[kasf->managedFile().path()].append(kasf);
|
||||
}
|
||||
for(const KAutoSaveFile *kasf : stale_files)
|
||||
{
|
||||
# ifdef Q_OS_WIN
|
||||
//Remove the first character '/' before the name of the drive
|
||||
text.append("<br>" + kasf->managedFile().path().remove(0,1));
|
||||
# else
|
||||
text.append("<br>" + kasf->managedFile().path());
|
||||
# endif
|
||||
for (auto &snapshots : groups) {
|
||||
std::sort(snapshots.begin(), snapshots.end(),
|
||||
[](KAutoSaveFile *a, KAutoSaveFile *b) {
|
||||
return QFileInfo(*a).lastModified()
|
||||
> QFileInfo(*b).lastModified();
|
||||
});
|
||||
}
|
||||
|
||||
//Open backup file
|
||||
if (QET::QetMessageBox::question(nullptr,
|
||||
tr("Fichier de restauration"),
|
||||
text,
|
||||
QMessageBox::Ok
|
||||
|QMessageBox::Cancel
|
||||
)
|
||||
== QMessageBox::Ok)
|
||||
BackupRestoreDialog dialog(groups, nullptr);
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
//The snapshots not picked are no longer needed.
|
||||
for (KAutoSaveFile *discarded : dialog.discardedFiles())
|
||||
{
|
||||
discarded->open(QIODevice::ReadWrite);
|
||||
delete discarded;
|
||||
}
|
||||
|
||||
const QList<KAutoSaveFile *> to_open = dialog.selectedFiles();
|
||||
//If there are open editors, find those that are visible
|
||||
if (diagramEditors().count())
|
||||
{
|
||||
diagramEditors().first()->setVisible(true);
|
||||
diagramEditors().first()->openBackupFiles(stale_files);
|
||||
diagramEditors().first()->openBackupFiles(to_open);
|
||||
}
|
||||
else
|
||||
{
|
||||
QETDiagramEditor *editor = new QETDiagramEditor();
|
||||
editor->openBackupFiles(stale_files);
|
||||
editor->openBackupFiles(to_open);
|
||||
}
|
||||
}
|
||||
else //Clear backup 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);
|
||||
|
||||
@@ -1781,6 +1781,17 @@ ProjectView *QETDiagramEditor::currentProjectView() const
|
||||
return(nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETDiagramEditor::templateSaved
|
||||
List a template saved from a folio in this editor's templates tab.
|
||||
@param location : the saved .qetmak file
|
||||
*/
|
||||
void QETDiagramEditor::templateSaved(const ElementsLocation &location)
|
||||
{
|
||||
if (m_element_collection_widget)
|
||||
m_element_collection_widget->addTemplate(location);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETDiagramEditor::currentProject
|
||||
@return the current edited project.
|
||||
|
||||
@@ -75,6 +75,7 @@ class QETDiagramEditor : public QETMainWindow
|
||||
ProjectView *viewForFile (const QString &) const;
|
||||
ProjectView *currentProjectView() const;
|
||||
QETProject *currentProject() const;
|
||||
void templateSaved(const ElementsLocation &location);
|
||||
bool drawGrid() const;
|
||||
void openBackupFiles (QList<KAutoSaveFile *> backup_files);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "../diagramcommands.h"
|
||||
#include "../qetapp.h"
|
||||
#include "../richtext/richtexteditor_p.h"
|
||||
#include "../textanchor.h"
|
||||
|
||||
/**
|
||||
@brief DiagramTextItem::DiagramTextItem
|
||||
@@ -193,6 +194,41 @@ Qt::Alignment DiagramTextItem::alignment() const
|
||||
return m_alignment;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramTextItem::anchorPos
|
||||
@return the anchor point of this text, in parent coordinates:
|
||||
the point of the text chosen by the alignment (top-left, right edge,
|
||||
centre...). For a top-left aligned text this is pos().
|
||||
The anchor stays in place when the text changes, see finishAlignment().
|
||||
*/
|
||||
QPointF DiagramTextItem::anchorPos() const
|
||||
{
|
||||
return TextAnchor::pos(this, m_alignment);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramTextItem::setAnchorPos
|
||||
Move this text so that its anchor point (see anchorPos()) is at anchor.
|
||||
@param anchor : in parent coordinates
|
||||
*/
|
||||
void DiagramTextItem::setAnchorPos(const QPointF &anchor)
|
||||
{
|
||||
setPos(TextAnchor::itemPosFor(this, m_alignment, anchor));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramTextItem::setAlignmentAtAnchor
|
||||
Change the alignment and move the text so that its new anchor point
|
||||
is where the previous one was.
|
||||
@param alignment
|
||||
*/
|
||||
void DiagramTextItem::setAlignmentAtAnchor(const Qt::Alignment &alignment)
|
||||
{
|
||||
const QPointF anchor = anchorPos();
|
||||
setAlignment(alignment);
|
||||
setAnchorPos(anchor);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramTextItem::frameRect
|
||||
@return the rect used to draw a frame around this text
|
||||
|
||||
@@ -36,6 +36,8 @@ class DiagramTextItem : public QGraphicsTextItem
|
||||
|
||||
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
|
||||
Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged)
|
||||
Q_PROPERTY(QPointF anchorPos READ anchorPos WRITE setAnchorPos)
|
||||
Q_PROPERTY(Qt::Alignment alignmentAtAnchor READ alignment WRITE setAlignmentAtAnchor NOTIFY alignmentChanged)
|
||||
Q_PROPERTY(QString plainText READ toPlainText WRITE setPlainText)
|
||||
Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged)
|
||||
|
||||
@@ -77,6 +79,10 @@ class DiagramTextItem : public QGraphicsTextItem
|
||||
void setAlignment(const Qt::Alignment &alignment);
|
||||
Qt::Alignment alignment() const;
|
||||
bool m_block_alignment = false;
|
||||
|
||||
QPointF anchorPos() const;
|
||||
void setAnchorPos(const QPointF &anchor);
|
||||
void setAlignmentAtAnchor(const Qt::Alignment &alignment);
|
||||
|
||||
QRectF frameRect() const;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
+18
-9
@@ -205,7 +205,7 @@ QETProject::QETProject(KAutoSaveFile *backup, QObject *parent) :
|
||||
QETProject::~QETProject()
|
||||
{
|
||||
//Wait for any in-flight async crash-recovery backup to finish: the worker
|
||||
//writes through &m_backup_file, a member that would otherwise be destroyed
|
||||
//writes through m_backup_files, a member that would otherwise be destroyed
|
||||
//under it (issue #492).
|
||||
m_backup_future.waitForFinished();
|
||||
|
||||
@@ -562,12 +562,16 @@ void QETProject::setFilePath(const QString &filepath)
|
||||
if (filepath == m_file_path) {
|
||||
return;
|
||||
}
|
||||
//Don't close/re-point the backup file while a backup is still writing it.
|
||||
//Don't close/re-point the backup files while a backup is still writing one.
|
||||
m_backup_future.waitForFinished();
|
||||
if (m_backup_file.isOpen()) {
|
||||
m_backup_file.close();
|
||||
const QUrl managed_file = QUrl::fromLocalFile(filepath);
|
||||
for (auto &backup_file : m_backup_files) {
|
||||
if (backup_file.isOpen()) {
|
||||
backup_file.close();
|
||||
}
|
||||
backup_file.setManagedFile(managed_file);
|
||||
}
|
||||
m_backup_file.setManagedFile(QUrl::fromLocalFile(filepath));
|
||||
m_next_backup_slot = 0;
|
||||
m_file_path = filepath;
|
||||
|
||||
QFileInfo fi(m_file_path);
|
||||
@@ -2337,14 +2341,17 @@ void QETProject::detachDiagram(Diagram *diagram)
|
||||
|
||||
/**
|
||||
@brief QETProject::writeBackup
|
||||
Write a backup file of this project, in the case that QET crash
|
||||
Write a backup file of this project, in the case that QET crash.
|
||||
The snapshots are written in turn to m_backup_files, so a write made
|
||||
while the project is already in a bad state only replaces the oldest
|
||||
snapshot, and the earlier ones are still there to recover from.
|
||||
*/
|
||||
void QETProject::writeBackup()
|
||||
{
|
||||
if (!m_backup_enabled)
|
||||
return;
|
||||
//Don't launch a new backup while the previous one is still writing:
|
||||
//both would write through &m_backup_file on different threads.
|
||||
//both could write through the same m_backup_files slot on different threads.
|
||||
if (m_backup_future.isRunning())
|
||||
return;
|
||||
//toXml() walks the whole project on the GUI thread, which freezes
|
||||
@@ -2357,8 +2364,10 @@ void QETProject::writeBackup()
|
||||
//Qt5-style QtConcurrent::run(function, reference-args) call did not
|
||||
//survive the Qt6 API change, a lambda behaves identically on both.
|
||||
QDomDocument xml_project(toXml());
|
||||
m_backup_future = QtConcurrent::run([this, xml_project]() mutable {
|
||||
return QET::writeToFile(xml_project, &m_backup_file, nullptr);
|
||||
KAutoSaveFile *target = &m_backup_files[m_next_backup_slot];
|
||||
m_next_backup_slot = (m_next_backup_slot + 1) % BackupGenerations;
|
||||
m_backup_future = QtConcurrent::run([target, xml_project]() mutable {
|
||||
return QET::writeToFile(xml_project, target, nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
#include <QSet>
|
||||
#include <QFuture>
|
||||
|
||||
#include <array>
|
||||
|
||||
class Diagram;
|
||||
class ElementsLocation;
|
||||
class QETResult;
|
||||
@@ -128,6 +130,10 @@ class QETProject : public QObject
|
||||
/// process can destroy the project before the write finishes (crash).
|
||||
static void setBackupEnabled(bool enabled);
|
||||
|
||||
/// Number of crash-recovery snapshots kept per project, written in
|
||||
/// turn by writeBackup(), so one bad write cannot replace the only copy
|
||||
static constexpr int BackupGenerations = 3;
|
||||
|
||||
///DEFAULT PROPERTIES
|
||||
BorderProperties defaultBorderProperties() const;
|
||||
void setDefaultBorderProperties(const BorderProperties &);
|
||||
@@ -366,7 +372,9 @@ class QETProject : public QObject
|
||||
QTimer m_save_backup_timer,
|
||||
m_autosave_timer;
|
||||
QFuture<bool> m_backup_future;
|
||||
KAutoSaveFile m_backup_file;
|
||||
/// Crash-recovery snapshots, written in turn by writeBackup()
|
||||
std::array<KAutoSaveFile, BackupGenerations> m_backup_files;
|
||||
int m_next_backup_slot = 0;
|
||||
QUuid m_uuid = QUuid::createUuid();
|
||||
QHash<QString, int> m_derived_uuid_keys;
|
||||
QSet<QUuid> m_saved_item_uuids; //symbol and wire uuids the file carries, see derivedItemUuid()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
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 TEXTANCHOR_H
|
||||
#define TEXTANCHOR_H
|
||||
|
||||
#include <QGraphicsItem>
|
||||
#include <QPointF>
|
||||
#include <QRectF>
|
||||
|
||||
/**
|
||||
The anchor point of a text: the point of the text chosen by its
|
||||
alignment (top-left, right edge, centre...). It is the point that
|
||||
stays in place when the text changes, and the point shown as
|
||||
"Position X/Y" in the text properties.
|
||||
|
||||
The saved position of a text is still its top-left corner (pos()),
|
||||
so existing projects and symbols are read and written unchanged.
|
||||
*/
|
||||
namespace TextAnchor
|
||||
{
|
||||
/**
|
||||
@return the point of rect chosen by alignment, in the
|
||||
coordinates of rect. Left and top are x = 0 and y = 0, as
|
||||
finishAlignment() of the text items assumes.
|
||||
*/
|
||||
inline QPointF localPoint(const QRectF &rect, Qt::Alignment alignment)
|
||||
{
|
||||
qreal x = 0, y = 0;
|
||||
|
||||
if (alignment & Qt::AlignRight)
|
||||
x = rect.right();
|
||||
else if (alignment & Qt::AlignHCenter)
|
||||
x = rect.center().x();
|
||||
|
||||
if (alignment & Qt::AlignBottom)
|
||||
y = rect.bottom();
|
||||
else if (alignment & Qt::AlignVCenter)
|
||||
y = rect.center().y();
|
||||
|
||||
return QPointF(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
@return the anchor point of item for alignment, in parent
|
||||
coordinates. For a top-left alignment this is item->pos().
|
||||
The rotation of the item is taken into account.
|
||||
*/
|
||||
inline QPointF pos(const QGraphicsItem *item, Qt::Alignment alignment)
|
||||
{
|
||||
return item->pos()
|
||||
+ item->mapToParent(localPoint(item->boundingRect(), alignment))
|
||||
- item->mapToParent(QPointF(0, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
@return the position (top-left corner) to give to item so that
|
||||
its anchor point for alignment is at anchor.
|
||||
*/
|
||||
inline QPointF itemPosFor(const QGraphicsItem *item,
|
||||
Qt::Alignment alignment,
|
||||
const QPointF &anchor)
|
||||
{
|
||||
return item->pos() + anchor - pos(item, alignment);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // TEXTANCHOR_H
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,7 @@ AboutQETDialog::AboutQETDialog(QWidget *parent) :
|
||||
ui(new Ui::AboutQETDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
QET::trackDialogGeometry(this);
|
||||
setAbout();
|
||||
setAuthors();
|
||||
setTranslators();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
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 "backuprestoredialog.h"
|
||||
|
||||
#ifdef BUILD_WITHOUT_KF
|
||||
# include "nokde/kautosavefile.h"
|
||||
#else
|
||||
# include <KAutoSaveFile>
|
||||
#endif
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDateTime>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileInfo>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QLocale>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
/**
|
||||
@brief BackupRestoreDialog::BackupRestoreDialog
|
||||
@param groups : managed project path -> its recovery generations
|
||||
(newest-first)
|
||||
@param parent : parent widget
|
||||
*/
|
||||
BackupRestoreDialog::BackupRestoreDialog(
|
||||
const QHash<QString, QList<KAutoSaveFile *>> &groups,
|
||||
QWidget *parent) :
|
||||
QDialog(parent),
|
||||
m_groups(groups)
|
||||
{
|
||||
setWindowTitle(tr("Fichiers de restauration", "window title"));
|
||||
|
||||
auto main_layout = new QVBoxLayout(this);
|
||||
|
||||
auto intro = new QLabel(
|
||||
tr("<b>Des fichiers de restauration ont été trouvés,<br>"
|
||||
"voulez-vous les ouvrir ?</b><br>"
|
||||
"Pour un projet ayant plusieurs versions de restauration, "
|
||||
"la plus récente est sélectionnée par défaut.",
|
||||
"dialog message"));
|
||||
intro->setWordWrap(true);
|
||||
main_layout->addWidget(intro);
|
||||
|
||||
auto grid = new QGridLayout();
|
||||
int row = 0;
|
||||
for (auto it = m_groups.constBegin(); it != m_groups.constEnd(); ++it)
|
||||
{
|
||||
const QString &path = it.key();
|
||||
const QList<KAutoSaveFile *> &generations = it.value();
|
||||
|
||||
# ifdef Q_OS_WIN
|
||||
QString display_path = path;
|
||||
display_path.remove(0, 1);
|
||||
# else
|
||||
const QString &display_path = path;
|
||||
# endif
|
||||
grid->addWidget(new QLabel(display_path), row, 0);
|
||||
|
||||
auto combo = new QComboBox(this);
|
||||
for (int i = 0; i < generations.size(); ++i)
|
||||
{
|
||||
const QDateTime modified =
|
||||
QFileInfo(*generations.at(i)).lastModified();
|
||||
QString label = QLocale::system().toString(
|
||||
modified, QLocale::ShortFormat);
|
||||
if (i == 0) {
|
||||
label = tr("%1 (la plus récente)", "recovery generation label")
|
||||
.arg(label);
|
||||
}
|
||||
combo->addItem(label);
|
||||
}
|
||||
combo->setEnabled(generations.size() > 1);
|
||||
grid->addWidget(combo, row, 1);
|
||||
|
||||
m_combo_for_path.insert(path, combo);
|
||||
++row;
|
||||
}
|
||||
main_layout->addLayout(grid);
|
||||
|
||||
auto buttons = new QDialogButtonBox(
|
||||
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
main_layout->addWidget(buttons);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BackupRestoreDialog::~BackupRestoreDialog
|
||||
*/
|
||||
BackupRestoreDialog::~BackupRestoreDialog() = default;
|
||||
|
||||
/**
|
||||
@return one recovery generation per project, matching the combo box
|
||||
selection (the newest generation, unless the user picked another).
|
||||
*/
|
||||
QList<KAutoSaveFile *> BackupRestoreDialog::selectedFiles() const
|
||||
{
|
||||
QList<KAutoSaveFile *> selected;
|
||||
for (auto it = m_groups.constBegin(); it != m_groups.constEnd(); ++it)
|
||||
{
|
||||
const int index = m_combo_for_path.value(it.key())->currentIndex();
|
||||
selected << it.value().at(index);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
/**
|
||||
@return every recovery generation the user did not pick; the caller
|
||||
should release and delete these.
|
||||
*/
|
||||
QList<KAutoSaveFile *> BackupRestoreDialog::discardedFiles() const
|
||||
{
|
||||
QList<KAutoSaveFile *> discarded;
|
||||
for (auto it = m_groups.constBegin(); it != m_groups.constEnd(); ++it)
|
||||
{
|
||||
const int index = m_combo_for_path.value(it.key())->currentIndex();
|
||||
const QList<KAutoSaveFile *> &generations = it.value();
|
||||
for (int i = 0; i < generations.size(); ++i) {
|
||||
if (i != index) {
|
||||
discarded << generations.at(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return discarded;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
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 BACKUPRESTOREDIALOG_H
|
||||
#define BACKUPRESTOREDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
class KAutoSaveFile;
|
||||
class QComboBox;
|
||||
|
||||
/**
|
||||
@brief Lets the user pick, per crashed project, which recovery
|
||||
generation to reopen when the crash-recovery rotation
|
||||
(@see QETProject::writeBackup) left more than one snapshot behind.
|
||||
*/
|
||||
class BackupRestoreDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/// @param groups : managed project file path -> its stale recovery
|
||||
/// generations, each list already sorted newest-first. Ownership of
|
||||
/// the KAutoSaveFile objects stays with the caller.
|
||||
explicit BackupRestoreDialog(
|
||||
const QHash<QString, QList<KAutoSaveFile *>> &groups,
|
||||
QWidget *parent = nullptr);
|
||||
~BackupRestoreDialog() override;
|
||||
|
||||
/// Valid once accepted: one entry per project, the chosen generation
|
||||
/// (defaults to the most recent one).
|
||||
QList<KAutoSaveFile *> selectedFiles() const;
|
||||
/// The generations the user did not pick; the caller should discard
|
||||
/// (release + delete) these.
|
||||
QList<KAutoSaveFile *> discardedFiles() const;
|
||||
|
||||
private:
|
||||
QHash<QString, QList<KAutoSaveFile *>> m_groups;
|
||||
QHash<QString, QComboBox *> m_combo_for_path;
|
||||
};
|
||||
|
||||
#endif // BACKUPRESTOREDIALOG_H
|
||||
@@ -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);
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "../projectview.h"
|
||||
#include "../qetgraphicsitem/conductor.h"
|
||||
#include "../conductorproperties.h"
|
||||
#include "../utils/colordialogdoubleclick.h"
|
||||
|
||||
#include <QColorDialog>
|
||||
#include <QMenu>
|
||||
@@ -217,10 +218,11 @@ void ConductorColorToolButton::applyColor(const QColor &color)
|
||||
*/
|
||||
void ConductorColorToolButton::chooseOtherColor()
|
||||
{
|
||||
const QColor c = QColorDialog::getColor(m_current, this,
|
||||
tr("Choisir une couleur de conducteur"));
|
||||
if (c.isValid()) {
|
||||
applyColor(c);
|
||||
QColorDialog dialog(m_current, this);
|
||||
dialog.setWindowTitle(tr("Choisir une couleur de conducteur"));
|
||||
ColorDialogDoubleClick::install(&dialog);
|
||||
if (dialog.exec() == QDialog::Accepted && dialog.selectedColor().isValid()) {
|
||||
applyColor(dialog.selectedColor());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "../../utils/qetutils.h"
|
||||
#include "../../qetmessagebox.h"
|
||||
#include "../../textgrid.h"
|
||||
#include "../../editor/terminalnamecheck.h"
|
||||
#include "../../ElementsCollection/qetlabelsfile.h"
|
||||
#include "../prefixconfigurationdialog.h"
|
||||
#include "../nokde/kcolorbutton.h"
|
||||
@@ -178,6 +179,7 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) :
|
||||
ui->MaxPartsElementEditorList_sb->setValue(settings.value("elementeditor/max-parts-element-editor-list", 200).toInt());
|
||||
ui->ElementEditor_Grid_PointSize_min_sb->setValue(settings.value("elementeditor/grid_pointsize_min", 1).toInt());
|
||||
ui->ElementEditor_Grid_PointSize_max_sb->setValue(settings.value("elementeditor/grid_pointsize_max", 1).toInt());
|
||||
ui->m_check_terminal_names_cb->setChecked(settings.value(TerminalNameCheck::settings_key, true).toBool());
|
||||
|
||||
QString path = settings.value("elements-collections/common-collection-path", "default").toString();
|
||||
if (path != "default")
|
||||
@@ -290,6 +292,7 @@ void GeneralConfigurationPage::applyConf()
|
||||
settings.setValue("elementeditor/max-parts-element-editor-list", ui->MaxPartsElementEditorList_sb->value());
|
||||
settings.setValue("elementeditor/grid_pointsize_min", ui->ElementEditor_Grid_PointSize_min_sb->value());
|
||||
settings.setValue("elementeditor/grid_pointsize_max", ui->ElementEditor_Grid_PointSize_max_sb->value());
|
||||
settings.setValue(TerminalNameCheck::settings_key, ui->m_check_terminal_names_cb->isChecked());
|
||||
|
||||
//DIAGRAM VIEW
|
||||
settings.setValue("diagramview/gestures", ui->m_use_gesture_trackpad->isChecked());
|
||||
|
||||
@@ -1250,6 +1250,19 @@ Vous pouvez spécifier ici la valeur par défaut de ce champ pour les éléments
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="m_check_terminal_names_cb">
|
||||
<property name="toolTip">
|
||||
<string>À l'enregistrement d'un élément : refuser deux bornes portant le même nom, et signaler les bornes sans nom (IEC 61666).</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Vérifier les noms des bornes à l'enregistrement</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_6">
|
||||
<property name="orientation">
|
||||
@@ -1285,6 +1298,7 @@ Vous pouvez spécifier ici la valeur par défaut de ce champ pour les éléments
|
||||
<tabstop>m_custom_tbt_path_cb</tabstop>
|
||||
<tabstop>m_highlight_integrated_elements</tabstop>
|
||||
<tabstop>m_default_elements_info</tabstop>
|
||||
<tabstop>m_check_terminal_names_cb</tabstop>
|
||||
<tabstop>m_lang_cb</tabstop>
|
||||
<tabstop>m_dyn_text_font_pb</tabstop>
|
||||
<tabstop>m_dyn_text_rotation_sb</tabstop>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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( "..." );
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -303,7 +303,7 @@ QList<QStandardItem *> DynamicElementTextModel::itemsForText(
|
||||
x_pos->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
||||
|
||||
QStandardItem *x_pos_a = new QStandardItem;
|
||||
x_pos_a->setData(deti->pos().x(), Qt::EditRole);
|
||||
x_pos_a->setData(deti->anchorPos().x(), Qt::EditRole);
|
||||
x_pos_a->setData(DynamicElementTextModel::pos, Qt::UserRole+1);
|
||||
x_pos_a->setFlags(Qt::ItemIsSelectable
|
||||
| Qt::ItemIsEnabled
|
||||
@@ -318,7 +318,7 @@ QList<QStandardItem *> DynamicElementTextModel::itemsForText(
|
||||
y_pos->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
||||
|
||||
QStandardItem *y_pos_a = new QStandardItem;
|
||||
y_pos_a->setData(deti->pos().y(), Qt::EditRole);
|
||||
y_pos_a->setData(deti->anchorPos().y(), Qt::EditRole);
|
||||
y_pos_a->setData(DynamicElementTextModel::pos, Qt::UserRole+1);
|
||||
y_pos_a->setFlags(Qt::ItemIsSelectable
|
||||
| Qt::ItemIsEnabled
|
||||
@@ -600,9 +600,9 @@ QUndoCommand *DynamicElementTextModel::undoForEditedText(
|
||||
{
|
||||
QPointF p(text_qsi->child(x_txt_row,1)->data(Qt::EditRole).toDouble(),
|
||||
text_qsi->child(y_txt_row,1)->data(Qt::EditRole).toDouble());
|
||||
if(p != deti->pos())
|
||||
if(p != deti->anchorPos())
|
||||
{
|
||||
QPropertyUndoCommand *quc = new QPropertyUndoCommand(deti, "pos", QVariant(deti->pos()), QVariant(p), undo);
|
||||
QPropertyUndoCommand *quc = new QPropertyUndoCommand(deti, "anchorPos", QVariant(deti->anchorPos()), QVariant(p), undo);
|
||||
quc->setAnimated(true, false);
|
||||
quc->setText(tr("Déplacer un texte d'élément"));
|
||||
}
|
||||
@@ -636,7 +636,7 @@ QUndoCommand *DynamicElementTextModel::undoForEditedText(
|
||||
Qt::Alignment alignment = text_qsi->child(align_txt_row, 1)->data(Qt::UserRole+2).value<Qt::Alignment>();
|
||||
if (alignment != deti->alignment())
|
||||
{
|
||||
QPropertyUndoCommand *quc = new QPropertyUndoCommand(deti, "alignment", QVariant(deti->alignment()), QVariant(alignment), undo);
|
||||
QPropertyUndoCommand *quc = new QPropertyUndoCommand(deti, "alignmentAtAnchor", QVariant(deti->alignment()), QVariant(alignment), undo);
|
||||
quc->setText(tr("Modifier l'alignement d'un texte d'élément"));
|
||||
}
|
||||
}
|
||||
@@ -1508,9 +1508,9 @@ void DynamicElementTextModel::updateDataFromText(DynamicElementTextItem *deti,
|
||||
case pos:
|
||||
{
|
||||
if(qsi->child(x_txt_row,1))
|
||||
qsi->child(x_txt_row,1)->setData(deti->pos().x(), Qt::EditRole);
|
||||
qsi->child(x_txt_row,1)->setData(deti->anchorPos().x(), Qt::EditRole);
|
||||
if(qsi->child(y_txt_row,1))
|
||||
qsi->child(y_txt_row,1)->setData(deti->pos().y(), Qt::EditRole);
|
||||
qsi->child(y_txt_row,1)->setData(deti->anchorPos().y(), Qt::EditRole);
|
||||
break;
|
||||
}
|
||||
case frame:
|
||||
@@ -1531,6 +1531,14 @@ void DynamicElementTextModel::updateDataFromText(DynamicElementTextItem *deti,
|
||||
}
|
||||
default:break;
|
||||
}
|
||||
|
||||
//Rotating or resizing the text moves its anchor point (Position X/Y)
|
||||
if ((type == rotation || type == textWidth)
|
||||
&& qsi->child(x_txt_row,1) && qsi->child(y_txt_row,1))
|
||||
{
|
||||
qsi->child(x_txt_row,1)->setData(deti->anchorPos().x(), Qt::EditRole);
|
||||
qsi->child(y_txt_row,1)->setData(deti->anchorPos().y(), Qt::EditRole);
|
||||
}
|
||||
|
||||
m_block_dataChanged = false;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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](){
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 "colordialogdoubleclick.h"
|
||||
|
||||
#include <QColorDialog>
|
||||
#include <QEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QWidget>
|
||||
|
||||
/**
|
||||
@brief ColorDialogDoubleClick::install
|
||||
Watch the swatches of @a dialog. The filter is a child of the dialog
|
||||
and is deleted with it.
|
||||
@param dialog
|
||||
*/
|
||||
void ColorDialogDoubleClick::install(QColorDialog *dialog)
|
||||
{
|
||||
if (!dialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
//The swatches are looked for when the dialog is shown: Qt builds
|
||||
//them only then when the platform offered a native dialog it
|
||||
//could not show after all.
|
||||
dialog->installEventFilter(new ColorDialogDoubleClick(dialog));
|
||||
}
|
||||
|
||||
ColorDialogDoubleClick::ColorDialogDoubleClick(QColorDialog *dialog) :
|
||||
QObject(dialog),
|
||||
m_dialog(dialog)
|
||||
{}
|
||||
|
||||
/**
|
||||
@brief ColorDialogDoubleClick::eventFilter
|
||||
The first click of a double-click has already selected the swatch
|
||||
(on its release), so the dialog's current colour is the one
|
||||
double-clicked: accept it. Queued, so the swatch finishes handling
|
||||
the second click before the dialog closes.
|
||||
*/
|
||||
bool ColorDialogDoubleClick::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (watched == m_dialog && event->type() == QEvent::Show) {
|
||||
//QWellArray is Qt's private class behind both swatch grids;
|
||||
//its name is the only handle on them (in the QtPrivate
|
||||
//namespace in recent Qt 6). If a Qt release renames it,
|
||||
//nothing matches and double-click just selects, as before.
|
||||
//Installing twice only moves the filter to the front.
|
||||
const auto children = m_dialog->findChildren<QWidget *>();
|
||||
for (QWidget *child : children) {
|
||||
if (child->inherits("QtPrivate::QWellArray")
|
||||
|| child->inherits("QWellArray")) {
|
||||
child->installEventFilter(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (event->type() == QEvent::MouseButtonDblClick
|
||||
&& static_cast<QMouseEvent *>(event)->button() == Qt::LeftButton)
|
||||
{
|
||||
QMetaObject::invokeMethod(m_dialog, &QDialog::accept, Qt::QueuedConnection);
|
||||
}
|
||||
return QObject::eventFilter(watched, event);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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 COLOR_DIALOG_DOUBLE_CLICK_H
|
||||
#define COLOR_DIALOG_DOUBLE_CLICK_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class QColorDialog;
|
||||
|
||||
/**
|
||||
@brief The ColorDialogDoubleClick class
|
||||
Lets a double-click on one of a QColorDialog's colour swatches (the
|
||||
basic colours and the custom colours) choose that colour and close
|
||||
the dialog, as OK does. Qt's dialog only selects the swatch.
|
||||
|
||||
Only Qt's own dialog has swatches to watch. Where the platform shows
|
||||
its native dialog instead (macOS, some Linux desktops), this does
|
||||
nothing and the native dialog behaves as it always has.
|
||||
*/
|
||||
class ColorDialogDoubleClick : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static void install(QColorDialog *dialog);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
private:
|
||||
explicit ColorDialogDoubleClick(QColorDialog *dialog);
|
||||
QColorDialog *m_dialog;
|
||||
};
|
||||
|
||||
#endif // COLOR_DIALOG_DOUBLE_CLICK_H
|
||||
@@ -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
|
||||
@@ -3,14 +3,18 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QTemporaryDir>
|
||||
#include <QUrl>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
#include <ctime>
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
@@ -106,3 +110,104 @@ TEST_CASE("Qt-only KAutoSaveFile recovers stale files", "[nokde][autosave]")
|
||||
CHECK_FALSE(QFile::exists(lock_file_name));
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("Multiple KAutoSaveFile generations for one managed file are all "
|
||||
"found stale after a crash", "[nokde][autosave]")
|
||||
{
|
||||
#ifndef Q_OS_UNIX
|
||||
SUCCEED("crash-style stale lock test is Unix-only");
|
||||
#else
|
||||
//Simulates QETProject's rotating crash-recovery generations
|
||||
//(BackupGenerations snapshots written round-robin): several
|
||||
//KAutoSaveFile instances sharing one managed file, alive at once.
|
||||
QTemporaryDir data_home;
|
||||
REQUIRE(data_home.isValid());
|
||||
|
||||
qputenv("XDG_DATA_HOME", QFile::encodeName(data_home.path()));
|
||||
QCoreApplication::setOrganizationName(QStringLiteral("QElectroTech"));
|
||||
QCoreApplication::setApplicationName(
|
||||
QStringLiteral("KAutoSaveFileGenerationsTest"));
|
||||
|
||||
const auto managed_path = data_home.filePath(QStringLiteral("project.qet"));
|
||||
QFile managed_file(managed_path);
|
||||
REQUIRE(managed_file.open(QIODevice::WriteOnly | QIODevice::Text));
|
||||
REQUIRE(managed_file.write("<project/>\n") > 0);
|
||||
managed_file.close();
|
||||
|
||||
constexpr int generations = 3;
|
||||
|
||||
int ready_pipe[2] = {-1, -1};
|
||||
REQUIRE(pipe(ready_pipe) == 0);
|
||||
|
||||
const auto child_pid = fork();
|
||||
REQUIRE(child_pid >= 0);
|
||||
|
||||
if (child_pid == 0) {
|
||||
close(ready_pipe[0]);
|
||||
|
||||
std::vector<std::unique_ptr<KAutoSaveFile>> backups;
|
||||
for (int i = 0; i < generations; ++i) {
|
||||
auto backup = std::make_unique<KAutoSaveFile>(
|
||||
QUrl::fromLocalFile(managed_path));
|
||||
if (!backup->open(QIODevice::WriteOnly
|
||||
| QIODevice::Truncate
|
||||
| QIODevice::Text)) {
|
||||
_exit(2);
|
||||
}
|
||||
const QByteArray payload =
|
||||
"<project><generation n=\"" + QByteArray::number(i)
|
||||
+ "\" /></project>\n";
|
||||
if (backup->write(payload) != payload.size()) {
|
||||
_exit(3);
|
||||
}
|
||||
if (!backup->flush()) {
|
||||
_exit(4);
|
||||
}
|
||||
//Give each generation a distinct, increasing mtime.
|
||||
struct timespec pause{0, 20 * 1000 * 1000};
|
||||
nanosleep(&pause, nullptr);
|
||||
backups.push_back(std::move(backup));
|
||||
}
|
||||
|
||||
const char ready = '1';
|
||||
if (write(ready_pipe[1], &ready, 1) != 1) {
|
||||
_exit(5);
|
||||
}
|
||||
close(ready_pipe[1]);
|
||||
|
||||
for (;;) {
|
||||
pause();
|
||||
}
|
||||
}
|
||||
|
||||
close(ready_pipe[1]);
|
||||
char ready = 0;
|
||||
REQUIRE(read(ready_pipe[0], &ready, 1) == 1);
|
||||
close(ready_pipe[0]);
|
||||
REQUIRE(ready == '1');
|
||||
|
||||
REQUIRE(kill(child_pid, SIGKILL) == 0);
|
||||
int status = 0;
|
||||
REQUIRE(waitpid(child_pid, &status, 0) == child_pid);
|
||||
REQUIRE(WIFSIGNALED(status));
|
||||
REQUIRE(WTERMSIG(status) == SIGKILL);
|
||||
|
||||
auto stale_files = KAutoSaveFile::allStaleFiles();
|
||||
REQUIRE(stale_files.size() == generations);
|
||||
|
||||
std::sort(stale_files.begin(), stale_files.end(),
|
||||
[](KAutoSaveFile *a, KAutoSaveFile *b) {
|
||||
return QFileInfo(*a).lastModified() < QFileInfo(*b).lastModified();
|
||||
});
|
||||
|
||||
for (int i = 0; i < generations; ++i) {
|
||||
std::unique_ptr<KAutoSaveFile> stale_file(stale_files.at(i));
|
||||
CHECK(stale_file->managedFile().path()
|
||||
== QFileInfo(managed_path).absoluteFilePath());
|
||||
REQUIRE(stale_file->open(QIODevice::ReadOnly | QIODevice::Text));
|
||||
const QByteArray content = stale_file->readAll();
|
||||
CHECK(content.contains(
|
||||
"generation n=\"" + QByteArray::number(i) + "\""));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -111,12 +111,35 @@ 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)
|
||||
|
||||
# terminalnamecheck.h is header-only: the terminal name rule applied by the
|
||||
# element editor on save and by --check-elements.
|
||||
add_executable(tst_terminalnamecheck tst_terminalnamecheck.cpp)
|
||||
add_test(NAME tst_terminalnamecheck COMMAND tst_terminalnamecheck)
|
||||
target_include_directories(tst_terminalnamecheck PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_terminalnamecheck 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)
|
||||
add_test(NAME tst_itemgroups COMMAND tst_itemgroups)
|
||||
target_include_directories(tst_itemgroups PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_itemgroups PRIVATE Qt::Test Qt::Widgets Qt::Xml)
|
||||
|
||||
# textanchor.h is header-only: the point of a text chosen by its alignment,
|
||||
# shown as Position X/Y in the text properties (#1155).
|
||||
add_executable(tst_textanchor tst_textanchor.cpp)
|
||||
add_test(NAME tst_textanchor COMMAND tst_textanchor)
|
||||
set_tests_properties(tst_textanchor PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
|
||||
target_include_directories(tst_textanchor PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_textanchor PRIVATE Qt::Test Qt::Widgets)
|
||||
|
||||
# legacyelementtypes.h is header-only: the rewrite of element type names in
|
||||
# nomenclature queries saved before June 2022.
|
||||
add_executable(tst_legacyelementtypes tst_legacyelementtypes.cpp)
|
||||
|
||||
@@ -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"
|
||||
@@ -0,0 +1,55 @@
|
||||
#include <QtTest>
|
||||
|
||||
#include "editor/terminalnamecheck.h"
|
||||
|
||||
class tst_terminalnamecheck : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void uniqueNamesPass()
|
||||
{
|
||||
QVERIFY(TerminalNameCheck::repeatedNames({"A1", "A2", "13", "14"}).isEmpty());
|
||||
}
|
||||
|
||||
// Shelly Pro 2PM in the shipped collection: three terminals named N.
|
||||
void repeatedNamesAreCounted()
|
||||
{
|
||||
const auto repeated = TerminalNameCheck::repeatedNames(
|
||||
{"L", "N", "O1", "N", "O2", "N", "L"});
|
||||
QCOMPARE(repeated.size(), 2);
|
||||
QCOMPARE(repeated.at(0), qMakePair(QString("L"), 2));
|
||||
QCOMPARE(repeated.at(1), qMakePair(QString("N"), 3));
|
||||
QCOMPARE(TerminalNameCheck::describe(repeated),
|
||||
QString::fromUtf8("L ×2, N ×3"));
|
||||
}
|
||||
|
||||
void surroundingSpacesAreIgnored()
|
||||
{
|
||||
QCOMPARE(TerminalNameCheck::repeatedNames({"PE", " PE "}).size(), 1);
|
||||
}
|
||||
|
||||
void caseMatters()
|
||||
{
|
||||
QVERIFY(TerminalNameCheck::repeatedNames({"n", "N"}).isEmpty());
|
||||
}
|
||||
|
||||
// Unnamed terminals are reported as unnamed, never as a repeat of "".
|
||||
void unnamedAreNotRepeats()
|
||||
{
|
||||
const QStringList names{"", " ", "1", ""};
|
||||
QVERIFY(TerminalNameCheck::repeatedNames(names).isEmpty());
|
||||
QCOMPARE(TerminalNameCheck::unnamedCount(names), 3);
|
||||
}
|
||||
|
||||
// describe() goes into a tr() message with %1: a name holding %2 stays literal.
|
||||
void describeKeepsPercentLiteral()
|
||||
{
|
||||
QCOMPARE(TerminalNameCheck::describe({qMakePair(QString("%2"), 2)}),
|
||||
QString::fromUtf8("%2 ×2"));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(tst_terminalnamecheck)
|
||||
|
||||
#include "tst_terminalnamecheck.moc"
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
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 <QtTest>
|
||||
#include <QGraphicsTextItem>
|
||||
|
||||
#include "textanchor.h"
|
||||
|
||||
static bool samePoint(const QPointF &a, const QPointF &b)
|
||||
{
|
||||
return qAbs(a.x() - b.x()) < 1e-9 && qAbs(a.y() - b.y()) < 1e-9;
|
||||
}
|
||||
|
||||
class tst_textanchor : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
// The texts of issue #1155: Position X/Y is the point chosen by the
|
||||
// alignment, so a right-aligned label keeps its right edge at X.
|
||||
void pointOfTheText_data()
|
||||
{
|
||||
QTest::addColumn<int>("alignment");
|
||||
QTest::addColumn<qreal>("fx"); // fraction of the width
|
||||
QTest::addColumn<qreal>("fy"); // fraction of the height
|
||||
|
||||
QTest::newRow("top left") << int(Qt::AlignTop | Qt::AlignLeft) << 0.0 << 0.0;
|
||||
QTest::newRow("top right") << int(Qt::AlignTop | Qt::AlignRight) << 1.0 << 0.0;
|
||||
QTest::newRow("centre") << int(Qt::AlignVCenter | Qt::AlignHCenter) << 0.5 << 0.5;
|
||||
QTest::newRow("middle left") << int(Qt::AlignVCenter | Qt::AlignLeft) << 0.0 << 0.5;
|
||||
QTest::newRow("middle right") << int(Qt::AlignVCenter | Qt::AlignRight) << 1.0 << 0.5;
|
||||
QTest::newRow("bottom centre") << int(Qt::AlignBottom | Qt::AlignHCenter) << 0.5 << 1.0;
|
||||
}
|
||||
|
||||
void pointOfTheText()
|
||||
{
|
||||
QFETCH(int, alignment);
|
||||
QFETCH(qreal, fx);
|
||||
QFETCH(qreal, fy);
|
||||
|
||||
QGraphicsTextItem text(QStringLiteral("XT2:12"));
|
||||
text.setPos(-52, -3);
|
||||
const QRectF r = text.boundingRect();
|
||||
|
||||
const QPointF expected(-52 + fx * r.width(), -3 + fy * r.height());
|
||||
QVERIFY(samePoint(TextAnchor::pos(&text, Qt::Alignment(alignment)), expected));
|
||||
}
|
||||
|
||||
// Top-left alignment keeps today's numbers: the anchor is pos(),
|
||||
// whatever the rotation or the rotation point.
|
||||
void topLeftIsPos()
|
||||
{
|
||||
QGraphicsTextItem text(QStringLiteral("-K1"));
|
||||
text.setPos(12.5, -40);
|
||||
for (qreal angle : {0.0, 90.0, 180.0, 270.0, 30.0}) {
|
||||
text.setRotation(angle);
|
||||
for (bool centre : {false, true}) {
|
||||
text.setTransformOriginPoint(centre ? text.boundingRect().center() : QPointF());
|
||||
QVERIFY(samePoint(TextAnchor::pos(&text, Qt::AlignTop | Qt::AlignLeft), text.pos()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A rotated text: the anchor is the point of the text where it is
|
||||
// drawn, e.g. the right edge of a text turned by 90° is below pos().
|
||||
void rotatedTextAnchorIsDrawnPoint()
|
||||
{
|
||||
QGraphicsTextItem text(QStringLiteral("XT2:12"));
|
||||
text.setPos(100, 100);
|
||||
text.setRotation(90);
|
||||
const QRectF r = text.boundingRect();
|
||||
|
||||
const QPointF anchor = TextAnchor::pos(&text, Qt::AlignTop | Qt::AlignRight);
|
||||
QVERIFY(samePoint(anchor, text.mapToParent(QPointF(r.right(), 0))));
|
||||
QVERIFY(samePoint(anchor, QPointF(100, 100 + r.right())));
|
||||
}
|
||||
|
||||
// Typing an anchor puts that point of the text there, for every
|
||||
// alignment, rotation and rotation point.
|
||||
void itemPosForRoundTrips()
|
||||
{
|
||||
const QList<Qt::Alignment> horizontal {Qt::AlignLeft, Qt::AlignHCenter, Qt::AlignRight};
|
||||
const QList<Qt::Alignment> vertical {Qt::AlignTop, Qt::AlignVCenter, Qt::AlignBottom};
|
||||
|
||||
QGraphicsTextItem text(QStringLiteral("Right."));
|
||||
text.setPos(-81, -13);
|
||||
const QPointF anchor(-52, -3);
|
||||
|
||||
for (Qt::Alignment h : horizontal)
|
||||
for (Qt::Alignment v : vertical)
|
||||
for (qreal angle : {0.0, 90.0, 270.0, 45.0})
|
||||
for (bool centre : {false, true}) {
|
||||
text.setRotation(angle);
|
||||
text.setTransformOriginPoint(centre ? text.boundingRect().center() : QPointF());
|
||||
text.setPos(TextAnchor::itemPosFor(&text, h | v, anchor));
|
||||
QVERIFY2(samePoint(TextAnchor::pos(&text, h | v), anchor),
|
||||
qPrintable(QStringLiteral("h %1 v %2 angle %3 centre %4")
|
||||
.arg(int(h)).arg(int(v)).arg(angle).arg(centre)));
|
||||
}
|
||||
}
|
||||
|
||||
// A longer text with the same anchor keeps its right edge in place:
|
||||
// the gap between a label and its symbol does not change (#1155).
|
||||
void rightEdgeStaysWhenTextGrows()
|
||||
{
|
||||
QGraphicsTextItem text(QStringLiteral("XT2:2"));
|
||||
const QPointF anchor(-10, 0);
|
||||
const Qt::Alignment right = Qt::AlignTop | Qt::AlignRight;
|
||||
text.setPos(TextAnchor::itemPosFor(&text, right, anchor));
|
||||
const qreal short_left = text.pos().x();
|
||||
|
||||
text.setPlainText(QStringLiteral("XT2:12"));
|
||||
text.setPos(TextAnchor::itemPosFor(&text, right, anchor));
|
||||
|
||||
QVERIFY(text.pos().x() < short_left);
|
||||
QCOMPARE(text.pos().x() + text.boundingRect().right(), anchor.x());
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(tst_textanchor)
|
||||
#include "tst_textanchor.moc"
|
||||
Reference in New Issue
Block a user