mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-25 07:24:21 +01:00
Merge remote-tracking branch 'origin/master' into fix-crossref-textfield
This commit is contained in:
@@ -450,6 +450,34 @@ void Diagram::wheelEvent(QGraphicsSceneWheelEvent *event)
|
||||
QGraphicsScene::wheelEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::event
|
||||
QGraphicsScene has its own Tab/Shift+Tab item-focus-chain traversal
|
||||
(mirroring QWidget's), checked before keyPressEvent() is ever reached:
|
||||
by default it would silently consume Tab/Backtab to move focus among
|
||||
the scene's own focusable items. Intercepting the key press here,
|
||||
ahead of that, is the only way to reliably override it: unlike
|
||||
QWidget::focusNextPrevChild(), QGraphicsScene::focusNextPrevChild() is
|
||||
only virtual starting in Qt 6 (guarded by the QT6_VIRTUAL macro), so a
|
||||
Diagram:: override of it would silently do nothing on a Qt 5 build.
|
||||
@param event
|
||||
*/
|
||||
bool Diagram::event(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
auto *key_event = static_cast<QKeyEvent *>(event);
|
||||
if ((key_event->key() == Qt::Key_Tab || key_event->key() == Qt::Key_Backtab)
|
||||
&& !isReadOnly() && !focusItem()) {
|
||||
bool forward = key_event->key() == Qt::Key_Tab
|
||||
&& !(key_event->modifiers() & Qt::ShiftModifier);
|
||||
selectNextItem(forward);
|
||||
event->accept();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return QGraphicsScene::event(event);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::keyPressEvent
|
||||
This event is managed by diagram event interface if any.
|
||||
@@ -1902,6 +1930,85 @@ void Diagram::invertSelection()
|
||||
emit selectionChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::selectAllConductors
|
||||
Select every conductor on this diagram, deselecting anything else.
|
||||
*/
|
||||
void Diagram::selectAllConductors()
|
||||
{
|
||||
if (items().isEmpty()) return;
|
||||
|
||||
blockSignals(true);
|
||||
for (auto item : items()) {
|
||||
item -> setSelected(dynamic_cast<Conductor *>(item) != nullptr);
|
||||
}
|
||||
blockSignals(false);
|
||||
emit selectionChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::selectAllTextFields
|
||||
Select every text field on this diagram (independent/static text,
|
||||
conductor labels, and dynamic element texts), deselecting anything else.
|
||||
*/
|
||||
void Diagram::selectAllTextFields()
|
||||
{
|
||||
if (items().isEmpty()) return;
|
||||
|
||||
blockSignals(true);
|
||||
for (auto item : items()) {
|
||||
item -> setSelected(dynamic_cast<DiagramTextItem *>(item) != nullptr);
|
||||
}
|
||||
blockSignals(false);
|
||||
emit selectionChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::selectNextItem
|
||||
Select the next (or, if @a forward is false, the previous) selectable
|
||||
item on this diagram, cycling through items() (z-order) and wrapping
|
||||
around at either end. If nothing is currently selected, selects the
|
||||
first (or last) item. Uses the same "what counts as a real selectable
|
||||
diagram item" filter as invertSelection(), so the candidate list and
|
||||
its order always match what the user could reach by clicking.
|
||||
@param forward true to select the next item, false for the previous one
|
||||
*/
|
||||
void Diagram::selectNextItem(bool forward)
|
||||
{
|
||||
QList<QGraphicsItem *> candidates;
|
||||
for (auto item : items()) {
|
||||
if (dynamic_cast<QetGraphicsItem *>(item) ||
|
||||
dynamic_cast<DiagramTextItem *>(item) ||
|
||||
dynamic_cast<Conductor *>(item)) {
|
||||
candidates << item;
|
||||
}
|
||||
}
|
||||
if (candidates.isEmpty())
|
||||
return;
|
||||
|
||||
int current_index = -1;
|
||||
for (int i = 0; i < candidates.size(); ++i) {
|
||||
if (candidates.at(i) -> isSelected()) {
|
||||
current_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int next_index;
|
||||
if (current_index == -1) {
|
||||
next_index = forward ? 0 : candidates.size() - 1;
|
||||
} else {
|
||||
next_index = forward
|
||||
? (current_index + 1) % candidates.size()
|
||||
: (current_index - 1 + candidates.size()) % candidates.size();
|
||||
}
|
||||
|
||||
clearSelection();
|
||||
QGraphicsItem *next_item = candidates.at(next_index);
|
||||
next_item -> setSelected(true);
|
||||
next_item -> ensureVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::insertFolioSeqHash
|
||||
This class inserts a stringlist containing all
|
||||
|
||||
+6
-1
@@ -150,8 +150,11 @@ class Diagram : public QGraphicsScene
|
||||
void wheelEvent (QGraphicsSceneWheelEvent *event) override;
|
||||
void keyPressEvent (QKeyEvent *event) override;
|
||||
void keyReleaseEvent (QKeyEvent *) override;
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
private:
|
||||
void selectNextItem(bool forward);
|
||||
|
||||
|
||||
public:
|
||||
void correctTextPos(Element* elmt);
|
||||
void restoreText(Element* elmt);
|
||||
@@ -287,6 +290,8 @@ class Diagram : public QGraphicsScene
|
||||
void selectAll();
|
||||
void deselectAll();
|
||||
void invertSelection();
|
||||
void selectAllConductors();
|
||||
void selectAllTextFields();
|
||||
|
||||
signals:
|
||||
void showDiagram (Diagram *);
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
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 "diagrameventaddpaste.h"
|
||||
|
||||
#include "../diagram.h"
|
||||
#include "../diagramcommands.h"
|
||||
#include "../qetapp.h"
|
||||
#include "../qetdiagrameditor.h"
|
||||
#include "../qetgraphicsitem/conductor.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QStatusBar>
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPaste::DiagramEventAddPaste
|
||||
@param diagram : diagram to paste into
|
||||
@param start_pos : where the pasted items first appear, in scene
|
||||
coordinates -- normally the cursor
|
||||
*/
|
||||
DiagramEventAddPaste::DiagramEventAddPaste(Diagram *diagram, const QPointF &start_pos) :
|
||||
DiagramEventInterface(diagram)
|
||||
{
|
||||
//DiagramEventInterface::init() is called by Diagram::setEventInterface
|
||||
//only when it is replacing an earlier interface, so call it here as
|
||||
//DiagramEventAddMacro does.
|
||||
init();
|
||||
|
||||
const QString clipboard_text = QApplication::clipboard()->text();
|
||||
if (clipboard_text.isEmpty()) return;
|
||||
|
||||
QDomDocument document_xml;
|
||||
if (!document_xml.setContent(clipboard_text)) return;
|
||||
|
||||
m_diagram->fromXml(document_xml, Diagram::snapToGrid(start_pos), false, &m_content);
|
||||
if (!m_content.count()) return;
|
||||
|
||||
//Remember where each item sits relative to the group's top left, so a
|
||||
//move is one assignment per item rather than an accumulated delta.
|
||||
QRectF group_rect;
|
||||
const QList<QGraphicsItem *> movable = m_content.items(MovableItems);
|
||||
for (auto *item : movable) {
|
||||
group_rect = group_rect.united(item->mapToScene(item->boundingRect()).boundingRect());
|
||||
}
|
||||
const QPointF top_left = group_rect.topLeft();
|
||||
for (auto *item : movable) {
|
||||
m_relative_pos.insert(item, item->pos() - top_left);
|
||||
}
|
||||
|
||||
m_diagram->clearSelection();
|
||||
for (auto *item : movable) {
|
||||
item->setSelected(true);
|
||||
}
|
||||
|
||||
if (!m_diagram->views().isEmpty()) {
|
||||
if (const auto qde = QETApp::diagramEditorAncestorOf(m_diagram->views().at(0))) {
|
||||
m_status_bar = qde->statusBar();
|
||||
}
|
||||
}
|
||||
showHint();
|
||||
|
||||
m_running = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPaste::~DiagramEventAddPaste
|
||||
If the placement never finished -- the editor closed, or another tool took
|
||||
over -- the items are still on the folio with nothing on the undo stack to
|
||||
account for them, so take them away.
|
||||
*/
|
||||
DiagramEventAddPaste::~DiagramEventAddPaste()
|
||||
{
|
||||
if (!m_finished && m_diagram) {
|
||||
cancel();
|
||||
}
|
||||
if (m_status_bar) {
|
||||
m_status_bar->clearMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPaste::clipboardHasDiagram
|
||||
@return true if the clipboard holds a diagram fragment
|
||||
*/
|
||||
bool DiagramEventAddPaste::clipboardHasDiagram()
|
||||
{
|
||||
return Diagram::clipboardMayContainDiagram();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPaste::init
|
||||
Suppress the context menu while placing, so a right click can cancel
|
||||
instead of opening a menu over the items being positioned.
|
||||
*/
|
||||
void DiagramEventAddPaste::init()
|
||||
{
|
||||
if (!m_diagram) return;
|
||||
const auto views = m_diagram->views();
|
||||
for (auto *view : views) {
|
||||
view->setContextMenuPolicy(Qt::NoContextMenu);
|
||||
}
|
||||
}
|
||||
|
||||
void DiagramEventAddPaste::showHint()
|
||||
{
|
||||
if (m_status_bar) {
|
||||
m_status_bar->showMessage(
|
||||
tr("Cliquez pour poser le collage, Échap ou clic droit pour annuler",
|
||||
"status bar tip while positioning a paste"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPaste::moveTo
|
||||
Put the group's top left corner at @a scene_pos, snapped to the grid.
|
||||
*/
|
||||
void DiagramEventAddPaste::moveTo(const QPointF &scene_pos)
|
||||
{
|
||||
const QPointF anchor = Diagram::snapToGrid(scene_pos);
|
||||
for (auto it = m_relative_pos.constBegin() ; it != m_relative_pos.constEnd() ; ++it) {
|
||||
if (it.key()) {
|
||||
it.key()->setPos(anchor + it.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DiagramEventAddPaste::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (!m_running) return;
|
||||
moveTo(event->scenePos());
|
||||
event->setAccepted(true);
|
||||
}
|
||||
|
||||
void DiagramEventAddPaste::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (!m_running) return;
|
||||
//Swallowed so the press cannot start a rubber band or drag an item
|
||||
//out of the group; the release is what decides.
|
||||
event->setAccepted(true);
|
||||
}
|
||||
|
||||
void DiagramEventAddPaste::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (!m_running) return;
|
||||
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
moveTo(event->scenePos());
|
||||
commit();
|
||||
} else if (event->button() == Qt::RightButton) {
|
||||
cancel();
|
||||
}
|
||||
event->setAccepted(true);
|
||||
}
|
||||
|
||||
void DiagramEventAddPaste::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
if (!m_running) return;
|
||||
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Escape:
|
||||
cancel();
|
||||
event->setAccepted(true);
|
||||
break;
|
||||
//Return and Enter drop the paste where it stands, so the whole
|
||||
//operation can be completed without a mouse.
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Enter:
|
||||
commit();
|
||||
event->setAccepted(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPaste::commit
|
||||
Hand the items to the undo stack where they stand.
|
||||
|
||||
PasteDiagramCommand's first redo() does not add the items to the scene --
|
||||
it assumes they are already there, which is what Diagram::fromXml did when
|
||||
this started. So pushing it here adopts them rather than duplicating them.
|
||||
*/
|
||||
void DiagramEventAddPaste::commit()
|
||||
{
|
||||
if (m_finished || !m_diagram) return;
|
||||
m_finished = true;
|
||||
m_running = false;
|
||||
|
||||
m_diagram->undoStack().push(new PasteDiagramCommand(m_diagram, m_content));
|
||||
emit finish();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPaste::cancel
|
||||
Take the items back off the folio. Nothing was pushed to the undo stack,
|
||||
so there is nothing to undo afterwards.
|
||||
*/
|
||||
void DiagramEventAddPaste::cancel()
|
||||
{
|
||||
if (m_finished || !m_diagram) return;
|
||||
m_finished = true;
|
||||
m_running = false;
|
||||
|
||||
//Conductors first: they hold pointers to the terminals of the
|
||||
//elements below, so removing an element out from under one would
|
||||
//leave it pointing at freed memory for as long as it is still in the
|
||||
//scene.
|
||||
const QList<Conductor *> conductors = m_content.conductors(DiagramContent::AnyConductor);
|
||||
for (auto *conductor : conductors) {
|
||||
m_diagram->removeItem(conductor);
|
||||
delete conductor;
|
||||
}
|
||||
|
||||
const QList<QGraphicsItem *> rest = m_content.items(MovableItems);
|
||||
for (auto *item : rest) {
|
||||
m_diagram->removeItem(item);
|
||||
delete item;
|
||||
}
|
||||
|
||||
m_content.clear();
|
||||
m_relative_pos.clear();
|
||||
emit finish();
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
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 DIAGRAMEVENTADDPASTE_H
|
||||
#define DIAGRAMEVENTADDPASTE_H
|
||||
|
||||
#include "diagrameventinterface.h"
|
||||
#include "../diagramcontent.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QPointer>
|
||||
|
||||
class QStatusBar;
|
||||
|
||||
/**
|
||||
@brief The DiagramEventAddPaste class
|
||||
Paste the clipboard as a placement you can still move.
|
||||
|
||||
The pasted items are added straight away and follow the cursor until a
|
||||
left click drops them; Escape or a right click takes them away again.
|
||||
This is the same interaction as placing a new element, so a paste behaves
|
||||
like every other "put something on the folio" action.
|
||||
|
||||
The items are the real ones from the start, not a preview: Diagram::fromXml
|
||||
creates them, this class moves them, and PasteDiagramCommand is pushed only
|
||||
once they are dropped. That keeps one copy of the paste logic rather than
|
||||
two, and means a cancelled paste leaves nothing on the undo stack.
|
||||
*/
|
||||
class DiagramEventAddPaste : public DiagramEventInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
///Items with a position of their own. Conductors are left out
|
||||
///deliberately: they are drawn from their terminals, so they
|
||||
///follow when the elements they attach to move.
|
||||
static const int MovableItems =
|
||||
DiagramContent::Elements
|
||||
| DiagramContent::TextFields
|
||||
| DiagramContent::Images
|
||||
| DiagramContent::Shapes
|
||||
| DiagramContent::Tables
|
||||
| DiagramContent::TerminalStrip;
|
||||
|
||||
DiagramEventAddPaste(Diagram *diagram, const QPointF &start_pos);
|
||||
~DiagramEventAddPaste() override;
|
||||
|
||||
void mouseMoveEvent (QGraphicsSceneMouseEvent *event) override;
|
||||
void mousePressEvent (QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseReleaseEvent (QGraphicsSceneMouseEvent *event) override;
|
||||
void keyPressEvent (QKeyEvent *event) override;
|
||||
void init() override;
|
||||
|
||||
///@return true if the clipboard holds something this can paste.
|
||||
static bool clipboardHasDiagram();
|
||||
|
||||
private:
|
||||
void moveTo(const QPointF &scene_pos);
|
||||
void commit();
|
||||
void cancel();
|
||||
void showHint();
|
||||
|
||||
DiagramContent m_content;
|
||||
///Each movable item's position relative to the group's top left,
|
||||
///taken once so repeated moves cannot accumulate rounding drift.
|
||||
QHash<QGraphicsItem *, QPointF> m_relative_pos;
|
||||
QPointer<QStatusBar> m_status_bar;
|
||||
bool m_finished{false};
|
||||
};
|
||||
|
||||
#endif // DIAGRAMEVENTADDPASTE_H
|
||||
+89
-15
@@ -702,6 +702,27 @@ void DiagramView::focusInEvent(QFocusEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramView::focusNextPrevChild
|
||||
By default, QWidget intercepts Tab/Shift+Tab to move keyboard focus to
|
||||
the next/previous widget before a key press event is ever generated,
|
||||
which would silently swallow the diagram's Tab-based item-selection
|
||||
cycling (see Diagram::event()). Returning false here disables that
|
||||
automatic focus-chain traversal for this view, so Tab/Shift+Tab reach
|
||||
keyPressEvent() (and from there, the scene) as ordinary key presses
|
||||
instead.
|
||||
@return always false
|
||||
*/
|
||||
bool DiagramView::focusNextPrevChild(bool next)
|
||||
{
|
||||
//Escape asked for focus to leave; allow exactly this one traversal.
|
||||
if (m_releasing_focus) {
|
||||
m_releasing_focus = false;
|
||||
return QGraphicsView::focusNextPrevChild(next);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramView::keyPressEvent
|
||||
Handles "key press" events. Reimplemented here to switch to visualisation
|
||||
@@ -717,6 +738,19 @@ void DiagramView::keyPressEvent(QKeyEvent *e)
|
||||
DiagramContent dc(m_diagram);
|
||||
switch(e -> key())
|
||||
{
|
||||
case Qt::Key_Escape:
|
||||
//Tab cycles the folio's items rather than moving focus (see
|
||||
//focusNextPrevChild above), so without this there would be no
|
||||
//way off the canvas for someone working without a mouse.
|
||||
//Escape steps back out: first it drops the selection, then it
|
||||
//hands focus to the next widget.
|
||||
if (m_diagram && !m_diagram->selectedItems().isEmpty()) {
|
||||
m_diagram->clearSelection();
|
||||
} else {
|
||||
m_releasing_focus = true;
|
||||
focusNextChild();
|
||||
}
|
||||
return;
|
||||
case Qt::Key_PageUp:
|
||||
current_project->changeTabUp();
|
||||
return;
|
||||
@@ -1218,30 +1252,70 @@ QList<QAction *> DiagramView::contextMenuActions() const
|
||||
*/
|
||||
void DiagramView::contextMenuEvent(QContextMenuEvent *e)
|
||||
{
|
||||
QGraphicsView::contextMenuEvent(e);
|
||||
if(e->isAccepted())
|
||||
return;
|
||||
QPoint menu_pos = e->pos();
|
||||
QPoint menu_global_pos = e->globalPos();
|
||||
|
||||
//A context menu raised from the keyboard (the Menu key, or
|
||||
//Shift+F10) carries no useful position: Qt does not aim it at the
|
||||
//selection. Two things then went wrong. QGraphicsView handed the
|
||||
//event to whichever item held focus, which answered with its own
|
||||
//generic Undo/Cut/Copy menu and accepted it, so the folio's real
|
||||
//menu was never built; and had it got past that, itemAt() below
|
||||
//would have looked up an unrelated point.
|
||||
//
|
||||
//So a keyboard-raised menu is built here directly rather than being
|
||||
//offered to the items first, and aimed at the selection when there
|
||||
//is one. The keyboard then gets the folio's menu, which is what a
|
||||
//right-click gets.
|
||||
const bool from_keyboard = e->reason() == QContextMenuEvent::Keyboard;
|
||||
|
||||
if (auto qgi = m_diagram->itemAt(mapToScene(e->pos()), transform()))
|
||||
if (from_keyboard)
|
||||
{
|
||||
if (!qgi->isSelected()) {
|
||||
m_diagram->clearSelection();
|
||||
//Aim at the selection when there is one, so the menu appears
|
||||
//beside what it acts on. With nothing selected there is nothing
|
||||
//to aim at, so use the middle of the view -- the folio's own
|
||||
//menu is still the right menu to show.
|
||||
const auto selection = m_diagram->selectedItems();
|
||||
if (!selection.isEmpty())
|
||||
{
|
||||
QRectF selection_rect;
|
||||
for (auto *item : selection) {
|
||||
selection_rect |= item->sceneBoundingRect();
|
||||
}
|
||||
menu_pos = mapFromScene(selection_rect.center());
|
||||
}
|
||||
else
|
||||
{
|
||||
menu_pos = viewport()->rect().center();
|
||||
}
|
||||
menu_global_pos = viewport()->mapToGlobal(menu_pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
QGraphicsView::contextMenuEvent(e);
|
||||
if(e->isAccepted())
|
||||
return;
|
||||
|
||||
// At this step qgi can be deleted for example if qgi is a QetGraphicsHandlerItem.
|
||||
// When we call clearSelection the parent item of the handler
|
||||
// is deselected and so delete all handlers, in this case,
|
||||
// qgi become a dangling pointer.
|
||||
// we need to call again itemAt.
|
||||
if (auto item_ = m_diagram->itemAt(mapToScene(e->pos()), transform())) {
|
||||
item_->setSelected(true);
|
||||
if (auto qgi = m_diagram->itemAt(mapToScene(menu_pos), transform()))
|
||||
{
|
||||
if (!qgi->isSelected()) {
|
||||
m_diagram->clearSelection();
|
||||
}
|
||||
|
||||
// At this step qgi can be deleted for example if qgi is a QetGraphicsHandlerItem.
|
||||
// When we call clearSelection the parent item of the handler
|
||||
// is deselected and so delete all handlers, in this case,
|
||||
// qgi become a dangling pointer.
|
||||
// we need to call again itemAt.
|
||||
if (auto item_ = m_diagram->itemAt(mapToScene(menu_pos), transform())) {
|
||||
item_->setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_diagram->selectedItems().isEmpty())
|
||||
{
|
||||
m_paste_here_pos = e->pos();
|
||||
m_paste_here_pos = menu_pos;
|
||||
m_paste_here->setEnabled(Diagram::clipboardMayContainDiagram());
|
||||
}
|
||||
|
||||
@@ -1250,7 +1324,7 @@ void DiagramView::contextMenuEvent(QContextMenuEvent *e)
|
||||
{
|
||||
QMenu *context_menu = new QMenu(this);
|
||||
context_menu->addActions(list);
|
||||
context_menu->popup(e->globalPos());
|
||||
context_menu->popup(menu_global_pos);
|
||||
e->accept();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,9 @@ class DiagramView : public QGraphicsView
|
||||
void keyPressEvent(QKeyEvent *) override;
|
||||
void keyReleaseEvent(QKeyEvent *) override;
|
||||
bool event(QEvent *) override;
|
||||
bool focusNextPrevChild(bool next) override;
|
||||
///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;
|
||||
void mousePressEvent(QMouseEvent *) override;
|
||||
void mouseMoveEvent(QMouseEvent *) override;
|
||||
|
||||
+33
-5
@@ -546,12 +546,40 @@ QString QET::joinWithSpaces(const QStringList &string_list) {
|
||||
QStringList QET::splitWithSpaces(const QString &string) {
|
||||
// les chaines sont separees par des espaces non echappes
|
||||
// = avec un nombre nul ou pair de backslashes devant
|
||||
|
||||
QStringList escaped_strings = string.split(QRegularExpression("[^\\]?(?:\\\\)* "),Qt::SkipEmptyParts);
|
||||
|
||||
//
|
||||
// This was a QRegularExpression("[^\\]?(?:\\\\)* ") split, which never
|
||||
// worked: "[^\]" opens a character class whose "\]" is an escaped
|
||||
// bracket, so the class is never closed and the pattern is invalid.
|
||||
// QRegularExpression::isValid() was false, QString::split() warned
|
||||
// "invalid QRegularExpression object" and returned an EMPTY list for
|
||||
// every input -- so a second instance's file arguments were always
|
||||
// dropped (bugtracker #248), not just ones containing spaces.
|
||||
//
|
||||
// A correct pattern is not expressible here either: the separator is a
|
||||
// space preceded by an even-length run of backslashes, and PCRE2 has no
|
||||
// variable-length lookbehind. Scanning explicitly is both correct and
|
||||
// easier to read than the alternatives.
|
||||
QStringList returned_list;
|
||||
foreach(QString escaped_string, escaped_strings) {
|
||||
returned_list << QET::unescapeSpaces(escaped_string);
|
||||
QString current;
|
||||
int backslashes = 0;
|
||||
for (const QChar &c : string) {
|
||||
if (c == QLatin1Char('\\')) {
|
||||
++backslashes;
|
||||
current += c;
|
||||
continue;
|
||||
}
|
||||
if (c == QLatin1Char(' ') && backslashes % 2 == 0) {
|
||||
if (!current.isEmpty()) {
|
||||
returned_list << QET::unescapeSpaces(current);
|
||||
}
|
||||
current.clear();
|
||||
} else {
|
||||
current += c;
|
||||
}
|
||||
backslashes = 0;
|
||||
}
|
||||
if (!current.isEmpty()) {
|
||||
returned_list << QET::unescapeSpaces(current);
|
||||
}
|
||||
return(returned_list);
|
||||
}
|
||||
|
||||
+25
-1
@@ -1659,7 +1659,31 @@ void QETApp::receiveMessage(int instanceId, QByteArray message)
|
||||
{
|
||||
QString my_message(str.mid(20));
|
||||
QStringList args_list = QET::splitWithSpaces(my_message);
|
||||
openFiles(QETArguments(args_list));
|
||||
|
||||
// Deferred, not called directly.
|
||||
//
|
||||
// This slot runs inside SingleApplication's readyRead handling:
|
||||
// SingleApplicationPrivate::slotDataAvailable() emits
|
||||
// receivedMessage() synchronously from the socket's readyRead
|
||||
// lambda. openFiles() then loads a project -- seconds of work on
|
||||
// a large one -- and openAndAddProject() puts up a modal
|
||||
// BackupDialog, whose exec() runs a nested event loop while the
|
||||
// socket handler is still on the stack.
|
||||
//
|
||||
// During that nested loop the secondary instance exits, the
|
||||
// connection closes and the QLocalSocket is deleted. When the
|
||||
// dialog is dismissed and the stack unwinds, QMetaObject::
|
||||
// activate() continues emitting on the freed sender and the
|
||||
// process dies. Reported with a backtrace on PR #861;
|
||||
// reproduced on Qt 6.10.2 by dismissing the dialog, which is the
|
||||
// step that makes it fail -- leaving it open never unwinds.
|
||||
//
|
||||
// A zero-timer returns to the event loop first, so the socket
|
||||
// stack is fully unwound before any of this runs.
|
||||
const QETArguments deferred_args{args_list};
|
||||
QTimer::singleShot(0, this, [this, deferred_args]() {
|
||||
openFiles(deferred_args);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "diagramevent/diagrameventaddshape.h"
|
||||
#include "diagramevent/diagrameventaddpath.h"
|
||||
#include "diagramevent/diagrameventaddtext.h"
|
||||
#include "diagramevent/diagrameventaddpaste.h"
|
||||
#include "diagramview.h"
|
||||
#include "elementspanelwidget.h"
|
||||
#include "factory/qetgraphicstablefactory.h"
|
||||
@@ -349,8 +350,21 @@ void QETDiagramEditor::setUpActions()
|
||||
currentDiagramView()->copy();
|
||||
});
|
||||
connect(m_paste, &QAction::triggered, [this]() {
|
||||
if(currentDiagramView())
|
||||
currentDiagramView()->paste();
|
||||
auto *dv = currentDiagramView();
|
||||
if (!dv || !dv->diagram()) return;
|
||||
|
||||
//Paste as a placement rather than dropping the items straight
|
||||
//down. Pasting in place put the copy exactly on top of the
|
||||
//original, where it was easy to miss entirely; now it appears
|
||||
//under the cursor and follows it until a click, Return, or Escape
|
||||
//to cancel -- the same interaction as placing a new element.
|
||||
const QPoint view_pos = dv->viewport()->mapFromGlobal(QCursor::pos());
|
||||
const QPointF start_pos = dv->viewport()->rect().contains(view_pos)
|
||||
? dv->mapToScene(view_pos)
|
||||
: dv->mapToScene(dv->viewport()->rect().center());
|
||||
|
||||
dv->diagram()->setEventInterface(
|
||||
new DiagramEventAddPaste(dv->diagram(), start_pos));
|
||||
});
|
||||
|
||||
//Reset conductor path
|
||||
@@ -688,18 +702,28 @@ void QETDiagramEditor::setUpActions()
|
||||
QAction *select_all = m_select_actions_group.addAction( QET::Icons::EditSelectAll, tr("Tout sélectionner") );
|
||||
QAction *select_nothing = m_select_actions_group.addAction( QET::Icons::EditSelectNone, tr("Désélectionner tout") );
|
||||
QAction *select_invert = m_select_actions_group.addAction( QET::Icons::EditSelectInvert, tr("Inverser la sélection") );
|
||||
QAction *select_all_conductors = m_select_actions_group.addAction( QET::Icons::Conductor, tr("Sélectionner tous les conducteurs") );
|
||||
QAction *select_all_text_fields = m_select_actions_group.addAction( QET::Icons::PartTextField, tr("Sélectionner tous les champs de texte") );
|
||||
|
||||
ShortcutManager::instance().registerAction(select_all, "diagrameditor.select_all", tr("Éditeur de schémas"), QKeySequence::SelectAll);
|
||||
ShortcutManager::instance().registerAction(select_nothing, "diagrameditor.select_nothing", tr("Éditeur de schémas"), QKeySequence::Deselect);
|
||||
ShortcutManager::instance().registerAction(select_invert, "diagrameditor.select_invert", tr("Éditeur de schémas"), Qt::CTRL | Qt::Key_I);
|
||||
//No default sequence for these two: they are menu actions, and the
|
||||
//point of registering them is so a user can bind one if they want.
|
||||
ShortcutManager::instance().registerAction(select_all_conductors, "diagrameditor.select_all_conductors", tr("Éditeur de schémas"), QKeySequence());
|
||||
ShortcutManager::instance().registerAction(select_all_text_fields, "diagrameditor.select_all_text_fields", tr("Éditeur de schémas"), QKeySequence());
|
||||
|
||||
select_all ->setStatusTip( tr("Sélectionne tous les éléments du folio", "status bar tip") );
|
||||
select_nothing->setStatusTip( tr("Désélectionne tous les éléments du folio", "status bar tip") );
|
||||
select_invert ->setStatusTip( tr("Désélectionne les éléments sélectionnés et sélectionne les éléments non sélectionnés", "status bar tip") );
|
||||
select_all_conductors ->setStatusTip( tr("Sélectionne tous les conducteurs du folio, désélectionne le reste", "status bar tip") );
|
||||
select_all_text_fields->setStatusTip( tr("Sélectionne tous les champs de texte du folio, désélectionne le reste", "status bar tip") );
|
||||
|
||||
select_all ->setData("select_all");
|
||||
select_nothing->setData("deselect");
|
||||
select_invert ->setData("invert_selection");
|
||||
select_all_conductors ->setData("select_all_conductors");
|
||||
select_all_text_fields->setData("select_all_text_fields");
|
||||
|
||||
connect(&m_select_actions_group, &QActionGroup::triggered, this, &QETDiagramEditor::selectGroupTriggered);
|
||||
|
||||
@@ -934,6 +958,15 @@ void QETDiagramEditor::setUpMenu()
|
||||
menu_edition -> addAction(m_cut);
|
||||
menu_edition -> addAction(m_copy);
|
||||
menu_edition -> addAction(m_paste);
|
||||
menu_edition -> addSeparator();
|
||||
//The same actions the "Ajouter" toolbar holds. They were toolbar-only,
|
||||
//which left them unreachable for anyone working without a mouse: a
|
||||
//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());
|
||||
menu_edition -> addSeparator();
|
||||
menu_edition -> addActions(m_select_actions_group.actions());
|
||||
menu_edition -> addSeparator();
|
||||
@@ -952,6 +985,9 @@ void QETDiagramEditor::setUpMenu()
|
||||
// menu Projet
|
||||
menu_project -> addAction(m_project_edit_properties);
|
||||
menu_project -> addAction(m_auto_conductor);
|
||||
//Sits beside m_auto_conductor, the setting it pairs with. It was
|
||||
//toolbar-only and so had no keyboard route at all.
|
||||
menu_project -> addAction(m_auto_break_conductor);
|
||||
menu_project -> addSeparator();
|
||||
menu_project -> addAction(m_project_add_diagram);
|
||||
menu_project -> addAction(m_remove_diagram_from_project);
|
||||
@@ -1584,6 +1620,10 @@ void QETDiagramEditor::selectGroupTriggered(QAction *action)
|
||||
diagram->deselectAll();
|
||||
else if (value == "invert_selection")
|
||||
diagram->invertSelection();
|
||||
else if (value == "select_all_conductors")
|
||||
diagram->selectAllConductors();
|
||||
else if (value == "select_all_text_fields")
|
||||
diagram->selectAllTextFields();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -574,8 +574,23 @@ void Conductor::paint(QPainter *painter, const QStyleOptionGraphicsItem *options
|
||||
painter -> setPen(final_conductor_pen);
|
||||
painter -> setBrush(junction_brush);
|
||||
painter -> setRenderHint(QPainter::Antialiasing, true);
|
||||
// The junction dot has to read as a dot on top of the conductor
|
||||
// that carries it, so it scales with the conductor width instead
|
||||
// of being a fixed 3.0 across: on a wide conductor a 3.0 dot is
|
||||
// narrower than the line and simply disappears (bugtracker #108).
|
||||
//
|
||||
// Floored at the historic 3.0 so nothing changes for the default
|
||||
// width of 1.0 or anything thinner -- only the wide conductors
|
||||
// the report is about are affected. m_properties.cond_size is
|
||||
// used rather than the pen, whose width is inflated by 4 while
|
||||
// the mouse is over the conductor.
|
||||
const qreal junction_diameter = qMax(3.0, 3.0 * m_properties.cond_size);
|
||||
const qreal junction_radius = junction_diameter / 2.0;
|
||||
foreach(QPointF point, junctions_list) {
|
||||
painter -> drawEllipse(QRectF(point.x() - 1.5, point.y() - 1.5, 3.0, 3.0));
|
||||
painter -> drawEllipse(QRectF(point.x() - junction_radius,
|
||||
point.y() - junction_radius,
|
||||
junction_diameter,
|
||||
junction_diameter));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <QWhatsThis>
|
||||
#include <QMenu>
|
||||
#include <QMenuBar>
|
||||
#include <QShortcut>
|
||||
#include <QDragEnterEvent>
|
||||
#include <QDesktopServices>
|
||||
|
||||
@@ -41,6 +42,14 @@ QETMainWindow::QETMainWindow(QWidget *widget, Qt::WindowFlags flags) :
|
||||
initCommonMenus();
|
||||
|
||||
setAcceptDrops(true);
|
||||
//A shortcut rather than a key handler: a key press goes to the
|
||||
//focused child widget, so a keyPressEvent() here would never see F10
|
||||
//while the canvas or a panel holds focus.
|
||||
QShortcut *menu_bar_shortcut = new QShortcut(QKeySequence(Qt::Key_F10), this);
|
||||
menu_bar_shortcut -> setContext(Qt::WindowShortcut);
|
||||
connect(menu_bar_shortcut, &QShortcut::activated,
|
||||
this, &QETMainWindow::activateMenuBar);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,6 +260,35 @@ void QETMainWindow::checkToolbarsmenu()
|
||||
/**
|
||||
Handle the \a e event.
|
||||
*/
|
||||
/**
|
||||
@brief QETMainWindow::activateMenuBar
|
||||
Open the first usable menu, as pressing Alt and a menu's letter would.
|
||||
|
||||
F10 is what most applications use for this, and QMenuBar does not handle
|
||||
it: given the key directly it leaves it unaccepted, and sent to the window
|
||||
it never reaches the menu bar at all, because a key press goes to the
|
||||
focused child widget. So the press fell through to whichever widget had
|
||||
focus and looked like nothing happening.
|
||||
|
||||
This is convenience, not access. Qt already provides two keyboard routes
|
||||
into the menus and both work: a bare Alt tap focuses the bar, and Alt with
|
||||
a menu's letter opens it. This adds the key people reach for out of habit.
|
||||
|
||||
A shortcut rather than a keyPressEvent() override, for the reason above --
|
||||
the window never sees the key while a child holds focus.
|
||||
*/
|
||||
void QETMainWindow::activateMenuBar() {
|
||||
QMenuBar *bar = menuBar();
|
||||
if (!bar) return;
|
||||
|
||||
for (QAction *action : bar -> actions()) {
|
||||
if (action -> isVisible() && action -> isEnabled() && action -> menu()) {
|
||||
bar -> setActiveAction(action);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool QETMainWindow::event(QEvent *e) {
|
||||
if (e -> type() == QEvent::WindowStateChange) {
|
||||
updateFullScreenAction();
|
||||
|
||||
@@ -39,6 +39,7 @@ class QETMainWindow : public QMainWindow {
|
||||
QAction *actionForMenu(QMenu *);
|
||||
|
||||
protected:
|
||||
void activateMenuBar();
|
||||
bool event(QEvent *) override;
|
||||
void dragEnterEvent(QDragEnterEvent *e) override;
|
||||
void dropEvent(QDropEvent *e) override;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QLabel>
|
||||
#include <QCheckBox>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QGridLayout>
|
||||
@@ -36,6 +37,7 @@
|
||||
#include <QAction>
|
||||
#include <QFont>
|
||||
#include <QTimer>
|
||||
#include <QSettings>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QGridLayout>
|
||||
@@ -56,12 +58,16 @@ PlcLinkWidget::PlcLinkWidget(Element *elmt, QWidget *parent)
|
||||
main_layout->addWidget(m_unlink_pb, 0, 1);
|
||||
main_layout->addWidget(m_show_this_pb, 0, 2);
|
||||
|
||||
// Row 1: Search field
|
||||
// Row 1: Hide linked elements checkbox
|
||||
m_hide_linked_cb = new QCheckBox(tr("Masquer les éléments connectés"), this);
|
||||
main_layout->addWidget(m_hide_linked_cb, 1, 0, 1, 3);
|
||||
|
||||
// Row 2: Search field
|
||||
m_search_field = new QLineEdit(this);
|
||||
m_search_field->setPlaceholderText(tr("Recherche"));
|
||||
main_layout->addWidget(m_search_field, 1, 0, 1, 3);
|
||||
main_layout->addWidget(m_search_field, 2, 0, 1, 3);
|
||||
|
||||
// Row 2: Tree widget
|
||||
// Row 3: Tree widget
|
||||
m_tree_widget = new QTreeWidget(this);
|
||||
m_tree_widget->setHeaderLabels({
|
||||
tr("Label"), tr("Type"), tr("Adresse"),
|
||||
@@ -78,9 +84,9 @@ PlcLinkWidget::PlcLinkWidget(Element *elmt, QWidget *parent)
|
||||
m_tree_widget->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
|
||||
m_tree_widget->header()->setSectionResizeMode(4, QHeaderView::ResizeToContents);
|
||||
m_tree_widget->header()->setSectionResizeMode(5, QHeaderView::ResizeToContents);
|
||||
main_layout->addWidget(m_tree_widget, 2, 0, 1, 3);
|
||||
main_layout->addWidget(m_tree_widget, 3, 0, 1, 3);
|
||||
|
||||
// Row 3: Hidden masters note
|
||||
// Row 4: Hidden masters note
|
||||
m_hidden_masters_label = new QLabel(
|
||||
tr("Remarque : les éléments maîtres ayant atteint leur nombre maximal "
|
||||
"d'esclaves sont masqués."), this);
|
||||
@@ -89,9 +95,9 @@ PlcLinkWidget::PlcLinkWidget(Element *elmt, QWidget *parent)
|
||||
italic_font.setItalic(true);
|
||||
m_hidden_masters_label->setFont(italic_font);
|
||||
m_hidden_masters_label->hide();
|
||||
main_layout->addWidget(m_hidden_masters_label, 3, 0, 1, 3);
|
||||
main_layout->addWidget(m_hidden_masters_label, 4, 0, 1, 3);
|
||||
|
||||
main_layout->setRowStretch(2, 1);
|
||||
main_layout->setRowStretch(3, 1);
|
||||
|
||||
setMinimumWidth(500);
|
||||
|
||||
@@ -104,6 +110,12 @@ PlcLinkWidget::PlcLinkWidget(Element *elmt, QWidget *parent)
|
||||
this, &PlcLinkWidget::on_m_search_field_textEdited);
|
||||
connect(m_tree_widget, &QTreeWidget::customContextMenuRequested,
|
||||
this, &PlcLinkWidget::on_m_tree_widget_customContextMenuRequested);
|
||||
connect(m_hide_linked_cb, &QCheckBox::toggled,
|
||||
this, &PlcLinkWidget::on_m_hide_linked_cb_toggled);
|
||||
|
||||
QSettings settings;
|
||||
m_hide_linked_cb->setChecked(
|
||||
settings.value(QStringLiteral("plclinkwidget/hideLinked"), false).toBool());
|
||||
|
||||
if (elmt)
|
||||
setElement(elmt);
|
||||
@@ -163,6 +175,7 @@ void PlcLinkWidget::buildPlcTree()
|
||||
{
|
||||
m_tree_widget->clear();
|
||||
m_io_entry_hash.clear();
|
||||
m_linked_children.clear();
|
||||
|
||||
if (!m_element || !m_element->diagram() || !m_element->diagram()->project())
|
||||
return;
|
||||
@@ -216,6 +229,7 @@ void PlcLinkWidget::buildPlcTree()
|
||||
parent_item->setExpanded(false);
|
||||
|
||||
// Add child items for each IO entry
|
||||
bool all_children_hidden = true;
|
||||
for (int i = 0; i < plc_data.ios.size(); ++i) {
|
||||
const auto &io = plc_data.ios.at(i);
|
||||
auto *child_item = new QTreeWidgetItem(parent_item);
|
||||
@@ -233,36 +247,54 @@ void PlcLinkWidget::buildPlcTree()
|
||||
entry.ioIndex = i;
|
||||
m_io_entry_hash.insert(child_item, entry);
|
||||
|
||||
// If this IO is already linked to a slave, grey it out and strike through
|
||||
// If this IO is already linked to a slave
|
||||
if (used_io_indices.contains(i)) {
|
||||
QFont strike_font = child_item->font(0);
|
||||
strike_font.setStrikeOut(true);
|
||||
child_item->setFont(0, strike_font);
|
||||
child_item->setFont(1, strike_font);
|
||||
child_item->setFont(2, strike_font);
|
||||
child_item->setFont(3, strike_font);
|
||||
child_item->setFont(4, strike_font);
|
||||
child_item->setFont(5, strike_font);
|
||||
m_linked_children.insert(child_item);
|
||||
if (m_hide_linked_cb->isChecked()) {
|
||||
child_item->setHidden(true);
|
||||
} else {
|
||||
all_children_hidden = false;
|
||||
QFont strike_font = child_item->font(0);
|
||||
strike_font.setStrikeOut(true);
|
||||
child_item->setFont(0, strike_font);
|
||||
child_item->setFont(1, strike_font);
|
||||
child_item->setFont(2, strike_font);
|
||||
child_item->setFont(3, strike_font);
|
||||
child_item->setFont(4, strike_font);
|
||||
child_item->setFont(5, strike_font);
|
||||
|
||||
QBrush grey_brush(Qt::gray);
|
||||
for (int col = 0; col < 6; ++col)
|
||||
child_item->setForeground(col, grey_brush);
|
||||
QBrush grey_brush(Qt::gray);
|
||||
for (int col = 0; col < 6; ++col)
|
||||
child_item->setForeground(col, grey_brush);
|
||||
|
||||
// Show which slave is linked
|
||||
for (Element *linked : elmt->linkedElements()) {
|
||||
if (elmt->groupIndexForElement(linked) == i) {
|
||||
child_item->setToolTip(0,
|
||||
tr("Lié à: %1").arg(linked->actualLabel()));
|
||||
break;
|
||||
// Show which slave is linked
|
||||
for (Element *linked : elmt->linkedElements()) {
|
||||
if (elmt->groupIndexForElement(linked) == i) {
|
||||
child_item->setToolTip(0,
|
||||
tr("Lié à: %1").arg(linked->actualLabel()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
child_item->setFlags(child_item->flags() & ~Qt::ItemIsSelectable);
|
||||
child_item->setFlags(child_item->flags() & ~Qt::ItemIsSelectable);
|
||||
}
|
||||
} else {
|
||||
all_children_hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If checkbox is on and every child is linked (hidden), hide the master too
|
||||
if (m_hide_linked_cb->isChecked() && all_children_hidden) {
|
||||
parent_item->setHidden(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PlcLinkWidget::isChildLinked(QTreeWidgetItem *child) const
|
||||
{
|
||||
return m_linked_children.contains(child);
|
||||
}
|
||||
|
||||
void PlcLinkWidget::hideButtons()
|
||||
{
|
||||
m_label->hide();
|
||||
@@ -296,15 +328,21 @@ void PlcLinkWidget::on_m_search_field_textEdited(const QString &text)
|
||||
}
|
||||
}
|
||||
}
|
||||
child->setHidden(!match);
|
||||
if (match) any_child_visible = true;
|
||||
bool hidden = m_hide_linked_cb->isChecked() && isChildLinked(child);
|
||||
child->setHidden(!match || hidden);
|
||||
if (match && !hidden) any_child_visible = true;
|
||||
}
|
||||
|
||||
// Also check if parent label matches
|
||||
if (!text.isEmpty() && parent->text(0).contains(text, Qt::CaseInsensitive)) {
|
||||
any_child_visible = true;
|
||||
for (int j = 0; j < parent->childCount(); ++j)
|
||||
parent->child(j)->setHidden(false);
|
||||
for (int j = 0; j < parent->childCount(); ++j) {
|
||||
QTreeWidgetItem *child = parent->child(j);
|
||||
bool hidden = m_hide_linked_cb->isChecked() && isChildLinked(child);
|
||||
if (!hidden) {
|
||||
any_child_visible = true;
|
||||
child->setHidden(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parent->setHidden(!any_child_visible);
|
||||
@@ -383,3 +421,10 @@ void PlcLinkWidget::on_m_show_this_pb_clicked()
|
||||
m_element->diagram()->showMe();
|
||||
m_element->setHighlighted(true);
|
||||
}
|
||||
|
||||
void PlcLinkWidget::on_m_hide_linked_cb_toggled(bool checked)
|
||||
{
|
||||
QSettings settings;
|
||||
settings.setValue(QStringLiteral("plclinkwidget/hideLinked"), checked);
|
||||
buildPlcTree();
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ class QTreeWidget;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QLabel;
|
||||
class QCheckBox;
|
||||
class Element;
|
||||
|
||||
/**
|
||||
@@ -58,17 +59,20 @@ class PlcLinkWidget : public AbstractElementPropertiesEditorWidget
|
||||
void buildPlcTree();
|
||||
void hideButtons();
|
||||
void showButtons();
|
||||
bool isChildLinked(QTreeWidgetItem *child) const;
|
||||
|
||||
private slots:
|
||||
void on_m_search_field_textEdited(const QString &text);
|
||||
void on_m_tree_widget_customContextMenuRequested(const QPoint &pos);
|
||||
void on_m_unlink_pb_clicked();
|
||||
void on_m_show_this_pb_clicked();
|
||||
void on_m_hide_linked_cb_toggled(bool checked);
|
||||
|
||||
private:
|
||||
QLabel *m_label{nullptr};
|
||||
QPushButton *m_unlink_pb{nullptr};
|
||||
QPushButton *m_show_this_pb{nullptr};
|
||||
QCheckBox *m_hide_linked_cb{nullptr};
|
||||
QLineEdit *m_search_field{nullptr};
|
||||
QTreeWidget *m_tree_widget{nullptr};
|
||||
QLabel *m_hidden_masters_label{nullptr};
|
||||
@@ -79,6 +83,7 @@ class PlcLinkWidget : public AbstractElementPropertiesEditorWidget
|
||||
int ioIndex = -1;
|
||||
};
|
||||
QHash<QTreeWidgetItem*, PlcIoEntry> m_io_entry_hash;
|
||||
QSet<QTreeWidgetItem*> m_linked_children;
|
||||
|
||||
Element *m_element_to_link = nullptr;
|
||||
int m_pending_io_index = -1;
|
||||
|
||||
Reference in New Issue
Block a user