mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
691 lines
21 KiB
C++
691 lines
21 KiB
C++
#include "globals.h"
|
|
#include "lib/fluxmap.h"
|
|
#include "lib/vfs/vfs.h"
|
|
#include "lib/utils.h"
|
|
#include "gui.h"
|
|
#include "layout.h"
|
|
#include "filesystemmodel.h"
|
|
#include "fileviewerwindow.h"
|
|
#include "textviewerwindow.h"
|
|
#include "jobqueue.h"
|
|
|
|
const std::string DND_TYPE = "fluxengine.files";
|
|
|
|
class BrowserPanelImpl : public BrowserPanelGen, public BrowserPanel, JobQueue
|
|
{
|
|
enum
|
|
{
|
|
STATE_DEAD,
|
|
STATE_WORKING,
|
|
STATE_IDLE,
|
|
};
|
|
|
|
public:
|
|
BrowserPanelImpl(MainWindow* mainWindow, wxSimplebook* parent):
|
|
BrowserPanelGen(parent),
|
|
BrowserPanel(mainWindow),
|
|
/* This is wrong. Apparently the wxDataViewCtrl doesn't work properly
|
|
* with DnD unless the format is wxDF_UNICODETEXT. It should be a custom
|
|
* value. */
|
|
_dndFormat(wxDF_UNICODETEXT)
|
|
{
|
|
_filesystemModel = FilesystemModel::Associate(browserTree);
|
|
|
|
/* This is a bug workaround for an issue in wxformbuilder's generated
|
|
* code; see https://github.com/wxFormBuilder/wxFormBuilder/pull/758.
|
|
* The default handler for the submenu doesn't allow events to fire on
|
|
* the button itself, so we have to override it with our own version. */
|
|
|
|
browserToolbar->Connect(browserMoreMenuButton->GetId(),
|
|
wxEVT_COMMAND_AUITOOLBAR_TOOL_DROPDOWN,
|
|
wxAuiToolBarEventHandler(BrowserPanelImpl::OnBrowserMoreMenuButton),
|
|
NULL,
|
|
this);
|
|
|
|
/* This is a bug workaround for an issue where the calculation of the
|
|
* item being dropped on is wrong due to the header not being taken into
|
|
* account. See https://forums.wxwidgets.org/viewtopic.php?t=44752. */
|
|
|
|
browserTree->EnableDragSource(_dndFormat);
|
|
browserTree->EnableDropTarget(_dndFormat);
|
|
|
|
parent->AddPage(this, "browser");
|
|
}
|
|
|
|
void OnBackButton(wxCommandEvent&) override
|
|
{
|
|
StartIdle();
|
|
}
|
|
|
|
private:
|
|
void SetState(int state)
|
|
{
|
|
_state = state;
|
|
CallAfter(
|
|
[&]()
|
|
{
|
|
UpdateState();
|
|
});
|
|
}
|
|
|
|
void SwitchFrom() override
|
|
{
|
|
SetState(STATE_DEAD);
|
|
}
|
|
|
|
public:
|
|
void StartBrowsing() override
|
|
{
|
|
try
|
|
{
|
|
SetPage(MainWindow::PAGE_BROWSER);
|
|
PrepareConfig();
|
|
|
|
_filesystemModel->Clear(Path());
|
|
_filesystemCapabilities = 0;
|
|
_filesystemIsReadOnly = true;
|
|
_filesystemNeedsFlushing = false;
|
|
|
|
SetState(STATE_WORKING);
|
|
|
|
QueueJob(
|
|
[this]()
|
|
{
|
|
_filesystem = Filesystem::createFilesystemFromConfig();
|
|
_filesystemCapabilities = _filesystem->capabilities();
|
|
_filesystemIsReadOnly = _filesystem->isReadOnly();
|
|
|
|
runOnUiThread(
|
|
[&]()
|
|
{
|
|
RepopulateBrowser();
|
|
});
|
|
});
|
|
}
|
|
catch (const ErrorException& e)
|
|
{
|
|
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
|
|
StartIdle();
|
|
}
|
|
}
|
|
|
|
void StartFormatting() override
|
|
{
|
|
try
|
|
{
|
|
SetPage(MainWindow::PAGE_BROWSER);
|
|
PrepareConfig();
|
|
|
|
_filesystemModel->Clear(Path());
|
|
_filesystemCapabilities = 0;
|
|
_filesystemIsReadOnly = true;
|
|
_filesystemNeedsFlushing = false;
|
|
|
|
SetState(STATE_WORKING);
|
|
|
|
QueueJob(
|
|
[this]()
|
|
{
|
|
_filesystem = Filesystem::createFilesystemFromConfig();
|
|
_filesystemCapabilities = _filesystem->capabilities();
|
|
_filesystemIsReadOnly = _filesystem->isReadOnly();
|
|
|
|
runOnUiThread(
|
|
[&]()
|
|
{
|
|
wxCommandEvent e;
|
|
OnBrowserFormatButton(e);
|
|
});
|
|
});
|
|
}
|
|
catch (const ErrorException& e)
|
|
{
|
|
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
|
|
StartIdle();
|
|
}
|
|
}
|
|
|
|
void OnQueueEmpty() override
|
|
{
|
|
SetState(STATE_IDLE);
|
|
}
|
|
|
|
void QueueJob(std::function<void(void)> f)
|
|
{
|
|
SetState(STATE_WORKING);
|
|
JobQueue::QueueJob(f);
|
|
}
|
|
|
|
private:
|
|
void UpdateState()
|
|
{
|
|
bool running = !IsQueueEmpty();
|
|
bool selection = browserTree->GetSelection().IsOk();
|
|
|
|
browserToolbar->EnableTool(
|
|
browserBackTool->GetId(), _state == STATE_IDLE);
|
|
|
|
uint32_t c = _filesystemCapabilities;
|
|
bool ro = _filesystemIsReadOnly;
|
|
bool needsFlushing = _filesystemNeedsFlushing;
|
|
|
|
browserToolbar->EnableTool(browserInfoTool->GetId(),
|
|
!running && (c & Filesystem::OP_GETDIRENT) && selection);
|
|
browserToolbar->EnableTool(browserViewTool->GetId(),
|
|
!running && (c & Filesystem::OP_GETFILE) && selection);
|
|
browserToolbar->EnableTool(browserSaveTool->GetId(),
|
|
!running && (c & Filesystem::OP_GETFILE) && selection);
|
|
browserMoreMenu->Enable(browserAddMenuItem->GetId(),
|
|
!running && !ro && (c & Filesystem::OP_PUTFILE));
|
|
browserMoreMenu->Enable(browserNewDirectoryMenuItem->GetId(),
|
|
!running && !ro && (c & Filesystem::OP_CREATEDIR));
|
|
browserMoreMenu->Enable(browserRenameMenuItem->GetId(),
|
|
!running && !ro && (c & Filesystem::OP_MOVE) && selection);
|
|
browserMoreMenu->Enable(browserDeleteMenuItem->GetId(),
|
|
!running && !ro && (c & Filesystem::OP_DELETE) && selection);
|
|
browserToolbar->EnableTool(browserFormatTool->GetId(),
|
|
!running && !ro && (c & Filesystem::OP_CREATE));
|
|
|
|
browserDiscardButton->Enable(!running && needsFlushing);
|
|
browserCommitButton->Enable(!running && needsFlushing);
|
|
|
|
browserToolbar->Refresh();
|
|
}
|
|
|
|
void OnBrowserMoreMenuButton(wxAuiToolBarEvent& event)
|
|
{
|
|
browserToolbar->SetToolSticky(event.GetId(), true);
|
|
wxRect rect = browserToolbar->GetToolRect(event.GetId());
|
|
wxPoint pt = browserToolbar->ClientToScreen(rect.GetBottomLeft());
|
|
pt = ScreenToClient(pt);
|
|
browserToolbar->PopupMenu(browserMoreMenu, pt);
|
|
browserToolbar->SetToolSticky(event.GetId(), false);
|
|
}
|
|
|
|
void RepopulateBrowser(Path path = Path())
|
|
{
|
|
QueueJob(
|
|
[this, path]()
|
|
{
|
|
auto files = _filesystem->list(path);
|
|
|
|
runOnUiThread(
|
|
[&]()
|
|
{
|
|
_filesystemModel->Clear(path);
|
|
for (auto& f : files)
|
|
_filesystemModel->Add(f);
|
|
|
|
auto node = _filesystemModel->Find(path);
|
|
if (node)
|
|
browserTree->Expand(node->item);
|
|
UpdateFilesystemData();
|
|
});
|
|
});
|
|
}
|
|
|
|
void UpdateFilesystemData()
|
|
{
|
|
QueueJob(
|
|
[this]()
|
|
{
|
|
auto metadata = _filesystem->getMetadata();
|
|
_filesystemNeedsFlushing = _filesystem->needsFlushing();
|
|
|
|
runOnUiThread(
|
|
[&]()
|
|
{
|
|
try
|
|
{
|
|
uint32_t blockSize =
|
|
std::stoul(metadata.at(Filesystem::BLOCK_SIZE));
|
|
uint32_t totalBlocks = std::stoul(
|
|
metadata.at(Filesystem::TOTAL_BLOCKS));
|
|
uint32_t usedBlocks = std::stoul(
|
|
metadata.at(Filesystem::USED_BLOCKS));
|
|
|
|
diskSpaceGauge->Enable();
|
|
diskSpaceGauge->SetRange(totalBlocks * blockSize);
|
|
diskSpaceGauge->SetValue(usedBlocks * blockSize);
|
|
}
|
|
catch (const std::out_of_range& e)
|
|
{
|
|
diskSpaceGauge->Disable();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
void OnBrowserDirectoryExpanding(wxDataViewEvent& event) override
|
|
{
|
|
auto node = _filesystemModel->Find(event.GetItem());
|
|
if (node && !node->populated && !node->populating)
|
|
{
|
|
node->populating = true;
|
|
RepopulateBrowser(node->dirent->path);
|
|
}
|
|
}
|
|
|
|
void OnBrowserInfoButton(wxCommandEvent&) override
|
|
{
|
|
auto item = browserTree->GetSelection();
|
|
auto node = _filesystemModel->Find(item);
|
|
|
|
std::stringstream ss;
|
|
ss << "File attributes for " << node->dirent->path.to_str() << ":\n\n";
|
|
for (const auto& e : node->dirent->attributes)
|
|
ss << e.first << "=" << quote(e.second) << "\n";
|
|
|
|
TextViewerWindow::Create(
|
|
this, node->dirent->path.to_str(), ss.str(), true)
|
|
->Show();
|
|
}
|
|
|
|
void OnBrowserViewButton(wxCommandEvent&) override
|
|
{
|
|
auto item = browserTree->GetSelection();
|
|
auto node = _filesystemModel->Find(item);
|
|
|
|
QueueJob(
|
|
[this, node]()
|
|
{
|
|
auto bytes = _filesystem->getFile(node->dirent->path);
|
|
|
|
runOnUiThread(
|
|
[&]()
|
|
{
|
|
(new FileViewerWindow(
|
|
this, node->dirent->path.to_str(), bytes))
|
|
->Show();
|
|
});
|
|
});
|
|
}
|
|
|
|
void OnBrowserSaveButton(wxCommandEvent&) override
|
|
{
|
|
auto item = browserTree->GetSelection();
|
|
auto node = _filesystemModel->Find(item);
|
|
|
|
GetfileDialog d(this, wxID_ANY);
|
|
d.filenameText->SetValue(node->dirent->path.to_str());
|
|
d.targetFilePicker->SetFileName(wxFileName(node->dirent->filename));
|
|
d.targetFilePicker->SetFocus();
|
|
d.buttons_OK->SetDefault();
|
|
if (d.ShowModal() != wxID_OK)
|
|
return;
|
|
|
|
auto localPath = d.targetFilePicker->GetPath().ToStdString();
|
|
QueueJob(
|
|
[this, node, localPath]()
|
|
{
|
|
auto bytes = _filesystem->getFile(node->dirent->path);
|
|
bytes.writeToFile(localPath);
|
|
});
|
|
}
|
|
|
|
/* Called from worker thread only! */
|
|
Path ResolveFileConflicts_WT(Path path)
|
|
{
|
|
do
|
|
{
|
|
try
|
|
{
|
|
_filesystem->getDirent(path);
|
|
}
|
|
catch (const FileNotFoundException& e)
|
|
{
|
|
break;
|
|
}
|
|
|
|
runOnUiThread(
|
|
[&]()
|
|
{
|
|
FileConflictDialog d(this, wxID_ANY);
|
|
d.oldNameText->SetValue(path.to_str());
|
|
d.newNameText->SetValue(path.to_str());
|
|
d.newNameText->SetFocus();
|
|
d.buttons_OK->SetDefault();
|
|
if (d.ShowModal() == wxID_OK)
|
|
path = Path(d.newNameText->GetValue().ToStdString());
|
|
else
|
|
path = Path("");
|
|
});
|
|
} while (!path.empty());
|
|
return path;
|
|
}
|
|
|
|
std::shared_ptr<FilesystemNode> GetTargetDirectoryNode(wxDataViewItem& item)
|
|
{
|
|
Path path;
|
|
if (item.IsOk())
|
|
{
|
|
auto node = _filesystemModel->Find(item);
|
|
if (!node)
|
|
return nullptr;
|
|
path = node->dirent->path;
|
|
}
|
|
|
|
auto node = _filesystemModel->Find(path);
|
|
if (!node)
|
|
return nullptr;
|
|
if (node->dirent->file_type != TYPE_DIRECTORY)
|
|
return _filesystemModel->Find(path.parent());
|
|
return node;
|
|
}
|
|
|
|
void OnBrowserAddMenuItem(wxCommandEvent&) override
|
|
{
|
|
auto item = browserTree->GetSelection();
|
|
auto dirNode = GetTargetDirectoryNode(item);
|
|
if (!dirNode)
|
|
return;
|
|
|
|
auto localPath = wxFileSelector("Choose the name of the file to add",
|
|
/* default_path= */ wxEmptyString,
|
|
/* default_filename= */ wxEmptyString,
|
|
/* default_extension= */ wxEmptyString,
|
|
/* wildcard= */ wxEmptyString,
|
|
/* flags= */ wxFD_OPEN | wxFD_FILE_MUST_EXIST)
|
|
.ToStdString();
|
|
if (localPath.empty())
|
|
return;
|
|
auto path = dirNode->dirent->path.concat(
|
|
wxFileName(localPath).GetFullName().ToStdString());
|
|
|
|
QueueJob(
|
|
[this, path, localPath]() mutable
|
|
{
|
|
path = ResolveFileConflicts_WT(path);
|
|
if (path.empty())
|
|
return;
|
|
|
|
auto bytes = Bytes::readFromFile(localPath);
|
|
_filesystem->putFile(path, bytes);
|
|
|
|
auto dirent = _filesystem->getDirent(path);
|
|
|
|
runOnUiThread(
|
|
[&]()
|
|
{
|
|
_filesystemModel->Add(dirent);
|
|
UpdateFilesystemData();
|
|
});
|
|
});
|
|
}
|
|
|
|
void OnBrowserDeleteMenuItem(wxCommandEvent&) override
|
|
{
|
|
auto item = browserTree->GetSelection();
|
|
auto node = _filesystemModel->Find(item);
|
|
if (!node)
|
|
return;
|
|
|
|
QueueJob(
|
|
[this, node]()
|
|
{
|
|
_filesystem->deleteFile(node->dirent->path);
|
|
|
|
runOnUiThread(
|
|
[&]()
|
|
{
|
|
_filesystemModel->Delete(node->dirent->path);
|
|
UpdateFilesystemData();
|
|
});
|
|
});
|
|
}
|
|
|
|
void OnBrowserFormatButton(wxCommandEvent&) override
|
|
{
|
|
FormatDialog d(this, wxID_ANY);
|
|
d.volumeNameText->SetFocus();
|
|
d.buttons_OK->SetDefault();
|
|
if (d.ShowModal() != wxID_OK)
|
|
return;
|
|
|
|
auto volumeName = d.volumeNameText->GetValue().ToStdString();
|
|
auto quickFormat = d.quickFormatCheckBox->GetValue();
|
|
QueueJob(
|
|
[this, volumeName, quickFormat]()
|
|
{
|
|
_filesystem->discardChanges();
|
|
_filesystem->create(quickFormat, volumeName);
|
|
|
|
runOnUiThread(
|
|
[&]()
|
|
{
|
|
RepopulateBrowser();
|
|
});
|
|
});
|
|
}
|
|
|
|
void OnBrowserFilenameChanged(wxDataViewEvent& event) override
|
|
{
|
|
if (!(_filesystem->capabilities() & Filesystem::OP_MOVE))
|
|
return;
|
|
|
|
auto node = _filesystemModel->Find(event.GetItem());
|
|
if (!node)
|
|
return;
|
|
|
|
if (node->newname.empty())
|
|
return;
|
|
if (node->newname == node->dirent->filename)
|
|
return;
|
|
|
|
QueueJob(
|
|
[this, node]() mutable
|
|
{
|
|
auto oldPath = node->dirent->path;
|
|
auto newPath = oldPath.parent().concat(node->newname);
|
|
|
|
newPath = ResolveFileConflicts_WT(newPath);
|
|
if (newPath.empty())
|
|
return;
|
|
|
|
_filesystem->moveFile(oldPath, newPath);
|
|
node->newname = "";
|
|
|
|
auto dirent = _filesystem->getDirent(newPath);
|
|
runOnUiThread(
|
|
[&]()
|
|
{
|
|
_filesystemModel->Delete(oldPath);
|
|
_filesystemModel->Add(dirent);
|
|
UpdateFilesystemData();
|
|
});
|
|
});
|
|
}
|
|
|
|
void OnBrowserRenameMenuItem(wxCommandEvent& event) override
|
|
{
|
|
auto item = browserTree->GetSelection();
|
|
auto node = _filesystemModel->Find(item);
|
|
|
|
FileRenameDialog d(this, wxID_ANY);
|
|
d.oldNameText->SetValue(node->dirent->path.to_str());
|
|
d.newNameText->SetValue(node->dirent->path.to_str());
|
|
d.newNameText->SetFocus();
|
|
d.buttons_OK->SetDefault();
|
|
if (d.ShowModal() != wxID_OK)
|
|
return;
|
|
|
|
ActuallyMoveFile(
|
|
node->dirent->path, Path(d.newNameText->GetValue().ToStdString()));
|
|
}
|
|
|
|
void ActuallyMoveFile(const Path& oldPath, Path newPath)
|
|
{
|
|
QueueJob(
|
|
[this, oldPath, newPath]() mutable
|
|
{
|
|
newPath = ResolveFileConflicts_WT(newPath);
|
|
if (newPath.empty())
|
|
return;
|
|
|
|
_filesystem->moveFile(oldPath, newPath);
|
|
|
|
auto dirent = _filesystem->getDirent(newPath);
|
|
runOnUiThread(
|
|
[&]()
|
|
{
|
|
_filesystemModel->Delete(oldPath);
|
|
_filesystemModel->Add(dirent);
|
|
UpdateFilesystemData();
|
|
});
|
|
});
|
|
}
|
|
|
|
void OnBrowserNewDirectoryMenuItem(wxCommandEvent& event) override
|
|
{
|
|
auto item = browserTree->GetSelection();
|
|
auto node = GetTargetDirectoryNode(item);
|
|
if (!node)
|
|
return;
|
|
auto path = node->dirent->path;
|
|
|
|
CreateDirectoryDialog d(this, wxID_ANY);
|
|
d.newNameText->SetValue(path.to_str() + "/");
|
|
d.newNameText->SetFocus();
|
|
d.buttons_OK->SetDefault();
|
|
if (d.ShowModal() != wxID_OK)
|
|
return;
|
|
|
|
auto newPath = Path(d.newNameText->GetValue().ToStdString());
|
|
QueueJob(
|
|
[this, newPath]() mutable
|
|
{
|
|
newPath = ResolveFileConflicts_WT(newPath);
|
|
_filesystem->createDirectory(newPath);
|
|
|
|
auto dirent = _filesystem->getDirent(newPath);
|
|
runOnUiThread(
|
|
[&]()
|
|
{
|
|
_filesystemModel->Add(dirent);
|
|
UpdateFilesystemData();
|
|
});
|
|
});
|
|
}
|
|
|
|
void OnBrowserBeginDrag(wxDataViewEvent& event) override
|
|
{
|
|
auto item = browserTree->GetSelection();
|
|
if (!item.IsOk())
|
|
{
|
|
event.Veto();
|
|
return;
|
|
}
|
|
|
|
auto node = _filesystemModel->Find(item);
|
|
if (!node)
|
|
{
|
|
event.Veto();
|
|
return;
|
|
}
|
|
|
|
wxTextDataObject* obj = new wxTextDataObject();
|
|
obj->SetText(node->dirent->path.to_str());
|
|
event.SetDataObject(obj);
|
|
event.SetDataFormat(_dndFormat);
|
|
}
|
|
|
|
void OnBrowserDropPossible(wxDataViewEvent& event) override
|
|
{
|
|
if (event.GetDataFormat() != _dndFormat)
|
|
{
|
|
event.Veto();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void OnBrowserDrop(wxDataViewEvent& event) override
|
|
{
|
|
try
|
|
{
|
|
if (event.GetDataFormat() != _dndFormat)
|
|
throw CancelException();
|
|
|
|
#if defined __WXGTK__
|
|
/* wxWidgets 3.0 data view DnD on GTK is borked. See
|
|
* https://forums.wxwidgets.org/viewtopic.php?t=44752. The hit
|
|
* detection is done against the wrong object, resulting in the
|
|
* header size not being taken into account, so we have to
|
|
* manually do hit detection correctly. */
|
|
|
|
auto* window = browserTree->GetMainWindow();
|
|
auto screenPos = wxGetMousePosition();
|
|
auto relPos = screenPos - window->GetScreenPosition();
|
|
|
|
wxDataViewItem item;
|
|
wxDataViewColumn* column;
|
|
browserTree->HitTest(relPos, item, column);
|
|
if (!item.IsOk())
|
|
throw CancelException();
|
|
#else
|
|
auto item = event.GetItem();
|
|
#endif
|
|
|
|
auto destDirNode = GetTargetDirectoryNode(item);
|
|
if (!destDirNode)
|
|
throw CancelException();
|
|
auto destDirPath = destDirNode->dirent->path;
|
|
|
|
wxTextDataObject obj;
|
|
obj.SetData(_dndFormat, event.GetDataSize(), event.GetDataBuffer());
|
|
auto srcPath = Path(obj.GetText().ToStdString());
|
|
if (srcPath.empty())
|
|
throw CancelException();
|
|
|
|
ActuallyMoveFile(srcPath, destDirPath.concat(srcPath.back()));
|
|
}
|
|
catch (const CancelException& e)
|
|
{
|
|
event.Veto();
|
|
}
|
|
}
|
|
|
|
void OnBrowserCommitButton(wxCommandEvent&) override
|
|
{
|
|
QueueJob(
|
|
[this]()
|
|
{
|
|
_filesystem->flushChanges();
|
|
UpdateFilesystemData();
|
|
});
|
|
}
|
|
|
|
void OnBrowserDiscardButton(wxCommandEvent&) override
|
|
{
|
|
QueueJob(
|
|
[this]()
|
|
{
|
|
_filesystem->discardChanges();
|
|
|
|
runOnUiThread(
|
|
[&]()
|
|
{
|
|
RepopulateBrowser();
|
|
});
|
|
});
|
|
}
|
|
|
|
void OnBrowserSelectionChanged(wxDataViewEvent& event) override
|
|
{
|
|
UpdateState();
|
|
}
|
|
|
|
private:
|
|
int _state = STATE_DEAD;
|
|
std::unique_ptr<Filesystem> _filesystem;
|
|
uint32_t _filesystemCapabilities;
|
|
bool _filesystemIsReadOnly;
|
|
bool _filesystemNeedsFlushing;
|
|
FilesystemModel* _filesystemModel;
|
|
wxDataFormat _dndFormat;
|
|
};
|
|
|
|
BrowserPanel* BrowserPanel::Create(MainWindow* mainWindow, wxSimplebook* parent)
|
|
{
|
|
return new BrowserPanelImpl(mainWindow, parent);
|
|
}
|