From 3fa4eb0dfc10c8ec8626611469623afbb7eb7db1 Mon Sep 17 00:00:00 2001 From: ouuan Date: Thu, 9 Jan 2020 16:15:39 +0800 Subject: [PATCH] Some changes about tabs (#71) * fix(appwindow.cpp): change competitive companion logic Use the competitive companion logic introduced in 22af606 and 1b1058a (don't open new tabs). * style: format codes * fix & refactor(openfile): use openFile() for opening files Use AppWindow::openFile() for opening files. Thus fix bug of opening files via command line arguments / drag & drop. Duplicate files were not detected when opening via these two ways before. * feat(appwindow.cpp): show file path in window title * feat: change to the tab while close confirming * fix(appwindow.cpp): change window title after closing all tabs * fix(appwindow): move openFile() to private * refactor(appwindow.cpp): use openFile() for new tab * fix: fix bugs when all tabs are closed Segment fault in the following situations are fixed: 1. all tabs are closed, apply settings in preference window 2. all tabs are closed, received a competitive companion request After fixing, if all tabs are closed and a competitive companion request is received, a new tab will be opened for it. * feat: use "untitled" instead of "Unsaved file" This is the same as many other text editors, for example, Sublime Text. "Unsaved file" is a little confusing, it may be understood as "file with unsaved changes". * feat & refactor: add Save All and refactor codes * fix: fix a segmentation fault To reproduce the segmentation fault: 1. open the editor 2. open a new tab (then there are two tabs, and the current tab is the second one) 3. close the first tab If you switch to the first tab and close the second one, everything works well. It's caused by not updated windowIndex. It's fixed by updating windowsIndex in onTabChanged(). * feat(appwindow.cpp): make tabs movable * refactor: use pointer in signal and tabWidget->indexOf() --- include/SettingsManager.hpp | 4 +- include/appwindow.hpp | 16 ++- include/mainwindow.hpp | 7 +- src/CompanionServer.cpp | 12 +- src/SettingsManager.cpp | 18 ++- src/appwindow.cpp | 237 +++++++++++++++++------------------- src/main.cc | 8 +- src/mainwindow.cc | 23 ++-- ui/appwindow.ui | 18 ++- 9 files changed, 177 insertions(+), 166 deletions(-) diff --git a/include/SettingsManager.hpp b/include/SettingsManager.hpp index a8462f158..52c5b9e1d 100644 --- a/include/SettingsManager.hpp +++ b/include/SettingsManager.hpp @@ -29,7 +29,8 @@ namespace Settings { -enum ViewMode{ +enum ViewMode +{ FULL_EDITOR, FULL_IO, SPLIT @@ -190,7 +191,6 @@ class SettingManager QKeySequence getHotkeyCompile(); QKeySequence getHotkeyViewModeToggler(); - private: QString mSettingsFile; QSettings *mSettings; diff --git a/include/appwindow.hpp b/include/appwindow.hpp index 0b7512fc9..d6e250375 100644 --- a/include/appwindow.hpp +++ b/include/appwindow.hpp @@ -19,7 +19,7 @@ class AppWindow : public QMainWindow public: explicit AppWindow(QWidget *parent = nullptr); - explicit AppWindow(QVector tabs, QWidget *parent = nullptr); + explicit AppWindow(QStringList args, QWidget *parent = nullptr); ~AppWindow() override; void closeEvent(QCloseEvent *event) override; @@ -47,13 +47,15 @@ class AppWindow : public QMainWindow void on_actionSave_triggered(); - void on_actionSave_as_triggered(); + void on_actionSave_As_triggered(); + + void on_actionSave_All_triggered(); void on_actionCheck_for_updates_triggered(); void onTabCloseRequested(int); void onTabChanged(int); - void onEditorTextChanged(bool); + void onEditorTextChanged(bool, MainWindow *); void onSaveTimerElapsed(); void onSettingsApplied(); void onSplitterMoved(int, int); @@ -78,11 +80,12 @@ class AppWindow : public QMainWindow void on_actionSplit_Mode_triggered(); -private: + void on_confirmTriggered(MainWindow *widget); + + private: Ui::AppWindow *ui; MessageLogger *activeLogger = nullptr; QTimer *timer = nullptr; - QMetaObject::Connection activeTextChangeConnections; QMetaObject::Connection activeSplitterMoveConnections; QMetaObject::Connection companionEditorConnections; Settings::SettingManager *settingManager = nullptr; @@ -97,6 +100,9 @@ class AppWindow : public QMainWindow void saveSettings(); QVector hotkeyObjects; void maybeSetHotkeys(); + void closeAll(); + bool closeTab(int index); + void openFile(QString fileName); }; #endif // APPWINDOW_HPP diff --git a/include/mainwindow.hpp b/include/mainwindow.hpp index 623c0a633..7d09c71ab 100644 --- a/include/mainwindow.hpp +++ b/include/mainwindow.hpp @@ -53,9 +53,7 @@ class MainWindow : public QMainWindow void save(bool force); void saveAs(); - int windowIndeX() const; - - bool closeChangedConfirm(); + bool closeConfirm(); void killProcesses(); void detachedExecution(); @@ -98,7 +96,8 @@ class MainWindow : public QMainWindow void on_changeLanguageButoon_clicked(); signals: - void editorTextChanged(bool isUnsaved); + void editorTextChanged(bool isUnsaved, MainWindow *widget); + void confirmTriggered(MainWindow *widget); private: const int windowIndex; diff --git a/src/CompanionServer.cpp b/src/CompanionServer.cpp index 52a4cc2ac..ceaad7dc6 100644 --- a/src/CompanionServer.cpp +++ b/src/CompanionServer.cpp @@ -44,7 +44,8 @@ void CompanionServer::updatePort(int port) // server->setMaxPendingConnections(1); QObject::connect(server, SIGNAL(newConnection()), this, SLOT(onNewConnection())); server->listen(QHostAddress::LocalHost, static_cast(port)); - log->warn("Companion", "Port changed to " + std::to_string(port)); + if (log != nullptr) + log->warn("Companion", "Port changed to " + std::to_string(port)); } CompanionServer::~CompanionServer() @@ -66,7 +67,8 @@ void CompanionServer::onReadReady() if (request.startsWith("POST") && request.contains("Content-Type: application/json")) { - log->info("Companion", "Got a POST Request"); + if (log != nullptr) + log->info("Companion", "Got a POST Request"); socket->write("HTTP/1.1 OK\r\n"); // \r needs to be before \n socket->write("Content-Type: text/html\r\n"); @@ -107,12 +109,14 @@ void CompanionServer::onReadReady() } else { - log->error("Companion", "JSONParser reported errors. \n" + error.errorString().toStdString(), true); + if (log != nullptr) + log->error("Companion", "JSONParser reported errors. \n" + error.errorString().toStdString(), true); } } else { - log->warn("Companion", "An Invalid Payload was delivered on the listening port"); + if (log != nullptr) + log->warn("Companion", "An Invalid Payload was delivered on the listening port"); socket->write("HTTP/1.1 OK\r\n"); // \r needs to be before \n socket->write("Content-Type: text/html\r\n"); socket->write("Connection: close\r\n"); diff --git a/src/SettingsManager.cpp b/src/SettingsManager.cpp index c7d047ae1..db6257bda 100644 --- a/src/SettingsManager.cpp +++ b/src/SettingsManager.cpp @@ -374,17 +374,23 @@ void SettingManager::setHotkeyFormat(QKeySequence sequence) ViewMode SettingManager::getViewMode() { QString strings = mSettings->value("view_mode", "split").toString(); - if(strings == "split") return Settings::ViewMode::SPLIT; - else if(strings == "code") return Settings::ViewMode::FULL_EDITOR; - else return Settings::ViewMode::FULL_IO; + if (strings == "split") + return Settings::ViewMode::SPLIT; + else if (strings == "code") + return Settings::ViewMode::FULL_EDITOR; + else + return Settings::ViewMode::FULL_IO; } void SettingManager::setViewMode(ViewMode v) { QString ans; - if(v == Settings::FULL_EDITOR) ans = "code"; - else if(v == Settings::FULL_IO) ans = "io"; - else ans = "split"; + if (v == Settings::FULL_EDITOR) + ans = "code"; + else if (v == Settings::FULL_IO) + ans = "io"; + else + ans = "split"; mSettings->setValue("view_mode", ans); } diff --git a/src/appwindow.cpp b/src/appwindow.cpp index 845a46815..db4e5af6c 100644 --- a/src/appwindow.cpp +++ b/src/appwindow.cpp @@ -9,32 +9,21 @@ #include #include -AppWindow::AppWindow(QVector tabs, QWidget *parent) : AppWindow(parent) +AppWindow::AppWindow(QStringList args, QWidget *parent) : AppWindow(parent) { - - if (tabs.size() > 0) - { - ui->tabWidget->clear(); - int i = 0; - for (auto e : tabs) - { - ui->tabWidget->addTab(e, e->fileName()); - QString lang = "Cpp"; - if (e->fileName().endsWith(".java")) - lang = "Java"; - else if (e->fileName().endsWith(".py") || e->fileName().endsWith("py3")) - lang = "Python"; - e->setLanguage(lang); - ui->tabWidget->setCurrentIndex(i); - i++; - } - } + ui->tabWidget->clear(); + if (args.size() > 1) + for (int i = 1; i < args.size(); ++i) + openFile(args[i]); + else + openFile(""); } AppWindow::AppWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::AppWindow) { ui->setupUi(this); ui->tabWidget->clear(); + ui->tabWidget->setMovable(true); setAcceptDrops(true); allocate(); @@ -66,15 +55,7 @@ AppWindow::~AppWindow() void AppWindow::closeEvent(QCloseEvent *event) { - for (int t = 0; t < ui->tabWidget->count(); t++) - { - auto tmp = dynamic_cast(ui->tabWidget->widget(t)); - if (tmp->closeChangedConfirm()) - { - ui->tabWidget->removeTab(t); - t--; - } - } + closeAll(); if (ui->tabWidget->count() == 0) event->accept(); else @@ -92,20 +73,10 @@ void AppWindow::dragEnterEvent(QDragEnterEvent *event) void AppWindow::dropEvent(QDropEvent *event) { auto files = event->mimeData()->urls(); - int t = ui->tabWidget->count(); for (auto e : files) { auto fileName = e.toLocalFile(); - auto fsp = new MainWindow(ui->tabWidget->count(), fileName); - QString lang = "Cpp"; - if (fileName.endsWith(".java")) - lang = "Java"; - else if (fileName.endsWith(".py") || fileName.endsWith(".py3")) - lang = "Python"; - ui->tabWidget->addTab(fsp, fsp->fileName()); - fsp->setLanguage(lang); - ui->tabWidget->setCurrentIndex(t); - t++; + openFile(fileName); } } @@ -140,16 +111,15 @@ void AppWindow::applySettings() ui->actionAutosave->setChecked(settingManager->isAutoSave()); Settings::ViewMode mode = settingManager->getViewMode(); - - switch(mode) + switch (mode) { - case Settings::ViewMode::FULL_EDITOR : + case Settings::ViewMode::FULL_EDITOR: on_actionEditor_Mode_triggered(); break; - case Settings::ViewMode::FULL_IO : + case Settings::ViewMode::FULL_IO: on_actionIO_Mode_triggered(); break; - case Settings::ViewMode::SPLIT : + case Settings::ViewMode::SPLIT: on_actionSplit_Mode_triggered(); } @@ -203,11 +173,29 @@ void AppWindow::maybeSetHotkeys() hotkeyObjects.push_back( new QShortcut(settingManager->getHotkeyKill(), this, SLOT(on_actionKill_Processes_triggered()))); } - if(!settingManager->getHotkeyViewModeToggler().isEmpty()) + if (!settingManager->getHotkeyViewModeToggler().isEmpty()) { hotkeyObjects.push_back( - new QShortcut(settingManager->getHotkeyViewModeToggler(), this, SLOT(onViewModeToggle()))); + new QShortcut(settingManager->getHotkeyViewModeToggler(), this, SLOT(onViewModeToggle()))); + } +} + +void AppWindow::closeAll() +{ + for (int t = 0; t < ui->tabWidget->count(); t++) + if (closeTab(t)) + --t; +} + +bool AppWindow::closeTab(int index) +{ + auto tmp = dynamic_cast(ui->tabWidget->widget(index)); + if (tmp->closeConfirm()) + { + ui->tabWidget->removeTab(index); + return true; } + return false; } void AppWindow::saveSettings() @@ -217,6 +205,35 @@ void AppWindow::saveSettings() settingManager->setMaximizedWindow(this->isMaximized()); } +void AppWindow::openFile(QString fileName) +{ + if (!fileName.isEmpty()) + { + for (int t = 0; t < ui->tabWidget->count(); t++) + { + auto tmp = dynamic_cast(ui->tabWidget->widget(t)); + if (fileName == tmp->filePath()) + { + ui->tabWidget->setCurrentIndex(t); + return; + } + } + } + + int t = ui->tabWidget->count(); + auto fsp = new MainWindow(t, fileName); + connect(fsp, SIGNAL(confirmTriggered(MainWindow *)), this, SLOT(on_confirmTriggered(MainWindow *))); + connect(fsp, SIGNAL(editorTextChanged(bool, MainWindow *)), this, SLOT(onEditorTextChanged(bool, MainWindow *))); + QString lang = "Cpp"; + if (fileName.endsWith(".java")) + lang = "Java"; + else if (fileName.endsWith(".py") || fileName.endsWith(".py3")) + lang = "Python"; + ui->tabWidget->addTab(fsp, fsp->fileName()); + fsp->setLanguage(lang); + ui->tabWidget->setCurrentIndex(t); +} + /***************** ABOUT SECTION ***************************/ void AppWindow::on_actionSupport_me_triggered() @@ -243,15 +260,7 @@ void AppWindow::on_actionAbout_triggered() void AppWindow::on_actionClose_All_triggered() { - for (int t = 0; t < ui->tabWidget->count(); t++) - { - auto tmp = dynamic_cast(ui->tabWidget->widget(t)); - if (tmp->closeChangedConfirm()) - { - ui->tabWidget->removeTab(t); - t--; - } - } + closeAll(); } void AppWindow::on_actionAutosave_triggered(bool checked) @@ -265,24 +274,14 @@ void AppWindow::on_actionAutosave_triggered(bool checked) void AppWindow::on_actionQuit_triggered() { - for (int t = 0; t < ui->tabWidget->count(); t++) - { - auto tmp = dynamic_cast(ui->tabWidget->widget(t)); - if (tmp->closeChangedConfirm()) - { - ui->tabWidget->removeTab(t); - t--; - } - } + closeAll(); if (ui->tabWidget->count() == 0) QApplication::exit(); } void AppWindow::on_actionNew_Tab_triggered() { - auto temp = new MainWindow(ui->tabWidget->count(), ""); - ui->tabWidget->addTab(temp, temp->fileName()); - ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + openFile(""); } void AppWindow::on_actionOpen_triggered() @@ -292,26 +291,7 @@ void AppWindow::on_actionOpen_triggered() if (fileName.isEmpty()) return; - QString lang = "Cpp"; - if (fileName.endsWith(".java")) - lang = "Java"; - else if (fileName.endsWith(".py") || fileName.endsWith(".py3")) - lang = "Python"; - - for (int t = 0; t < ui->tabWidget->count(); t++) - { - auto tmp = dynamic_cast(ui->tabWidget->widget(t)); - if (fileName == tmp->filePath()) - { - ui->tabWidget->setCurrentIndex(t); - return; - } - } - - auto tmp = new MainWindow(ui->tabWidget->count(), fileName); - ui->tabWidget->addTab(tmp, tmp->fileName()); - tmp->setLanguage(lang); - ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + openFile(fileName); } void AppWindow::on_actionSave_triggered() @@ -319,15 +299,22 @@ void AppWindow::on_actionSave_triggered() int currentIdx = ui->tabWidget->currentIndex(); auto tmp = dynamic_cast(ui->tabWidget->widget(currentIdx)); tmp->save(true); - onEditorTextChanged(false); } -void AppWindow::on_actionSave_as_triggered() +void AppWindow::on_actionSave_As_triggered() { int currentIdx = ui->tabWidget->currentIndex(); auto tmp = dynamic_cast(ui->tabWidget->widget(currentIdx)); tmp->saveAs(); - onEditorTextChanged(false); +} + +void AppWindow::on_actionSave_All_triggered() +{ + for (int t = 0; t < ui->tabWidget->count(); ++t) + { + auto tmp = dynamic_cast(ui->tabWidget->widget(t)); + tmp->save(true); + } } /************************ PREFERENCES SECTION **********************/ @@ -354,10 +341,7 @@ void AppWindow::on_actionSettings_triggered() void AppWindow::onTabCloseRequested(int index) { - // splitterState.clear(); - auto tmp = dynamic_cast(ui->tabWidget->widget(index)); - if (tmp->closeChangedConfirm()) - ui->tabWidget->removeTab(index); + closeTab(index); } void AppWindow::onTabChanged(int index) @@ -366,14 +350,19 @@ void AppWindow::onTabChanged(int index) { activeLogger = nullptr; server->setMessageLogger(nullptr); + setWindowTitle("CP Editor: Competitive Programmers Editor"); return; } - disconnect(activeTextChangeConnections); disconnect(activeSplitterMoveConnections); auto tmp = dynamic_cast(ui->tabWidget->widget(index)); + if (tmp->getOpenFile() == nullptr) + setWindowTitle("CP Editor: " + tmp->fileName()); + else + setWindowTitle("CP Editor: " + tmp->filePath()); + activeLogger = tmp->getLogger(); server->setMessageLogger(activeLogger); @@ -383,27 +372,20 @@ void AppWindow::onTabChanged(int index) if (!splitterState.isEmpty()) tmp->getSplitter()->restoreState(splitterState); - activeTextChangeConnections = connect(tmp, SIGNAL(editorTextChanged(bool)), this, SLOT(onEditorTextChanged(bool))); activeSplitterMoveConnections = connect(tmp->getSplitter(), SIGNAL(splitterMoved(int, int)), this, SLOT(onSplitterMoved(int, int))); } -void AppWindow::onEditorTextChanged(bool isUnsaved) +void AppWindow::onEditorTextChanged(bool isUnsaved, MainWindow *widget) { - auto current = ui->tabWidget->currentIndex(); + int index = ui->tabWidget->indexOf(widget); + if (index == -1) + return; + auto name = dynamic_cast(ui->tabWidget->widget(index))->fileName(); if (isUnsaved) - { - if (!ui->tabWidget->tabText(current).endsWith("*")) - ui->tabWidget->setTabText(current, ui->tabWidget->tabText(current) + "*"); - } + ui->tabWidget->setTabText(index, name + " *"); else - { - if (ui->tabWidget->tabText(current).endsWith("*")) - { - auto name = dynamic_cast(ui->tabWidget->widget(current))->fileName(); - ui->tabWidget->setTabText(current, name); - } - } + ui->tabWidget->setTabText(index, name); } void AppWindow::onSaveTimerElapsed() @@ -447,31 +429,33 @@ void AppWindow::onSettingsApplied() void AppWindow::onIncomingCompanionRequest(Network::CompanionData data) { - auto newTab = new MainWindow(ui->tabWidget->currentIndex(), ""); - newTab->setSettingsData(settingManager->toData()); - newTab->maybeLoadTemplate(); - newTab->applyCompanion(data); - ui->tabWidget->addTab(newTab, newTab->fileName()); - ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + auto current = ui->tabWidget->currentIndex(); + if (current == -1) + { + openFile(""); + current = 0; + } + auto tmp = dynamic_cast(ui->tabWidget->widget(current)); + tmp->applyCompanion(data); } -void AppWindow::onViewModeToggle(){ - if(ui->actionEditor_Mode->isChecked()) +void AppWindow::onViewModeToggle() +{ + if (ui->actionEditor_Mode->isChecked()) { on_actionIO_Mode_triggered(); - return ; + return; } - if(ui->actionSplit_Mode->isChecked()) + if (ui->actionSplit_Mode->isChecked()) { on_actionEditor_Mode_triggered(); - return ; + return; } - if(ui->actionIO_Mode->isChecked()) + if (ui->actionIO_Mode->isChecked()) { on_actionSplit_Mode_triggered(); - return ; + return; } - } void AppWindow::onSplitterMoved(int _, int __) @@ -555,3 +539,10 @@ void AppWindow::on_actionSplit_Mode_triggered() ui->actionSplit_Mode->setChecked(true); onTabChanged(ui->tabWidget->currentIndex()); } + +void AppWindow::on_confirmTriggered(MainWindow *widget) +{ + int index = ui->tabWidget->indexOf(widget); + if (index != -1) + ui->tabWidget->setCurrentIndex(index); +} diff --git a/src/main.cc b/src/main.cc index b6646ca59..e4d86ff8f 100644 --- a/src/main.cc +++ b/src/main.cc @@ -24,13 +24,9 @@ int main(int argc, char *argv[]) { QApplication a(argc, argv); QStringList args = a.arguments(); - QVector windows; - if (args.size() > 1) - for (int t = 1; t < args.size(); t++) - windows.push_back(new MainWindow(t - 1, args[t])); - AppWindow w(windows); - w.setWindowTitle("CP Editor: Competitive Programmers Editor"); + AppWindow w(args); w.show(); + return a.exec(); } diff --git a/src/mainwindow.cc b/src/mainwindow.cc index 21dedde75..49fa94e97 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -278,7 +278,7 @@ void MainWindow::saveTests() QString MainWindow::fileName() const { - return openFile == nullptr || !openFile->isOpen() ? "Unsaved file" : QFileInfo(*openFile).fileName(); + return openFile == nullptr || !openFile->isOpen() ? "untitled" : QFileInfo(*openFile).fileName(); } QString MainWindow::filePath() const @@ -367,20 +367,20 @@ void MainWindow::setSettingsData(Settings::SettingsData data) else editor->setWordWrapMode(QTextOption::NoWrap); - if(data.viewMode == Settings::ViewMode::FULL_EDITOR) + if (data.viewMode == Settings::ViewMode::FULL_EDITOR) { ui->splitter->restoreState(""); - ui->splitter->setSizes({1,0}); + ui->splitter->setSizes({1, 0}); } - else if(data.viewMode == Settings::ViewMode::FULL_IO) + else if (data.viewMode == Settings::ViewMode::FULL_IO) { ui->splitter->restoreState(""); - ui->splitter->setSizes({0,1}); + ui->splitter->setSizes({0, 1}); } else { ui->splitter->restoreState(""); - ui->splitter->setSizes({1,1}); + ui->splitter->setSizes({1, 1}); } compiler->updateCommandCpp(data.compileCommandCpp); @@ -447,14 +447,9 @@ void MainWindow::saveAs() } } -int MainWindow::windowIndeX() const -{ - return windowIndex; -} - void MainWindow::onTextChangedTriggered() { - emit editorTextChanged(isTextChanged()); + emit editorTextChanged(isTextChanged(), this); } void MainWindow::on_compile_clicked() @@ -780,6 +775,7 @@ bool MainWindow::saveFile(bool force, std::string head) { if (force) { + emit confirmTriggered(this); auto filename = QFileDialog::getSaveFileName( this, tr("Save File"), "", "Source Files (*.cpp *.hpp *.h *.cc *.cxx *.c *.py *.py3 *.java)"); if (filename.isEmpty()) @@ -847,12 +843,13 @@ bool MainWindow::isTextChanged() return true; } -bool MainWindow::closeChangedConfirm() +bool MainWindow::closeConfirm() { bool isChanged = isTextChanged(); bool confirmed = !isChanged; if (!confirmed) { + emit confirmTriggered(this); auto res = QMessageBox::warning(this, "Save?", fileName() + " has been modified.\nDo you want to save your changes?", QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel); diff --git a/ui/appwindow.ui b/ui/appwindow.ui index 3f2a92ae5..796130a8f 100644 --- a/ui/appwindow.ui +++ b/ui/appwindow.ui @@ -68,7 +68,8 @@ - + + @@ -136,9 +137,9 @@ Ctrl+S - + - Save as + Save As Save as new file @@ -147,6 +148,17 @@ Ctrl+Shift+S + + + Save All + + + Save all opened files + + + Ctrl+Alt+Shift+S + + true