// // Application_Events.cpp // interface/src // // Split from Application.cpp by HifiExperiments on 3/30/24 // Created by Andrzej Kapolka on 5/10/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. // Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // #include "Application.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AudioClient.h" #include "GLCanvas.h" #include "Menu.h" #if defined(Q_OS_ANDROID) #include "AndroidHelper.h" #endif Q_LOGGING_CATEGORY(trace_app_input_mouse, "trace.app.input.mouse") static const unsigned int THROTTLED_SIM_FRAMERATE = 15; static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE; class LambdaEvent : public QEvent { std::function _fun; public: LambdaEvent(const std::function & fun) : QEvent(static_cast(ApplicationEvent::Lambda)), _fun(fun) {} LambdaEvent(std::function && fun) : QEvent(static_cast(ApplicationEvent::Lambda)), _fun(fun) {} void call() const { _fun(); } }; bool Application::notify(QObject* object, QEvent* event) { if (thread() == QThread::currentThread()) { PROFILE_RANGE_IF_LONGER(app, "notify", 2) return QApplication::notify(object, event); } return QApplication::notify(object, event); } static inline bool isKeyEvent(QEvent::Type type) { return type == QEvent::KeyPress || type == QEvent::KeyRelease; } bool Application::event(QEvent* event) { if (_aboutToQuit) { return false; } // This helps avoid deadlock issue early during Application initialization if (!_isMenuInitialized) { return QApplication::event(event); } if (!Menu::getInstance()) { return false; } if ((event->type() == QEvent::InputMethod || event->type() == QEvent::InputMethodQuery) && handleInputMethodEventForFocusedEntity(event)) { return true; } // Allow focused Entities to handle keyboard input if (isKeyEvent(event->type()) && handleKeyEventForFocusedEntity(event)) { return true; } int type = event->type(); switch (type) { case ApplicationEvent::Lambda: static_cast(event)->call(); return true; // Explicit idle keeps the idle running at a lower interval, but without any rendering // see (windowMinimizedChanged) case ApplicationEvent::Idle: idle(); #ifdef DEBUG_EVENT_QUEUE_DEPTH // The event queue may very well grow beyond 400, so // this code should only be enabled on local builds { int count = ::hifi::qt::getEventQueueSize(QThread::currentThread()); if (count > 400) { ::hifi::qt::dumpEventQueue(QThread::currentThread()); } } #endif // DEBUG_EVENT_QUEUE_DEPTH _pendingIdleEvent.store(false); return true; case QEvent::MouseMove: mouseMoveEvent(static_cast(event)); return true; case QEvent::MouseButtonPress: mousePressEvent(static_cast(event)); return true; case QEvent::MouseButtonDblClick: mouseDoublePressEvent(static_cast(event)); return true; case QEvent::MouseButtonRelease: mouseReleaseEvent(static_cast(event)); return true; case QEvent::KeyPress: keyPressEvent(static_cast(event)); return true; case QEvent::KeyRelease: keyReleaseEvent(static_cast(event)); return true; case QEvent::FocusOut: focusOutEvent(static_cast(event)); return true; case QEvent::FocusIn: { //testing to see if we can set focus when focus is not set to root window. _glWidget->activateWindow(); _glWidget->setFocus(); return true; } case QEvent::TouchBegin: touchBeginEvent(static_cast(event)); event->accept(); return true; case QEvent::TouchEnd: touchEndEvent(static_cast(event)); return true; case QEvent::TouchUpdate: touchUpdateEvent(static_cast(event)); return true; case QEvent::Gesture: touchGestureEvent((QGestureEvent*)event); return true; case QEvent::Wheel: wheelEvent(static_cast(event)); return true; case QEvent::Drop: dropEvent(static_cast(event)); return true; case QEvent::FileOpen: if (handleFileOpenEvent(static_cast(event))) { return true; } break; default: break; } return QApplication::event(event); } bool Application::eventFilter(QObject* object, QEvent* event) { auto eventType = event->type(); if (_aboutToQuit && eventType != QEvent::DeferredDelete && eventType != QEvent::Destroy) { return true; } #if defined(Q_OS_MAC) // On Mac OS, Cmd+LeftClick is treated as a RightClick (more specifically, it seems to // be Cmd+RightClick without the modifier being dropped). Starting in Qt 5.12, only // on Mac, the MouseButtonRelease event for these mouse presses is sent to the top // level QWidgetWindow, but are not propagated further. This means that the Application // will see a MouseButtonPress, but no MouseButtonRelease, causing the client to get // stuck in "mouse-look." The cause of the problem is in the way QWidgetWindow processes // events where QMouseEvent::button() is not equal to QMouseEvent::buttons(). In this case // QMouseEvent::button() is Qt::RightButton, while QMouseEvent::buttons() is (correctly?) // Qt::LeftButton. // // The change here gets around this problem by capturing these // pseudo-RightClicks, and re-emitting them as "pure" RightClicks, where // QMouseEvent::button() == QMouseEvent::buttons() == Qt::RightButton. // if (eventType == QEvent::MouseButtonPress) { auto mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::RightButton && mouseEvent->buttons() == Qt::LeftButton && mouseEvent->modifiers() == Qt::MetaModifier) { QMouseEvent* newEvent = new QMouseEvent( QEvent::MouseButtonPress, mouseEvent->localPos(), mouseEvent->windowPos(), mouseEvent->screenPos(), Qt::RightButton, Qt::MouseButtons(Qt::RightButton), mouseEvent->modifiers()); QApplication::postEvent(object, newEvent); return true; } } #endif if (eventType == QEvent::KeyPress || eventType == QEvent::KeyRelease || eventType == QEvent::MouseMove) { getRefreshRateManager().resetInactiveTimer(); } if (event->type() == QEvent::Leave) { getApplicationCompositor().handleLeaveEvent(); } if (event->type() == QEvent::ShortcutOverride) { #if !defined(DISABLE_QML) if (getOffscreenUI()->shouldSwallowShortcut(event)) { event->accept(); return true; } #endif // Filter out captured keys before they're used for shortcut actions. if (_controllerScriptingInterface->isKeyCaptured(static_cast(event))) { event->accept(); return true; } } if (event->type() == QEvent::WindowStateChange) { if (getWindow()->windowState() & Qt::WindowMinimized) { getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::MINIMIZED); } else { auto* windowStateChangeEvent = static_cast(event); if (windowStateChangeEvent->oldState() & Qt::WindowMinimized) { getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE); getRefreshRateManager().resetInactiveTimer(); } } } return false; } void Application::postLambdaEvent(const std::function& f) { if (this->thread() == QThread::currentThread()) { f(); } else { QCoreApplication::postEvent(this, new LambdaEvent(f)); } } void Application::sendLambdaEvent(const std::function& f) { if (this->thread() == QThread::currentThread()) { f(); } else { LambdaEvent event(f); QCoreApplication::sendEvent(this, &event); } } void Application::pushPostUpdateLambda(void* key, const std::function& func) { std::unique_lock guard(_postUpdateLambdasLock); _postUpdateLambdas[key] = func; } // thread-safe void Application::onPresent(quint32 frameCount) { bool expected = false; if (_pendingIdleEvent.compare_exchange_strong(expected, true)) { postEvent(this, new QEvent((QEvent::Type)ApplicationEvent::Idle), Qt::HighEventPriority); } expected = false; if (_graphicsEngine->checkPendingRenderEvent() && !isAboutToQuit()) { postEvent(_graphicsEngine->_renderEventHandler, new QEvent((QEvent::Type)ApplicationEvent::Render)); } } void Application::activeChanged(Qt::ApplicationState state) { switch (state) { case Qt::ApplicationActive: _isForeground = true; if (!_aboutToQuit && _startUpFinished) { getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE); } break; case Qt::ApplicationSuspended: break; case Qt::ApplicationHidden: break; case Qt::ApplicationInactive: if (!_aboutToQuit && _startUpFinished) { getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::UNFOCUS); } break; default: _isForeground = false; break; } } void Application::windowMinimizedChanged(bool minimized) { // initialize the _minimizedWindowTimer static std::once_flag once; std::call_once(once, [&] { connect(&_minimizedWindowTimer, &QTimer::timeout, this, [] { QCoreApplication::postEvent(QCoreApplication::instance(), new QEvent(static_cast(Idle)), Qt::HighEventPriority); }); }); // avoid rendering to the display plugin but continue posting Idle events, // so that physics continues to simulate and the deadlock watchdog knows we're alive if (!minimized && !getActiveDisplayPlugin()->isActive()) { _minimizedWindowTimer.stop(); getActiveDisplayPlugin()->activate(); } else if (minimized && getActiveDisplayPlugin()->isActive()) { getActiveDisplayPlugin()->deactivate(); _minimizedWindowTimer.start(THROTTLED_SIM_FRAME_PERIOD_MS); } } void Application::keyPressEvent(QKeyEvent* event) { if (!event->isAutoRepeat()) { _keysPressed.insert(event->key(), *event); } _controllerScriptingInterface->emitKeyPressEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isKeyCaptured(event) || isInterstitialMode()) { return; } if (hasFocus() && getLoginDialogPoppedUp()) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->keyReleaseEvent(event); } bool isControlOrCommand = event->modifiers().testFlag(Qt::ControlModifier); bool isOption = event->modifiers().testFlag(Qt::AltModifier); switch (event->key()) { case Qt::Key_4: case Qt::Key_5: case Qt::Key_6: case Qt::Key_7: if (isControlOrCommand || isOption) { unsigned int index = static_cast(event->key() - Qt::Key_1); const auto& displayPlugins = PluginManager::getInstance()->getDisplayPlugins(); if (index < displayPlugins.size()) { auto targetPlugin = displayPlugins.at(index); QString targetName = targetPlugin->getName(); auto menu = Menu::getInstance(); QAction* action = menu->getActionForOption(targetName); if (action && !action->isChecked()) { action->trigger(); } } } break; } } else if (hasFocus()) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->keyPressEvent(event); } bool isShifted = event->modifiers().testFlag(Qt::ShiftModifier); bool isControlOrCommand = event->modifiers().testFlag(Qt::ControlModifier); bool isMetaOrMacControl = event->modifiers().testFlag(Qt::MetaModifier); bool isOption = event->modifiers().testFlag(Qt::AltModifier); switch (event->key()) { case Qt::Key_Enter: case Qt::Key_Return: if (isOption) { if (_window->isFullScreen()) { unsetFullscreen(); } else { setFullscreen(nullptr); } } break; case Qt::Key_1: { Menu* menu = Menu::getInstance(); menu->triggerOption(MenuOption::FirstPersonLookAt); break; } case Qt::Key_2: { Menu* menu = Menu::getInstance(); menu->triggerOption(MenuOption::SelfieCamera); break; } case Qt::Key_3: { Menu* menu = Menu::getInstance(); menu->triggerOption(MenuOption::LookAtCamera); break; } case Qt::Key_4: case Qt::Key_5: case Qt::Key_6: case Qt::Key_7: if (isControlOrCommand || isOption) { unsigned int index = static_cast(event->key() - Qt::Key_1); const auto& displayPlugins = PluginManager::getInstance()->getDisplayPlugins(); if (index < displayPlugins.size()) { auto targetPlugin = displayPlugins.at(index); QString targetName = targetPlugin->getName(); auto menu = Menu::getInstance(); QAction* action = menu->getActionForOption(targetName); if (action && !action->isChecked()) { action->trigger(); } } } break; case Qt::Key_G: if (isShifted && isControlOrCommand && isOption && isMetaOrMacControl) { static const QString HIFI_FRAMES_FOLDER_VAR = "HIFI_FRAMES_FOLDER"; static const QString GPU_FRAME_FOLDER = QProcessEnvironment::systemEnvironment().contains(HIFI_FRAMES_FOLDER_VAR) ? QProcessEnvironment::systemEnvironment().value(HIFI_FRAMES_FOLDER_VAR) : "hifiFrames"; static QString GPU_FRAME_TEMPLATE = GPU_FRAME_FOLDER + "/{DATE}_{TIME}"; QString fullPath = FileUtils::computeDocumentPath(FileUtils::replaceDateTimeTokens(GPU_FRAME_TEMPLATE)); if (FileUtils::canCreateFile(fullPath)) { getActiveDisplayPlugin()->captureFrame(fullPath.toStdString()); } } break; case Qt::Key_X: if (isShifted && isControlOrCommand) { auto offscreenUi = getOffscreenUI(); offscreenUi->togglePinned(); //offscreenUi->getSurfaceContext()->engine()->clearComponentCache(); //OffscreenUi::information("Debugging", "Component cache cleared"); // placeholder for dialogs being converted to QML. } break; case Qt::Key_Y: if (isShifted && isControlOrCommand) { getActiveDisplayPlugin()->cycleDebugOutput(); } break; case Qt::Key_B: if (isOption) { controller::InputRecorder* inputRecorder = controller::InputRecorder::getInstance(); inputRecorder->stopPlayback(); } break; case Qt::Key_L: if (isShifted && isControlOrCommand) { Menu::getInstance()->triggerOption(MenuOption::Log); } else if (isControlOrCommand) { auto dialogsManager = DependencyManager::get(); dialogsManager->toggleAddressBar(); } break; case Qt::Key_R: if (isControlOrCommand && !event->isAutoRepeat()) { DependencyManager::get()->reloadAllScripts(); getOffscreenUI()->clearCache(); } break; case Qt::Key_Asterisk: Menu::getInstance()->triggerOption(MenuOption::DefaultSkybox); break; case Qt::Key_M: if (isControlOrCommand) { auto audioClient = DependencyManager::get(); audioClient->setMuted(!audioClient->isMuted()); QSharedPointer audioScriptingInterface = qSharedPointerDynamicCast(DependencyManager::get()); if (audioScriptingInterface && audioScriptingInterface->getPTT()) { audioScriptingInterface->setPushingToTalk(!audioClient->isMuted()); } } break; case Qt::Key_S: if (isShifted && isControlOrCommand && !isOption) { Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings); } break; case Qt::Key_Apostrophe: { if (isControlOrCommand) { auto cursor = Cursor::Manager::instance().getCursor(); auto curIcon = cursor->getIcon(); if (curIcon == Cursor::Icon::DEFAULT) { showCursor(Cursor::Icon::RETICLE); } else if (curIcon == Cursor::Icon::RETICLE) { showCursor(Cursor::Icon::SYSTEM); } else if (curIcon == Cursor::Icon::SYSTEM) { showCursor(Cursor::Icon::LINK); } else { showCursor(Cursor::Icon::DEFAULT); } } else if (!event->isAutoRepeat()){ resetSensors(true); } break; } case Qt::Key_Backslash: Menu::getInstance()->triggerOption(MenuOption::Chat); break; case Qt::Key_Slash: Menu::getInstance()->triggerOption(MenuOption::Stats); break; case Qt::Key_Plus: { if (isControlOrCommand && event->modifiers().testFlag(Qt::KeypadModifier)) { auto& cursorManager = Cursor::Manager::instance(); cursorManager.setScale(cursorManager.getScale() * 1.1f); } else { getMyAvatar()->increaseSize(); } break; } case Qt::Key_Minus: { if (isControlOrCommand && event->modifiers().testFlag(Qt::KeypadModifier)) { auto& cursorManager = Cursor::Manager::instance(); cursorManager.setScale(cursorManager.getScale() / 1.1f); } else { getMyAvatar()->decreaseSize(); } break; } case Qt::Key_Equal: getMyAvatar()->resetSize(); break; case Qt::Key_Escape: { getActiveDisplayPlugin()->abandonCalibration(); break; } default: event->ignore(); break; } } } void Application::keyReleaseEvent(QKeyEvent* event) { if (!event->isAutoRepeat()) { _keysPressed.remove(event->key()); } #if defined(Q_OS_ANDROID) if (event->key() == Qt::Key_Back) { event->accept(); AndroidHelper::instance().requestActivity("Home", false); } #endif _controllerScriptingInterface->emitKeyReleaseEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isKeyCaptured(event)) { return; } if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->keyReleaseEvent(event); } } void Application::focusOutEvent(QFocusEvent* event) { const auto& inputPlugins = PluginManager::getInstance()->getInputPlugins(); for(const auto& inputPlugin : inputPlugins) { if (inputPlugin->isActive()) { inputPlugin->pluginFocusOutEvent(); } } // FIXME spacemouse code still needs cleanup #if 0 //SpacemouseDevice::getInstance().focusOutEvent(); //SpacemouseManager::getInstance().getDevice()->focusOutEvent(); SpacemouseManager::getInstance().ManagerFocusOutEvent(); #endif synthesizeKeyReleasEvents(); } void Application::synthesizeKeyReleasEvents() { // synthesize events for keys currently pressed, since we may not get their release events // Because our key event handlers may manipulate _keysPressed, lets swap the keys pressed into a local copy, // clearing the existing list. QHash keysPressed; std::swap(keysPressed, _keysPressed); for (auto& ev : keysPressed) { QKeyEvent synthesizedEvent { QKeyEvent::KeyRelease, ev.key(), Qt::NoModifier, ev.text() }; keyReleaseEvent(&synthesizedEvent); } } void Application::mouseMoveEvent(QMouseEvent* event) { PROFILE_RANGE(app_input_mouse, __FUNCTION__); if (_ignoreMouseMove) { _ignoreMouseMove = false; return; } maybeToggleMenuVisible(event); auto& compositor = getApplicationCompositor(); // if this is a real mouse event, and we're in HMD mode, then we should use it to move the // compositor reticle // handleRealMouseMoveEvent() will return true, if we shouldn't process the event further if (!compositor.fakeEventActive() && compositor.handleRealMouseMoveEvent()) { return; // bail } #if !defined(DISABLE_QML) auto offscreenUi = getOffscreenUI(); auto eventPosition = compositor.getMouseEventPosition(event); QPointF transformedPos = offscreenUi ? offscreenUi->mapToVirtualScreen(eventPosition) : QPointF(); #else QPointF transformedPos; #endif auto button = event->button(); auto buttons = event->buttons(); // Determine if the ReticleClick Action is 1 and if so, fake include the LeftMouseButton if (_reticleClickPressed) { if (button == Qt::NoButton) { button = Qt::LeftButton; } buttons |= Qt::LeftButton; } QMouseEvent mappedEvent(event->type(), transformedPos, event->screenPos(), button, buttons, event->modifiers()); if (compositor.getReticleVisible() || !isHMDMode() || !compositor.getReticleOverDesktop() || getOverlays().getOverlayAtPoint(glm::vec2(transformedPos.x(), transformedPos.y())) != UNKNOWN_ENTITY_ID) { getEntities()->mouseMoveEvent(&mappedEvent); } _controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isMouseCaptured()) { return; } if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->mouseMoveEvent(event, _captureMouse, _mouseCaptureTarget); } } void Application::mousePressEvent(QMouseEvent* event) { #if !defined(DISABLE_QML) auto offscreenUi = getOffscreenUI(); // If we get a mouse press event it means it wasn't consumed by the offscreen UI, // hence, we should defocus all of the offscreen UI windows, in order to allow // keyboard shortcuts not to be swallowed by them. In particular, WebEngineViews // will consume all keyboard events. offscreenUi->unfocusWindows(); auto eventPosition = getApplicationCompositor().getMouseEventPosition(event); QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition); #else QPointF transformedPos; #endif QMouseEvent mappedEvent(event->type(), transformedPos, event->screenPos(), event->button(), event->buttons(), event->modifiers()); QUuid result = getEntities()->mousePressEvent(&mappedEvent); setKeyboardFocusEntity(getEntities()->wantsKeyboardFocus(result) ? result : UNKNOWN_ENTITY_ID); _controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isMouseCaptured()) { return; } #if defined(Q_OS_MAC) // Fix for OSX right click dragging on window when coming from a native window bool isFocussed = hasFocus(); if (!isFocussed && event->button() == Qt::MouseButton::RightButton) { setFocus(); isFocussed = true; } if (isFocussed) { #else if (hasFocus()) { #endif if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->mousePressEvent(event); } } } void Application::mouseDoublePressEvent(QMouseEvent* event) { #if !defined(DISABLE_QML) auto offscreenUi = getOffscreenUI(); auto eventPosition = getApplicationCompositor().getMouseEventPosition(event); QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition); #else QPointF transformedPos; #endif QMouseEvent mappedEvent(event->type(), transformedPos, event->screenPos(), event->button(), event->buttons(), event->modifiers()); getEntities()->mouseDoublePressEvent(&mappedEvent); // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isMouseCaptured()) { return; } _controllerScriptingInterface->emitMouseDoublePressEvent(event); } void Application::mouseReleaseEvent(QMouseEvent* event) { #if !defined(DISABLE_QML) auto offscreenUi = getOffscreenUI(); auto eventPosition = getApplicationCompositor().getMouseEventPosition(event); QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition); #else QPointF transformedPos; #endif QMouseEvent mappedEvent(event->type(), transformedPos, event->screenPos(), event->button(), event->buttons(), event->modifiers()); getEntities()->mouseReleaseEvent(&mappedEvent); _controllerScriptingInterface->emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isMouseCaptured()) { return; } if (hasFocus()) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->mouseReleaseEvent(event); } } } void Application::touchBeginEvent(QTouchEvent* event) { TouchEvent thisEvent(*event); // on touch begin, we don't compare to last event _controllerScriptingInterface->emitTouchBeginEvent(thisEvent); // send events to any registered scripts _lastTouchEvent = thisEvent; // and we reset our last event to this event before we call our update touchUpdateEvent(event); // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isTouchCaptured()) { return; } if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->touchBeginEvent(event); } if (_touchscreenDevice && _touchscreenDevice->isActive()) { _touchscreenDevice->touchBeginEvent(event); } if (_touchscreenVirtualPadDevice && _touchscreenVirtualPadDevice->isActive()) { _touchscreenVirtualPadDevice->touchBeginEvent(event); } } void Application::touchEndEvent(QTouchEvent* event) { TouchEvent thisEvent(*event, _lastTouchEvent); _controllerScriptingInterface->emitTouchEndEvent(thisEvent); // send events to any registered scripts _lastTouchEvent = thisEvent; // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isTouchCaptured()) { return; } if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->touchEndEvent(event); } if (_touchscreenDevice && _touchscreenDevice->isActive()) { _touchscreenDevice->touchEndEvent(event); } if (_touchscreenVirtualPadDevice && _touchscreenVirtualPadDevice->isActive()) { _touchscreenVirtualPadDevice->touchEndEvent(event); } // put any application specific touch behavior below here.. } void Application::touchUpdateEvent(QTouchEvent* event) { if (event->type() == QEvent::TouchUpdate) { TouchEvent thisEvent(*event, _lastTouchEvent); _controllerScriptingInterface->emitTouchUpdateEvent(thisEvent); // send events to any registered scripts _lastTouchEvent = thisEvent; } // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isTouchCaptured()) { return; } if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->touchUpdateEvent(event); } if (_touchscreenDevice && _touchscreenDevice->isActive()) { _touchscreenDevice->touchUpdateEvent(event); } if (_touchscreenVirtualPadDevice && _touchscreenVirtualPadDevice->isActive()) { _touchscreenVirtualPadDevice->touchUpdateEvent(event); } } void Application::touchGestureEvent(QGestureEvent* event) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->touchGestureEvent(event); } if (_touchscreenDevice && _touchscreenDevice->isActive()) { _touchscreenDevice->touchGestureEvent(event); } if (_touchscreenVirtualPadDevice && _touchscreenVirtualPadDevice->isActive()) { _touchscreenVirtualPadDevice->touchGestureEvent(event); } } void Application::wheelEvent(QWheelEvent* event) const { _controllerScriptingInterface->emitWheelEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isWheelCaptured() || getLoginDialogPoppedUp()) { return; } if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->wheelEvent(event); } } void Application::dropEvent(QDropEvent *event) { const QMimeData* mimeData = event->mimeData(); for (auto& url : mimeData->urls()) { QString urlString = url.toString(); if (acceptURL(urlString, true)) { event->acceptProposedAction(); } } } bool Application::handleInputMethodEventForFocusedEntity(QEvent* event) { if (_keyboardFocusedEntity.get() != UNKNOWN_ENTITY_ID) { switch (event->type()) { case QEvent::InputMethod: case QEvent::InputMethodQuery: { auto eventHandler = getEntities()->getEventHandler(_keyboardFocusedEntity.get()); if (eventHandler) { event->setAccepted(false); QCoreApplication::sendEvent(eventHandler, event); if (event->isAccepted()) { _lastAcceptedKeyPress = usecTimestampNow(); return true; } } break; } default: break; } } return false; } bool Application::handleKeyEventForFocusedEntity(QEvent* event) { if (_keyboardFocusedEntity.get() != UNKNOWN_ENTITY_ID) { switch (event->type()) { case QEvent::KeyPress: case QEvent::KeyRelease: { auto eventHandler = getEntities()->getEventHandler(_keyboardFocusedEntity.get()); if (eventHandler) { event->setAccepted(false); QCoreApplication::sendEvent(eventHandler, event); if (event->isAccepted()) { _lastAcceptedKeyPress = usecTimestampNow(); return true; } } break; } default: break; } } return false; } bool Application::handleFileOpenEvent(QFileOpenEvent* fileEvent) { QUrl url = fileEvent->url(); if (!url.isEmpty()) { QString urlString = url.toString(); if (canAcceptURL(urlString)) { return acceptURL(urlString); } } return false; }