#include "FileExplorerPlugin.h" #include #include #include #include #include #include #include #include #include #include #include #include QString FileExplorerPlugin::pname() { return "Test Plugin"; } QString FileExplorerPlugin::pdesc() { return "Two-pane file manager (Thunar-like) with create/edit/rename/delete."; } QWidget* FileExplorerPlugin::pcontent() { // If already created, return it (plugin may call multiple times) if (mainWidget) return mainWidget; projectRoot = QDir::currentPath(); // default project directory; adjust if needed mainWidget = new QWidget(); mainWidget->setWindowTitle("Test Plugin - File Manager"); mainWidget->resize(1000, 600); QVBoxLayout *vLayout = new QVBoxLayout(mainWidget); // Horizontal splitter: left = dir tree, right = file list QSplitter *splitter = new QSplitter(Qt::Horizontal, mainWidget); // ---------------------------- // LEFT: directory tree (dirs only) // ---------------------------- dirModel = new QFileSystemModel(this); dirModel->setRootPath(projectRoot); dirModel->setFilter(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Drives); dirTree = new QTreeView(splitter); dirTree->setModel(dirModel); dirTree->setRootIndex(dirModel->index(projectRoot)); dirTree->setHeaderHidden(true); dirTree->setContextMenuPolicy(Qt::CustomContextMenu); dirTree->setAnimated(true); dirTree->setExpandsOnDoubleClick(true); dirTree->setSelectionMode(QAbstractItemView::SingleSelection); // collapse columns other than the name (column 0) for (int c = 1; c < dirModel->columnCount(); ++c) dirTree->setColumnHidden(c, true); // ---------------------------- // RIGHT: files in current directory // ---------------------------- fileModel = new QFileSystemModel(this); fileModel->setFilter(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System); fileModel->setRootPath(projectRoot); fileList = new QListView(splitter); fileList->setModel(fileModel); fileList->setRootIndex(fileModel->index(projectRoot)); fileList->setViewMode(QListView::ListMode); fileList->setSelectionMode(QAbstractItemView::ExtendedSelection); fileList->setContextMenuPolicy(Qt::CustomContextMenu); fileList->setUniformItemSizes(true); splitter->setStretchFactor(0, 0); splitter->setStretchFactor(1, 1); splitter->setSizes({250, 750}); vLayout->addWidget(splitter); // ---------------------------- // Connections // ---------------------------- connect(dirTree, &QTreeView::clicked, this, &FileExplorerPlugin::onDirSelected); connect(dirTree, &QTreeView::customContextMenuRequested, this, &FileExplorerPlugin::showDirContextMenu); connect(fileList, &QListView::customContextMenuRequested, this, &FileExplorerPlugin::showFileContextMenu); connect(fileList, &QListView::activated, this, &FileExplorerPlugin::onFileActivated); return mainWidget; } // ---------- Helpers ---------- QString FileExplorerPlugin::selectedFilePath(const QModelIndex &index) const { if (!index.isValid()) return QString(); return fileModel->filePath(index); } QString FileExplorerPlugin::selectedDirPath(const QModelIndex &index) const { if (!index.isValid()) return QString(); return dirModel->filePath(index); } // ---------- Slot: when a directory is selected in left pane ---------- void FileExplorerPlugin::onDirSelected(const QModelIndex &index) { QString path = selectedDirPath(index); if (path.isEmpty()) return; // Show contents of selected dir on the right QModelIndex fileRoot = fileModel->setRootPath(path); fileList->setRootIndex(fileRoot); } // ---------- Double-click / activate on file: open editor for text files ---------- void FileExplorerPlugin::onFileActivated(const QModelIndex &index) { QString path = selectedFilePath(index); if (path.isEmpty()) return; QFileInfo info(path); if (info.isDir()) { // navigate into directory QModelIndex fileRoot = fileModel->setRootPath(path); fileList->setRootIndex(fileRoot); return; } // Try to open text files in simple editor; binary files will fail to load as text editFile(path); } // ---------- Context menu: right pane (files + folders) ---------- void FileExplorerPlugin::showFileContextMenu(const QPoint &pos) { // Determine clicked index relative to the fileList root QModelIndex clicked = fileList->indexAt(pos); QString currentDir = fileModel->filePath(fileList->rootIndex()); QMenu menu(mainWidget); QAction *actCreateFile = menu.addAction("Create File"); QAction *actCreateFolder = menu.addAction("Create Folder"); QAction *actEdit = nullptr; QAction *actRename = nullptr; QAction *actDelete = nullptr; if (clicked.isValid()) { actEdit = menu.addAction("Edit"); actRename = menu.addAction("Rename"); actDelete = menu.addAction("Delete"); } QAction *selected = menu.exec(fileList->viewport()->mapToGlobal(pos)); if (!selected) return; if (selected == actCreateFile) { bool ok; QString name = QInputDialog::getText(mainWidget, "Create File", "File name:", QLineEdit::Normal, QString(), &ok); if (!ok || name.isEmpty()) return; QString newPath = QDir(currentDir).filePath(name); QFile f(newPath); if (!f.open(QIODevice::WriteOnly)) { QMessageBox::warning(mainWidget, "Error", "Unable to create file:\n" + newPath); return; } f.close(); // refresh model fileModel->setRootPath(currentDir); fileList->setRootIndex(fileModel->index(currentDir)); return; } if (selected == actCreateFolder) { bool ok; QString name = QInputDialog::getText(mainWidget, "Create Folder", "Folder name:", QLineEdit::Normal, QString(), &ok); if (!ok || name.isEmpty()) return; QDir dir(currentDir); if (!dir.mkdir(name)) { QMessageBox::warning(mainWidget, "Error", "Unable to create folder:\n" + dir.filePath(name)); return; } fileModel->setRootPath(currentDir); fileList->setRootIndex(fileModel->index(currentDir)); return; } if (!clicked.isValid()) return; QString path = selectedFilePath(clicked); QFileInfo info(path); if (selected == actEdit) { if (info.isDir()) { // nothing to edit QMessageBox::information(mainWidget, "Edit", "Cannot edit a directory."); return; } editFile(path); return; } if (selected == actRename) { bool ok; QString newName = QInputDialog::getText(mainWidget, "Rename", "New name:", QLineEdit::Normal, fileModel->fileName(clicked), &ok); if (!ok || newName.isEmpty()) return; QString newPath = info.dir().filePath(newName); if (!QFile::rename(path, newPath)) { // try QDir::rename for directories if (info.isDir() && !QDir().rename(path, newPath)) { QMessageBox::warning(mainWidget, "Error", "Rename failed."); } else if (!info.isDir()) { QMessageBox::warning(mainWidget, "Error", "Rename failed."); } } // refresh QString currentDirPath = fileModel->filePath(fileList->rootIndex()); fileModel->setRootPath(currentDirPath); fileList->setRootIndex(fileModel->index(currentDirPath)); return; } if (selected == actDelete) { if (QMessageBox::question(mainWidget, "Delete", "Delete selected item(s)?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { return; } bool okAny = true; // support multi-selection QModelIndexList sel = fileList->selectionModel()->selectedIndexes(); if (sel.isEmpty()) sel = { clicked }; for (const QModelIndex &mi : sel) { QString p = fileModel->filePath(mi); QFileInfo fi(p); bool success = fi.isDir() ? QDir(p).removeRecursively() : QFile::remove(p); if (!success) okAny = false; } if (!okAny) QMessageBox::warning(mainWidget, "Error", "One or more items could not be deleted."); QString currentDirPath = fileModel->filePath(fileList->rootIndex()); fileModel->setRootPath(currentDirPath); fileList->setRootIndex(fileModel->index(currentDirPath)); return; } } // ---------- Context menu: left pane (directory tree) ---------- void FileExplorerPlugin::showDirContextMenu(const QPoint &pos) { QModelIndex idx = dirTree->indexAt(pos); if (!idx.isValid()) return; QString path = selectedDirPath(idx); if (path.isEmpty()) return; QMenu menu(mainWidget); QAction *actSetAsProjectRoot = menu.addAction("Set as Project Root"); QAction *actCreateFolder = menu.addAction("Create Folder"); QAction *actRename = menu.addAction("Rename"); QAction *actDelete = menu.addAction("Delete"); QAction *selected = menu.exec(dirTree->viewport()->mapToGlobal(pos)); if (!selected) return; if (selected == actSetAsProjectRoot) { projectRoot = path; // re-root dirModel and fileModel dirTree->setRootIndex(dirModel->setRootPath(projectRoot)); fileList->setRootIndex(fileModel->setRootPath(projectRoot)); return; } if (selected == actCreateFolder) { bool ok; QString name = QInputDialog::getText(mainWidget, "Create Folder", "Folder name:", QLineEdit::Normal, QString(), &ok); if (!ok || name.isEmpty()) return; QDir d(path); if (!d.mkdir(name)) { QMessageBox::warning(mainWidget, "Error", "Could not create folder."); } // refresh view dirModel->setRootPath(projectRoot); dirTree->setRootIndex(dirModel->index(projectRoot)); return; } if (selected == actRename) { bool ok; QString newName = QInputDialog::getText(mainWidget, "Rename Folder", "New name:", QLineEdit::Normal, dirModel->fileName(idx), &ok); if (!ok || newName.isEmpty()) return; QFileInfo info(path); QString newPath = info.dir().filePath(newName); if (!QDir().rename(path, newPath)) { QMessageBox::warning(mainWidget, "Error", "Rename failed."); } dirModel->setRootPath(projectRoot); dirTree->setRootIndex(dirModel->index(projectRoot)); return; } if (selected == actDelete) { if (QMessageBox::question(mainWidget, "Delete Folder", "Delete this folder and its contents?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { return; } if (!QDir(path).removeRecursively()) { QMessageBox::warning(mainWidget, "Error", "Delete failed."); } else { dirModel->setRootPath(projectRoot); dirTree->setRootIndex(dirModel->index(projectRoot)); // if deleted path == current fileList root, move fileList to projectRoot QString currentFileRoot = fileModel->filePath(fileList->rootIndex()); if (currentFileRoot.startsWith(path)) { fileList->setRootIndex(fileModel->index(projectRoot)); } } return; } } // ---------- Simple text editor dialog for editing files ---------- void FileExplorerPlugin::editFile(const QString &path) { QFileInfo fi(path); if (!fi.exists() || !fi.isFile()) { QMessageBox::warning(mainWidget, "Edit", "File does not exist or is not a regular file."); return; } QFile f(path); if (!f.open(QIODevice::ReadOnly)) { QMessageBox::warning(mainWidget, "Edit", "Unable to open file for reading."); return; } QByteArray data = f.readAll(); f.close(); // Create modal dialog with QTextEdit QDialog dlg(mainWidget); dlg.setWindowTitle(QString("Edit: %1").arg(fi.fileName())); dlg.resize(800, 600); QVBoxLayout *lay = new QVBoxLayout(&dlg); QLabel *info = new QLabel(QString("Path: %1").arg(path), &dlg); lay->addWidget(info); QTextEdit *editor = new QTextEdit(&dlg); // attempt to interpret as UTF-8; binary files will look garbled editor->setPlainText(QString::fromUtf8(data)); lay->addWidget(editor); QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel, &dlg); lay->addWidget(btns); connect(btns, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); connect(btns, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); if (dlg.exec() == QDialog::Accepted) { QString text = editor->toPlainText(); QFile out(path); if (!out.open(QIODevice::WriteOnly | QIODevice::Truncate)) { QMessageBox::warning(mainWidget, "Save", "Unable to open file for writing."); return; } QByteArray outData = text.toUtf8(); qint64 written = out.write(outData); out.close(); if (written != outData.size()) { QMessageBox::warning(mainWidget, "Save", "Not all data could be written."); } // refresh QString cur = fileModel->filePath(fileList->rootIndex()); fileModel->setRootPath(cur); fileList->setRootIndex(fileModel->index(cur)); } }