593 lines
18 KiB
C++
593 lines
18 KiB
C++
/*
|
|
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <filesystem>
|
|
|
|
#include <imgui.h>
|
|
|
|
#include <keel/media.hpp>
|
|
#include <glutils/glutils.hpp>
|
|
#include <turbine/turbine.hpp>
|
|
|
|
#include <studio/configio.hpp>
|
|
#include "clawviewer.hpp"
|
|
#include "filedialogmanager.hpp"
|
|
#include "studioui.hpp"
|
|
|
|
namespace studio {
|
|
|
|
void navigateTo(StudioContext &ctx, ox::StringParam filePath, ox::StringParam navArgs) noexcept {
|
|
ctx.ui.navigateTo(std::move(filePath), std::move(navArgs));
|
|
}
|
|
|
|
namespace ig {
|
|
extern bool s_mainWinHasFocus;
|
|
}
|
|
static ox::Vector<Module const*> modules;
|
|
|
|
void registerModule(Module const*mod) noexcept {
|
|
if (mod) {
|
|
modules.emplace_back(mod);
|
|
}
|
|
}
|
|
|
|
|
|
struct StudioConfig {
|
|
static constexpr auto TypeName = "net.drinkingtea.studio.StudioConfig";
|
|
static constexpr auto TypeVersion = 1;
|
|
ox::String projectPath;
|
|
ox::String activeTabItemName;
|
|
ox::Vector<ox::String> openFiles;
|
|
bool showProjectExplorer = true;
|
|
};
|
|
|
|
OX_MODEL_BEGIN(StudioConfig)
|
|
OX_MODEL_FIELD_RENAME(activeTabItemName, active_tab_item_name)
|
|
OX_MODEL_FIELD_RENAME(projectPath, project_path)
|
|
OX_MODEL_FIELD_RENAME(openFiles, open_files)
|
|
OX_MODEL_FIELD_RENAME(showProjectExplorer, show_project_explorer)
|
|
OX_MODEL_END()
|
|
|
|
StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexcept:
|
|
m_sctx{*this, ctx},
|
|
m_tctx{ctx},
|
|
m_projectDataDir{std::move(projectDataDir)},
|
|
m_projectExplorer{keelCtx(m_tctx)},
|
|
m_newProject{m_projectDataDir},
|
|
m_aboutPopup{m_tctx} {
|
|
turbine::setApplicationData(m_tctx, &m_sctx);
|
|
m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile);
|
|
m_projectExplorer.addDir.connect(this, &StudioUI::addDir);
|
|
m_projectExplorer.addItem.connect(this, &StudioUI::addFile);
|
|
m_projectExplorer.deleteItem.connect(this, &StudioUI::deleteFile);
|
|
m_projectExplorer.renameItem.connect(this, &StudioUI::renameFile);
|
|
m_projectExplorer.makeCopy.connect(this, &StudioUI::makeCopyDlg);
|
|
m_projectExplorer.moveDir.connect(this, &StudioUI::queueDirMove);
|
|
m_projectExplorer.moveItem.connect(this, &StudioUI::queueFileMove);
|
|
m_renameFile.moveFile.connect(this, &StudioUI::queueFileMove);
|
|
m_newProject.finished.connect(this, &StudioUI::createOpenProject);
|
|
m_newMenu.finished.connect(this, &StudioUI::openFile);
|
|
m_closeFileConfirm.response.connect(this, &StudioUI::handleCloseFileResponse);
|
|
loadModules();
|
|
// open project and files
|
|
auto const [config, err] = studio::readConfig<StudioConfig>(keelCtx(m_tctx));
|
|
m_showProjectExplorer = config.showProjectExplorer;
|
|
if (!err) {
|
|
auto const openProjErr = openProjectPath(config.projectPath);
|
|
if (!openProjErr) {
|
|
for (auto const&f: config.openFiles) {
|
|
auto const openFileErr = openFileActiveTab(f, config.activeTabItemName == f);
|
|
if (openFileErr) {
|
|
oxErrorf("\nCould not open editor for file:\n\t{}\nReason:\n\t{}\n", f, toStr(openFileErr));
|
|
continue;
|
|
}
|
|
m_activeEditor = m_editors.back().value->get();
|
|
}
|
|
}
|
|
} else {
|
|
if constexpr(!ox::defines::Debug) {
|
|
oxErrf("Could not open studio config file: {}: {}\n", err.errCode, toStr(err));
|
|
} else {
|
|
oxErrf(
|
|
"Could not open studio config file: {}: {} ({}:{})\n",
|
|
err.errCode, toStr(err), err.src.file_name(), err.src.line());
|
|
}
|
|
}
|
|
}
|
|
|
|
void StudioUI::handleKeyEvent(turbine::Key const key, bool const down) noexcept {
|
|
for (auto const p : m_popups) {
|
|
if (p->isOpen()) {
|
|
if (key == turbine::Key::Escape) {
|
|
p->close();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
if (m_activeEditor && !ImGui::IsKeyDown(ImGuiKey_ModCtrl)) {
|
|
m_activeEditor->keyStateChanged(key, down);
|
|
}
|
|
}
|
|
|
|
void StudioUI::navigateTo(ox::StringParam path, ox::StringParam navArgs) noexcept {
|
|
m_navAction.emplace(std::move(path), std::move(navArgs));
|
|
}
|
|
|
|
void StudioUI::draw() noexcept {
|
|
glutils::clearScreen();
|
|
drawMenu();
|
|
auto const&viewport = *ImGui::GetMainViewport();
|
|
ImGui::SetNextWindowPos(viewport.WorkPos);
|
|
ImGui::SetNextWindowSize(viewport.WorkSize);
|
|
ImGui::SetNextWindowViewport(viewport.ID);
|
|
auto constexpr windowFlags =
|
|
ImGuiWindowFlags_NoTitleBar |
|
|
ImGuiWindowFlags_NoResize |
|
|
ImGuiWindowFlags_NoMove |
|
|
ImGuiWindowFlags_NoScrollbar |
|
|
ImGuiWindowFlags_NoSavedSettings;
|
|
ImGui::Begin("MainWindow##Studio", nullptr, windowFlags);
|
|
{
|
|
ig::s_mainWinHasFocus = ImGui::IsWindowFocused(
|
|
ImGuiFocusedFlags_RootAndChildWindows | ImGuiFocusedFlags_NoPopupHierarchy);
|
|
if (m_showProjectExplorer) {
|
|
auto const v = ImGui::GetContentRegionAvail();
|
|
m_projectExplorer.draw(m_sctx, {300, v.y});
|
|
ImGui::SameLine();
|
|
}
|
|
drawTabBar();
|
|
for (auto const&w : m_widgets) {
|
|
w->draw(m_sctx);
|
|
}
|
|
for (auto const p : m_popups) {
|
|
p->draw(m_sctx);
|
|
}
|
|
m_closeFileConfirm.draw(m_sctx);
|
|
m_copyFilePopup.draw(m_sctx);
|
|
}
|
|
ImGui::End();
|
|
handleKeyInput();
|
|
m_taskRunner.update(m_tctx);
|
|
procFileMoves();
|
|
}
|
|
|
|
void StudioUI::drawMenu() noexcept {
|
|
if (ImGui::BeginMainMenuBar()) {
|
|
if (ImGui::BeginMenu("File")) {
|
|
if (ImGui::MenuItem("New...", "Ctrl+N", false, m_project)) {
|
|
m_newMenu.open();
|
|
}
|
|
if (ImGui::MenuItem("New Project...", "Ctrl+Shift+N")) {
|
|
m_newProject.open();
|
|
}
|
|
if (ImGui::MenuItem("Open Project...", "Ctrl+O")) {
|
|
m_taskRunner.add(*ox::make<FileDialogManager>(this, &StudioUI::openProjectPath));
|
|
}
|
|
if (ImGui::MenuItem("Save", "Ctrl+S", false, m_activeEditor && m_activeEditor->unsavedChanges())) {
|
|
m_activeEditor->save();
|
|
}
|
|
if (ImGui::MenuItem("Quit", "Ctrl+Q")) {
|
|
turbine::requestShutdown(m_tctx);
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
if (ImGui::BeginMenu("Edit")) {
|
|
auto undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
|
|
if (ImGui::MenuItem("Undo", "Ctrl+Z", false, undoStack && undoStack->canUndo())) {
|
|
oxLogError(undoStack->undo());
|
|
}
|
|
if (ImGui::MenuItem("Redo", "Ctrl+Y", false, undoStack && undoStack->canRedo())) {
|
|
oxLogError(undoStack->redo());
|
|
}
|
|
ImGui::Separator();
|
|
if (ImGui::MenuItem("Copy", "Ctrl+C", false, m_activeEditor && m_activeEditor->copyEnabled())) {
|
|
m_activeEditor->copy();
|
|
}
|
|
if (ImGui::MenuItem("Cut", "Ctrl+X", false, m_activeEditor && m_activeEditor->cutEnabled())) {
|
|
m_activeEditor->cut();
|
|
}
|
|
if (ImGui::MenuItem("Paste", "Ctrl+V", false, m_activeEditor && m_activeEditor->pasteEnabled())) {
|
|
m_activeEditor->paste();
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
if (ImGui::BeginMenu("View")) {
|
|
if (ImGui::MenuItem("Project Explorer", "Ctrl+Shift+1", m_showProjectExplorer)) {
|
|
toggleProjectExplorer();
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
if (ImGui::BeginMenu("Help")) {
|
|
if (ImGui::MenuItem("About")) {
|
|
m_aboutPopup.open();
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
ImGui::EndMainMenuBar();
|
|
}
|
|
}
|
|
|
|
void StudioUI::drawTabBar() noexcept {
|
|
auto const viewport = ImGui::GetContentRegionAvail();
|
|
ImGui::BeginChild("TabWindow##MainWindow##Studio", ImVec2(viewport.x, viewport.y));
|
|
constexpr auto tabBarFlags = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_TabListPopupButton;
|
|
if (ImGui::BeginTabBar("TabBar##TabWindow##MainWindow##Studio", tabBarFlags)) {
|
|
drawTabs();
|
|
ImGui::EndTabBar();
|
|
}
|
|
ImGui::EndChild();
|
|
}
|
|
|
|
void StudioUI::drawTabs() noexcept {
|
|
for (auto it = m_editors.begin(); it != m_editors.end();) {
|
|
auto const&e = *it;
|
|
auto open = true;
|
|
auto const unsavedChanges = e->unsavedChanges() ? ImGuiTabItemFlags_UnsavedDocument : 0;
|
|
auto const selected = m_activeEditorUpdatePending == e.get() ? ImGuiTabItemFlags_SetSelected : 0;
|
|
auto const flags = unsavedChanges | selected | ImGuiTabItemFlags_NoAssumedClosure;
|
|
if (ImGui::BeginTabItem(e->itemDisplayName().c_str(), &open, flags)) {
|
|
if (m_activeEditor != e.get()) [[unlikely]] {
|
|
m_activeEditor = e.get();
|
|
studio::editConfig<StudioConfig>(keelCtx(m_tctx), [&](StudioConfig &config) {
|
|
config.activeTabItemName = m_activeEditor->itemPath();
|
|
});
|
|
turbine::setRefreshWithin(m_tctx, 0);
|
|
} else [[likely]] {
|
|
if (m_activeEditorUpdatePending == e.get()) [[unlikely]] {
|
|
m_activeEditorUpdatePending = nullptr;
|
|
}
|
|
if (m_activeEditorOnLastDraw != e.get()) [[unlikely]] {
|
|
m_activeEditor->onActivated();
|
|
}
|
|
if (m_closeActiveTab) [[unlikely]] {
|
|
ImGui::SetTabItemClosed(e->itemDisplayName().c_str());
|
|
|
|
} else if (open) [[likely]] {
|
|
e->draw(m_sctx);
|
|
}
|
|
m_activeEditorOnLastDraw = e.get();
|
|
}
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (!open) {
|
|
if (e->unsavedChanges()) {
|
|
m_closeFileConfirm.open();
|
|
++it;
|
|
} else {
|
|
e->close();
|
|
if (m_activeEditor == (*it).get()) {
|
|
m_activeEditor = nullptr;
|
|
}
|
|
try {
|
|
OX_THROW_ERROR(m_editors.erase(it).moveTo(it));
|
|
} catch (ox::Exception const&ex) {
|
|
oxErrf("Editor tab deletion failed: {} ({}:{})\n", ex.what(), ex.src.file_name(), ex.src.line());
|
|
} catch (std::exception const&ex) {
|
|
oxErrf("Editor tab deletion failed: {}\n", ex.what());
|
|
}
|
|
}
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
if (m_closeActiveTab) [[unlikely]] {
|
|
if (m_activeEditor) {
|
|
auto const idx = find_if(
|
|
m_editors.begin(), m_editors.end(),
|
|
[this](ox::UPtr<BaseEditor> const &e) {
|
|
return m_activeEditor == e.get();
|
|
});
|
|
if (idx != m_editors.end()) {
|
|
oxLogError(m_editors.erase(idx.offset()).error);
|
|
}
|
|
m_activeEditor = nullptr;
|
|
}
|
|
m_closeActiveTab = false;
|
|
}
|
|
if (m_navAction) {
|
|
oxLogError(openFile(m_navAction->path));
|
|
m_activeEditor->navigateTo(m_navAction->args);
|
|
m_navAction.reset();
|
|
}
|
|
}
|
|
|
|
void StudioUI::loadEditorMaker(EditorMaker const&editorMaker) noexcept {
|
|
for (auto const&ext : editorMaker.fileTypes) {
|
|
m_editorMakers[ext] = editorMaker.make;
|
|
}
|
|
}
|
|
|
|
void StudioUI::loadModule(Module const&mod) noexcept {
|
|
for (auto const&editorMaker : mod.editors(m_sctx)) {
|
|
loadEditorMaker(editorMaker);
|
|
}
|
|
for (auto &im : mod.itemMakers(m_sctx)) {
|
|
m_newMenu.addItemMaker(std::move(im));
|
|
}
|
|
auto tmplts = mod.itemTemplates(m_sctx);
|
|
for (auto &t : tmplts) {
|
|
m_newMenu.installItemTemplate(t);
|
|
}
|
|
}
|
|
|
|
void StudioUI::loadModules() noexcept {
|
|
for (auto const mod : modules) {
|
|
loadModule(*mod);
|
|
}
|
|
}
|
|
|
|
void StudioUI::toggleProjectExplorer() noexcept {
|
|
m_showProjectExplorer = !m_showProjectExplorer;
|
|
editConfig<StudioConfig>(keelCtx(m_tctx), [&](StudioConfig &config) {
|
|
config.showProjectExplorer = m_showProjectExplorer;
|
|
});
|
|
}
|
|
|
|
void StudioUI::redo() noexcept {
|
|
auto const undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
|
|
if (undoStack && undoStack->canRedo()) {
|
|
oxLogError(m_activeEditor->undoStack()->redo());
|
|
}
|
|
}
|
|
|
|
void StudioUI::undo() noexcept {
|
|
auto const undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
|
|
if (undoStack && undoStack->canUndo()) {
|
|
oxLogError(m_activeEditor->undoStack()->undo());
|
|
}
|
|
}
|
|
|
|
void StudioUI::save() noexcept {
|
|
if (m_activeEditor && m_activeEditor->unsavedChanges()) {
|
|
m_activeEditor->save();
|
|
}
|
|
}
|
|
|
|
void StudioUI::handleKeyInput() noexcept {
|
|
if (ImGui::IsKeyDown(ImGuiKey_ModCtrl)) {
|
|
if (ImGui::IsKeyPressed(ImGuiKey_C)) {
|
|
if (m_activeEditor && m_activeEditor->copyEnabled()) {
|
|
m_activeEditor->copy();
|
|
}
|
|
} else if (ImGui::IsKeyPressed(ImGuiKey_N)) {
|
|
if (turbine::buttonDown(m_tctx, turbine::Key::Mod_Shift)) {
|
|
m_newProject.open();
|
|
} else {
|
|
m_newMenu.open();
|
|
}
|
|
} else if (ImGui::IsKeyPressed(ImGuiKey_O)) {
|
|
m_taskRunner.add(*ox::make<FileDialogManager>(this, &StudioUI::openProjectPath));
|
|
} else if (ImGui::IsKeyPressed(ImGuiKey_S)) {
|
|
save();
|
|
} else if (ImGui::IsKeyPressed(ImGuiKey_Q)) {
|
|
turbine::requestShutdown(m_tctx);
|
|
} else if (ImGui::IsKeyPressed(ImGuiKey_V)) {
|
|
if (m_activeEditor && m_activeEditor->pasteEnabled()) {
|
|
m_activeEditor->paste();
|
|
}
|
|
} else if (ImGui::IsKeyPressed(ImGuiKey_W)) {
|
|
if (m_activeEditor) {
|
|
if (m_activeEditor->unsavedChanges()) {
|
|
m_closeFileConfirm.open();
|
|
} else {
|
|
oxLogError(closeCurrentFile());
|
|
}
|
|
}
|
|
} else if (ImGui::IsKeyPressed(ImGuiKey_X)) {
|
|
if (m_activeEditor && m_activeEditor->cutEnabled()) {
|
|
m_activeEditor->cut();
|
|
}
|
|
} else if (ImGui::IsKeyPressed(ImGuiKey_Y)) {
|
|
auto const undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
|
|
if (undoStack) { oxLogError(undoStack->redo()); }
|
|
} else if (ImGui::IsKeyPressed(ImGuiKey_Z)) {
|
|
auto const undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
|
|
if (undoStack) { oxLogError(undoStack->undo()); }
|
|
} else if (ImGui::IsKeyPressed(ImGuiKey_1) && ImGui::IsKeyDown(ImGuiKey_ModShift)) {
|
|
toggleProjectExplorer();
|
|
} else {
|
|
if (!m_editors.empty()) {
|
|
auto const range = ox::min<size_t>(9u, m_editors.size()) - 1;
|
|
for (auto i = 0u; i < 9; ++i) {
|
|
if (ImGui::IsKeyPressed(static_cast<ImGuiKey>(static_cast<int>(ImGuiKey_1) + i))) {
|
|
m_activeEditor = m_editors[i < m_editors.size() ? i : range].get();
|
|
m_activeEditorUpdatePending = m_activeEditor;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ox::Error StudioUI::addDir(ox::StringViewCR path) noexcept {
|
|
m_newDirDialog.openPath(path);
|
|
return {};
|
|
}
|
|
|
|
ox::Error StudioUI::addFile(ox::StringViewCR path) noexcept {
|
|
m_newMenu.openPath(path);
|
|
return {};
|
|
}
|
|
|
|
ox::Error StudioUI::deleteFile(ox::StringViewCR path) noexcept {
|
|
m_deleteConfirmation.openPath(path);
|
|
return {};
|
|
}
|
|
|
|
ox::Error StudioUI::renameFile(ox::StringViewCR path) noexcept {
|
|
return m_renameFile.openPath(path);
|
|
}
|
|
|
|
ox::Error StudioUI::handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR newPath, ox::UUID const&) noexcept {
|
|
for (auto &f : m_openFiles) {
|
|
if (f == oldPath) {
|
|
f = newPath;
|
|
editConfig<StudioConfig>(keelCtx(m_sctx), [&](StudioConfig &cfg) {
|
|
auto p = find(cfg.openFiles.begin(), cfg.openFiles.end(), oldPath);
|
|
*p = newPath;
|
|
cfg.activeTabItemName = newPath;
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
// needed to keep this tab open, ImGui loses track when the name changes
|
|
if (m_activeEditor) {
|
|
m_activeEditorUpdatePending = m_activeEditor;
|
|
}
|
|
return m_projectExplorer.refreshProjectTreeModel();
|
|
}
|
|
|
|
ox::Error StudioUI::handleDeleteFile(ox::StringViewCR path) noexcept {
|
|
for (auto &e : m_editors) {
|
|
if (path == e->itemPath()) {
|
|
oxLogError(closeFile(path));
|
|
m_closeActiveTab = true;
|
|
break;
|
|
}
|
|
}
|
|
return m_projectExplorer.refreshProjectTreeModel();
|
|
}
|
|
|
|
ox::Error StudioUI::createOpenProject(ox::StringViewCR path) noexcept {
|
|
std::error_code ec;
|
|
std::filesystem::create_directories(toStdStringView(path), ec);
|
|
OX_RETURN_ERROR(ox::Error(ec.value() != 0, "Could not create project directory"));
|
|
OX_RETURN_ERROR(openProjectPath(path));
|
|
return m_project->writeTypeStore();
|
|
}
|
|
|
|
ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept {
|
|
OX_REQUIRE_M(fs, keel::loadRomFs(path.view()));
|
|
OX_RETURN_ERROR(keel::setRomFs(keelCtx(m_tctx), std::move(fs)));
|
|
OX_RETURN_ERROR(
|
|
ox::make_unique_catch<Project>(keelCtx(m_tctx), std::move(path), m_projectDataDir)
|
|
.moveTo(m_project));
|
|
m_sctx.project = m_project.get();
|
|
turbine::setWindowTitle(m_tctx, ox::sfmt("{} - {}", keelCtx(m_tctx).appName, m_project->projectPath()));
|
|
m_deleteConfirmation.deleteFile.connect(m_sctx.project, &Project::deleteItem);
|
|
m_copyFilePopup.makeCopy.connect(m_sctx.project, &Project::copyItem);
|
|
m_newDirDialog.newDir.connect(m_sctx.project, &Project::mkdir);
|
|
m_project->dirAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
|
|
m_project->fileAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
|
|
m_project->fileDeleted.connect(this, &StudioUI::handleDeleteFile);
|
|
m_project->fileMoved.connect(this, &StudioUI::handleMoveFile);
|
|
m_openFiles.clear();
|
|
m_editors.clear();
|
|
studio::editConfig<StudioConfig>(keelCtx(m_tctx), [&](StudioConfig &config) {
|
|
config.projectPath = ox::String(m_project->projectPath());
|
|
config.openFiles.clear();
|
|
});
|
|
return m_projectExplorer.refreshProjectTreeModel();
|
|
}
|
|
|
|
ox::Error StudioUI::openFile(ox::StringViewCR path) noexcept {
|
|
return openFileActiveTab(path, true);
|
|
}
|
|
|
|
ox::Error StudioUI::openFileActiveTab(ox::StringViewCR path, bool const makeActiveTab) noexcept {
|
|
if (!m_project) {
|
|
return ox::Error(1, "No project open to open a file from");
|
|
}
|
|
if (m_openFiles.contains(path)) {
|
|
for (auto &e : m_editors) {
|
|
if (makeActiveTab && e->itemPath() == path) {
|
|
m_activeEditor = e.get();
|
|
m_activeEditorUpdatePending = e.get();
|
|
break;
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
OX_REQUIRE(ext, fileExt(path));
|
|
// create Editor
|
|
BaseEditor *editor = nullptr;
|
|
auto const err = m_editorMakers.contains(ext) ?
|
|
m_editorMakers[ext](path).moveTo(editor) :
|
|
ox::makeCatch<ClawEditor>(m_sctx, path).moveTo(editor);
|
|
if (err) {
|
|
if constexpr(!ox::defines::Debug) {
|
|
oxErrf("Could not open Editor: {}\n", toStr(err));
|
|
} else {
|
|
oxErrf("Could not open Editor: {} ({}:{})\n", err.errCode, err.src.file_name(), err.src.line());
|
|
}
|
|
return err;
|
|
}
|
|
editor->closed.connect(this, &StudioUI::closeFile);
|
|
m_editors.emplace_back(editor);
|
|
m_openFiles.emplace_back(path);
|
|
if (makeActiveTab) {
|
|
m_activeEditor = m_editors.back().value->get();
|
|
m_activeEditorUpdatePending = editor;
|
|
}
|
|
// save to config
|
|
studio::editConfig<StudioConfig>(keelCtx(m_tctx), [&path](StudioConfig &config) {
|
|
if (!config.openFiles.contains(path)) {
|
|
config.openFiles.emplace_back(path);
|
|
}
|
|
});
|
|
return {};
|
|
}
|
|
|
|
ox::Error StudioUI::makeCopyDlg(ox::StringViewCR path) noexcept {
|
|
return m_copyFilePopup.open(path);
|
|
}
|
|
|
|
ox::Error StudioUI::handleCloseFileResponse(ig::PopupResponse const response) noexcept {
|
|
if (response == ig::PopupResponse::OK && m_activeEditor) {
|
|
return closeCurrentFile();
|
|
}
|
|
return {};
|
|
}
|
|
|
|
ox::Error StudioUI::closeCurrentFile() noexcept {
|
|
for (auto &e : m_editors) {
|
|
if (m_activeEditor == e.get()) {
|
|
oxLogError(closeFile(e->itemPath()));
|
|
m_closeActiveTab = true;
|
|
break;
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
ox::Error StudioUI::closeFile(ox::StringViewCR path) noexcept {
|
|
if (!m_openFiles.contains(path)) {
|
|
return {};
|
|
}
|
|
std::ignore = m_openFiles.erase(std::remove(m_openFiles.begin(), m_openFiles.end(), path));
|
|
// save to config
|
|
studio::editConfig<StudioConfig>(keelCtx(m_tctx), [&](StudioConfig &config) {
|
|
std::ignore = config.openFiles.erase(std::remove(config.openFiles.begin(), config.openFiles.end(), path));
|
|
});
|
|
return {};
|
|
}
|
|
|
|
ox::Error StudioUI::queueDirMove(ox::StringParam src, ox::StringParam dst) noexcept {
|
|
m_queuedDirMoves.emplace_back(std::move(src), std::move(dst));
|
|
return {};
|
|
}
|
|
|
|
ox::Error StudioUI::queueFileMove(ox::StringParam src, ox::StringParam dst) noexcept {
|
|
m_queuedMoves.emplace_back(std::move(src), std::move(dst));
|
|
return {};
|
|
}
|
|
|
|
void StudioUI::procFileMoves() noexcept {
|
|
for (auto const &m : m_queuedMoves) {
|
|
if (!m_project->exists(m.b)) {
|
|
oxLogError(m_project->moveItem(m.a, m.b));
|
|
}
|
|
}
|
|
m_queuedMoves.clear();
|
|
for (auto const &m : m_queuedDirMoves) {
|
|
if (!m_project->exists(m.b)) {
|
|
oxLogError(m_project->moveDir(m.a, m.b));
|
|
}
|
|
}
|
|
m_queuedDirMoves.clear();
|
|
}
|
|
|
|
}
|