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