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