mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-14 17:44:13 +02:00
Merge remote-tracking branch 'upstream/master' into feature/customise-toolbar-options
# Conflicts: # tests/qttest/CMakeLists.txt
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
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 "textresizehandles.h"
|
||||
|
||||
#include "qetgraphicshandleritem.h"
|
||||
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QCursor>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QGraphicsTextItem>
|
||||
#include <QTextDocument>
|
||||
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
@brief TextResizeHandles::TextResizeHandles
|
||||
@param text : the text to resize, becomes the parent of the handles.
|
||||
The text may be in a scene or not yet.
|
||||
@param handle_size : see QETUtils::graphicsHandlerSize()
|
||||
*/
|
||||
TextResizeHandles::TextResizeHandles(QGraphicsTextItem *text, qreal handle_size) :
|
||||
m_text(text)
|
||||
{
|
||||
setFlag(QGraphicsItem::ItemHasNoContents);
|
||||
setParentItem(text);
|
||||
|
||||
for (int i = TextResize::TopLeft ; i <= TextResize::BottomLeft ; ++i)
|
||||
{
|
||||
auto *handle = new QetGraphicsHandlerItem(handle_size);
|
||||
handle->setParentItem(this);
|
||||
handle->setColor(Qt::darkGreen);
|
||||
handle->setToolTip(tr("Glisser pour changer la largeur du texte, "
|
||||
"double-cliquer pour une largeur automatique"));
|
||||
handle->setCursor(i == TextResize::TopLeft || i == TextResize::BottomRight
|
||||
? Qt::SizeFDiagCursor : Qt::SizeBDiagCursor);
|
||||
m_handles << handle;
|
||||
}
|
||||
installHandleFilters();
|
||||
|
||||
m_size_connection = connect(text->document()->documentLayout(),
|
||||
&QAbstractTextDocumentLayout::documentSizeChanged,
|
||||
this, &TextResizeHandles::updateHandlesPos);
|
||||
updateHandlesPos();
|
||||
}
|
||||
|
||||
TextResizeHandles::~TextResizeHandles()
|
||||
{
|
||||
disconnect(m_size_connection);
|
||||
}
|
||||
|
||||
QRectF TextResizeHandles::boundingRect() const {
|
||||
return QRectF();
|
||||
}
|
||||
|
||||
/**
|
||||
@return the handle at corner
|
||||
*/
|
||||
QetGraphicsHandlerItem *TextResizeHandles::handle(TextResize::Corner corner) const {
|
||||
return m_handles.at(corner);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief TextResizeHandles::updateHandlesPos
|
||||
Put the handles at the corners of the box of the text. Called when the
|
||||
size of the text changes; the owner calls it when the box changes in an
|
||||
other way.
|
||||
The box is boundingRect(), the box a user sees selected and resizes,
|
||||
not the tighter frame of a framed text (qelectrotech#591).
|
||||
*/
|
||||
void TextResizeHandles::updateHandlesPos()
|
||||
{
|
||||
const QRectF rect = m_text->boundingRect();
|
||||
for (int i = TextResize::TopLeft ; i <= TextResize::BottomLeft ; ++i)
|
||||
m_handles.at(i)->setPos(TextResize::cornerOf(rect, TextResize::Corner(i)));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief TextResizeHandles::itemChange
|
||||
A scene drops the event filters of an item that leaves it, so the
|
||||
handles stop reacting to the mouse when the text is removed from its
|
||||
scene and added back (undoing a delete does that), or when they were
|
||||
created before the text was in a scene. Install them again each time
|
||||
these handles enter a scene.
|
||||
*/
|
||||
QVariant TextResizeHandles::itemChange(GraphicsItemChange change, const QVariant &value)
|
||||
{
|
||||
if (change == QGraphicsItem::ItemSceneHasChanged)
|
||||
installHandleFilters();
|
||||
return QGraphicsObject::itemChange(change, value);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief TextResizeHandles::installHandleFilters
|
||||
Filter the mouse events of the handles, when they are in a scene.
|
||||
*/
|
||||
void TextResizeHandles::installHandleFilters()
|
||||
{
|
||||
if (!scene())
|
||||
return;
|
||||
for (QetGraphicsHandlerItem *handle : std::as_const(m_handles))
|
||||
if (handle->scene() == scene())
|
||||
handle->installSceneEventFilter(this);
|
||||
}
|
||||
|
||||
bool TextResizeHandles::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
|
||||
{
|
||||
const int index = m_handles.indexOf(static_cast<QetGraphicsHandlerItem *>(watched));
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
switch (event->type())
|
||||
{
|
||||
case QEvent::GraphicsSceneMousePress:
|
||||
{
|
||||
auto *mouse_event = static_cast<QGraphicsSceneMouseEvent *>(event);
|
||||
if (mouse_event->button() != Qt::LeftButton)
|
||||
return false;
|
||||
pressed(TextResize::Corner(index), mouse_event->scenePos());
|
||||
return true;
|
||||
}
|
||||
case QEvent::GraphicsSceneMouseMove:
|
||||
if (m_dragging)
|
||||
moved(static_cast<QGraphicsSceneMouseEvent *>(event)->scenePos());
|
||||
return true;
|
||||
case QEvent::GraphicsSceneMouseRelease:
|
||||
released();
|
||||
return true;
|
||||
case QEvent::GraphicsSceneMouseDoubleClick:
|
||||
resetToAutomaticWidth();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void TextResizeHandles::pressed(TextResize::Corner corner, const QPointF &scene_pos)
|
||||
{
|
||||
m_dragging = true;
|
||||
m_press_scene = scene_pos;
|
||||
m_dragged = corner;
|
||||
m_old_width = currentWidth();
|
||||
m_old_pos = m_text->pos();
|
||||
m_press_inverse = m_text->sceneTransform().inverted();
|
||||
m_fixed_local = TextResize::cornerOf(m_text->boundingRect(), TextResize::opposite(corner));
|
||||
m_fixed_scene = m_text->mapToScene(m_fixed_local);
|
||||
m_min_width = TextResize::minimumWidth(m_text->document());
|
||||
}
|
||||
|
||||
void TextResizeHandles::moved(const QPointF &scene_pos)
|
||||
{
|
||||
//A click without a move keeps an automatic width automatic
|
||||
if (scene_pos == m_press_scene)
|
||||
return;
|
||||
|
||||
//Whole pixels, as in the width spin boxes
|
||||
const qreal width = qMax(qreal(qRound(TextResize::widthForDrag(m_press_inverse, m_fixed_local,
|
||||
scene_pos, m_dragged, 0))),
|
||||
std::ceil(m_min_width));
|
||||
m_text->setProperty("textWidth", width);
|
||||
TextResize::pinCorner(m_text, TextResize::opposite(m_dragged), m_fixed_scene);
|
||||
updateHandlesPos();
|
||||
}
|
||||
|
||||
void TextResizeHandles::released()
|
||||
{
|
||||
if (!m_dragging)
|
||||
return;
|
||||
m_dragging = false;
|
||||
|
||||
const qreal new_width = currentWidth();
|
||||
if (!qFuzzyCompare(m_old_width, new_width) || m_old_pos != m_text->pos())
|
||||
emit resizeFinished(m_old_width, new_width, m_old_pos, m_text->pos());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief TextResizeHandles::resetToAutomaticWidth
|
||||
Back to the automatic width (-1): the text keeps the point chosen by its
|
||||
alignment in place, as it does for any other change of its width.
|
||||
*/
|
||||
void TextResizeHandles::resetToAutomaticWidth()
|
||||
{
|
||||
m_dragging = false;
|
||||
|
||||
const qreal old_width = currentWidth();
|
||||
if (old_width < 0)
|
||||
return;
|
||||
|
||||
const QPointF old_pos = m_text->pos();
|
||||
m_text->setProperty("textWidth", qreal(-1));
|
||||
updateHandlesPos();
|
||||
emit resizeFinished(old_width, currentWidth(), old_pos, m_text->pos());
|
||||
}
|
||||
|
||||
qreal TextResizeHandles::currentWidth() const {
|
||||
return m_text->property("textWidth").toReal();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@brief TextResizeCommand::TextResizeCommand
|
||||
@param text : a text with a "textWidth" property
|
||||
@param old_width
|
||||
@param new_width
|
||||
@param old_pos
|
||||
@param new_pos
|
||||
@param parent
|
||||
*/
|
||||
TextResizeCommand::TextResizeCommand(QGraphicsObject *text,
|
||||
qreal old_width, qreal new_width,
|
||||
const QPointF &old_pos, const QPointF &new_pos,
|
||||
QUndoCommand *parent) :
|
||||
QUndoCommand(parent),
|
||||
m_text(text),
|
||||
m_old_width(old_width),
|
||||
m_new_width(new_width),
|
||||
m_old_pos(old_pos),
|
||||
m_new_pos(new_pos)
|
||||
{
|
||||
setText(tr("Redimensionner un texte"));
|
||||
}
|
||||
|
||||
void TextResizeCommand::undo() {
|
||||
apply(m_old_width, m_old_pos);
|
||||
}
|
||||
|
||||
void TextResizeCommand::redo() {
|
||||
apply(m_new_width, m_new_pos);
|
||||
}
|
||||
|
||||
void TextResizeCommand::apply(qreal width, const QPointF &pos)
|
||||
{
|
||||
if (!m_text)
|
||||
return;
|
||||
m_text->setProperty("textWidth", width);
|
||||
m_text->setPos(pos);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
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 TEXTRESIZEHANDLES_H
|
||||
#define TEXTRESIZEHANDLES_H
|
||||
|
||||
#include "../textresize.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QGraphicsObject>
|
||||
#include <QPointer>
|
||||
#include <QTransform>
|
||||
#include <QUndoCommand>
|
||||
#include <QVector>
|
||||
|
||||
class QetGraphicsHandlerItem;
|
||||
class QGraphicsTextItem;
|
||||
|
||||
/**
|
||||
@brief The TextResizeHandles class
|
||||
Four corner handles to change the width of a text box with the mouse.
|
||||
The opposite corner of the dragged one stays in place, the text wraps
|
||||
to the new width and the height of the box follows the text.
|
||||
Double-clicking a handle goes back to the automatic width.
|
||||
|
||||
The text must have a qreal "textWidth" property (-1 = automatic width).
|
||||
The handles are a child of the text, so they follow it when it, or its
|
||||
symbol, is moved, rotated or zoomed (qelectrotech#1002).
|
||||
|
||||
The width is changed live while dragging; the owner of the text makes
|
||||
the change undoable when resizeFinished() is emitted, by pushing a
|
||||
TextResizeCommand on its own undo stack.
|
||||
*/
|
||||
class TextResizeHandles : public QGraphicsObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TextResizeHandles(QGraphicsTextItem *text, qreal handle_size = 10);
|
||||
~TextResizeHandles() override;
|
||||
|
||||
QRectF boundingRect() const override;
|
||||
void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override {}
|
||||
|
||||
QetGraphicsHandlerItem *handle(TextResize::Corner corner) const;
|
||||
void updateHandlesPos();
|
||||
|
||||
signals:
|
||||
void resizeFinished(qreal old_width, qreal new_width,
|
||||
QPointF old_pos, QPointF new_pos);
|
||||
|
||||
protected:
|
||||
bool sceneEventFilter(QGraphicsItem *watched, QEvent *event) override;
|
||||
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
|
||||
|
||||
private:
|
||||
void installHandleFilters();
|
||||
void pressed(TextResize::Corner corner, const QPointF &scene_pos);
|
||||
void moved(const QPointF &scene_pos);
|
||||
void released();
|
||||
void resetToAutomaticWidth();
|
||||
qreal currentWidth() const;
|
||||
|
||||
QGraphicsTextItem *m_text;
|
||||
QVector<QetGraphicsHandlerItem *> m_handles;
|
||||
QMetaObject::Connection m_size_connection;
|
||||
|
||||
//The drag in progress
|
||||
bool m_dragging = false;
|
||||
TextResize::Corner m_dragged = TextResize::BottomRight;
|
||||
QTransform m_press_inverse;
|
||||
QPointF m_press_scene,
|
||||
m_fixed_local,
|
||||
m_fixed_scene,
|
||||
m_old_pos;
|
||||
qreal m_old_width = -1,
|
||||
m_min_width = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief The TextResizeCommand class
|
||||
Undo a width change made with TextResizeHandles: the width, then the
|
||||
position, which keeps the opposite corner of the dragged one in place.
|
||||
Both are set in the same order on undo and redo, because a text may
|
||||
move itself when its width changes (to keep its alignment point).
|
||||
*/
|
||||
class TextResizeCommand : public QUndoCommand
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(TextResizeCommand)
|
||||
|
||||
public:
|
||||
TextResizeCommand(QGraphicsObject *text,
|
||||
qreal old_width, qreal new_width,
|
||||
const QPointF &old_pos, const QPointF &new_pos,
|
||||
QUndoCommand *parent = nullptr);
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
void apply(qreal width, const QPointF &pos);
|
||||
|
||||
QPointer<QGraphicsObject> m_text;
|
||||
qreal m_old_width,
|
||||
m_new_width;
|
||||
QPointF m_old_pos,
|
||||
m_new_pos;
|
||||
};
|
||||
|
||||
#endif // TEXTRESIZEHANDLES_H
|
||||
@@ -205,6 +205,7 @@ void TerminalStripTreeDockWidget::on_m_tree_view_currentItemChanged(QTreeWidgetI
|
||||
Q_UNUSED(previous)
|
||||
|
||||
if (!current) {
|
||||
m_current_is_free_terminal = false;
|
||||
setCurrentStrip(nullptr);
|
||||
return;
|
||||
}
|
||||
@@ -225,11 +226,12 @@ void TerminalStripTreeDockWidget::on_m_tree_view_currentItemChanged(QTreeWidgetI
|
||||
}
|
||||
}
|
||||
|
||||
if (strip_ != m_current_strip) {
|
||||
setCurrentStrip(strip_);
|
||||
} else if (current_is_free != m_current_is_free_terminal) {
|
||||
//The flag must follow every selection change, or a reload of the tree
|
||||
//(e.g. after moving a free terminal) leaves it stale and the next
|
||||
//click on a free terminal shows nothing (#1306)
|
||||
if (strip_ != m_current_strip || current_is_free != m_current_is_free_terminal) {
|
||||
m_current_is_free_terminal = current_is_free;
|
||||
emit currentStripChanged(nullptr);
|
||||
setCurrentStrip(strip_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1248,6 +1248,8 @@ void projectDataBase::addDrawingItem(QGraphicsItem *item)
|
||||
}
|
||||
else if (auto text = qobject_cast<IndependentTextItem *>(object)) {
|
||||
connect(text, &IndependentTextItem::uuidChanged, this, &projectDataBase::drawingItemChanged, unique);
|
||||
//Also changes the height of the text, kept in the row
|
||||
connect(text, &IndependentTextItem::textWidthChanged, this, &projectDataBase::drawingItemChanged, unique);
|
||||
//Sent by the document, not the item: drawingItemChanged() walks
|
||||
//back up to the item. It is the one signal that catches every way
|
||||
//the text changes -- typing, undo, a script's setTextContent().
|
||||
@@ -1424,6 +1426,9 @@ bool projectDataBase::writeDrawingItem(QObject *object)
|
||||
query = &m_insert_independent_text_query;
|
||||
query->bindValue(QStringLiteral(":text"), text->toPlainText());
|
||||
query->bindValue(QStringLiteral(":rotation"), text->rotation());
|
||||
//NULL for the automatic width
|
||||
query->bindValue(QStringLiteral(":text_width"), text->textWidth() > 0
|
||||
? QVariant(text->textWidth()) : QVariant());
|
||||
}
|
||||
else if (auto image = qobject_cast<DiagramImageItem *>(object))
|
||||
{
|
||||
@@ -1667,7 +1672,7 @@ bool projectDataBase::createDataBase()
|
||||
"type VARCHAR(20), color VARCHAR(20), fill VARCHAR(20), "
|
||||
"FOREIGN KEY (diagram_uuid) REFERENCES diagram (uuid))",
|
||||
QStringLiteral("CREATE TABLE independent_text (") + drawing_columns +
|
||||
"text TEXT, rotation REAL, "
|
||||
"text TEXT, rotation REAL, text_width REAL, "
|
||||
"FOREIGN KEY (diagram_uuid) REFERENCES diagram (uuid))",
|
||||
QStringLiteral("CREATE TABLE image (") + drawing_columns +
|
||||
"pixel_width INTEGER, pixel_height INTEGER, "
|
||||
@@ -2123,8 +2128,8 @@ void projectDataBase::prepareQuery()
|
||||
", :type, :color, :fill)");
|
||||
m_insert_independent_text_query = QSqlQuery(m_data_base);
|
||||
m_insert_independent_text_query.prepare("INSERT OR REPLACE INTO independent_text (" + drawing_columns +
|
||||
", text, rotation) VALUES (" + drawing_values +
|
||||
", :text, :rotation)");
|
||||
", text, rotation, text_width) VALUES (" + drawing_values +
|
||||
", :text, :rotation, :text_width)");
|
||||
m_insert_image_query = QSqlQuery(m_data_base);
|
||||
m_insert_image_query.prepare("INSERT OR REPLACE INTO image (" + drawing_columns +
|
||||
", pixel_width, pixel_height) VALUES (" + drawing_values +
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "qetgraphicsitem/independenttextitem.h"
|
||||
#include "qetgraphicsitem/qetshapeitem.h"
|
||||
#include "qetgraphicsitem/terminal.h"
|
||||
#include "textlines.h"
|
||||
|
||||
#include <QGraphicsSimpleTextItem>
|
||||
#include <QSet>
|
||||
@@ -358,7 +359,8 @@ void DxfExport::write(Diagram *diagram, int width, int height,
|
||||
qreal y = dti->scenePos().y()
|
||||
- ydir * fontSize * 1.8
|
||||
- xdir * fontSize * 0.9;
|
||||
QStringList lines = dti -> toPlainText().split('\n');
|
||||
//As drawn: a text with a width is wrapped
|
||||
QStringList lines = TextLines::layoutLines(dti -> document());
|
||||
qreal offset = fontSize * 1.6;
|
||||
foreach (QString line, lines) {
|
||||
if (line.size() > 0 && line != "_" )
|
||||
|
||||
@@ -235,7 +235,10 @@ void PartDynamicTextField::fromXml(const QDomElement &dom_elmt) {
|
||||
|
||||
m_uuid = QUuid(dom_elmt.attribute("uuid", QUuid::createUuid().toString()));
|
||||
setFrame(dom_elmt.attribute("frame", "false") == "true"? true : false);
|
||||
//The saved position is the one to keep
|
||||
m_block_alignment = true;
|
||||
setTextWidth(dom_elmt.attribute("text_width", QString::number(-1)).toDouble());
|
||||
m_block_alignment = false;
|
||||
|
||||
QMetaEnum me = DynamicElementTextItem::textFromMetaEnum();
|
||||
m_text_from = DynamicElementTextItem::TextFrom(
|
||||
@@ -431,13 +434,23 @@ bool PartDynamicTextField::frame() const
|
||||
return m_frame;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief PartDynamicTextField::setTextWidth
|
||||
Set the width of this text (-1 = automatic width). The point of the text
|
||||
chosen by its alignment stays in place, as when the text changes: a
|
||||
right-aligned or centred field no longer grows to the right only, and
|
||||
undoing a width change puts it back where it was.
|
||||
@param width
|
||||
*/
|
||||
void PartDynamicTextField::setTextWidth(qreal width) {
|
||||
prepareAlignment();
|
||||
this -> document() -> setTextWidth(width);
|
||||
|
||||
//Adjust the width, to ideal width if needed
|
||||
if(width > 0 && document() -> size().width() > width)
|
||||
document() -> setTextWidth(document() -> idealWidth());
|
||||
|
||||
finishAlignment();
|
||||
m_text_width = document() -> textWidth();
|
||||
emit textWidthChanged(m_text_width);
|
||||
}
|
||||
|
||||
@@ -157,7 +157,9 @@ void DynamicTextFieldEditor::updateForm()
|
||||
m_color_kpb -> setColor(m_text_field.data() -> color());
|
||||
}
|
||||
#endif
|
||||
ui -> m_width_sb -> setValue(m_text_field.data() -> textWidth());
|
||||
//Rounded as on_m_width_sb_editingFinished() compares it;
|
||||
//setValue(int) would truncate 61.7 to 61
|
||||
ui -> m_width_sb -> setValue(qRound(m_text_field.data() -> textWidth()));
|
||||
ui -> m_font_pb -> setText(m_text_field -> font().family());
|
||||
|
||||
switch (m_text_field.data() -> textFrom()) {
|
||||
@@ -363,7 +365,9 @@ void DynamicTextFieldEditor::on_m_width_sb_editingFinished()
|
||||
qreal width = (qreal)ui -> m_width_sb -> value();
|
||||
|
||||
for (int i = 0; i < m_parts.length(); i++) {
|
||||
if(width != m_parts[i] -> textWidth()) {
|
||||
//The box shows whole pixels: a width fitted to the text is not
|
||||
//rounded just by leaving the box.
|
||||
if(ui -> m_width_sb -> value() != qRound(m_parts[i] -> textWidth())) {
|
||||
QPropertyUndoCommand *undo = new QPropertyUndoCommand(m_parts[i], "textWidth", m_parts[i] -> textWidth(), width);
|
||||
undo -> setText(tr("Modifier la largeur d'un texte"));
|
||||
undoStack().push(undo);
|
||||
|
||||
@@ -101,11 +101,17 @@
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QSpinBox" name="m_width_sb">
|
||||
<property name="specialValueText">
|
||||
<string>Auto</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>px</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>500</number>
|
||||
<number>10000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -366,6 +366,7 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
//export to pdf
|
||||
m_export_to_pdf = new QAction(QET::Icons::PDF, tr("Exporter en pdf"), this);
|
||||
ShortcutManager::instance().registerAction(m_export_to_pdf, "diagrameditor.export_to_pdf", tr("Éditeur de schémas"), QKeySequence());
|
||||
m_export_to_pdf->setStatusTip(tr("Exporte un ou plusieurs folios du projet courant", "status bar tip"));
|
||||
connect(m_export_to_pdf, &QAction::triggered, [this] () {
|
||||
auto project = currentProject();
|
||||
@@ -482,6 +483,7 @@ void QETDiagramEditor::setUpActions()
|
||||
//setting, so it does not need a diagram open or anything
|
||||
//selected the way m_duplicate itself does.
|
||||
m_configure_duplicate = new QAction(tr("Configurer la duplication..."), this);
|
||||
ShortcutManager::instance().registerAction(m_configure_duplicate, "diagrameditor.configure_duplicate", tr("Éditeur de schémas"), QKeySequence());
|
||||
m_configure_duplicate->setStatusTip(tr("Choisir l'espacement et la direction utilisés par Dupliquer", "status bar tip"));
|
||||
connect(m_configure_duplicate, &QAction::triggered, [this]() {
|
||||
DuplicateOffsetDialog dialog(this);
|
||||
@@ -517,6 +519,7 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
//AutoBreakConductor
|
||||
m_auto_break_conductor = new QAction (QET::Icons::Conductor, tr("Coupure automatique de conducteur(s)","Tool tip of auto break conductor"), this);
|
||||
ShortcutManager::instance().registerAction(m_auto_break_conductor, "diagrameditor.auto_break_conductor", tr("Éditeur de schémas"), QKeySequence());
|
||||
m_auto_break_conductor->setStatusTip (tr("Couper automatiquement les conducteurs existants lors du placement d'un élément", "Status tip of auto break conductor"));
|
||||
m_auto_break_conductor->setCheckable (true);
|
||||
{
|
||||
@@ -535,6 +538,7 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
//Draw or not the background grid
|
||||
m_draw_grid = new QAction ( QET::Icons::Grid, tr("Afficher la grille"), this);
|
||||
ShortcutManager::instance().registerAction(m_draw_grid, "diagrameditor.draw_grid", tr("Éditeur de schémas"), QKeySequence());
|
||||
m_draw_grid->setStatusTip(tr("Affiche ou masque la grille des folios"));
|
||||
QSettings settings;
|
||||
m_draw_grid->setCheckable(true);
|
||||
@@ -576,6 +580,7 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
// Draw or not the custom guides
|
||||
m_draw_guides = new QAction ( QIcon::fromTheme("guides"), tr("Afficher les guides"), this);
|
||||
ShortcutManager::instance().registerAction(m_draw_guides, "diagrameditor.draw_guides", tr("Éditeur de schémas"), QKeySequence());
|
||||
m_draw_guides->setStatusTip(tr("Affiche ou masque les guides"));
|
||||
m_draw_guides->setCheckable(true);
|
||||
m_draw_guides->setChecked(settings.value("diagrameditor/guides_display_startup", false).toBool());
|
||||
@@ -588,6 +593,7 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
//Keep the column numbers and row letters of the folio in sight
|
||||
m_cell_rulers = new QAction(tr("Garder les en-têtes visibles"), this);
|
||||
ShortcutManager::instance().registerAction(m_cell_rulers, "diagrameditor.cell_rulers", tr("Éditeur de schémas"), QKeySequence());
|
||||
m_cell_rulers->setStatusTip(tr("Garde les numéros de colonne et les lettres de ligne du folio visibles au bord de la vue"));
|
||||
m_cell_rulers->setCheckable(true);
|
||||
m_cell_rulers->setChecked(settings.value("diagrameditor/cell_rulers", false).toBool());
|
||||
@@ -600,6 +606,7 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
//Draw the limits of the folio columns and rows across the drawing
|
||||
m_cell_lines = new QAction(tr("Afficher les limites des cases"), this);
|
||||
ShortcutManager::instance().registerAction(m_cell_lines, "diagrameditor.cell_lines", tr("Éditeur de schémas"), QKeySequence());
|
||||
m_cell_lines->setStatusTip(tr("Trace les limites des colonnes et des lignes du folio sur le schéma, à l'écran seulement"));
|
||||
m_cell_lines->setCheckable(true);
|
||||
m_cell_lines->setChecked(settings.value("diagrameditor/cell_lines", false).toBool());
|
||||
@@ -654,6 +661,7 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
//Edit current project properties
|
||||
m_project_edit_properties = new QAction(QET::Icons::ProjectProperties, tr("Propriétés du projet"), this);
|
||||
ShortcutManager::instance().registerAction(m_project_edit_properties, "diagrameditor.project_edit_properties", tr("Éditeur de schémas"), QKeySequence());
|
||||
connect(m_project_edit_properties, &QAction::triggered, [this]() {
|
||||
editProjectProperties(currentProjectView());
|
||||
});
|
||||
@@ -669,10 +677,12 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
//Remove current folio from current project
|
||||
m_remove_diagram_from_project = new QAction(QET::Icons::DiagramDelete, tr("Supprimer le folio"), this);
|
||||
ShortcutManager::instance().registerAction(m_remove_diagram_from_project, "diagrameditor.remove_diagram_from_project", tr("Éditeur de schémas"), QKeySequence());
|
||||
connect(m_remove_diagram_from_project, &QAction::triggered, this, &QETDiagramEditor::removeDiagramFromProject);
|
||||
|
||||
//Clean the current project
|
||||
m_clean_project = new QAction(QET::Icons::EditClear, tr("Nettoyer le projet"), this);
|
||||
ShortcutManager::instance().registerAction(m_clean_project, "diagrameditor.clean_project", tr("Éditeur de schémas"), QKeySequence());
|
||||
connect(m_clean_project, &QAction::triggered, [this]() {
|
||||
if (ProjectView *current_project = currentProjectView()) {
|
||||
if (current_project->cleanProject()) {
|
||||
@@ -683,6 +693,7 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
//Export nomenclature to CSV
|
||||
m_csv_export = new QAction(QET::Icons::DocumentSpreadsheet, tr("Exporter au format CSV"), this);
|
||||
ShortcutManager::instance().registerAction(m_csv_export, "diagrameditor.csv_export", tr("Éditeur de schémas"), QKeySequence());
|
||||
connect(m_csv_export, &QAction::triggered, [this]() {
|
||||
BOMExportDialog bom(currentProjectView()->project(), this);
|
||||
bom.exec();
|
||||
@@ -690,6 +701,7 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
//Add a nomenclature item
|
||||
m_add_nomenclature = new QAction(QET::Icons::TableOfContent, tr("Ajouter une nomenclature"), this);
|
||||
ShortcutManager::instance().registerAction(m_add_nomenclature, "diagrameditor.add_nomenclature", tr("Éditeur de schémas"), QKeySequence());
|
||||
connect(m_add_nomenclature, &QAction::triggered, this, [this]() {
|
||||
if(this->currentDiagramView()) {
|
||||
QetGraphicsTableFactory::createAndAddNomenclature(this->currentDiagramView()->diagram());
|
||||
@@ -698,6 +710,7 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
//Add a summary item
|
||||
m_add_summary = new QAction(QET::Icons::TableOfContent, tr("Ajouter un sommaire"), this);
|
||||
ShortcutManager::instance().registerAction(m_add_summary, "diagrameditor.add_summary", tr("Éditeur de schémas"), QKeySequence());
|
||||
connect(m_add_summary, &QAction::triggered, this, [this]() {
|
||||
if(this->currentDiagramView()) {
|
||||
QetGraphicsTableFactory::createAndAddSummary(this->currentDiagramView()->diagram());
|
||||
@@ -705,6 +718,7 @@ void QETDiagramEditor::setUpActions()
|
||||
});
|
||||
|
||||
m_terminal_strip_dialog = new QAction(QET::Icons::TerminalStrip, tr("Gestionnaire de borniers (DEV)"), this);
|
||||
ShortcutManager::instance().registerAction(m_terminal_strip_dialog, "diagrameditor.terminal_strip_dialog", tr("Éditeur de schémas"), QKeySequence());
|
||||
connect(m_terminal_strip_dialog, &QAction::triggered, this, [this]()
|
||||
{
|
||||
if (auto project = this->currentProject())
|
||||
@@ -715,10 +729,12 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
//Launch the plugin of terminal generator
|
||||
m_project_terminalBloc = new QAction(QET::Icons::TerminalStrip, tr("Lancer le plugin de création de borniers"), this);
|
||||
ShortcutManager::instance().registerAction(m_project_terminalBloc, "diagrameditor.terminal_strip_plugin", tr("Éditeur de schémas"), QKeySequence());
|
||||
connect(m_project_terminalBloc, &QAction::triggered, this, &QETDiagramEditor::generateTerminalBlock);
|
||||
|
||||
//Export conductor num to csv
|
||||
m_project_export_conductor_num = new QAction(QET::Icons::DocumentSpreadsheet, tr("Exporter la liste des noms de conducteurs"), this);
|
||||
ShortcutManager::instance().registerAction(m_project_export_conductor_num, "diagrameditor.export_conductor_names", tr("Éditeur de schémas"), QKeySequence());
|
||||
connect(m_project_export_conductor_num, &QAction::triggered, [this]() {
|
||||
QETProject *project = this->currentProject();
|
||||
if (project)
|
||||
@@ -729,6 +745,7 @@ void QETDiagramEditor::setUpActions()
|
||||
});
|
||||
// Export wiring list to CSV
|
||||
m_project_export_wiring_list = new QAction(QET::Icons::DocumentSpreadsheet, tr("Exporter le plan de câblage"), this);
|
||||
ShortcutManager::instance().registerAction(m_project_export_wiring_list, "diagrameditor.export_wiring_list", tr("Éditeur de schémas"), QKeySequence());
|
||||
connect(m_project_export_wiring_list, &QAction::triggered, [this]() {
|
||||
QETProject *project = this->currentProject();
|
||||
if (project)
|
||||
@@ -740,6 +757,7 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
// Show the wiring list read from the project database
|
||||
m_project_wiring_list_view = new QAction(QET::Icons::DocumentSpreadsheet, tr("Liste de câblage (base de données)"), this);
|
||||
ShortcutManager::instance().registerAction(m_project_wiring_list_view, "diagrameditor.wiring_list_view", tr("Éditeur de schémas"), QKeySequence());
|
||||
connect(m_project_wiring_list_view, &QAction::triggered, [this]() {
|
||||
QETProject *project = this->currentProject();
|
||||
if (project)
|
||||
@@ -751,10 +769,12 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
// Terminal Numbering
|
||||
m_terminal_numbering = new QAction(QET::Icons::TerminalStrip, tr("Numérotation automatique des bornes"), this);
|
||||
ShortcutManager::instance().registerAction(m_terminal_numbering, "diagrameditor.terminal_numbering", tr("Éditeur de schémas"), QKeySequence());
|
||||
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);
|
||||
ShortcutManager::instance().registerAction(m_reload_element_drawings, "diagrameditor.reload_element_drawings", tr("Éditeur de schémas"), QKeySequence());
|
||||
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)"));
|
||||
@@ -763,6 +783,7 @@ void QETDiagramEditor::setUpActions()
|
||||
#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);
|
||||
ShortcutManager::instance().registerAction(m_run_script, "diagrameditor.run_script", tr("Éditeur de schémas"), QKeySequence());
|
||||
m_run_script->setStatusTip(
|
||||
tr("Exécute un script JavaScript sur le projet courant (voir qet.*"
|
||||
" dans le script pour l'API disponible)"));
|
||||
@@ -813,6 +834,7 @@ void QETDiagramEditor::setUpActions()
|
||||
//Stored scripts are files in a folder, written by hand, by the
|
||||
//script manager or by an assistant: open it to add one.
|
||||
m_open_scripts_folder = new QAction(QET::Icons::FolderOpen, tr("Ouvrir le dossier des scripts"), this);
|
||||
ShortcutManager::instance().registerAction(m_open_scripts_folder, "diagrameditor.open_scripts_folder", tr("Éditeur de schémas"), QKeySequence());
|
||||
m_open_scripts_folder->setStatusTip(
|
||||
tr("Chaque fichier .js de ce dossier qui commence par un en-tête"
|
||||
" // ==QETScript== devient un bouton"));
|
||||
@@ -824,6 +846,7 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
#ifdef QET_EXPORT_PROJECT_DB
|
||||
m_export_project_db = new QAction(QET::Icons::DocumentSpreadsheet, tr("Exporter la base de donnée interne du projet"), this);
|
||||
ShortcutManager::instance().registerAction(m_export_project_db, "diagrameditor.export_project_db", tr("Éditeur de schémas"), QKeySequence());
|
||||
connect(m_export_project_db, &QAction::triggered, [this]() {
|
||||
projectDataBase::exportDb(this->currentProject()->dataBase(), this);
|
||||
});
|
||||
@@ -831,11 +854,13 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
//MDI view style
|
||||
m_tabbed_view_mode = new QAction(tr("en utilisant des onglets"), this);
|
||||
ShortcutManager::instance().registerAction(m_tabbed_view_mode, "diagrameditor.tabbed_view_mode", tr("Éditeur de schémas"), QKeySequence());
|
||||
m_tabbed_view_mode->setStatusTip(tr("Présente les différents projets ouverts des onglets", "status bar tip"));
|
||||
m_tabbed_view_mode->setCheckable(true);
|
||||
connect(m_tabbed_view_mode, &QAction::triggered, this, &QETDiagramEditor::setTabbedMode);
|
||||
|
||||
m_windowed_view_mode = new QAction(tr("en utilisant des fenêtres"), this);
|
||||
ShortcutManager::instance().registerAction(m_windowed_view_mode, "diagrameditor.windowed_view_mode", tr("Éditeur de schémas"), QKeySequence());
|
||||
m_windowed_view_mode->setStatusTip(tr("Présente les différents projets ouverts dans des sous-fenêtres", "status bar tip"));
|
||||
m_windowed_view_mode->setCheckable(true);
|
||||
connect(m_windowed_view_mode, &QAction::triggered, this, &QETDiagramEditor::setWindowedMode);
|
||||
@@ -846,15 +871,18 @@ void QETDiagramEditor::setUpActions()
|
||||
m_group_view_mode -> setExclusive(true);
|
||||
|
||||
m_tile_window = new QAction(tr("&Mosaïque"), this);
|
||||
ShortcutManager::instance().registerAction(m_tile_window, "diagrameditor.tile_windows", tr("Éditeur de schémas"), QKeySequence());
|
||||
m_tile_window->setStatusTip(tr("Dispose les fenêtres en mosaïque", "status bar tip"));
|
||||
connect(m_tile_window, &QAction::triggered, &m_workspace, &QMdiArea::tileSubWindows);
|
||||
|
||||
m_cascade_window = new QAction(tr("&Cascade"), this);
|
||||
ShortcutManager::instance().registerAction(m_cascade_window, "diagrameditor.cascade_windows", tr("Éditeur de schémas"), QKeySequence());
|
||||
m_cascade_window->setStatusTip(tr("Dispose les fenêtres en cascade", "status bar tip"));
|
||||
connect(m_cascade_window, &QAction::triggered, &m_workspace, &QMdiArea::cascadeSubWindows);
|
||||
|
||||
//Switch selection/view mode
|
||||
m_mode_selection = new QAction(QET::Icons::PartSelect, tr("Mode Selection"), this);
|
||||
ShortcutManager::instance().registerAction(m_mode_selection, "diagrameditor.mode_selection", tr("Éditeur de schémas"), QKeySequence());
|
||||
m_mode_selection->setStatusTip(tr("Permet de sélectionner les éléments", "status bar tip"));
|
||||
m_mode_selection->setCheckable(true);
|
||||
m_mode_selection->setChecked(true);
|
||||
@@ -867,6 +895,7 @@ void QETDiagramEditor::setUpActions()
|
||||
});
|
||||
|
||||
m_mode_visualise = new QAction(QET::Icons::ViewMove, tr("Mode Visualisation"), this);
|
||||
ShortcutManager::instance().registerAction(m_mode_visualise, "diagrameditor.mode_visualise", tr("Éditeur de schémas"), QKeySequence());
|
||||
m_mode_visualise->setStatusTip(tr("Permet de visualiser le folio sans pouvoir le modifier", "status bar tip"));
|
||||
m_mode_visualise->setCheckable(true);
|
||||
connect(m_mode_visualise, &QAction::triggered, [this]() {
|
||||
@@ -938,6 +967,12 @@ void QETDiagramEditor::setUpActions()
|
||||
add_row ->setData("add_row");
|
||||
remove_row ->setData("remove_row");
|
||||
|
||||
for (QAction *action : m_row_column_actions_group.actions()) {
|
||||
ShortcutManager::instance().registerAction(
|
||||
action, "diagrameditor." + action->data().toString(),
|
||||
tr("Éditeur de schémas"), QKeySequence());
|
||||
}
|
||||
|
||||
connect(&m_row_column_actions_group, &QActionGroup::triggered, this, &QETDiagramEditor::rowColumnGroupTriggered);
|
||||
|
||||
//Selections Actions (related to a selected item)
|
||||
@@ -955,6 +990,8 @@ void QETDiagramEditor::setUpActions()
|
||||
ShortcutManager::instance().registerAction(m_rotate_selection, "diagrameditor.rotate_selection", tr("Éditeur de schémas"), Qt::Key_Space);
|
||||
ShortcutManager::instance().registerAction(m_rotate_group_selection, "diagrameditor.rotate_group_selection", tr("Éditeur de schémas"), Qt::SHIFT | Qt::Key_Space);
|
||||
ShortcutManager::instance().registerAction(m_rotate_texts, "diagrameditor.rotate_texts", tr("Éditeur de schémas"), Qt::CTRL | Qt::Key_Space);
|
||||
ShortcutManager::instance().registerAction(m_find_element, "diagrameditor.find_element", tr("Éditeur de schémas"), QKeySequence());
|
||||
ShortcutManager::instance().registerAction(m_group_selected_texts, "diagrameditor.group_selected_texts", tr("Éditeur de schémas"), QKeySequence());
|
||||
ShortcutManager::instance().registerAction(m_edit_selection, "diagrameditor.edit_selection", tr("Éditeur de schémas"), Qt::CTRL | Qt::Key_E);
|
||||
|
||||
//Re-enter placement mode with the element placed last. Bare A rather
|
||||
|
||||
@@ -194,6 +194,30 @@ void DiagramImageItem::setPixmap(const QPixmap &pixmap) {
|
||||
emit pixmapChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramImageItem::setImageSource
|
||||
Set the picture's source -- original, crop rectangle and transparent
|
||||
colours. Only stores them: the displayed pixmap is a property of its
|
||||
own, set by the same undo command.
|
||||
*/
|
||||
void DiagramImageItem::setImageSource(const ImageSource &source)
|
||||
{
|
||||
m_base_pixmap = source.base;
|
||||
m_crop_rect = source.crop;
|
||||
m_transparent_colors = source.colors;
|
||||
}
|
||||
|
||||
QVariant DiagramImageItem::imageSourceVariant() const
|
||||
{
|
||||
return QVariant::fromValue(imageSource());
|
||||
}
|
||||
|
||||
void DiagramImageItem::setImageSourceVariant(const QVariant &source)
|
||||
{
|
||||
if (source.canConvert<ImageSource>())
|
||||
setImageSource(source.value<ImageSource>());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramImageItem::setScaleFactorX / setScaleFactorY / setRotationAngle
|
||||
Matching QetShapeItem's own setters for the identical fields --
|
||||
@@ -1814,21 +1838,13 @@ void DiagramImageItem::replaceImage()
|
||||
// or region was cropped for the old one, so this starts that
|
||||
// memory fresh rather than carrying over choices that would no
|
||||
// longer make sense.
|
||||
m_base_pixmap = newPixmap;
|
||||
m_crop_rect = newPixmap.rect();
|
||||
m_transparent_colors.clear();
|
||||
const ImageSource oldSource = imageSource();
|
||||
const ImageSource newSource{newPixmap, newPixmap.rect(), {}};
|
||||
|
||||
auto *undo = new QPropertyUndoCommand(this, "pixmap", oldPixmap, newPixmap);
|
||||
undo->setText(tr("Remplacer une image"));
|
||||
// Every call here is a separate, deliberate menu action with no
|
||||
// compound child of its own (unlike crop(), which always chains a
|
||||
// pos/rawPivot change and is naturally immune) -- two of them in a
|
||||
// row would carry the exact same object, property, and text, which
|
||||
// is indistinguishable from a legitimate merge to
|
||||
// QPropertyUndoCommand::mergeWith(). A dummy child (already treated
|
||||
// as "never merge" by that check) keeps each one its own, separate
|
||||
// undo step regardless.
|
||||
new QUndoCommand(undo);
|
||||
new QPropertyUndoCommand(this, "imageSource", QVariant::fromValue(oldSource),
|
||||
QVariant::fromValue(newSource), undo);
|
||||
diagram()->undoStack().push(undo);
|
||||
}
|
||||
|
||||
@@ -1854,7 +1870,9 @@ void DiagramImageItem::mirror(bool horizontal)
|
||||
// colour values don't change when the image is mirrored, only their
|
||||
// positions, so whatever was already keyed transparent should stay
|
||||
// remembered and still apply correctly to the flipped version.
|
||||
m_base_pixmap = m_base_pixmap.transformed(flip);
|
||||
const ImageSource oldSource = imageSource();
|
||||
ImageSource newSource = oldSource;
|
||||
newSource.base = m_base_pixmap.transformed(flip);
|
||||
|
||||
// m_crop_rect, unlike the colour list, DOES need to change: it's
|
||||
// defined in terms of positions within the base, and those
|
||||
@@ -1864,19 +1882,16 @@ void DiagramImageItem::mirror(bool horizontal)
|
||||
// whether this reads m_base_pixmap's size from before or after the
|
||||
// assignment above).
|
||||
if (horizontal)
|
||||
m_crop_rect = QRect(m_base_pixmap.width() - m_crop_rect.left() - m_crop_rect.width(),
|
||||
newSource.crop = QRect(m_base_pixmap.width() - m_crop_rect.left() - m_crop_rect.width(),
|
||||
m_crop_rect.top(), m_crop_rect.width(), m_crop_rect.height());
|
||||
else
|
||||
m_crop_rect = QRect(m_crop_rect.left(), m_base_pixmap.height() - m_crop_rect.top() - m_crop_rect.height(),
|
||||
newSource.crop = QRect(m_crop_rect.left(), m_base_pixmap.height() - m_crop_rect.top() - m_crop_rect.height(),
|
||||
m_crop_rect.width(), m_crop_rect.height());
|
||||
|
||||
auto *undo = new QPropertyUndoCommand(this, "pixmap", oldPixmap, newPixmap);
|
||||
undo->setText(horizontal ? tr("Miroir horizontal d'une image") : tr("Miroir vertical d'une image"));
|
||||
// See replaceImage()'s identical comment: a separate, deliberate
|
||||
// action with no compound child of its own, so a dummy one is
|
||||
// needed to stop two consecutive same-direction mirrors (identical
|
||||
// object, property, and text) from silently merging into one.
|
||||
new QUndoCommand(undo);
|
||||
new QPropertyUndoCommand(this, "imageSource", QVariant::fromValue(oldSource),
|
||||
QVariant::fromValue(newSource), undo);
|
||||
diagram()->undoStack().push(undo);
|
||||
}
|
||||
|
||||
@@ -1919,41 +1934,20 @@ void DiagramImageItem::setTransparentColor()
|
||||
if (dialog.exec() != QDialog::Accepted)
|
||||
return;
|
||||
|
||||
m_transparent_colors = dialog.pickedColors();
|
||||
const ImageSource oldSource = imageSource();
|
||||
ImageSource newSource = oldSource;
|
||||
newSource.colors = dialog.pickedColors();
|
||||
|
||||
const QPixmap oldPixmap = pixmap_;
|
||||
const QPixmap newPixmap = dialog.resultPixmap();
|
||||
|
||||
auto *undo = new QPropertyUndoCommand(this, "pixmap", oldPixmap, newPixmap);
|
||||
undo->setText(tr("Définir une couleur transparente"));
|
||||
// See replaceImage()'s identical comment: a separate, deliberate
|
||||
// action with no compound child of its own, so a dummy one is
|
||||
// needed to stop two consecutive transparency edits (identical
|
||||
// object, property, and text) from silently merging into one.
|
||||
new QUndoCommand(undo);
|
||||
new QPropertyUndoCommand(this, "imageSource", QVariant::fromValue(oldSource),
|
||||
QVariant::fromValue(newSource), undo);
|
||||
diagram()->undoStack().push(undo);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramImageItem::crop
|
||||
Context-menu action: opens ImageCropDialog against pixmap_ (the
|
||||
current, already colour-keyed display, so cropping is WYSIWYG
|
||||
against whatever is actually visible), then applies the chosen
|
||||
rectangle to both pixmap_ and m_base_pixmap together -- kept in
|
||||
sync the same way mirror() keeps them in sync, since cropping is a
|
||||
permanent, geometric change to the image's own content, unlike
|
||||
setTransparentColor()'s non-destructive colour keying.
|
||||
|
||||
pos() also needs adjusting, not just pixmap_: setPixmap() (called
|
||||
via the "pixmap" undo command below) recomputes
|
||||
transformOriginPoint() from the new, smaller boundingRect(), but
|
||||
pos() itself is untouched by that -- without fixing it up here too,
|
||||
the surviving content would visually jump to wherever local (0,0)
|
||||
happens to land after shrinking, rather than staying exactly where
|
||||
it already was. Chained into one undo step together with the pixmap
|
||||
change, since undoing a crop has to restore both, or the restored
|
||||
(larger) image ends up in the wrong place.
|
||||
*/
|
||||
/**
|
||||
@brief DiagramImageItem::crop
|
||||
Context-menu action: opens ImageCropDialog against m_base_pixmap
|
||||
@@ -1962,27 +1956,7 @@ void DiagramImageItem::setTransparentColor()
|
||||
destructive: nothing about the original content is ever discarded,
|
||||
only which region of it is currently being shown, exactly the same
|
||||
principle setTransparentColor() already follows for its own choices.
|
||||
Recomputes pixmap_ via computeDisplayPixmap() so any already-picked
|
||||
transparent colours are correctly re-applied to the newly-cropped
|
||||
region, rather than lost (the crop dialog itself knows nothing
|
||||
about them).
|
||||
|
||||
pos() also needs adjusting, not just pixmap_: setPixmap() (called
|
||||
via the "pixmap" undo command below) recomputes
|
||||
transformOriginPoint() from the new boundingRect(), but pos() itself
|
||||
is untouched by that -- without fixing it up here too, the
|
||||
surviving content would visually jump to wherever local (0,0) ends
|
||||
up after the crop rect changes, rather than staying exactly where
|
||||
it already was. This has to work whether this is the first crop
|
||||
ever applied or an adjustment of an existing one, so the position
|
||||
math is always done relative to the CURRENT crop rect (m_crop_rect,
|
||||
before it's updated below) -- when there's no previous crop, that's
|
||||
simply the whole base, which is what the very first version of this
|
||||
method assumed unconditionally.
|
||||
|
||||
Chained into one undo step together with the pixmap change, since
|
||||
undoing a crop has to restore both, or the restored (larger) image
|
||||
ends up in the wrong place.
|
||||
The crop itself is done by applyCrop().
|
||||
*/
|
||||
void DiagramImageItem::crop()
|
||||
{
|
||||
@@ -1994,9 +1968,41 @@ void DiagramImageItem::crop()
|
||||
if (dialog.exec() != QDialog::Accepted)
|
||||
return;
|
||||
|
||||
const QRect newCropRect = dialog.cropRect();
|
||||
applyCrop(dialog.cropRect());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramImageItem::applyCrop
|
||||
Show @a cropRect of the original (in the original's own pixels),
|
||||
keeping the centre of the kept region where it is on the folio.
|
||||
Recomputes pixmap_ via computeDisplayPixmap() so any already-picked
|
||||
transparent colours are correctly re-applied to the newly-cropped
|
||||
region, rather than lost.
|
||||
|
||||
pos() also needs adjusting, not just pixmap_: setPixmap() (called
|
||||
via the "pixmap" undo command below) recomputes
|
||||
transformOriginPoint() from the new boundingRect(), but pos() itself
|
||||
is untouched by that -- without fixing it up here too, the
|
||||
surviving content would visually jump to wherever local (0,0) ends
|
||||
up after the crop rect changes, rather than staying exactly where
|
||||
it already was. This has to work whether this is the first crop
|
||||
ever applied or an adjustment of an existing one, so the position
|
||||
math is always done relative to the CURRENT crop rect (m_crop_rect,
|
||||
before it's updated below) -- when there's no previous crop, that's
|
||||
simply the whole base.
|
||||
|
||||
One undo step, which restores the pixmap, the position, the pivot
|
||||
and the crop rectangle together.
|
||||
@return false if nothing was cropped: read-only folio, or a
|
||||
rectangle that is empty, outside the original, or the current one.
|
||||
*/
|
||||
bool DiagramImageItem::applyCrop(const QRect &cropRect)
|
||||
{
|
||||
if (!diagram() || diagram()->isReadOnly())
|
||||
return false;
|
||||
const QRect newCropRect = cropRect.intersected(m_base_pixmap.rect());
|
||||
if (newCropRect.isEmpty() || newCropRect == m_crop_rect)
|
||||
return; // nothing actually changed
|
||||
return false; // nothing actually changed
|
||||
|
||||
// newCropRect is in m_base_pixmap's own coordinates; converting its
|
||||
// center into the CURRENT local space (pixmap_'s own coordinates,
|
||||
@@ -2009,7 +2015,9 @@ void DiagramImageItem::crop()
|
||||
|
||||
const QPixmap oldPixmap = pixmap_;
|
||||
const QPixmap newPixmap = computeDisplayPixmap(m_base_pixmap, newCropRect, m_transparent_colors);
|
||||
m_crop_rect = newCropRect;
|
||||
const ImageSource oldSource = imageSource();
|
||||
ImageSource newSource = oldSource;
|
||||
newSource.crop = newCropRect;
|
||||
|
||||
// boundingRect() is exactly QRectF(pixmap_.rect()) (confirmed by
|
||||
// reading the actual implementation, not assumed) -- so the new
|
||||
@@ -2027,6 +2035,9 @@ void DiagramImageItem::crop()
|
||||
undo->setText(tr("Rogner une image"));
|
||||
new QPropertyUndoCommand(this, "pos", oldPos, newPos, undo);
|
||||
new QPropertyUndoCommand(this, "rawPivot", oldPivot, newOriginPoint, undo);
|
||||
new QPropertyUndoCommand(this, "imageSource", QVariant::fromValue(oldSource),
|
||||
QVariant::fromValue(newSource), undo);
|
||||
m_pivotIsCustom = false;
|
||||
diagram()->undoStack().push(undo);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ class DiagramImageItem : public QetGraphicsItem {
|
||||
Q_PROPERTY(qreal skewY READ skewY WRITE setSkewY NOTIFY transformChanged)
|
||||
Q_PROPERTY(QPointF pivot READ pivot WRITE setPivot NOTIFY transformChanged)
|
||||
Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged)
|
||||
// The picture's source -- original, crop rectangle, transparent
|
||||
// colours -- as one value, so that every edit of it (crop, colour
|
||||
// key, mirror, replace) is undone together with the displayed pixmap.
|
||||
Q_PROPERTY(QVariant imageSource READ imageSourceVariant WRITE setImageSourceVariant)
|
||||
// A second, deliberately non-compensating property on the SAME
|
||||
// underlying value -- setPivot() (above) intentionally adjusts
|
||||
// pos() to keep the image visually in place, which is exactly
|
||||
@@ -65,6 +69,19 @@ class DiagramImageItem : public QetGraphicsItem {
|
||||
DiagramImageItem(QetGraphicsItem * = nullptr);
|
||||
DiagramImageItem(const QPixmap &pixmap, QetGraphicsItem * = nullptr);
|
||||
~DiagramImageItem() override;
|
||||
|
||||
struct ImageSource
|
||||
{
|
||||
QPixmap base;
|
||||
QRect crop;
|
||||
QList<ImageTransparentColorDialog::PickedColor> colors;
|
||||
};
|
||||
ImageSource imageSource() const { return {m_base_pixmap, m_crop_rect, m_transparent_colors}; }
|
||||
void setImageSource(const ImageSource &source);
|
||||
QVariant imageSourceVariant() const;
|
||||
void setImageSourceVariant(const QVariant &source);
|
||||
QRect cropRect() const { return m_crop_rect; }
|
||||
bool applyCrop(const QRect &cropRect);
|
||||
|
||||
// attributes
|
||||
public:
|
||||
@@ -246,4 +263,6 @@ class DiagramImageItem : public QetGraphicsItem {
|
||||
QPointF m_label_scale{1.0, 1.0}; // scale the label rect was last computed for -- see updateLabelScale()
|
||||
bool m_resizeCenterAnchored = false; // decided once, at press time -- see handlerMousePressEvent()'s comment for why, mirroring the identical fix already made for shape creation
|
||||
};
|
||||
Q_DECLARE_METATYPE(DiagramImageItem::ImageSource)
|
||||
|
||||
#endif
|
||||
|
||||
@@ -269,7 +269,9 @@ void DiagramTextItem::setHtml(const QString &text)
|
||||
block = block.next();
|
||||
}
|
||||
|
||||
if (m_non_left_alignment) {
|
||||
//Room for centred or right-aligned lines, unless the user has
|
||||
//given this text a width of its own
|
||||
if (m_non_left_alignment && !hasUserTextWidth()) {
|
||||
document()->setTextWidth(document()->idealWidth() + 40.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,8 @@ class DiagramTextItem : public QGraphicsTextItem
|
||||
void prepareAlignment();
|
||||
void finishAlignment();
|
||||
|
||||
virtual bool hasUserTextWidth() const {return false;}
|
||||
|
||||
|
||||
protected:
|
||||
bool
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
#include "../utils/qetutils.h"
|
||||
|
||||
#include <QDomElement>
|
||||
#include <QScopedPointer>
|
||||
#include <QSettings>
|
||||
#include <QtCore/qnumeric.h>
|
||||
|
||||
/**
|
||||
Constructeur
|
||||
@@ -36,6 +38,7 @@ IndependentTextItem::IndependentTextItem() :
|
||||
DiagramTextItem(nullptr)
|
||||
{
|
||||
ShownKinds::tag(this, ShownKinds::FreeTexts);
|
||||
wrapAtWords();
|
||||
//Start from the font last applied to a text item this session,
|
||||
//falling back to the app-wide Preferences default otherwise.
|
||||
setFont(LastUsedStyle::hasTextFont() ? LastUsedStyle::textFont()
|
||||
@@ -53,6 +56,19 @@ IndependentTextItem::IndependentTextItem(const QString &text) :
|
||||
DiagramTextItem(text, nullptr)
|
||||
{
|
||||
ShownKinds::tag(this, ShownKinds::FreeTexts);
|
||||
wrapAtWords();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief IndependentTextItem::wrapAtWords
|
||||
A text with a width wraps between words only: a word longer than the
|
||||
width goes past it rather than being cut, as for the texts of symbols.
|
||||
*/
|
||||
void IndependentTextItem::wrapAtWords()
|
||||
{
|
||||
QTextOption option = document()->defaultTextOption();
|
||||
option.setWrapMode(QTextOption::WordWrap);
|
||||
document()->setDefaultTextOption(option);
|
||||
}
|
||||
|
||||
/// Destructeur
|
||||
@@ -78,6 +94,10 @@ void IndependentTextItem::fromXml(const QDomElement &e) {
|
||||
QETUtils::fontFromString(font, e.attribute("font"));
|
||||
setFont(font);
|
||||
}
|
||||
//Optional: absent for a text with the automatic width, the only
|
||||
//kind older versions know (they show such a text unwrapped).
|
||||
//Read after the text, setHtml() sets a width of its own.
|
||||
setTextWidth(e.attribute(QStringLiteral("text_width"), QStringLiteral("-1")).toDouble());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,10 +113,41 @@ QDomElement IndependentTextItem::toXml(QDomDocument &document) const
|
||||
result.setAttribute("text", toHtml());
|
||||
result.setAttribute("rotation", QString::number(QET::correctAngle(rotation())));
|
||||
result.setAttribute("font", QETUtils::fontToString(font()));
|
||||
//Only when set, so a text with the automatic width is saved as before
|
||||
if (m_text_width > 0)
|
||||
result.setAttribute("text_width", QString::number(m_text_width));
|
||||
|
||||
return(result);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief IndependentTextItem::setTextWidth
|
||||
Set the width of this text (-1 = automatic width): the text wraps to
|
||||
it, its top-left corner stays in place.
|
||||
@param width
|
||||
*/
|
||||
void IndependentTextItem::setTextWidth(qreal width)
|
||||
{
|
||||
if (!qIsFinite(width) || width <= 0)
|
||||
width = -1;
|
||||
if (qFuzzyCompare(width, m_text_width))
|
||||
return;
|
||||
|
||||
qreal document_width = width;
|
||||
//The automatic width of a text with centred or right-aligned
|
||||
//lines, as setHtml() gives it
|
||||
if (width < 0 && m_non_left_alignment)
|
||||
{
|
||||
QScopedPointer<QTextDocument> natural(document()->clone());
|
||||
natural->setTextWidth(-1);
|
||||
document_width = natural->idealWidth() + 40.0;
|
||||
}
|
||||
|
||||
document()->setTextWidth(document_width);
|
||||
m_text_width = width;
|
||||
emit textWidthChanged(width);
|
||||
}
|
||||
|
||||
void IndependentTextItem::focusOutEvent(QFocusEvent *event)
|
||||
{
|
||||
DiagramTextItem::focusOutEvent(event);
|
||||
|
||||
@@ -29,10 +29,13 @@
|
||||
class IndependentTextItem : public DiagramTextItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(qreal textWidth READ textWidth WRITE setTextWidth NOTIFY textWidthChanged)
|
||||
|
||||
// constructors, destructor
|
||||
signals:
|
||||
void uuidChanged();
|
||||
void textWidthChanged(qreal width);
|
||||
|
||||
public:
|
||||
IndependentTextItem();
|
||||
@@ -49,11 +52,23 @@ class IndependentTextItem : public DiagramTextItem
|
||||
QUuid uuid() const {return m_uuid;}
|
||||
void setUuid(const QUuid &uuid) {m_uuid = uuid; emit uuidChanged();}
|
||||
void newUuid() {setUuid(QUuid::createUuid());} //create new uuid for this item
|
||||
|
||||
//Hide QGraphicsTextItem::textWidth()/setTextWidth(), which are not
|
||||
//virtual: called through a QGraphicsTextItem or DiagramTextItem
|
||||
//pointer they would change the document only, and the width would
|
||||
//be neither saved nor shown in the properties. Use these, or the
|
||||
//"textWidth" property.
|
||||
qreal textWidth() const {return m_text_width;}
|
||||
void setTextWidth(qreal width);
|
||||
|
||||
protected:
|
||||
void focusOutEvent(QFocusEvent *event) override;
|
||||
bool hasUserTextWidth() const override {return m_text_width > 0;}
|
||||
|
||||
private:
|
||||
void wrapAtWords();
|
||||
|
||||
QUuid m_uuid = QUuid::createUuid();
|
||||
qreal m_text_width = -1;
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -69,6 +69,7 @@ void QETMainWindow::initCommonActions()
|
||||
QETApp *qet_app = QETApp::instance();
|
||||
|
||||
configure_action_ = new QAction(QET::Icons::Configure, tr("&Configurer QElectroTech"), this);
|
||||
ShortcutManager::instance().registerAction(configure_action_, "mainwindow.configure", tr("Général"), QKeySequence());
|
||||
configure_action_ -> setStatusTip(tr("Permet de régler différents paramètres de QElectroTech", "status bar tip"));
|
||||
connect(configure_action_, &QAction::triggered, [qet_app]()
|
||||
{
|
||||
@@ -107,8 +108,10 @@ void QETMainWindow::initCommonActions()
|
||||
connect(fullscreen_action_, &QAction::triggered, this, &QETMainWindow::toggleFullScreen);
|
||||
|
||||
whatsthis_action_ = QWhatsThis::createAction(this);
|
||||
ShortcutManager::instance().registerAction(whatsthis_action_, "mainwindow.whats_this", tr("Général"), Qt::SHIFT | Qt::Key_F1);
|
||||
|
||||
about_qet_ = new QAction(QET::Icons::QETLogo, tr("À &propos de QElectroTech"), this);
|
||||
ShortcutManager::instance().registerAction(about_qet_, "mainwindow.about_qet", tr("Général"), QKeySequence());
|
||||
about_qet_ -> setStatusTip(tr("Affiche des informations sur QElectroTech", "status bar tip"));
|
||||
connect(about_qet_, &QAction::triggered, qet_app, &QETApp::aboutQET);
|
||||
|
||||
@@ -123,6 +126,7 @@ void QETMainWindow::initCommonActions()
|
||||
ShortcutManager::instance().registerAction(manual_online_, "mainwindow.manual_online", tr("Général"), Qt::Key_F1);
|
||||
|
||||
connect_ai_ = new QAction(tr("Connecter un assistant IA..."), this);
|
||||
ShortcutManager::instance().registerAction(connect_ai_, "mainwindow.connect_ai", tr("Général"), QKeySequence());
|
||||
connect_ai_ -> setStatusTip(tr("Affiche la configuration qui permet à un assistant IA d'utiliser QElectroTech", "status bar tip"));
|
||||
connect(connect_ai_, &QAction::triggered, this, [this]() {
|
||||
AiAssistantDialog dialog(this);
|
||||
@@ -130,6 +134,7 @@ void QETMainWindow::initCommonActions()
|
||||
});
|
||||
|
||||
youtube_ = new QAction(QET::Icons::QETVideo, tr("Chaine Youtube"), this);
|
||||
ShortcutManager::instance().registerAction(youtube_, "mainwindow.youtube", tr("Général"), QKeySequence());
|
||||
youtube_ -> setStatusTip(tr("Lance le navigateur par défaut vers la chaine Youtube de QElectroTech", "status bar tip"));
|
||||
|
||||
connect(youtube_, &QAction::triggered, [](bool) {
|
||||
@@ -138,9 +143,11 @@ void QETMainWindow::initCommonActions()
|
||||
});
|
||||
|
||||
upgrade_ = new QAction(QET::Icons::QETDownload, tr("Télécharger une nouvelle version (dev)"), this);
|
||||
ShortcutManager::instance().registerAction(upgrade_, "mainwindow.download_windows", tr("Général"), QKeySequence());
|
||||
upgrade_ -> setStatusTip(tr("Lance le navigateur par défaut vers le dépot Nightly en ligne de QElectroTech", "status bar tip"));
|
||||
|
||||
upgrade_M = new QAction(QET::Icons::QETDownload, tr("Télécharger une nouvelle version (dev)"), this);
|
||||
ShortcutManager::instance().registerAction(upgrade_M, "mainwindow.download_mac", tr("Général"), QKeySequence());
|
||||
upgrade_M -> setStatusTip(tr("Lance le navigateur par défaut vers le dépot Nightly en ligne de QElectroTech", "status bar tip"));
|
||||
|
||||
connect(upgrade_, &QAction::triggered, [](bool) {
|
||||
@@ -154,6 +161,7 @@ void QETMainWindow::initCommonActions()
|
||||
});
|
||||
|
||||
donate_ = new QAction(QET::Icons::QETDonate, tr("Soutenir le projet par un don"), this);
|
||||
ShortcutManager::instance().registerAction(donate_, "mainwindow.donate", tr("Général"), QKeySequence());
|
||||
donate_ -> setStatusTip(tr("Soutenir le projet QElectroTech par un don", "status bar tip"));
|
||||
|
||||
connect(donate_, &QAction::triggered, [](bool) {
|
||||
@@ -162,10 +170,12 @@ void QETMainWindow::initCommonActions()
|
||||
});
|
||||
|
||||
about_qt_ = new QAction(QET::Icons::QtLogo, tr("À propos de &Qt"), this);
|
||||
ShortcutManager::instance().registerAction(about_qt_, "mainwindow.about_qt", tr("Général"), QKeySequence());
|
||||
about_qt_ -> setStatusTip(tr("Affiche des informations sur la bibliothèque Qt", "status bar tip"));
|
||||
connect(about_qt_, &QAction::triggered, qApp, &QApplication::aboutQt);
|
||||
|
||||
diagnostics_action_ = new QAction(QET::Icons::DialogInformation, tr("Enregistrer un rapport de diagnostic..."), this);
|
||||
ShortcutManager::instance().registerAction(diagnostics_action_, "mainwindow.diagnostics_report", tr("Général"), QKeySequence());
|
||||
diagnostics_action_ -> setStatusTip(tr("Génère un rapport avec les derniers messages de journalisation, pour l'inclure dans un rapport de bug", "status bar tip"));
|
||||
connect(diagnostics_action_, &QAction::triggered, this, []() {
|
||||
QETApp::instance()->showDiagnosticsReport();
|
||||
|
||||
@@ -858,7 +858,9 @@ QJsonObject LiveServer::print(const QJsonObject &request)
|
||||
if (fi.exists()) return failure(QStringLiteral("%1 already exists; printing never replaces a file").arg(file));
|
||||
}
|
||||
|
||||
QPrinter printer = file.isEmpty() ? QPrinter(info) : QPrinter();
|
||||
//info is still null when printing to a file, which is what QPrinter()
|
||||
//uses. No ?: here: MSVC copies its result, and QPrinter cannot be copied
|
||||
QPrinter printer(info);
|
||||
//96 dpi, as the PDF export draws: symbols are replayed at the
|
||||
//device's resolution, so at a printer's 600-1200 dpi they came out
|
||||
//many times too big next to the wires (seen on a test print)
|
||||
|
||||
@@ -3475,6 +3475,44 @@ bool QetScriptApi::setImageRotation(int folioIndex, int imageIndex, double angle
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QetScriptApi::cropImage
|
||||
Show only the rectangle (x, y, width, height) of the image's original,
|
||||
in the original's own pixels, as the crop tool does: one undo step,
|
||||
the kept region staying where it is on the folio.
|
||||
@return false if nothing was cropped: no such image, read-only
|
||||
project, or a rectangle that is empty, outside the original, or the
|
||||
current crop.
|
||||
*/
|
||||
bool QetScriptApi::cropImage(int folioIndex, int imageIndex, int x, int y, int width, int height)
|
||||
{
|
||||
if (!m_project) return false;
|
||||
if (m_project->isReadOnly()) {
|
||||
log(QStringLiteral("qet.cropImage: project is read-only"));
|
||||
return false;
|
||||
}
|
||||
const QList<DiagramImageItem *> list = sortedImages(folioIndex);
|
||||
if (imageIndex < 0 || imageIndex >= list.count()) {
|
||||
log(QStringLiteral("qet.cropImage: folio %1 has %2 image(s), no index %3")
|
||||
.arg(folioIndex).arg(list.count()).arg(imageIndex));
|
||||
return false;
|
||||
}
|
||||
return list.at(imageIndex)->applyCrop(QRect(x, y, width, height));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QetScriptApi::imageCrop
|
||||
@return the image's crop rectangle in its original's pixels, as
|
||||
"x,y,width,height", or an empty string for no such image.
|
||||
*/
|
||||
QString QetScriptApi::imageCrop(int folioIndex, int imageIndex) const
|
||||
{
|
||||
const QList<DiagramImageItem *> list = sortedImages(folioIndex);
|
||||
if (imageIndex < 0 || imageIndex >= list.count()) return QString();
|
||||
const QRect r = list.at(imageIndex)->cropRect();
|
||||
return QStringLiteral("%1,%2,%3,%4").arg(r.x()).arg(r.y()).arg(r.width()).arg(r.height());
|
||||
}
|
||||
|
||||
bool QetScriptApi::deleteImage(int folioIndex, int imageIndex)
|
||||
{
|
||||
if (!m_project) return false;
|
||||
|
||||
@@ -540,6 +540,8 @@ class QetScriptApi : public QObject
|
||||
Q_INVOKABLE int addImage(int folioIndex, const QString &filePath, double x, double y);
|
||||
Q_INVOKABLE bool setImageScale(int folioIndex, int imageIndex, double factor);
|
||||
Q_INVOKABLE bool setImageRotation(int folioIndex, int imageIndex, double angle);
|
||||
Q_INVOKABLE bool cropImage(int folioIndex, int imageIndex, int x, int y, int width, int height);
|
||||
Q_INVOKABLE QString imageCrop(int folioIndex, int imageIndex) const;
|
||||
Q_INVOKABLE bool deleteImage(int folioIndex, int imageIndex);
|
||||
Q_INVOKABLE int addPdfPage(int folioIndex, const QString &pdfPath, int pageNumber,
|
||||
int dpi, double x, double y);
|
||||
|
||||
@@ -137,6 +137,13 @@ QList<ShortcutManager::ShortcutInfo> ShortcutManager::allShortcuts() const
|
||||
info.description = entry.description;
|
||||
info.default_sequence = entry.default_sequence;
|
||||
info.current_sequence = savedSequence(id, entry.default_sequence);
|
||||
for (const QPointer<QObject> &target : entry.targets) {
|
||||
if (target) {
|
||||
info.icon = target->property("icon").value<QIcon>();
|
||||
info.action = qobject_cast<QAction *>(target.data());
|
||||
break;
|
||||
}
|
||||
}
|
||||
list << info;
|
||||
}
|
||||
return list;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#define SHORTCUTMANAGER_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QIcon>
|
||||
#include <QKeySequence>
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
@@ -60,6 +61,10 @@ class ShortcutManager
|
||||
QString description;
|
||||
QKeySequence default_sequence;
|
||||
QKeySequence current_sequence;
|
||||
/// The command's icon, from its first live target; may be null
|
||||
QIcon icon;
|
||||
/// The first live target if it is a QAction, else nullptr
|
||||
QAction *action = nullptr;
|
||||
};
|
||||
|
||||
void registerAction(QObject *target, const QString &id,
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
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 TEXTLINES_H
|
||||
#define TEXTLINES_H
|
||||
|
||||
#include <QStringList>
|
||||
#include <QTextBlock>
|
||||
#include <QTextDocument>
|
||||
#include <QTextLayout>
|
||||
|
||||
namespace TextLines
|
||||
{
|
||||
/**
|
||||
@return the lines of document as they are laid out: a paragraph
|
||||
wrapped to the width of the text gives several lines. For the
|
||||
exports that write a text line by line (DXF).
|
||||
A paragraph that is not wrapped gives exactly its line of
|
||||
toPlainText().split('\n'), as these exports wrote before: trailing
|
||||
spaces are kept, and non-breaking spaces become plain spaces. In a
|
||||
wrapped paragraph, the lines lose their trailing spaces (the one a
|
||||
line was broken at, and a line made only of spaces becomes empty).
|
||||
*/
|
||||
inline QStringList layoutLines(const QTextDocument *document)
|
||||
{
|
||||
document->size(); //Lay the document out
|
||||
|
||||
QStringList lines;
|
||||
for (QTextBlock block = document->begin() ; block.isValid() ; block = block.next())
|
||||
{
|
||||
const QString text = block.text();
|
||||
const QTextLayout *layout = block.layout();
|
||||
const int count = layout ? layout->lineCount() : 0;
|
||||
if (count == 0) {
|
||||
lines << QString(text).replace(QChar::Nbsp, QLatin1Char(' '));
|
||||
continue;
|
||||
}
|
||||
|
||||
//Wrapped: a line ends without a line break typed by the user
|
||||
bool wrapped = false;
|
||||
for (int i = 0 ; i < count - 1 ; ++i) {
|
||||
const QTextLine line = layout->lineAt(i);
|
||||
const int end = line.textStart() + line.textLength();
|
||||
if (end == 0 || text.at(end - 1) != QChar::LineSeparator)
|
||||
wrapped = true;
|
||||
}
|
||||
|
||||
for (int i = 0 ; i < count ; ++i)
|
||||
{
|
||||
const QTextLine line = layout->lineAt(i);
|
||||
QString part = text.mid(line.textStart(), line.textLength());
|
||||
if (part.endsWith(QChar::LineSeparator))
|
||||
part.chop(1); //A line break typed by the user (Shift+Enter)
|
||||
if (wrapped) {
|
||||
while (!part.isEmpty() && part.back().isSpace())
|
||||
part.chop(1);
|
||||
}
|
||||
lines << part.replace(QChar::Nbsp, QLatin1Char(' '));
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // TEXTLINES_H
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
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 TEXTRESIZE_H
|
||||
#define TEXTRESIZE_H
|
||||
|
||||
#include <QGraphicsItem>
|
||||
#include <QScopedPointer>
|
||||
#include <QTextDocument>
|
||||
#include <QTextOption>
|
||||
#include <QTransform>
|
||||
|
||||
/**
|
||||
The geometry of changing the width of a text box by dragging one of
|
||||
its corners: the text wraps to the new width, the opposite corner
|
||||
stays where it is drawn.
|
||||
*/
|
||||
namespace TextResize
|
||||
{
|
||||
/**
|
||||
A corner of the box of a text, in the order of the resize handles.
|
||||
*/
|
||||
enum Corner {
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomRight,
|
||||
BottomLeft
|
||||
};
|
||||
|
||||
inline Corner opposite(Corner corner) {
|
||||
return Corner((int(corner) + 2) % 4);
|
||||
}
|
||||
|
||||
inline bool isLeft(Corner corner) {
|
||||
return corner == TopLeft || corner == BottomLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
@return the corner of rect, in the coordinates of rect.
|
||||
*/
|
||||
inline QPointF cornerOf(const QRectF &rect, Corner corner)
|
||||
{
|
||||
switch (corner) {
|
||||
case TopLeft: return rect.topLeft();
|
||||
case TopRight: return rect.topRight();
|
||||
case BottomRight: return rect.bottomRight();
|
||||
case BottomLeft: return rect.bottomLeft();
|
||||
}
|
||||
return rect.topLeft();
|
||||
}
|
||||
|
||||
/**
|
||||
@return the smallest width that document can be given: the width
|
||||
of its longest word, which is never broken, plus the margins.
|
||||
*/
|
||||
inline qreal minimumWidth(const QTextDocument *document)
|
||||
{
|
||||
QScopedPointer<QTextDocument> narrow(document->clone());
|
||||
QTextOption option = narrow->defaultTextOption();
|
||||
option.setWrapMode(QTextOption::WordWrap);
|
||||
narrow->setDefaultTextOption(option);
|
||||
narrow->setTextWidth(0);
|
||||
return narrow->size().width();
|
||||
}
|
||||
|
||||
/**
|
||||
@return the width given by dragging the corner dragged of a text
|
||||
box to mouse_scene, while the opposite corner stays at fixed_local.
|
||||
@param press_inverse : the inverse of the scene transform of the
|
||||
text when the drag started. Moving the text to keep the opposite
|
||||
corner in place changes its transform, using the transform of the
|
||||
press keeps that move from feeding back into the width.
|
||||
@param fixed_local : the opposite corner, in the coordinates of the
|
||||
text when the drag started.
|
||||
@param min_width : see minimumWidth().
|
||||
Vertical movements of the mouse are ignored: the height of the box
|
||||
follows the wrapped text.
|
||||
*/
|
||||
inline qreal widthForDrag(const QTransform &press_inverse,
|
||||
const QPointF &fixed_local,
|
||||
const QPointF &mouse_scene,
|
||||
Corner dragged,
|
||||
qreal min_width)
|
||||
{
|
||||
const QPointF mouse = press_inverse.map(mouse_scene);
|
||||
const qreal width = isLeft(dragged) ? fixed_local.x() - mouse.x()
|
||||
: mouse.x() - fixed_local.x();
|
||||
return qMax(width, min_width);
|
||||
}
|
||||
|
||||
/**
|
||||
Move item so that its corner corner is at scene_point, in scene
|
||||
coordinates. Right for any rotation, rotation point and alignment.
|
||||
*/
|
||||
inline void pinCorner(QGraphicsItem *item, Corner corner, const QPointF &scene_point)
|
||||
{
|
||||
const QPointF now = item->mapToScene(cornerOf(item->boundingRect(), corner));
|
||||
QPointF delta = scene_point - now;
|
||||
if (const QGraphicsItem *parent = item->parentItem())
|
||||
delta = parent->mapFromScene(scene_point) - parent->mapFromScene(now);
|
||||
item->setPos(item->pos() + delta);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // TEXTRESIZE_H
|
||||
@@ -20,6 +20,9 @@
|
||||
#include "../../qeticons.h"
|
||||
#include "../../shortcutmanager.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QComboBox>
|
||||
#include <QFrame>
|
||||
#include <QHBoxLayout>
|
||||
@@ -27,6 +30,8 @@
|
||||
#include <QKeySequenceEdit>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QMenuBar>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QToolButton>
|
||||
@@ -43,6 +48,10 @@
|
||||
this, so "general" matches "Général" and "Ctrl+S" matches "ctrl+s"
|
||||
regardless of the keyboard layout the query was typed on.
|
||||
*/
|
||||
namespace {
|
||||
enum Column { ActionColumn, MenuColumn, SequenceColumn, ResetColumn };
|
||||
}
|
||||
|
||||
static QString normalizedForSearch(const QString &text)
|
||||
{
|
||||
const QString decomposed = text.normalized(QString::NormalizationForm_D);
|
||||
@@ -85,20 +94,51 @@ ShortcutsConfigPage::ShortcutsConfigPage(QWidget *parent) :
|
||||
connect(m_quick_filter, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
this, &ShortcutsConfigPage::quickFilterChanged);
|
||||
|
||||
m_category_filter = new QComboBox(this);
|
||||
m_category_filter->setObjectName(QStringLiteral("categoryFilterCombo"));
|
||||
m_category_filter->addItem(tr("Toutes les catégories"));
|
||||
connect(m_category_filter, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
this, &ShortcutsConfigPage::quickFilterChanged);
|
||||
|
||||
m_count_label = new QLabel(this);
|
||||
m_count_label->setObjectName(QStringLiteral("shortcutCountLabel"));
|
||||
|
||||
auto *filter_layout = new QHBoxLayout();
|
||||
filter_layout->addWidget(m_filter_edit, 1);
|
||||
filter_layout->addWidget(m_category_filter);
|
||||
filter_layout->addWidget(m_quick_filter);
|
||||
filter_layout->addWidget(m_count_label);
|
||||
vlayout->addLayout(filter_layout);
|
||||
|
||||
//Press a key combination to list what uses it, without having to
|
||||
//know how QElectroTech spells it ("Ctrl+Maj+S", "Ctrl+Shift+S"…)
|
||||
m_key_search = new QKeySequenceEdit(this);
|
||||
m_key_search->setObjectName(QStringLiteral("keySearchEdit"));
|
||||
m_key_search->setToolTip(tr("Appuyez sur une combinaison de touches pour voir quelle commande l'utilise"));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
m_key_search->setMaximumSequenceLength(1);
|
||||
#endif
|
||||
connect(m_key_search, &QKeySequenceEdit::keySequenceChanged, this, [this]() { applyFilter(); });
|
||||
|
||||
auto *clear_key_button = new QToolButton(this);
|
||||
clear_key_button->setIcon(QET::Icons::EditClear);
|
||||
clear_key_button->setToolTip(tr("Effacer la touche recherchée"));
|
||||
clear_key_button->setAutoRaise(true);
|
||||
connect(clear_key_button, &QToolButton::clicked, m_key_search, &QKeySequenceEdit::clear);
|
||||
|
||||
auto *key_layout = new QHBoxLayout();
|
||||
key_layout->addWidget(new QLabel(tr("Rechercher par touche :"), this));
|
||||
key_layout->addWidget(m_key_search);
|
||||
key_layout->addWidget(clear_key_button);
|
||||
key_layout->addStretch();
|
||||
vlayout->addLayout(key_layout);
|
||||
|
||||
m_tree = new QTreeWidget(this);
|
||||
m_tree->setHeaderLabels({tr("Action"), tr("Raccourci"), QString()});
|
||||
m_tree->header()->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
m_tree->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
|
||||
m_tree->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
|
||||
m_tree->setHeaderLabels({tr("Action"), tr("Menu"), tr("Raccourci"), QString()});
|
||||
m_tree->header()->setSectionResizeMode(ActionColumn, QHeaderView::Stretch);
|
||||
m_tree->header()->setSectionResizeMode(MenuColumn, QHeaderView::ResizeToContents);
|
||||
m_tree->header()->setSectionResizeMode(SequenceColumn, QHeaderView::ResizeToContents);
|
||||
m_tree->header()->setSectionResizeMode(ResetColumn, QHeaderView::ResizeToContents);
|
||||
m_tree->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
m_tree->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
vlayout->addWidget(m_tree);
|
||||
@@ -106,7 +146,12 @@ ShortcutsConfigPage::ShortcutsConfigPage(QWidget *parent) :
|
||||
auto *reset_all_button = new QPushButton(tr("Tout réinitialiser"), this);
|
||||
connect(reset_all_button, &QPushButton::clicked, this, &ShortcutsConfigPage::resetAllRows);
|
||||
|
||||
auto *copy_button = new QPushButton(QET::Icons::EditCopy, tr("Copier la liste"), this);
|
||||
copy_button->setToolTip(tr("Copie les raccourcis affichés, à coller dans un tableur ou un document"));
|
||||
connect(copy_button, &QPushButton::clicked, this, &ShortcutsConfigPage::copyList);
|
||||
|
||||
auto *bottom_layout = new QHBoxLayout();
|
||||
bottom_layout->addWidget(copy_button);
|
||||
bottom_layout->addStretch();
|
||||
bottom_layout->addWidget(reset_all_button);
|
||||
vlayout->addLayout(bottom_layout);
|
||||
@@ -139,6 +184,9 @@ void ShortcutsConfigPage::populateTable()
|
||||
|
||||
m_tree->clear();
|
||||
m_rows.clear();
|
||||
while (m_category_filter->count() > 1) {
|
||||
m_category_filter->removeItem(1);
|
||||
}
|
||||
m_rows.reserve(shortcuts.size());
|
||||
|
||||
QHash<QString, QTreeWidgetItem *> category_nodes;
|
||||
@@ -147,18 +195,22 @@ void ShortcutsConfigPage::populateTable()
|
||||
QTreeWidgetItem *category_item = category_nodes.value(info.category, nullptr);
|
||||
if (!category_item) {
|
||||
category_item = new QTreeWidgetItem(m_tree);
|
||||
category_item->setText(0, info.category);
|
||||
category_item->setText(ActionColumn, info.category);
|
||||
category_item->setFlags(category_item->flags() & ~Qt::ItemIsEditable);
|
||||
category_nodes.insert(info.category, category_item);
|
||||
m_category_filter->addItem(info.category);
|
||||
}
|
||||
|
||||
auto *child = new QTreeWidgetItem(category_item);
|
||||
child->setText(0, info.description);
|
||||
const QString menu_path = menuPath(info.action);
|
||||
child->setText(ActionColumn, info.description);
|
||||
child->setIcon(ActionColumn, info.icon);
|
||||
child->setText(MenuColumn, menu_path);
|
||||
child->setFlags(child->flags() & ~Qt::ItemIsEditable);
|
||||
|
||||
auto *edit = new QKeySequenceEdit(info.current_sequence, m_tree);
|
||||
connect(edit, &QKeySequenceEdit::editingFinished, this, &ShortcutsConfigPage::checkConflicts);
|
||||
m_tree->setItemWidget(child, 1, edit);
|
||||
m_tree->setItemWidget(child, SequenceColumn, edit);
|
||||
|
||||
auto *reset_button = new QToolButton(m_tree);
|
||||
reset_button->setIcon(QET::Icons::EditUndo);
|
||||
@@ -166,9 +218,10 @@ void ShortcutsConfigPage::populateTable()
|
||||
reset_button->setAutoRaise(true);
|
||||
const int row_index = m_rows.size();
|
||||
connect(reset_button, &QToolButton::clicked, this, [this, row_index]() { resetRow(row_index); });
|
||||
m_tree->setItemWidget(child, 2, reset_button);
|
||||
m_tree->setItemWidget(child, ResetColumn, reset_button);
|
||||
|
||||
m_rows << Row{info.id, info.category, info.description, info.default_sequence, edit, child, false};
|
||||
m_rows << Row{info.id, info.category, info.description, menu_path,
|
||||
info.default_sequence, edit, child, false};
|
||||
}
|
||||
|
||||
checkConflicts();
|
||||
@@ -210,6 +263,9 @@ void ShortcutsConfigPage::applyFilter()
|
||||
: needle.split(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts);
|
||||
|
||||
const int quick_filter = m_quick_filter->currentIndex();
|
||||
const QString category = m_category_filter->currentIndex() > 0
|
||||
? m_category_filter->currentText() : QString();
|
||||
const QKeySequence key = m_key_search->keySequence();
|
||||
|
||||
int visible_actions = 0;
|
||||
for (const Row &row : std::as_const(m_rows)) {
|
||||
@@ -218,7 +274,8 @@ void ShortcutsConfigPage::applyFilter()
|
||||
// of "Shift"), which is precisely the kind of false positive that hides
|
||||
// the one binding the user is looking for.
|
||||
const QString text_haystack = normalizedForSearch(
|
||||
row.category + QLatin1Char(' ') + row.description);
|
||||
row.category + QLatin1Char(' ') + row.description
|
||||
+ QLatin1Char(' ') + row.menu_path);
|
||||
const QString sequence_text = normalizedForSearch(row.edit->keySequence().toString());
|
||||
|
||||
bool matches = true;
|
||||
@@ -230,6 +287,16 @@ void ShortcutsConfigPage::applyFilter()
|
||||
}
|
||||
}
|
||||
|
||||
if (matches && !category.isEmpty()) {
|
||||
matches = row.category == category;
|
||||
}
|
||||
if (matches && !key.isEmpty()) {
|
||||
//A pressed key also finds the sequences it starts, such
|
||||
//as a two-key shortcut whose first key it is
|
||||
const QKeySequence sequence = row.edit->keySequence();
|
||||
matches = !sequence.isEmpty() && key.matches(sequence) != QKeySequence::NoMatch;
|
||||
}
|
||||
|
||||
if (matches) {
|
||||
switch (quick_filter) {
|
||||
case BoundOnly:
|
||||
@@ -252,7 +319,8 @@ void ShortcutsConfigPage::applyFilter()
|
||||
}
|
||||
}
|
||||
|
||||
const bool filtering = !needle.isEmpty() || quick_filter != ShowAll;
|
||||
const bool filtering = !needle.isEmpty() || quick_filter != ShowAll
|
||||
|| !category.isEmpty() || !key.isEmpty();
|
||||
for (int i = 0; i < m_tree->topLevelItemCount(); ++i) {
|
||||
QTreeWidgetItem *top = m_tree->topLevelItem(i);
|
||||
bool any_visible = false;
|
||||
@@ -322,11 +390,11 @@ void ShortcutsConfigPage::checkConflicts()
|
||||
other_descriptions << m_rows.at(other_row).description;
|
||||
}
|
||||
}
|
||||
current_row.item->setBackground(0, QColor(255, 205, 205));
|
||||
current_row.item->setBackground(ActionColumn, QColor(255, 205, 205));
|
||||
current_row.edit->setToolTip(
|
||||
tr("Ce raccourci est aussi utilisé par : %1").arg(other_descriptions.join(QStringLiteral(", "))));
|
||||
} else {
|
||||
current_row.item->setBackground(0, QBrush());
|
||||
current_row.item->setBackground(ActionColumn, QBrush());
|
||||
current_row.edit->setToolTip(QString());
|
||||
}
|
||||
}
|
||||
@@ -335,7 +403,9 @@ void ShortcutsConfigPage::checkConflicts()
|
||||
// conflicts-only quick filter); refresh the visible set so the list doesn't
|
||||
// show stale results.
|
||||
const bool filtering = !m_filter_edit->text().trimmed().isEmpty()
|
||||
|| m_quick_filter->currentIndex() != ShowAll;
|
||||
|| m_quick_filter->currentIndex() != ShowAll
|
||||
|| m_category_filter->currentIndex() > 0
|
||||
|| !m_key_search->keySequence().isEmpty();
|
||||
if (filtering) {
|
||||
applyFilter();
|
||||
}
|
||||
@@ -374,6 +444,71 @@ void ShortcutsConfigPage::applyConf()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ShortcutsConfigPage::menuPath
|
||||
@param action
|
||||
@return where \a action is in the menu bar, such as "Projet › Scripts",
|
||||
or an empty string for a command that is in no menu of the menu bar
|
||||
(a toolbar or shortcut bar only command, or one with no live action)
|
||||
*/
|
||||
QString ShortcutsConfigPage::menuPath(const QAction *action)
|
||||
{
|
||||
if (!action) {
|
||||
return QString();
|
||||
}
|
||||
for (QObject *object : action->associatedObjects()) {
|
||||
QStringList titles;
|
||||
for (auto *menu = qobject_cast<QMenu *>(object); menu; ) {
|
||||
titles.prepend(menu->title().remove(QLatin1Char('&')));
|
||||
QMenu *parent_menu = nullptr;
|
||||
bool in_menu_bar = false;
|
||||
for (QObject *owner : menu->menuAction()->associatedObjects()) {
|
||||
if (qobject_cast<QMenuBar *>(owner)) {
|
||||
in_menu_bar = true;
|
||||
} else if (!parent_menu) {
|
||||
parent_menu = qobject_cast<QMenu *>(owner);
|
||||
}
|
||||
}
|
||||
if (in_menu_bar) {
|
||||
return titles.join(QStringLiteral(" › "));
|
||||
}
|
||||
menu = parent_menu;
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ShortcutsConfigPage::listAsText
|
||||
@return the shortcuts shown, as the edits currently hold them, one per
|
||||
line with tab-separated columns and a header line: pasted into a
|
||||
spreadsheet it fills one cell per column.
|
||||
*/
|
||||
QString ShortcutsConfigPage::listAsText() const
|
||||
{
|
||||
QStringList lines;
|
||||
lines << QStringList{tr("Catégorie"), tr("Menu"), tr("Action"), tr("Raccourci")}
|
||||
.join(QLatin1Char('\t'));
|
||||
for (const Row &row : std::as_const(m_rows)) {
|
||||
if (row.item->isHidden()) {
|
||||
continue;
|
||||
}
|
||||
lines << QStringList{row.category, row.menu_path, row.description,
|
||||
row.edit->keySequence().toString(QKeySequence::NativeText)}
|
||||
.join(QLatin1Char('\t'));
|
||||
}
|
||||
return lines.join(QLatin1Char('\n')) + QLatin1Char('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ShortcutsConfigPage::copyList
|
||||
Put listAsText() on the clipboard.
|
||||
*/
|
||||
void ShortcutsConfigPage::copyList()
|
||||
{
|
||||
QApplication::clipboard()->setText(listAsText());
|
||||
}
|
||||
|
||||
QString ShortcutsConfigPage::title() const
|
||||
{
|
||||
return tr("Raccourcis", "configuration page title");
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <QKeySequence>
|
||||
|
||||
class QAction;
|
||||
class QComboBox;
|
||||
class QKeySequenceEdit;
|
||||
class QLabel;
|
||||
@@ -48,11 +49,15 @@ class ShortcutsConfigPage : public ConfigPage
|
||||
QString title() const override;
|
||||
QIcon icon() const override;
|
||||
|
||||
static QString menuPath(const QAction *action);
|
||||
QString listAsText() const;
|
||||
|
||||
private slots:
|
||||
void filterRows(const QString &filter_text);
|
||||
void quickFilterChanged(int index);
|
||||
void checkConflicts();
|
||||
void resetAllRows();
|
||||
void copyList();
|
||||
|
||||
private:
|
||||
enum QuickFilter {
|
||||
@@ -66,6 +71,7 @@ class ShortcutsConfigPage : public ConfigPage
|
||||
QString id;
|
||||
QString category;
|
||||
QString description;
|
||||
QString menu_path;
|
||||
QKeySequence default_sequence;
|
||||
QKeySequenceEdit *edit;
|
||||
QTreeWidgetItem *item;
|
||||
@@ -78,6 +84,8 @@ class ShortcutsConfigPage : public ConfigPage
|
||||
|
||||
QLineEdit *m_filter_edit;
|
||||
QComboBox *m_quick_filter;
|
||||
QComboBox *m_category_filter;
|
||||
QKeySequenceEdit *m_key_search;
|
||||
QLabel *m_count_label;
|
||||
QTreeWidget *m_tree;
|
||||
QList<Row> m_rows;
|
||||
|
||||
Reference in New Issue
Block a user