#include "DocDockWidget.hpp" // Qt Headers #include #include // Roblox Studio Headers #include "RobloxMainWindow.hpp" #include "DocDockManager.hpp" #include "RobloxDocManager.hpp" #include "StudioUtilities.hpp" static const char* DocDockWidgetSetting = "DocDockWidget/"; DocDockWidget::DocDockWidget(RobloxMainWindow& mainWindow, DocDockManager& docManager, IRobloxDoc& doc) : QDockWidget(&mainWindow) , m_MainWindow(mainWindow) , m_DocManager(docManager) , m_Doc(doc) , m_DocType(doc.docType()) , m_IsMoving(false) , m_Viewer(doc.getViewer()) , m_IsSimulatingDrag(false) { AYAASSERT(m_Viewer); setAttribute(Qt::WA_DeleteOnClose); setWidget(m_Viewer); setFloating(true); setFocusPolicy(Qt::StrongFocus); setTitle(m_Doc.displayName(), m_Doc.fileName()); setMinimumSize(m_Viewer->minimumSize()); setMaximumSize(m_Viewer->maximumSize()); setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); connect(this, SIGNAL(topLevelChanged(bool)), this, SLOT(onTopLevelChanged(bool))); connect(this, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(onDockLocationChanged(Qt::DockWidgetArea))); // restore geometry for this type of doc const QSettings settings; const QString settingName = DocDockWidgetSetting + QString::number(m_DocType) + "/geometry"; restoreGeometry(settings.value(settingName).toByteArray()); connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(onFocusChanged(QWidget*, QWidget*))); show(); } /** * Saves geometry for the next time this type of doc is opened. * Must call cleanup() before destruction. */ DocDockWidget::~DocDockWidget() { AYAASSERT(!widget()); QSettings settings; const QString settingName = DocDockWidgetSetting + QString::number(m_DocType) + "/geometry"; settings.setValue(settingName, saveGeometry()); } /** * Cleans up the widget and makes sure it won't be deleted. * Must be called before the destructor. We need to do an immediate clean up, we can't * wait for the destructor to be called since we use deleteLater. */ void DocDockWidget::cleanup() { if (!widget()) return; stopDragging(); setFloating(false); // make sure we're not the ones deleting the viewer setWidget(NULL); QCoreApplication::removePostedEvents(m_Viewer); m_Viewer->setParent(NULL); m_Viewer->hide(); m_MainWindow.removeDockWidget(this); } /** * Simulates a window dragging operation. */ void DocDockWidget::startDragging() { if (!m_IsSimulatingDrag) { m_IsSimulatingDrag = true; m_StartPos = QCursor::pos(); grabMouse(); simulateDrag(); } } /** * Begins a simulated window drag. * This forces the window to be moved as the cursor is moved until the left button is released. */ void DocDockWidget::simulateDrag() { if (m_IsSimulatingDrag) { const QPoint& worldPos = QCursor::pos(); const QPoint offset(width() / 2, style()->pixelMetric(QStyle::PM_TitleBarHeight) / 2); move(worldPos - offset); } } /** * Changes the dock widget title when the doc names change. * Updates the object name to the new title. */ void DocDockWidget::setTitle(const QString& displayName, const QString& fileName) { QString title = displayName; if (!fileName.isEmpty()) title += " - " + fileName; setWindowTitle(title); setObjectName(title); } /** * Callback for user clicking on the attach button. * Attaches doc to the tab widget. */ void DocDockWidget::onAttachDoc() { RobloxDocManager::Instance().attachDoc(m_Doc); } /** * Requests doc close so that it will be saved properly. */ void DocDockWidget::closeEvent(QCloseEvent* event) { AYAASSERT(widget()); if (m_MainWindow.requestDocClose(m_Doc)) QDockWidget::closeEvent(event); else event->ignore(); } /** * Callback when the window is being moved. * This causes dragging to begin and the tab hover over location to be detected. */ void DocDockWidget::moveEvent(QMoveEvent* event) { QDockWidget::moveEvent(event); // ignore move event if we're not floating if (!isFloating()) return; if (!m_IsMoving) { m_IsMoving = true; m_StartPos = QCursor::pos() + event->oldPos() - event->pos(); setMouseTracking(true); } else { // only hover over if we attempted to move the window a minimum distance const QPoint moveDelta = QCursor::pos() - m_StartPos; if (moveDelta.manhattanLength() > QApplication::startDragDistance()) RobloxDocManager::Instance().setDockHoverOverPos(QCursor::pos()); } m_MainWindow.updateEmbeddedFindPosition(); } /** * Callback when the window is being resized. * This causes dragging to abort. */ void DocDockWidget::resizeEvent(QResizeEvent* event) { QDockWidget::resizeEvent(event); m_StartPos = QCursor::pos(); // ignore any dragging that might have happened stopDragging(); } /** * Callback for the mouse moving during a drag. * If we're simulating a drag, move the window. */ void DocDockWidget::mouseMoveEvent(QMouseEvent* event) { QDockWidget::mouseMoveEvent(event); if (m_IsSimulatingDrag) simulateDrag(); else if (event->button() != Qt::LeftButton) stopDragging(); } /** * Ends the window drag operation. * Attempts to attach the dock to the tab widget if over the tab widget. * * @return true if the doc was attached */ bool DocDockWidget::stopDragging() { bool attached = false; if (m_IsMoving || m_IsSimulatingDrag) { if (m_IsSimulatingDrag) { releaseMouse(); m_IsSimulatingDrag = false; } setMouseTracking(false); m_IsMoving = false; // only attach if we attempted to move the window and didn't just click on it const QPoint moveDelta = QCursor::pos() - m_StartPos; QPoint clickPos; if (moveDelta.manhattanLength() > QApplication::startDragDistance()) clickPos = QCursor::pos(); else clickPos = QPoint(INT_MAX, INT_MAX); // don't want to attach it attached = RobloxDocManager::Instance().attemptAttach(m_Doc, clickPos); if (!attached) RobloxDocManager::Instance().setCurrentDoc(&m_Doc); } return attached; } /** * Handles non-client mouse press events to begin window dragging and setting the * current doc if the window is activated. */ bool DocDockWidget::event(QEvent* event) { bool result = QDockWidget::event(event); if (event->type() == QEvent::NonClientAreaMouseButtonRelease || event->type() == QEvent::MouseButtonRelease) { // Must catch mouse events here because dock widget doesn't give em to us in mouesReleaseEvent. // On Mac, this is the only way to detect an end window drag event. Aya::BaldPtr mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::LeftButton) { stopDragging(); result = true; } } else if (event->type() == QEvent::NonClientAreaMouseButtonPress || event->type() == QEvent::MouseButtonPress) { // Reset start pos in case this is just a click. Aya::BaldPtr mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::LeftButton) m_StartPos = QCursor::pos(); } else if (event->type() == QEvent::WindowActivate && widget()) { RobloxDocManager::Instance().setCurrentDoc(&m_Doc); } return result; } /** * Detect changes in widget focus. * Only if our new focus is an ancestor of the dockwidget do we need to set the current doc. */ void DocDockWidget::onFocusChanged(QWidget* oldWidget, QWidget* newWidget) { if (isAncestorOf(newWidget) && widget()) RobloxDocManager::Instance().setCurrentDoc(&m_Doc); } #ifdef Q_WS_WIN32 /** * Windows event callback to handle the end of the window drag operation. * This is the only way to detect when the mouse button has been released during a * drag on Windows. */ bool DocDockWidget::winEvent(MSG* msg, long* result) { if (msg->message == WM_EXITSIZEMOVE) stopDragging(); return QDockWidget::winEvent(msg, result); } #endif /** * Callback when the dock is either docked or undocked (plugged or unplugged). * Begin dragging when undocked. * Stop dragging if it's been docked. This is just overkill. */ void DocDockWidget::onTopLevelChanged(bool TopLevel) { if (TopLevel) startDragging(); else stopDragging(); } /** * Callback when the dock widget docks somewhere else. * Stop dragging when it's been docked. This is just overkill. */ void DocDockWidget::onDockLocationChanged(Qt::DockWidgetArea area) { stopDragging(); }