Files
aya/client/studio/src/DocDockWidget.cpp
2025-12-17 16:47:48 +00:00

321 lines
8.7 KiB
C++

#include "DocDockWidget.hpp"
// Qt Headers
#include <QSettings>
#include <QMouseEvent>
// 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<QMouseEvent> mouseEvent = static_cast<QMouseEvent*>(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<QMouseEvent> mouseEvent = static_cast<QMouseEvent*>(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();
}