Files
ctdocpp/src/FileTreeWidget.cpp
TIPC1110 a32f79f6d5 feat: Implement core functionality for DTC C/C++ IDE
- 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.
2025-07-04 12:23:20 +07:00

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);
}
}
}