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.
This commit is contained in:
360
src/FileTreeWidget.cpp
Normal file
360
src/FileTreeWidget.cpp
Normal file
@@ -0,0 +1,360 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user