mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-13 08:14:13 +02:00
Merge master into stage 2 (#1042) as the base of the stage PRs
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
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 BORDERCELLLABELS_H
|
||||
#define BORDERCELLLABELS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
/// The labels the border of a folio writes on its rows and columns, shared
|
||||
/// by BorderTitleBlock::draw() and the cell rulers of DiagramView so the
|
||||
/// two cannot disagree. Header-only so it can be unit-tested directly --
|
||||
/// see tests/qttest/tst_bordercelllabels.cpp.
|
||||
namespace BorderCellLabels {
|
||||
|
||||
/// @return the label of row \a row, counted from 1: A..Z, then AA, AB...
|
||||
/// (the sequence BorderTitleBlock::incrementLetters() walks through).
|
||||
inline QString rowLabel(int row)
|
||||
{
|
||||
QString label;
|
||||
while (row > 0) {
|
||||
--row;
|
||||
label.prepend(QChar('A' + row % 26));
|
||||
row /= 26;
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
/// @return the label of column \a column, counted from 1. When
|
||||
/// \a starts_at_zero (the "border-columns_0" setting) the first column
|
||||
/// is labelled 0.
|
||||
inline QString columnLabel(int column, bool starts_at_zero)
|
||||
{
|
||||
return QString::number(starts_at_zero ? column - 1 : column);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // BORDERCELLLABELS_H
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
#include "bordertitleblock.h"
|
||||
|
||||
#include "bordercelllabels.h"
|
||||
#include "createdxf.h"
|
||||
#include "diagram.h"
|
||||
#include "diagramposition.h"
|
||||
@@ -29,6 +30,7 @@
|
||||
|
||||
#include <QLocale>
|
||||
#include <QPainter>
|
||||
#include <QRegularExpression>
|
||||
#include <utility>
|
||||
|
||||
#define MIN_COLUMN_COUNT 3
|
||||
@@ -533,6 +535,8 @@ void BorderTitleBlock::draw(QPainter *painter)
|
||||
|
||||
//Draw the nums of columns
|
||||
if (display_border_ && display_columns_) {
|
||||
const bool columns_start_at_zero =
|
||||
settings.value("border-columns_0", true).toBool();
|
||||
for (int i = 1 ; i <= columns_count_ ; ++ i) {
|
||||
QRectF numbered_rectangle = QRectF(
|
||||
diagram_rect_.topLeft().x()
|
||||
@@ -543,23 +547,15 @@ void BorderTitleBlock::draw(QPainter *painter)
|
||||
columns_header_height_
|
||||
);
|
||||
painter -> drawRect(numbered_rectangle);
|
||||
if (settings.value("border-columns_0", true).toBool()){
|
||||
painter -> drawText(numbered_rectangle,
|
||||
Qt::AlignVCenter
|
||||
| Qt::AlignCenter,
|
||||
QString("%1").arg(i - 1));
|
||||
}else{
|
||||
painter -> drawText(numbered_rectangle,
|
||||
Qt::AlignVCenter
|
||||
| Qt::AlignCenter,
|
||||
QString("%1").arg(i));
|
||||
}
|
||||
BorderCellLabels::columnLabel(i, columns_start_at_zero));
|
||||
}
|
||||
}
|
||||
|
||||
//Draw the nums of rows
|
||||
if (display_border_ && display_rows_) {
|
||||
QString row_string("A");
|
||||
for (int i = 1 ; i <= rows_count_ ; ++ i) {
|
||||
QRectF lettered_rectangle = QRectF(
|
||||
diagram_rect_.topLeft().x(),
|
||||
@@ -575,8 +571,7 @@ void BorderTitleBlock::draw(QPainter *painter)
|
||||
painter -> drawText(lettered_rectangle,
|
||||
Qt::AlignVCenter
|
||||
| Qt::AlignCenter,
|
||||
row_string);
|
||||
row_string = incrementLetters(row_string);
|
||||
BorderCellLabels::rowLabel(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -935,6 +930,46 @@ void BorderTitleBlock::updateDiagramContextForTitleBlock(
|
||||
m_titleblock_template_renderer -> setContext(context);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BorderTitleBlock::cellRect
|
||||
Convert a cell written the way the border labels it (ex : B13, the row
|
||||
letter(s) then the column number) to its rect in scene coordinate.
|
||||
This is the reverse of convertPosition().
|
||||
@param cell : the cell to convert, case and surrounding spaces ignored
|
||||
@return the rect of the cell, or a null QRectF if \a cell is not a
|
||||
cell reference or lies outside of the border.
|
||||
*/
|
||||
QRectF BorderTitleBlock::cellRect(const QString &cell) const
|
||||
{
|
||||
static const QRegularExpression cell_re(
|
||||
QStringLiteral("^\\s*([A-Za-z]+)\\s*(\\d{1,4})\\s*$"));
|
||||
const QRegularExpressionMatch match = cell_re.match(cell);
|
||||
if (!match.hasMatch())
|
||||
return QRectF();
|
||||
|
||||
//Row letters count like A..Z, AA, AB... (see incrementLetters())
|
||||
int row = 0;
|
||||
for (const QChar c : match.captured(1).toUpper()) {
|
||||
row = row * 26 + (c.unicode() - 'A' + 1);
|
||||
if (row > rows_count_)
|
||||
return QRectF();
|
||||
}
|
||||
|
||||
int column = match.captured(2).toInt();
|
||||
QSettings settings;
|
||||
if (settings.value("border-columns_0", true).toBool())
|
||||
++column;
|
||||
|
||||
if (row < 1 || column < 1 || column > columns_count_)
|
||||
return QRectF();
|
||||
|
||||
const QPointF top_left = insideBorderRect().topLeft();
|
||||
return QRectF(top_left.x() + (column - 1) * columns_width_,
|
||||
top_left.y() + (row - 1) * rows_height_,
|
||||
columns_width_,
|
||||
rows_height_);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BorderTitleBlock::incrementLetters
|
||||
increments string with Letters A to Z
|
||||
|
||||
@@ -159,6 +159,7 @@ class BorderTitleBlock : public QObject
|
||||
void setDiagramHeight(const qreal &);
|
||||
|
||||
DiagramPosition convertPosition(const QPointF &);
|
||||
QRectF cellRect(const QString &cell) const;
|
||||
|
||||
// methods to set title block basic data
|
||||
void setFolio(const QString &folio);
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
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 "cellruler.h"
|
||||
|
||||
#include "bordercelllabels.h"
|
||||
#include "diagram.h"
|
||||
#include "diagramview.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QSettings>
|
||||
|
||||
/**
|
||||
@brief CellRuler::CellRuler
|
||||
@param orientation : Qt::Horizontal for the column numbers along the
|
||||
top, Qt::Vertical for the row letters along the left
|
||||
@param view : the view this ruler is placed on
|
||||
*/
|
||||
CellRuler::CellRuler(Qt::Orientation orientation, DiagramView *view) :
|
||||
QWidget(view),
|
||||
m_orientation(orientation),
|
||||
m_view(view)
|
||||
{
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
hide();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CellRuler::thickness
|
||||
@return the height of the top ruler, which is also the width of the
|
||||
side ruler, so the corner they share is square. Constant whatever the
|
||||
zoom.
|
||||
*/
|
||||
int CellRuler::thickness() const
|
||||
{
|
||||
const QFontMetrics metrics = fontMetrics();
|
||||
return qMax(metrics.height(), metrics.horizontalAdvance(QStringLiteral("WW"))) + 6;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CellRuler::setLeadingSpace
|
||||
@param space : pixels left empty before the first pixel of the
|
||||
viewport, along this ruler
|
||||
*/
|
||||
void CellRuler::setLeadingSpace(int space)
|
||||
{
|
||||
m_leading_space = space;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CellRuler::paintEvent
|
||||
Draw a cell for each column (or row) of the folio border, at the
|
||||
position the view shows it. When the cells get too small for their
|
||||
labels, only one label every 2, 5, 10... cells is written.
|
||||
*/
|
||||
void CellRuler::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
|
||||
QPainter painter(this);
|
||||
painter.fillRect(rect(), palette().color(QPalette::Button));
|
||||
|
||||
const bool horizontal = m_orientation == Qt::Horizontal;
|
||||
const int length = horizontal ? width() : height();
|
||||
const int depth = horizontal ? height() : width();
|
||||
|
||||
//Separate the ruler from the drawing
|
||||
painter.setPen(palette().color(QPalette::Dark));
|
||||
if (horizontal) {
|
||||
painter.drawLine(0, depth - 1, length, depth - 1);
|
||||
} else {
|
||||
painter.drawLine(depth - 1, 0, depth - 1, length);
|
||||
}
|
||||
|
||||
Diagram *diagram = m_view->diagram();
|
||||
if (!diagram) {
|
||||
return;
|
||||
}
|
||||
const BorderTitleBlock &border = diagram->border_and_titleblock;
|
||||
const QRectF inside = border.insideBorderRect();
|
||||
const QTransform transform = m_view->viewportTransform();
|
||||
|
||||
const int count = horizontal ? border.columnsCount() : border.rowsCount();
|
||||
const qreal cell_size = horizontal ? border.columnsWidth() : border.rowsHeight();
|
||||
const qreal first = horizontal ? inside.left() : inside.top();
|
||||
const qreal scale = horizontal ? transform.m11() : transform.m22();
|
||||
const qreal offset = (horizontal ? transform.dx() : transform.dy()) + m_leading_space;
|
||||
const qreal cell_pixels = cell_size * scale;
|
||||
if (count < 1 || cell_pixels <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool columns_start_at_zero =
|
||||
QSettings().value("border-columns_0", true).toBool();
|
||||
auto label = [&](int index) {
|
||||
return horizontal ? BorderCellLabels::columnLabel(index, columns_start_at_zero)
|
||||
: BorderCellLabels::rowLabel(index);
|
||||
};
|
||||
|
||||
//Room one label needs along the ruler, and the smallest step
|
||||
//between written labels that gives it that room
|
||||
const QFontMetrics metrics = fontMetrics();
|
||||
const int label_room = horizontal
|
||||
? metrics.horizontalAdvance(label(count)) + 6
|
||||
: metrics.height() + 2;
|
||||
int step = 1;
|
||||
for (int candidate : {1, 2, 5, 10, 20, 50, 100, 200, 500}) {
|
||||
step = candidate;
|
||||
if (candidate * cell_pixels >= label_room) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1 ; i <= count ; ++i) {
|
||||
const qreal start = offset + (first + (i - 1) * cell_size) * scale;
|
||||
const qreal end = start + cell_pixels;
|
||||
if (end < m_leading_space || start > length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//Cell edges, drawn only when the cells are wide enough
|
||||
//for them to read as cells rather than as a hatching
|
||||
painter.setPen(palette().color(QPalette::Dark));
|
||||
if (cell_pixels >= 4) {
|
||||
if (horizontal) {
|
||||
painter.drawLine(QPointF(start, 0), QPointF(start, depth - 1));
|
||||
if (i == count) painter.drawLine(QPointF(end, 0), QPointF(end, depth - 1));
|
||||
} else {
|
||||
painter.drawLine(QPointF(0, start), QPointF(depth - 1, start));
|
||||
if (i == count) painter.drawLine(QPointF(0, end), QPointF(depth - 1, end));
|
||||
}
|
||||
}
|
||||
|
||||
//Written labels are the ones a multiple of step: 0, 5, 10...
|
||||
//for the columns, A, F, K... for the rows
|
||||
const int position = (horizontal && !columns_start_at_zero) ? i : i - 1;
|
||||
if (position % step != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
painter.setPen(palette().color(QPalette::ButtonText));
|
||||
const qreal centre = (start + end) / 2;
|
||||
const QRectF text_rect = horizontal
|
||||
? QRectF(centre - label_room / 2.0, 0, label_room, depth - 1)
|
||||
: QRectF(0, centre - label_room / 2.0, depth - 1, label_room);
|
||||
painter.drawText(text_rect, Qt::AlignCenter | Qt::TextDontClip, label(i));
|
||||
}
|
||||
|
||||
//Keep the corner empty: the other ruler's labels do not belong there
|
||||
if (m_leading_space > 0) {
|
||||
painter.fillRect(horizontal ? QRect(0, 0, m_leading_space, depth - 1)
|
||||
: QRect(0, 0, depth - 1, m_leading_space),
|
||||
palette().color(QPalette::Button));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
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 CELLRULER_H
|
||||
#define CELLRULER_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class DiagramView;
|
||||
|
||||
/**
|
||||
@brief The CellRuler class
|
||||
A bar along the top or the left edge of a DiagramView that repeats the
|
||||
column numbers or the row letters of the folio border, aligned with the
|
||||
cells at any zoom, so they stay in sight however far the view is
|
||||
scrolled. It sits in the view's margins, outside of the scene: printing
|
||||
and exporting are unaffected.
|
||||
*/
|
||||
class CellRuler : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CellRuler(Qt::Orientation orientation, DiagramView *view);
|
||||
|
||||
int thickness() const;
|
||||
void setLeadingSpace(int space);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
Qt::Orientation m_orientation;
|
||||
DiagramView *m_view;
|
||||
/// Pixels before the viewport starts, left empty: the corner the
|
||||
/// side ruler fills when both rulers are shown.
|
||||
int m_leading_space = 0;
|
||||
};
|
||||
|
||||
#endif // CELLRULER_H
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "qetinformation.h"
|
||||
#include "qetproject.h"
|
||||
#include "diagramsortkeys.h"
|
||||
#include "textgrid.h"
|
||||
#include <QTextStream>
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
@@ -2684,6 +2685,29 @@ QPointF Diagram::snapToGrid(const QPointF &p)
|
||||
return (QPointF(p_x, p_y));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::snapToTextGrid
|
||||
Return the nearest point of p on the text grid, see TextGrid.
|
||||
Ctrl held rounds to the nearest pixel instead, as snapToGrid() does.
|
||||
@param p point to find the nearest snapped point
|
||||
@return
|
||||
*/
|
||||
QPointF Diagram::snapToTextGrid(const QPointF &p)
|
||||
{
|
||||
QSettings settings;
|
||||
const qreal divisor =
|
||||
QApplication::keyboardModifiers().testFlag(Qt::ControlModifier)
|
||||
? 0
|
||||
: settings.value(TextGrid::settings_key, 1).toReal();
|
||||
|
||||
return TextGrid::snap(p,
|
||||
settings.value(QStringLiteral("diagrameditor/Xgrid"),
|
||||
Diagram::xGrid).toInt(),
|
||||
settings.value(QStringLiteral("diagrameditor/Ygrid"),
|
||||
Diagram::yGrid).toInt(),
|
||||
divisor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -233,6 +233,7 @@ class Diagram : public QGraphicsScene
|
||||
BorderOptions borderOptions();
|
||||
DiagramPosition convertPosition(const QPointF &);
|
||||
static QPointF snapToGrid(const QPointF &p);
|
||||
static QPointF snapToTextGrid(const QPointF &p);
|
||||
|
||||
bool drawTerminals() const;
|
||||
void setDrawTerminals(bool);
|
||||
|
||||
+125
-3
@@ -16,6 +16,7 @@
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "diagramview.h"
|
||||
#include "cellruler.h"
|
||||
#include "lastusedstyle.h"
|
||||
#include "qetproject.h"
|
||||
#include "QPropertyUndoCommand/qpropertyundocommand.h"
|
||||
@@ -43,6 +44,7 @@
|
||||
#include <QDropEvent>
|
||||
#include <QPainter>
|
||||
#include <QPointer>
|
||||
#include <algorithm>
|
||||
|
||||
/**
|
||||
Constructeur
|
||||
@@ -107,6 +109,13 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
|
||||
connect(&(m_diagram -> border_and_titleblock), &BorderTitleBlock::informationChanged, this, &DiagramView::updateWindowTitle);
|
||||
connect(diagram, &Diagram::findElementRequired, this, &DiagramView::findElementRequired);
|
||||
|
||||
m_top_ruler = new CellRuler(Qt::Horizontal, this);
|
||||
m_side_ruler = new CellRuler(Qt::Vertical, this);
|
||||
m_cell_rulers_shown = QSettings().value("diagrameditor/cell_rulers", false).toBool();
|
||||
connect(&m_diagram->border_and_titleblock, &BorderTitleBlock::borderChanged, this, &DiagramView::updateCellRulers);
|
||||
connect(&m_diagram->border_and_titleblock, &BorderTitleBlock::displayChanged, this, &DiagramView::updateCellRulers);
|
||||
updateCellRulers();
|
||||
|
||||
QShortcut *edit_conductor_color_shortcut = new QShortcut(QKeySequence(Qt::Key_F2), this);
|
||||
connect(edit_conductor_color_shortcut, &QShortcut::activated, [this]()
|
||||
{
|
||||
@@ -443,6 +452,23 @@ void DiagramView::zoomReset()
|
||||
adjustGridToZoom();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramView::zoomToRect
|
||||
Adjust zoom to fit \a rect, in scene coordinate, in the view.
|
||||
@param rect
|
||||
*/
|
||||
void DiagramView::zoomToRect(const QRectF &rect)
|
||||
{
|
||||
fitInView(rect, Qt::KeepAspectRatio);
|
||||
//Zooming in makes the scroll bars appear, which resizes the viewport
|
||||
//from a queued call; that resize is anchored under the mouse and
|
||||
//would scroll away from rect, so center again once it has run.
|
||||
QMetaObject::invokeMethod(this, [this, rect]() {
|
||||
centerOn(rect.center());
|
||||
}, Qt::QueuedConnection);
|
||||
adjustGridToZoom();
|
||||
}
|
||||
|
||||
/**
|
||||
Copie les elements selectionnes du schema dans le presse-papier puis les supprime
|
||||
Copies the selected elements from the diagram to the clipboard and then deletes them
|
||||
@@ -1228,6 +1254,82 @@ void DiagramView::paintingInverted(bool inverted)
|
||||
m_diagram->setInvertedLightness(inverted);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramView::setCellRulersShown
|
||||
Show or hide the rulers that keep the column numbers and the row
|
||||
letters of the folio border in sight along the edges of this view.
|
||||
@param shown
|
||||
*/
|
||||
void DiagramView::setCellRulersShown(bool shown)
|
||||
{
|
||||
m_cell_rulers_shown = shown;
|
||||
updateCellRulers();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramView::updateCellRulers
|
||||
Show each ruler when the rulers are wanted and the folio shows the
|
||||
matching header, and give it room in the margins of the view. The
|
||||
part of the folio in sight stays in sight when the viewport resizes.
|
||||
*/
|
||||
void DiagramView::updateCellRulers()
|
||||
{
|
||||
const BorderTitleBlock &border = m_diagram->border_and_titleblock;
|
||||
const bool top = m_cell_rulers_shown
|
||||
&& border.borderIsDisplayed() && border.columnsAreDisplayed();
|
||||
const bool side = m_cell_rulers_shown
|
||||
&& border.borderIsDisplayed() && border.rowsAreDisplayed();
|
||||
const int thickness = m_top_ruler->thickness();
|
||||
|
||||
m_top_ruler->setVisible(top);
|
||||
m_side_ruler->setVisible(side);
|
||||
m_side_ruler->setLeadingSpace(top ? thickness : 0);
|
||||
|
||||
const QMargins margins(side ? thickness : 0, top ? thickness : 0, 0, 0);
|
||||
if (margins != viewportMargins()) {
|
||||
const QPointF centre = mapToScene(viewport()->rect().center());
|
||||
setViewportMargins(margins);
|
||||
centerOn(centre);
|
||||
}
|
||||
placeCellRulers();
|
||||
m_top_ruler->update();
|
||||
m_side_ruler->update();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramView::placeCellRulers
|
||||
Lay the rulers along the top and the left edges of the viewport, the
|
||||
side ruler covering the corner too when both are shown.
|
||||
*/
|
||||
void DiagramView::placeCellRulers()
|
||||
{
|
||||
if (!m_top_ruler) {
|
||||
return;
|
||||
}
|
||||
const QRect viewport_rect = viewport()->geometry();
|
||||
const int thickness = m_top_ruler->thickness();
|
||||
const int corner = m_top_ruler->isHidden() ? 0 : thickness;
|
||||
m_top_ruler->setGeometry(viewport_rect.left(), viewport_rect.top() - thickness,
|
||||
viewport_rect.width(), thickness);
|
||||
m_side_ruler->setGeometry(viewport_rect.left() - thickness, viewport_rect.top() - corner,
|
||||
thickness, viewport_rect.height() + corner);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramView::viewportEvent
|
||||
Keep the rulers along the viewport when it resizes, which it also does
|
||||
without the view resizing, when the scroll bars come and go.
|
||||
@param event
|
||||
@return what QGraphicsView::viewportEvent() returns
|
||||
*/
|
||||
bool DiagramView::viewportEvent(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::Resize) {
|
||||
placeCellRulers();
|
||||
}
|
||||
return PaletteGraphicsView::viewportEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramView::paintEvent
|
||||
Reimplemented from QGraphicsView
|
||||
@@ -1237,6 +1339,13 @@ void DiagramView::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
PaletteGraphicsView::paintEvent(event);
|
||||
|
||||
//Scrolling and zooming both repaint the viewport: follow them
|
||||
if (viewportTransform() != m_rulers_transform) {
|
||||
m_rulers_transform = viewportTransform();
|
||||
m_top_ruler->update();
|
||||
m_side_ruler->update();
|
||||
}
|
||||
|
||||
if (m_free_rubberbanding && m_free_rubberband.count() >= 3)
|
||||
{
|
||||
QPainter painter(viewport());
|
||||
@@ -1357,10 +1466,15 @@ QList<QAction *> DiagramView::contextMenuActions() const
|
||||
{
|
||||
if (m_diagram->selectedItems().isEmpty())
|
||||
{
|
||||
//Drawing comes first. The row and column actions change
|
||||
//the folio's layout and are rarely wanted, so they sit one
|
||||
//level down where a stray click cannot reach them.
|
||||
list << m_paste_here;
|
||||
list << m_separators.at(0);
|
||||
list << qde->m_add_item_menu->menuAction();
|
||||
list << m_separators.at(1);
|
||||
list << qde->m_edit_diagram_properties;
|
||||
list << qde->m_row_column_actions_group.actions();
|
||||
list << qde->m_row_column_menu->menuAction();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1376,11 +1490,19 @@ QList<QAction *> DiagramView::contextMenuActions() const
|
||||
list << qde->m_depth_action_group->actions();
|
||||
}
|
||||
|
||||
//Remove from the context menu the actions which are disabled.
|
||||
//Remove from the context menu the actions which are disabled,
|
||||
//and the submenus in which every action is disabled.
|
||||
const QList<QAction *> actions = list;
|
||||
for(QAction *action : actions)
|
||||
{
|
||||
if (!action->isEnabled()) {
|
||||
bool usable = action->isEnabled();
|
||||
if (usable && action->menu())
|
||||
{
|
||||
const QList<QAction *> sub_actions = action->menu()->actions();
|
||||
usable = std::any_of(sub_actions.cbegin(), sub_actions.cend(),
|
||||
[](QAction *a) { return a->isEnabled(); });
|
||||
}
|
||||
if (!usable) {
|
||||
list.removeAll(action);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <QClipboard>
|
||||
#include "palettegraphicsview.h"
|
||||
|
||||
class CellRuler;
|
||||
class Conductor;
|
||||
class Diagram;
|
||||
class QETDiagramEditor;
|
||||
@@ -62,6 +63,11 @@ class DiagramView : public PaletteGraphicsView
|
||||
QList<QAction *> m_separators;
|
||||
QPolygonF m_free_rubberband;
|
||||
bool m_free_rubberbanding = false;
|
||||
CellRuler *m_top_ruler = nullptr;
|
||||
CellRuler *m_side_ruler = nullptr;
|
||||
bool m_cell_rulers_shown = false;
|
||||
/// Last viewport transform the rulers were painted for
|
||||
QTransform m_rulers_transform;
|
||||
|
||||
|
||||
public:
|
||||
@@ -78,6 +84,7 @@ class DiagramView : public PaletteGraphicsView
|
||||
/// cursor query (QCursor::pos()/setPos() are silently ignored by
|
||||
/// several window managers and compositors, Wayland included).
|
||||
QPoint lastMousePos() const { return m_last_mouse_pos; }
|
||||
void setCellRulersShown(bool shown);
|
||||
|
||||
bool startElementPlacement(const ElementsLocation &location,
|
||||
const QPointF &scene_pos);
|
||||
@@ -95,6 +102,7 @@ class DiagramView : public PaletteGraphicsView
|
||||
///Set for one call only, by the Escape handler, to let focus leave the view.
|
||||
bool m_releasing_focus = false;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
bool viewportEvent(QEvent *event) override;
|
||||
void paintingInverted(bool inverted) override;
|
||||
void mousePressEvent(QMouseEvent *) override;
|
||||
void mouseMoveEvent(QMouseEvent *) override;
|
||||
@@ -117,6 +125,8 @@ class DiagramView : public PaletteGraphicsView
|
||||
QRectF viewedSceneRect() const;
|
||||
bool mustIntegrateTitleBlockTemplate(const TitleBlockTemplateLocation &) const;
|
||||
bool gestures() const;
|
||||
void updateCellRulers();
|
||||
void placeCellRulers();
|
||||
|
||||
/// Lowest and highest allowed value of the view transform scale (m11).
|
||||
/// Prevents wheel-zoom from driving the transform to overflow, which
|
||||
@@ -147,6 +157,7 @@ class DiagramView : public PaletteGraphicsView
|
||||
void zoomFit();
|
||||
void zoomContent();
|
||||
void zoomReset();
|
||||
void zoomToRect(const QRectF &rect);
|
||||
void cut();
|
||||
void copy();
|
||||
void paste(const QPointF & = QPointF(), QClipboard::Mode = QClipboard::Clipboard);
|
||||
|
||||
@@ -22,8 +22,11 @@
|
||||
#include "qetapp.h"
|
||||
#include "qetgraphicsitem/dynamicelementtextitem.h"
|
||||
#include "qetgraphicsitem/elementtextitemgroup.h"
|
||||
#include "qetdiagrameditor.h"
|
||||
#include "textgrid.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QSettings>
|
||||
|
||||
/**
|
||||
@brief ElementTextsMover::ElementTextsMover
|
||||
@@ -80,6 +83,19 @@ int ElementTextsMover::beginMovement(Diagram *diagram, QGraphicsItem *driver_ite
|
||||
return -1;
|
||||
|
||||
m_movement_running = true;
|
||||
|
||||
m_status_bar.clear();
|
||||
if (!diagram->views().isEmpty())
|
||||
if (const auto qde = QETApp::diagramEditorAncestorOf(diagram->views().at(0)))
|
||||
m_status_bar = qde->statusBar();
|
||||
if (m_status_bar)
|
||||
{
|
||||
const qreal divisor = QSettings().value(TextGrid::settings_key, 1).toReal();
|
||||
m_status_bar->showMessage(divisor > 0
|
||||
? QObject::tr("Grille des textes %1. Relâcher Maj et maintenir Ctrl pour placer librement.")
|
||||
.arg(TextGrid::ratioLabel(divisor))
|
||||
: QObject::tr("Grille des textes désactivée."));
|
||||
}
|
||||
|
||||
return m_items_hash.size();
|
||||
}
|
||||
@@ -101,7 +117,7 @@ void ElementTextsMover::continueMovement(QGraphicsSceneMouseEvent *event)
|
||||
button_down_parent_pos = qgi->mapToParent(qgi->mapFromScene(event->buttonDownScenePos(Qt::LeftButton)));
|
||||
|
||||
QPointF new_pos = m_items_hash.value(qgi) + current_parent_pos - button_down_parent_pos;
|
||||
event->modifiers() == Qt::ControlModifier ? qgi->setPos(new_pos) : qgi->setPos(Diagram::snapToGrid(new_pos));
|
||||
event->modifiers() == Qt::ControlModifier ? qgi->setPos(new_pos) : qgi->setPos(Diagram::snapToTextGrid(new_pos));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,13 +128,19 @@ void ElementTextsMover::continueMovement(QGraphicsSceneMouseEvent *event)
|
||||
void ElementTextsMover::endMovement()
|
||||
{
|
||||
//No movement or no items to move
|
||||
if (m_status_bar)
|
||||
m_status_bar->clearMessage();
|
||||
|
||||
if(!m_movement_running || m_items_hash.isEmpty())
|
||||
return;
|
||||
|
||||
//Movement is null
|
||||
QGraphicsItem *qgi = m_items_hash.keys().first();
|
||||
if(qgi->pos() == m_items_hash.value(qgi))
|
||||
{
|
||||
m_movement_running = false;
|
||||
return;
|
||||
}
|
||||
|
||||
QUndoCommand *undo = new QUndoCommand(undoText());
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include <QSet>
|
||||
#include <QPointF>
|
||||
#include <QHash>
|
||||
#include <QPointer>
|
||||
#include <QStatusBar>
|
||||
|
||||
class QGraphicsItem;
|
||||
class DiagramTextItem;
|
||||
@@ -55,6 +57,7 @@ class ElementTextsMover
|
||||
QHash <DiagramTextItem *, QPointF> m_texts_hash;
|
||||
QHash <QGraphicsItemGroup *, QPointF> m_grps_hash;
|
||||
QHash <QGraphicsItem *, QPointF> m_items_hash;
|
||||
QPointer<QStatusBar> m_status_bar;
|
||||
int m_text_count = 0,
|
||||
m_group_count = 0;
|
||||
};
|
||||
|
||||
@@ -2231,6 +2231,7 @@ void QETApp::configureQET()
|
||||
// affiche le dialogue puis evite de le lier a un quelconque widget parent
|
||||
cd.exec();
|
||||
cd.setParent(nullptr, cd.windowFlags());
|
||||
emit textGridChanged();
|
||||
|
||||
#ifdef QET_SPACEMOUSE_SUPPORT
|
||||
if (m_space_mouse_listener) {
|
||||
|
||||
@@ -260,6 +260,10 @@ class QETApp : public QObject
|
||||
|
||||
static QString m_interface_language;
|
||||
|
||||
signals:
|
||||
/// The text grid setting changed, see TextGrid.
|
||||
void textGridChanged();
|
||||
|
||||
public slots:
|
||||
void systray(QSystemTrayIcon::ActivationReason);
|
||||
void reduceEveryEditor();
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "scripting/qetscripting.h"
|
||||
#endif
|
||||
#include <QCoreApplication>
|
||||
#include <QToolButton>
|
||||
#include "ElementsCollection/elementscollectionwidget.h"
|
||||
#include "QWidgetAnimation/qwidgetanimation.h"
|
||||
#include "autoNum/ui/autonumberingdockwidget.h"
|
||||
@@ -48,6 +49,7 @@
|
||||
#include "qeticons.h"
|
||||
#include "qetmessagebox.h"
|
||||
#include "recentfiles.h"
|
||||
#include "textgrid.h"
|
||||
#include "shortcutmanager.h"
|
||||
#include "ui/bomexportdialog.h"
|
||||
#include "ui/conductorcolortoolbutton.h"
|
||||
@@ -488,6 +490,33 @@ void QETDiagramEditor::setUpActions()
|
||||
}
|
||||
});
|
||||
|
||||
//Snap step for dragged texts, as a fraction of the folio grid
|
||||
m_text_grid_menu = new QMenu(tr("Grille des textes"), this);
|
||||
m_text_grid_menu->setIcon(QET::Icons::Grid);
|
||||
m_text_grid_menu->setToolTipsVisible(true);
|
||||
m_text_grid_button = new QToolButton(this);
|
||||
m_text_grid_button->setMenu(m_text_grid_menu);
|
||||
m_text_grid_button->setPopupMode(QToolButton::InstantPopup);
|
||||
m_text_grid_button->setToolButtonStyle(Qt::ToolButtonTextOnly);
|
||||
m_text_grid_button->setToolTip(tr("Grille d'accrochage des textes déplacés à la souris.\n"
|
||||
"Maintenir Ctrl pendant le déplacement pour placer librement."));
|
||||
auto text_grid_group = new QActionGroup(this);
|
||||
for (const qreal divisor : TextGrid::divisors)
|
||||
{
|
||||
QAction *action = m_text_grid_menu->addAction(
|
||||
divisor > 0 ? TextGrid::ratioLabel(divisor) : tr("Désactivée"));
|
||||
action->setCheckable(true);
|
||||
action->setData(divisor);
|
||||
text_grid_group->addAction(action);
|
||||
}
|
||||
connect(text_grid_group, &QActionGroup::triggered, this, [](QAction *action) {
|
||||
QSettings().setValue(TextGrid::settings_key, action->data());
|
||||
emit QETApp::instance()->textGridChanged();
|
||||
});
|
||||
connect(QETApp::instance(), &QETApp::textGridChanged,
|
||||
this, &QETDiagramEditor::updateTextGridButton);
|
||||
updateTextGridButton();
|
||||
|
||||
// Draw or not the custom guides
|
||||
m_draw_guides = new QAction ( QIcon::fromTheme("guides"), tr("Afficher les guides"), this);
|
||||
m_draw_guides->setStatusTip(tr("Affiche ou masque les guides"));
|
||||
@@ -500,6 +529,18 @@ 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);
|
||||
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());
|
||||
connect(m_cell_rulers, &QAction::triggered, [this](bool checked) {
|
||||
QSettings().setValue("diagrameditor/cell_rulers", checked);
|
||||
foreach (ProjectView *prjv, this->openedProjects())
|
||||
foreach (DiagramView *dv, prjv->diagram_views())
|
||||
dv->setCellRulersShown(checked);
|
||||
});
|
||||
|
||||
//Edit current diagram properties
|
||||
m_edit_diagram_properties = new QAction(QET::Icons::DialogInformation, tr("Propriétés du folio"), this);
|
||||
ShortcutManager::instance().registerAction(m_edit_diagram_properties, "diagrameditor.edit_diagram_properties", tr("Éditeur de schémas"), Qt::CTRL | Qt::Key_L);
|
||||
@@ -983,6 +1024,7 @@ void QETDiagramEditor::setUpToolBar()
|
||||
view_tool_bar -> addWidget(new DiagramEditorHandlerSizeWidget(this));
|
||||
view_tool_bar -> addSeparator();
|
||||
view_tool_bar -> addAction(m_draw_grid);
|
||||
view_tool_bar -> addWidget(m_text_grid_button);
|
||||
view_tool_bar -> addAction(m_draw_guides);
|
||||
view_tool_bar -> addWidget(m_background_color_button);
|
||||
view_tool_bar -> addSeparator();
|
||||
@@ -1072,9 +1114,9 @@ void QETDiagramEditor::setUpMenu()
|
||||
//toolbar button has no key, so text fields, images and every drawing
|
||||
//shape simply could not be added. m_depth_action_group below has
|
||||
//always been in both places; this brings these into line with it.
|
||||
QMenu *menu_add_item = menu_edition -> addMenu(tr("A&jouter"));
|
||||
menu_add_item -> setIcon(QET::Icons::Add);
|
||||
menu_add_item -> addActions(m_add_item_actions_group.actions());
|
||||
m_add_item_menu = menu_edition -> addMenu(tr("A&jouter"));
|
||||
m_add_item_menu -> setIcon(QET::Icons::Add);
|
||||
m_add_item_menu -> addActions(m_add_item_actions_group.actions());
|
||||
menu_edition -> addSeparator();
|
||||
menu_edition -> addActions(m_select_actions_group.actions());
|
||||
menu_edition -> addSeparator();
|
||||
@@ -1084,6 +1126,12 @@ void QETDiagramEditor::setUpMenu()
|
||||
menu_edition -> addSeparator();
|
||||
menu_edition -> addAction(m_edit_diagram_properties);
|
||||
menu_edition -> addActions(m_row_column_actions_group.actions());
|
||||
//Not added to a menu here: it exists so the folio's context menu can
|
||||
//hold the row and column actions one level down (see
|
||||
//DiagramView::contextMenuActions()).
|
||||
m_row_column_menu = new QMenu(tr("Lignes et colonnes"), this);
|
||||
m_row_column_menu -> setIcon(QET::Icons::EditTableInsertColumnRight);
|
||||
m_row_column_menu -> addActions(m_row_column_actions_group.actions());
|
||||
menu_edition -> addSeparator();
|
||||
menu_edition -> addActions(m_depth_action_group->actions());
|
||||
menu_edition -> addSeparator();
|
||||
@@ -1137,7 +1185,9 @@ void QETDiagramEditor::setUpMenu()
|
||||
menu_affichage -> addAction(m_mode_visualise);
|
||||
menu_affichage -> addSeparator();
|
||||
menu_affichage -> addAction(m_draw_grid);
|
||||
menu_affichage -> addMenu(m_text_grid_menu);
|
||||
menu_affichage -> addAction(m_draw_guides);
|
||||
menu_affichage -> addAction(m_cell_rulers);
|
||||
menu_affichage -> addMenu(m_background_color_button->menu());
|
||||
menu_affichage -> addSeparator();
|
||||
menu_affichage -> addActions(m_zoom_actions_group.actions());
|
||||
@@ -1970,6 +2020,7 @@ void QETDiagramEditor::slot_updateActions()
|
||||
m_background_color_button-> setEnabled(opened_diagram);
|
||||
m_draw_grid-> setEnabled(opened_diagram);
|
||||
m_draw_guides-> setEnabled(opened_diagram);
|
||||
m_cell_rulers-> setEnabled(opened_diagram);
|
||||
|
||||
//Project menu
|
||||
m_project_edit_properties -> setEnabled(opened_project);
|
||||
@@ -3301,3 +3352,23 @@ void QETDiagramEditor::slot_runScript() {
|
||||
QetScripting::runOnProject(script_path, project, currentDiagramView());
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
@brief QETDiagramEditor::updateTextGridButton
|
||||
Show the current text grid on its toolbar button and check it in its menu.
|
||||
*/
|
||||
void QETDiagramEditor::updateTextGridButton()
|
||||
{
|
||||
const qreal divisor = QSettings().value(TextGrid::settings_key, 1).toReal();
|
||||
for (QAction *action : m_text_grid_menu->actions())
|
||||
{
|
||||
if (qFuzzyCompare(action->data().toReal() + 1, divisor + 1))
|
||||
{
|
||||
action->setChecked(true);
|
||||
m_text_grid_button->setText(tr("Textes %1").arg(action->text()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
//A divisor the menu does not offer, set by hand in the config file
|
||||
m_text_grid_button->setText(tr("Textes %1").arg(TextGrid::ratioLabel(divisor)));
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
#include <QSignalMapper>
|
||||
#include <QUndoGroup>
|
||||
|
||||
class QToolButton;
|
||||
|
||||
class QMdiSubWindow;
|
||||
class QETProject;
|
||||
class QETResult;
|
||||
@@ -167,6 +169,7 @@ class QETDiagramEditor : public QETMainWindow
|
||||
void subWindowActivated(QMdiSubWindow *subWindows);
|
||||
|
||||
private slots:
|
||||
void updateTextGridButton();
|
||||
void selectionChanged();
|
||||
|
||||
public:
|
||||
@@ -180,6 +183,10 @@ class QETDiagramEditor : public QETMainWindow
|
||||
m_row_column_actions_group, /// Action related to add/remove rows/column in diagram
|
||||
m_selection_actions_group, ///Action related to edit a selected item
|
||||
*m_depth_action_group = nullptr;
|
||||
|
||||
QMenu
|
||||
*m_add_item_menu = nullptr, ///< Submenu of m_add_item_actions_group
|
||||
*m_row_column_menu = nullptr; ///< Submenu of m_row_column_actions_group
|
||||
|
||||
private:
|
||||
QActionGroup
|
||||
@@ -208,6 +215,7 @@ class QETDiagramEditor : public QETMainWindow
|
||||
*m_auto_break_conductor, ///< Enable/Disable the use of auto break conductor
|
||||
*m_draw_grid, ///< Switch the background grid display or not
|
||||
*m_draw_guides = nullptr, ///< Switch the custom guides display or not
|
||||
*m_cell_rulers = nullptr, ///< Keep the folio column/row headers in sight or not
|
||||
*m_project_edit_properties, ///< Edit the properties of the current project.
|
||||
*m_project_add_diagram, ///< Add a diagram to the current project.
|
||||
*m_remove_diagram_from_project, ///< Delete a diagram from the current project
|
||||
@@ -247,6 +255,8 @@ class QETDiagramEditor : public QETMainWindow
|
||||
ConductorColorToolButton *m_conductor_color_button = nullptr;
|
||||
///< Diagram background color picker, in the "Affichage" toolbar
|
||||
DiagramBgColorToolButton *m_background_color_button = nullptr;
|
||||
QMenu *m_text_grid_menu = nullptr; ///< Snap step used when dragging texts
|
||||
QToolButton *m_text_grid_button = nullptr;
|
||||
|
||||
QList <QAction *> m_zoom_action_toolBar; ///Only zoom action must displayed in the toolbar
|
||||
|
||||
|
||||
@@ -185,7 +185,7 @@ void ConductorTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
|
||||
|
||||
if (parent_conductor_) {
|
||||
if (parent_conductor_->nearShape().contains(intended_pos)) {
|
||||
event->modifiers() == Qt::ControlModifier ? setPos(intended_pos) : setPos(Diagram::snapToGrid(intended_pos));
|
||||
event->modifiers() == Qt::ControlModifier ? setPos(intended_pos) : setPos(Diagram::snapToTextGrid(intended_pos));
|
||||
parent_conductor_ -> setHighlighted(Conductor::Normal);
|
||||
} else {
|
||||
parent_conductor_ -> setHighlighted(Conductor::Alert);
|
||||
|
||||
@@ -370,7 +370,7 @@ void DiagramTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
|
||||
|
||||
//Set the actual pos
|
||||
QPointF new_pos = event->scenePos() + m_mouse_to_origin_movement;
|
||||
event->modifiers() == Qt::ControlModifier ? setPos(new_pos) : setPos(Diagram::snapToGrid(new_pos));
|
||||
event->modifiers() == Qt::ControlModifier ? setPos(new_pos) : setPos(Diagram::snapToTextGrid(new_pos));
|
||||
|
||||
|
||||
//Update the actual movement for other selected item
|
||||
|
||||
@@ -639,7 +639,7 @@ void DynamicElementTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
//DiagramTextItem::mouseMoveEvent() for independent texts.
|
||||
//Without it this was the only text move in the editor that
|
||||
//ignored the grid.
|
||||
event->modifiers() == Qt::ControlModifier ? setPos(new_pos) : setPos(Diagram::snapToGrid(new_pos));
|
||||
event->modifiers() == Qt::ControlModifier ? setPos(new_pos) : setPos(Diagram::snapToTextGrid(new_pos));
|
||||
|
||||
if(diagram())
|
||||
diagram()->elementTextsMover().continueMovement(event);
|
||||
|
||||
@@ -688,7 +688,7 @@ void ElementTextItemGroup::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
button_down_parent_pos = mapToParent(mapFromScene(event->buttonDownScenePos(Qt::LeftButton)));
|
||||
|
||||
QPointF new_pos = m_initial_position + current_parent_pos - button_down_parent_pos;
|
||||
event->modifiers() == Qt::ControlModifier ? setPos(new_pos) : setPos(Diagram::snapToGrid(new_pos));
|
||||
event->modifiers() == Qt::ControlModifier ? setPos(new_pos) : setPos(Diagram::snapToTextGrid(new_pos));
|
||||
|
||||
if(diagram())
|
||||
diagram()->elementTextsMover().continueMovement(event);
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
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 TEXTGRID_H
|
||||
#define TEXTGRID_H
|
||||
|
||||
#include <QList>
|
||||
#include <QPointF>
|
||||
#include <QString>
|
||||
#include <QtMath>
|
||||
|
||||
/**
|
||||
The text grid: the step texts snap to when dragged with the mouse,
|
||||
a fraction of the folio grid. A divisor of 1 is the folio grid itself,
|
||||
0 means no grid. Because every step divides the folio grid, a text
|
||||
snapped to it still lines up with every element and with the texts
|
||||
of other elements.
|
||||
*/
|
||||
namespace TextGrid
|
||||
{
|
||||
/// The choices offered in the menu and the preferences, 0 first.
|
||||
inline const QList<qreal> divisors{0, 1, 2, 5, 10};
|
||||
|
||||
/// QSettings key holding the divisor.
|
||||
inline const QString settings_key{QStringLiteral("diagrameditor/text_grid_divisor")};
|
||||
|
||||
/// "1:5" for 5. Not meaningful for 0.
|
||||
inline QString ratioLabel(qreal divisor) {
|
||||
return QStringLiteral("1:") + QString::number(divisor);
|
||||
}
|
||||
|
||||
/**
|
||||
@return p snapped to a grid of x_grid / divisor by y_grid / divisor,
|
||||
or rounded to the nearest pixel when divisor is 0 or less.
|
||||
*/
|
||||
inline QPointF snap(const QPointF &p, int x_grid, int y_grid, qreal divisor)
|
||||
{
|
||||
if (divisor <= 0 || x_grid <= 0 || y_grid <= 0)
|
||||
return QPointF(qRound(p.x()), qRound(p.y()));
|
||||
|
||||
const qreal x_step = x_grid / divisor;
|
||||
const qreal y_step = y_grid / divisor;
|
||||
return QPointF(qRound(p.x() / x_step) * x_step,
|
||||
qRound(p.y() / y_step) * y_step);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // TEXTGRID_H
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "../../utils/qetsettings.h"
|
||||
#include "../../utils/qetutils.h"
|
||||
#include "../../qetmessagebox.h"
|
||||
#include "../../textgrid.h"
|
||||
#include "../nokde/kcolorbutton.h"
|
||||
#include <QFileDialog>
|
||||
#include <QFontDialog>
|
||||
@@ -71,6 +72,15 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) :
|
||||
ui->m_collection_dblclick_edits->setChecked(!settings.value("elementscollection/double-click-inserts", true).toBool());
|
||||
ui->DiagramEditor_xGrid_sb->setValue(settings.value("diagrameditor/Xgrid", 10).toInt());
|
||||
ui->DiagramEditor_yGrid_sb->setValue(settings.value("diagrameditor/Ygrid", 10).toInt());
|
||||
for (const qreal divisor : TextGrid::divisors)
|
||||
ui->DiagramEditor_textGrid_cb->addItem(
|
||||
divisor > 0 ? TextGrid::ratioLabel(divisor) : tr("Désactivée"),
|
||||
divisor);
|
||||
int text_grid_index = ui->DiagramEditor_textGrid_cb->findData(
|
||||
settings.value(TextGrid::settings_key, 1).toReal());
|
||||
if (text_grid_index < 0)
|
||||
text_grid_index = ui->DiagramEditor_textGrid_cb->findData(qreal(1));
|
||||
ui->DiagramEditor_textGrid_cb->setCurrentIndex(text_grid_index);
|
||||
ui->DiagramEditor_xKeyGrid_sb->setValue(settings.value("diagrameditor/key_Xgrid", 10).toInt());
|
||||
ui->DiagramEditor_yKeyGrid_sb->setValue(settings.value("diagrameditor/key_Ygrid", 10).toInt());
|
||||
ui->DiagramEditor_xKeyGridFine_sb->setValue(settings.value("diagrameditor/key_fine_Xgrid", 1).toInt());
|
||||
@@ -291,6 +301,7 @@ void GeneralConfigurationPage::applyConf()
|
||||
//Grid step and key navigation
|
||||
settings.setValue("diagrameditor/Xgrid", ui->DiagramEditor_xGrid_sb->value());
|
||||
settings.setValue("diagrameditor/Ygrid", ui->DiagramEditor_yGrid_sb->value());
|
||||
settings.setValue(TextGrid::settings_key, ui->DiagramEditor_textGrid_cb->currentData());
|
||||
settings.setValue("diagrameditor/key_Xgrid", ui->DiagramEditor_xKeyGrid_sb->value());
|
||||
settings.setValue("diagrameditor/key_Ygrid", ui->DiagramEditor_yKeyGrid_sb->value());
|
||||
settings.setValue("diagrameditor/key_fine_Xgrid", ui->DiagramEditor_xKeyGridFine_sb->value());
|
||||
|
||||
@@ -794,6 +794,26 @@ Vous pouvez spécifier ici la valeur par défaut de ce champ pour les éléments
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="Label_Diagram_textGrid">
|
||||
<property name="text">
|
||||
<string>Grille des textes déplacés à la souris</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Fraction de la grille des folios. Maintenir Ctrl pendant le déplacement pour placer librement.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QComboBox" name="DiagramEditor_textGrid_cb">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -18,12 +18,15 @@
|
||||
#include "jumptoelementdialog.h"
|
||||
|
||||
#include "../diagram.h"
|
||||
#include "../diagramview.h"
|
||||
#include "../qetproject.h"
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
|
||||
#include <QEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
#include <QRegularExpression>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
/**
|
||||
@@ -38,7 +41,7 @@ JumpToElementDialog::JumpToElementDialog(Diagram *diagram, QWidget *parent) :
|
||||
setWindowTitle(tr("Atteindre un élément", "window title"));
|
||||
|
||||
m_filter_edit = new QLineEdit(this);
|
||||
m_filter_edit->setPlaceholderText(tr("Nom, label ou information de l'élément…"));
|
||||
m_filter_edit->setPlaceholderText(tr("Nom, label ou information de l'élément, ou case (ex. B13 ou 3-B13)…"));
|
||||
m_filter_edit->installEventFilter(this);
|
||||
|
||||
m_result_list = new QListWidget(this);
|
||||
@@ -86,6 +89,7 @@ void JumpToElementDialog::buildCandidates()
|
||||
|
||||
Candidate candidate;
|
||||
candidate.element = element;
|
||||
candidate.label = label;
|
||||
candidate.display_text = label.isEmpty() ? name : (label + QStringLiteral(" — ") + name);
|
||||
|
||||
QStringList search_parts;
|
||||
@@ -111,6 +115,7 @@ void JumpToElementDialog::updateFilteredList(const QString &filter_text)
|
||||
m_result_list->clear();
|
||||
|
||||
const QString needle = filter_text.trimmed().toLower();
|
||||
bool exact_label_match = false;
|
||||
for (int i = 0; i < m_candidates.size(); ++i) {
|
||||
const Candidate &candidate = m_candidates.at(i);
|
||||
if (!candidate.element) {
|
||||
@@ -121,6 +126,18 @@ void JumpToElementDialog::updateFilteredList(const QString &filter_text)
|
||||
}
|
||||
auto *list_item = new QListWidgetItem(candidate.display_text, m_result_list);
|
||||
list_item->setData(Qt::UserRole, i);
|
||||
if (candidate.label.compare(needle, Qt::CaseInsensitive) == 0) {
|
||||
exact_label_match = true;
|
||||
}
|
||||
}
|
||||
|
||||
//A cell of the border, on this folio (ex : B13) or on another one
|
||||
//(ex : 3-B13, folio 3, the way cross references write it), comes
|
||||
//first, unless an element is labelled exactly like it: Enter keeps
|
||||
//jumping to that element.
|
||||
if (QListWidgetItem *cell_item = cellItem(needle)) {
|
||||
m_result_list->insertItem(exact_label_match ? m_result_list->count() : 0,
|
||||
cell_item);
|
||||
}
|
||||
|
||||
if (m_result_list->count() > 0) {
|
||||
@@ -142,6 +159,17 @@ void JumpToElementDialog::activateCurrentItem()
|
||||
}
|
||||
|
||||
const int index = current->data(Qt::UserRole).toInt();
|
||||
if (index == -1) {
|
||||
const QList<Diagram *> diagrams = m_diagram->project()
|
||||
? m_diagram->project()->diagrams()
|
||||
: QList<Diagram *>();
|
||||
const int folio = current->data(Qt::UserRole + 2).toInt();
|
||||
if (folio >= 0 && folio < diagrams.size()) {
|
||||
zoomToCell(diagrams.at(folio), current->data(Qt::UserRole + 1).toRectF());
|
||||
}
|
||||
accept();
|
||||
return;
|
||||
}
|
||||
if (index < 0 || index >= m_candidates.size()) {
|
||||
reject();
|
||||
return;
|
||||
@@ -159,6 +187,93 @@ void JumpToElementDialog::activateCurrentItem()
|
||||
accept();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief JumpToElementDialog::cellItem
|
||||
@param needle : the typed text, trimmed and lower case
|
||||
@return a new list item for the cell \a needle names, on m_diagram
|
||||
(ex : b13) or on the folio at a position of the project (ex : 3-b13,
|
||||
3b13, p3b13), or nullptr if \a needle names no cell of an existing
|
||||
folio. The item holds -1, the cell rect and the folio position.
|
||||
*/
|
||||
QListWidgetItem *JumpToElementDialog::cellItem(const QString &needle) const
|
||||
{
|
||||
if (!m_diagram || !m_diagram->project()) {
|
||||
return nullptr;
|
||||
}
|
||||
const QList<Diagram *> diagrams = m_diagram->project()->diagrams();
|
||||
|
||||
Diagram *diagram = m_diagram;
|
||||
QString cell = needle;
|
||||
QString text;
|
||||
QRectF cell_rect = diagram->border_and_titleblock.cellRect(cell);
|
||||
if (cell_rect.isNull()) {
|
||||
//A folio position then a cell, like %f-%l%c in cross references
|
||||
static const QRegularExpression folio_cell_re(
|
||||
QStringLiteral("^p?\\s*(\\d{1,4})\\s*[-/.:]?\\s*([a-z]+\\s*\\d{1,4})$"));
|
||||
const QRegularExpressionMatch match = folio_cell_re.match(needle);
|
||||
if (!match.hasMatch()) {
|
||||
return nullptr;
|
||||
}
|
||||
const int folio = match.captured(1).toInt();
|
||||
if (folio < 1 || folio > diagrams.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
diagram = diagrams.at(folio - 1);
|
||||
cell = match.captured(2);
|
||||
cell_rect = diagram->border_and_titleblock.cellRect(cell);
|
||||
if (cell_rect.isNull()) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
cell = cell.remove(QLatin1Char(' ')).toUpper();
|
||||
if (diagram == m_diagram) {
|
||||
text = tr("Case %1").arg(cell);
|
||||
} else {
|
||||
const QString title = diagram->title();
|
||||
text = title.isEmpty()
|
||||
? tr("Folio %1, case %2").arg(diagrams.indexOf(diagram) + 1).arg(cell)
|
||||
: tr("Folio %1 (%2), case %3").arg(diagrams.indexOf(diagram) + 1).arg(title, cell);
|
||||
}
|
||||
|
||||
auto *item = new QListWidgetItem(text);
|
||||
item->setData(Qt::UserRole, -1);
|
||||
item->setData(Qt::UserRole + 1, cell_rect);
|
||||
item->setData(Qt::UserRole + 2, diagrams.indexOf(diagram));
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief JumpToElementDialog::zoomToCell
|
||||
Show \a diagram and zoom its view on \a cell_rect with one cell of
|
||||
context around it.
|
||||
@param diagram : the folio holding the cell
|
||||
@param cell_rect : the cell, in scene coordinate
|
||||
*/
|
||||
void JumpToElementDialog::zoomToCell(Diagram *diagram, const QRectF &cell_rect)
|
||||
{
|
||||
const QRectF rect = cell_rect.adjusted(-cell_rect.width(), -cell_rect.height(),
|
||||
cell_rect.width(), cell_rect.height());
|
||||
const bool other_folio = diagram != m_diagram;
|
||||
if (other_folio) {
|
||||
diagram->showMe();
|
||||
}
|
||||
for (QGraphicsView *view : diagram->views()) {
|
||||
if (auto *diagram_view = qobject_cast<DiagramView *>(view)) {
|
||||
if (other_folio) {
|
||||
//A folio shown for the first time is laid out at
|
||||
//its size once the tab switch has been handled
|
||||
QMetaObject::invokeMethod(diagram_view, [diagram_view, rect]() {
|
||||
diagram_view->zoomToRect(rect);
|
||||
}, Qt::QueuedConnection);
|
||||
} else {
|
||||
diagram_view->zoomToRect(rect);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief JumpToElementDialog::eventFilter
|
||||
Redirect Up/Down/Enter/Escape typed in the filter field to the result
|
||||
|
||||
@@ -25,13 +25,15 @@ class Diagram;
|
||||
class Element;
|
||||
class QLineEdit;
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
|
||||
/**
|
||||
@brief The JumpToElementDialog class
|
||||
A lightweight, transient "quick open" popup: type part of an element's
|
||||
label or other information to live-filter the elements on a diagram,
|
||||
then Enter to select the chosen element on the diagram and scroll it
|
||||
into view. Up/Down move through the filtered list, Escape cancels
|
||||
into view. Typing a cell of the border instead (ex : B13, or 3-B13 for
|
||||
the third folio of the project) offers to zoom on that cell. Up/Down move through the filtered list, Escape cancels
|
||||
without changing the current selection.
|
||||
*/
|
||||
class JumpToElementDialog : public QDialog
|
||||
@@ -51,9 +53,12 @@ class JumpToElementDialog : public QDialog
|
||||
|
||||
private:
|
||||
void buildCandidates();
|
||||
QListWidgetItem *cellItem(const QString &needle) const;
|
||||
void zoomToCell(Diagram *diagram, const QRectF &cell_rect);
|
||||
|
||||
struct Candidate {
|
||||
QPointer<Element> element;
|
||||
QString label;
|
||||
QString display_text;
|
||||
QString search_text;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user