/** * ogrewidget.cpp * Copyright (c) 2013 ROBLOX Corp. All Rights Reserved. */ #include "stdafx.h" #include "ogrewidget.h" #include "rbx/TaskScheduler.h" // Qt Headers #include #include #include #include #include #include #include #include // Roblox Headers #include "FastLog.h" #include "util/Http.h" // Roblox Studio Headers #include "RobloxSettings.h" #include "RobloxCustomWidgets.h" #include "CommonInsertWidget.h" #include "RobloxMainWindow.h" #include "UpdateUIManager.h" #include "InsertObjectListWidgetItem.h" #include "RobloxDocManager.h" #include "RobloxIDEDoc.h" #include "V8DataModel/InputObject.h" #include "Util/RobloxGoogleAnalytics.h" #include "Util/PhysicalProperties.h" #include "v8datamodel/Workspace.h" #include "RobloxPluginHost.h" #include "StudioUtilities.h" LOGGROUP(TaskSchedulerTiming) LOGGROUP(RenderRequest) FASTFLAG(GoogleAnalyticsTrackingEnabled) DYNAMIC_FASTFLAGVARIABLE(BackTabInputInStudio, false) FASTFLAGVARIABLE(DontSwallowInputForStudioShortcuts, false) DYNAMIC_FASTFLAG(MaterialPropertiesEnabled) static RBX::KeyCode keyCodeTOUIKeyCode(int keyCode); static RBX::ModCode modifiersToUIModCode(int modifier); QOgreWidget::QOgreWidget(const QString& name,QWidget *parent) : QWidget(NULL) , m_pRobloxView(NULL) , m_bIgnoreEnterEvent(0) , m_bIgnoreLeaveEvent(false) , m_bUpdateInProgress(false) , m_bMouseCommandInvoked(false) , m_hasApplicationFocus(true) , m_bRobloxViewInitialized(false) , m_luaTextBoxHasFocus(false) { setObjectName(name); //set default states setAttribute(Qt::WA_PaintOnScreen, true); setAttribute(Qt::WA_OpaquePaintEvent, true); setAttribute(Qt::WA_NoSystemBackground, true); setFocusPolicy(Qt::StrongFocus); setAutoFillBackground(true); setAcceptDrops(true); setMinimumSize(100, 100); setMaximumSize(3800,2100); setSizePolicy(QSizePolicy::Maximum,QSizePolicy::Maximum); setContextMenuPolicy(Qt::PreventContextMenu); } void QOgreWidget::setRobloxView(RobloxView *rbxView) { m_pRobloxView = rbxView; if (!m_pRobloxView) return; QPoint mousePos = mapFromGlobal(QCursor::pos()); if (rect().contains(mousePos)) enterEvent(NULL); else leaveEvent(NULL); } void QOgreWidget::activate() { qApp->installEventFilter(this); // reset bounds to force a resize if parent or size changed // Roblox view can already be destroyed in resetting case if(m_pRobloxView) m_pRobloxView->setBounds(width(),height()); } void QOgreWidget::deActivate() { FASTLOG(FLog::RenderRequest, "QOgreWidget deactivated, OGRE_VIEW_UPDATE removed"); if (RBX::TaskScheduler::singleton().isCyclicExecutive()) { //NOTHING! } else { QCoreApplication::removePostedEvents(this, OGRE_VIEW_UPDATE); } qApp->removeEventFilter(this); } bool QOgreWidget::eventFilter(QObject * watched, QEvent * evt) { QEvent::Type eventType = evt->type(); if (eventType == QEvent::ApplicationActivate) m_hasApplicationFocus = true; else if (eventType == QEvent::ApplicationDeactivate) m_hasApplicationFocus = false; // whenever popup widgets are shown, QOgreWidget receives only enter and leave event but no mouse move event // so, do not handle enter/leave event in all such cases // e.g Color Picker Widget which comes up from the Properties Window if ((watched != this ) && watched->isWidgetType() && (static_cast(watched)->windowFlags() & Qt::Popup) == Qt::Popup) { // In case of Floating Dock Widget, do not show ArrowCursor when the mouse is over Ogre Window if(watched->inherits("QDockWidget")) return QWidget::eventFilter(watched, evt); if (eventType == QEvent::Show) { m_bIgnoreEnterEvent++; } else if (eventType == QEvent::Hide) { --m_bIgnoreEnterEvent; if (!m_bIgnoreEnterEvent) { QPoint mousePos = mapFromGlobal(QCursor::pos()); if (rect().contains(mousePos)) enterEvent(NULL); } } else if (eventType == QEvent::MouseButtonRelease) { // on close of a popup, mouse events are replayed (button doesn't matter) on the widget under the mouse // make sure we disable it on the popup as well as it's parent to avoid any unnecessary event propagation QWidget* pWidget = static_cast(watched); if (pWidget->rect().contains(pWidget->mapFromGlobal(QCursor::pos()))) { pWidget->setAttribute(Qt::WA_NoMouseReplay); if (pWidget->parent() && pWidget->parent()->isWidgetType()) static_cast(pWidget->parent())->setAttribute(Qt::WA_NoMouseReplay); } } } if (FFlag::DontSwallowInputForStudioShortcuts) { if (watched == this && hasFocus()) { if (evt->type() == QEvent::ShortcutOverride) { bool processed = false; QKeyEvent* keyEvent = static_cast(evt); int keyCode = keyEvent->key() | StudioUtilities::translateKeyModifiers(keyEvent->modifiers(), keyEvent->text()); if (UpdateUIManager::Instance().getMainWindow().isShortcut(QKeySequence(keyCode))) { processed = true; } //ShorcutOverride is sent for every key press, not just shortcuts. We don't want to fire handleKeyEvent in KeyPressed because then studio shortcuts get swallowed. handleKeyEvent((QKeyEvent*)evt, RBX::InputObject::TYPE_KEYBOARD, RBX::InputObject::INPUT_STATE_BEGIN, processed); } else if (evt->type() == QEvent::KeyRelease) { handleKeyEvent((QKeyEvent*)evt, RBX::InputObject::TYPE_KEYBOARD, RBX::InputObject::INPUT_STATE_END); } } } // This is needed for Modal Dialog Show/Hide on Mac #ifdef Q_WS_MAC if ( eventType == QEvent::ActivationChange || eventType == QEvent::FocusIn || eventType == QEvent::FocusOut ) { if (rect().contains(mapFromGlobal(QCursor::pos()))) isActiveWindow() ? enterEvent(NULL) : leaveEvent(NULL); } else if (eventType == QEvent::Hide && watched->inherits("QDialog")) { if (rect().contains(mapFromGlobal(QCursor::pos())) && isActiveWindow()) m_bIgnoreLeaveEvent = true; } #else if( eventType == QEvent::ActivationChange ) { if (rect().contains(mapFromGlobal(QCursor::pos()))) isActiveWindow() ? enterEvent(NULL) : leaveEvent(NULL); } #endif return QWidget::eventFilter(watched, evt); } bool QOgreWidget::event(QEvent * evt) { if (m_pRobloxView) { if (evt->type() == QEvent::FocusOut) { m_pRobloxView->handleFocus(false); setLuaTextBoxHasFocus(false); } else if (evt->type() == QEvent::FocusIn) { m_pRobloxView->handleFocus(true); } } // If a lua text box as focus then prevent all shortcut overrides // from executing. if (m_luaTextBoxHasFocus) { if (evt->type() == QEvent::ShortcutOverride) { QKeyEvent* keyEvent = static_cast(evt); keyEvent->accept(); return true; } } if (evt->type() == QEvent::KeyPress) { if (static_cast(evt)->modifiers().testFlag(Qt::ControlModifier) && (static_cast(evt)->key() == Qt::Key_Tab)) { evt->accept(); QCoreApplication::sendEvent(&UpdateUIManager::Instance().getMainWindow(), evt); return true; } } FASTLOG(FLog::TaskSchedulerTiming, "QT Event fired through QOgreWidget"); if ((evt->type() != OGRE_VIEW_UPDATE) || !m_pRobloxView) { FASTLOG1(FLog::RenderRequest, "OgreWidget Event returns before updateView. RBXView: %d", (int)m_pRobloxView); return QWidget::event(evt); } if (!m_bUpdateInProgress) { m_bUpdateInProgress = true; m_pRobloxView->updateView(); m_bUpdateInProgress = false; } return true; } void QOgreWidget::closeEvent(QCloseEvent *) { if (m_pRobloxView) delete m_pRobloxView; m_pRobloxView = NULL; } void QOgreWidget::enterEvent(QEvent *) { if(!m_pRobloxView) return; #ifdef Q_WS_MAC if ( QApplication::activeWindow() != &UpdateUIManager::Instance().getMainWindow() ) return; #endif //inform rbx view about the change m_pRobloxView->handleMouseInside(true); //enable mouse tracking setMouseTracking(true); } void QOgreWidget::leaveEvent(QEvent *) { #ifdef Q_WS_MAC if (m_bIgnoreLeaveEvent) { m_bIgnoreLeaveEvent = false; return; } #endif if(!m_pRobloxView) return; //inform rbx view about the change m_pRobloxView->handleMouseInside(false); //disable mouse tracking setMouseTracking(false); #ifdef Q_WS_MAC setCursor(Qt::ArrowCursor); #endif } void QOgreWidget::focusOutEvent(QFocusEvent* focusEvent) { QWidget::focusOutEvent(focusEvent); if (m_pRobloxView) m_pRobloxView->resetKeyState(); } void QOgreWidget::resizeEvent(QResizeEvent *evt) { if (!m_pRobloxView) return; m_pRobloxView->setBounds(evt->size().width(), evt->size().height()); QPoint mousePos = mapFromGlobal(QCursor::pos()); if (rect().contains(mousePos)) enterEvent(NULL); } void QOgreWidget::mousePressEvent(QMouseEvent *evt) { if (!m_pRobloxView) return; QPoint currentPos = evt->pos(); RBX::InputObject::UserInputType mouseEventType = RBX::InputObject::TYPE_NONE; switch(evt->button()) { case Qt::LeftButton: mouseEventType = RBX::InputObject::TYPE_MOUSEBUTTON1; break; case Qt::RightButton: mouseEventType = RBX::InputObject::TYPE_MOUSEBUTTON2; break; case Qt::MiddleButton: mouseEventType = RBX::InputObject::TYPE_MOUSEBUTTON3; break; default: break; } m_pRobloxView->handleMouse(mouseEventType, RBX::InputObject::INPUT_STATE_BEGIN, currentPos.x(), currentPos.y(), modifiersToUIModCode(evt->modifiers())); evt->accept(); } void QOgreWidget::mouseReleaseEvent(QMouseEvent *evt) { if (!m_pRobloxView) return; RBX::InputObject::UserInputType mouseEventType = RBX::InputObject::TYPE_NONE; switch(evt->button()) { case Qt::LeftButton: mouseEventType = RBX::InputObject::TYPE_MOUSEBUTTON1; break; case Qt::RightButton: mouseEventType = RBX::InputObject::TYPE_MOUSEBUTTON2; break; case Qt::MiddleButton: mouseEventType = RBX::InputObject::TYPE_MOUSEBUTTON3; break; default: break; } evt->accept(); QPoint currentPos = evt->pos(); m_pRobloxView->handleMouse( mouseEventType, RBX::InputObject::INPUT_STATE_END, currentPos.x(), currentPos.y(), modifiersToUIModCode(evt->modifiers()) ); } void QOgreWidget::mouseMoveEvent(QMouseEvent *evt) { FASTLOG(FLog::TaskSchedulerTiming, "QOgreWidget::mouseMoveEvent firing"); if (m_pRobloxView) { lastMovePoint = evt->pos(); lastMoveModCode = modifiersToUIModCode(evt->modifiers()); m_pRobloxView->handleMouse(RBX::InputObject::TYPE_MOUSEMOVEMENT, RBX::InputObject::INPUT_STATE_CHANGE, lastMovePoint.x(), lastMovePoint.y(), lastMoveModCode); } evt->accept(); } void QOgreWidget::handleKeyEvent(QKeyEvent * evt, RBX::InputObject::UserInputType eventType, RBX::InputObject::UserInputState eventState, bool processed) { if (eventType == RBX::InputObject::TYPE_KEYBOARD && (!m_pRobloxView || evt->isAutoRepeat())) { if (!FFlag::DontSwallowInputForStudioShortcuts) { if (eventState == RBX::InputObject::INPUT_STATE_BEGIN) Super::keyPressEvent(evt); else if(eventState == RBX::InputObject::INPUT_STATE_END) Super::keyReleaseEvent(evt); } return; } std::string textString = evt->text().toStdString(); m_pRobloxView->handleKey(eventType, eventState, keyCodeTOUIKeyCode(evt->key()), modifiersToUIModCode(evt->modifiers()), (textString.length() > 0) ? textString.c_str()[0] : NULL, processed); if (!FFlag::DontSwallowInputForStudioShortcuts) { evt->accept(); } } void QOgreWidget::keyPressEvent(QKeyEvent * evt) { if (FFlag::DontSwallowInputForStudioShortcuts) { if (!m_pRobloxView || evt->isAutoRepeat()) { Super::keyPressEvent(evt); return; } evt->accept(); } else { handleKeyEvent(evt, RBX::InputObject::TYPE_KEYBOARD, RBX::InputObject::INPUT_STATE_BEGIN); } } bool QOgreWidget::focusNextPrevChild(bool next) { return false; } void QOgreWidget::keyReleaseEvent(QKeyEvent * evt) { if (FFlag::DontSwallowInputForStudioShortcuts) { if (!m_pRobloxView || evt->isAutoRepeat()) { Super::keyReleaseEvent(evt); return; } evt->accept(); } else { handleKeyEvent(evt, RBX::InputObject::TYPE_KEYBOARD, RBX::InputObject::INPUT_STATE_END); } } void QOgreWidget::wheelEvent(QWheelEvent *evt) { if (!m_pRobloxView) return; QPoint currentPos = evt->pos(); m_pRobloxView->handleScrollWheel(evt->delta(), currentPos.x(), currentPos.y()); evt->accept(); } void QOgreWidget::paintEvent(QPaintEvent *evt) { if (!m_bRobloxViewInitialized) if (RobloxIDEDoc* playDoc = RobloxDocManager::Instance().getPlayDoc()) { m_bRobloxViewInitialized = true; playDoc->initializeRobloxView(); } if (!m_pRobloxView) return QWidget::paintEvent(evt); m_pRobloxView->requestUpdateView(); evt->accept(); } void QOgreWidget::dragEnterEvent(QDragEnterEvent *evt) { if (!isValidDrag(evt)) return QWidget::dragEnterEvent(evt); const QMimeData *pMimeData = evt->mimeData(); if (pMimeData && pMimeData->hasUrls()) { QList urlList = pMimeData->urls(); shared_ptr insertedInstances = m_pRobloxView->handleDropOperation(urlList.at(0).toString(), evt->pos().x(), evt->pos().y(), m_bMouseCommandInvoked); if (RobloxIDEDoc* playDoc = RobloxDocManager::Instance().getPlayDoc()) if (RBX::DataModel* dataModel = playDoc->getDataModel()) UpdateUIManager::Instance().getMainWindow().getPluginHost()->handleDragEnterEvent(dataModel, shared_ptr(insertedInstances), RBX::Vector2(evt->pos().x(), evt->pos().y())); } else { QListWidget *pListWidget = static_cast(evt->source()); QList itemList = pListWidget->selectedItems(); if (itemList.size() <= 0) return QWidget::dragEnterEvent(evt); InsertObjectListWidgetItem *pListWidgetItem = dynamic_cast(itemList.at(0)); if (pListWidgetItem) { if (FFlag::GoogleAnalyticsTrackingEnabled) { RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_ACTION, "InsertObject", pListWidgetItem->text().toStdString().c_str()); } m_pRobloxView->handleDropOperation(pListWidgetItem->getInstance(), evt->pos().x(), evt->pos().y(), m_bMouseCommandInvoked); } } #ifdef Q_WS_MAC if (m_bMouseCommandInvoked) { QApplication::setOverrideCursor(Qt::ClosedHandCursor); enterEvent(NULL); } else { QApplication::setOverrideCursor(Qt::DragCopyCursor); leaveEvent(NULL); } #endif evt->acceptProposedAction(); } void QOgreWidget::dragMoveEvent(QDragMoveEvent *evt) { if(!m_pRobloxView) return QWidget::dragMoveEvent(evt); if (m_bMouseCommandInvoked) { lastMovePoint = evt->pos(); m_pRobloxView->handleMouse(RBX::InputObject::TYPE_MOUSEMOVEMENT, RBX::InputObject::INPUT_STATE_CHANGE, lastMovePoint.x(), lastMovePoint.y(), lastMoveModCode); } evt->acceptProposedAction(); } void QOgreWidget::dropEvent(QDropEvent *evt) { const QMimeData *pMimeData = evt->mimeData(); if (!m_pRobloxView || !pMimeData) return QWidget::dropEvent(evt); #ifdef Q_WS_MAC QApplication::restoreOverrideCursor(); #endif if (m_bMouseCommandInvoked) { QPoint currentPos = evt->pos(); m_pRobloxView->handleMouse(RBX::InputObject::TYPE_MOUSEBUTTON1, RBX::InputObject::INPUT_STATE_END, currentPos.x(), currentPos.y(), (RBX::ModCode)0); } evt->acceptProposedAction(); setFocus(); } void QOgreWidget::dragLeaveEvent(QDragLeaveEvent *evt) { if(!m_pRobloxView) return QWidget::dragLeaveEvent(evt); m_pRobloxView->cancelDropOperation(m_bMouseCommandInvoked); #ifdef Q_WS_MAC QApplication::restoreOverrideCursor(); leaveEvent(NULL); #endif evt->accept(); } #ifdef Q_WS_WIN bool QOgreWidget::winEvent(MSG * msg, long * result) { if ( (msg->message == WM_KEYUP) && //key up (msg->wParam == VK_SNAPSHOT) && //print screen key (UpdateUIManager::Instance().getMainWindow().getBuildMode() == BM_BASIC) )//build mode is basic { //invoke snapshot command QMetaObject::invokeMethod(UpdateUIManager::Instance().getMainWindow().screenShotAction, "triggered", Qt::QueuedConnection); return true; } return QWidget::winEvent(msg, result); } #endif bool QOgreWidget::isValidDrag(QDragEnterEvent *evt) { bool result = false; const QMimeData *pMimeData = evt->mimeData(); if (m_pRobloxView && pMimeData) { // All drags from HTML should be called via window.external.Insert in JS if (pMimeData->hasFormat("text/html")) return false; if (pMimeData->hasUrls()) { QList urlList = pMimeData->urls(); QString urlQStr = urlList.at(0).toString(); QString fileStr = urlList.at(0).toLocalFile(); if (!urlQStr.isEmpty() && fileStr.isEmpty()) result = true; } else { QWidget *pSourceWidget = evt->source(); if (pSourceWidget && (pSourceWidget->objectName() == "InsertObjectListWidget")) result = true; } } return result; } static RBX::KeyCode keyCodeTOUIKeyCode(int keyCode) { /// Handle all albhabets if (keyCode >= 65 && keyCode <= 90) return RBX::KeyCode(keyCode - Qt::Key_A + 'a'); // Handle Numbers if(keyCode >= Qt::Key_0 && keyCode <= Qt::Key_9) return RBX::KeyCode(keyCode); // Handle F1 to F15 if(keyCode >= Qt::Key_F1 && keyCode <= Qt::Key_F15) return RBX::KeyCode(keyCode - 0xFFFF16); RBX::KeyCode rbxKey = RBX::SDLK_UNKNOWN; // Handle Special Unordered Keys Here switch(keyCode) { case Qt::Key_CapsLock: rbxKey = RBX::SDLK_CAPSLOCK; break; case Qt::Key_Backspace : rbxKey = RBX::SDLK_BACKSPACE; break; case Qt::Key_Up : rbxKey = RBX::SDLK_UP; break; case Qt::Key_Down : rbxKey = RBX::SDLK_DOWN; break; case Qt::Key_Left : rbxKey = RBX::SDLK_LEFT; break; case Qt::Key_Right : rbxKey = RBX::SDLK_RIGHT; break; case Qt::Key_Insert : rbxKey = RBX::SDLK_INSERT; break; case Qt::Key_Delete : rbxKey = RBX::SDLK_DELETE; break; case Qt::Key_Home : rbxKey = RBX::SDLK_HOME; break; case Qt::Key_End : rbxKey = RBX::SDLK_END; break; case Qt::Key_PageUp : rbxKey = RBX::SDLK_PAGEUP; break; case Qt::Key_PageDown : rbxKey = RBX::SDLK_PAGEDOWN; break; case Qt::Key_Space : rbxKey = RBX::SDLK_SPACE; break; case Qt::Key_Control: rbxKey = RBX::SDLK_LCTRL; break; case Qt::Key_Alt: rbxKey = RBX::SDLK_LALT; break; case Qt::Key_Shift: rbxKey = RBX::SDLK_LSHIFT; break; case Qt::Key_Escape: rbxKey = RBX::SDLK_ESCAPE; break; case Qt::Key_Minus: rbxKey = RBX::SDLK_MINUS; break; case Qt::Key_Equal: rbxKey = RBX::SDLK_EQUALS; break; case Qt::Key_Tab: rbxKey = RBX::SDLK_TAB; break; case Qt::Key_Backtab: { if (DFFlag::BackTabInputInStudio) { rbxKey = RBX::SDLK_TAB; break; } } case Qt::Key_BracketLeft: rbxKey = RBX::SDLK_LEFTBRACKET; break; case Qt::Key_BracketRight: rbxKey = RBX::SDLK_RIGHTBRACKET; break; case Qt::Key_Return: case Qt::Key_Enter: rbxKey = RBX::SDLK_RETURN; break; case Qt::Key_Semicolon: rbxKey = RBX::SDLK_SEMICOLON; break; case Qt::Key_QuoteLeft: rbxKey = RBX::SDLK_BACKQUOTE; break; case Qt::Key_Apostrophe: rbxKey = RBX::SDLK_QUOTE; break; case Qt::Key_QuoteDbl: rbxKey = RBX::SDLK_QUOTEDBL; break; case Qt::Key_Backslash: rbxKey = RBX::SDLK_BACKSLASH; break; case Qt::Key_Comma: rbxKey = RBX::SDLK_COMMA; break; case Qt::Key_Period: rbxKey = RBX::SDLK_PERIOD; break; case Qt::Key_Slash: rbxKey = RBX::SDLK_SLASH; break; case Qt::Key_multiply: rbxKey = RBX::SDLK_KP_MULTIPLY; break; case Qt::Key_NumLock: rbxKey = RBX::SDLK_NUMLOCK; break; case Qt::Key_ScrollLock: rbxKey = RBX::SDLK_SCROLLOCK; break; case Qt::Key_Asterisk: rbxKey = RBX::SDLK_ASTERISK; break; case Qt::Key_Plus: rbxKey = RBX::SDLK_PLUS; break; } return rbxKey; } static RBX::ModCode modifiersToUIModCode(int modifier) { unsigned int modCode = 0; if (modifier & Qt::ShiftModifier) { modCode = modCode | RBX::KMOD_LSHIFT; } if (modifier & Qt::ControlModifier) { modCode = modCode | RBX::KMOD_LCTRL; } if (modifier & Qt::AltModifier) { modCode = modCode | RBX::KMOD_LALT; } return (RBX::ModCode) modCode; }