Resolved merge conflicts

Signed-off-by: Chris Galvan <chgalvan@amazon.com>
This commit is contained in:
Chris Galvan
2021-07-02 12:42:44 -05:00
18948 changed files with 23832 additions and 24928 deletions
@@ -0,0 +1,35 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "EditorMetricsPlainTextNameRegistration.h"
namespace Editor
{
EditorMetricsPlainTextNameRegistrationBusListener::EditorMetricsPlainTextNameRegistrationBusListener()
{
AzFramework::MetricsPlainTextNameRegistrationBus::Handler::BusConnect();
}
EditorMetricsPlainTextNameRegistrationBusListener::~EditorMetricsPlainTextNameRegistrationBusListener()
{
AzFramework::MetricsPlainTextNameRegistrationBus::Handler::BusDisconnect();
}
void EditorMetricsPlainTextNameRegistrationBusListener::RegisterForNameSending(const AZStd::vector<AZ::Uuid>& typeIdsThatCanBeSentAsPlainText)
{
m_registeredTypeIds.insert(typeIdsThatCanBeSentAsPlainText.begin(), typeIdsThatCanBeSentAsPlainText.end());
}
bool EditorMetricsPlainTextNameRegistrationBusListener::IsTypeRegisteredForNameSending(const AZ::Uuid& typeId)
{
return (m_registeredTypeIds.find(typeId) != m_registeredTypeIds.end());
}
} // namespace editor
@@ -0,0 +1,28 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzFramework/Metrics/MetricsPlainTextNameRegistration.h>
#include <AzCore/std/containers/set.h>
namespace Editor
{
class EditorMetricsPlainTextNameRegistrationBusListener : public AzFramework::MetricsPlainTextNameRegistrationBus::Handler
{
public:
EditorMetricsPlainTextNameRegistrationBusListener();
virtual ~EditorMetricsPlainTextNameRegistrationBusListener();
void RegisterForNameSending(const AZStd::vector<AZ::Uuid>& typeIdsThatCanBeSentAsPlainText) override;
bool IsTypeRegisteredForNameSending(const AZ::Uuid& typeId) override;
private:
AZStd::set<AZ::Uuid> m_registeredTypeIds;
};
} // namespace editor
File diff suppressed because it is too large Load Diff
+122
View File
@@ -0,0 +1,122 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#ifndef LEVELEDITORMENUHANDLER_H
#define LEVELEDITORMENUHANDLER_H
#if !defined(Q_MOC_RUN)
#include <QObject>
#include <QString>
#include <QMenu>
#include <QPointer>
#include "ActionManager.h"
#include "QtViewPaneManager.h"
#include <AzToolsFramework/ComponentMode/EditorComponentModeBus.h>
#endif
class MainWindow;
class QtViewPaneManager;
class QSettings;
struct QtViewPane;
class LevelEditorMenuHandler
: public QObject
, private AzToolsFramework::ComponentModeFramework::EditorComponentModeNotificationBus::Handler
, private AzToolsFramework::EditorMenuRequestBus::Handler
{
Q_OBJECT
public:
LevelEditorMenuHandler(MainWindow* mainWindow, QtViewPaneManager* const viewPaneManager, QSettings& settings);
~LevelEditorMenuHandler();
void Initialize();
bool MRUEntryIsValid(const QString& entry, const QString& gameFolderPath);
void IncrementViewPaneVersion();
int GetViewPaneVersion() const;
void UpdateViewLayoutsMenu(ActionManager::MenuWrapper& layoutsMenu);
void ResetToolsMenus();
QAction* CreateViewPaneAction(const QtViewPane* view);
// It's used when users update the Tool Box Macro list in the Configure Tool Box Macro dialog
void UpdateMacrosMenu();
Q_SIGNALS:
void ActivateAssetImporter();
private:
QMenu* CreateFileMenu();
QMenu* CreateEditMenu();
void PopulateEditMenu(ActionManager::MenuWrapper& editMenu);
QMenu* CreateGameMenu();
QMenu* CreateToolsMenu();
QMenu* CreateViewMenu();
QMenu* CreateHelpMenu();
void checkOrOpenView();
QMap<QString, QList<QtViewPane*>> CreateMenuMap(QMap<QString, QList<QtViewPane*>>& menuMap, QtViewPanes& allRegisteredViewPanes);
void CreateMenuOptions(QMap<QString, QList<QtViewPane*>>* menuMap, ActionManager::MenuWrapper& menu, const char* category);
void CreateDebuggingSubMenu(ActionManager::MenuWrapper gameMenu);
void UpdateMRUFiles();
void ClearAll();
void OnUpdateOpenRecent();
void OnUpdateMacrosMenu();
void UpdateOpenViewPaneMenu();
QAction* CreateViewPaneMenuItem(ActionManager* actionManager, ActionManager::MenuWrapper& menu, const QtViewPane* view);
void InitializeViewPaneMenu(ActionManager* actionManager, ActionManager::MenuWrapper& menu, AZStd::function < bool(const QtViewPane& view)> functor);
void LoadComponentLayout();
void AddDisableActionInSimModeListener(QAction* action);
// EditorComponentModeNotificationBus
void EnteredComponentMode(const AZStd::vector<AZ::Uuid>& componentModeTypes) override;
void LeftComponentMode(const AZStd::vector<AZ::Uuid>& componentModeTypes) override;
// EditorMenuRequestBus
void AddEditMenuAction(QAction* action) override;
void AddMenuAction(AZStd::string_view categoryId, QAction* action, bool addToToolsToolbar) override;
void RestoreEditMenuToDefault() override;
MainWindow* m_mainWindow;
ActionManager* m_actionManager;
QtViewPaneManager* m_viewPaneManager;
QPointer<QMenu> m_viewportViewsMenu;
ActionManager::MenuWrapper m_toolsMenu;
QMenu* m_mostRecentLevelsMenu = nullptr;
QMenu* m_mostRecentProjectsMenu = nullptr;
QMenu* m_editmenu = nullptr;
ActionManager::MenuWrapper m_viewPanesMenu;
ActionManager::MenuWrapper m_layoutsMenu;
ActionManager::MenuWrapper m_macrosMenu;
const char* m_levelExtension = nullptr;
int m_viewPaneVersion = 0;
QList<QMenu*> m_topLevelMenus;
QSettings& m_settings;
};
#endif // LEVELEDITORMENUHANDLER_H
+659
View File
@@ -0,0 +1,659 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "QtEditorApplication.h"
// Qt
#include <QAbstractEventDispatcher>
#include <QScopedValueRollback>
#include <QToolBar>
#include <QLoggingCategory>
#if defined(AZ_PLATFORM_WINDOWS)
#include <QtGui/qpa/qplatformnativeinterface.h>
#include <QtGui/private/qhighdpiscaling_p.h>
#endif
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
// AzFramework
#if defined(AZ_PLATFORM_WINDOWS)
# include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#endif // defined(AZ_PLATFORM_WINDOWS)
// AzQtComponents
#include <AzQtComponents/Components/GlobalEventFilter.h>
#include <AzQtComponents/Components/O3DEStylesheet.h>
#include <AzQtComponents/Components/Titlebar.h>
#include <AzQtComponents/Components/WindowDecorationWrapper.h>
// Editor
#include "Settings.h"
#include "CryEdit.h"
enum
{
// in milliseconds
GameModeIdleFrequency = 0,
EditorModeIdleFrequency = 1,
InactiveModeFrequency = 10,
UninitializedFrequency = 9999,
};
Q_LOGGING_CATEGORY(InputDebugging, "o3de.editor.input")
// internal, private namespace:
namespace
{
class EditorGlobalEventFilter
: public AzQtComponents::GlobalEventFilter
{
public:
explicit EditorGlobalEventFilter(QObject* watch)
: AzQtComponents::GlobalEventFilter(watch) {}
bool eventFilter(QObject* obj, QEvent* e) override
{
static bool isRecursing = false;
if (isRecursing)
{
return false;
}
QScopedValueRollback<bool> guard(isRecursing, true);
// Detect Widget move
// We're doing this before the events are actually consumed to avoid confusion
if (IsDragGuardedWidget(obj))
{
switch (e->type())
{
case QEvent::MouseButtonPress:
{
m_widgetDraggedState = WidgetDraggedState::Clicked;
break;
}
case QEvent::Move:
case QEvent::MouseMove:
{
if (m_widgetDraggedState == WidgetDraggedState::Clicked)
{
m_widgetDraggedState = WidgetDraggedState::Dragged;
}
break;
}
}
}
if (e->type() == QEvent::MouseButtonRelease)
{
m_widgetDraggedState = WidgetDraggedState::None;
}
switch (e->type())
{
case QEvent::KeyPress:
case QEvent::KeyRelease:
{
if (GetIEditor()->IsInGameMode())
{
// don't let certain keys fall through to the game when it's running
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(e);
auto key = keyEvent->key();
if ((key == Qt::Key_Alt) || (key == Qt::Key_AltGr) || ((key >= Qt::Key_F1) && (key <= Qt::Key_F35)))
{
return true;
}
}
}
break;
case QEvent::Shortcut:
{
// Eat shortcuts in game mode or when a guarded widget is being dragged
if (GetIEditor()->IsInGameMode() || m_widgetDraggedState == WidgetDraggedState::Dragged)
{
return true;
}
}
break;
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
case QEvent::MouseMove:
{
#if AZ_TRAIT_OS_PLATFORM_APPLE
auto widget = qobject_cast<QWidget*>(obj);
if (widget && widget->graphicsProxyWidget() != nullptr)
{
QMouseEvent* me = static_cast<QMouseEvent*>(e);
QWidget* target = qApp->widgetAt(QCursor::pos());
if (target)
{
QMouseEvent ev(me->type(), target->mapFromGlobal(QCursor::pos()), me->button(), me->buttons(), me->modifiers());
qApp->notify(target, &ev);
return true;
}
}
#endif
GuardMouseEventSelectionChangeMetrics(e);
}
break;
}
return GlobalEventFilter::eventFilter(obj, e);
}
private:
bool m_mouseButtonWasDown = false;
void GuardMouseEventSelectionChangeMetrics(QEvent* e)
{
// Force the metrics collector to queue up any selection changed metrics until mouse release, so that we don't
// get flooded with multiple selection changed events when one, sent on mouse release, is enough.
if (e->type() == QEvent::MouseButtonPress)
{
if (!m_mouseButtonWasDown)
{
m_mouseButtonWasDown = true;
}
}
else if (e->type() == QEvent::MouseButtonRelease)
{
// This is a tricky case. We don't want to send the end selection change event too early
// because there might be other things responding to the mouse release after this, and we want to
// block handling of the selection change events until we're entirely finished with the mouse press.
// So, queue the handling with a single shot timer, but then check the state of the mouse buttons
// to ensure that they haven't been pressed in between the release and the timer firing off.
QTimer::singleShot(0, this, [this]() {
if (!QApplication::mouseButtons() && m_mouseButtonWasDown)
{
m_mouseButtonWasDown = false;
}
});
}
}
//! Detect if the event's target is a Widget we want to guard from shortcuts while it's being dragged.
//! This function can be easily expanded to handle exceptions.
bool IsDragGuardedWidget(const QObject* obj)
{
return qobject_cast<const QWidget*>(obj) != nullptr;
}
//! Enum to keep track of Widget dragged state
enum class WidgetDraggedState
{
None, //!< No widget is being clicked nor dragged
Clicked, //!< A widget has been clicked on but has not been dragged
Dragged, //!< A widget is being dragged
};
WidgetDraggedState m_widgetDraggedState = WidgetDraggedState::None;
};
static void LogToDebug([[maybe_unused]] QtMsgType Type, [[maybe_unused]] const QMessageLogContext& Context, const QString& message)
{
#if defined(WIN32) || defined(WIN64)
OutputDebugStringW(L"Qt: ");
OutputDebugStringW(reinterpret_cast<const wchar_t*>(message.utf16()));
OutputDebugStringW(L"\n");
#endif
}
}
namespace Editor
{
void ScanDirectories(QFileInfoList& directoryList, const QStringList& filters, QFileInfoList& files, ScanDirectoriesUpdateCallBack updateCallback)
{
while (!directoryList.isEmpty())
{
QDir directory(directoryList.front().absoluteFilePath(), "*", QDir::Name | QDir::IgnoreCase, QDir::AllEntries);
directoryList.pop_front();
if (directory.exists())
{
// Append each file from this directory that matches one of the filters to files
directory.setNameFilters(filters);
directory.setFilter(QDir::Files);
files.append(directory.entryInfoList());
// Add all of the subdirectories from this directory to the queue to be searched
directory.setNameFilters(QStringList("*"));
directory.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
directoryList.append(directory.entryInfoList());
if (updateCallback)
{
updateCallback();
}
}
}
}
EditorQtApplication::EditorQtApplication(int& argc, char** argv)
: QApplication(argc, argv)
, m_inWinEventFilter(false)
, m_stylesheet(new AzQtComponents::O3DEStylesheet(this))
, m_idleTimer(new QTimer(this))
{
m_idleTimer->setInterval(UninitializedFrequency);
setWindowIcon(QIcon(":/Application/res/o3de_editor.ico"));
// set the default key store for our preferences:
setOrganizationName("O3DE");
setOrganizationDomain("o3de.org");
setApplicationName("O3DE Editor");
connect(m_idleTimer, &QTimer::timeout, this, &EditorQtApplication::maybeProcessIdle);
connect(this, &QGuiApplication::applicationStateChanged, this, [this] { ResetIdleTimerInterval(PollState); });
installEventFilter(this);
// Disable our debugging input helpers by default
QLoggingCategory::setFilterRules(QStringLiteral("o3de.editor.input.*=false"));
// Initialize our stylesheet here to allow Gems to register stylesheets when their system components activate.
AZ::IO::FixedMaxPath engineRootPath;
{
// Create a ComponentApplication to initialize the AZ::SystemAllocator and initialize the SettingsRegistry
AZ::ComponentApplication application(argc, argv);
auto settingsRegistry = AZ::SettingsRegistry::Get();
settingsRegistry->Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
}
m_stylesheet->initialize(this, engineRootPath);
}
void EditorQtApplication::Initialize()
{
GetIEditor()->RegisterNotifyListener(this);
// install QTranslator
InstallEditorTranslators();
// install hooks and filters last and revoke them first
InstallFilters();
// install this filter. It will be a parent of the application and cleaned up when it is cleaned up automically
auto globalEventFilter = new EditorGlobalEventFilter(this);
installEventFilter(globalEventFilter);
}
void EditorQtApplication::LoadSettings()
{
AZ::SerializeContext* context;
EBUS_EVENT_RESULT(context, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(context, "No serialize context");
char resolvedPath[AZ_MAX_PATH_LEN];
AZ::IO::FileIOBase::GetInstance()->ResolvePath("@user@/EditorUserSettings.xml", resolvedPath, AZ_MAX_PATH_LEN);
m_localUserSettings.Load(resolvedPath, context);
m_localUserSettings.Activate(AZ::UserSettings::CT_LOCAL);
AZ::UserSettingsOwnerRequestBus::Handler::BusConnect(AZ::UserSettings::CT_LOCAL);
m_activatedLocalUserSettings = true;
}
void EditorQtApplication::UnloadSettings()
{
if (m_activatedLocalUserSettings)
{
SaveSettings();
m_localUserSettings.Deactivate();
AZ::UserSettingsOwnerRequestBus::Handler::BusDisconnect();
m_activatedLocalUserSettings = false;
}
}
void EditorQtApplication::SaveSettings()
{
if (m_activatedLocalUserSettings)
{
AZ::SerializeContext* context;
EBUS_EVENT_RESULT(context, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(context, "No serialize context");
char resolvedPath[AZ_MAX_PATH_LEN];
AZ::IO::FileIOBase::GetInstance()->ResolvePath("@user@/EditorUserSettings.xml", resolvedPath, AZ_ARRAY_SIZE(resolvedPath));
m_localUserSettings.Save(resolvedPath, context);
}
}
void EditorQtApplication::maybeProcessIdle()
{
if (!m_isMovingOrResizing)
{
if (auto winapp = CCryEditApp::instance())
{
winapp->OnIdle(0);
}
}
}
void EditorQtApplication::InstallQtLogHandler()
{
qInstallMessageHandler(LogToDebug);
}
void EditorQtApplication::InstallFilters()
{
if (auto dispatcher = QAbstractEventDispatcher::instance())
{
dispatcher->installNativeEventFilter(this);
}
}
void EditorQtApplication::UninstallFilters()
{
if (auto dispatcher = QAbstractEventDispatcher::instance())
{
dispatcher->removeNativeEventFilter(this);
}
}
EditorQtApplication::~EditorQtApplication()
{
if (GetIEditor())
{
GetIEditor()->UnregisterNotifyListener(this);
}
UninstallFilters();
UninstallEditorTranslators();
}
#if defined(AZ_PLATFORM_WINDOWS)
bool EditorQtApplication::nativeEventFilter([[maybe_unused]] const QByteArray& eventType, void* message, long* result)
{
MSG* msg = (MSG*)message;
if (msg->message == WM_MOVING || msg->message == WM_SIZING)
{
m_isMovingOrResizing = true;
}
else if (msg->message == WM_EXITSIZEMOVE)
{
m_isMovingOrResizing = false;
}
// Prevent the user from being able to move the window in game mode.
// This is done during the hit test phase to bypass the native window move messages. If the window
// decoration wrapper title bar contains the cursor, set the result to HTCLIENT instead of
// HTCAPTION.
if (msg->message == WM_NCHITTEST && GetIEditor()->IsInGameMode())
{
const LRESULT defWinProcResult = DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
if (defWinProcResult == 1)
{
if (QWidget* widget = QWidget::find((WId)msg->hwnd))
{
if (auto wrapper = qobject_cast<const AzQtComponents::WindowDecorationWrapper *>(widget))
{
AzQtComponents::TitleBar* titleBar = wrapper->titleBar();
const short global_x = static_cast<short>(LOWORD(msg->lParam));
const short global_y = static_cast<short>(HIWORD(msg->lParam));
const QPoint globalPos = QHighDpi::fromNativePixels(QPoint(global_x, global_y), widget->window()->windowHandle());
const QPoint local = titleBar->mapFromGlobal(globalPos);
if (titleBar->draggableRect().contains(local) && !titleBar->isTopResizeArea(globalPos))
{
*result = HTCLIENT;
return true;
}
}
}
}
}
// Ensure that the Windows WM_INPUT messages get passed through to the AzFramework input system.
// These events are now consumed both in and out of game mode.
if (msg->message == WM_INPUT)
{
UINT rawInputSize;
const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER);
GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, NULL, &rawInputSize, rawInputHeaderSize);
AZStd::array<BYTE, sizeof(RAWINPUT)> rawInputBytesArray;
LPBYTE rawInputBytes = rawInputBytesArray.data();
const UINT bytesCopied = GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize);
CRY_ASSERT(bytesCopied == rawInputSize);
RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes;
CRY_ASSERT(rawInput);
AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputEvent, *rawInput);
return false;
}
else if (msg->message == WM_DEVICECHANGE)
{
if (msg->wParam == 0x0007) // DBT_DEVNODES_CHANGED
{
AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputDeviceChangeEvent);
}
return true;
}
return false;
}
#endif
void EditorQtApplication::OnEditorNotifyEvent(EEditorNotifyEvent event)
{
switch (event)
{
case eNotify_OnStyleChanged:
RefreshStyleSheet();
emit skinChanged();
break;
case eNotify_OnQuit:
GetIEditor()->UnregisterNotifyListener(this);
break;
case eNotify_OnBeginGameMode:
// GetIEditor()->IsInGameMode() Isn't reliable when called from within the notification handler
ResetIdleTimerInterval(GameMode);
break;
case eNotify_OnEndGameMode:
ResetIdleTimerInterval(EditorMode);
break;
}
}
QColor EditorQtApplication::InterpolateColors(QColor a, QColor b, float factor)
{
return QColor(int(a.red() * (1.0f - factor) + b.red() * factor),
int(a.green() * (1.0f - factor) + b.green() * factor),
int(a.blue() * (1.0f - factor) + b.blue() * factor),
int(a.alpha() * (1.0f - factor) + b.alpha() * factor));
}
void EditorQtApplication::RefreshStyleSheet()
{
m_stylesheet->Refresh();
}
void EditorQtApplication::setIsMovingOrResizing(bool isMovingOrResizing)
{
if (m_isMovingOrResizing == isMovingOrResizing)
{
return;
}
m_isMovingOrResizing = isMovingOrResizing;
}
bool EditorQtApplication::isMovingOrResizing() const
{
return m_isMovingOrResizing;
}
const QColor& EditorQtApplication::GetColorByName(const QString& name)
{
return m_stylesheet->GetColorByName(name);
}
EditorQtApplication* EditorQtApplication::instance()
{
return static_cast<EditorQtApplication*>(QApplication::instance());
}
bool EditorQtApplication::IsActive()
{
return applicationState() == Qt::ApplicationActive;
}
QTranslator* EditorQtApplication::CreateAndInitializeTranslator(const QString& filename, const QString& directory)
{
Q_ASSERT(QFile::exists(directory + "/" + filename));
QTranslator* translator = new QTranslator();
translator->load(filename, directory);
installTranslator(translator);
return translator;
}
void EditorQtApplication::InstallEditorTranslators()
{
m_editorTranslator = CreateAndInitializeTranslator("editor_en-us.qm", ":/Translations");
m_assetBrowserTranslator = CreateAndInitializeTranslator("assetbrowser_en-us.qm", ":/Translations");
}
void EditorQtApplication::DeleteTranslator(QTranslator*& translator)
{
removeTranslator(translator);
delete translator;
translator = nullptr;
}
void EditorQtApplication::UninstallEditorTranslators()
{
DeleteTranslator(m_editorTranslator);
DeleteTranslator(m_assetBrowserTranslator);
}
void EditorQtApplication::EnableOnIdle(bool enable)
{
if (enable)
{
if (m_idleTimer->interval() == UninitializedFrequency)
{
ResetIdleTimerInterval();
}
m_idleTimer->start();
}
else
{
m_idleTimer->stop();
}
}
bool EditorQtApplication::OnIdleEnabled() const
{
if (m_idleTimer->interval() == UninitializedFrequency)
{
return false;
}
return m_idleTimer->isActive();
}
void EditorQtApplication::ResetIdleTimerInterval(TimerResetFlag flag)
{
bool isInGameMode = flag == GameMode;
if (flag == PollState)
{
isInGameMode = GetIEditor() ? GetIEditor()->IsInGameMode() : false;
}
// Game mode takes precedence over anything else
if (isInGameMode)
{
m_idleTimer->setInterval(GameModeIdleFrequency);
}
else
{
if (applicationState() & Qt::ApplicationActive)
{
m_idleTimer->setInterval(EditorModeIdleFrequency);
}
else
{
m_idleTimer->setInterval(InactiveModeFrequency);
}
}
}
bool EditorQtApplication::eventFilter(QObject* object, QEvent* event)
{
switch (event->type())
{
case QEvent::MouseButtonPress:
m_pressedButtons |= reinterpret_cast<QMouseEvent*>(event)->button();
break;
case QEvent::MouseButtonRelease:
m_pressedButtons &= ~(reinterpret_cast<QMouseEvent*>(event)->button());
break;
case QEvent::KeyPress:
m_pressedKeys.insert(reinterpret_cast<QKeyEvent*>(event)->key());
break;
case QEvent::KeyRelease:
m_pressedKeys.remove(reinterpret_cast<QKeyEvent*>(event)->key());
break;
#ifdef AZ_PLATFORM_WINDOWS
case QEvent::Leave:
{
// if we receive a leave event for a toolbar on Windows
// check first whether we really left it. If we didn't: start checking
// for the tool bar under the mouse by timer to check when we really left.
// Synthesize a new leave event then. Workaround for LY-69788
auto toolBarAt = [](const QPoint& pos) -> QToolBar* {
QWidget* widget = qApp->widgetAt(pos);
while (widget != nullptr)
{
if (QToolBar* tb = qobject_cast<QToolBar*>(widget))
{
return tb;
}
widget = widget->parentWidget();
}
return nullptr;
};
if (object == toolBarAt(QCursor::pos()))
{
QTimer* t = new QTimer(object);
t->start(100);
connect(t, &QTimer::timeout, object, [t, object, toolBarAt]() {
if (object != toolBarAt(QCursor::pos()))
{
QEvent event(QEvent::Leave);
qApp->sendEvent(object, &event);
t->deleteLater();
}
});
return true;
}
break;
}
#endif
default:
break;
}
return QApplication::eventFilter(object, event);
}
} // end namespace Editor
#include <Core/moc_QtEditorApplication.cpp>
+138
View File
@@ -0,0 +1,138 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QApplication>
#include <QAbstractNativeEventFilter>
#include <QColor>
#include <QMap>
#include <QTranslator>
#include <QSet>
#include "IEventLoopHook.h"
#include <unordered_map>
#include <AzCore/PlatformDef.h>
#include <AzCore/UserSettings/UserSettingsProvider.h>
#include <IEditor.h>
#endif
class QFileInfo;
typedef QList<QFileInfo> QFileInfoList;
class QByteArray;
namespace AzQtComponents
{
class O3DEStylesheet;
}
enum EEditorNotifyEvent;
// This contains the main "QApplication"-derived class for the editor itself.
// it performs the message hooking and other functions to allow CryEdit to function.
namespace Editor
{
typedef void(* ScanDirectoriesUpdateCallBack)(void);
/*!
\param directoryList A list of directories to search. ScanDirectories will also search the subdirectories of each of these.
\param filters A list of filename filters. Supports * and ? wildcards. The filters will not apply to the directory names.
\param files Any file that is found and matches any of the filters will be added to files.
\param updateCallback An optional callback function that will be called once for every directory and subdirectory that is scanned.
*/
void ScanDirectories(QFileInfoList& directoryList, const QStringList& filters, QFileInfoList& files, ScanDirectoriesUpdateCallBack updateCallback = nullptr);
class EditorQtApplication
: public QApplication
, public QAbstractNativeEventFilter
, public IEditorNotifyListener
, public AZ::UserSettingsOwnerRequestBus::Handler
{
Q_OBJECT
Q_PROPERTY(QSet<int> pressedKeys READ pressedKeys)
Q_PROPERTY(int pressedMouseButtons READ pressedMouseButtons)
public:
// Call this before creating this object:
static void InstallQtLogHandler();
EditorQtApplication(int& argc, char** argv);
virtual ~EditorQtApplication();
void Initialize();
void LoadSettings();
void UnloadSettings();
// AZ::UserSettingsOwnerRequestBus::Handler
void SaveSettings() override;
////
static EditorQtApplication* instance();
static bool IsActive();
bool isMovingOrResizing() const;
// QAbstractNativeEventFilter:
bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override;
// IEditorNotifyListener:
void OnEditorNotifyEvent(EEditorNotifyEvent event) override;
const QColor& GetColorByName(const QString& colorName);
void EnableOnIdle(bool enable = true);
bool OnIdleEnabled() const;
bool eventFilter(QObject* object, QEvent* event) override;
QSet<int> pressedKeys() const { return m_pressedKeys; }
int pressedMouseButtons() const { return m_pressedButtons; }
public Q_SLOTS:
void setIsMovingOrResizing(bool isMovingOrResizing);
signals:
void skinChanged();
private:
enum TimerResetFlag
{
PollState,
GameMode,
EditorMode
};
void ResetIdleTimerInterval(TimerResetFlag = PollState);
static QColor InterpolateColors(QColor a, QColor b, float factor);
void RefreshStyleSheet();
void InstallFilters();
void UninstallFilters();
void maybeProcessIdle();
AzQtComponents::O3DEStylesheet* m_stylesheet;
bool m_inWinEventFilter = false;
// Translators
void InstallEditorTranslators();
void UninstallEditorTranslators();
QTranslator* CreateAndInitializeTranslator(const QString& filename, const QString& directory);
void DeleteTranslator(QTranslator*& translator);
QTranslator* m_editorTranslator = nullptr;
QTranslator* m_assetBrowserTranslator = nullptr;
QTimer* const m_idleTimer = nullptr;
bool m_isMovingOrResizing = false;
AZ::UserSettingsProvider m_localUserSettings;
Qt::MouseButtons m_pressedButtons = Qt::NoButton;
QSet<int> m_pressedKeys;
bool m_activatedLocalUserSettings = false;
};
} // namespace editor
@@ -0,0 +1,17 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "QtEditorApplication.h"
namespace Editor
{
bool EditorQtApplication::nativeEventFilter(const QByteArray& , void* , long* )
{
// TODO_KDAB_LINUX
return false;
}
}
@@ -0,0 +1,43 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#import <AppKit/NSEvent.h>
#include "EditorDefs.h"
#include "QtEditorApplication.h"
// AzFramework
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
// Qt
#include <QCursor>
namespace Editor
{
bool EditorQtApplication::nativeEventFilter(const QByteArray& eventType, void* message, long* result)
{
NSEvent* event = (NSEvent*)message;
if (GetIEditor()->IsInGameMode())
{
AzFramework::RawInputNotificationBusMac::Broadcast(&AzFramework::RawInputNotificationsMac::OnRawInputEvent, event);
return true;
}
if ([event type] == NSEventTypeMouseEntered)
{
// filter out mouse enter events when the widget the mouse entered into
// does not exist anymore. This happened occasionally on startup when hovering
// over the splash screen - LY-84100
if (widgetAt(QCursor::pos()) == nullptr)
{
return true;
}
}
return false;
}
} // end namespace Editor
+196
View File
@@ -0,0 +1,196 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
// AzTest
#include <AzTest/AzTest.h>
// Qt
#include <QBuffer>
static QString CreateTestString(int size)
{
QString testString;
testString.resize(size);
// generate a string with some kind of pattern, so that
// decoding is verified against something semi-realistic
QString pattern = "TestPattern";
for (int i = 0; i < size; i++)
{
testString[i] = pattern[i % pattern.length()];
}
return testString;
}
TEST(CArchive, 1ByteLengthString)
{
QString outString = CreateTestString(20); // use a test size less than 2^8, so it takes only 1 byte for the length
QBuffer memBlock;
memBlock.open(QIODevice::ReadWrite);
CArchive writeArchive(&memBlock, CArchive::store);
writeArchive << outString;
QByteArray dataBuffer = memBlock.buffer();
// buffer size after write is the 1 byte for the string length followed by the string
EXPECT_EQ(memBlock.pos(), (1 + outString.length()));
const char* data = dataBuffer.constData();
EXPECT_EQ(data[0], outString.length());
for (int i = 0; i < outString.length(); i++)
{
EXPECT_EQ(data[1 + i], outString[i]);
}
// now confirm that we can read it back out
memBlock.reset();
CArchive readArchive(&memBlock, CArchive::load);
QString inString;
readArchive >> inString;
EXPECT_EQ(inString, outString);
}
TEST(CArchive, test2ByteLengthString)
{
int testSize = 0xff + 1; // use a test size greater than 2^8, so it takes more than 1 byte for the length
QString outString = CreateTestString(testSize);
QBuffer memBlock;
memBlock.open(QIODevice::ReadWrite);
CArchive writeArchive(&memBlock, CArchive::store);
writeArchive << outString;
QByteArray dataBuffer = memBlock.buffer();
// buffer size after write is the 1 marker byte (0xff), 2 bytes for the string length followed by the string
EXPECT_EQ(memBlock.pos(), (1 + 2 + testSize));
const char* data = dataBuffer.constData();
EXPECT_EQ(data[0], '\xff');
short shortLength = 0;
memcpy(&shortLength, data + 1, 2);
EXPECT_EQ(shortLength, testSize);
for (int i = 0; i < testSize; i++)
{
EXPECT_EQ(data[3 + i], outString[i]);
}
// now confirm that we can read it back out
memBlock.reset();
CArchive readArchive(&memBlock, CArchive::load);
QString inString;
readArchive >> inString;
EXPECT_EQ(inString, outString);
}
TEST(CArchive, test4ByteLengthString)
{
int testSize = 0xffff + 1; // use a test size greater than 2^16, so it takes more than 2 bytes for the length
QString outString = CreateTestString(testSize);
QBuffer memBlock;
memBlock.open(QIODevice::ReadWrite);
CArchive writeArchive(&memBlock, CArchive::store);
writeArchive << outString;
QByteArray dataBuffer = memBlock.buffer();
// buffer size after write is the 1 marker byte (0xff), the 2 byte marker (0xffff), 4 bytes for the string length followed by the string
EXPECT_EQ(memBlock.pos(), (1 + 2 + 4 + testSize));
const char* data = dataBuffer.constData();
EXPECT_EQ(data[0], '\xff');
EXPECT_EQ(data[1], '\xff');
EXPECT_EQ(data[2], '\xff');
uint32 doubleWordLength = 0;
memcpy(&doubleWordLength, data + 3, 4);
EXPECT_EQ(doubleWordLength, testSize);
for (int i = 0; i < testSize; i++)
{
EXPECT_EQ(data[7 + i], outString[i]);
}
// now confirm that we can read it back out
memBlock.reset();
CArchive readArchive(&memBlock, CArchive::load);
QString inString;
readArchive >> inString;
EXPECT_EQ(inString, outString);
}
TEST(CArchive, testWindowsWideCharacterString1ByteLength)
{
// NOTE: CArchive only writes out Utf-8 now, so we can't actually
// use that class to encode to Windows wide character strings.
// That is why this test (unlike the non-Windows wide character tests)
// is manually creating a stream and only confirming that CArchive can decode it.
QString outString = CreateTestString(20); // use a test size smaller than 2^8, so that we only need one byte to store the length
// we have to write the 0xff marker, then the 2 byte marker to indicate it's wide characters,
// then the actual length (1 byte), then the string, with windows wide characters (2 bytes per)
QByteArray testBuffer;
testBuffer.resize(1 + 2 + 1 + (outString.length() * 2));
testBuffer[0] = '\xff';
testBuffer[1] = '\xfe';
testBuffer[2] = '\xff';
testBuffer[3] = aznumeric_cast<char>(outString.length());
memcpy(testBuffer.data() + 4, outString.utf16(), outString.length() * 2);
// now confirm that we can read it back out
QBuffer memBlock(&testBuffer);
memBlock.open(QIODevice::ReadOnly);
CArchive readArchive(&memBlock, CArchive::load);
QString inString;
readArchive >> inString;
EXPECT_EQ(inString, outString);
}
TEST(CArchive, testWindowsWideCharacterString2ByteLength)
{
// NOTE: CArchive only writes out Utf-8 now, so we can't actually
// use that class to encode to Windows wide character strings.
// That is why this test (unlike the non-Windows wide character tests)
// is manually creating a stream and only confirming that CArchive can decode it.
int testSize = 0xff + 1; // use a test size greater than 2^8, so that we need two bytes to store the length
QString outString = CreateTestString(testSize);
// we have to write the 0xff marker, then the 2 byte marker to indicate it's wide characters,
// then the marker to indicate it's more than a 1 byte length, then the actual length (2 byte),
// then the string, with windows wide characters (2 bytes per)
QByteArray testBuffer;
testBuffer.resize(1 + 2 + 1 + 2 + (testSize * 2));
testBuffer[0] = '\xff';
testBuffer[1] = '\xfe';
testBuffer[2] = '\xff';
testBuffer[3] = '\xff';
memcpy(testBuffer.data() + 4, &testSize, 2);
memcpy(testBuffer.data() + 6, outString.utf16(), testSize * 2);
// now confirm that we can read it back out
QBuffer memBlock(&testBuffer);
memBlock.open(QIODevice::ReadOnly);
CArchive readArchive(&memBlock, CArchive::load);
QString inString;
readArchive >> inString;
EXPECT_EQ(inString, outString);
}
+60
View File
@@ -0,0 +1,60 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
// AzTest
#include <AzTest/AzTest.h>
// AzCore
#include <AzCore/Memory/AllocatorScope.h>
// AzFramework
#include <AzFramework/IO/LocalFileIO.h>
#include <Mocks/ICryPakMock.h>
using ::testing::NiceMock;
class EditorCoreTestEnvironment
: public AZ::Test::ITestEnvironment
{
public:
AZ_TEST_CLASS_ALLOCATOR(EditorCoreTestEnvironment);
virtual ~EditorCoreTestEnvironment()
{
}
protected:
void SetupEnvironment() override
{
m_allocatorScope.ActivateAllocators();
m_cryPak = new NiceMock<CryPakMock>();
// Initialize the fileIO
AZ::IO::FileIOBase::SetInstance(&m_fileIO);
// Setup gEnv with the systems/mocks we will be using for the unit tests
m_stubEnv.pCryPak = m_cryPak;
m_stubEnv.pFileIO = &m_fileIO;
gEnv = &m_stubEnv;
}
void TeardownEnvironment() override
{
delete m_cryPak;
m_allocatorScope.DeactivateAllocators();
}
private:
AZ::AllocatorScope<AZ::OSAllocator, AZ::SystemAllocator, AZ::LegacyAllocator, CryStringAllocator> m_allocatorScope;
SSystemGlobalEnvironment m_stubEnv;
AZ::IO::LocalFileIO m_fileIO;
NiceMock<CryPakMock>* m_cryPak;
};
AZ_UNIT_TEST_HOOK(new EditorCoreTestEnvironment);
+25
View File
@@ -0,0 +1,25 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include <AzTest/AzTest.h>
#include "Util/PathUtil.h"
#include <CrySystemBus.h>
TEST(PathUtil, GamePathToFullPath_DoesNotBufferOverflow)
{
// There are no test assertions in this test because the purpose is just to verify that the test runs without crashing
QString pngExtension(".png");
// Create a string of lenth AZ_MAX_PATH_LEN that ends in .png
QString longStringMaxPath(AZ_MAX_PATH_LEN, 'x');
longStringMaxPath.replace(longStringMaxPath.length() - pngExtension.length(), longStringMaxPath.length(), pngExtension);
Path::GamePathToFullPath(longStringMaxPath);
QString longStringMaxPathPlusOne(AZ_MAX_PATH_LEN + 1, 'x');
longStringMaxPathPlusOne.replace(longStringMaxPathPlusOne.length() - pngExtension.length(), longStringMaxPathPlusOne.length(), pngExtension);
Path::GamePathToFullPath(longStringMaxPathPlusOne);
}