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 "qdesigner_dnditem_p.h" |
30 | #include "formwindowbase_p.h" |
31 | #include <QtDesigner/private/ui4_p.h> |
32 | |
33 | #include <QtGui/qpainter.h> |
34 | #include <QtGui/qbitmap.h> |
35 | #include <QtGui/qpixmap.h> |
36 | #include <QtGui/qimage.h> |
37 | #include <QtWidgets/qlabel.h> |
38 | #include <QtGui/qdrag.h> |
39 | #include <QtGui/qcursor.h> |
40 | #include <QtGui/qevent.h> |
41 | #include <QtGui/qrgb.h> |
42 | |
43 | #include <QtCore/qmap.h> |
44 | |
45 | QT_BEGIN_NAMESPACE |
46 | |
47 | namespace qdesigner_internal { |
48 | |
49 | QDesignerDnDItem::QDesignerDnDItem(DropType type, QWidget *source) : |
50 | m_source(source), |
51 | m_type(type), |
52 | m_dom_ui(nullptr), |
53 | m_widget(nullptr), |
54 | m_decoration(nullptr) |
55 | { |
56 | } |
57 | |
58 | void QDesignerDnDItem::init(DomUI *ui, QWidget *widget, QWidget *decoration, |
59 | const QPoint &global_mouse_pos) |
60 | { |
61 | Q_ASSERT(widget != nullptr || ui != nullptr); |
62 | Q_ASSERT(decoration != nullptr); |
63 | |
64 | m_dom_ui = ui; |
65 | m_widget = widget; |
66 | m_decoration = decoration; |
67 | |
68 | m_hot_spot = global_mouse_pos - m_decoration->geometry().topLeft(); |
69 | } |
70 | |
71 | QDesignerDnDItem::~QDesignerDnDItem() |
72 | { |
73 | if (m_decoration != nullptr) |
74 | m_decoration->deleteLater(); |
75 | delete m_dom_ui; |
76 | } |
77 | |
78 | DomUI *QDesignerDnDItem::domUi() const |
79 | { |
80 | return m_dom_ui; |
81 | } |
82 | |
83 | QWidget *QDesignerDnDItem::decoration() const |
84 | { |
85 | return m_decoration; |
86 | } |
87 | |
88 | QPoint QDesignerDnDItem::hotSpot() const |
89 | { |
90 | return m_hot_spot; |
91 | } |
92 | |
93 | QWidget *QDesignerDnDItem::widget() const |
94 | { |
95 | return m_widget; |
96 | } |
97 | |
98 | QDesignerDnDItem::DropType QDesignerDnDItem::type() const |
99 | { |
100 | return m_type; |
101 | } |
102 | |
103 | QWidget *QDesignerDnDItem::source() const |
104 | { |
105 | return m_source; |
106 | } |
107 | |
108 | void QDesignerDnDItem::setDomUi(DomUI *dom_ui) |
109 | { |
110 | delete m_dom_ui; |
111 | m_dom_ui = dom_ui; |
112 | } |
113 | |
114 | // ---------- QDesignerMimeData |
115 | |
116 | // Make pixmap transparent on Windows only. Mac is transparent by default, Unix usually does not work. |
117 | #ifdef Q_OS_WIN |
118 | # define TRANSPARENT_DRAG_PIXMAP |
119 | #endif |
120 | |
121 | QDesignerMimeData::QDesignerMimeData(const QDesignerDnDItems &items, QDrag *drag) : |
122 | m_items(items) |
123 | { |
124 | enum { Alpha = 200 }; |
125 | QPoint decorationTopLeft; |
126 | switch (m_items.size()) { |
127 | case 0: |
128 | break; |
129 | case 1: { |
130 | QWidget *deco = m_items.first()->decoration(); |
131 | decorationTopLeft = deco->pos(); |
132 | const QPixmap widgetPixmap = deco->grab(rectangle: QRect(0, 0, -1, -1)); |
133 | #ifdef TRANSPARENT_DRAG_PIXMAP |
134 | QImage image(widgetPixmap.size(), QImage::Format_ARGB32); |
135 | image.setDevicePixelRatio(widgetPixmap.devicePixelRatio()); |
136 | image.fill(QColor(Qt::transparent).rgba()); |
137 | QPainter painter(&image); |
138 | painter.drawPixmap(QPoint(0, 0), widgetPixmap); |
139 | painter.end(); |
140 | setImageTransparency(image, Alpha); |
141 | drag->setPixmap(QPixmap::fromImage(image)); |
142 | #else |
143 | drag->setPixmap(widgetPixmap); |
144 | #endif |
145 | } |
146 | break; |
147 | default: { |
148 | // determine size of drag decoration by uniting all geometries |
149 | const QDesignerDnDItems::const_iterator cend = m_items.constEnd(); |
150 | QDesignerDnDItems::const_iterator it =m_items.constBegin(); |
151 | QRect unitedGeometry = (*it)->decoration()->geometry(); |
152 | const qreal devicePixelRatio = (*it)->decoration()->devicePixelRatioF(); |
153 | for (++it; it != cend; ++it ) |
154 | unitedGeometry = unitedGeometry .united(r: (*it)->decoration()->geometry()); |
155 | |
156 | // paint with offset. At the same time, create a mask bitmap, containing widget rectangles. |
157 | const QSize imageSize = (QSizeF(unitedGeometry.size()) * devicePixelRatio).toSize(); |
158 | QImage image(imageSize, QImage::Format_ARGB32); |
159 | image.setDevicePixelRatio(devicePixelRatio); |
160 | image.fill(pixel: QColor(Qt::transparent).rgba()); |
161 | QBitmap mask(imageSize); |
162 | mask.setDevicePixelRatio(devicePixelRatio); |
163 | mask.clear(); |
164 | // paint with offset, determine action |
165 | QPainter painter(&image); |
166 | QPainter maskPainter(&mask); |
167 | decorationTopLeft = unitedGeometry.topLeft(); |
168 | for (it = m_items.constBegin() ; it != cend; ++it ) { |
169 | QWidget *w = (*it)->decoration(); |
170 | const QPixmap wp = w->grab(rectangle: QRect(0, 0, -1, -1)); |
171 | const QPoint pos = w->pos() - decorationTopLeft; |
172 | painter.drawPixmap(p: pos, pm: wp); |
173 | maskPainter.fillRect(r: QRect(pos, w->size()), c: Qt::color1); |
174 | } |
175 | painter.end(); |
176 | maskPainter.end(); |
177 | #ifdef TRANSPARENT_DRAG_PIXMAP |
178 | setImageTransparency(image, Alpha); |
179 | #endif |
180 | QPixmap pixmap = QPixmap::fromImage(image); |
181 | pixmap.setMask(mask); |
182 | drag->setPixmap(pixmap); |
183 | } |
184 | break; |
185 | } |
186 | // determine hot spot and reconstruct the exact starting position as form window |
187 | // introduces some offset when detecting DnD |
188 | m_globalStartPos = m_items.first()->decoration()->pos() + m_items.first()->hotSpot(); |
189 | m_hotSpot = m_globalStartPos - decorationTopLeft; |
190 | drag->setHotSpot(m_hotSpot); |
191 | |
192 | drag->setMimeData(this); |
193 | } |
194 | |
195 | QDesignerMimeData::~QDesignerMimeData() |
196 | { |
197 | const QDesignerDnDItems::const_iterator cend = m_items.constEnd(); |
198 | for (QDesignerDnDItems::const_iterator it = m_items.constBegin(); it != cend; ++it ) |
199 | delete *it; |
200 | } |
201 | |
202 | Qt::DropAction QDesignerMimeData::proposedDropAction() const |
203 | { |
204 | return m_items.first()->type() == QDesignerDnDItemInterface::CopyDrop ? Qt::CopyAction : Qt::MoveAction; |
205 | } |
206 | |
207 | Qt::DropAction QDesignerMimeData::execDrag(const QDesignerDnDItems &items, QWidget * dragSource) |
208 | { |
209 | if (items.isEmpty()) |
210 | return Qt::IgnoreAction; |
211 | |
212 | QDrag *drag = new QDrag(dragSource); |
213 | QDesignerMimeData *mimeData = new QDesignerMimeData(items, drag); |
214 | |
215 | // Store pointers to widgets that are to be re-shown if a move operation is canceled |
216 | QWidgetList reshowWidgets; |
217 | const QDesignerDnDItems::const_iterator cend = items.constEnd(); |
218 | for (QDesignerDnDItems::const_iterator it = items.constBegin(); it != cend; ++it ) |
219 | if (QWidget *w = (*it)->widget()) |
220 | if ((*it)->type() == QDesignerDnDItemInterface::MoveDrop) |
221 | reshowWidgets.push_back(t: w); |
222 | |
223 | const Qt::DropAction executedAction = drag->exec(supportedActions: Qt::CopyAction|Qt::MoveAction, defaultAction: mimeData->proposedDropAction()); |
224 | |
225 | if (executedAction == Qt::IgnoreAction) { |
226 | for (QWidget *w : qAsConst(t&: reshowWidgets)) |
227 | w->show(); |
228 | } |
229 | |
230 | return executedAction; |
231 | } |
232 | |
233 | |
234 | void QDesignerMimeData::moveDecoration(const QPoint &globalPos) const |
235 | { |
236 | const QPoint relativeDistance = globalPos - m_globalStartPos; |
237 | const QDesignerDnDItems::const_iterator cend = m_items.constEnd(); |
238 | for (QDesignerDnDItems::const_iterator it =m_items.constBegin(); it != cend; ++it ) { |
239 | QWidget *w = (*it)->decoration(); |
240 | w->move(w->pos() + relativeDistance); |
241 | } |
242 | } |
243 | |
244 | void QDesignerMimeData::removeMovedWidgetsFromSourceForm(const QDesignerDnDItems &items) |
245 | { |
246 | typedef QMultiMap<FormWindowBase *, QWidget *> FormWidgetMap; |
247 | FormWidgetMap formWidgetMap; |
248 | // Find moved widgets per form |
249 | const QDesignerDnDItems::const_iterator cend = items.constEnd(); |
250 | for (QDesignerDnDItems::const_iterator it = items.constBegin(); it != cend; ++it ) |
251 | if ((*it)->type() == QDesignerDnDItemInterface::MoveDrop) |
252 | if (QWidget *w = (*it)->widget()) |
253 | if (FormWindowBase *fb = qobject_cast<FormWindowBase *>(object: (*it)->source())) |
254 | formWidgetMap.insert(akey: fb, avalue: w); |
255 | |
256 | const auto &formWindows = formWidgetMap.uniqueKeys(); |
257 | for (FormWindowBase *fb : formWindows) |
258 | fb->deleteWidgetList(widget_list: formWidgetMap.values(akey: fb)); |
259 | } |
260 | |
261 | void QDesignerMimeData::acceptEventWithAction(Qt::DropAction desiredAction, QDropEvent *e) |
262 | { |
263 | if (e->proposedAction() == desiredAction) { |
264 | e->acceptProposedAction(); |
265 | } else { |
266 | e->setDropAction(desiredAction); |
267 | e->accept(); |
268 | } |
269 | } |
270 | |
271 | void QDesignerMimeData::acceptEvent(QDropEvent *e) const |
272 | { |
273 | acceptEventWithAction(desiredAction: proposedDropAction(), e); |
274 | } |
275 | |
276 | void QDesignerMimeData::setImageTransparency(QImage &image, int alpha) |
277 | { |
278 | const int height = image.height(); |
279 | for (int l = 0; l < height; l++) { |
280 | QRgb *line = reinterpret_cast<QRgb *>(image.scanLine(l)); |
281 | QRgb *lineEnd = line + image.width(); |
282 | for ( ; line < lineEnd; line++) { |
283 | const QRgb rgba = *line; |
284 | *line = qRgba(r: qRed(rgb: rgba), g: qGreen(rgb: rgba), b: qBlue(rgb: rgba), a: alpha); |
285 | } |
286 | } |
287 | } |
288 | |
289 | } // namespace qdesigner_internal |
290 | |
291 | QT_END_NAMESPACE |
292 | |