mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-30 06:34:13 +02:00
Merge remote-tracking branch 'upstream/master' into feature/162-js-scripting
# Conflicts: # sources/qetdiagrameditor.cpp # sources/qetdiagrameditor.h
This commit is contained in:
@@ -29,6 +29,9 @@ TerminalStripLayoutEditor::TerminalStripLayoutEditor(QSharedPointer<TerminalStri
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->m_graphics_view->setScene(new QGraphicsScene{ this });
|
||||
// The preview is printed output: black ink on a white page, whatever
|
||||
// the application palette.
|
||||
ui->m_graphics_view->setBackgroundBrush(Qt::white);
|
||||
ui->m_graphics_view->scene()->addItem(&m_preview_strip_item);
|
||||
updateUi();
|
||||
}
|
||||
|
||||
@@ -658,21 +658,14 @@ void ElementsPanelWidget::duplicateDiagram()
|
||||
BorderProperties bp = source_diagram->border_and_titleblock.exportBorder();
|
||||
new_diagram->border_and_titleblock.importBorder(bp);
|
||||
|
||||
for (QGraphicsItem *item : source_diagram->items()) {
|
||||
if (Element *elmt = dynamic_cast<Element *>(item)) {
|
||||
source_diagram->correctTextPos(elmt);
|
||||
}
|
||||
}
|
||||
|
||||
QDomDocument doc = source_diagram->toXml();
|
||||
// Serialize the whole diagram with is_copy_command=true.
|
||||
// This is the same mechanism as Ctrl+C: toXml(true, true)
|
||||
// internally calls correctTextPos/restoreText for Slave and
|
||||
// Report elements only — producing correct text positions
|
||||
// in the XML. No manual correctTextPos/restoreText needed.
|
||||
QDomDocument doc = source_diagram->toXml(true, true);
|
||||
QDomElement diagram_elmt = doc.documentElement();
|
||||
|
||||
for (QGraphicsItem *item : source_diagram->items()) {
|
||||
if (Element *elmt = dynamic_cast<Element *>(item)) {
|
||||
source_diagram->restoreText(elmt);
|
||||
}
|
||||
}
|
||||
|
||||
new_diagram->fromXml(diagram_elmt, QPointF(0, 0), false, nullptr);
|
||||
|
||||
for (QGraphicsItem *item : new_diagram->items()) {
|
||||
@@ -683,7 +676,18 @@ void ElementsPanelWidget::duplicateDiagram()
|
||||
// of the project database, so duplicates fail to insert and
|
||||
// silently vanish from nomenclature/summary tables.
|
||||
elmt->newUuid();
|
||||
new_diagram->restoreText(elmt);
|
||||
|
||||
// toXml(true, true) applied correctTextPos to Slave and
|
||||
// Report elements, which shifted their text positions to
|
||||
// match the stripped composite text. restoreText()
|
||||
// recalculates the position for the actual resolved text.
|
||||
// Only Slave and Report need this — other element types
|
||||
// were not affected by correctTextPos.
|
||||
if (elmt->linkType() == Element::Slave ||
|
||||
elmt->linkType() & Element::AllReport)
|
||||
{
|
||||
new_diagram->restoreText(elmt);
|
||||
}
|
||||
}
|
||||
else if (Conductor *cond = dynamic_cast<Conductor *>(item)) {
|
||||
// Same reasoning for conductors: conductor.uuid is the PRIMARY
|
||||
|
||||
@@ -94,6 +94,29 @@ void ElementPictureFactory::getPictures(const ElementsLocation &location, QPictu
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ElementPictureFactory::dropCache
|
||||
Forget the cached drawing of the element at @p location, so the next
|
||||
getPictures()/pixmap()/getPrimitives() call rebuilds it from the
|
||||
definition's current content instead of returning what was cached the
|
||||
first time this location was drawn.
|
||||
|
||||
A placed Element keeps its own copy of the picture in m_picture /
|
||||
m_low_zoom_picture (set once, in buildFromXml()), so dropping the shared
|
||||
cache here does not by itself change what is on screen -- callers doing
|
||||
a manual refresh (bugtracker #802) still need each Element to re-fetch
|
||||
its picture afterwards.
|
||||
@param location
|
||||
*/
|
||||
void ElementPictureFactory::dropCache(const ElementsLocation &location)
|
||||
{
|
||||
const QUuid uuid = cacheKey(location);
|
||||
m_pictures_H.remove(uuid);
|
||||
m_low_pictures_H.remove(uuid);
|
||||
m_pixmap_H.remove(uuid);
|
||||
m_primitives_H.remove(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ElementPictureFactory::pixmap
|
||||
@param location
|
||||
|
||||
@@ -85,13 +85,14 @@ class ElementPictureFactory
|
||||
void getPictures(const ElementsLocation &location, QPicture &picture, QPicture &low_picture);
|
||||
QPixmap pixmap(const ElementsLocation &location);
|
||||
ElementPictureFactory::primitives getPrimitives(const ElementsLocation &location);
|
||||
|
||||
void dropCache(const ElementsLocation &location);
|
||||
|
||||
private:
|
||||
ElementPictureFactory() {}
|
||||
ElementPictureFactory (const ElementPictureFactory &);
|
||||
ElementPictureFactory operator= (const ElementPictureFactory &);
|
||||
~ElementPictureFactory();
|
||||
|
||||
|
||||
static QUuid cacheKey(const ElementsLocation &location);
|
||||
bool build(const ElementsLocation &location, QPicture *picture=nullptr, QPicture *low_picture=nullptr);
|
||||
void parseElement(const QDomElement &dom, QPainter &painter, primitives &prim) const;
|
||||
|
||||
+40
-1
@@ -26,6 +26,7 @@
|
||||
#include "projectview.h"
|
||||
#include "qetdiagrameditor.h"
|
||||
#include "qeticons.h"
|
||||
#include "qetpalette.h"
|
||||
#include "utils/qetutils.h"
|
||||
#include "qetmessagebox.h"
|
||||
#include "qetproject.h"
|
||||
@@ -50,6 +51,7 @@
|
||||
#include <QFontDatabase>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QRegularExpression>
|
||||
#include <QStyleHints>
|
||||
#ifdef BUILD_WITHOUT_KF
|
||||
# include "ui/nokde/kautosavefile.h"
|
||||
#else
|
||||
@@ -1786,8 +1788,10 @@ void QETApp::invertMainWindowVisibility(QWidget *window) {
|
||||
false pour utiliser celles du theme en cours
|
||||
*/
|
||||
void QETApp::useSystemPalette(bool use) {
|
||||
// The base palette is always initial_palette_ (see initStyle()); the
|
||||
// setting only decides whether the user's style.css is layered on top.
|
||||
qApp->setPalette(initial_palette_);
|
||||
if (use) {
|
||||
qApp->setPalette(initial_palette_);
|
||||
// Drop any stylesheet previously loaded from style.css: with system
|
||||
// colors requested, the palette set just above is what provides them.
|
||||
//
|
||||
@@ -2341,9 +2345,44 @@ void QETApp::initStyle()
|
||||
{
|
||||
initial_palette_ = qApp->palette();
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
// main.cpp forces the Fusion style on macOS, but the palette Qt hands
|
||||
// us there is the one its platform theme builds for the native style:
|
||||
// Window, Button and Base share one color, and in dark mode the
|
||||
// Inactive ButtonText is black. Fusion draws its frames, gradients and
|
||||
// combo box text from those roles, so controls lose their edges and
|
||||
// combo text goes black once the window loses focus. Replace it with a
|
||||
// palette laid out the way Fusion expects (see qetpalette.h).
|
||||
//
|
||||
// macOS only: on Linux, Fusion is Qt's default style on desktops
|
||||
// without a platform theme, and the palette there carries the user's
|
||||
// desktop colors, which must stay in effect. Making Fusion and this
|
||||
// palette the default everywhere is discussed in #870.
|
||||
if (QET::Palette::styleIsFusion(qApp->style()))
|
||||
initial_palette_ = QET::Palette::forFusion(initial_palette_);
|
||||
#endif
|
||||
|
||||
//Apply or not the system style
|
||||
QSettings settings;
|
||||
useSystemPalette(settings.value("usesystemcolors", true).toBool());
|
||||
|
||||
#if defined(Q_OS_MACOS) && QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
// Setting an application palette stops Qt from following the OS
|
||||
// light/dark switch on its own, so follow it here. The platform accent
|
||||
// color is not reachable any more at this point; the palette's own
|
||||
// selection blue is used instead.
|
||||
connect(qApp->styleHints(), &QStyleHints::colorSchemeChanged, this,
|
||||
[this](Qt::ColorScheme scheme)
|
||||
{
|
||||
if (!QET::Palette::styleIsFusion(qApp->style()))
|
||||
return;
|
||||
initial_palette_ = scheme == Qt::ColorScheme::Dark
|
||||
? QET::Palette::fusionDark()
|
||||
: QET::Palette::fusionLight();
|
||||
QSettings settings;
|
||||
useSystemPalette(settings.value("usesystemcolors", true).toBool());
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "autoNum/ui/autonumberingdockwidget.h"
|
||||
#include "conductornumexport.h"
|
||||
#include "diagramcommands.h"
|
||||
#include "diagramcontent.h"
|
||||
#include "diagramevent/diagrameventaddimage.h"
|
||||
#ifdef QET_HAS_QTPDF
|
||||
#include "diagramevent/diagrameventaddpdf.h"
|
||||
@@ -35,6 +36,7 @@
|
||||
#include "diagramevent/diagrameventaddpaste.h"
|
||||
#include "diagramview.h"
|
||||
#include "elementspanelwidget.h"
|
||||
#include "factory/elementpicturefactory.h"
|
||||
#include "factory/qetgraphicstablefactory.h"
|
||||
#include "print/projectprintwindow.h"
|
||||
#include "project/projectpropertieshandler.h"
|
||||
@@ -552,6 +554,13 @@ void QETDiagramEditor::setUpActions()
|
||||
m_terminal_numbering = new QAction(QET::Icons::TerminalStrip, tr("Numérotation automatique des bornes"), this);
|
||||
connect(m_terminal_numbering, &QAction::triggered, this, &QETDiagramEditor::slot_terminalNumbering);
|
||||
|
||||
// Reload element drawings from their current definition (bugtracker #802)
|
||||
m_reload_element_drawings = new QAction(QET::Icons::ViewRefresh, tr("Recharger les dessins des éléments"), this);
|
||||
m_reload_element_drawings->setStatusTip(
|
||||
tr("Redessine chaque élément placé d'après sa définition actuelle,"
|
||||
" sans avoir à fermer et rouvrir le projet (action non annulable)"));
|
||||
connect(m_reload_element_drawings, &QAction::triggered, this, &QETDiagramEditor::slot_reloadElementDrawings);
|
||||
|
||||
#ifdef QET_HAS_SCRIPTING
|
||||
// Run a JavaScript macro against the current project (bugtracker #162).
|
||||
m_run_script = new QAction(tr("Exécuter un script..."), this);
|
||||
@@ -1014,6 +1023,7 @@ void QETDiagramEditor::setUpMenu()
|
||||
menu_project -> addAction(m_project_export_wiring_list);
|
||||
menu_project -> addAction(m_project_wiring_list_view);
|
||||
menu_project -> addAction(m_terminal_numbering);
|
||||
menu_project -> addAction(m_reload_element_drawings);
|
||||
#ifdef QET_HAS_SCRIPTING
|
||||
menu_project -> addAction(m_run_script);
|
||||
#endif
|
||||
@@ -1871,6 +1881,7 @@ void QETDiagramEditor::slot_updateActions()
|
||||
m_project_export_wiring_list -> setEnabled(opened_project);
|
||||
m_project_wiring_list_view -> setEnabled(opened_project);
|
||||
m_terminal_numbering -> setEnabled(editable_project);
|
||||
m_reload_element_drawings -> setEnabled(opened_project);
|
||||
#ifdef QET_HAS_SCRIPTING
|
||||
m_run_script -> setEnabled(opened_project);
|
||||
#endif
|
||||
@@ -2989,6 +3000,100 @@ void QETDiagramEditor::slot_terminalNumbering() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETDiagramEditor::slot_reloadElementDrawings
|
||||
Redraw every placed element of the current project from its current
|
||||
definition (bugtracker #802): a symbol edited and saved after being
|
||||
placed keeps showing its old drawing otherwise, until the whole project
|
||||
is closed and reopened.
|
||||
|
||||
Purely visual and not undoable, the way pressing a "refresh" button
|
||||
would be: nothing is pushed on the undo stack and the project is not
|
||||
marked as modified. Elements whose size, hotspot or terminals changed
|
||||
are skipped and listed: they must be removed and re-inserted, which
|
||||
deletes the conductors already connected to them.
|
||||
*/
|
||||
void QETDiagramEditor::slot_reloadElementDrawings() {
|
||||
QETProject *project = currentProject();
|
||||
if (!project) return;
|
||||
|
||||
QList<Element *> elements;
|
||||
QSet<QString> dropped_locations;
|
||||
for (Diagram *diagram : project->diagrams())
|
||||
{
|
||||
DiagramContent content(diagram, false);
|
||||
for (Element *elmt : content.m_elements)
|
||||
{
|
||||
elements << elmt;
|
||||
const QString key = elmt->location().toString();
|
||||
if (!dropped_locations.contains(key))
|
||||
{
|
||||
ElementPictureFactory::instance()->dropCache(elmt->location());
|
||||
dropped_locations.insert(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int reloaded = 0;
|
||||
int unavailable = 0;
|
||||
QStringList geometry_changed;
|
||||
for (Element *elmt : elements)
|
||||
{
|
||||
switch (elmt->reloadPicture())
|
||||
{
|
||||
case Element::ReloadPictureResult::Reloaded:
|
||||
++reloaded;
|
||||
break;
|
||||
case Element::ReloadPictureResult::Unavailable:
|
||||
++unavailable;
|
||||
break;
|
||||
case Element::ReloadPictureResult::GeometryChanged:
|
||||
{
|
||||
const Diagram *diagram = elmt->diagram();
|
||||
const QString folio = diagram
|
||||
? tr("folio %1").arg(project->folioIndex(diagram) + 1)
|
||||
: QString();
|
||||
geometry_changed << QStringLiteral("%1 (%2)").arg(elmt->name(), folio);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString message = tr("%n élément(s) redessiné(s).", "", reloaded);
|
||||
|
||||
if (unavailable) {
|
||||
message += QStringLiteral("\n\n")
|
||||
% tr("%n élément(s) dont la définition est introuvable ou illisible :"
|
||||
" leur dessin actuel a été conservé.", "", unavailable);
|
||||
}
|
||||
|
||||
if (geometry_changed.isEmpty())
|
||||
{
|
||||
QET::QetMessageBox::information(
|
||||
this, tr("Recharger les dessins des éléments"), message);
|
||||
return;
|
||||
}
|
||||
|
||||
message += QStringLiteral("\n\n")
|
||||
% tr("%n élément(s) non redessiné(s) : leur taille, leur point de saisie"
|
||||
" ou leurs bornes ont changé (borne ajoutée, supprimée ou déplacée).",
|
||||
"", geometry_changed.size())
|
||||
% QStringLiteral("\n\n")
|
||||
% tr("Pour les mettre à jour, il faut les supprimer puis les réinsérer."
|
||||
" Attention : cette opération supprime les conducteurs déjà reliés"
|
||||
" à ces éléments, qu'il faudra retracer.");
|
||||
|
||||
//The full list goes in the expandable, scrollable details area
|
||||
//so the dialog stays readable on large projects.
|
||||
QMessageBox box(QMessageBox::Warning,
|
||||
tr("Recharger les dessins des éléments"),
|
||||
message,
|
||||
QMessageBox::Ok,
|
||||
this);
|
||||
box.setDetailedText(geometry_changed.join(QLatin1Char('\n')));
|
||||
box.exec();
|
||||
}
|
||||
|
||||
#ifdef QET_HAS_SCRIPTING
|
||||
/**
|
||||
@brief QETDiagramEditor::slot_runScript
|
||||
|
||||
@@ -136,6 +136,7 @@ class QETDiagramEditor : public QETMainWindow
|
||||
void editProjectProperties(ProjectView *);
|
||||
void editProjectProperties(QETProject *);
|
||||
void slot_terminalNumbering();
|
||||
void slot_reloadElementDrawings();
|
||||
#ifdef QET_HAS_SCRIPTING
|
||||
void slot_runScript();
|
||||
#endif
|
||||
@@ -214,6 +215,7 @@ class QETDiagramEditor : public QETMainWindow
|
||||
*m_project_export_wiring_list, ///< Action to export the wiring list
|
||||
*m_project_wiring_list_view, ///< Action to show the wiring list read from the project database
|
||||
*m_terminal_numbering, ///< Action to launch terminal numbering
|
||||
*m_reload_element_drawings, ///< Action to redraw every placed element from its current definition
|
||||
#ifdef QET_HAS_SCRIPTING
|
||||
*m_run_script, ///< Action to run a JavaScript macro against the current project
|
||||
#endif
|
||||
|
||||
@@ -1784,6 +1784,139 @@ ElementsLocation Element::location() const
|
||||
return m_location;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::reloadPicture
|
||||
Re-fetch this element's drawing from its location and repaint.
|
||||
|
||||
A placed element is drawn once from its definition, at construction
|
||||
(buildFromXml()), and nothing afterwards ever makes it look again --
|
||||
editing and saving the definition leaves every already-placed instance
|
||||
showing the old drawing until the project is closed and reopened
|
||||
(bugtracker #802). This is the per-instance half of the fix.
|
||||
|
||||
Deliberately limited to the drawing. Terminals are what conductors are
|
||||
attached to: if the new definition adds, removes or moves a terminal,
|
||||
or changes the element size or hotspot, the new drawing would no longer
|
||||
match the live terminals and bounding rect. Such an element is left
|
||||
untouched and GeometryChanged is returned; it has to be removed and
|
||||
re-inserted, which deletes the conductors already connected to it.
|
||||
|
||||
If the definition cannot be found or read, the current drawing is kept
|
||||
and Unavailable is returned, so the element never goes blank.
|
||||
|
||||
Purely visual: nothing is pushed on the undo stack and the project is
|
||||
not marked as modified.
|
||||
@return what happened to this element
|
||||
*/
|
||||
Element::ReloadPictureResult Element::reloadPicture()
|
||||
{
|
||||
if (!m_location.exist()) {
|
||||
return ReloadPictureResult::Unavailable;
|
||||
}
|
||||
|
||||
const QDomElement definition = m_location.xml();
|
||||
if (definition.isNull()) {
|
||||
return ReloadPictureResult::Unavailable;
|
||||
}
|
||||
|
||||
if (!definitionGeometryMatches(definition)) {
|
||||
return ReloadPictureResult::GeometryChanged;
|
||||
}
|
||||
|
||||
QPicture picture;
|
||||
QPicture low_zoom_picture;
|
||||
ElementPictureFactory::instance()->getPictures(m_location,
|
||||
picture,
|
||||
low_zoom_picture);
|
||||
if (picture.isNull()) {
|
||||
return ReloadPictureResult::Unavailable;
|
||||
}
|
||||
|
||||
m_picture = picture;
|
||||
m_low_zoom_picture = low_zoom_picture;
|
||||
update();
|
||||
return ReloadPictureResult::Reloaded;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::definitionGeometryMatches
|
||||
Compare the geometry described by @p definition with this live element:
|
||||
size and hotspot (normalized the same way setSize()/setHotspot() do it)
|
||||
and the set of terminal positions (same parsing rules as
|
||||
TerminalData::fromXml()).
|
||||
@param definition : the <definition> root of the element
|
||||
@return true if the new drawing can be applied without desynchronizing
|
||||
the bounding rect or the terminals
|
||||
*/
|
||||
bool Element::definitionGeometryMatches(const QDomElement &definition) const
|
||||
{
|
||||
int w = 0, h = 0, hot_x = 0, hot_y = 0;
|
||||
if (!QET::attributeIsAnInteger(definition, QStringLiteral("width"), &w) ||
|
||||
!QET::attributeIsAnInteger(definition, QStringLiteral("height"), &h) ||
|
||||
!QET::attributeIsAnInteger(definition, QStringLiteral("hotspot_x"), &hot_x) ||
|
||||
!QET::attributeIsAnInteger(definition, QStringLiteral("hotspot_y"), &hot_y)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Same rounding as setSize()
|
||||
while (w % 10) ++w;
|
||||
while (h % 10) ++h;
|
||||
if (QSize(w, h) != dimensions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Same clamping as setHotspot()
|
||||
const QPoint new_hotspot = dimensions.isNull()
|
||||
? QPoint(0, 0)
|
||||
: QPoint(qMin(hot_x, w), qMin(hot_y, h));
|
||||
if (new_hotspot != hotspot_coord) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Terminal positions described by the new definition
|
||||
QList<QPointF> new_terminals;
|
||||
for (QDomElement description = definition.firstChildElement(QStringLiteral("description")) ;
|
||||
!description.isNull() ;
|
||||
description = description.nextSiblingElement(QStringLiteral("description")))
|
||||
{
|
||||
for (QDomElement terminal = description.firstChildElement(QStringLiteral("terminal")) ;
|
||||
!terminal.isNull() ;
|
||||
terminal = terminal.nextSiblingElement(QStringLiteral("terminal")))
|
||||
{
|
||||
qreal x = 0.0, y = 0.0;
|
||||
if (QET::attributeIsAReal(terminal, QStringLiteral("x"), &x) &&
|
||||
QET::attributeIsAReal(terminal, QStringLiteral("y"), &y)) {
|
||||
new_terminals << QPointF(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (new_terminals.size() != m_terminals.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Every live terminal must still exist at the same place
|
||||
for (const Terminal *terminal : m_terminals)
|
||||
{
|
||||
const QPointF live_pos = mapFromScene(terminal->dockConductor());
|
||||
bool found = false;
|
||||
for (int i = 0 ; i < new_terminals.size() ; ++i)
|
||||
{
|
||||
const QPointF delta = new_terminals.at(i) - live_pos;
|
||||
if (qAbs(delta.x()) < 0.01 && qAbs(delta.y()) < 0.01) {
|
||||
new_terminals.removeAt(i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Element::updateConductorTexts
|
||||
*Slot that is triggered when a cable is *
|
||||
|
||||
@@ -83,6 +83,7 @@ class Element : public QetGraphicsItem
|
||||
Element::kind link_type = Element::Simple);
|
||||
~Element() override;
|
||||
private:
|
||||
bool definitionGeometryMatches(const QDomElement &definition) const;
|
||||
Element(const Element &);
|
||||
|
||||
// attributes
|
||||
@@ -153,6 +154,13 @@ class Element : public QetGraphicsItem
|
||||
|
||||
QString name() const override;
|
||||
ElementsLocation location() const;
|
||||
/// Result of Element::reloadPicture()
|
||||
enum class ReloadPictureResult {
|
||||
Reloaded, ///< drawing replaced by the current definition
|
||||
Unavailable, ///< definition missing or unreadable, old drawing kept
|
||||
GeometryChanged ///< size, hotspot or terminals changed, old drawing kept
|
||||
};
|
||||
ReloadPictureResult reloadPicture();
|
||||
virtual void setHighlighted(bool);
|
||||
void displayHelpLine(bool b = true);
|
||||
QSize size() const;
|
||||
|
||||
@@ -86,7 +86,12 @@ Terminal::Terminal(TerminalData* data, Element* e) :
|
||||
* Destruction of the terminal, and also docked conductor
|
||||
*/
|
||||
Terminal::~Terminal() {
|
||||
qDeleteAll(m_conductors_list);
|
||||
// Each conductor's destructor calls removeConductor() on both its
|
||||
// terminals, which mutates m_conductors_list while qDeleteAll() is
|
||||
// still iterating it. Delete from a snapshot so the live list can
|
||||
// change underneath without affecting the iteration.
|
||||
const QList<Conductor *> conductors_to_delete = m_conductors_list;
|
||||
qDeleteAll(conductors_to_delete);
|
||||
delete d;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
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 "qetpalette.h"
|
||||
|
||||
#include <QStyle>
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
/**
|
||||
Set one role for the Active and Inactive groups (same color: Fusion
|
||||
does not draw an inactive window differently, and a differing
|
||||
Inactive color is what turned combo box text black on macOS) and
|
||||
for the Disabled group.
|
||||
*/
|
||||
void setRole(QPalette &p, QPalette::ColorRole role,
|
||||
const QColor &active, const QColor &disabled)
|
||||
{
|
||||
p.setColor(QPalette::Active, role, active);
|
||||
p.setColor(QPalette::Inactive, role, active);
|
||||
p.setColor(QPalette::Disabled, role, disabled);
|
||||
}
|
||||
|
||||
/// Same color in all three groups.
|
||||
void setRole(QPalette &p, QPalette::ColorRole role, const QColor &c)
|
||||
{
|
||||
setRole(p, role, c, c);
|
||||
}
|
||||
|
||||
/// sRGB channel to linear light, as defined by WCAG 2.
|
||||
double linear(int channel)
|
||||
{
|
||||
const double c = channel / 255.0;
|
||||
return c <= 0.03928 ? c / 12.92 : std::pow((c + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
double relativeLuminance(const QColor &c)
|
||||
{
|
||||
return 0.2126 * linear(c.red())
|
||||
+ 0.7152 * linear(c.green())
|
||||
+ 0.0722 * linear(c.blue());
|
||||
}
|
||||
|
||||
/**
|
||||
The selection color shared by both palettes: a blue that reads at
|
||||
better than 6:1 under white text. Fusion's own default (48,140,198)
|
||||
only reaches 3.7:1.
|
||||
*/
|
||||
const QColor kHighlight(30, 96, 176);
|
||||
}
|
||||
|
||||
bool QET::Palette::styleIsFusion(const QStyle *style)
|
||||
{
|
||||
return style && style->objectName().compare(QLatin1String("fusion"),
|
||||
Qt::CaseInsensitive) == 0;
|
||||
}
|
||||
|
||||
bool QET::Palette::isDark(const QPalette &palette)
|
||||
{
|
||||
return palette.color(QPalette::Active, QPalette::Window).lightness() < 128;
|
||||
}
|
||||
|
||||
double QET::Palette::contrastRatio(const QColor &a, const QColor &b)
|
||||
{
|
||||
double lighter = relativeLuminance(a);
|
||||
double darker = relativeLuminance(b);
|
||||
if (lighter < darker)
|
||||
std::swap(lighter, darker);
|
||||
return (lighter + 0.05) / (darker + 0.05);
|
||||
}
|
||||
|
||||
QPalette QET::Palette::fusionLight()
|
||||
{
|
||||
QPalette p;
|
||||
const QColor window(239, 239, 239);
|
||||
const QColor text(Qt::black);
|
||||
const QColor disabled_text(128, 128, 128);
|
||||
|
||||
setRole(p, QPalette::Window, window);
|
||||
setRole(p, QPalette::WindowText, text, disabled_text);
|
||||
setRole(p, QPalette::Base, Qt::white, window);
|
||||
setRole(p, QPalette::AlternateBase, QColor(247, 247, 247));
|
||||
setRole(p, QPalette::Text, text, disabled_text);
|
||||
setRole(p, QPalette::Button, window);
|
||||
setRole(p, QPalette::ButtonText, text, disabled_text);
|
||||
setRole(p, QPalette::BrightText, Qt::white);
|
||||
setRole(p, QPalette::Highlight, kHighlight, QColor(145, 145, 145));
|
||||
setRole(p, QPalette::HighlightedText, Qt::white);
|
||||
setRole(p, QPalette::ToolTipBase, QColor(255, 255, 220));
|
||||
setRole(p, QPalette::ToolTipText, text);
|
||||
setRole(p, QPalette::Link, QColor(0, 90, 170));
|
||||
setRole(p, QPalette::LinkVisited, QColor(110, 40, 140));
|
||||
setRole(p, QPalette::PlaceholderText, QColor(118, 118, 118));
|
||||
// The 3D roles Fusion shades frames and gradients with.
|
||||
setRole(p, QPalette::Light, Qt::white);
|
||||
setRole(p, QPalette::Midlight, QColor(202, 202, 202));
|
||||
setRole(p, QPalette::Mid, QColor(184, 184, 184));
|
||||
setRole(p, QPalette::Dark, QColor(159, 159, 159));
|
||||
setRole(p, QPalette::Shadow, QColor(118, 118, 118));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||
setRole(p, QPalette::Accent, kHighlight);
|
||||
#endif
|
||||
return p;
|
||||
}
|
||||
|
||||
QPalette QET::Palette::fusionDark()
|
||||
{
|
||||
QPalette p;
|
||||
const QColor window(53, 53, 53);
|
||||
const QColor text(220, 220, 220);
|
||||
// Fusion lifts a button's fill to about (89,89,89) at the top of its
|
||||
// gradient; 175 is the darkest grey that still reads 3:1 on that.
|
||||
const QColor disabled_text(175, 175, 175);
|
||||
|
||||
setRole(p, QPalette::Window, window);
|
||||
setRole(p, QPalette::WindowText, text, disabled_text);
|
||||
setRole(p, QPalette::Base, QColor(30, 30, 30), QColor(45, 45, 45));
|
||||
setRole(p, QPalette::AlternateBase, QColor(45, 45, 45));
|
||||
setRole(p, QPalette::Text, text, disabled_text);
|
||||
setRole(p, QPalette::Button, QColor(66, 66, 66));
|
||||
setRole(p, QPalette::ButtonText, text, disabled_text);
|
||||
setRole(p, QPalette::BrightText, QColor(255, 90, 90));
|
||||
setRole(p, QPalette::Highlight, kHighlight, QColor(80, 80, 80));
|
||||
setRole(p, QPalette::HighlightedText, Qt::white);
|
||||
setRole(p, QPalette::ToolTipBase, QColor(66, 66, 66));
|
||||
setRole(p, QPalette::ToolTipText, text);
|
||||
setRole(p, QPalette::Link, QColor(100, 170, 255));
|
||||
setRole(p, QPalette::LinkVisited, QColor(190, 130, 255));
|
||||
setRole(p, QPalette::PlaceholderText, QColor(150, 150, 150));
|
||||
setRole(p, QPalette::Light, QColor(90, 90, 90));
|
||||
setRole(p, QPalette::Midlight, QColor(75, 75, 75));
|
||||
setRole(p, QPalette::Mid, QColor(35, 35, 35));
|
||||
setRole(p, QPalette::Dark, QColor(20, 20, 20));
|
||||
setRole(p, QPalette::Shadow, QColor(10, 10, 10));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||
setRole(p, QPalette::Accent, kHighlight);
|
||||
#endif
|
||||
return p;
|
||||
}
|
||||
|
||||
QPalette QET::Palette::withPlatformAccent(QPalette palette, const QPalette &platform)
|
||||
{
|
||||
const QColor highlight = platform.color(QPalette::Active, QPalette::Highlight);
|
||||
const QColor highlighted_text = platform.color(QPalette::Active, QPalette::HighlightedText);
|
||||
if (!highlight.isValid() || !highlighted_text.isValid())
|
||||
return palette;
|
||||
if (contrastRatio(highlight, highlighted_text) < 4.5)
|
||||
return palette;
|
||||
|
||||
palette.setColor(QPalette::Active, QPalette::Highlight, highlight);
|
||||
palette.setColor(QPalette::Inactive, QPalette::Highlight, highlight);
|
||||
palette.setColor(QPalette::Active, QPalette::HighlightedText, highlighted_text);
|
||||
palette.setColor(QPalette::Inactive, QPalette::HighlightedText, highlighted_text);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||
palette.setColor(QPalette::Active, QPalette::Accent, highlight);
|
||||
palette.setColor(QPalette::Inactive, QPalette::Accent, highlight);
|
||||
#endif
|
||||
return palette;
|
||||
}
|
||||
|
||||
QPalette QET::Palette::forFusion(const QPalette &platform)
|
||||
{
|
||||
return withPlatformAccent(isDark(platform) ? fusionDark() : fusionLight(),
|
||||
platform);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
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 QET_PALETTE_H
|
||||
#define QET_PALETTE_H
|
||||
|
||||
#include <QPalette>
|
||||
|
||||
class QStyle;
|
||||
|
||||
/**
|
||||
Palettes tuned for the Fusion style.
|
||||
|
||||
main.cpp forces the Fusion style on macOS, but the palette still comes
|
||||
from Qt's macOS platform theme, which is built for the native style.
|
||||
That palette gives Window, Button and Base the same color, makes Dark
|
||||
lighter than Light, and in dark mode sets the Inactive ButtonText to
|
||||
black. Fusion derives its outlines, gradients and indicator colors from
|
||||
those roles, so under that palette controls lose their edges, radio
|
||||
indicators vanish, and combo box text turns black as soon as the window
|
||||
loses focus.
|
||||
|
||||
This namespace provides a light and a dark palette with the role
|
||||
separation Fusion expects, and the WCAG 2 contrast helper the tests use
|
||||
to keep them readable.
|
||||
*/
|
||||
namespace QET {
|
||||
namespace Palette {
|
||||
/**
|
||||
@return true when the style draws with Fusion's rules,
|
||||
i.e. its object name is "fusion".
|
||||
*/
|
||||
bool styleIsFusion(const QStyle *style);
|
||||
|
||||
/**
|
||||
@return true when the palette's Window color is dark
|
||||
(lightness below 128).
|
||||
*/
|
||||
bool isDark(const QPalette &palette);
|
||||
|
||||
/**
|
||||
WCAG 2 contrast ratio between two opaque colors, from 1 (equal)
|
||||
to 21 (black on white). Normal text needs at least 4.5, large
|
||||
text and non-text controls at least 3.
|
||||
*/
|
||||
double contrastRatio(const QColor &a, const QColor &b);
|
||||
|
||||
/**
|
||||
A light palette with the role separation Fusion expects: Window
|
||||
and Button a light grey, Base white, Text black, and Inactive
|
||||
equal to Active so nothing changes when the window loses focus.
|
||||
*/
|
||||
QPalette fusionLight();
|
||||
|
||||
/**
|
||||
A dark palette with the same role separation: Window a mid
|
||||
grey, Button lighter than Window, Base darker than Window, Text
|
||||
light, and Inactive equal to Active.
|
||||
*/
|
||||
QPalette fusionDark();
|
||||
|
||||
/**
|
||||
Copy the Highlight and HighlightedText colors of the platform
|
||||
palette into ours, so the user's accent color survives, but only
|
||||
when the pair reads at 4.5:1 or better. Otherwise ours is kept.
|
||||
*/
|
||||
QPalette withPlatformAccent(QPalette palette, const QPalette &platform);
|
||||
|
||||
/**
|
||||
The palette QET should use under Fusion, given the palette the
|
||||
platform handed to the application: fusionDark() when the
|
||||
platform palette is dark, fusionLight() otherwise, with the
|
||||
platform's accent color kept when it is readable.
|
||||
*/
|
||||
QPalette forFusion(const QPalette &platform);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -251,8 +251,10 @@ QList<QStandardItem *> DynamicElementTextModel::itemsForText(
|
||||
QStandardItem *color = new QStandardItem(tr("Couleur"));
|
||||
color->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
||||
|
||||
// Shown as a swatch next to the value, not as the text color: black
|
||||
// text (the default) is unreadable on a dark palette.
|
||||
QStandardItem *colora = new QStandardItem;
|
||||
colora->setData(deti->color(), Qt::ForegroundRole);
|
||||
colora->setData(deti->color(), Qt::DecorationRole);
|
||||
colora->setData(deti->color(), Qt::EditRole);
|
||||
colora->setData(DynamicElementTextModel::color, Qt::UserRole+1);
|
||||
colora->setFlags(Qt::ItemIsSelectable
|
||||
@@ -1500,7 +1502,7 @@ void DynamicElementTextModel::updateDataFromText(DynamicElementTextItem *deti,
|
||||
case color:
|
||||
{
|
||||
qsi->child(color_txt_row,1)->setData(deti->color(), Qt::EditRole);
|
||||
qsi->child(color_txt_row,1)->setData(deti->color(), Qt::ForegroundRole);
|
||||
qsi->child(color_txt_row,1)->setData(deti->color(), Qt::DecorationRole);
|
||||
break;
|
||||
}
|
||||
case pos:
|
||||
@@ -1832,7 +1834,7 @@ void DynamicTextItemDelegate::setModelData(
|
||||
{
|
||||
QColor color = editor->property("color").value<QColor>();
|
||||
qsi->setData(color, Qt::EditRole);
|
||||
qsi->setData(color, Qt::ForegroundRole);
|
||||
qsi->setData(color, Qt::DecorationRole);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user