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

source code of qttools/src/designer/src/components/buddyeditor/buddyeditor.cpp