Sanguine_Studio/app/mainwindow.cpp

804 lines
23 KiB
C++
Raw Permalink Normal View History

#include "mainwindow.h"
#include "gschighlighter.h"
#include "ui_mainwindow.h"
#include <aboutdialog.h>
#include <developerdialog.h>
#include <savedialog.h>
#include <gsceditor.h>
#include <QUrl>
#include <QDesktopServices>
#include <QFileDialog>
#include <QStandardPaths>
#include <QSettings>
#include <QFileSystemModel>
#include <QTextEdit>
#include <QShortcut>
#include <QStringListModel>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, mFolderPath()
, mOpenGscPath()
, mOpenTxtPath()
, mNewGscPath()
, mNewTxtPath()
, mRecentFiles()
, mPinnedFiles()
, mRecentFolders()
, mPinnedFolders()
, mPinIcon(":/images/pin")
, mFolderRootItem()
, mFileRootItem()
, mHighlighter()
, mCompleter()
{
ui->setupUi(this);
ui->tabWidget->setContextMenuPolicy(Qt::CustomContextMenu);
ui->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
mFileRootItem = new QTreeWidgetItem(ui->treeWidget);
mFileRootItem->setText(0, "Files");
mFolderRootItem = new QTreeWidgetItem(ui->treeWidget);
mFolderRootItem->setText(0, "Folders");
mHighlighter = new GSCHighlighter();
mCompleter = new QCompleter(this);
mCompleter->setModel(modelFromFile(":/wordlist"));
mCompleter->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
mCompleter->setCaseSensitivity(Qt::CaseInsensitive);
mCompleter->setWrapAround(false);
// Connect menu actions to tab widget
connect(ui->actionClose, &QAction::triggered, ui->tabWidget, &TabWidget::closeCurrentTab);
connect(ui->actionClose_All, &QAction::triggered, ui->tabWidget, &TabWidget::closeAllTabs);
connect(ui->actionSave, &QAction::triggered, ui->tabWidget, &TabWidget::saveCurrentTab);
connect(ui->actionSave_All, &QAction::triggered, ui->tabWidget, &TabWidget::saveAllTabs);
connect(ui->actionSave_As, &QAction::triggered, ui->tabWidget, &TabWidget::saveCurrentTabAs);
connect(ui->tabWidget, &QTabWidget::currentChanged, this, [this](int aTabIndex)
{
setWindowTitle(QString("Sanguine Studio - %1").arg(ui->tabWidget->tabPath(aTabIndex)));
GSCEditor* editor = dynamic_cast<GSCEditor*>(ui->tabWidget->widget(aTabIndex));
if (!editor) { return; }
editor->setCompleter(mCompleter);
mHighlighter->setDocument(editor->document());
});
connect(ui->tabWidget, &TabWidget::tabClosed, this, [this](const QString& aTabPath)
{
for (int i = 0; i < mFileRootItem->childCount(); i++)
{
QTreeWidgetItem* currentMatch = mFileRootItem->child(i);
const QString matchText = currentMatch->text(1);
if (aTabPath == matchText)
{
delete currentMatch;
}
}
});
QShortcut* shortcut = new QShortcut(QKeySequence(tr("Ctrl+S")), this);
connect(shortcut, &QShortcut::activated, this, [this]()
{
int tabIndex = ui->tabWidget->currentIndex();
if (tabIndex == -1) { return; }
QString tabText = ui->tabWidget->tabText(tabIndex);
if (!tabText.contains("*")) { return; }
GSCEditor* editor = dynamic_cast<GSCEditor*>(ui->tabWidget->widget(tabIndex));
if (!editor) { return; }
const QString filePath = editor->property("Path").toString();
saveFile(editor->toPlainText().toUtf8(), filePath);
ui->tabWidget->setTabText(tabIndex, tabText.replace("*", ""));
});
QSettings settings;
if (!settings.contains("folder_path"))
{
mFolderPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
}
else
{
mFolderPath = settings.value("folder_path").toString();
}
if (!settings.contains("open_gsc_path"))
{
mOpenGscPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
}
else
{
mOpenGscPath = settings.value("open_gsc_path").toString();
}
if (!settings.contains("open_txt_path"))
{
mOpenTxtPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
}
else
{
mOpenTxtPath = settings.value("open_txt_path").toString();
}
if (!settings.contains("new_gsc_path"))
{
mNewGscPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
}
else
{
mNewGscPath = settings.value("new_gsc_path").toString();
}
if (!settings.contains("new_txt_path"))
{
mNewTxtPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
}
else
{
mNewTxtPath = settings.value("new_txt_path").toString();
}
if (!settings.contains("recent_files"))
{
mRecentFiles = QStringList();
}
else
{
mRecentFiles = settings.value("recent_files").toStringList();
}
updateRecentFiles();
if (!settings.contains("recent_folders"))
{
mRecentFolders = QStringList();
}
else
{
mRecentFolders = settings.value("recent_folders").toStringList();
}
updateRecentFolders();
if (!settings.contains("pinned_files"))
{
mPinnedFiles = QStringList();
}
else
{
mPinnedFiles = settings.value("pinned_files").toStringList();
}
openPinnedFiles();
if (!settings.contains("pinned_folders"))
{
mPinnedFolders = QStringList();
}
else
{
mPinnedFolders = settings.value("pinned_folders").toStringList();
}
openPinnedFolders();
}
MainWindow::~MainWindow()
{
QSettings settings;
settings.setValue("folder_path", mFolderPath);
settings.setValue("open_gsc_path", mOpenGscPath);
settings.setValue("open_txt_path", mOpenTxtPath);
settings.setValue("new_gsc_path", mNewGscPath);
settings.setValue("new_txt_path", mNewTxtPath);
settings.setValue("recent_files", mRecentFiles);
settings.setValue("recent_folders", mRecentFolders);
settings.setValue("pinned_files", mPinnedFiles);
settings.setValue("pinned_folders", mPinnedFolders);
delete ui;
delete mFolderRootItem;
delete mFileRootItem;
delete mHighlighter;
delete mCompleter;
}
QAbstractItemModel* MainWindow::modelFromFile(const QString& fileName)
{
QFile file(fileName);
if (!file.open(QFile::ReadOnly))
return new QStringListModel(mCompleter);
QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
QStringList words;
while (!file.atEnd()) {
QByteArray line = file.readLine();
if (!line.isEmpty())
words << QString::fromUtf8(line.trimmed());
}
QGuiApplication::restoreOverrideCursor();
return new QStringListModel(words, mCompleter);
}
void MainWindow::openFile(const QString &aFilePath)
{
// Write file if it doesn't already exist
QFile gscFile(aFilePath);
if (!gscFile.open(QIODevice::ReadWrite))
{
qDebug() << "Failed to open" << aFilePath;
return;
}
const QString fileStem = aFilePath.split('/').last();
// If has children, check if they exist in the tree already
if (mFileRootItem->childCount())
{
for (int i = 0; i < mFileRootItem->childCount(); i++)
{
// Get child and check validity
QTreeWidgetItem* childItem = mFileRootItem->child(i);
if (!childItem) { continue; }
// Check if path contains previous gsc
if (fileStem == childItem->text(0))
{
qDebug() << fileStem << "already exists!";
return;
}
}
}
addRecentFile(aFilePath);
setWindowTitle(QString("Sanguine Studio - %1").arg(aFilePath));
QTreeWidgetItem *openFileItem = new QTreeWidgetItem(mFileRootItem);
openFileItem->setText(0, fileStem);
openFileItem->setText(1, aFilePath);
GSCEditor* newEditor = createEditor(fileStem, aFilePath);
newEditor->setSaveableText(QString::fromUtf8(gscFile.readAll()));
connect(newEditor, &GSCEditor::saveStatusChanged, ui->tabWidget, &TabWidget::refreshCurrentTab);
ui->treeWidget->expandItem(mFileRootItem);
}
void MainWindow::pinFile(const QString &aFilePath)
{
if (mPinnedFiles.contains(aFilePath)) { return; }
mPinnedFiles.push_back(aFilePath);
updatePinnedFiles();
}
void MainWindow::updatePinnedFiles()
{
for (int i = 0; i < mPinnedFiles.size(); i++)
{
int tabIndex = ui->tabWidget->tabIndexFromPath(mPinnedFiles[i]);
if (tabIndex != -1) {
ui->tabWidget->setTabIcon(tabIndex, mPinIcon);
}
QList<QTreeWidgetItem*> treeItems = ui->treeWidget->findItems(mPinnedFiles[i], Qt::MatchExactly, 1);
for (int i = 0; i < treeItems.size(); i++)
{
QTreeWidgetItem* currentItem = treeItems[i];
currentItem->setIcon(0, mPinIcon);
}
}
}
void MainWindow::openPinnedFiles()
{
for (int i = 0; i < mPinnedFiles.size(); i++)
{
openFile(mPinnedFiles[i]);
}
updatePinnedFiles();
}
void MainWindow::openSubFolder(const QString &aFolderPath, QTreeWidget *aTreeWidget, QTreeWidgetItem *aItem, QTreeWidgetItem* aRoot)
{
if (!aItem) { return; }
QDir dir(aFolderPath);
if (!dir.exists()) { return; }
dir.setFilter(QDir::Dirs | QDir::Files | QDir::NoSymLinks);
QFileInfoList list = dir.entryInfoList();
int size = list.size();
for (int i = 0; i < size; i++) {
QFileInfo info = list.at(i);
if (info.fileName() == "." || info.fileName() == "..") continue;
if (info.isDir()) {
QTreeWidgetItem *fileItem = new QTreeWidgetItem(QStringList() << info.fileName(), 0); //0 means directory
fileItem->setText(1, info.filePath());
if (aTreeWidget == nullptr)
{
aItem->addChild(fileItem);
}
else
{
aRoot->addChild(fileItem);
}
openSubFolder(info.filePath(), nullptr, fileItem, aRoot);
} else {
QTreeWidgetItem *fileItem = new QTreeWidgetItem(QStringList() << info.fileName(), 1); //1 means it is a file
fileItem->setText(1, info.filePath());
if (aTreeWidget == nullptr)
{
aItem->addChild(fileItem);
}
else
{
aRoot->addChild(fileItem);
}
}
}
}
void MainWindow::pinFolder(const QString &aFolderPath)
{
if (mPinnedFolders.contains(aFolderPath)) { return; }
mPinnedFolders.push_back(aFolderPath);
updatePinnedFolders();
}
void MainWindow::updatePinnedFolders()
{
for (int i = 0; i < mPinnedFolders.size(); i++)
{
}
}
void MainWindow::openPinnedFolders()
{
for (int i = 0; i < mPinnedFolders.size(); i++)
{
openFolder(mPinnedFolders[i]);
}
}
void MainWindow::addRecentFolder(const QString &aFolderPath)
{
// if in list, move to top
if (mRecentFolders.contains(aFolderPath))
{
mRecentFolders.removeOne(aFolderPath);
}
mRecentFolders.push_front(aFolderPath);
if (mRecentFolders.size() > MAX_RECENT_FOLDERS)
{
mRecentFolders.removeLast();
}
updateRecentFolders();
}
void MainWindow::updateRecentFolders()
{
if (mRecentFolders.isEmpty())
{
ui->actionRecent_Folders->setEnabled(false);
return;
}
ui->actionRecent_Folders->setEnabled(true);
QMenu* menu = ui->actionRecent_Folders->menu();
if (!menu)
{
menu = new QMenu();
ui->actionRecent_Folders->setMenu(menu);
}
else
{
menu->clear();
}
for (int i = 0; i < mRecentFolders.size(); i++)
{
const QString recentFolderPath = mRecentFolders[i];
QAction* recentFolderAction = new QAction(menu);
recentFolderAction->setText(QString("%1. %2").arg(i + 1).arg(recentFolderPath));
menu->addAction(recentFolderAction);
connect(recentFolderAction, &QAction::triggered, this, [recentFolderPath, this]()
{
openFolder(recentFolderPath);
});
}
}
void MainWindow::addRecentFile(const QString &aFilePath)
{
// if in list, move to top
if (mRecentFiles.contains(aFilePath))
{
mRecentFiles.removeOne(aFilePath);
}
mRecentFiles.push_front(aFilePath);
if (mRecentFiles.size() > MAX_RECENT_FILES)
{
mRecentFiles.removeLast();
}
updateRecentFiles();
}
void MainWindow::updateRecentFiles()
{
if (mRecentFiles.isEmpty())
{
ui->actionRecent_Files->setEnabled(false);
return;
}
ui->actionRecent_Files->setEnabled(true);
QMenu* menu = ui->actionRecent_Files->menu();
if (!menu)
{
menu = new QMenu();
ui->actionRecent_Files->setMenu(menu);
}
else
{
menu->clear();
}
for (int i = 0; i < mRecentFiles.size(); i++)
{
const QString recentFilePath = mRecentFiles[i];
QAction* recentFileAction = new QAction(menu);
recentFileAction->setText(QString("%1. %2").arg(i + 1).arg(recentFilePath));
menu->addAction(recentFileAction);
connect(recentFileAction, &QAction::triggered, this, [recentFilePath, this]()
{
openFile(recentFilePath);
});
}
}
void MainWindow::openFolder(const QString &aFolderPath)
{
QTreeWidgetItem* newFolderItem = new QTreeWidgetItem(mFolderRootItem);
newFolderItem->setText(0, mFolderPath.split('/').last());
addRecentFolder(mFolderPath);
openSubFolder(mFolderPath, ui->treeWidget, newFolderItem, newFolderItem);
ui->treeWidget->expandItem(mFolderRootItem);
}
void MainWindow::on_actionOpen_Folder_triggered()
{
QFileDialog fileDialog(this);
fileDialog.setFileMode(QFileDialog::Directory);
fileDialog.setWindowTitle("Open Folder");
fileDialog.setDirectory(mFolderPath);
fileDialog.setOptions(QFileDialog::ShowDirsOnly);
if (fileDialog.exec() == QDialog::Rejected) { return; }
mFolderPath = fileDialog.directory().path();
openFolder(mFolderPath);
ui->treeWidget->expandItem(mFolderRootItem);
}
void MainWindow::on_actionOpenGSC_File_gsc_triggered()
{
QFileDialog fileDialog(this);
fileDialog.setFileMode(QFileDialog::ExistingFile);
fileDialog.setWindowTitle("Open GSC File");
fileDialog.setNameFilter("Script Files (*.gsc *.csc *.gsh);;All Files (*.*)");
fileDialog.setDirectory(mOpenGscPath);
if (fileDialog.exec() == QDialog::Rejected) { return; }
const QStringList selectedFiles = fileDialog.selectedFiles();
mOpenGscPath = selectedFiles.first();
openFile(mOpenGscPath);
}
void MainWindow::on_actionOpenText_File_txt_triggered()
{
QFileDialog fileDialog(this);
fileDialog.setFileMode(QFileDialog::ExistingFile);
fileDialog.setWindowTitle("Open TXT File");
fileDialog.setNameFilter("TXT Files (*.txt);;All Files (*.*)");
fileDialog.setDirectory(mOpenTxtPath);
if (fileDialog.exec() == QDialog::Rejected) { return; }
const QStringList selectedFiles = fileDialog.selectedFiles();
mOpenTxtPath = selectedFiles.first();
openFile(mOpenTxtPath);
}
void MainWindow::on_actionNewGSC_File_gsc_triggered()
{
QFileDialog fileDialog(this);
fileDialog.setFileMode(QFileDialog::AnyFile);
fileDialog.setWindowTitle("New GSC File");
fileDialog.setNameFilter("Script Files (*.gsc *.csc *.gsh);;All Files (*.*)");
fileDialog.setDirectory(mNewGscPath);
if (fileDialog.exec() == QDialog::Rejected) { return; }
const QStringList selectedFiles = fileDialog.selectedFiles();
mNewGscPath = selectedFiles.first();
openFile(mNewGscPath);
}
void MainWindow::on_actionNewText_File_txt_triggered()
{
QFileDialog fileDialog(this);
fileDialog.setFileMode(QFileDialog::AnyFile);
fileDialog.setWindowTitle("New TXT File");
fileDialog.setNameFilter("TXT Files (*.txt);;All Files (*.*)");
fileDialog.setDirectory(mNewTxtPath);
if (fileDialog.exec() == QDialog::Rejected) { return; }
const QStringList selectedFiles = fileDialog.selectedFiles();
mNewTxtPath = selectedFiles.first();
openFile(mNewTxtPath);
}
void MainWindow::on_actionConsole_triggered()
{
}
void MainWindow::on_actionDeveloperMode_triggered()
{
DeveloperDialog devDialog;
devDialog.exec();
}
void MainWindow::on_actionFive_State_Modding_Website_triggered()
{
QDesktopServices::openUrl(QUrl("https://fivestarmodding.xyz/"));
}
void MainWindow::on_action5SM_Discord_triggered()
{
QDesktopServices::openUrl(QUrl("https://discord.gg/xSfEuQ67jV"));
}
void MainWindow::on_actionAbout_Sanguine_triggered()
{
AboutDialog aboutDialog;
aboutDialog.exec();
}
void MainWindow::on_actionRedLine_Solutions_Website_triggered()
{
QDesktopServices::openUrl(QUrl("https://redline.llc/"));
}
void MainWindow::on_treeWidget_itemSelectionChanged()
{
QTreeWidgetItem* selectedItem = ui->treeWidget->selectedItems().first();
const QString selectedText = selectedItem->text(0);
const QString selectedPath = selectedItem->text(1);
const QString fileStem = selectedPath.split('/').last();
if (!selectedText.contains(".gsc") && !selectedText.contains(".csc") && !selectedText.contains(".txt"))
{
return;
}
for (int i = 0; i < ui->tabWidget->count(); i++)
{
if (ui->tabWidget->tabText(i).replace("*", "") == selectedText)
{
ui->tabWidget->setCurrentIndex(i);
return;
}
}
// Write file if it doesn't already exist
QFile gscFile(selectedPath);
if (!gscFile.open(QIODevice::ReadWrite))
{
qDebug() << "Failed to open" << selectedPath;
return;
}
GSCEditor* newEditor = createEditor(selectedText, selectedPath);
newEditor->setSaveableText(QString::fromUtf8(gscFile.readAll()));
newEditor->setCompleter(mCompleter);
mHighlighter->setDocument(newEditor->document());
connect(newEditor, &GSCEditor::saveStatusChanged, ui->tabWidget, &TabWidget::refreshCurrentTab);
}
GSCEditor* MainWindow::createEditor(QString aTabName, QString aPath)
{
GSCEditor* newEditor = new GSCEditor();
newEditor->setProperty("Path", QVariant::fromValue(aPath));
newEditor->setCompleter(mCompleter);
mHighlighter->setDocument(newEditor->document());
int tabIndex = ui->tabWidget->addTab(newEditor, aTabName);
ui->tabWidget->setCurrentIndex(tabIndex);
return newEditor;
}
void MainWindow::saveFile(const QByteArray& aData, const QString aFilePath)
{
QFile saveFile(aFilePath);
if (!saveFile.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text))
{
qDebug() << "ERROR: Failed to open file " << aFilePath;
return;
}
saveFile.write(aData);
}
void MainWindow::on_tabWidget_customContextMenuRequested(const QPoint &pos)
{
int tabIndex = ui->tabWidget->tabBar()->tabAt(pos);
if (tabIndex == -1) { return; }
QAction *closeFileAction = new QAction(this);
closeFileAction->setText("Close File");
connect(closeFileAction, &QAction::triggered, this, [this, tabIndex]()
{
ui->tabWidget->closeTab(tabIndex);
});
QAction *saveFileAction = new QAction(this);
saveFileAction->setText("Save File");
connect(saveFileAction, &QAction::triggered, this, [this, tabIndex]()
{
ui->tabWidget->saveTab(tabIndex);
});
QAction *saveFileAsAction = new QAction(this);
saveFileAsAction->setText("Save Files As");
connect(saveFileAsAction, &QAction::triggered, this, [this, tabIndex]()
{
ui->tabWidget->saveTabAs(tabIndex);
});
QAction *pinFileAction = new QAction(this);
pinFileAction->setText("Pin File");
connect(pinFileAction, &QAction::triggered, this, [this, tabIndex]()
{
pinFile(ui->tabWidget->tabPath(tabIndex));
});
if (!ui->tabWidget->tabText(tabIndex).contains("*"))
{
saveFileAction->setEnabled(false);
}
QMenu *tabWidgetContextMenu = new QMenu;
tabWidgetContextMenu->addAction(closeFileAction);
tabWidgetContextMenu->addSeparator();
tabWidgetContextMenu->addAction(saveFileAction);
tabWidgetContextMenu->addAction(saveFileAsAction);
tabWidgetContextMenu->addSeparator();
tabWidgetContextMenu->addAction(pinFileAction);
tabWidgetContextMenu->exec(QCursor::pos());
}
void MainWindow::on_actionRemove_Comments_triggered()
{
}
void MainWindow::on_actionBeautify_Code_triggered()
{
}
void MainWindow::on_actionClear_Saved_Data_triggered()
{
mFolderPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
mOpenGscPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
mOpenTxtPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
mNewGscPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
mNewTxtPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
mRecentFiles = QStringList();
mRecentFolders = QStringList();
ui->tabWidget->closeAllTabs();
QApplication::quit();
}
void MainWindow::on_treeWidget_customContextMenuRequested(const QPoint &pos)
{
QTreeWidgetItem* item = ui->treeWidget->itemAt(pos);
if (!item) { return; }
const QString selectedText = item->text(0);
const QString selectedPath = item->text(1);
const QString fileStem = selectedPath.split('/').last();
if (!selectedText.contains(".gsc") && !selectedText.contains(".csc") && !selectedText.contains(".txt"))
{
return;
}
int tabIndex = -1;
for (int i = 0; i < ui->tabWidget->count(); i++)
{
if (ui->tabWidget->tabPath(i) == selectedPath)
{
tabIndex = i;
break;
}
}
QAction *closeFileAction = new QAction(this);
closeFileAction->setText("Close File");
connect(closeFileAction, &QAction::triggered, this, [this, tabIndex, &item]()
{
if (ui->tabWidget->closeTab(tabIndex))
{
delete item;
}
});
QAction *saveFileAction = new QAction(this);
saveFileAction->setText("Save File");
connect(saveFileAction, &QAction::triggered, this, [this, tabIndex]()
{
ui->tabWidget->saveTab(tabIndex);
});
QAction *saveFileAsAction = new QAction(this);
saveFileAsAction->setText("Save Files As");
connect(saveFileAsAction, &QAction::triggered, this, [this, tabIndex]()
{
ui->tabWidget->saveTabAs(tabIndex);
});
QAction *pinFileAction = new QAction(this);
pinFileAction->setText("Pin File");
connect(pinFileAction, &QAction::triggered, this, [this, tabIndex]()
{
pinFile(ui->tabWidget->tabPath(tabIndex));
});
if (!ui->tabWidget->tabText(tabIndex).contains("*"))
{
saveFileAction->setEnabled(false);
}
QMenu *tabWidgetContextMenu = new QMenu;
tabWidgetContextMenu->addAction(closeFileAction);
tabWidgetContextMenu->addSeparator();
tabWidgetContextMenu->addAction(saveFileAction);
tabWidgetContextMenu->addAction(saveFileAsAction);
tabWidgetContextMenu->addSeparator();
tabWidgetContextMenu->addAction(pinFileAction);
tabWidgetContextMenu->exec(QCursor::pos());
}