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 | |
52 | QT_BEGIN_NAMESPACE |
53 | |
54 | static const char *buddyPropertyC = "buddy" ; |
55 | |
56 | static 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 | |
76 | static 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 | |
87 | namespace qdesigner_internal { |
88 | |
89 | /******************************************************************************* |
90 | ** BuddyEditor |
91 | */ |
92 | |
93 | BuddyEditor::BuddyEditor(QDesignerFormWindowInterface *form, QWidget *parent) : |
94 | ConnectionEdit(parent, form), |
95 | m_formWindow(form), |
96 | m_updating(false) |
97 | { |
98 | } |
99 | |
100 | |
101 | QWidget *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 | |
128 | Connection *BuddyEditor::createConnection(QWidget *source, QWidget *destination) |
129 | { |
130 | return new Connection(this, source, destination); |
131 | } |
132 | |
133 | QDesignerFormWindowInterface *BuddyEditor::formWindow() const |
134 | { |
135 | return m_formWindow; |
136 | } |
137 | |
138 | void 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 | |
209 | void 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 | |
230 | static 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 | |
238 | void 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 | |
271 | void 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 | |
304 | void 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 | |
327 | void 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 |
372 | QWidget *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 | |
407 | void BuddyEditor::(QMenu &) |
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 | |
417 | QT_END_NAMESPACE |
418 | |