Compare commits

...

5 Commits

Author SHA1 Message Date
Laurent Trinques 265aa44320 Merge pull request #858 from Kellermorph/checkbox-plc-connection
Add Hide linked elements checkbox to PLC link widget
2026-09-15 18:44:24 +02:00
Laurent Trinques 395c6f6602 Merge pull request #876 from ispyisail/fix/keyboard-context-menu
Give the keyboard the folio's context menu, not a generic one
2026-09-15 16:15:04 +02:00
ispyisail 6ed26358e8 Give the keyboard the folio's context menu, not an item's generic one
Pressing the Menu key (or Shift+F10) on a folio produced a bare
Undo/Redo/Cut/Copy/Paste/Delete/Select All menu with almost everything
disabled, instead of the menu a right-click gives.

A keyboard-raised QContextMenuEvent carries no useful position -- Qt does
not aim it at the selection. contextMenuEvent() passed the event to
QGraphicsView first, which handed it to whichever item held focus; that
item answered with its own default menu and accepted the event, so the
early return fired and the folio's menu was never built. Even past that,
the itemAt() lookup below would have used an unrelated point.

A keyboard-raised menu is now built directly rather than offered to the
items first, and aimed at the centre of the selection, or at the middle of
the view when nothing is selected. The mouse path is unchanged.

Measured on the same branch with only this change applied: before, the
menu carried 7 actions, all but one disabled; after, 16, positioned on the
selected element. Builds clean on Qt 5 and Qt 6, tests 5/5 on both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:03:01 +12:00
Kellermorph 3c1c29f6c3 connect searchfield 2026-09-13 21:32:34 +02:00
Kellermorph cafc5bcf6d Add Hide linked elements checkbox to PLC link widget 2026-09-13 20:36:59 +02:00
3 changed files with 137 additions and 47 deletions
+55 -15
View File
@@ -1252,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());
}
@@ -1284,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();
}
}
+77 -32
View File
@@ -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);
@@ -369,3 +407,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();
}
+5
View File
@@ -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;