Merge upstream/master: translation wave plus generic device

Upstream moved 56 commits ahead of the last merge. Six files conflicted,
all of them files where upstream's French -> English tr() translation wave
touched lines we had also touched with the cable feature. Resolution rule
applied everywhere: take upstream's English source strings, keep our cable
additions.

Resolved:

- sources/ui/xrefpropertieswidget.ui : kept our structure (m_labels_gb
  pulled out of the display group, m_slave_row, the m_font_pb "Police..."
  button of the cable cross-reference) and mechanically applied upstream's
  20 English string replacements on top of it.

- sources/ui/xrefpropertieswidget.cpp : our version plus upstream's 7
  translated combo entries; the tr("Cable") item we add for the cable
  cross-reference type stays.

- sources/autoNum/ui/numparteditorw.cpp : upstream's English part names
  ("number format 1", "Sheet no.", "Plant", "Location", ...) for the
  number-format list, with our comment explaining why type 3 (cable) takes
  the conductor kinds of variable.

- sources/ui/configpage/configpages.cpp : upstream's English tab titles
  (Conductors / Elements / Sheets) plus our Cables tab.

- sources/ui/configpage/projectconfigpages.cpp : upstream's "Sheets" tab
  title plus our Cables tab block (single-rule SelectAutonumW(3)).

- sources/qetdiagrameditor.cpp : union of both sides - upstream's new
  "Add a generic device" action (icon, status tip, data id) and its
  English menu names ("File"/"Edit"/"Project"/"Display"), together with our
  cable action, the Numbering and Lists menus, the cable list actions and
  the CSV export in the Project menu.

Kept French: our own cable tr() source strings stay French for now. That
is the smaller change and the translation pass can follow separately.

Verified: full build 1065/1065, ctest 91/92 (only the pre-existing
headless tst_menubarkeyboard fails: no window manager, so Alt+F and F10
cannot open a menu bar), smoke start exit 124, no conflict marker left in
the tree.
This commit is contained in:
Kellermorph
2026-10-09 20:44:58 +02:00
437 changed files with 233166 additions and 197854 deletions
+185 -18
View File
@@ -34,6 +34,7 @@
#include "qetdiagrameditor.h"
#include "qetgraphicsitem/conductor.h"
#include "qetgraphicsitem/conductortextitem.h"
#include "qetgraphicsitem/diagramimageitem.h"
#include "qetgraphicsitem/independenttextitem.h"
#include "qeticons.h"
#include "qetpalette.h"
@@ -46,6 +47,7 @@
#include "diagram.h"
#include "foliogrid.h"
#include "diagramcontexttoolbar.h"
#include "imagedrop.h"
#include "diagramgestureoverlay.h"
#include "gesturesettings.h"
#include "shortcutbarsettings.h"
@@ -53,12 +55,14 @@
#include "ElementsCollection/xmlelementcollection.h"
#include "NameList/nameslist.h"
#include "elementdialog.h"
#include "genericdevice/genericdevicewizard.h"
#include "qetapp.h"
#include "qetgraphicsitem/element.h"
#include "qetinformation.h"
#include "qetversion.h"
#include <QApplication>
#include <QDropEvent>
#include <QMessageBox>
#include <QPainter>
#include <QPointer>
#include <QSet>
@@ -104,9 +108,9 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
setInteractive(true);
QString whatsthis = tr(
"Ceci est la zone dans laquelle vous concevez vos schémas en y ajoutant"
" des éléments et en posant des conducteurs entre leurs bornes. Il est"
" également possible d'ajouter des textes indépendants.",
"In this area you conceive your diagrams by adding elements and "
"conductors between their terminals. You may also add independent "
"texts.",
"\"What's this?\" tip"
);
setWhatsThis(whatsthis);
@@ -128,25 +132,25 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
m_diagram->loadElmtFolioSeq();
m_diagram->loadCndFolioSeq();
m_paste_here = new QAction(QET::Icons::EditPaste, tr("Coller ici", "context menu action"), this);
m_paste_here = new QAction(QET::Icons::EditPaste, tr("Paste Here", "context menu action"), this);
connect(m_paste_here, &QAction::triggered, this, &DiagramView::pasteHere);
m_multi_paste = new QAction(QET::Icons::EditPaste, tr("Collage multiple"), this);
m_multi_paste = new QAction(QET::Icons::EditPaste, tr("Multiple paste"), this);
connect(m_multi_paste, &QAction::triggered, [this]() {
MultiPasteDialog d(this->m_diagram, this);
d.exec();
});
// Setup the action to create a template
m_create_template = new QAction(tr("Créer un template", "context menu action"), this);
m_create_template = new QAction(tr("Create a 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);
m_generate_cabinet_thumbnail = new QAction(tr("Make a cabinet thumbnail", "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);
m_folio_report_menu = new QMenu(tr("Sheet reference"), this);
//setup three separators, to be use in context menu
for(int i=0 ; i<3 ; ++i)
@@ -202,7 +206,7 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
// prepare a color dialog showing the initial conductor color
QPointer<QColorDialog> color_dialog = new QColorDialog(this);
color_dialog->setWindowTitle(tr("Choisir la nouvelle couleur de ce conducteur"));
color_dialog->setWindowTitle(tr("Choose the new color for this conductor"));
#ifdef Q_OS_MACOS
color_dialog -> setWindowFlags(Qt::Sheet);
#endif
@@ -225,7 +229,7 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
new_value.setValue(initial_properties);
QPropertyUndoCommand *undo = new QPropertyUndoCommand(edited_conductor, "properties", old_value, new_value);
undo->setText(tr("Modifier les propriétés d'un conducteur", "undo caption"));
undo->setText(tr("Edit conductor properties", "undo caption"));
m_diagram->undoStack().push(undo);
// remember it for the next conductor drawn this session,
@@ -249,7 +253,14 @@ DiagramView::~DiagramView()
@param e le QDragEnterEvent correspondant au drag'n drop tente
*/
void DiagramView::dragEnterEvent(QDragEnterEvent *e) {
if (e -> mimeData() -> hasFormat("application/x-qet-element-uri")) {
// Picture files from the file manager are checked first: a file drag
// also carries its path as text, which would otherwise become a text.
// Any other file (a .qet project...) is left to the main window.
if (!ImageDrop::imageFiles(e -> mimeData()).isEmpty()) {
e -> acceptProposedAction();
} else if (ImageDrop::hasOnlyOtherUrls(e -> mimeData())) {
e -> ignore();
} else if (e -> mimeData() -> hasFormat("application/x-qet-element-uri")) {
e -> acceptProposedAction();
} else if (e -> mimeData() -> hasFormat("application/x-qet-titleblock-uri")) {
e -> acceptProposedAction();
@@ -265,7 +276,9 @@ void DiagramView::dragEnterEvent(QDragEnterEvent *e) {
@param e le QDragMoveEvent correspondant au drag'n drop tente
*/
void DiagramView::dragMoveEvent(QDragMoveEvent *e) {
if (e -> mimeData() -> hasFormat("text/plain")) e -> acceptProposedAction();
if (!ImageDrop::imageFiles(e -> mimeData()).isEmpty()) e -> acceptProposedAction();
else if (ImageDrop::hasOnlyOtherUrls(e -> mimeData())) e -> ignore();
else if (e -> mimeData() -> hasFormat("text/plain")) e -> acceptProposedAction();
else e-> ignore();
}
@@ -275,7 +288,11 @@ void DiagramView::dragMoveEvent(QDragMoveEvent *e) {
*/
void DiagramView::dropEvent(QDropEvent *e) {
if (e -> mimeData() -> hasFormat("application/x-qet-element-uri")) {
if (!ImageDrop::imageFiles(e -> mimeData()).isEmpty()) {
handleImageFilesDrop(e);
} else if (ImageDrop::hasOnlyOtherUrls(e -> mimeData())) {
e -> ignore();
} else if (e -> mimeData() -> hasFormat("application/x-qet-element-uri")) {
handleElementDrop(e);
} else if (e -> mimeData() -> hasFormat("application/x-qet-titleblock-uri")) {
handleTitleBlockDrop(e);
@@ -438,6 +455,87 @@ void DiagramView::handleTextDrop(QDropEvent *e) {
iti, m_diagram, mapToScene(e->position().toPoint())));
}
/**
@brief DiagramView::handleImageFilesDrop
Add the picture files dropped from the file manager. A single one is
centred on the drop point and kept inside the frame; a picture too
large for the folio is scaled down to leave a free margin of
ImageDrop::frameMargin to the frame on every side. Several pictures
are spread side by side in a grid over that same area
(ImageDrop::gridLayout). One undo step removes them all. Files that cannot be used are
listed once, after the others have been placed.
@param e the QDropEvent describing the current drag'n drop
*/
void DiagramView::handleImageFilesDrop(QDropEvent *e)
{
if (m_diagram -> isReadOnly()) return;
e -> acceptProposedAction();
const QStringList files = ImageDrop::imageFiles(e -> mimeData());
const QPointF drop_pos = mapToScene(e -> position().toPoint());
const QRectF frame = m_diagram -> border_and_titleblock.insideBorderRect();
QStringList refused;
QList<DiagramImageItem *> items;
for (const QString &file : files)
{
QString error;
const QImage image = ImageDrop::load(file, &error);
if (image.isNull())
refused << QStringLiteral("%1 : %2").arg(QFileInfo(file).fileName(), error);
else
items << new DiagramImageItem(QPixmap::fromImage(image));
}
// Where each picture goes, as it will look on the folio: one picture
// is centred on the drop point and fitted to the frame, several are
// spread over the frame in a grid.
QList<QRectF> targets;
if (items.size() == 1)
{
const QSizeF size = items.first() -> mapRectToScene(items.first() -> boundingRect()).size();
const qreal scale = ImageDrop::fitScale(size, frame);
QRectF r(QPointF(), size * scale);
r.moveCenter(drop_pos);
targets << ImageDrop::keepInside(r, scale < 1.0 ? ImageDrop::innerFrame(frame) : frame);
}
else
{
QList<QSizeF> sizes;
for (DiagramImageItem *item : items)
sizes << item -> mapRectToScene(item -> boundingRect()).size();
targets = ImageDrop::gridLayout(sizes, ImageDrop::innerFrame(frame));
}
auto *undo = new QUndoCommand();
const int placed = int(items.size());
for (int i = 0 ; i < placed ; ++i)
{
DiagramImageItem *item = items.at(i);
const QRectF natural = item -> mapRectToScene(item -> boundingRect());
const qreal scale = natural.width() > 0 ? targets.at(i).width() / natural.width() : 1.0;
if (scale < 1.0) {
item -> setScaleFactorX(scale);
item -> setScaleFactorY(scale);
}
const QPointF offset = item -> mapRectToScene(item -> boundingRect()).topLeft() - item -> pos();
new AddGraphicsObjectCommand(item, m_diagram, targets.at(i).topLeft() - offset, undo);
}
if (placed) {
undo -> setText(placed == 1 ? tr("Add an image")
: tr("Add %n image(s)", nullptr, placed));
m_diagram -> undoStack().push(undo);
} else {
delete undo;
}
if (!refused.isEmpty())
QMessageBox::warning(this, tr("Images not added"),
tr("These files could not be added:") + "\n\n"
+ refused.join("\n"));
}
/**
Set the Diagram in visualisation mode
*/
@@ -1078,7 +1176,7 @@ void DiagramView::mouseReleaseEvent(QMouseEvent *e)
{
//Popup a menu with an action to create conductors between
//all selected terminals.
QAction *act = new QAction(tr("Connecter les bornes sélectionnées"), this);
QAction *act = new QAction(tr("Connect the selected terminals"), this);
QPolygonF polygon_ = m_free_rubberband;
connect(act, &QAction::triggered, [this, polygon_]()
{
@@ -1301,6 +1399,17 @@ void DiagramView::keyPressEvent(QKeyEvent *e)
if (m_event_interface && m_event_interface->keyPressEvent(e))
return;
//An item in a state Escape must end -- a picture while one of its
//handles is dragged -- sees each key before the shortcuts below,
//which would otherwise clear the selection under it.
QGraphicsObject *keyboard_item = m_diagram ? m_diagram->keyboardItem() : nullptr;
if (keyboard_item && keyboard_item->scene() == m_diagram) {
e->ignore();
m_diagram->sendEvent(keyboard_item, e);
if (e->isAccepted())
return;
}
ProjectView *current_project = this->diagramEditor()->currentProjectView();
DiagramContent dc(m_diagram);
switch(e -> key())
@@ -1510,7 +1619,7 @@ QString DiagramView::title() const
QString view_title;
QString diagram_title(m_diagram -> title());
if (diagram_title.isEmpty()) {
view_title = tr("Sans titre", "what to display for untitled diagrams");
view_title = tr("Untitled", "what to display for untitled diagrams");
} else {
view_title = diagram_title;
}
@@ -2360,11 +2469,11 @@ void DiagramView::createTemplateFromSelection()
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));
QMessageBox::information(this, tr("Registered template"),
tr("The template has been successfully saved as :\n%1").arg(full_path));
} else {
qDebug() << "Error: Could not open file for writing:" << full_path;
QMessageBox::critical(this, tr("Erreur"), tr("Le fichier n'a pas pu être écrit."));
QMessageBox::critical(this, tr("Error"), tr("The file could not be written."));
}
}
@@ -2528,6 +2637,64 @@ void DiagramView::generateCabinetThumbnails()
}
}
/**
@brief DiagramView::addGenericDevice
Edit > Add > Generic device. Open the wizard; on Finish, write the box
it describes as an ordinary symbol into the folder "Generic devices" of
this project's embedded collection and put it on the cursor, ready to
be placed. The file name carries a short uuid, so two devices given the
same name never overwrite each other.
*/
void DiagramView::addGenericDevice()
{
QETProject *project = m_diagram ? m_diagram->project() : nullptr;
if (!project || project->isReadOnly() || m_diagram->isReadOnly()) {
return;
}
XmlElementCollection *collection = project->embeddedElementCollection();
if (!collection) {
return;
}
//Measured with the fonts the folio draws with, so names fit
const GenericDevice::Fonts fonts{QETApp::diagramTextsFont(7),
QETApp::dynamicTextsItemFont(9)};
GenericDeviceWizard wizard(fonts, this);
if (wizard.exec() != QDialog::Accepted) {
return;
}
const GenericDevice::Spec spec = wizard.spec();
const QString dir_name = QStringLiteral("Generic devices");
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 "en"
dir_names.addName(QStringLiteral("fr"), QStringLiteral("Appareils génériques"));
if (!collection->createDir(QStringLiteral("import"), dir_name, dir_names)) {
return;
}
}
const QString file_name = QET::stringToFileName(spec.name)
+ QLatin1Char('-')
+ QUuid::createUuid().toString(QUuid::Id128).left(8)
+ QStringLiteral(".elmt");
QDomDocument doc;
const QDomElement definition = GenericDevice::toDefinition(
spec, fonts, doc, QetVersion::currentVersion().toString());
if (!collection->addElementDefinition(dir_path, file_name, definition)) {
return;
}
project->setModified(true);
const ElementsLocation location(dir_path + QLatin1Char('/') + file_name, project);
startElementPlacement(location, defaultPlacementPos());
}
/**
@return l'editeur de schemas parent ou 0
*/