1 /************************************************************************
2  *
3  * Copyright 2010-2012 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 "primitives.h"
23 
24 #include "../Common.h"
25 #include "../type_codec.hpp"
26 #include "../QcApplication.h"
27 #include "../QObjectProxy.h"
28 #include "../style/style.hpp"
29 #include "../QcCallback.hpp"
30 #include "QtCollider.h"
31 
32 #ifdef Q_OS_MAC
33 #    include "../hacks/hacks_mac.hpp"
34 #endif
35 
36 #include <PyrKernel.h>
37 
38 #include <QDesktopServices>
39 #include <QFontDatabase>
40 #include <QFontInfo>
41 #include <QFontMetrics>
42 #include <QDesktopWidget>
43 #include <QStyleFactory>
44 #include <QCursor>
45 #include <QScreen>
46 
47 namespace QtCollider {
48 
49 QC_LANG_PRIMITIVE(QtGUI_SetDebugLevel, 1, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
50     QtCollider::setDebugLevel(QtCollider::get(a));
51     return errNone;
52 }
53 
54 QC_LANG_PRIMITIVE(QtGUI_DebugLevel, 0, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
55     SetInt(r, QtCollider::debugLevel());
56     return errNone;
57 }
58 
59 QC_LANG_PRIMITIVE(QWindow_ScreenBounds, 0, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
60     if (!QcApplication::compareThread())
61         return QtCollider::wrongThreadError();
62 
63     QRect screenGeometry = QApplication::primaryScreen()->geometry();
64     QtCollider::set(r, screenGeometry);
65     return errNone;
66 }
67 
68 QC_LANG_PRIMITIVE(QWindow_AvailableGeometry, 0, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
69     if (!QcApplication::compareThread())
70         return QtCollider::wrongThreadError();
71 
72     QRect rect = QApplication::primaryScreen()->availableGeometry();
73     QtCollider::set(r, rect);
74     return errNone;
75 }
76 
77 QC_LANG_PRIMITIVE(Qt_StringBounds, 2, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
78     QString str = QtCollider::get(a);
79 
80     QFont f = QtCollider::get(a + 1);
81 
82     QFontMetrics fm(f);
83     QRect bounds = fm.boundingRect(str);
84 
85     // we keep the font height even on empty string;
86     if (str.isEmpty())
87         bounds.setHeight(fm.height());
88 
89     QtCollider::set(r, bounds);
90     return errNone;
91 }
92 
93 QC_LANG_PRIMITIVE(Qt_AvailableFonts, 0, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
94     QFontDatabase database;
95     QVariantList list;
96     Q_FOREACH (QString family, database.families())
97         list << family;
98     QtCollider::set(r, list);
99     return errNone;
100 }
101 
102 QC_LANG_PRIMITIVE(QFont_SetDefaultFont, 2, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
103     if (!QcApplication::compareThread())
104         return QtCollider::wrongThreadError();
105 
106     if (!isKindOfSlot(a + 0, SC_CLASS(Font)))
107         return errWrongType;
108 
109     QFont font(QtCollider::read<QFont>(a + 0));
110     const char* className = IsSym(a + 1) ? slotRawSymbol(a + 1)->name : 0;
111 
112     QApplication::setFont(font, className);
113 
114     return errNone;
115 }
116 
117 QC_LANG_PRIMITIVE(QFont_DefaultFamilyForStyle, 1, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
118     // NOTE:
119     // On X11 systems we rely on default fontconfig mappings of font familiy names,
120     // as style hints are not necessarily supported.
121     // On other systems, style hints should work.
122 
123     if (!QcApplication::compareThread())
124         return QtCollider::wrongThreadError();
125 
126     if (!IsInt(a))
127         return errWrongType;
128 
129     QFont::StyleHint styleHint;
130     QString family;
131     switch (slotRawInt(a)) {
132     case 0:
133         styleHint = QFont::SansSerif;
134         family = "sans-serif";
135         break;
136     case 1:
137         styleHint = QFont::Serif;
138         family = "serif";
139         break;
140     case 2:
141         styleHint = QFont::Monospace;
142         family = "monospace";
143         break;
144     default:
145         styleHint = QFont::AnyStyle;
146     }
147 
148     QFont font(family);
149     font.setStyleHint(styleHint, QFont::PreferMatch);
150 
151     QtCollider::set(r, font.defaultFamily());
152 
153     return errNone;
154 }
155 
156 QC_LANG_PRIMITIVE(Qt_GlobalPalette, 0, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
157     if (!QcApplication::compareThread())
158         return QtCollider::wrongThreadError();
159 
160     QPalette p(QApplication::palette());
161     QtCollider::set(r, p);
162     return errNone;
163 }
164 
165 QC_LANG_PRIMITIVE(Qt_SetGlobalPalette, 1, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
166     if (!QcApplication::compareThread())
167         return QtCollider::wrongThreadError();
168 
169 // The line below is a workaround. The non-win term causes Error C2440 in VS
170 // https://msdn.microsoft.com/en-us/library/sy5tsf8z.aspx
171 #if defined(_MSC_VER)
172     QPalette p = (QPalette &&) QtCollider::get(a);
173 #else
174     QPalette p = QtCollider::get(a);
175 #endif
176 
177     QApplication::setPalette(p);
178 
179     return errNone;
180 }
181 
182 QC_LANG_PRIMITIVE(Qt_FocusWidget, 0, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
183     if (!QcApplication::compareThread())
184         return QtCollider::wrongThreadError();
185 
186     QWidget* w = QApplication::focusWidget();
187 
188 #ifdef Q_OS_MAC
189     // On Mac we need to make additional checks, as Qt does not monitor
190     // focus changes to native Cocoa windows in the same application.
191     if (w && !QtCollider::Mac::isKeyWindow(w))
192         w = 0;
193 #endif
194 
195     if (w) {
196         QObjectProxy* proxy = QObjectProxy::fromObject(w);
197         if (proxy && proxy->scObject()) {
198             SetObject(r, proxy->scObject());
199             return errNone;
200         }
201     }
202 
203     SetNil(r);
204     return errNone;
205 }
206 
207 QC_LANG_PRIMITIVE(Qt_SetStyle, 1, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
208     if (!QcApplication::compareThread())
209         return QtCollider::wrongThreadError();
210 
211     QString str = QtCollider::get(a);
212     if (str.isEmpty())
213         return errFailed;
214 
215     QStyle* style = QStyleFactory::create(str);
216     if (!style)
217         return errFailed;
218 
219     QApplication::setStyle(new QtCollider::Style::StyleImpl(style));
220     return errNone;
221 }
222 
223 QC_LANG_PRIMITIVE(Qt_AvailableStyles, 0, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
224     if (!QcApplication::compareThread())
225         return QtCollider::wrongThreadError();
226 
227     QVariantList list;
228     Q_FOREACH (QString key, QStyleFactory::keys())
229         list << key;
230 
231     QtCollider::set(r, list);
232     return errNone;
233 }
234 
235 QC_LANG_PRIMITIVE(QWebView_ClearMemoryCaches, 0, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
236     if (!QcApplication::compareThread())
237         return QtCollider::wrongThreadError();
238 
239     // @TODO WebEngine: New cache method?
240     // QWebEngineSettings::clearMemoryCaches();
241 
242     return errNone;
243 }
244 
245 QC_LANG_PRIMITIVE(Qt_IsMethodOverridden, 2, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
246     if (NotObj(a) || NotSym(a + 1))
247         return errWrongType;
248 
249     PyrObject* self = slotRawObject(r);
250     PyrObjectHdr* superclass = slotRawObject(a);
251     PyrSymbol* method = slotRawSymbol(a + 1);
252 
253     for (PyrClass* klass = self->classptr; klass != superclass && klass != class_object;
254          klass = slotRawSymbol(&klass->superclass)->u.classobj) {
255         PyrSlot* methodSlot = &klass->methods;
256         if (!IsObj(methodSlot))
257             continue;
258         PyrObject* methodArray = slotRawObject(methodSlot);
259         PyrSlot* methods = methodArray->slots;
260         for (int i = 0; i < methodArray->size; ++i) {
261             PyrMethod* m = slotRawMethod(methods + i);
262             if (slotRawSymbol(&m->name) == method) {
263                 SetTrue(r);
264                 return errNone;
265             }
266         }
267     }
268 
269     SetFalse(r);
270     return errNone;
271 }
272 
273 QC_LANG_PRIMITIVE(Qt_CursorPosition, 0, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
274     if (!QcApplication::compareThread())
275         return QtCollider::wrongThreadError();
276 
277     QtCollider::set(r, QCursor::pos());
278 
279     return errNone;
280 }
281 
282 QC_LANG_PRIMITIVE(Qt_SetUrlHandler, 2, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
283     if (!QcApplication::compareThread())
284         return QtCollider::wrongThreadError();
285 
286     QString str = QtCollider::get(a);
287 
288     if (IsNil(a + 1)) {
289         QDesktopServices::unsetUrlHandler(str);
290     } else {
291         QcCallback* cb = QtCollider::get(a + 1);
292         QDesktopServices::setUrlHandler(str, cb, "onCalled");
293     }
294 
295     return errNone;
296 }
297 
298 QC_LANG_PRIMITIVE(Qt_SetAppMenus, 1, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
299     if (!QcApplication::compareThread())
300         return QtCollider::wrongThreadError();
301 
302     QList<QMenu*> menuList;
303 
304 
305     if (isKindOfSlot(a, class_array)) {
306         QMenuBar* menuBar = QcApplication::getMainMenu();
307         if (menuBar) {
308             menuBar->clear();
309 
310             PyrObject* obj = slotRawObject(a);
311             PyrSlot* slots = obj->slots;
312             int size = obj->size;
313 
314             for (int i = 0; i < size; ++i, ++slots) {
315                 QMenu* menu = QtCollider::get<QMenu*>(slots);
316                 if (menu) {
317                     menuBar->addMenu(menu);
318                 } else {
319                     menuBar->addSeparator();
320                 }
321             }
322         }
323     }
324 
325     return errNone;
326 }
327 
328 QC_LANG_PRIMITIVE(QView_AddActionToView, 3, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
329     if (!QcApplication::compareThread())
330         return QtCollider::wrongThreadError();
331 
332     QObjectProxy* widgetObj = TypeCodec<QObjectProxy*>::safeRead(a);
333     QObjectProxy* actionObj = TypeCodec<QObjectProxy*>::safeRead(a + 1);
334     QObjectProxy* beforeObj = TypeCodec<QObjectProxy*>::safeRead(a + 2);
335 
336     if (widgetObj && actionObj) {
337         QWidget* widget = qobject_cast<QWidget*>(widgetObj->object());
338         QAction* action = qobject_cast<QAction*>(actionObj->object());
339         QAction* beforeAction = beforeObj ? qobject_cast<QAction*>(beforeObj->object()) : 0;
340 
341         if (!beforeAction) {
342             PyrSlot* indexArg = a + 2;
343 
344             if (NotNil(indexArg)) {
345                 if (IsInt(indexArg)) {
346                     int index = std::max(slotRawInt(indexArg), 0);
347 
348                     auto actions = widget->actions();
349 
350                     if (index < actions.size()) {
351                         beforeAction = actions[index];
352                     }
353                 } else {
354                     return errFailed;
355                 }
356             }
357         }
358 
359         if (widget && action) {
360             if (beforeAction) {
361                 widget->insertAction(beforeAction, action);
362             } else {
363                 widget->addAction(action);
364             }
365             return errNone;
366         }
367     }
368 
369     return errFailed;
370 }
371 
372 QC_LANG_PRIMITIVE(QView_RemoveActionFromView, 2, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
373     if (!QcApplication::compareThread())
374         return QtCollider::wrongThreadError();
375 
376     QObjectProxy* widgetObj = TypeCodec<QObjectProxy*>::safeRead(a);
377     QObjectProxy* actionObj = TypeCodec<QObjectProxy*>::safeRead(a + 1);
378 
379     if (widgetObj && actionObj) {
380         QWidget* widget = qobject_cast<QWidget*>(widgetObj->object());
381         QAction* action = qobject_cast<QAction*>(actionObj->object());
382 
383         if (widget && action) {
384             widget->removeAction(action);
385             return errNone;
386         }
387     }
388 
389     return errFailed;
390 }
391 
392 QC_LANG_PRIMITIVE(QView_RemoveAllActionsFromView, 1, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
393     if (!QcApplication::compareThread())
394         return QtCollider::wrongThreadError();
395 
396     QObjectProxy* widgetObj = TypeCodec<QObjectProxy*>::safeRead(a);
397 
398     if (widgetObj) {
399         QWidget* widget = qobject_cast<QWidget*>(widgetObj->object());
400 
401         if (widget) {
402             auto actions = widget->actions();
403             for (auto action : actions) {
404                 widget->removeAction(action);
405             }
406 
407             return errNone;
408         }
409     }
410 
411     return errFailed;
412 }
413 
414 
defineMiscPrimitives()415 void defineMiscPrimitives() {
416     LangPrimitiveDefiner definer;
417     definer.define<QtGUI_SetDebugLevel>();
418     definer.define<QtGUI_DebugLevel>();
419     definer.define<QWindow_ScreenBounds>();
420     definer.define<QWindow_AvailableGeometry>();
421     definer.define<Qt_StringBounds>();
422     definer.define<Qt_AvailableFonts>();
423     definer.define<QFont_SetDefaultFont>();
424     definer.define<QFont_DefaultFamilyForStyle>();
425     definer.define<Qt_GlobalPalette>();
426     definer.define<Qt_SetGlobalPalette>();
427     definer.define<Qt_FocusWidget>();
428     definer.define<Qt_SetStyle>();
429     definer.define<Qt_AvailableStyles>();
430     definer.define<Qt_IsMethodOverridden>();
431     definer.define<QWebView_ClearMemoryCaches>();
432     definer.define<Qt_CursorPosition>();
433     definer.define<Qt_SetUrlHandler>();
434     definer.define<Qt_SetAppMenus>();
435     definer.define<QView_AddActionToView>();
436     definer.define<QView_RemoveActionFromView>();
437     definer.define<QView_RemoveAllActionsFromView>();
438 }
439 
440 } // namespace QtCollider
441