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
canBeBuddy(QWidget * w,QDesignerFormWindowInterface * form)56 static bool canBeBuddy(QWidget *w, QDesignerFormWindowInterface *form)
57 {
58 if (qobject_cast<const QLayoutWidget*>(w) || qobject_cast<const QLabel*>(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*>(ext, 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(sheet->property(index), &ok));
69 // Refuse No-focus unless the widget is promoted.
70 return (ok && q != Qt::NoFocus) || qdesigner_internal::isPromoted(form->core(), w);
71 }
72 }
73 return false;
74 }
75
buddy(QLabel * label,QDesignerFormEditorInterface * core)76 static QString buddy(QLabel *label, QDesignerFormEditorInterface *core)
77 {
78 QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), label);
79 if (sheet == nullptr)
80 return QString();
81 const int prop_idx = sheet->indexOf(QLatin1String(buddyPropertyC));
82 if (prop_idx == -1)
83 return QString();
84 return sheet->property(prop_idx).toString();
85 }
86
87 namespace qdesigner_internal {
88
89 /*******************************************************************************
90 ** BuddyEditor
91 */
92
BuddyEditor(QDesignerFormWindowInterface * form,QWidget * parent)93 BuddyEditor::BuddyEditor(QDesignerFormWindowInterface *form, QWidget *parent) :
94 ConnectionEdit(parent, form),
95 m_formWindow(form),
96 m_updating(false)
97 {
98 }
99
100
widgetAt(const QPoint & pos) const101 QWidget *BuddyEditor::widgetAt(const QPoint &pos) const
102 {
103 QWidget *w = ConnectionEdit::widgetAt(pos);
104
105 while (w != nullptr && !m_formWindow->isManaged(w))
106 w = w->parentWidget();
107 if (!w)
108 return w;
109
110 if (state() == Editing) {
111 QLabel *label = qobject_cast<QLabel*>(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(EndPoint::Source) == w)
118 return nullptr;
119 }
120 } else {
121 if (!canBeBuddy(w, m_formWindow))
122 return nullptr;
123 }
124
125 return w;
126 }
127
createConnection(QWidget * source,QWidget * destination)128 Connection *BuddyEditor::createConnection(QWidget *source, QWidget *destination)
129 {
130 return new Connection(this, source, destination);
131 }
132
formWindow() const133 QDesignerFormWindowInterface *BuddyEditor::formWindow() const
134 {
135 return m_formWindow;
136 }
137
updateBackground()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, m_formWindow->core());
149 if (buddy_name.isEmpty())
150 continue;
151
152 const QWidgetList targets = background()->findChildren<QWidget*>(buddy_name);
153 if (targets.isEmpty())
154 continue;
155
156 const auto wit = std::find_if(targets.cbegin(), targets.cend(),
157 [] (const QWidget *w) { return !w->isHidden(); });
158 if (wit == targets.cend())
159 continue;
160
161 Connection *con = new Connection(this);
162 con->setEndPoint(EndPoint::Source, label, widgetRect(label).center());
163 con->setEndPoint(EndPoint::Target, *wit, widgetRect(*wit).center());
164 newList.append(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(EndPoint::Source);
173 QObject *target = con->object(EndPoint::Target);
174 const bool found =
175 std::any_of(newList.cbegin(), newList.cend(),
176 [source, target] (const Connection *nc)
177 { return nc->object(EndPoint::Source) == source && nc->object(EndPoint::Target) == target; });
178 if (!found)
179 toRemove.append(con);
180 }
181 if (!toRemove.isEmpty()) {
182 DeleteConnectionsCommand command(this, toRemove);
183 command.redo();
184 for (Connection *con : qAsConst(toRemove))
185 delete takeConnection(con);
186 }
187
188 for (Connection *newConn : qAsConst(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(EndPoint::Source) == newConn->object(EndPoint::Source) &&
194 con->object(EndPoint::Target) == newConn->object(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
setBackground(QWidget * background)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, m_formWindow->core());
217 if (buddy_name.isEmpty())
218 continue;
219 QWidget *target = background->findChild<QWidget*>(buddy_name);
220 if (target == nullptr)
221 continue;
222
223 Connection *con = new Connection(this);
224 con->setEndPoint(EndPoint::Source, label, widgetRect(label).center());
225 con->setEndPoint(EndPoint::Target, target, widgetRect(target).center());
226 addConnection(con);
227 }
228 }
229
createBuddyCommand(QDesignerFormWindowInterface * fw,QLabel * label,QWidget * buddy)230 static QUndoCommand *createBuddyCommand(QDesignerFormWindowInterface *fw, QLabel *label, QWidget *buddy)
231 {
232 SetPropertyCommand *command = new SetPropertyCommand(fw);
233 command->init(label, QLatin1String(buddyPropertyC), buddy->objectName());
234 command->setText(BuddyEditor::tr("Add buddy"));
235 return command;
236 }
237
endConnection(QWidget * target,const QPoint & pos)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(EndPoint::Target, target, pos);
244
245 QWidget *source = tmp_con->widget(EndPoint::Source);
246 Q_ASSERT(source != nullptr);
247 Q_ASSERT(target != nullptr);
248 setEnabled(false);
249 Connection *new_con = createConnection(source, target);
250 setEnabled(true);
251 if (new_con != nullptr) {
252 new_con->setEndPoint(EndPoint::Source, source, tmp_con->endPointPos(EndPoint::Source));
253 new_con->setEndPoint(EndPoint::Target, target, tmp_con->endPointPos(EndPoint::Target));
254
255 selectNone();
256 addConnection(new_con);
257 QLabel *source = qobject_cast<QLabel*>(new_con->widget(EndPoint::Source));
258 QWidget *target = new_con->widget(EndPoint::Target);
259 if (source) {
260 undoStack()->push(createBuddyCommand(m_formWindow, source, target));
261 } else {
262 qDebug("BuddyEditor::endConnection(): not a label");
263 }
264 setSelected(new_con, true);
265 }
266
267 clearNewlyAddedConnection();
268 findObjectsUnderMouse(mapFromGlobal(QCursor::pos()));
269 }
270
widgetRemoved(QWidget * widget)271 void BuddyEditor::widgetRemoved(QWidget *widget)
272 {
273 QWidgetList child_list = widget->findChildren<QWidget*>();
274 child_list.prepend(widget);
275
276 ConnectionSet remove_set;
277 for (QWidget *w : qAsConst(child_list)) {
278 const ConnectionList &cl = connectionList();
279 for (Connection *con : cl) {
280 if (con->widget(EndPoint::Source) == w || con->widget(EndPoint::Target) == w)
281 remove_set.insert(con, con);
282 }
283 }
284
285 if (!remove_set.isEmpty()) {
286 undoStack()->beginMacro(tr("Remove buddies"));
287 for (Connection *con : qAsConst(remove_set)) {
288 setSelected(con, false);
289 con->update();
290 QWidget *source = con->widget(EndPoint::Source);
291 if (qobject_cast<QLabel*>(source) == 0) {
292 qDebug("BuddyConnection::widgetRemoved(): not a label");
293 } else {
294 ResetPropertyCommand *command = new ResetPropertyCommand(formWindow());
295 command->init(source, QLatin1String(buddyPropertyC));
296 undoStack()->push(command);
297 }
298 delete takeConnection(con);
299 }
300 undoStack()->endMacro();
301 }
302 }
303
deleteSelected()304 void BuddyEditor::deleteSelected()
305 {
306 const ConnectionSet selectedConnections = selection(); // want copy for unselect
307 if (selectedConnections.isEmpty())
308 return;
309
310 undoStack()->beginMacro(tr("Remove %n buddies", nullptr, selectedConnections.size()));
311 for (Connection *con : selectedConnections) {
312 setSelected(con, false);
313 con->update();
314 QWidget *source = con->widget(EndPoint::Source);
315 if (qobject_cast<QLabel*>(source) == 0) {
316 qDebug("BuddyConnection::deleteSelected(): not a label");
317 } else {
318 ResetPropertyCommand *command = new ResetPropertyCommand(formWindow());
319 command->init(source, QLatin1String(buddyPropertyC));
320 undoStack()->push(command);
321 }
322 delete takeConnection(con);
323 }
324 undoStack()->endMacro();
325 }
326
autoBuddy()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(c->widget(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(label)) {
344 const QString buddy_name = buddy(label, m_formWindow->core());
345 if (buddy_name.isEmpty())
346 newBuddy = findBuddy(label, usedBuddies);
347 }
348 if (newBuddy) {
349 buddies.push_back(newBuddy);
350 usedBuddies.push_back(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(tr("Add %n buddies", nullptr, count));
362 for (int i = 0; i < count; i++)
363 undoStack()->push(createBuddyCommand(m_formWindow, labelList.at(i), 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, buddies.contains(con->widget(EndPoint::Target)));
369 }
370
371 // Geometrically find a potential buddy for label by checking neighbouring children of parent
findBuddy(QLabel * l,const QWidgetList & existingBuddies) const372 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 (x, y))
386 if (m_formWindow->isManaged(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 (x, y))
395 if (m_formWindow->isManaged(c)) {
396 neighbour = c;
397 break;
398 }
399 break;
400 }
401 if (neighbour && !existingBuddies.contains(neighbour) && canBeBuddy(neighbour, m_formWindow))
402 return neighbour;
403
404 return nullptr;
405 }
406
createContextMenu(QMenu & menu)407 void BuddyEditor::createContextMenu(QMenu &menu)
408 {
409 QAction *autoAction = menu.addAction(tr("Set automatically"));
410 connect(autoAction, &QAction::triggered, this, &BuddyEditor::autoBuddy);
411 menu.addSeparator();
412 ConnectionEdit::createContextMenu(menu);
413 }
414
415 }
416
417 QT_END_NAMESPACE
418