mirror of https://github.com/PCSX2/pcsx2.git
872 lines
24 KiB
C++
872 lines
24 KiB
C++
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#include "DockManager.h"
|
|
|
|
#include "Debugger/DebuggerView.h"
|
|
#include "Debugger/DebuggerWindow.h"
|
|
#include "Debugger/Docking/DockTables.h"
|
|
#include "Debugger/Docking/DockViews.h"
|
|
#include "Debugger/Docking/DropIndicators.h"
|
|
#include "Debugger/Docking/LayoutEditorDialog.h"
|
|
#include "Debugger/Docking/NoLayoutsWidget.h"
|
|
|
|
#include "common/Assertions.h"
|
|
#include "common/FileSystem.h"
|
|
#include "common/StringUtil.h"
|
|
#include "common/Path.h"
|
|
|
|
#include <kddockwidgets/Config.h>
|
|
#include <kddockwidgets/core/Group.h>
|
|
#include <kddockwidgets/core/Stack.h>
|
|
#include <kddockwidgets/core/indicators/SegmentedDropIndicatorOverlay.h>
|
|
#include <kddockwidgets/qtwidgets/Stack.h>
|
|
|
|
#include <QtCore/QTimer>
|
|
#include <QtCore/QtTranslation>
|
|
#include <QtWidgets/QMessageBox>
|
|
#include <QtWidgets/QProxyStyle>
|
|
#include <QtWidgets/QStyleFactory>
|
|
|
|
DockManager::DockManager(QObject* parent)
|
|
: QObject(parent)
|
|
{
|
|
QTimer* autosave_timer = new QTimer(this);
|
|
connect(autosave_timer, &QTimer::timeout, this, &DockManager::saveCurrentLayout);
|
|
autosave_timer->start(60 * 1000);
|
|
}
|
|
|
|
void DockManager::configureDockingSystem()
|
|
{
|
|
std::string indicator_style = Host::GetBaseStringSettingValue(
|
|
"Debugger/UserInterface", "DropIndicatorStyle", "Classic");
|
|
|
|
if (indicator_style == "Segmented" || indicator_style == "Minimalistic")
|
|
{
|
|
KDDockWidgets::Core::ViewFactory::s_dropIndicatorType = KDDockWidgets::DropIndicatorType::Segmented;
|
|
DockSegmentedDropIndicatorOverlay::s_indicator_style = indicator_style;
|
|
}
|
|
else
|
|
{
|
|
KDDockWidgets::Core::ViewFactory::s_dropIndicatorType = KDDockWidgets::DropIndicatorType::Classic;
|
|
}
|
|
|
|
static bool done = false;
|
|
if (done)
|
|
return;
|
|
|
|
KDDockWidgets::initFrontend(KDDockWidgets::FrontendType::QtWidgets);
|
|
|
|
KDDockWidgets::Config& config = KDDockWidgets::Config::self();
|
|
|
|
config.setFlags(
|
|
KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible |
|
|
KDDockWidgets::Config::Flag_AlwaysShowTabs |
|
|
KDDockWidgets::Config::Flag_AllowReorderTabs |
|
|
KDDockWidgets::Config::Flag_TitleBarIsFocusable);
|
|
|
|
// We set this flag regardless of whether or not the windowing system
|
|
// supports compositing since it's only used by the built-in docking
|
|
// indicator, and we only fall back to that if compositing is disabled.
|
|
config.setInternalFlags(KDDockWidgets::Config::InternalFlag_DisableTranslucency);
|
|
|
|
config.setDockWidgetFactoryFunc(&DockManager::dockWidgetFactory);
|
|
config.setViewFactory(new DockViewFactory());
|
|
config.setDragAboutToStartFunc(&DockManager::dragAboutToStart);
|
|
config.setStartDragDistance(std::max(QApplication::startDragDistance(), 32));
|
|
|
|
done = true;
|
|
}
|
|
|
|
bool DockManager::deleteLayout(DockLayout::Index layout_index)
|
|
{
|
|
pxAssertRel(layout_index != DockLayout::INVALID_INDEX,
|
|
"DockManager::deleteLayout called with INVALID_INDEX.");
|
|
|
|
if (layout_index == m_current_layout)
|
|
{
|
|
DockLayout::Index other_layout = DockLayout::INVALID_INDEX;
|
|
if (layout_index + 1 < m_layouts.size())
|
|
other_layout = layout_index + 1;
|
|
else if (layout_index > 0)
|
|
other_layout = layout_index - 1;
|
|
|
|
switchToLayout(other_layout);
|
|
}
|
|
|
|
m_layouts.at(layout_index).deleteFile();
|
|
m_layouts.erase(m_layouts.begin() + layout_index);
|
|
|
|
// All the layouts after the one being deleted have been shifted over by
|
|
// one, so adjust the current layout index accordingly.
|
|
if (m_current_layout > layout_index && m_current_layout != DockLayout::INVALID_INDEX)
|
|
m_current_layout--;
|
|
|
|
if (m_layouts.empty() && g_debugger_window)
|
|
{
|
|
NoLayoutsWidget* widget = new NoLayoutsWidget;
|
|
connect(widget->createDefaultLayoutsButton(), &QPushButton::clicked, this, &DockManager::resetAllLayouts);
|
|
|
|
KDDockWidgets::QtWidgets::DockWidget* dock = new KDDockWidgets::QtWidgets::DockWidget("placeholder");
|
|
dock->setTitle(tr("No Layouts"));
|
|
dock->setWidget(widget);
|
|
g_debugger_window->addDockWidget(dock, KDDockWidgets::Location_OnTop);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void DockManager::switchToLayout(DockLayout::Index layout_index, bool blink_tab)
|
|
{
|
|
if (layout_index != m_current_layout)
|
|
{
|
|
if (m_current_layout != DockLayout::INVALID_INDEX)
|
|
{
|
|
DockLayout& layout = m_layouts.at(m_current_layout);
|
|
layout.freeze();
|
|
layout.save(m_current_layout);
|
|
}
|
|
|
|
// Clear out the existing positions of toolbars so they don't affect
|
|
// where new toolbars appear for other layouts.
|
|
if (g_debugger_window)
|
|
g_debugger_window->clearToolBarState();
|
|
|
|
updateToolBarLockState();
|
|
|
|
m_current_layout = layout_index;
|
|
|
|
if (m_current_layout != DockLayout::INVALID_INDEX)
|
|
{
|
|
DockLayout& layout = m_layouts.at(m_current_layout);
|
|
layout.thaw();
|
|
|
|
int tab_index = static_cast<int>(layout_index);
|
|
if (m_menu_bar && tab_index >= 0)
|
|
m_menu_bar->onCurrentLayoutChanged(layout_index);
|
|
}
|
|
}
|
|
|
|
if (blink_tab)
|
|
m_menu_bar->startBlink(m_current_layout);
|
|
}
|
|
|
|
bool DockManager::switchToLayoutWithCPU(BreakPointCpu cpu, bool blink_tab)
|
|
{
|
|
// Don't interrupt the user if the current layout already has the right CPU.
|
|
if (m_current_layout != DockLayout::INVALID_INDEX && m_layouts.at(m_current_layout).cpu() == cpu)
|
|
{
|
|
switchToLayout(m_current_layout, blink_tab);
|
|
return true;
|
|
}
|
|
|
|
for (DockLayout::Index i = 0; i < m_layouts.size(); i++)
|
|
{
|
|
if (m_layouts[i].cpu() == cpu)
|
|
{
|
|
switchToLayout(i, blink_tab);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void DockManager::loadLayouts()
|
|
{
|
|
m_layouts.clear();
|
|
|
|
// Load the layouts.
|
|
FileSystem::FindResultsArray files;
|
|
FileSystem::FindFiles(
|
|
EmuFolders::DebuggerLayouts.c_str(),
|
|
"*.json",
|
|
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES,
|
|
&files);
|
|
|
|
bool needs_reset = false;
|
|
std::vector<DockLayout::Index> indices_last_session;
|
|
|
|
for (const FILESYSTEM_FIND_DATA& ffd : files)
|
|
{
|
|
DockLayout::LoadResult result;
|
|
DockLayout::Index index_last_session = DockLayout::INVALID_INDEX;
|
|
DockLayout::Index index =
|
|
createLayout(ffd.FileName, result, index_last_session);
|
|
|
|
DockLayout& layout = m_layouts.at(index);
|
|
|
|
// Try to make sure the layout has a unique name.
|
|
const QString& name = layout.name();
|
|
QString new_name = name;
|
|
if (result == DockLayout::SUCCESS || result == DockLayout::DEFAULT_LAYOUT_HASH_MISMATCH)
|
|
{
|
|
for (int i = 2; hasNameConflict(new_name, index) && i < 100; i++)
|
|
{
|
|
if (i == 99)
|
|
{
|
|
result = DockLayout::CONFLICTING_NAME;
|
|
break;
|
|
}
|
|
|
|
new_name = tr("%1 #%2").arg(name).arg(i);
|
|
}
|
|
}
|
|
|
|
needs_reset |= result != DockLayout::SUCCESS;
|
|
|
|
if (result != DockLayout::SUCCESS && result != DockLayout::DEFAULT_LAYOUT_HASH_MISMATCH)
|
|
{
|
|
deleteLayout(index);
|
|
|
|
// Only delete the file if we've identified that it's actually a
|
|
// layout file.
|
|
if (result == DockLayout::MAJOR_VERSION_MISMATCH || result == DockLayout::CONFLICTING_NAME)
|
|
FileSystem::DeleteFilePath(ffd.FileName.c_str());
|
|
|
|
continue;
|
|
}
|
|
|
|
if (new_name != name)
|
|
{
|
|
layout.setName(new_name);
|
|
layout.save(index);
|
|
}
|
|
|
|
indices_last_session.emplace_back(index_last_session);
|
|
}
|
|
|
|
// Make sure the layouts remain in the same order they were in previously.
|
|
std::vector<size_t> layout_indices;
|
|
for (size_t i = 0; i < m_layouts.size(); i++)
|
|
layout_indices.emplace_back(i);
|
|
|
|
std::sort(layout_indices.begin(), layout_indices.end(),
|
|
[&indices_last_session](size_t lhs, size_t rhs) {
|
|
DockLayout::Index lhs_index_last_session = indices_last_session.at(lhs);
|
|
DockLayout::Index rhs_index_last_session = indices_last_session.at(rhs);
|
|
return lhs_index_last_session < rhs_index_last_session;
|
|
});
|
|
|
|
bool order_changed = false;
|
|
std::vector<DockLayout> sorted_layouts;
|
|
for (size_t i = 0; i < layout_indices.size(); i++)
|
|
{
|
|
if (i != indices_last_session[layout_indices[i]])
|
|
order_changed = true;
|
|
|
|
sorted_layouts.emplace_back(std::move(m_layouts[layout_indices[i]]));
|
|
}
|
|
|
|
m_layouts = std::move(sorted_layouts);
|
|
|
|
if (m_layouts.empty() || needs_reset)
|
|
resetDefaultLayouts();
|
|
else
|
|
updateLayoutSwitcher();
|
|
|
|
// Make sure the indices in the existing layout files match up with the
|
|
// indices of any new layouts.
|
|
if (order_changed)
|
|
saveLayouts();
|
|
}
|
|
|
|
bool DockManager::saveLayouts()
|
|
{
|
|
for (DockLayout::Index i = 0; i < m_layouts.size(); i++)
|
|
if (!m_layouts[i].save(i))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DockManager::saveCurrentLayout()
|
|
{
|
|
if (m_current_layout == DockLayout::INVALID_INDEX)
|
|
return true;
|
|
|
|
return m_layouts.at(m_current_layout).save(m_current_layout);
|
|
}
|
|
|
|
void DockManager::resetAllLayouts()
|
|
{
|
|
switchToLayout(DockLayout::INVALID_INDEX);
|
|
|
|
for (DockLayout& layout : m_layouts)
|
|
layout.deleteFile();
|
|
|
|
m_layouts.clear();
|
|
|
|
for (const DockTables::DefaultDockLayout& layout : DockTables::DEFAULT_DOCK_LAYOUTS)
|
|
{
|
|
QString name = QCoreApplication::translate("DebuggerLayout", layout.name.c_str());
|
|
createLayout(name, layout.cpu, true, layout.name);
|
|
}
|
|
|
|
switchToLayout(0);
|
|
updateLayoutSwitcher();
|
|
saveLayouts();
|
|
}
|
|
|
|
void DockManager::resetDefaultLayouts()
|
|
{
|
|
switchToLayout(DockLayout::INVALID_INDEX);
|
|
|
|
std::vector<DockLayout> old_layouts = std::move(m_layouts);
|
|
m_layouts = std::vector<DockLayout>();
|
|
|
|
for (const DockTables::DefaultDockLayout& layout : DockTables::DEFAULT_DOCK_LAYOUTS)
|
|
{
|
|
QString name = QCoreApplication::translate("DebuggerLayout", layout.name.c_str());
|
|
createLayout(name, layout.cpu, true, layout.name);
|
|
}
|
|
|
|
for (DockLayout& layout : old_layouts)
|
|
if (!layout.isDefault())
|
|
m_layouts.emplace_back(std::move(layout));
|
|
else
|
|
layout.deleteFile();
|
|
|
|
switchToLayout(0);
|
|
updateLayoutSwitcher();
|
|
saveLayouts();
|
|
}
|
|
|
|
void DockManager::createToolsMenu(QMenu* menu)
|
|
{
|
|
menu->clear();
|
|
|
|
if (m_current_layout == DockLayout::INVALID_INDEX || !g_debugger_window)
|
|
return;
|
|
|
|
for (QToolBar* widget : g_debugger_window->findChildren<QToolBar*>())
|
|
{
|
|
QAction* action = menu->addAction(widget->windowTitle());
|
|
action->setText(widget->windowTitle());
|
|
action->setCheckable(true);
|
|
action->setChecked(widget->isVisible());
|
|
connect(action, &QAction::triggered, this, [widget]() {
|
|
widget->setVisible(!widget->isVisible());
|
|
});
|
|
menu->addAction(action);
|
|
}
|
|
}
|
|
|
|
void DockManager::createWindowsMenu(QMenu* menu)
|
|
{
|
|
menu->clear();
|
|
|
|
if (m_current_layout == DockLayout::INVALID_INDEX)
|
|
return;
|
|
|
|
DockLayout& layout = m_layouts.at(m_current_layout);
|
|
|
|
// Create a menu that allows for multiple dock widgets of the same type to
|
|
// be opened.
|
|
QMenu* add_another_menu = menu->addMenu(tr("Add Another..."));
|
|
|
|
std::vector<DebuggerView*> add_another_widgets;
|
|
std::set<std::string> add_another_types;
|
|
for (const auto& [unique_name, widget] : layout.debuggerViews())
|
|
{
|
|
std::string type = widget->metaObject()->className();
|
|
|
|
if (widget->supportsMultipleInstances() && !add_another_types.contains(type))
|
|
{
|
|
add_another_widgets.emplace_back(widget);
|
|
add_another_types.emplace(type);
|
|
}
|
|
}
|
|
|
|
std::sort(add_another_widgets.begin(), add_another_widgets.end(),
|
|
[](const DebuggerView* lhs, const DebuggerView* rhs) {
|
|
if (lhs->displayNameWithoutSuffix() == rhs->displayNameWithoutSuffix())
|
|
return lhs->displayNameSuffixNumber() < rhs->displayNameSuffixNumber();
|
|
|
|
return lhs->displayNameWithoutSuffix() < rhs->displayNameWithoutSuffix();
|
|
});
|
|
|
|
for (DebuggerView* widget : add_another_widgets)
|
|
{
|
|
const char* type = widget->metaObject()->className();
|
|
|
|
const auto description_iterator = DockTables::DEBUGGER_VIEWS.find(type);
|
|
pxAssert(description_iterator != DockTables::DEBUGGER_VIEWS.end());
|
|
|
|
QAction* action = add_another_menu->addAction(description_iterator->second.display_name);
|
|
connect(action, &QAction::triggered, this, [this, type]() {
|
|
if (m_current_layout == DockLayout::INVALID_INDEX)
|
|
return;
|
|
|
|
m_layouts.at(m_current_layout).createDebuggerView(type);
|
|
});
|
|
}
|
|
|
|
if (add_another_widgets.empty())
|
|
add_another_menu->setDisabled(true);
|
|
|
|
menu->addSeparator();
|
|
|
|
struct DebuggerViewToggle
|
|
{
|
|
QString display_name;
|
|
std::optional<int> suffix_number;
|
|
QAction* action;
|
|
};
|
|
|
|
std::vector<DebuggerViewToggle> toggles;
|
|
std::set<std::string> toggle_types;
|
|
|
|
// Create a menu item for each open debugger view.
|
|
for (const auto& [unique_name, widget] : layout.debuggerViews())
|
|
{
|
|
QAction* action = new QAction(menu);
|
|
action->setText(widget->displayName());
|
|
action->setCheckable(true);
|
|
action->setChecked(true);
|
|
connect(action, &QAction::triggered, this, [this, unique_name]() {
|
|
if (m_current_layout == DockLayout::INVALID_INDEX)
|
|
return;
|
|
|
|
m_layouts.at(m_current_layout).destroyDebuggerView(unique_name);
|
|
});
|
|
|
|
DebuggerViewToggle& toggle = toggles.emplace_back();
|
|
toggle.display_name = widget->displayNameWithoutSuffix();
|
|
toggle.suffix_number = widget->displayNameSuffixNumber();
|
|
toggle.action = action;
|
|
|
|
toggle_types.emplace(widget->metaObject()->className());
|
|
}
|
|
|
|
// Create menu items to open debugger views without any open instances.
|
|
for (const auto& [type, desc] : DockTables::DEBUGGER_VIEWS)
|
|
{
|
|
if (!toggle_types.contains(type))
|
|
{
|
|
QString display_name = QCoreApplication::translate("DebuggerView", desc.display_name);
|
|
|
|
QAction* action = new QAction(menu);
|
|
action->setText(display_name);
|
|
action->setCheckable(true);
|
|
action->setChecked(false);
|
|
connect(action, &QAction::triggered, this, [this, type]() {
|
|
if (m_current_layout == DockLayout::INVALID_INDEX)
|
|
return;
|
|
|
|
m_layouts.at(m_current_layout).createDebuggerView(type);
|
|
});
|
|
|
|
DebuggerViewToggle& toggle = toggles.emplace_back();
|
|
toggle.display_name = display_name;
|
|
toggle.suffix_number = std::nullopt;
|
|
toggle.action = action;
|
|
}
|
|
}
|
|
|
|
std::sort(toggles.begin(), toggles.end(),
|
|
[](const DebuggerViewToggle& lhs, const DebuggerViewToggle& rhs) {
|
|
if (lhs.display_name == rhs.display_name)
|
|
return lhs.suffix_number < rhs.suffix_number;
|
|
|
|
return lhs.display_name < rhs.display_name;
|
|
});
|
|
|
|
for (const DebuggerViewToggle& toggle : toggles)
|
|
menu->addAction(toggle.action);
|
|
}
|
|
|
|
QWidget* DockManager::createMenuBar(QWidget* original_menu_bar)
|
|
{
|
|
pxAssert(!m_menu_bar);
|
|
|
|
m_menu_bar = new DockMenuBar(original_menu_bar);
|
|
|
|
connect(m_menu_bar, &DockMenuBar::currentLayoutChanged, this, [this](DockLayout::Index layout_index) {
|
|
if (layout_index >= m_layouts.size())
|
|
return;
|
|
|
|
switchToLayout(layout_index);
|
|
});
|
|
connect(m_menu_bar, &DockMenuBar::newButtonClicked, this, &DockManager::newLayoutClicked);
|
|
connect(m_menu_bar, &DockMenuBar::layoutMoved, this, &DockManager::layoutSwitcherTabMoved);
|
|
connect(m_menu_bar, &DockMenuBar::lockButtonToggled, this, &DockManager::setLayoutLockedAndSaveSetting);
|
|
connect(m_menu_bar, &DockMenuBar::layoutSwitcherContextMenuRequested,
|
|
this, &DockManager::openLayoutSwitcherContextMenu);
|
|
|
|
updateLayoutSwitcher();
|
|
|
|
bool layout_locked = Host::GetBaseBoolSettingValue("Debugger/UserInterface", "LayoutLocked", true);
|
|
setLayoutLocked(layout_locked, false);
|
|
|
|
return m_menu_bar;
|
|
}
|
|
|
|
void DockManager::updateLayoutSwitcher()
|
|
{
|
|
if (m_menu_bar)
|
|
m_menu_bar->updateLayoutSwitcher(m_current_layout, m_layouts);
|
|
}
|
|
|
|
void DockManager::newLayoutClicked()
|
|
{
|
|
// The plus button has just been made the current tab, so set it back to the
|
|
// one corresponding to the current layout again.
|
|
m_menu_bar->onCurrentLayoutChanged(m_current_layout);
|
|
|
|
auto name_validator = [this](const QString& name) {
|
|
return !hasNameConflict(name, DockLayout::INVALID_INDEX);
|
|
};
|
|
|
|
bool can_clone_current_layout = m_current_layout != DockLayout::INVALID_INDEX;
|
|
|
|
QPointer<LayoutEditorDialog> dialog = new LayoutEditorDialog(
|
|
name_validator, can_clone_current_layout, g_debugger_window);
|
|
|
|
if (dialog->exec() == QDialog::Accepted && name_validator(dialog->name()))
|
|
{
|
|
DockLayout::Index new_layout = DockLayout::INVALID_INDEX;
|
|
|
|
const auto [mode, index] = dialog->initialState();
|
|
switch (mode)
|
|
{
|
|
case LayoutEditorDialog::DEFAULT_LAYOUT:
|
|
{
|
|
const DockTables::DefaultDockLayout& default_layout = DockTables::DEFAULT_DOCK_LAYOUTS.at(index);
|
|
new_layout = createLayout(dialog->name(), dialog->cpu(), false, default_layout.name);
|
|
break;
|
|
}
|
|
case LayoutEditorDialog::BLANK_LAYOUT:
|
|
{
|
|
new_layout = createLayout(dialog->name(), dialog->cpu(), false);
|
|
break;
|
|
}
|
|
case LayoutEditorDialog::CLONE_LAYOUT:
|
|
{
|
|
if (m_current_layout == DockLayout::INVALID_INDEX)
|
|
break;
|
|
|
|
DockLayout::Index old_layout = m_current_layout;
|
|
|
|
// Freeze the current layout so we can copy the geometry.
|
|
switchToLayout(DockLayout::INVALID_INDEX);
|
|
|
|
new_layout = createLayout(dialog->name(), dialog->cpu(), false, m_layouts.at(old_layout));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (new_layout != DockLayout::INVALID_INDEX)
|
|
{
|
|
updateLayoutSwitcher();
|
|
switchToLayout(new_layout);
|
|
}
|
|
}
|
|
|
|
delete dialog.get();
|
|
}
|
|
|
|
void DockManager::openLayoutSwitcherContextMenu(const QPoint& pos, QTabBar* layout_switcher)
|
|
{
|
|
DockLayout::Index layout_index = static_cast<DockLayout::Index>(layout_switcher->tabAt(pos));
|
|
if (layout_index >= m_layouts.size())
|
|
return;
|
|
|
|
DockLayout& layout = m_layouts[layout_index];
|
|
|
|
QMenu* menu = new QMenu(layout_switcher);
|
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
QAction* edit_action = menu->addAction(tr("Edit Layout"));
|
|
connect(edit_action, &QAction::triggered, [this, layout_index]() {
|
|
editLayoutClicked(layout_index);
|
|
});
|
|
|
|
QAction* reset_action = menu->addAction(tr("Reset Layout"));
|
|
reset_action->setEnabled(layout.canReset());
|
|
reset_action->connect(reset_action, &QAction::triggered, [this, layout_index]() {
|
|
resetLayoutClicked(layout_index);
|
|
});
|
|
|
|
QAction* delete_action = menu->addAction(tr("Delete Layout"));
|
|
connect(delete_action, &QAction::triggered, [this, layout_index]() {
|
|
deleteLayoutClicked(layout_index);
|
|
});
|
|
|
|
menu->popup(layout_switcher->mapToGlobal(pos));
|
|
}
|
|
|
|
void DockManager::editLayoutClicked(DockLayout::Index layout_index)
|
|
{
|
|
if (layout_index >= m_layouts.size())
|
|
return;
|
|
|
|
DockLayout& layout = m_layouts[layout_index];
|
|
|
|
auto name_validator = [this, layout_index](const QString& name) {
|
|
return !hasNameConflict(name, layout_index);
|
|
};
|
|
|
|
QPointer<LayoutEditorDialog> dialog = new LayoutEditorDialog(
|
|
layout.name(), layout.cpu(), name_validator, g_debugger_window);
|
|
|
|
if (dialog->exec() != QDialog::Accepted || !name_validator(dialog->name()))
|
|
return;
|
|
|
|
layout.setName(dialog->name());
|
|
layout.setCpu(dialog->cpu());
|
|
|
|
layout.save(layout_index);
|
|
|
|
delete dialog.get();
|
|
|
|
updateLayoutSwitcher();
|
|
}
|
|
|
|
void DockManager::resetLayoutClicked(DockLayout::Index layout_index)
|
|
{
|
|
if (layout_index >= m_layouts.size())
|
|
return;
|
|
|
|
DockLayout& layout = m_layouts[layout_index];
|
|
if (!layout.canReset())
|
|
return;
|
|
|
|
QString text = tr("Are you sure you want to reset layout '%1'?").arg(layout.name());
|
|
if (QMessageBox::question(g_debugger_window, tr("Confirmation"), text) != QMessageBox::Yes)
|
|
return;
|
|
|
|
bool current_layout = layout_index == m_current_layout;
|
|
|
|
if (current_layout)
|
|
switchToLayout(DockLayout::INVALID_INDEX);
|
|
|
|
layout.reset();
|
|
layout.save(layout_index);
|
|
|
|
if (current_layout)
|
|
switchToLayout(layout_index);
|
|
}
|
|
|
|
void DockManager::deleteLayoutClicked(DockLayout::Index layout_index)
|
|
{
|
|
if (layout_index >= m_layouts.size())
|
|
return;
|
|
|
|
DockLayout& layout = m_layouts[layout_index];
|
|
|
|
QString text = tr("Are you sure you want to delete layout '%1'?").arg(layout.name());
|
|
if (QMessageBox::question(g_debugger_window, tr("Confirmation"), text) != QMessageBox::Yes)
|
|
return;
|
|
|
|
deleteLayout(layout_index);
|
|
updateLayoutSwitcher();
|
|
}
|
|
|
|
void DockManager::layoutSwitcherTabMoved(DockLayout::Index from_index, DockLayout::Index to_index)
|
|
{
|
|
if (from_index >= m_layouts.size() || to_index >= m_layouts.size())
|
|
{
|
|
// This happens when the user tries to move a layout to the right of the
|
|
// plus button.
|
|
updateLayoutSwitcher();
|
|
return;
|
|
}
|
|
|
|
DockLayout& from_layout = m_layouts[from_index];
|
|
DockLayout& to_layout = m_layouts[to_index];
|
|
|
|
std::swap(from_layout, to_layout);
|
|
|
|
from_layout.save(from_index);
|
|
to_layout.save(to_index);
|
|
|
|
if (from_index == m_current_layout)
|
|
m_current_layout = to_index;
|
|
else if (to_index == m_current_layout)
|
|
m_current_layout = from_index;
|
|
}
|
|
|
|
bool DockManager::hasNameConflict(const QString& name, DockLayout::Index layout_index)
|
|
{
|
|
std::string safe_name = Path::SanitizeFileName(name.toStdString());
|
|
for (DockLayout::Index i = 0; i < m_layouts.size(); i++)
|
|
{
|
|
std::string other_safe_name = Path::SanitizeFileName(m_layouts[i].name().toStdString());
|
|
if (i != layout_index && StringUtil::compareNoCase(other_safe_name, safe_name))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void DockManager::updateDockWidgetTitles()
|
|
{
|
|
if (m_current_layout == DockLayout::INVALID_INDEX)
|
|
return;
|
|
|
|
m_layouts.at(m_current_layout).updateDockWidgetTitles();
|
|
}
|
|
|
|
const std::map<QString, QPointer<DebuggerView>>& DockManager::debuggerViews()
|
|
{
|
|
static std::map<QString, QPointer<DebuggerView>> dummy;
|
|
if (m_current_layout == DockLayout::INVALID_INDEX)
|
|
return dummy;
|
|
|
|
return m_layouts.at(m_current_layout).debuggerViews();
|
|
}
|
|
|
|
size_t DockManager::countDebuggerViewsOfType(const char* type)
|
|
{
|
|
if (m_current_layout == DockLayout::INVALID_INDEX)
|
|
return 0;
|
|
|
|
return m_layouts.at(m_current_layout).countDebuggerViewsOfType(type);
|
|
}
|
|
|
|
void DockManager::recreateDebuggerView(const QString& unique_name)
|
|
{
|
|
if (m_current_layout == DockLayout::INVALID_INDEX)
|
|
return;
|
|
|
|
m_layouts.at(m_current_layout).recreateDebuggerView(unique_name);
|
|
}
|
|
|
|
void DockManager::destroyDebuggerView(const QString& unique_name)
|
|
{
|
|
if (m_current_layout == DockLayout::INVALID_INDEX)
|
|
return;
|
|
|
|
m_layouts.at(m_current_layout).destroyDebuggerView(unique_name);
|
|
}
|
|
|
|
void DockManager::setPrimaryDebuggerView(DebuggerView* widget, bool is_primary)
|
|
{
|
|
if (m_current_layout == DockLayout::INVALID_INDEX)
|
|
return;
|
|
|
|
m_layouts.at(m_current_layout).setPrimaryDebuggerView(widget, is_primary);
|
|
}
|
|
|
|
void DockManager::switchToDebuggerView(DebuggerView* widget)
|
|
{
|
|
if (m_current_layout == DockLayout::INVALID_INDEX)
|
|
return;
|
|
|
|
for (const auto& [unique_name, test_widget] : m_layouts.at(m_current_layout).debuggerViews())
|
|
{
|
|
if (widget == test_widget)
|
|
{
|
|
auto [controller, view] = DockUtils::dockWidgetFromName(unique_name);
|
|
controller->setAsCurrentTab();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DockManager::updateTheme()
|
|
{
|
|
if (m_menu_bar)
|
|
m_menu_bar->updateTheme();
|
|
|
|
for (DockLayout& layout : m_layouts)
|
|
for (const auto& [unique_name, widget] : layout.debuggerViews())
|
|
widget->updateStyleSheet();
|
|
|
|
// KDDockWidgets::QtWidgets::TabBar sets its own style to a subclass of
|
|
// QProxyStyle in its constructor, so we need to update that here.
|
|
for (KDDockWidgets::Core::Group* group : KDDockWidgets::DockRegistry::self()->groups())
|
|
{
|
|
auto tab_bar = static_cast<KDDockWidgets::QtWidgets::TabBar*>(group->tabBar()->view());
|
|
if (QProxyStyle* style = qobject_cast<QProxyStyle*>(tab_bar->style()))
|
|
style->setBaseStyle(QStyleFactory::create(qApp->style()->name()));
|
|
}
|
|
}
|
|
|
|
bool DockManager::isLayoutLocked()
|
|
{
|
|
return m_layout_locked;
|
|
}
|
|
|
|
void DockManager::setLayoutLockedAndSaveSetting(bool locked)
|
|
{
|
|
setLayoutLocked(locked, true);
|
|
}
|
|
|
|
void DockManager::setLayoutLocked(bool locked, bool save_setting)
|
|
{
|
|
m_layout_locked = locked;
|
|
|
|
if (m_menu_bar)
|
|
m_menu_bar->onLockStateChanged(locked);
|
|
|
|
updateToolBarLockState();
|
|
|
|
for (KDDockWidgets::Core::Group* group : KDDockWidgets::DockRegistry::self()->groups())
|
|
{
|
|
auto stack = static_cast<KDDockWidgets::QtWidgets::Stack*>(group->stack()->view());
|
|
stack->setTabsClosable(!m_layout_locked);
|
|
|
|
// HACK: Make sure the sizes of the tabs get updated.
|
|
if (stack->tabBar()->count() > 0)
|
|
stack->tabBar()->setTabText(0, stack->tabBar()->tabText(0));
|
|
}
|
|
|
|
if (save_setting)
|
|
{
|
|
Host::SetBaseBoolSettingValue("Debugger/UserInterface", "LayoutLocked", m_layout_locked);
|
|
Host::CommitBaseSettingChanges();
|
|
}
|
|
}
|
|
|
|
void DockManager::updateToolBarLockState()
|
|
{
|
|
if (!g_debugger_window)
|
|
return;
|
|
|
|
for (QToolBar* toolbar : g_debugger_window->findChildren<QToolBar*>())
|
|
toolbar->setMovable(!m_layout_locked || toolbar->isFloating());
|
|
}
|
|
|
|
std::optional<BreakPointCpu> DockManager::cpu()
|
|
{
|
|
if (m_current_layout == DockLayout::INVALID_INDEX)
|
|
return std::nullopt;
|
|
|
|
return m_layouts.at(m_current_layout).cpu();
|
|
}
|
|
|
|
KDDockWidgets::Core::DockWidget* DockManager::dockWidgetFactory(const QString& name)
|
|
{
|
|
if (!g_debugger_window)
|
|
return nullptr;
|
|
|
|
DockManager& manager = g_debugger_window->dockManager();
|
|
if (manager.m_current_layout == DockLayout::INVALID_INDEX)
|
|
return nullptr;
|
|
|
|
return manager.m_layouts.at(manager.m_current_layout).createDockWidget(name);
|
|
}
|
|
|
|
bool DockManager::dragAboutToStart(KDDockWidgets::Core::Draggable* draggable)
|
|
{
|
|
bool locked = true;
|
|
if (g_debugger_window)
|
|
locked = g_debugger_window->dockManager().isLayoutLocked();
|
|
|
|
KDDockWidgets::Config::self().setDropIndicatorsInhibited(locked);
|
|
|
|
if (draggable->isInProgrammaticDrag())
|
|
return true;
|
|
|
|
// Allow floating windows to be dragged around even if the layout is locked.
|
|
if (draggable->isWindow())
|
|
return true;
|
|
|
|
if (!g_debugger_window)
|
|
return false;
|
|
|
|
return !locked;
|
|
}
|