Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
buddyeditor.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "buddyeditor.h"
5
6#include <QtDesigner/abstractformwindow.h>
7#include <QtDesigner/propertysheet.h>
8#include <QtDesigner/abstractformeditor.h>
9#include <QtDesigner/qextensionmanager.h>
10
11#include <qdesigner_command_p.h>
12#include <qdesigner_propertycommand_p.h>
13#include <qdesigner_utils_p.h>
14#include <qlayout_widget_p.h>
15#include <connectionedit_p.h>
16#include <metadatabase_p.h>
17
18#include <QtWidgets/qlabel.h>
19#include <QtWidgets/qmenu.h>
20#include <QtWidgets/qapplication.h>
21
22#include <QtGui/qaction.h>
23
24#include <QtCore/qdebug.h>
25#include <QtCore/qlist.h>
26
27#include <algorithm>
28
29QT_BEGIN_NAMESPACE
30
31using namespace Qt::StringLiterals;
32
33static constexpr auto buddyPropertyC = "buddy"_L1;
34
35static bool canBeBuddy(QWidget *w, QDesignerFormWindowInterface *form)
36{
37 if (qobject_cast<const QLayoutWidget*>(w) || qobject_cast<const QLabel*>(w))
38 return false;
39 if (w == form->mainContainer() || w->isHidden() )
40 return false;
41
42 QExtensionManager *ext = form->core()->extensionManager();
43 if (QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(ext, w)) {
44 const int index = sheet->indexOf(u"focusPolicy"_s);
45 if (index != -1) {
46 bool ok = false;
47 const Qt::FocusPolicy q = static_cast<Qt::FocusPolicy>(qdesigner_internal::Utils::valueOf(sheet->property(index), &ok));
48 // Refuse No-focus unless the widget is promoted.
49 return (ok && q != Qt::NoFocus) || qdesigner_internal::isPromoted(form->core(), w);
50 }
51 }
52 return false;
53}
54
55static QString buddy(QLabel *label, QDesignerFormEditorInterface *core)
56{
57 QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), label);
58 if (sheet == nullptr)
59 return QString();
60 const int prop_idx = sheet->indexOf(buddyPropertyC);
61 if (prop_idx == -1)
62 return QString();
63 return sheet->property(prop_idx).toString();
64}
65
67
68/*******************************************************************************
69** BuddyEditor
70*/
71
72BuddyEditor::BuddyEditor(QDesignerFormWindowInterface *form, QWidget *parent) :
73 ConnectionEdit(parent, form),
74 m_formWindow(form),
75 m_updating(false)
76{
77}
78
79
80QWidget *BuddyEditor::widgetAt(const QPoint &pos) const
81{
82 QWidget *w = ConnectionEdit::widgetAt(pos);
83
84 while (w != nullptr && !m_formWindow->isManaged(w))
85 w = w->parentWidget();
86 if (!w)
87 return w;
88
89 if (state() == Editing) {
90 QLabel *label = qobject_cast<QLabel*>(w);
91 if (label == nullptr)
92 return nullptr;
93 const int cnt = connectionCount();
94 for (int i = 0; i < cnt; ++i) {
95 Connection *con = connection(i);
96 if (con->widget(EndPoint::Source) == w)
97 return nullptr;
98 }
99 } else {
100 if (!canBeBuddy(w, m_formWindow))
101 return nullptr;
102 }
103
104 return w;
105}
106
107Connection *BuddyEditor::createConnection(QWidget *source, QWidget *destination)
108{
109 return new Connection(this, source, destination);
110}
111
112QDesignerFormWindowInterface *BuddyEditor::formWindow() const
113{
114 return m_formWindow;
115}
116
117void BuddyEditor::updateBackground()
118{
119 if (m_updating || background() == nullptr)
120 return;
121 ConnectionEdit::updateBackground();
122
123 m_updating = true;
124 QList<Connection *> newList;
125 const auto label_list = background()->findChildren<QLabel*>();
126 for (QLabel *label : label_list) {
127 const QString buddy_name = buddy(label, m_formWindow->core());
128 if (buddy_name.isEmpty())
129 continue;
130
131 const QWidgetList targets = background()->findChildren<QWidget*>(buddy_name);
132 if (targets.isEmpty())
133 continue;
134
135 const auto wit = std::find_if(targets.cbegin(), targets.cend(),
136 [] (const QWidget *w) { return !w->isHidden(); });
137 if (wit == targets.cend())
138 continue;
139
140 Connection *con = new Connection(this);
141 con->setEndPoint(EndPoint::Source, label, widgetRect(label).center());
142 con->setEndPoint(EndPoint::Target, *wit, widgetRect(*wit).center());
143 newList.append(con);
144 }
145
146 QList<Connection *> toRemove;
147
148 const int c = connectionCount();
149 for (int i = 0; i < c; i++) {
150 Connection *con = connection(i);
151 QObject *source = con->object(EndPoint::Source);
152 QObject *target = con->object(EndPoint::Target);
153 const bool found =
154 std::any_of(newList.cbegin(), newList.cend(),
155 [source, target] (const Connection *nc)
156 { return nc->object(EndPoint::Source) == source && nc->object(EndPoint::Target) == target; });
157 if (!found)
158 toRemove.append(con);
159 }
160 if (!toRemove.isEmpty()) {
161 DeleteConnectionsCommand command(this, toRemove);
162 command.redo();
163 for (Connection *con : std::as_const(toRemove))
164 delete takeConnection(con);
165 }
166
167 for (Connection *newConn : std::as_const(newList)) {
168 bool found = false;
169 const int c = connectionCount();
170 for (int i = 0; i < c; i++) {
171 Connection *con = connection(i);
172 if (con->object(EndPoint::Source) == newConn->object(EndPoint::Source) &&
173 con->object(EndPoint::Target) == newConn->object(EndPoint::Target)) {
174 found = true;
175 break;
176 }
177 }
178 if (found) {
179 delete newConn;
180 } else {
181 AddConnectionCommand command(this, newConn);
182 command.redo();
183 }
184 }
185 m_updating = false;
186}
187
188void BuddyEditor::setBackground(QWidget *background)
189{
190 clear();
191 ConnectionEdit::setBackground(background);
192 if (background == nullptr)
193 return;
194
195 const auto label_list = background->findChildren<QLabel*>();
196 for (QLabel *label : label_list) {
197 const QString buddy_name = buddy(label, m_formWindow->core());
198 if (buddy_name.isEmpty())
199 continue;
200 QWidget *target = background->findChild<QWidget*>(buddy_name);
201 if (target == nullptr)
202 continue;
203
204 Connection *con = new Connection(this);
205 con->setEndPoint(EndPoint::Source, label, widgetRect(label).center());
206 con->setEndPoint(EndPoint::Target, target, widgetRect(target).center());
207 addConnection(con);
208 }
209}
210
211static QUndoCommand *createBuddyCommand(QDesignerFormWindowInterface *fw, QLabel *label, QWidget *buddy)
212{
213 SetPropertyCommand *command = new SetPropertyCommand(fw);
214 command->init(label, buddyPropertyC, buddy->objectName());
215 command->setText(BuddyEditor::tr("Add buddy"));
216 return command;
217}
218
219void BuddyEditor::endConnection(QWidget *target, const QPoint &pos)
220{
221 Connection *tmp_con = newlyAddedConnection();
222 Q_ASSERT(tmp_con != nullptr);
223
224 tmp_con->setEndPoint(EndPoint::Target, target, pos);
225
226 QWidget *source = tmp_con->widget(EndPoint::Source);
227 Q_ASSERT(source != nullptr);
228 Q_ASSERT(target != nullptr);
229 setEnabled(false);
230 Connection *new_con = createConnection(source, target);
231 setEnabled(true);
232 if (new_con != nullptr) {
233 new_con->setEndPoint(EndPoint::Source, source, tmp_con->endPointPos(EndPoint::Source));
234 new_con->setEndPoint(EndPoint::Target, target, tmp_con->endPointPos(EndPoint::Target));
235
236 selectNone();
237 addConnection(new_con);
238 QLabel *source = qobject_cast<QLabel*>(new_con->widget(EndPoint::Source));
239 QWidget *target = new_con->widget(EndPoint::Target);
240 if (source) {
241 undoStack()->push(createBuddyCommand(m_formWindow, source, target));
242 } else {
243 qDebug("BuddyEditor::endConnection(): not a label");
244 }
245 setSelected(new_con, true);
246 }
247
248 clearNewlyAddedConnection();
249 findObjectsUnderMouse(mapFromGlobal(QCursor::pos()));
250}
251
252void BuddyEditor::widgetRemoved(QWidget *widget)
253{
254 QWidgetList child_list = widget->findChildren<QWidget*>();
255 child_list.prepend(widget);
256
257 ConnectionSet remove_set;
258 for (QWidget *w : std::as_const(child_list)) {
259 const ConnectionList &cl = connectionList();
260 for (Connection *con : cl) {
261 if (con->widget(EndPoint::Source) == w || con->widget(EndPoint::Target) == w)
262 remove_set.insert(con, con);
263 }
264 }
265
266 if (!remove_set.isEmpty()) {
267 undoStack()->beginMacro(tr("Remove buddies"));
268 for (Connection *con : std::as_const(remove_set)) {
269 setSelected(con, false);
270 con->update();
271 QWidget *source = con->widget(EndPoint::Source);
272 if (qobject_cast<QLabel*>(source) == 0) {
273 qDebug("BuddyConnection::widgetRemoved(): not a label");
274 } else {
275 ResetPropertyCommand *command = new ResetPropertyCommand(formWindow());
276 command->init(source, buddyPropertyC);
277 undoStack()->push(command);
278 }
279 delete takeConnection(con);
280 }
281 undoStack()->endMacro();
282 }
283}
284
286{
287 const ConnectionSet selectedConnections = selection(); // want copy for unselect
288 if (selectedConnections.isEmpty())
289 return;
290
291 undoStack()->beginMacro(tr("Remove %n buddies", nullptr, selectedConnections.size()));
292 for (Connection *con : selectedConnections) {
293 setSelected(con, false);
294 con->update();
295 QWidget *source = con->widget(EndPoint::Source);
296 if (qobject_cast<QLabel*>(source) == 0) {
297 qDebug("BuddyConnection::deleteSelected(): not a label");
298 } else {
299 ResetPropertyCommand *command = new ResetPropertyCommand(formWindow());
300 command->init(source, buddyPropertyC);
301 undoStack()->push(command);
302 }
303 delete takeConnection(con);
304 }
305 undoStack()->endMacro();
306}
307
309{
310 // Any labels?
311 auto labelList = background()->findChildren<QLabel*>();
312 if (labelList.isEmpty())
313 return;
314 // Find already used buddies
315 QWidgetList usedBuddies;
316 const ConnectionList &beforeConnections = connectionList();
317 for (const Connection *c : beforeConnections)
318 usedBuddies.push_back(c->widget(EndPoint::Target));
319 // Find potential new buddies, keep lists in sync
320 QWidgetList buddies;
321 for (auto it = labelList.begin(); it != labelList.end(); ) {
322 QLabel *label = *it;
323 QWidget *newBuddy = nullptr;
324 if (m_formWindow->isManaged(label)) {
325 const QString buddy_name = buddy(label, m_formWindow->core());
326 if (buddy_name.isEmpty())
327 newBuddy = findBuddy(label, usedBuddies);
328 }
329 if (newBuddy) {
330 buddies.push_back(newBuddy);
331 usedBuddies.push_back(newBuddy);
332 ++it;
333 } else {
334 it = labelList.erase(it);
335 }
336 }
337 // Add the list in one go.
338 if (labelList.isEmpty())
339 return;
340 const auto count = labelList.size();
341 Q_ASSERT(count == buddies.size());
342 undoStack()->beginMacro(tr("Add %n buddies", nullptr, count));
343 for (qsizetype i = 0; i < count; ++i)
344 undoStack()->push(createBuddyCommand(m_formWindow, labelList.at(i), buddies.at(i)));
345 undoStack()->endMacro();
346 // Now select all new ones
347 const ConnectionList &connections = connectionList();
348 for (Connection *con : connections)
349 setSelected(con, buddies.contains(con->widget(EndPoint::Target)));
350}
351
352// Geometrically find a potential buddy for label by checking neighbouring children of parent
353QWidget *BuddyEditor::findBuddy(QLabel *l, const QWidgetList &existingBuddies) const
354{
355 enum { DeltaX = 5 };
356 const QWidget *parent = l->parentWidget();
357 // Try to find next managed neighbour on horizontal line
358 const QRect geom = l->geometry();
359 const int y = geom.center().y();
360 QWidget *neighbour = nullptr;
361 switch (l->layoutDirection()) {
362 case Qt::LayoutDirectionAuto:
363 case Qt::LeftToRight: { // Walk right to find next managed neighbour
364 const int xEnd = parent->size().width();
365 for (int x = geom.right() + 1; x < xEnd; x += DeltaX)
366 if (QWidget *c = parent->childAt (x, y))
367 if (m_formWindow->isManaged(c)) {
368 neighbour = c;
369 break;
370 }
371 }
372 break;
373 case Qt::RightToLeft: // Walk left to find next managed neighbour
374 for (int x = geom.x() - 1; x >= 0; x -= DeltaX)
375 if (QWidget *c = parent->childAt (x, y))
376 if (m_formWindow->isManaged(c)) {
377 neighbour = c;
378 break;
379 }
380 break;
381 }
382 if (neighbour && !existingBuddies.contains(neighbour) && canBeBuddy(neighbour, m_formWindow))
383 return neighbour;
384
385 return nullptr;
386}
387
389{
390 QAction *autoAction = menu.addAction(tr("Set automatically"));
391 connect(autoAction, &QAction::triggered, this, &BuddyEditor::autoBuddy);
392 menu.addSeparator();
393 ConnectionEdit::createContextMenu(menu);
394}
395
396}
397
398QT_END_NAMESPACE
static bool canBeBuddy(QWidget *w, QDesignerFormWindowInterface *form)
static QString buddy(QLabel *label, QDesignerFormEditorInterface *core)
static constexpr auto buddyPropertyC
friend class QWidget
Definition qpainter.h:421
QDesignerFormWindowInterface * formWindow() const
QWidget * widgetAt(const QPoint &pos) const override
void createContextMenu(QMenu &menu) override
Auxiliary methods to store/retrieve settings.
static QUndoCommand * createBuddyCommand(QDesignerFormWindowInterface *fw, QLabel *label, QWidget *buddy)