Compare commits
9 Commits
plugin-loa
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 384dd0f9f4 | |||
| b874491d9c | |||
| 4d64a1bb56 | |||
| 0abd53bf6f | |||
| 3560a92086 | |||
| b735e80fe5 | |||
| d7e3355cf6 | |||
| e25d8686da | |||
| 455d8945ec |
@@ -3,9 +3,17 @@ project(SOM LANGUAGES CXX)
|
|||||||
|
|
||||||
find_package(Qt6 6.5 REQUIRED COMPONENTS Core Widgets)
|
find_package(Qt6 6.5 REQUIRED COMPONENTS Core Widgets)
|
||||||
|
|
||||||
|
option(DEV_BUILD "Enable developer-mode build" OFF)
|
||||||
|
|
||||||
|
if(DEV_BUILD)
|
||||||
|
add_compile_definitions(DEV_BUILD=1)
|
||||||
|
else()
|
||||||
|
add_compile_definitions(DEV_BUILD=0)
|
||||||
|
endif()
|
||||||
|
|
||||||
qt_standard_project_setup()
|
qt_standard_project_setup()
|
||||||
|
|
||||||
qt_add_executable(SOM
|
qt_add_executable(som
|
||||||
WIN32 MACOSX_BUNDLE
|
WIN32 MACOSX_BUNDLE
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/mainwindow.cpp
|
src/mainwindow.cpp
|
||||||
@@ -14,7 +22,11 @@ qt_add_executable(SOM
|
|||||||
src/interface.h
|
src/interface.h
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(SOM
|
set_target_properties(som PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/out"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(som
|
||||||
PRIVATE
|
PRIVATE
|
||||||
Qt::Core
|
Qt::Core
|
||||||
Qt::Widgets
|
Qt::Widgets
|
||||||
@@ -24,14 +36,14 @@ add_subdirectory(plugins)
|
|||||||
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
install(TARGETS SOM
|
install(TARGETS som
|
||||||
BUNDLE DESTINATION .
|
BUNDLE DESTINATION .
|
||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_generate_deploy_app_script(
|
qt_generate_deploy_app_script(
|
||||||
TARGET SOM
|
TARGET som
|
||||||
OUTPUT_SCRIPT deploy_script
|
OUTPUT_SCRIPT deploy_script
|
||||||
NO_UNSUPPORTED_PLATFORM_ERROR
|
NO_UNSUPPORTED_PLATFORM_ERROR
|
||||||
)
|
)
|
||||||
|
|||||||
41
PKGBUILD
Normal file
41
PKGBUILD
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Maintainer: Your Name <youremail@example.com>
|
||||||
|
pkgname=som
|
||||||
|
pkgver=1.0.0
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc="My Qt6 C++ CMake application"
|
||||||
|
arch=('x86_64')
|
||||||
|
url="https://example.com"
|
||||||
|
license=('GPL')
|
||||||
|
depends=('qt6-base') # add other dependencies your app needs
|
||||||
|
makedepends=('cmake' 'gcc' 'make' 'qt6-base')
|
||||||
|
source=()
|
||||||
|
|
||||||
|
build() {
|
||||||
|
cd ..
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
|
||||||
|
# Run CMake
|
||||||
|
|
||||||
|
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||||
|
make
|
||||||
|
}
|
||||||
|
|
||||||
|
package() {
|
||||||
|
|
||||||
|
echo $PWD
|
||||||
|
cd ../build
|
||||||
|
echo $PWD
|
||||||
|
# Install the app binary
|
||||||
|
install -Dm755 out/som "$pkgdir/usr/bin/som"
|
||||||
|
|
||||||
|
# Install plugins
|
||||||
|
install -d "$pkgdir/usr/lib/som"
|
||||||
|
cp -r out/plugins/* "$pkgdir/usr/lib/som/"
|
||||||
|
|
||||||
|
# Install the .desktop file
|
||||||
|
install -Dm644 ../som.desktop "$pkgdir/usr/share/applications/som.desktop"
|
||||||
|
|
||||||
|
# Optionally, install an icon if you have one
|
||||||
|
# install -Dm644 ../myapp.png "$pkgdir/usr/share/icons/hicolor/128x128/apps/myapp.png"
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ Simple Qt6 / C++ tool to organise and manage my SparkleVerse OS build.
|
|||||||
```bash
|
```bash
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake ..
|
cmake .. (Add -DDEV_BUILD=TRUE if doing a dev build to force SOM to look in the out/plugins dir instead of /usr/lib)
|
||||||
make
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ add_subdirectory(CalculatorPlugin)
|
|||||||
add_subdirectory(IrcClientPlugin)
|
add_subdirectory(IrcClientPlugin)
|
||||||
add_subdirectory(TerminalPlugin)
|
add_subdirectory(TerminalPlugin)
|
||||||
add_subdirectory(ScreenShotPlugin)
|
add_subdirectory(ScreenShotPlugin)
|
||||||
|
add_subdirectory(FileExplorerPlugin)
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ target_sources(CalculatorPlugin PRIVATE
|
|||||||
button.cpp button.h
|
button.cpp button.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set_target_properties(CalculatorPlugin PROPERTIES
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/out/plugins"
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(CalculatorPlugin PRIVATE
|
target_link_libraries(CalculatorPlugin PRIVATE
|
||||||
Qt::Core
|
Qt::Core
|
||||||
Qt::Gui
|
Qt::Gui
|
||||||
|
|||||||
30
plugins/FileExplorerPlugin/CMakeLists.txt
Normal file
30
plugins/FileExplorerPlugin/CMakeLists.txt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
project(FileExplorerPlugin VERSION 1.0 LANGUAGES C CXX)
|
||||||
|
|
||||||
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
find_package(QT NAMES Qt5 Qt6 REQUIRED COMPONENTS Core)
|
||||||
|
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Gui Widgets)
|
||||||
|
|
||||||
|
qt_add_plugin(FileExplorerPlugin)
|
||||||
|
target_sources(FileExplorerPlugin PRIVATE
|
||||||
|
FileExplorerPlugin.cpp FileExplorerPlugin.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(FileExplorerPlugin PROPERTIES
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/out/plugins"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(FileExplorerPlugin PRIVATE
|
||||||
|
Qt::Core
|
||||||
|
Qt::Gui
|
||||||
|
Qt::Widgets
|
||||||
|
)
|
||||||
|
|
||||||
|
install(TARGETS FileExplorerPlugin
|
||||||
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
FRAMEWORK DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
)
|
||||||
393
plugins/FileExplorerPlugin/FileExplorerPlugin.cpp
Normal file
393
plugins/FileExplorerPlugin/FileExplorerPlugin.cpp
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
#include "FileExplorerPlugin.h"
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QInputDialog>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QLabel>
|
||||||
|
|
||||||
|
QString FileExplorerPlugin::pname() {
|
||||||
|
return "File Explorer 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("File Explorer Plugin");
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
56
plugins/FileExplorerPlugin/FileExplorerPlugin.h
Normal file
56
plugins/FileExplorerPlugin/FileExplorerPlugin.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#ifndef TESTPLUGIN_H
|
||||||
|
#define TESTPLUGIN_H
|
||||||
|
|
||||||
|
#include <../../src/interface.h>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QtPlugin>
|
||||||
|
#include <QString>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include <QFileSystemModel>
|
||||||
|
#include <QTreeView>
|
||||||
|
#include <QListView>
|
||||||
|
#include <QSplitter>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QTextEdit>
|
||||||
|
|
||||||
|
class FileExplorerPlugin : public QObject, public Interface {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PLUGIN_METADATA(IID "org.SOM.Interface" FILE "FileExplorerPlugin.json")
|
||||||
|
Q_INTERFACES(Interface)
|
||||||
|
|
||||||
|
public:
|
||||||
|
QString pname() override;
|
||||||
|
QString pdesc() override;
|
||||||
|
QWidget *pcontent() override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onDirSelected(const QModelIndex &index);
|
||||||
|
void onFileActivated(const QModelIndex &index);
|
||||||
|
void showFileContextMenu(const QPoint &pos);
|
||||||
|
void showDirContextMenu(const QPoint &pos);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QWidget *mainWidget = nullptr;
|
||||||
|
|
||||||
|
// Models + views
|
||||||
|
QFileSystemModel *dirModel = nullptr; // directories only, left pane
|
||||||
|
QTreeView *dirTree = nullptr;
|
||||||
|
|
||||||
|
QFileSystemModel *fileModel = nullptr; // files+dirs, right pane
|
||||||
|
QListView *fileList = nullptr;
|
||||||
|
|
||||||
|
// Project root (left pane root). Default: application current path
|
||||||
|
QString projectRoot;
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
QString selectedFilePath(const QModelIndex &index) const;
|
||||||
|
QString selectedDirPath(const QModelIndex &index) const;
|
||||||
|
void createFileInCurrentRoot();
|
||||||
|
void createFolderInCurrentRoot();
|
||||||
|
void editFile(const QString &path);
|
||||||
|
bool removePath(const QString &path);
|
||||||
|
bool renamePath(const QString &oldPath, const QString &newName);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
1
plugins/FileExplorerPlugin/FileExplorerPlugin.json
Normal file
1
plugins/FileExplorerPlugin/FileExplorerPlugin.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -23,6 +23,10 @@ target_sources(IrcClientPlugin PRIVATE
|
|||||||
topichandler.cpp topichandler.h
|
topichandler.cpp topichandler.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set_target_properties(IrcClientPlugin PROPERTIES
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/out/plugins"
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(IrcClientPlugin PRIVATE
|
target_link_libraries(IrcClientPlugin PRIVATE
|
||||||
Qt::Core
|
Qt::Core
|
||||||
Qt::Gui
|
Qt::Gui
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ target_sources(ScreenShotPlugin PRIVATE
|
|||||||
ScreenShotPlugin.cpp ScreenShotPlugin.h
|
ScreenShotPlugin.cpp ScreenShotPlugin.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set_target_properties(ScreenShotPlugin PROPERTIES
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/out/plugins"
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(ScreenShotPlugin PRIVATE
|
target_link_libraries(ScreenShotPlugin PRIVATE
|
||||||
Qt::Core
|
Qt::Core
|
||||||
Qt::Gui
|
Qt::Gui
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ target_sources(TerminalPlugin PRIVATE
|
|||||||
TerminalPlugin.cpp TerminalPlugin.h
|
TerminalPlugin.cpp TerminalPlugin.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set_target_properties(TerminalPlugin PROPERTIES
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/out/plugins"
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(TerminalPlugin PRIVATE
|
target_link_libraries(TerminalPlugin PRIVATE
|
||||||
Qt::Core
|
Qt::Core
|
||||||
Qt::Gui
|
Qt::Gui
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QAction>
|
||||||
|
|
||||||
QString TerminalPlugin::pname()
|
QString TerminalPlugin::pname()
|
||||||
{
|
{
|
||||||
@@ -18,21 +19,48 @@ QWidget* TerminalPlugin::pcontent()
|
|||||||
{
|
{
|
||||||
QWidget *test = new QWidget();
|
QWidget *test = new QWidget();
|
||||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||||
|
|
||||||
console = new QTermWidget(test);
|
console = new QTermWidget(test);
|
||||||
|
|
||||||
QFont font = QApplication::font();
|
QFont font = QApplication::font();
|
||||||
font.setFamily("Monospace");
|
font.setFamily("Monospace");
|
||||||
font.setPointSize(12);
|
font.setPointSize(12);
|
||||||
|
|
||||||
console->setTerminalFont(font);
|
console->setTerminalFont(font);
|
||||||
|
|
||||||
console->setScrollBarPosition(QTermWidget::ScrollBarRight);
|
console->setScrollBarPosition(QTermWidget::ScrollBarRight);
|
||||||
console->setColorScheme("WhiteOnBlack");
|
console->setColorScheme("WhiteOnBlack");
|
||||||
|
|
||||||
|
console->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
connect(console, &QWidget::customContextMenuRequested, this, [=](const QPoint &pos) {
|
||||||
|
QMenu menu;
|
||||||
|
|
||||||
|
QAction *copyAct = new QAction(tr("Copy"), &menu);
|
||||||
|
QAction *pasteAct = new QAction(tr("Paste"), &menu);
|
||||||
|
|
||||||
|
if (console->selectedText().isEmpty()) {
|
||||||
|
copyAct->setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(copyAct, &QAction::triggered, [=]() {
|
||||||
|
console->copyClipboard();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(pasteAct, &QAction::triggered, [=]() {
|
||||||
|
console->pasteClipboard();
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.addAction(copyAct);
|
||||||
|
menu.addAction(pasteAct);
|
||||||
|
|
||||||
|
|
||||||
|
QPoint globalPos = console->mapToGlobal(pos);
|
||||||
|
menu.exec(globalPos);
|
||||||
|
});
|
||||||
|
|
||||||
test->setWindowTitle(tr("Terminal Plugin"));
|
test->setWindowTitle(tr("Terminal Plugin"));
|
||||||
test->resize(600, 400);
|
test->resize(600, 400);
|
||||||
mainLayout->addWidget(console);
|
mainLayout->addWidget(console);
|
||||||
test->setLayout(mainLayout);
|
test->setLayout(mainLayout);
|
||||||
QIcon icon = QIcon(":/images/monkey.png");
|
|
||||||
test->setWindowIcon(icon);
|
|
||||||
return test;
|
return test;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ target_sources(WebBrowserPlugin PRIVATE
|
|||||||
WebBrowserPlugin.cpp WebBrowserPlugin.h
|
WebBrowserPlugin.cpp WebBrowserPlugin.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set_target_properties(WebBrowserPlugin PROPERTIES
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/out/plugins"
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(WebBrowserPlugin PRIVATE
|
target_link_libraries(WebBrowserPlugin PRIVATE
|
||||||
Qt::Core
|
Qt::Core
|
||||||
Qt::Gui
|
Qt::Gui
|
||||||
|
|||||||
8
som.desktop
Normal file
8
som.desktop
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Name=SOM
|
||||||
|
Comment=Description of My Qt6 App
|
||||||
|
Exec=/usr/bin/som
|
||||||
|
Icon=myapp
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Utility;Development;
|
||||||
@@ -77,7 +77,7 @@ void MainWindow::initSettings()
|
|||||||
for (const QString &group : settings.childGroups()) {
|
for (const QString &group : settings.childGroups()) {
|
||||||
settings.beginGroup(group);
|
settings.beginGroup(group);
|
||||||
|
|
||||||
QString pluginName = group.section('_', 0, 0);
|
QString pluginName = group.split("_").first();
|
||||||
pluginName = QUrl::fromPercentEncoding(pluginName.toUtf8());
|
pluginName = QUrl::fromPercentEncoding(pluginName.toUtf8());
|
||||||
|
|
||||||
Interface *iPlugin = nullptr;
|
Interface *iPlugin = nullptr;
|
||||||
@@ -92,9 +92,11 @@ void MainWindow::initSettings()
|
|||||||
|
|
||||||
if (iPlugin) {
|
if (iPlugin) {
|
||||||
QWidget *pluginWidget = iPlugin->pcontent();
|
QWidget *pluginWidget = iPlugin->pcontent();
|
||||||
|
QString uniqueId = QUuid::createUuid().toString();
|
||||||
if (pluginWidget) {
|
if (pluginWidget) {
|
||||||
QMdiSubWindow *sub = mdi->addSubWindow(pluginWidget);
|
QMdiSubWindow *sub = mdi->addSubWindow(pluginWidget);
|
||||||
sub->setProperty("uniqueId", group);
|
sub->setProperty("name", iPlugin->pname());
|
||||||
|
sub->setProperty("uniqueId", uniqueId);
|
||||||
sub->restoreGeometry(settings.value("geometry").toByteArray());
|
sub->restoreGeometry(settings.value("geometry").toByteArray());
|
||||||
sub->setAttribute(Qt::WA_DeleteOnClose);
|
sub->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
sub->show();
|
sub->show();
|
||||||
@@ -119,7 +121,7 @@ void MainWindow::saveSettings()
|
|||||||
settings.beginGroup("Plugins");
|
settings.beginGroup("Plugins");
|
||||||
QList<QMdiSubWindow*> subWindows = mdi->subWindowList();
|
QList<QMdiSubWindow*> subWindows = mdi->subWindowList();
|
||||||
for (QMdiSubWindow* sub : subWindows) {
|
for (QMdiSubWindow* sub : subWindows) {
|
||||||
QString id = sub->windowTitle() + "_" + sub->property("uniqueId").toString();
|
QString id = sub->property("name").toString() + "_" + sub->property("uniqueId").toString();
|
||||||
settings.beginGroup(id);
|
settings.beginGroup(id);
|
||||||
settings.setValue("geometry", sub->saveGeometry());
|
settings.setValue("geometry", sub->saveGeometry());
|
||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
@@ -144,9 +146,13 @@ void MainWindow::settings()
|
|||||||
|
|
||||||
void MainWindow::loadPlugins()
|
void MainWindow::loadPlugins()
|
||||||
{
|
{
|
||||||
|
#if DEV_BUILD
|
||||||
pluginsDir = QDir(QCoreApplication::applicationDirPath());
|
pluginsDir = QDir(QCoreApplication::applicationDirPath());
|
||||||
pluginsDir.cd("plugins");
|
pluginsDir.cd("plugins");
|
||||||
|
#else
|
||||||
|
pluginsDir = QDir("/usr/lib");
|
||||||
|
pluginsDir.cd("som");
|
||||||
|
#endif
|
||||||
const auto entryList = pluginsDir.entryList(QDir::Files);
|
const auto entryList = pluginsDir.entryList(QDir::Files);
|
||||||
for (const QString &fileName : entryList) {
|
for (const QString &fileName : entryList) {
|
||||||
|
|
||||||
@@ -174,6 +180,7 @@ void MainWindow::changePlugin()
|
|||||||
QWidget* pluginContent = iPlugin->pcontent();
|
QWidget* pluginContent = iPlugin->pcontent();
|
||||||
auto *sub = mdi->addSubWindow(pluginContent);
|
auto *sub = mdi->addSubWindow(pluginContent);
|
||||||
sub->setProperty("uniqueId", uniqueId);
|
sub->setProperty("uniqueId", uniqueId);
|
||||||
|
sub->setProperty("name", iPlugin->pname());
|
||||||
sub->setAttribute(Qt::WA_DeleteOnClose);
|
sub->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
sub->show();
|
sub->show();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user