1 /************************************************************************
2 *
3 * Copyright 2010-2011 Jakob Leben (jakob.leben@gmail.com)
4 *
5 * This file is part of SuperCollider Qt GUI.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 ************************************************************************/
21
22 #include "QWidgetProxy.h"
23 #include "painting.h"
24 #include "Common.h"
25 #include "hacks/hacks_qt.hpp"
26
27 #include <QApplication>
28 #include <QLayout>
29 #include <QMouseEvent>
30 #include <QKeyEvent>
31 #include <QPainter>
32 #include <QFontMetrics>
33 #include <QUrl>
34 #include <QMimeData>
35 #include <QDrag>
36
37 #ifdef Q_WS_X11
38 # include "hacks/hacks_x11.hpp"
39 # include <QX11Info>
40 # include <X11/Xlib.h>
41 // X11 defines the following, clashing with QEvent::Type enum
42 # undef KeyPress
43 # undef KeyRelease
44 #endif
45
46 #ifdef Q_OS_MAC
47 # include "./hacks/hacks_mac.hpp"
48 #endif
49
50 using namespace QtCollider;
51
52 QAtomicInt QWidgetProxy::_globalEventMask = 0;
53 QMimeData* QWidgetProxy::sDragData = 0;
54 QString QWidgetProxy::sDragLabel;
55
QWidgetProxy(QWidget * w,PyrObject * po)56 QWidgetProxy::QWidgetProxy(QWidget* w, PyrObject* po):
57 QObjectProxy(w, po),
58 _keyEventWidget(w),
59 _mouseEventWidget(w),
60 _performDrag(false) {}
61
setKeyEventWidget(QWidget * w)62 void QWidgetProxy::setKeyEventWidget(QWidget* w) {
63 if (w == 0 || w == _keyEventWidget)
64 return;
65
66 QWidget* me = widget();
67
68 if (_keyEventWidget != me)
69 _keyEventWidget->removeEventFilter(this);
70
71 _keyEventWidget = w;
72
73 if (_keyEventWidget != me) {
74 _keyEventWidget->installEventFilter(this);
75 }
76 }
77
setMouseEventWidget(QWidget * w)78 void QWidgetProxy::setMouseEventWidget(QWidget* w) {
79 if (w == 0 || w == _mouseEventWidget)
80 return;
81
82 QWidget* me = widget();
83
84 if (_mouseEventWidget != me)
85 _mouseEventWidget->removeEventFilter(this);
86
87 _mouseEventWidget = w;
88
89 if (_mouseEventWidget != me) {
90 _mouseEventWidget->installEventFilter(this);
91 }
92 }
93
alwaysOnTop()94 bool QWidgetProxy::alwaysOnTop() {
95 QWidget* w = widget();
96 if (!w)
97 return false;
98
99 Qt::WindowFlags flags = w->windowFlags();
100 if (flags & Qt::Window && flags & Qt::WindowStaysOnTopHint)
101 return true;
102 else
103 return false;
104 }
105
refresh()106 void QWidgetProxy::refresh() {
107 QWidget* w = widget();
108 if (w)
109 sendRefreshEventRecursive(w);
110 }
111
setLayout(QObjectProxy * layoutProxy)112 void QWidgetProxy::setLayout(QObjectProxy* layoutProxy) {
113 QWidget* w = widget();
114 QLayout* l = qobject_cast<QLayout*>(layoutProxy->object());
115 if (!w || !l)
116 return;
117
118 QLayout* exLayout = w->layout();
119 if (exLayout != l) {
120 if (exLayout != 0) {
121 qcDebugMsg(2, QStringLiteral("Deleting old layout."));
122 delete exLayout;
123 }
124 qcDebugMsg(2, QStringLiteral("Setting layout."));
125 w->setLayout(l);
126 l->activate();
127 } else {
128 qcDebugMsg(2, QStringLiteral("Layout same as existing. Will do nothing."));
129 }
130 }
131
setParent(QObjectProxy * parentProxy)132 bool QWidgetProxy::setParent(QObjectProxy* parentProxy) {
133 QObject* parent = parentProxy->object();
134 if (!parent || !widget())
135 return true;
136
137 if (parent->isWidgetType()) {
138 QWidget* pw = qobject_cast<QWidget*>(parent);
139 bool ok = pw->metaObject()->invokeMethod(pw, "addChild", Q_ARG(QWidget*, widget()));
140 if (!ok)
141 widget()->setParent(pw);
142 return true;
143 }
144 return false;
145 }
146
setDragData(QMimeData * data,const QString & label)147 void QWidgetProxy::setDragData(QMimeData* data, const QString& label) {
148 if (data == 0)
149 return;
150
151 if (sDragData == 0) {
152 sDragData = data;
153 sDragLabel = label;
154 _performDrag = true;
155 } else {
156 delete data;
157 qcErrorMsg("QWidgetProxy: attempt at starting a drag while another one is in progress.");
158 }
159 }
160
customEvent(QEvent * e)161 void QWidgetProxy::customEvent(QEvent* e) {
162 int type = e->type();
163 switch (type) {
164 case QtCollider::Event_Proxy_BringFront:
165 bringFrontEvent();
166 return;
167 case QtCollider::Event_Proxy_SetFocus:
168 setFocusEvent(static_cast<SetFocusEvent*>(e));
169 return;
170 case QtCollider::Event_Proxy_SetAlwaysOnTop:
171 setAlwaysOnTopEvent(static_cast<SetAlwaysOnTopEvent*>(e));
172 return;
173 default:
174 QObjectProxy::customEvent(e);
175 }
176 }
177
178
bringFrontEvent()179 void QWidgetProxy::bringFrontEvent() {
180 QWidget* w = widget();
181 if (!w)
182 return;
183
184 w->setWindowState(w->windowState() & ~Qt::WindowMinimized | Qt::WindowActive);
185 w->show();
186 w->raise();
187
188 #ifdef Q_WS_X11
189 raise_window(QX11Info::display(), w);
190 #endif
191
192 #ifdef Q_OS_MAC
193 QtCollider::Mac::activateApp();
194 #endif
195 }
196
setFocusEvent(QtCollider::SetFocusEvent * e)197 void QWidgetProxy::setFocusEvent(QtCollider::SetFocusEvent* e) {
198 if (!widget())
199 return;
200
201 if (e->focus)
202 widget()->setFocus(Qt::OtherFocusReason);
203 else
204 widget()->clearFocus();
205 }
206
setAlwaysOnTopEvent(QtCollider::SetAlwaysOnTopEvent * e)207 void QWidgetProxy::setAlwaysOnTopEvent(QtCollider::SetAlwaysOnTopEvent* e) {
208 QWidget* w = widget();
209 if (!w)
210 return;
211
212 Qt::WindowFlags flags = w->windowFlags();
213 if (flags & Qt::Window) {
214 if (e->alwaysOnTop)
215 flags |= Qt::WindowStaysOnTopHint;
216 else
217 flags &= ~Qt::WindowStaysOnTopHint;
218
219 // record the initial state to restore it later
220 QPoint pos = w->pos();
221 bool visible = w->isVisible();
222
223 w->setWindowFlags(flags);
224
225 // setting window flags will move the window to (0,0) and hide it,
226 // so restore the initial state
227 w->move(pos);
228 if (visible)
229 w->show();
230 }
231 }
232
performDrag()233 void QWidgetProxy::performDrag() {
234 Q_ASSERT(sDragData);
235
236 QFont f;
237 const QString& label = sDragLabel;
238 QFontMetrics fm(f);
239 QSize size = fm.size(0, label) + QSize(8, 4);
240
241 QPixmap pix(size);
242 QPainter p(&pix);
243 p.setBrush(QColor(255, 255, 255));
244 QRect r(pix.rect());
245 p.drawRect(r.adjusted(0, 0, -1, -1));
246 p.drawText(r, Qt::AlignCenter, label);
247 p.end();
248
249 QDrag* drag = new QDrag(widget());
250 drag->setMimeData(sDragData);
251 drag->setPixmap(pix);
252 drag->setHotSpot(QPoint(0, +r.height() + 2));
253 drag->exec();
254
255 sDragData = 0;
256 sDragLabel.clear();
257 }
258
preProcessEvent(QObject * o,QEvent * e,EventHandlerData & eh,QList<QVariant> & args)259 bool QWidgetProxy::preProcessEvent(QObject* o, QEvent* e, EventHandlerData& eh, QList<QVariant>& args) {
260 // NOTE We assume that qObject need not be checked here, as we wouldn't get events if
261 // it wasn't existing
262 int acquired_globalEventMask = _globalEventMask.load();
263
264 switch (eh.type) {
265 case QEvent::KeyPress:
266 return ((acquired_globalEventMask & KeyPress) || eh.enabled) && interpretKeyEvent(o, e, args);
267
268 case QEvent::KeyRelease:
269 return ((acquired_globalEventMask & KeyRelease) || eh.enabled) && interpretKeyEvent(o, e, args);
270
271 case QEvent::MouseButtonPress:
272 case QEvent::MouseMove:
273 case QEvent::MouseButtonRelease:
274 case QEvent::MouseButtonDblClick:
275 case QEvent::Enter:
276 case QEvent::Leave:
277 return eh.enabled && interpretMouseEvent(o, e, args);
278
279 case QEvent::Wheel:
280 return eh.enabled && interpretMouseWheelEvent(o, e, args);
281
282 case QEvent::DragEnter:
283 case QEvent::DragMove:
284 case QEvent::Drop:
285 return eh.enabled && interpretDragEvent(o, e, args);
286
287 default:
288 return eh.enabled;
289 }
290 }
291
interpretMouseEvent(QObject * o,QEvent * e,QList<QVariant> & args)292 bool QWidgetProxy::interpretMouseEvent(QObject* o, QEvent* e, QList<QVariant>& args) {
293 if (o != _mouseEventWidget || !_mouseEventWidget->isEnabled())
294 return false;
295
296 QWidget* w = widget();
297
298 QEvent::Type etype = e->type();
299
300 if (etype == QEvent::Enter || etype == QEvent::Leave)
301 return true;
302
303 QMouseEvent* mouse = static_cast<QMouseEvent*>(e);
304 QPoint pt = (_mouseEventWidget == w ? mouse->pos() : _mouseEventWidget->mapTo(w, mouse->pos()));
305 args << pt.x();
306 args << pt.y();
307
308 args << (int)mouse->modifiers();
309
310 if (etype == QEvent::MouseMove) {
311 int buttons = mouse->buttons();
312
313 if (buttons == 0) {
314 // Special treatment of mouse-tracking events.
315
316 QWidget* win = w->window();
317
318 // Only accept if window has a special property enabled.
319 if (!(win && win->property("_qc_win_mouse_tracking").toBool()))
320 return false;
321
322 // Reject the events when mouse pointer leaves the window,
323 // resulting in out-of-bounds coordinates
324 if (win == w) {
325 if (pt.x() < 0 || pt.x() >= w->width() || pt.y() < 0 || pt.y() >= w->height())
326 return false;
327 }
328 }
329
330 args << (int)mouse->buttons();
331 } else {
332 // MouseButtonPress, MouseButtonDblClick, MouseButtonRelease
333
334 int button;
335
336 switch (mouse->button()) {
337 case Qt::LeftButton:
338 button = 0;
339 break;
340 case Qt::RightButton:
341 button = 1;
342 break;
343 case Qt::MidButton:
344 button = 2;
345 break;
346 default:
347 button = -1;
348 }
349
350 args << button;
351
352 if (etype == QEvent::MouseButtonPress)
353 args << 1;
354 else if (etype == QEvent::MouseButtonDblClick)
355 args << 2;
356 }
357
358 return true;
359 }
360
interpretMouseWheelEvent(QObject * o,QEvent * e,QList<QVariant> & args)361 bool QWidgetProxy::interpretMouseWheelEvent(QObject* o, QEvent* e, QList<QVariant>& args) {
362 // NOTE: There seems to be a bug in wheel event propagation:
363 // the event is propagated to parent twice!
364 // Therefore we do not let the propagated events through to SC,
365 // (we only let the "spontaneous" ones).
366
367 if (o != _mouseEventWidget || !e->spontaneous() || !_mouseEventWidget->isEnabled())
368 return false;
369
370 QWheelEvent* we = static_cast<QWheelEvent*>(e);
371
372 QWidget* w = widget();
373 QPoint pt = _mouseEventWidget == w ? we->pos() : _mouseEventWidget->mapTo(w, we->pos());
374
375 // only hi-res trackpads return pixelDelta everything else uses angleDelta..
376 QPointF delta = we->pixelDelta().isNull() ? we->angleDelta() / 8.f // scaled to return steps of 15
377 : we->pixelDelta() * 0.25f; // this matches old scaling of delta
378
379 args << pt.x();
380 args << pt.y();
381 args << (int)we->modifiers();
382 args << delta.x();
383 args << delta.y();
384
385 return true;
386 }
387
interpretKeyEvent(QObject * o,QEvent * e,QList<QVariant> & args)388 bool QWidgetProxy::interpretKeyEvent(QObject* o, QEvent* e, QList<QVariant>& args) {
389 if (o != _keyEventWidget || !_keyEventWidget->isEnabled())
390 return false;
391
392 QKeyEvent* ke = static_cast<QKeyEvent*>(e);
393
394 int key = ke->key();
395
396 int mods = ke->modifiers();
397
398 QChar character;
399
400 #ifdef Q_OS_MAC
401 bool isLetter = key >= Qt::Key_A && key <= Qt::Key_Z;
402 if (mods & Qt::MetaModifier && isLetter) {
403 character = QChar(key - Qt::Key_A + 1);
404 } else if (mods & Qt::AltModifier && isLetter) {
405 character = (mods & Qt::ShiftModifier) ? QChar(key) : QChar(key - Qt::Key_A + 97);
406 } else
407 #endif
408 {
409 QString text(ke->text());
410 if (text.count())
411 character = text[0];
412 }
413
414 int unicode = character.unicode();
415
416 #ifdef Q_WS_X11
417 KeySym sym = ke->nativeVirtualKey();
418 int keycode = XKeysymToKeycode(QX11Info::display(), sym);
419 #else
420 // FIXME: On Mac OS X, this does not work for modifier keys
421 int keycode = ke->nativeVirtualKey();
422 #endif
423
424 args << character;
425 args << mods;
426 args << unicode;
427 args << keycode;
428 args << key;
429 args << ke->spontaneous();
430
431 return true;
432 }
433
urlAsString(const QUrl & url)434 static QString urlAsString(const QUrl& url) {
435 if (QURL_IS_LOCAL_FILE(url))
436 return url.toLocalFile();
437 else
438 return url.toString();
439 }
440
interpretMimeData(const QMimeData * data,QList<QVariant> & args)441 static bool interpretMimeData(const QMimeData* data, QList<QVariant>& args) {
442 if (data->hasUrls()) {
443 QList<QUrl> urls = data->urls();
444 if (urls.count() > 1) {
445 QVariantList list;
446 Q_FOREACH (QUrl url, urls)
447 list << urlAsString(url);
448 args << QVariant(list);
449 } else {
450 args << urlAsString(urls[0]);
451 }
452 } else if (data->hasText()) {
453 args << data->text();
454 } else {
455 return false;
456 }
457
458 return true;
459 }
460
interpretDragEvent(QObject * o,QEvent * e,QList<QVariant> & args)461 bool QWidgetProxy::interpretDragEvent(QObject* o, QEvent* e, QList<QVariant>& args) {
462 if (o != _mouseEventWidget)
463 return false;
464
465 QDropEvent* dnd = static_cast<QDropEvent*>(e);
466
467 const QMimeData* data = dnd->mimeData();
468
469 if (dnd->type() == QEvent::DragEnter) {
470 bool internal = data->hasFormat("application/supercollider");
471 args << internal;
472 if (!internal)
473 interpretMimeData(data, args);
474 } else {
475 QPoint pos = dnd->pos();
476 args << pos.x() << pos.y();
477 }
478
479 return true;
480 }
481
postProcessEvent(QObject * object,QEvent * event,bool handled)482 bool QWidgetProxy::postProcessEvent(QObject* object, QEvent* event, bool handled) {
483 if (_performDrag) {
484 _performDrag = false;
485 performDrag();
486 return true;
487 }
488
489 return handled;
490 }
491
customPaint(QPainter * painter)492 void QWidgetProxy::customPaint(QPainter* painter) {
493 if (QtCollider::paintingAnnounced()) {
494 qcDebugMsg(1, "WARNING: Custom painting already in progress. Will not paint.");
495 return;
496 }
497
498 QtCollider::announcePainting();
499
500 QtCollider::lockLang();
501
502 if (QtCollider::beginPainting(painter, this)) {
503 invokeScMethod(SC_SYM(doDrawFunc), QList<QVariant>(), 0, true);
504 QtCollider::endPainting();
505 }
506
507 QtCollider::unlockLang();
508 }
509
sendRefreshEventRecursive(QWidget * w)510 void QWidgetProxy::sendRefreshEventRecursive(QWidget* w) {
511 QEvent event(static_cast<QEvent::Type>(QtCollider::Event_Refresh));
512 QApplication::sendEvent(w, &event);
513
514 const QObjectList& children = w->children();
515 Q_FOREACH (QObject* child, children) {
516 if (child->isWidgetType())
517 sendRefreshEventRecursive(static_cast<QWidget*>(child));
518 }
519 }
520