
- Added ProjectManager class to handle project operations including opening, closing, building, and running projects. - Introduced SyntaxHighlighter class for syntax highlighting in C and C++ files. - Developed TerminalWidget for command execution and output display. - Created TextEditor with line numbering and auto-indentation features. - Established main application entry point in main.cpp. - Designed UI layout for MainWindow using Qt Designer.
361 lines
10 KiB
C++
361 lines
10 KiB
C++
#include "FileTreeWidget.h"
|
|
|
|
#include <QtWidgets/QHeaderView>
|
|
#include <QtWidgets/QMenu>
|
|
#include <QtGui/QAction>
|
|
#include <QtWidgets/QInputDialog>
|
|
#include <QtWidgets/QMessageBox>
|
|
#include <QtCore/QFileInfo>
|
|
#include <QtCore/QMimeData>
|
|
#include <QtCore/QTimer>
|
|
#include <QtGui/QContextMenuEvent>
|
|
#include <QtGui/QPalette>
|
|
#include <QtGui/QFont>
|
|
#include <QtWidgets/QLabel>
|
|
|
|
FileTreeWidget::FileTreeWidget(QWidget *parent)
|
|
: QTreeWidget(parent)
|
|
, m_fileWatcher(nullptr)
|
|
, m_contextMenu(nullptr)
|
|
, m_contextItem(nullptr)
|
|
{
|
|
setHeaderHidden(true);
|
|
setRootIsDecorated(true);
|
|
setAlternatingRowColors(false); // VS Code style: no alternating rows
|
|
setDragDropMode(QAbstractItemView::InternalMove);
|
|
setIndentation(18); // VS Code style
|
|
setStyleSheet(R"(
|
|
QTreeWidget {
|
|
background: #1e1e1e;
|
|
color: #d4d4d4;
|
|
border: none;
|
|
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
|
|
font-size: 14px;
|
|
}
|
|
QTreeWidget::item {
|
|
height: 28px;
|
|
padding-left: 8px;
|
|
border-radius: 4px;
|
|
}
|
|
QTreeWidget::item:selected {
|
|
background: #2a2d2e;
|
|
color: #ffffff;
|
|
}
|
|
QTreeWidget::item:hover {
|
|
background: #23272e;
|
|
}
|
|
QTreeWidget::branch:has-children:!has-siblings:closed,
|
|
QTreeWidget::branch:closed:has-children:has-siblings {
|
|
border-image: none;
|
|
image: url(:/vsc/arrow-right.svg);
|
|
}
|
|
QTreeWidget::branch:open:has-children:!has-siblings,
|
|
QTreeWidget::branch:open:has-children:has-siblings {
|
|
border-image: none;
|
|
image: url(:/vsc/arrow-down.svg);
|
|
}
|
|
)");
|
|
// Add Explorer header like VS Code
|
|
QLabel *explorerHeader = new QLabel("EXPLORER", this);
|
|
explorerHeader->setStyleSheet("color: #858585; background: #23272e; font-weight: bold; padding: 8px 8px 4px 8px; letter-spacing: 1px;");
|
|
explorerHeader->setFixedHeight(28);
|
|
explorerHeader->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
|
explorerHeader->setFont(QFont("JetBrains Mono", 11, QFont::Bold));
|
|
explorerHeader->move(0, 0);
|
|
explorerHeader->raise();
|
|
setViewportMargins(0, 28, 0, 0);
|
|
|
|
// Setup file watcher
|
|
m_fileWatcher = new QFileSystemWatcher(this);
|
|
connect(m_fileWatcher, &QFileSystemWatcher::directoryChanged,
|
|
this, &FileTreeWidget::onDirectoryChanged);
|
|
|
|
// Setup context menu
|
|
setupContextMenu();
|
|
setContextMenuPolicy(Qt::CustomContextMenu);
|
|
connect(this, &QTreeWidget::customContextMenuRequested,
|
|
this, &FileTreeWidget::onCustomContextMenu);
|
|
|
|
// Connect signals
|
|
connect(this, &QTreeWidget::itemClicked,
|
|
this, &FileTreeWidget::onItemClicked);
|
|
connect(this, &QTreeWidget::itemDoubleClicked,
|
|
this, &FileTreeWidget::onItemDoubleClicked);
|
|
}
|
|
|
|
FileTreeWidget::~FileTreeWidget()
|
|
{
|
|
}
|
|
|
|
void FileTreeWidget::setRootPath(const QString &path)
|
|
{
|
|
if (m_rootPath == path) {
|
|
return;
|
|
}
|
|
|
|
m_rootPath = path;
|
|
clear();
|
|
|
|
if (!path.isEmpty() && QDir(path).exists()) {
|
|
populateTree(path);
|
|
|
|
// Watch the root directory
|
|
if (m_fileWatcher) {
|
|
m_fileWatcher->removePaths(m_fileWatcher->directories());
|
|
m_fileWatcher->addPath(path);
|
|
}
|
|
}
|
|
}
|
|
|
|
QString FileTreeWidget::rootPath() const
|
|
{
|
|
return m_rootPath;
|
|
}
|
|
|
|
void FileTreeWidget::refreshTree()
|
|
{
|
|
setRootPath(m_rootPath);
|
|
}
|
|
|
|
void FileTreeWidget::populateTree(const QString &path, QTreeWidgetItem *parent)
|
|
{
|
|
QDir dir(path);
|
|
if (!dir.exists()) {
|
|
return;
|
|
}
|
|
|
|
// Get directory entries
|
|
QFileInfoList entries = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot,
|
|
QDir::DirsFirst | QDir::Name);
|
|
|
|
for (const QFileInfo &entry : entries) {
|
|
QTreeWidgetItem *item = new QTreeWidgetItem();
|
|
item->setText(0, entry.fileName());
|
|
item->setData(0, Qt::UserRole, entry.absoluteFilePath());
|
|
|
|
if (entry.isDir()) {
|
|
item->setData(0, Qt::UserRole + 1, true); // isDirectory flag
|
|
item->setIcon(0, style()->standardIcon(QStyle::SP_DirIcon));
|
|
|
|
// Add placeholder child for expandable directories
|
|
QTreeWidgetItem *placeholder = new QTreeWidgetItem();
|
|
placeholder->setText(0, "Loading...");
|
|
item->addChild(placeholder);
|
|
|
|
// Watch directory for changes
|
|
if (m_fileWatcher) {
|
|
m_fileWatcher->addPath(entry.absoluteFilePath());
|
|
}
|
|
} else {
|
|
item->setData(0, Qt::UserRole + 1, false); // isDirectory flag
|
|
item->setIcon(0, style()->standardIcon(QStyle::SP_FileIcon));
|
|
}
|
|
|
|
if (parent) {
|
|
parent->addChild(item);
|
|
} else {
|
|
addTopLevelItem(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FileTreeWidget::setupContextMenu()
|
|
{
|
|
m_contextMenu = new QMenu(this);
|
|
|
|
QAction *newFileAction = m_contextMenu->addAction("New File", this, &FileTreeWidget::newFile);
|
|
QAction *newFolderAction = m_contextMenu->addAction("New Folder", this, &FileTreeWidget::newFolder);
|
|
m_contextMenu->addSeparator();
|
|
QAction *deleteAction = m_contextMenu->addAction("Delete", this, &FileTreeWidget::deleteItem);
|
|
QAction *renameAction = m_contextMenu->addAction("Rename", this, &FileTreeWidget::renameItem);
|
|
}
|
|
|
|
QString FileTreeWidget::getItemPath(QTreeWidgetItem *item) const
|
|
{
|
|
if (!item) {
|
|
return QString();
|
|
}
|
|
|
|
return item->data(0, Qt::UserRole).toString();
|
|
}
|
|
|
|
bool FileTreeWidget::isDirectory(QTreeWidgetItem *item) const
|
|
{
|
|
if (!item) {
|
|
return false;
|
|
}
|
|
|
|
return item->data(0, Qt::UserRole + 1).toBool();
|
|
}
|
|
|
|
void FileTreeWidget::onItemClicked(QTreeWidgetItem *item, int column)
|
|
{
|
|
Q_UNUSED(column)
|
|
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
QString path = getItemPath(item);
|
|
|
|
if (isDirectory(item)) {
|
|
emit directorySelected(path);
|
|
|
|
// Expand directory and load children if needed
|
|
if (!item->isExpanded() && item->childCount() == 1 &&
|
|
item->child(0)->text(0) == "Loading...") {
|
|
|
|
// Remove placeholder
|
|
delete item->takeChild(0);
|
|
|
|
// Populate directory
|
|
populateTree(path, item);
|
|
}
|
|
} else {
|
|
emit fileSelected(path);
|
|
}
|
|
}
|
|
|
|
void FileTreeWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column)
|
|
{
|
|
Q_UNUSED(column)
|
|
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
QString path = getItemPath(item);
|
|
|
|
if (!isDirectory(item)) {
|
|
emit fileDoubleClicked(path);
|
|
}
|
|
}
|
|
|
|
void FileTreeWidget::onCustomContextMenu(const QPoint &point)
|
|
{
|
|
m_contextItem = itemAt(point);
|
|
|
|
if (m_contextMenu) {
|
|
m_contextMenu->exec(mapToGlobal(point));
|
|
}
|
|
}
|
|
|
|
void FileTreeWidget::onDirectoryChanged(const QString &path)
|
|
{
|
|
Q_UNUSED(path)
|
|
// Refresh tree when directory changes
|
|
QTimer::singleShot(100, this, &FileTreeWidget::refreshTree);
|
|
}
|
|
|
|
void FileTreeWidget::newFile()
|
|
{
|
|
QString dirPath = m_rootPath;
|
|
|
|
if (m_contextItem) {
|
|
QString itemPath = getItemPath(m_contextItem);
|
|
if (isDirectory(m_contextItem)) {
|
|
dirPath = itemPath;
|
|
} else {
|
|
dirPath = QFileInfo(itemPath).absolutePath();
|
|
}
|
|
}
|
|
|
|
bool ok;
|
|
QString fileName = QInputDialog::getText(this, "New File", "File name:", QLineEdit::Normal, QString(), &ok);
|
|
|
|
if (ok && !fileName.isEmpty()) {
|
|
QString filePath = QDir(dirPath).absoluteFilePath(fileName);
|
|
QFile file(filePath);
|
|
|
|
if (file.open(QIODevice::WriteOnly)) {
|
|
file.close();
|
|
refreshTree();
|
|
} else {
|
|
QMessageBox::warning(this, "Error", "Could not create file: " + fileName);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FileTreeWidget::newFolder()
|
|
{
|
|
QString dirPath = m_rootPath;
|
|
|
|
if (m_contextItem) {
|
|
QString itemPath = getItemPath(m_contextItem);
|
|
if (isDirectory(m_contextItem)) {
|
|
dirPath = itemPath;
|
|
} else {
|
|
dirPath = QFileInfo(itemPath).absolutePath();
|
|
}
|
|
}
|
|
|
|
bool ok;
|
|
QString folderName = QInputDialog::getText(this, "New Folder", "Folder name:", QLineEdit::Normal, QString(), &ok);
|
|
|
|
if (ok && !folderName.isEmpty()) {
|
|
QString folderPath = QDir(dirPath).absoluteFilePath(folderName);
|
|
QDir dir;
|
|
|
|
if (dir.mkpath(folderPath)) {
|
|
refreshTree();
|
|
} else {
|
|
QMessageBox::warning(this, "Error", "Could not create folder: " + folderName);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FileTreeWidget::deleteItem()
|
|
{
|
|
if (!m_contextItem) {
|
|
return;
|
|
}
|
|
|
|
QString itemPath = getItemPath(m_contextItem);
|
|
QString itemName = m_contextItem->text(0);
|
|
|
|
int ret = QMessageBox::question(this, "Delete",
|
|
QString("Are you sure you want to delete '%1'?").arg(itemName),
|
|
QMessageBox::Yes | QMessageBox::No);
|
|
|
|
if (ret == QMessageBox::Yes) {
|
|
if (isDirectory(m_contextItem)) {
|
|
QDir dir(itemPath);
|
|
if (dir.removeRecursively()) {
|
|
refreshTree();
|
|
} else {
|
|
QMessageBox::warning(this, "Error", "Could not delete folder: " + itemName);
|
|
}
|
|
} else {
|
|
QFile file(itemPath);
|
|
if (file.remove()) {
|
|
refreshTree();
|
|
} else {
|
|
QMessageBox::warning(this, "Error", "Could not delete file: " + itemName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FileTreeWidget::renameItem()
|
|
{
|
|
if (!m_contextItem) {
|
|
return;
|
|
}
|
|
|
|
QString oldPath = getItemPath(m_contextItem);
|
|
QString oldName = m_contextItem->text(0);
|
|
|
|
bool ok;
|
|
QString newName = QInputDialog::getText(this, "Rename", "New name:", QLineEdit::Normal, oldName, &ok);
|
|
|
|
if (ok && !newName.isEmpty() && newName != oldName) {
|
|
QString newPath = QFileInfo(oldPath).absolutePath() + "/" + newName;
|
|
|
|
if (QFile::rename(oldPath, newPath)) {
|
|
refreshTree();
|
|
} else {
|
|
QMessageBox::warning(this, "Error", "Could not rename: " + oldName);
|
|
}
|
|
}
|
|
}
|