mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-03 01:04:13 +02:00
Merge shortcut-bar (with master) into shortcut-bar-customise
This commit is contained in:
@@ -287,6 +287,8 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/shortcutmanager.h
|
${QET_DIR}/sources/shortcutmanager.h
|
||||||
${QET_DIR}/sources/shortcutbarsettings.cpp
|
${QET_DIR}/sources/shortcutbarsettings.cpp
|
||||||
${QET_DIR}/sources/shortcutbarsettings.h
|
${QET_DIR}/sources/shortcutbarsettings.h
|
||||||
|
${QET_DIR}/sources/commandsearchpopup.cpp
|
||||||
|
${QET_DIR}/sources/commandsearchpopup.h
|
||||||
${QET_DIR}/sources/titleblockcell.cpp
|
${QET_DIR}/sources/titleblockcell.cpp
|
||||||
${QET_DIR}/sources/titleblockcell.h
|
${QET_DIR}/sources/titleblockcell.h
|
||||||
${QET_DIR}/sources/titleblockproperties.cpp
|
${QET_DIR}/sources/titleblockproperties.cpp
|
||||||
|
|||||||
+26
-4
@@ -73,7 +73,7 @@ void CellRuler::paintEvent(QPaintEvent *event)
|
|||||||
Q_UNUSED(event)
|
Q_UNUSED(event)
|
||||||
|
|
||||||
QPainter painter(this);
|
QPainter painter(this);
|
||||||
painter.fillRect(rect(), palette().color(QPalette::Button));
|
painter.fillRect(rect(), background());
|
||||||
|
|
||||||
const bool horizontal = m_orientation == Qt::Horizontal;
|
const bool horizontal = m_orientation == Qt::Horizontal;
|
||||||
const int length = horizontal ? width() : height();
|
const int length = horizontal ? width() : height();
|
||||||
@@ -92,12 +92,15 @@ void CellRuler::paintEvent(QPaintEvent *event)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const BorderTitleBlock &border = diagram->border_and_titleblock;
|
const BorderTitleBlock &border = diagram->border_and_titleblock;
|
||||||
const QRectF inside = border.insideBorderRect();
|
|
||||||
const QTransform transform = m_view->viewportTransform();
|
const QTransform transform = m_view->viewportTransform();
|
||||||
|
|
||||||
const int count = horizontal ? border.columnsCount() : border.rowsCount();
|
const int count = horizontal ? border.columnsCount() : border.rowsCount();
|
||||||
const qreal cell_size = horizontal ? border.columnsWidth() : border.rowsHeight();
|
const qreal cell_size = horizontal ? border.columnsWidth() : border.rowsHeight();
|
||||||
const qreal first = horizontal ? inside.left() : inside.top();
|
//Where BorderTitleBlock::draw() puts the first cell: after the
|
||||||
|
//other header's room even when that header is hidden, which
|
||||||
|
//insideBorderRect() does not account for
|
||||||
|
const qreal first = Diagram::margin
|
||||||
|
+ (horizontal ? border.rowsHeaderWidth() : border.columnsHeaderHeight());
|
||||||
const qreal scale = horizontal ? transform.m11() : transform.m22();
|
const qreal scale = horizontal ? transform.m11() : transform.m22();
|
||||||
const qreal offset = (horizontal ? transform.dx() : transform.dy()) + m_leading_space;
|
const qreal offset = (horizontal ? transform.dx() : transform.dy()) + m_leading_space;
|
||||||
const qreal cell_pixels = cell_size * scale;
|
const qreal cell_pixels = cell_size * scale;
|
||||||
@@ -165,6 +168,25 @@ void CellRuler::paintEvent(QPaintEvent *event)
|
|||||||
if (m_leading_space > 0) {
|
if (m_leading_space > 0) {
|
||||||
painter.fillRect(horizontal ? QRect(0, 0, m_leading_space, depth - 1)
|
painter.fillRect(horizontal ? QRect(0, 0, m_leading_space, depth - 1)
|
||||||
: QRect(0, 0, depth - 1, m_leading_space),
|
: QRect(0, 0, depth - 1, m_leading_space),
|
||||||
palette().color(QPalette::Button));
|
background());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief CellRuler::background
|
||||||
|
@return the button colour laid over the window colour, always opaque.
|
||||||
|
The Windows 11 style gives buttons a translucent colour; filled with it
|
||||||
|
as is, a ruler (painted with Qt::WA_OpaquePaintEvent, so never cleared
|
||||||
|
first) would let every previous frame show through, and zooming would
|
||||||
|
leave a shadow of the old labels behind the new ones.
|
||||||
|
*/
|
||||||
|
QColor CellRuler::background() const
|
||||||
|
{
|
||||||
|
const QColor window = palette().color(QPalette::Window);
|
||||||
|
const QColor button = palette().color(QPalette::Button);
|
||||||
|
const qreal alpha = button.alphaF();
|
||||||
|
return QColor::fromRgbF(
|
||||||
|
button.redF() * alpha + window.redF() * (1 - alpha),
|
||||||
|
button.greenF() * alpha + window.greenF() * (1 - alpha),
|
||||||
|
button.blueF() * alpha + window.blueF() * (1 - alpha));
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ class CellRuler : public QWidget
|
|||||||
void paintEvent(QPaintEvent *event) override;
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QColor background() const;
|
||||||
|
|
||||||
Qt::Orientation m_orientation;
|
Qt::Orientation m_orientation;
|
||||||
DiagramView *m_view;
|
DiagramView *m_view;
|
||||||
/// Pixels before the viewport starts, left empty: the corner the
|
/// Pixels before the viewport starts, left empty: the corner the
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
/*
|
||||||
|
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 "commandsearchpopup.h"
|
||||||
|
|
||||||
|
#include "shortcutmanager.h"
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QKeyEvent>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief CommandSearchPopup::CommandSearchPopup
|
||||||
|
@param owner : the window whose commands are searched; also the parent
|
||||||
|
*/
|
||||||
|
CommandSearchPopup::CommandSearchPopup(QWidget *owner) :
|
||||||
|
QFrame(owner, Qt::Popup)
|
||||||
|
{
|
||||||
|
setFrameShape(QFrame::StyledPanel);
|
||||||
|
setFrameShadow(QFrame::Raised);
|
||||||
|
setMinimumWidth(380);
|
||||||
|
|
||||||
|
m_search = new QLineEdit(this);
|
||||||
|
m_search->setPlaceholderText(tr("Rechercher une commande…"));
|
||||||
|
m_search->setClearButtonEnabled(true);
|
||||||
|
m_list = new QListWidget(this);
|
||||||
|
m_list->setIconSize(QSize(20, 20));
|
||||||
|
m_list->setMinimumHeight(280);
|
||||||
|
m_list->setFocusPolicy(Qt::NoFocus);
|
||||||
|
|
||||||
|
auto *hint = new QLabel(tr("Entrée pour lancer · Échap pour fermer"), this);
|
||||||
|
hint->setEnabled(false);
|
||||||
|
|
||||||
|
auto *layout = new QVBoxLayout(this);
|
||||||
|
layout->setContentsMargins(6, 6, 6, 6);
|
||||||
|
layout->setSpacing(4);
|
||||||
|
layout->addWidget(m_search);
|
||||||
|
layout->addWidget(m_list);
|
||||||
|
layout->addWidget(hint);
|
||||||
|
|
||||||
|
connect(m_search, &QLineEdit::textChanged, this, &CommandSearchPopup::filter);
|
||||||
|
connect(m_list, &QListWidget::itemClicked, this, [this]() { runCurrent(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief CommandSearchPopup::fold
|
||||||
|
@return @a text lower-cased, without accents and without the "&" of
|
||||||
|
mnemonics, for matching
|
||||||
|
*/
|
||||||
|
QString CommandSearchPopup::fold(const QString &text)
|
||||||
|
{
|
||||||
|
QString out;
|
||||||
|
const QString decomposed = text.normalized(QString::NormalizationForm_D);
|
||||||
|
out.reserve(decomposed.size());
|
||||||
|
for (const QChar c : decomposed) {
|
||||||
|
if (c.category() != QChar::Mark_NonSpacing && c != QLatin1Char('&')) {
|
||||||
|
out.append(c.toLower());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief CommandSearchPopup::popUpAt
|
||||||
|
Rebuild the command list -- actions come and go with windows, and their
|
||||||
|
enabled state changes -- then show at @a global_pos, kept on screen.
|
||||||
|
*/
|
||||||
|
void CommandSearchPopup::popUpAt(const QPoint &global_pos)
|
||||||
|
{
|
||||||
|
collect();
|
||||||
|
m_search->clear();
|
||||||
|
filter();
|
||||||
|
|
||||||
|
adjustSize();
|
||||||
|
QPoint pos = global_pos;
|
||||||
|
if (QScreen *screen = QGuiApplication::screenAt(global_pos)) {
|
||||||
|
const QRect avail = screen->availableGeometry();
|
||||||
|
pos.setX(qBound(avail.left(), pos.x(), avail.right() - width()));
|
||||||
|
pos.setY(qBound(avail.top(), pos.y(), avail.bottom() - height()));
|
||||||
|
}
|
||||||
|
move(pos);
|
||||||
|
show();
|
||||||
|
m_search->setFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief CommandSearchPopup::collect
|
||||||
|
Every command registered by the owning window, except this search.
|
||||||
|
*/
|
||||||
|
void CommandSearchPopup::collect()
|
||||||
|
{
|
||||||
|
m_commands.clear();
|
||||||
|
for (const ShortcutManager::ShortcutInfo &info :
|
||||||
|
ShortcutManager::instance().allShortcuts())
|
||||||
|
{
|
||||||
|
if (info.id == QLatin1String("diagrameditor.command_search")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QAction *action = ShortcutManager::instance().action(info.id, parentWidget());
|
||||||
|
if (!action || !action->isVisible()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const QString text = action->text().remove(QLatin1Char('&'));
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
m_commands.append({action, text, fold(text)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief CommandSearchPopup::filter
|
||||||
|
Show the commands matching the search text, best first: a name starting
|
||||||
|
with it, then a word starting with it, then containing it. An empty
|
||||||
|
search lists everything, alphabetically.
|
||||||
|
*/
|
||||||
|
void CommandSearchPopup::filter()
|
||||||
|
{
|
||||||
|
const QString needle = fold(m_search->text().trimmed());
|
||||||
|
|
||||||
|
QList<QPair<int, const Command *>> hits;
|
||||||
|
for (const Command &command : std::as_const(m_commands))
|
||||||
|
{
|
||||||
|
int score = 0;
|
||||||
|
if (needle.isEmpty()) {
|
||||||
|
score = 1;
|
||||||
|
} else if (command.folded.startsWith(needle)) {
|
||||||
|
score = 3;
|
||||||
|
} else if (command.folded.contains(QLatin1Char(' ') + needle)) {
|
||||||
|
score = 2;
|
||||||
|
} else if (command.folded.contains(needle)) {
|
||||||
|
score = 1;
|
||||||
|
}
|
||||||
|
if (score) {
|
||||||
|
hits.append({score, &command});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::stable_sort(hits.begin(), hits.end(), [](const auto &a, const auto &b) {
|
||||||
|
if (a.first != b.first) {
|
||||||
|
return a.first > b.first;
|
||||||
|
}
|
||||||
|
return a.second->folded < b.second->folded;
|
||||||
|
});
|
||||||
|
|
||||||
|
m_list->clear();
|
||||||
|
for (const auto &hit : std::as_const(hits))
|
||||||
|
{
|
||||||
|
QAction *action = hit.second->action;
|
||||||
|
const QKeySequence key = action->shortcut();
|
||||||
|
auto *item = new QListWidgetItem(
|
||||||
|
action->icon(),
|
||||||
|
key.isEmpty() ? hit.second->text
|
||||||
|
: QStringLiteral("%1 (%2)").arg(hit.second->text,
|
||||||
|
key.toString(QKeySequence::NativeText)));
|
||||||
|
item->setData(Qt::UserRole, QVariant::fromValue(static_cast<void *>(action)));
|
||||||
|
if (!action->isEnabled()) {
|
||||||
|
item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
|
||||||
|
}
|
||||||
|
m_list->addItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Preselect the first command that can run, so Enter works at once
|
||||||
|
for (int i = 0 ; i < m_list->count() ; ++i) {
|
||||||
|
if (m_list->item(i)->flags() & Qt::ItemIsEnabled) {
|
||||||
|
m_list->setCurrentRow(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief CommandSearchPopup::runCurrent
|
||||||
|
Close, then trigger the highlighted command: it may open a dialog or
|
||||||
|
start a tool on the folio, which need the focus the popup holds.
|
||||||
|
*/
|
||||||
|
void CommandSearchPopup::runCurrent()
|
||||||
|
{
|
||||||
|
QListWidgetItem *item = m_list->currentItem();
|
||||||
|
if (!item || !(item->flags() & Qt::ItemIsEnabled)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto *action = static_cast<QAction *>(item->data(Qt::UserRole).value<void *>());
|
||||||
|
hide();
|
||||||
|
if (action) {
|
||||||
|
action->trigger();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief CommandSearchPopup::keyPressEvent
|
||||||
|
Up and Down move through the list while typing goes on in the search
|
||||||
|
field; Enter runs, Esc closes.
|
||||||
|
*/
|
||||||
|
void CommandSearchPopup::keyPressEvent(QKeyEvent *event)
|
||||||
|
{
|
||||||
|
switch (event->key())
|
||||||
|
{
|
||||||
|
case Qt::Key_Escape:
|
||||||
|
hide();
|
||||||
|
return;
|
||||||
|
case Qt::Key_Return:
|
||||||
|
case Qt::Key_Enter:
|
||||||
|
runCurrent();
|
||||||
|
return;
|
||||||
|
case Qt::Key_Down:
|
||||||
|
case Qt::Key_Up:
|
||||||
|
case Qt::Key_PageDown:
|
||||||
|
case Qt::Key_PageUp:
|
||||||
|
QCoreApplication::sendEvent(m_list, event);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
QFrame::keyPressEvent(event);
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
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 COMMANDSEARCHPOPUP_H
|
||||||
|
#define COMMANDSEARCHPOPUP_H
|
||||||
|
|
||||||
|
#include <QFrame>
|
||||||
|
#include <QList>
|
||||||
|
|
||||||
|
class QAction;
|
||||||
|
class QLineEdit;
|
||||||
|
class QListWidget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Type part of a command's name, press Enter to run it.
|
||||||
|
|
||||||
|
Lists every command the owning window registered with ShortcutManager,
|
||||||
|
best match first, with its key if it has one. Matching ignores case and
|
||||||
|
accents, so "editer" finds "Éditer". Disabled commands are listed,
|
||||||
|
greyed, but cannot be run.
|
||||||
|
*/
|
||||||
|
class CommandSearchPopup : public QFrame
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CommandSearchPopup(QWidget *owner);
|
||||||
|
|
||||||
|
void popUpAt(const QPoint &global_pos);
|
||||||
|
static QString fold(const QString &text);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void keyPressEvent(QKeyEvent *event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void collect();
|
||||||
|
void filter();
|
||||||
|
void runCurrent();
|
||||||
|
|
||||||
|
struct Command {
|
||||||
|
QAction *action;
|
||||||
|
QString text; ///< as shown
|
||||||
|
QString folded; ///< for matching
|
||||||
|
};
|
||||||
|
|
||||||
|
QLineEdit *m_search = nullptr;
|
||||||
|
QListWidget *m_list = nullptr;
|
||||||
|
QList<Command> m_commands;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COMMANDSEARCHPOPUP_H
|
||||||
+92
-8
@@ -30,6 +30,7 @@
|
|||||||
#include "qetgraphicsitem/conductortextitem.h"
|
#include "qetgraphicsitem/conductortextitem.h"
|
||||||
#include "qetgraphicsitem/independenttextitem.h"
|
#include "qetgraphicsitem/independenttextitem.h"
|
||||||
#include "qeticons.h"
|
#include "qeticons.h"
|
||||||
|
#include "qetpalette.h"
|
||||||
#include "titleblock/integrationmovetemplateshandler.h"
|
#include "titleblock/integrationmovetemplateshandler.h"
|
||||||
#include "ui/diagrampropertiesdialog.h"
|
#include "ui/diagrampropertiesdialog.h"
|
||||||
#include "ui/multipastedialog.h"
|
#include "ui/multipastedialog.h"
|
||||||
@@ -112,6 +113,7 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
|
|||||||
m_top_ruler = new CellRuler(Qt::Horizontal, this);
|
m_top_ruler = new CellRuler(Qt::Horizontal, this);
|
||||||
m_side_ruler = new CellRuler(Qt::Vertical, this);
|
m_side_ruler = new CellRuler(Qt::Vertical, this);
|
||||||
m_cell_rulers_shown = QSettings().value("diagrameditor/cell_rulers", false).toBool();
|
m_cell_rulers_shown = QSettings().value("diagrameditor/cell_rulers", false).toBool();
|
||||||
|
m_cell_lines_shown = QSettings().value("diagrameditor/cell_lines", false).toBool();
|
||||||
connect(&m_diagram->border_and_titleblock, &BorderTitleBlock::borderChanged, this, &DiagramView::updateCellRulers);
|
connect(&m_diagram->border_and_titleblock, &BorderTitleBlock::borderChanged, this, &DiagramView::updateCellRulers);
|
||||||
connect(&m_diagram->border_and_titleblock, &BorderTitleBlock::displayChanged, this, &DiagramView::updateCellRulers);
|
connect(&m_diagram->border_and_titleblock, &BorderTitleBlock::displayChanged, this, &DiagramView::updateCellRulers);
|
||||||
updateCellRulers();
|
updateCellRulers();
|
||||||
@@ -1254,6 +1256,72 @@ void DiagramView::paintingInverted(bool inverted)
|
|||||||
m_diagram->setInvertedLightness(inverted);
|
m_diagram->setInvertedLightness(inverted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief DiagramView::setCellLinesShown
|
||||||
|
Show or hide the lines that mark the columns and the rows of the folio
|
||||||
|
border across the drawing, in this view only: printing and exporting
|
||||||
|
never draw them.
|
||||||
|
@param shown
|
||||||
|
*/
|
||||||
|
void DiagramView::setCellLinesShown(bool shown)
|
||||||
|
{
|
||||||
|
m_cell_lines_shown = shown;
|
||||||
|
viewport()->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief DiagramView::drawBackground
|
||||||
|
Reimplemented from PaletteGraphicsView: over the folio background, the
|
||||||
|
cell lines when they are shown. Dashed and faint, so they do not read
|
||||||
|
as conductors, and under every item.
|
||||||
|
@param painter
|
||||||
|
@param rect
|
||||||
|
*/
|
||||||
|
void DiagramView::drawBackground(QPainter *painter, const QRectF &rect)
|
||||||
|
{
|
||||||
|
PaletteGraphicsView::drawBackground(painter, rect);
|
||||||
|
|
||||||
|
const BorderTitleBlock &border = m_diagram->border_and_titleblock;
|
||||||
|
if (!m_cell_lines_shown || !border.borderIsDisplayed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Where the border draws its cells, whether or not the other
|
||||||
|
//header is displayed
|
||||||
|
const QPointF origin(Diagram::margin + border.rowsHeaderWidth(),
|
||||||
|
Diagram::margin + border.columnsHeaderHeight());
|
||||||
|
const qreal right = origin.x() + border.columnsCount() * border.columnsWidth();
|
||||||
|
const qreal bottom = origin.y() + border.rowsCount() * border.rowsHeight();
|
||||||
|
|
||||||
|
QPainter *p = scenePainter(painter);
|
||||||
|
p->save();
|
||||||
|
p->setRenderHint(QPainter::Antialiasing, false);
|
||||||
|
QColor color = QET::Palette::gridDotColor(Diagram::background_color,
|
||||||
|
invertsLightness());
|
||||||
|
color.setAlpha(70);
|
||||||
|
QPen pen(color, 1, Qt::DashLine);
|
||||||
|
pen.setCosmetic(true);
|
||||||
|
p->setPen(pen);
|
||||||
|
|
||||||
|
if (border.columnsAreDisplayed()) {
|
||||||
|
for (int i = 1 ; i < border.columnsCount() ; ++i) {
|
||||||
|
const qreal x = origin.x() + i * border.columnsWidth();
|
||||||
|
if (x >= rect.left() && x <= rect.right()) {
|
||||||
|
p->drawLine(QPointF(x, origin.y()), QPointF(x, bottom));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (border.rowsAreDisplayed()) {
|
||||||
|
for (int i = 1 ; i < border.rowsCount() ; ++i) {
|
||||||
|
const qreal y = origin.y() + i * border.rowsHeight();
|
||||||
|
if (y >= rect.top() && y <= rect.bottom()) {
|
||||||
|
p->drawLine(QPointF(origin.x(), y), QPointF(right, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p->restore();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief DiagramView::setCellRulersShown
|
@brief DiagramView::setCellRulersShown
|
||||||
Show or hide the rulers that keep the column numbers and the row
|
Show or hide the rulers that keep the column numbers and the row
|
||||||
@@ -1268,17 +1336,24 @@ void DiagramView::setCellRulersShown(bool shown)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
@brief DiagramView::updateCellRulers
|
@brief DiagramView::updateCellRulers
|
||||||
Show each ruler when the rulers are wanted and the folio shows the
|
Show each ruler when the rulers are wanted, the folio shows the
|
||||||
matching header, and give it room in the margins of the view. The
|
matching header and that header is not already wholly in sight, and
|
||||||
part of the folio in sight stays in sight when the viewport resizes.
|
give it room in the margins of the view. The drawing does not move on
|
||||||
|
screen when a ruler comes or goes: the ruler covers or uncovers the
|
||||||
|
edge of the viewport, as if it lay over it.
|
||||||
*/
|
*/
|
||||||
void DiagramView::updateCellRulers()
|
void DiagramView::updateCellRulers()
|
||||||
{
|
{
|
||||||
const BorderTitleBlock &border = m_diagram->border_and_titleblock;
|
const BorderTitleBlock &border = m_diagram->border_and_titleblock;
|
||||||
|
const QRectF in_sight = mapToScene(viewport()->rect()).boundingRect();
|
||||||
|
const QRectF columns = border.columnsRect();
|
||||||
|
const QRectF rows = border.rowsRect();
|
||||||
const bool top = m_cell_rulers_shown
|
const bool top = m_cell_rulers_shown
|
||||||
&& border.borderIsDisplayed() && border.columnsAreDisplayed();
|
&& border.borderIsDisplayed() && border.columnsAreDisplayed()
|
||||||
|
&& (columns.top() < in_sight.top() || columns.bottom() > in_sight.bottom());
|
||||||
const bool side = m_cell_rulers_shown
|
const bool side = m_cell_rulers_shown
|
||||||
&& border.borderIsDisplayed() && border.rowsAreDisplayed();
|
&& border.borderIsDisplayed() && border.rowsAreDisplayed()
|
||||||
|
&& (rows.left() < in_sight.left() || rows.right() > in_sight.right());
|
||||||
const int thickness = m_top_ruler->thickness();
|
const int thickness = m_top_ruler->thickness();
|
||||||
|
|
||||||
m_top_ruler->setVisible(top);
|
m_top_ruler->setVisible(top);
|
||||||
@@ -1287,9 +1362,12 @@ void DiagramView::updateCellRulers()
|
|||||||
|
|
||||||
const QMargins margins(side ? thickness : 0, top ? thickness : 0, 0, 0);
|
const QMargins margins(side ? thickness : 0, top ? thickness : 0, 0, 0);
|
||||||
if (margins != viewportMargins()) {
|
if (margins != viewportMargins()) {
|
||||||
const QPointF centre = mapToScene(viewport()->rect().center());
|
const QPointF origin = mapToScene(viewport()->rect().center());
|
||||||
|
const QPoint before = viewport()->mapToGlobal(mapFromScene(origin));
|
||||||
setViewportMargins(margins);
|
setViewportMargins(margins);
|
||||||
centerOn(centre);
|
const QPoint moved = viewport()->mapToGlobal(mapFromScene(origin)) - before;
|
||||||
|
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + moved.x());
|
||||||
|
verticalScrollBar()->setValue(verticalScrollBar()->value() + moved.y());
|
||||||
}
|
}
|
||||||
placeCellRulers();
|
placeCellRulers();
|
||||||
m_top_ruler->update();
|
m_top_ruler->update();
|
||||||
@@ -1339,11 +1417,17 @@ void DiagramView::paintEvent(QPaintEvent *event)
|
|||||||
{
|
{
|
||||||
PaletteGraphicsView::paintEvent(event);
|
PaletteGraphicsView::paintEvent(event);
|
||||||
|
|
||||||
//Scrolling and zooming both repaint the viewport: follow them
|
//Scrolling and zooming both repaint the viewport: follow them.
|
||||||
|
//Showing or hiding a ruler resizes the viewport, which cannot be
|
||||||
|
//done while it paints.
|
||||||
if (viewportTransform() != m_rulers_transform) {
|
if (viewportTransform() != m_rulers_transform) {
|
||||||
m_rulers_transform = viewportTransform();
|
m_rulers_transform = viewportTransform();
|
||||||
m_top_ruler->update();
|
m_top_ruler->update();
|
||||||
m_side_ruler->update();
|
m_side_ruler->update();
|
||||||
|
if (m_cell_rulers_shown) {
|
||||||
|
QMetaObject::invokeMethod(this, &DiagramView::updateCellRulers,
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_free_rubberbanding && m_free_rubberband.count() >= 3)
|
if (m_free_rubberbanding && m_free_rubberband.count() >= 3)
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ class DiagramView : public PaletteGraphicsView
|
|||||||
bool m_cell_rulers_shown = false;
|
bool m_cell_rulers_shown = false;
|
||||||
/// Last viewport transform the rulers were painted for
|
/// Last viewport transform the rulers were painted for
|
||||||
QTransform m_rulers_transform;
|
QTransform m_rulers_transform;
|
||||||
|
bool m_cell_lines_shown = false;
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QString title() const;
|
QString title() const;
|
||||||
@@ -84,11 +85,12 @@ class DiagramView : public PaletteGraphicsView
|
|||||||
/// cursor query (QCursor::pos()/setPos() are silently ignored by
|
/// cursor query (QCursor::pos()/setPos() are silently ignored by
|
||||||
/// several window managers and compositors, Wayland included).
|
/// several window managers and compositors, Wayland included).
|
||||||
QPoint lastMousePos() const { return m_last_mouse_pos; }
|
QPoint lastMousePos() const { return m_last_mouse_pos; }
|
||||||
void setCellRulersShown(bool shown);
|
|
||||||
|
|
||||||
bool startElementPlacement(const ElementsLocation &location,
|
bool startElementPlacement(const ElementsLocation &location,
|
||||||
const QPointF &scene_pos);
|
const QPointF &scene_pos);
|
||||||
QPointF defaultPlacementPos() const;
|
QPointF defaultPlacementPos() const;
|
||||||
|
void setCellRulersShown(bool shown);
|
||||||
|
void setCellLinesShown(bool shown);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void mouseDoubleClickEvent(QMouseEvent *) override;
|
void mouseDoubleClickEvent(QMouseEvent *) override;
|
||||||
@@ -103,6 +105,7 @@ class DiagramView : public PaletteGraphicsView
|
|||||||
bool m_releasing_focus = false;
|
bool m_releasing_focus = false;
|
||||||
void paintEvent(QPaintEvent *event) override;
|
void paintEvent(QPaintEvent *event) override;
|
||||||
bool viewportEvent(QEvent *event) override;
|
bool viewportEvent(QEvent *event) override;
|
||||||
|
void drawBackground(QPainter *painter, const QRectF &rect) override;
|
||||||
void paintingInverted(bool inverted) override;
|
void paintingInverted(bool inverted) override;
|
||||||
void mousePressEvent(QMouseEvent *) override;
|
void mousePressEvent(QMouseEvent *) override;
|
||||||
void mouseMoveEvent(QMouseEvent *) override;
|
void mouseMoveEvent(QMouseEvent *) override;
|
||||||
|
|||||||
@@ -80,6 +80,11 @@ class PaletteGraphicsView : public QGraphicsView
|
|||||||
nothing by default.
|
nothing by default.
|
||||||
*/
|
*/
|
||||||
virtual void paintingInverted(bool inverted);
|
virtual void paintingInverted(bool inverted);
|
||||||
|
/// @return the painter the scene really paints with: the
|
||||||
|
/// off-screen image's while painting inverted, else \a painter.
|
||||||
|
/// For subclasses that draw more in drawBackground().
|
||||||
|
QPainter *scenePainter(QPainter *painter)
|
||||||
|
{ return m_inverting ? &m_buffer_painter : painter; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void paintInverted(QPaintEvent *event);
|
void paintInverted(QPaintEvent *event);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "ElementsCollection/elementpickerpopup.h"
|
#include "ElementsCollection/elementpickerpopup.h"
|
||||||
#include "shortcutbarsettings.h"
|
#include "shortcutbarsettings.h"
|
||||||
#include "qetgraphicsitem/conductor.h"
|
#include "qetgraphicsitem/conductor.h"
|
||||||
|
#include "commandsearchpopup.h"
|
||||||
#include "QWidgetAnimation/qwidgetanimation.h"
|
#include "QWidgetAnimation/qwidgetanimation.h"
|
||||||
#include "autoNum/ui/autonumberingdockwidget.h"
|
#include "autoNum/ui/autonumberingdockwidget.h"
|
||||||
#include "conductornumexport.h"
|
#include "conductornumexport.h"
|
||||||
@@ -545,6 +546,18 @@ void QETDiagramEditor::setUpActions()
|
|||||||
dv->setCellRulersShown(checked);
|
dv->setCellRulersShown(checked);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Draw the limits of the folio columns and rows across the drawing
|
||||||
|
m_cell_lines = new QAction(tr("Afficher les limites des cases"), this);
|
||||||
|
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());
|
||||||
|
connect(m_cell_lines, &QAction::triggered, [this](bool checked) {
|
||||||
|
QSettings().setValue("diagrameditor/cell_lines", checked);
|
||||||
|
foreach (ProjectView *prjv, this->openedProjects())
|
||||||
|
foreach (DiagramView *dv, prjv->diagram_views())
|
||||||
|
dv->setCellLinesShown(checked);
|
||||||
|
});
|
||||||
|
|
||||||
//Edit current diagram properties
|
//Edit current diagram properties
|
||||||
m_edit_diagram_properties = new QAction(QET::Icons::DialogInformation, tr("Propriétés du folio"), this);
|
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);
|
ShortcutManager::instance().registerAction(m_edit_diagram_properties, "diagrameditor.edit_diagram_properties", tr("Éditeur de schémas"), Qt::CTRL | Qt::Key_L);
|
||||||
@@ -826,6 +839,26 @@ void QETDiagramEditor::setUpActions()
|
|||||||
connect(m_insert_last_element, &QAction::triggered,
|
connect(m_insert_last_element, &QAction::triggered,
|
||||||
this, &QETDiagramEditor::insertLastElement);
|
this, &QETDiagramEditor::insertLastElement);
|
||||||
addAction(m_insert_last_element);
|
addAction(m_insert_last_element);
|
||||||
|
//Type to find and run any command, as SolidWorks' "Search Commands"
|
||||||
|
//and the command palette of many editors. Ctrl+Shift+P, the key those
|
||||||
|
//editors use, is taken by the autonumbering dock; M for "menu".
|
||||||
|
m_command_search = new QAction(tr("Rechercher une commande…"), this);
|
||||||
|
m_command_search->setStatusTip(
|
||||||
|
tr("Tapez une partie du nom d'une commande et appuyez sur Entrée pour la lancer",
|
||||||
|
"status bar tip"));
|
||||||
|
ShortcutManager::instance().registerAction(
|
||||||
|
m_command_search, "diagrameditor.command_search",
|
||||||
|
tr("Éditeur de schémas"), Qt::CTRL | Qt::SHIFT | Qt::Key_M);
|
||||||
|
connect(m_command_search, &QAction::triggered, this, [this]() {
|
||||||
|
if (!m_command_search_popup) {
|
||||||
|
m_command_search_popup = new CommandSearchPopup(this);
|
||||||
|
}
|
||||||
|
const QRect area = geometry();
|
||||||
|
m_command_search_popup->popUpAt(
|
||||||
|
area.contains(QCursor::pos()) ? QCursor::pos()
|
||||||
|
: area.center());
|
||||||
|
});
|
||||||
|
addAction(m_command_search);
|
||||||
|
|
||||||
//Cursor-anchored picker. Insert is unbound anywhere in the tree and
|
//Cursor-anchored picker. Insert is unbound anywhere in the tree and
|
||||||
//reads correctly for the action, which keeps A free for the far more
|
//reads correctly for the action, which keeps A free for the far more
|
||||||
@@ -988,8 +1021,9 @@ void QETDiagramEditor::setUpActions()
|
|||||||
add_path->setCheckable(true);
|
add_path->setCheckable(true);
|
||||||
|
|
||||||
connect(&m_add_item_actions_group, &QActionGroup::triggered, this, &QETDiagramEditor::addItemGroupTriggered);
|
connect(&m_add_item_actions_group, &QActionGroup::triggered, this, &QETDiagramEditor::addItemGroupTriggered);
|
||||||
//No default key, but an id: they can then be bound in the Shortcuts
|
//No default key, but an id: they can then be found by the command
|
||||||
//page and placed on the shortcut bar, like every other command.
|
//search, bound in the Shortcuts page and placed on the shortcut bar,
|
||||||
|
//like every other command.
|
||||||
for (QAction *action : m_add_item_actions_group.actions()) {
|
for (QAction *action : m_add_item_actions_group.actions()) {
|
||||||
ShortcutManager::instance().registerAction(
|
ShortcutManager::instance().registerAction(
|
||||||
action, "diagrameditor.add_" + action->data().toString(),
|
action, "diagrameditor.add_" + action->data().toString(),
|
||||||
@@ -1151,6 +1185,7 @@ void QETDiagramEditor::setUpMenu()
|
|||||||
menu_edition -> addAction(m_insert_last_element);
|
menu_edition -> addAction(m_insert_last_element);
|
||||||
menu_edition -> addAction(m_show_element_picker);
|
menu_edition -> addAction(m_show_element_picker);
|
||||||
menu_edition -> addAction(m_show_shortcut_bar);
|
menu_edition -> addAction(m_show_shortcut_bar);
|
||||||
|
menu_edition -> addAction(m_command_search);
|
||||||
menu_edition -> addSeparator();
|
menu_edition -> addSeparator();
|
||||||
//The same actions the "Ajouter" toolbar holds. They were toolbar-only,
|
//The same actions the "Ajouter" toolbar holds. They were toolbar-only,
|
||||||
//which left them unreachable for anyone working without a mouse: a
|
//which left them unreachable for anyone working without a mouse: a
|
||||||
@@ -1231,6 +1266,7 @@ void QETDiagramEditor::setUpMenu()
|
|||||||
menu_affichage -> addMenu(m_text_grid_menu);
|
menu_affichage -> addMenu(m_text_grid_menu);
|
||||||
menu_affichage -> addAction(m_draw_guides);
|
menu_affichage -> addAction(m_draw_guides);
|
||||||
menu_affichage -> addAction(m_cell_rulers);
|
menu_affichage -> addAction(m_cell_rulers);
|
||||||
|
menu_affichage -> addAction(m_cell_lines);
|
||||||
menu_affichage -> addMenu(m_background_color_button->menu());
|
menu_affichage -> addMenu(m_background_color_button->menu());
|
||||||
menu_affichage -> addSeparator();
|
menu_affichage -> addSeparator();
|
||||||
menu_affichage -> addActions(m_zoom_actions_group.actions());
|
menu_affichage -> addActions(m_zoom_actions_group.actions());
|
||||||
@@ -2066,6 +2102,7 @@ void QETDiagramEditor::slot_updateActions()
|
|||||||
m_draw_grid-> setEnabled(opened_diagram);
|
m_draw_grid-> setEnabled(opened_diagram);
|
||||||
m_draw_guides-> setEnabled(opened_diagram);
|
m_draw_guides-> setEnabled(opened_diagram);
|
||||||
m_cell_rulers-> setEnabled(opened_diagram);
|
m_cell_rulers-> setEnabled(opened_diagram);
|
||||||
|
m_cell_lines-> setEnabled(opened_diagram);
|
||||||
|
|
||||||
//Project menu
|
//Project menu
|
||||||
m_project_edit_properties -> setEnabled(opened_project);
|
m_project_edit_properties -> setEnabled(opened_project);
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class RecentFiles;
|
|||||||
class DiagramPropertiesEditorDockWidget;
|
class DiagramPropertiesEditorDockWidget;
|
||||||
class ElementsCollectionWidget;
|
class ElementsCollectionWidget;
|
||||||
class ElementPickerPopup;
|
class ElementPickerPopup;
|
||||||
|
class CommandSearchPopup;
|
||||||
class AutoNumberingDockWidget;
|
class AutoNumberingDockWidget;
|
||||||
class TerminalNumberingDialog;
|
class TerminalNumberingDialog;
|
||||||
|
|
||||||
@@ -219,6 +220,7 @@ class QETDiagramEditor : public QETMainWindow
|
|||||||
*m_draw_grid, ///< Switch the background grid display or not
|
*m_draw_grid, ///< Switch the background grid display or not
|
||||||
*m_draw_guides = nullptr, ///< Switch the custom guides 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_cell_rulers = nullptr, ///< Keep the folio column/row headers in sight or not
|
||||||
|
*m_cell_lines = nullptr, ///< Draw the folio column/row limits across the drawing or not
|
||||||
*m_project_edit_properties, ///< Edit the properties of the current project.
|
*m_project_edit_properties, ///< Edit the properties of the current project.
|
||||||
*m_project_add_diagram, ///< Add a diagram to 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
|
*m_remove_diagram_from_project, ///< Delete a diagram from the current project
|
||||||
@@ -278,6 +280,8 @@ class QETDiagramEditor : public QETMainWindow
|
|||||||
QAction *m_show_element_picker = nullptr;
|
QAction *m_show_element_picker = nullptr;
|
||||||
QAction *m_show_shortcut_bar = nullptr;
|
QAction *m_show_shortcut_bar = nullptr;
|
||||||
ElementPickerPopup *m_element_picker = nullptr; ///< Built on first use
|
ElementPickerPopup *m_element_picker = nullptr; ///< Built on first use
|
||||||
|
QAction *m_command_search = nullptr;
|
||||||
|
CommandSearchPopup *m_command_search_popup = nullptr; ///< Built on first use
|
||||||
ElementsCollectionWidget *m_element_collection_widget;
|
ElementsCollectionWidget *m_element_collection_widget;
|
||||||
/// Last element placed from the collection, for "insert last"
|
/// Last element placed from the collection, for "insert last"
|
||||||
ElementsLocation m_last_inserted_element;
|
ElementsLocation m_last_inserted_element;
|
||||||
|
|||||||
@@ -156,6 +156,19 @@ add_test(NAME tst_qetstrings COMMAND tst_qetstrings)
|
|||||||
target_include_directories(tst_qetstrings PRIVATE ${QET_DIR}/sources)
|
target_include_directories(tst_qetstrings PRIVATE ${QET_DIR}/sources)
|
||||||
target_link_libraries(tst_qetstrings PRIVATE Qt::Test Qt::Widgets Qt::Xml pugixml::pugixml)
|
target_link_libraries(tst_qetstrings PRIVATE Qt::Test Qt::Widgets Qt::Xml pugixml::pugixml)
|
||||||
|
|
||||||
|
# CommandSearchPopup: accent-blind matching, ranking, and that Enter runs
|
||||||
|
# only the owning window's highlighted, enabled command.
|
||||||
|
add_executable(
|
||||||
|
tst_commandsearch
|
||||||
|
tst_commandsearch.cpp
|
||||||
|
${QET_DIR}/sources/commandsearchpopup.cpp
|
||||||
|
${QET_DIR}/sources/commandsearchpopup.h
|
||||||
|
${QET_DIR}/sources/shortcutmanager.cpp)
|
||||||
|
add_test(NAME tst_commandsearch COMMAND tst_commandsearch)
|
||||||
|
set_tests_properties(tst_commandsearch PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
|
||||||
|
target_include_directories(tst_commandsearch PRIVATE ${QET_DIR}/sources)
|
||||||
|
target_link_libraries(tst_commandsearch PRIVATE Qt::Test Qt::Widgets)
|
||||||
|
|
||||||
# QETSql::isSingleReadOnlyStatement() -- read-only enforcement for every
|
# QETSql::isSingleReadOnlyStatement() -- read-only enforcement for every
|
||||||
# project-database query, including the ones a .qet file carries. Compiles
|
# project-database query, including the ones a .qet file carries. Compiles
|
||||||
# sqlreadonly.cpp alone against its own in-memory SQLite, so the security
|
# sqlreadonly.cpp alone against its own in-memory SQLite, so the security
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
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 "commandsearchpopup.h"
|
||||||
|
#include "shortcutmanager.h"
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QSignalSpy>
|
||||||
|
#include <QTest>
|
||||||
|
|
||||||
|
/**
|
||||||
|
CommandSearchPopup: accent- and case-blind matching, ranking, and that
|
||||||
|
Enter runs the highlighted command of the owning window only.
|
||||||
|
*/
|
||||||
|
class tst_commandsearch : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
QAction *add(QWidget *owner, const QString &id, const QString &text)
|
||||||
|
{
|
||||||
|
auto *action = new QAction(text, owner);
|
||||||
|
ShortcutManager::instance().registerAction(action, id, QStringLiteral("test"), QKeySequence());
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList shown(CommandSearchPopup &popup)
|
||||||
|
{
|
||||||
|
QStringList texts;
|
||||||
|
auto *list = popup.findChild<QListWidget *>();
|
||||||
|
for (int i = 0 ; i < list->count() ; ++i) {
|
||||||
|
texts << list->item(i)->text();
|
||||||
|
}
|
||||||
|
return texts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void fold_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QString>("input");
|
||||||
|
QTest::addColumn<QString>("expected");
|
||||||
|
QTest::newRow("accents") << QStringLiteral("Éditer l'élément") << QStringLiteral("editer l'element");
|
||||||
|
QTest::newRow("mnemonic") << QStringLiteral("&Fichier") << QStringLiteral("fichier");
|
||||||
|
QTest::newRow("cedilla") << QStringLiteral("Façade") << QStringLiteral("facade");
|
||||||
|
QTest::newRow("plain") << QStringLiteral("zoom") << QStringLiteral("zoom");
|
||||||
|
}
|
||||||
|
|
||||||
|
void fold()
|
||||||
|
{
|
||||||
|
QFETCH(QString, input);
|
||||||
|
QFETCH(QString, expected);
|
||||||
|
QCOMPARE(CommandSearchPopup::fold(input), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ranksAndRunsTheBestMatch()
|
||||||
|
{
|
||||||
|
QWidget owner;
|
||||||
|
QWidget other;
|
||||||
|
QAction *rotate = add(&owner, QStringLiteral("t.rotate"), QStringLiteral("Pivoter"));
|
||||||
|
add(&owner, QStringLiteral("t.rotate_texts"), QStringLiteral("Orienter les textes"));
|
||||||
|
QAction *edit = add(&owner, QStringLiteral("t.edit"), QStringLiteral("Éditer l'item sélectionné"));
|
||||||
|
add(&owner, QStringLiteral("t.text"), QStringLiteral("Ajouter un champ de texte"));
|
||||||
|
//Same id family, another window: must not be listed
|
||||||
|
add(&other, QStringLiteral("t.other"), QStringLiteral("Texte d'une autre fenêtre"));
|
||||||
|
|
||||||
|
CommandSearchPopup popup(&owner);
|
||||||
|
popup.popUpAt(QPoint(0, 0));
|
||||||
|
auto *search = popup.findChild<QLineEdit *>();
|
||||||
|
|
||||||
|
search->setText(QStringLiteral("texte"));
|
||||||
|
//word start ("…textes") before a contained match; other window absent
|
||||||
|
QCOMPARE(shown(popup), (QStringList{QStringLiteral("Ajouter un champ de texte"),
|
||||||
|
QStringLiteral("Orienter les textes")}));
|
||||||
|
|
||||||
|
search->setText(QStringLiteral("editer"));
|
||||||
|
QCOMPARE(shown(popup).value(0), QStringLiteral("Éditer l'item sélectionné"));
|
||||||
|
QSignalSpy edited(edit, &QAction::triggered);
|
||||||
|
QSignalSpy rotated(rotate, &QAction::triggered);
|
||||||
|
QTest::keyClick(&popup, Qt::Key_Return);
|
||||||
|
QCOMPARE(edited.count(), 1);
|
||||||
|
QCOMPARE(rotated.count(), 0);
|
||||||
|
QVERIFY(!popup.isVisible());
|
||||||
|
}
|
||||||
|
|
||||||
|
void disabledCommandsCannotRun()
|
||||||
|
{
|
||||||
|
QWidget owner;
|
||||||
|
QAction *paste = add(&owner, QStringLiteral("u.paste"), QStringLiteral("Coller"));
|
||||||
|
paste->setEnabled(false);
|
||||||
|
CommandSearchPopup popup(&owner);
|
||||||
|
popup.popUpAt(QPoint(0, 0));
|
||||||
|
popup.findChild<QLineEdit *>()->setText(QStringLiteral("coller"));
|
||||||
|
QCOMPARE(shown(popup), QStringList{QStringLiteral("Coller")});
|
||||||
|
QSignalSpy pasted(paste, &QAction::triggered);
|
||||||
|
QTest::keyClick(&popup, Qt::Key_Return);
|
||||||
|
QCOMPARE(pasted.count(), 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QTEST_MAIN(tst_commandsearch)
|
||||||
|
#include "tst_commandsearch.moc"
|
||||||
Reference in New Issue
Block a user