1 /************************************************************************
2  *
3  * Copyright 2010 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 "QcApplication.h"
23 #include "widgets/QcTreeWidget.h"
24 
25 #include <PyrLexer.h>
26 #include <VMGlobals.h>
27 #include <PyrKernel.h>
28 #include <PyrSlot.h>
29 #include <GC.h>
30 #include "SC_Filesystem.hpp"
31 
32 #include <QThread>
33 #include <QFileOpenEvent>
34 #include <QKeyEvent>
35 #include <QIcon>
36 #include <QMenuBar>
37 #include <QSharedPointer>
38 
39 #include "hacks/hacks_mac.hpp"
40 
41 #ifdef Q_OS_MAC
42 #    include "../../common/SC_Apple.hpp"
43 #endif
44 
45 extern bool compiledOK;
46 
47 QcApplication* QcApplication::_instance = 0;
48 QMutex QcApplication::_mutex;
49 
50 #ifdef Q_WS_X11
51 #    include <X11/Xlib.h>
52 #endif
53 
54 
55 /* on x11, we need to check, if we can actually connect to the X server */
QtColliderUseGui(void)56 static bool QtColliderUseGui(void) {
57 #ifdef Q_WS_X11
58     Display* dpy = XOpenDisplay(NULL);
59     if (!dpy)
60         return false;
61     XCloseDisplay(dpy);
62     return true;
63 #else
64     return true;
65 #endif
66 }
67 
68 // undefine some interfering X11 definitions
69 #undef KeyPress
70 
71 bool QcApplication::_systemHasMouseWheel = false;
72 
QcApplication(int & argc,char ** argv)73 QcApplication::QcApplication(int& argc, char** argv): QApplication(argc, argv, QtColliderUseGui()) {
74     _mutex.lock();
75     _instance = this;
76     _mutex.unlock();
77     this->setAttribute(Qt::AA_UseHighDpiPixmaps);
78 
79 #ifdef Q_OS_MAC
80     QtCollider::Mac::DisableAutomaticWindowTabbing();
81 #endif
82 
83     if (QtColliderUseGui()) { // avoid a crash on linux, if x is not available
84         QIcon icon;
85         icon.addFile(":/icons/sc-cube-128");
86         icon.addFile(":/icons/sc-cube-48");
87         icon.addFile(":/icons/sc-cube-32");
88         icon.addFile(":/icons/sc-cube-16");
89         setWindowIcon(icon);
90         createMenu();
91     }
92 
93 #ifdef Q_OS_MAC
94     // On Mac, we may need to disable "App Nap", so we aren't put to sleep unexpectedly
95     SC::Apple::disableAppNap();
96 #endif
97 
98     _handleCmdPeriod = SC_Filesystem::instance().getIdeName() != "scapp";
99 }
100 
~QcApplication()101 QcApplication::~QcApplication() {
102     _mutex.lock();
103     _instance = 0;
104     _mutex.unlock();
105 }
106 
createMenu()107 void QcApplication::createMenu() {
108     _mainMenu = QSharedPointer<QMenuBar>::create();
109 
110 #ifdef Q_OS_MAC
111     // macOS registers cmd+q on menu bars by default. Here we register a handler for cmd+q that does nothing
112     // and we disable the Quit menu by default
113     auto* action = new QAction("");
114     action->setMenuRole(QAction::QuitRole);
115     action->setEnabled(false);
116     QObject::connect(action, SIGNAL(triggered()), this, SLOT(onQuit()));
117     auto* menu = new QMenu(tr("&File"));
118     menu->addAction(action);
119     _mainMenu->addMenu(menu);
120 #endif
121 }
122 
onQuit()123 void QcApplication::onQuit() {
124     qWarning("[QcApplication::onQuit] CMD+Q was caught by the interpreter. "
125              "This is weird, it should not happen. "
126              "Please file an issue at https://github.com/supercollider/supercollider/issues");
127 }
128 
compareThread()129 bool QcApplication::compareThread() { return gMainVMGlobals->canCallOS; }
130 
interpret(const QString & str,bool print)131 void QcApplication::interpret(const QString& str, bool print) {
132     QtCollider::lockLang();
133     if (compiledOK) {
134         VMGlobals* g = gMainVMGlobals;
135 
136         PyrString* strObj = newPyrString(g->gc, str.toStdString().c_str(), 0, true);
137 
138         SetObject(&slotRawInterpreter(&g->process->interpreter)->cmdLine, strObj);
139         g->gc->GCWriteNew(slotRawObject(&g->process->interpreter),
140                           strObj); // we know strObj is white so we can use GCWriteNew
141 
142         runLibrary(print ? SC_SYM(interpretPrintCmdLine) : SC_SYM(interpretCmdLine));
143     }
144     QtCollider::unlockLang();
145 }
146 
event(QEvent * event)147 bool QcApplication::event(QEvent* event) {
148     switch (event->type()) {
149     case QEvent::FileOpen: {
150         // open the file dragged onto the application icon on Mac
151         QFileOpenEvent* fe = static_cast<QFileOpenEvent*>(event);
152         interpret(QStringLiteral("Document.open(\"%1\")").arg(fe->file()), false);
153         event->accept();
154         return true;
155     }
156     default:
157         break;
158     }
159 
160     return QApplication::event(event);
161 }
162 
notify(QObject * object,QEvent * event)163 bool QcApplication::notify(QObject* object, QEvent* event) {
164     switch (event->type()) {
165     case QEvent::KeyPress: {
166         QKeyEvent* kevent = static_cast<QKeyEvent*>(event);
167         if (_handleCmdPeriod && (kevent->key() == Qt::Key_Period) && (kevent->modifiers() & Qt::ControlModifier)) {
168             static QString cmdPeriodCommand("CmdPeriod.run");
169             interpret(cmdPeriodCommand, false);
170         }
171         break;
172     }
173     case QEvent::Wheel: {
174         _systemHasMouseWheel = true;
175         break;
176     }
177     default:
178         break;
179     }
180 
181     bool result = QApplication::notify(object, event);
182 
183 #ifdef Q_OS_MAC
184     // XXX Explicitly accept all handled events so they don't propagate outside the application.
185     // This is a hack; for a not-fully-understood reason Qt past 5.7 sends these events to the
186     // native window if they aren't accepted here. This caused issue #4058. Accepting them here
187     // seems to solve the problem, but might cause other issues since it is a heavy-handed way
188     // of doing this. TODO - solve more elegantly
189     if (result)
190         event->accept();
191 #endif
192     return result;
193 }
194