aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsaturneric <[email protected]>2024-11-24 20:17:43 +0000
committersaturneric <[email protected]>2024-11-24 20:17:43 +0000
commitb4e556d834a0c1e1cedbbda0a91cd892f9105d20 (patch)
tree6fe99ac300a8643daa7177b8f83820acb097f9aa
parentfix: solve key list drag and drop refreshing issue (diff)
downloadGpgFrontend-b4e556d834a0c1e1cedbbda0a91cd892f9105d20.tar.gz
GpgFrontend-b4e556d834a0c1e1cedbbda0a91cd892f9105d20.zip
feat: allow drag and drop to open text file
-rw-r--r--src/core/function/basic/ChannelObject.cpp2
-rw-r--r--src/ui/main_window/MainWindow.cpp3
-rw-r--r--src/ui/main_window/MainWindowSlotFunction.cpp17
-rw-r--r--src/ui/widgets/InfoBoardWidget.cpp3
-rw-r--r--src/ui/widgets/InfoBoardWidget.h4
-rw-r--r--src/ui/widgets/PlainTextEditorPage.cpp3
-rw-r--r--src/ui/widgets/TextEdit.cpp204
-rw-r--r--src/ui/widgets/TextEdit.h99
-rw-r--r--src/ui/widgets/TextEditTabWidget.cpp242
-rw-r--r--src/ui/widgets/TextEditTabWidget.h118
10 files changed, 474 insertions, 221 deletions
diff --git a/src/core/function/basic/ChannelObject.cpp b/src/core/function/basic/ChannelObject.cpp
index 39a5568b..27999f5e 100644
--- a/src/core/function/basic/ChannelObject.cpp
+++ b/src/core/function/basic/ChannelObject.cpp
@@ -28,8 +28,6 @@
#include "ChannelObject.h"
-#include <iostream>
-
namespace GpgFrontend {
ChannelObject::ChannelObject() noexcept = default;
diff --git a/src/ui/main_window/MainWindow.cpp b/src/ui/main_window/MainWindow.cpp
index 8e5e1d03..597697f4 100644
--- a/src/ui/main_window/MainWindow.cpp
+++ b/src/ui/main_window/MainWindow.cpp
@@ -39,6 +39,7 @@
#include "ui/struct/settings_object/KeyServerSO.h"
#include "ui/widgets/KeyList.h"
#include "ui/widgets/TextEdit.h"
+#include "ui/widgets/TextEditTabWidget.h"
namespace GpgFrontend::UI {
@@ -97,7 +98,7 @@ void MainWindow::Init() noexcept {
UISignalStation::GetInstance(),
&UISignalStation::SignalKeyDatabaseRefresh);
- connect(edit_->tab_widget_, &QTabWidget::currentChanged, this,
+ connect(edit_->tab_widget_, &TextEditTabWidget::currentChanged, this,
&MainWindow::slot_disable_tab_actions);
connect(UISignalStation::GetInstance(),
&UISignalStation::SignalRefreshStatusBar, this,
diff --git a/src/ui/main_window/MainWindowSlotFunction.cpp b/src/ui/main_window/MainWindowSlotFunction.cpp
index cea105b7..9a8e57d0 100644
--- a/src/ui/main_window/MainWindowSlotFunction.cpp
+++ b/src/ui/main_window/MainWindowSlotFunction.cpp
@@ -276,23 +276,6 @@ void MainWindow::upload_key_to_server() {
}
void MainWindow::SlotOpenFile(const QString& path) {
- QFileInfo const info(path);
- if (!info.isFile() || !info.isReadable()) {
- QMessageBox::critical(
- this, tr("Error"),
- tr("Cannot open this file. Please make sure that this "
- "is a regular file and it's readable."));
- return;
- }
-
- if (info.size() > static_cast<qint64>(1024 * 1024)) {
- QMessageBox::critical(
- this, tr("Error"),
- tr("Cannot open this file. The file is TOO LARGE (>1MB) for "
- "GpgFrontend Text Editor."));
- return;
- }
-
edit_->SlotOpenFile(path);
}
diff --git a/src/ui/widgets/InfoBoardWidget.cpp b/src/ui/widgets/InfoBoardWidget.cpp
index 21a6c08a..92b22452 100644
--- a/src/ui/widgets/InfoBoardWidget.cpp
+++ b/src/ui/widgets/InfoBoardWidget.cpp
@@ -32,6 +32,7 @@
#include "core/model/SettingsObject.h"
#include "ui/UISignalStation.h"
#include "ui/struct/settings_object/AppearanceSO.h"
+#include "ui/widgets/TextEditTabWidget.h"
#include "ui_InfoBoard.h"
namespace GpgFrontend::UI {
@@ -103,7 +104,7 @@ void InfoBoardWidget::AssociateTextEdit(QTextEdit* edit) {
connect(edit, &QTextEdit::textChanged, this, &InfoBoardWidget::SlotReset);
}
-void InfoBoardWidget::AssociateTabWidget(QTabWidget* tab) {
+void InfoBoardWidget::AssociateTabWidget(TextEditTabWidget* tab) {
m_text_page_ = nullptr;
m_tab_widget_ = tab;
connect(tab, &QTabWidget::tabBarClicked, this, &InfoBoardWidget::SlotReset);
diff --git a/src/ui/widgets/InfoBoardWidget.h b/src/ui/widgets/InfoBoardWidget.h
index b124cea2..1dfe7a52 100644
--- a/src/ui/widgets/InfoBoardWidget.h
+++ b/src/ui/widgets/InfoBoardWidget.h
@@ -46,6 +46,8 @@ typedef enum {
INFO_ERROR_NEUTRAL = 3,
} InfoBoardStatus;
+class TextEditTabWidget;
+
/**
* @brief Class for handling the verify label shown at bottom of a textedit-page
*/
@@ -72,7 +74,7 @@ class InfoBoardWidget : public QWidget {
*
* @param tab
*/
- void AssociateTabWidget(QTabWidget* tab);
+ void AssociateTabWidget(TextEditTabWidget* tab);
/**
* @brief
diff --git a/src/ui/widgets/PlainTextEditorPage.cpp b/src/ui/widgets/PlainTextEditorPage.cpp
index 6e92b5b6..bd1c9837 100644
--- a/src/ui/widgets/PlainTextEditorPage.cpp
+++ b/src/ui/widgets/PlainTextEditorPage.cpp
@@ -54,6 +54,9 @@ PlainTextEditorPage::PlainTextEditorPage(QString file_path, QWidget *parent)
this->ui_->characterLabel->setText(tr("0 character"));
this->ui_->lfLabel->setHidden(true);
this->ui_->encodingLabel->setText("Unicode");
+ this->ui_->textPage->setAcceptDrops(false);
+
+ setAcceptDrops(false);
connect(ui_->textPage, &QPlainTextEdit::textChanged, this, [=]() {
// if file is loading
diff --git a/src/ui/widgets/TextEdit.cpp b/src/ui/widgets/TextEdit.cpp
index 4d83cff3..a742d49d 100644
--- a/src/ui/widgets/TextEdit.cpp
+++ b/src/ui/widgets/TextEdit.cpp
@@ -26,22 +26,23 @@
*
*/
-#include "ui/widgets/TextEdit.h"
+#include "TextEdit.h"
#include <QtPrintSupport>
+#include <cstddef>
#include <tuple>
+#include <utility>
#include <vector>
#include "core/GpgModel.h"
-#include "core/function/GlobalSettingStation.h"
-#include "core/model/CacheObject.h"
#include "ui/UISignalStation.h"
+#include "ui/widgets/HelpPage.h"
+#include "ui/widgets/TextEditTabWidget.h"
namespace GpgFrontend::UI {
TextEdit::TextEdit(QWidget* parent) : QWidget(parent) {
- count_page_ = 0;
- tab_widget_ = new QTabWidget(this);
+ tab_widget_ = new TextEditTabWidget(this);
tab_widget_->setMovable(true);
tab_widget_->setTabsClosable(true);
tab_widget_->setDocumentMode(true);
@@ -58,46 +59,13 @@ TextEdit::TextEdit(QWidget* parent) : QWidget(parent) {
setAcceptDrops(false);
}
-void TextEdit::SlotNewTab() {
- QString header = tr("untitled") + QString::number(++count_page_) + ".txt";
-
- auto* page = new PlainTextEditorPage();
- auto index = tab_widget_->addTab(page, header);
- tab_widget_->setTabIcon(index, QIcon(":/icons/file.png"));
- tab_widget_->setCurrentIndex(tab_widget_->count() - 1);
- page->GetTextPage()->setFocus();
- connect(page->GetTextPage()->document(), &QTextDocument::modificationChanged,
- this, &TextEdit::SlotShowModified);
- connect(page->GetTextPage()->document(), &QTextDocument::contentsChanged,
- this, &TextEdit::slot_save_status_to_cache_for_revovery);
-}
+void TextEdit::SlotNewTab() { tab_widget_->SlotNewTab(); }
void TextEdit::SlotNewTabWithContent(QString title, const QString& content) {
- QString header = tr("untitled") + QString::number(++count_page_) + ".txt";
- if (!title.isEmpty()) {
- // modify title
- if (!title.isEmpty() && title[0] == '*') {
- title.remove(0, 1);
- }
- // set title
- header = title;
- }
-
- auto* page = new PlainTextEditorPage();
- auto index = tab_widget_->addTab(page, header);
- tab_widget_->setTabIcon(index, QIcon(":/icons/file.png"));
- tab_widget_->setCurrentIndex(tab_widget_->count() - 1);
- page->GetTextPage()->setFocus();
- connect(page->GetTextPage()->document(), &QTextDocument::modificationChanged,
- this, &TextEdit::SlotShowModified);
- connect(page->GetTextPage()->document(), &QTextDocument::contentsChanged,
- this, &TextEdit::slot_save_status_to_cache_for_revovery);
-
- // set content with modified status
- page->GetTextPage()->document()->setPlainText(content);
+ tab_widget_->SlotNewTabWithContent(std::move(title), content);
}
-void TextEdit::slotNewHelpTab(const QString& title, const QString& path) const {
+void TextEdit::SlotNewHelpTab(const QString& title, const QString& path) const {
auto* page = new HelpPage(path);
tab_widget_->addTab(page, title);
tab_widget_->setCurrentIndex(tab_widget_->count() - 1);
@@ -119,31 +87,42 @@ void TextEdit::SlotNewFileTab() {
}
void TextEdit::SlotOpenFile(const QString& path) {
- QFile file(path);
- auto result = file.open(QIODevice::ReadOnly | QIODevice::Text);
- if (result) {
- auto* page = new PlainTextEditorPage(path);
- connect(page->GetTextPage()->document(),
- &QTextDocument::modificationChanged, this,
- &TextEdit::SlotShowModified);
- // connect to cache recovery fucntion
- connect(page->GetTextPage()->document(), &QTextDocument::contentsChanged,
- this, &TextEdit::slot_save_status_to_cache_for_revovery);
+ QFileInfo const info(path);
+ if (!info.isFile() || !info.isReadable()) {
+ QMessageBox::critical(
+ this, tr("Error"),
+ tr("Cannot open this file. Please make sure that this "
+ "is a regular file and it's readable."));
+ return;
+ }
- QApplication::setOverrideCursor(Qt::WaitCursor);
- auto index = tab_widget_->addTab(page, stripped_name(path));
- tab_widget_->setTabIcon(index, QIcon(":/icons/file.png"));
- tab_widget_->setCurrentIndex(tab_widget_->count() - 1);
- QApplication::restoreOverrideCursor();
- page->GetTextPage()->setFocus();
- page->ReadFile();
- } else {
+ if (info.size() > static_cast<qint64>(1024 * 1024)) {
+ QMessageBox::critical(
+ this, tr("Error"),
+ tr("Cannot open this file. The file is TOO LARGE (>1MB) for "
+ "GpgFrontend Text Editor."));
+ return;
+ }
+
+ QFile file(path);
+ if (!file.open(QIODevice::ReadOnly)) {
QMessageBox::warning(
- this, tr("Warning"),
- tr("Cannot read file %1:\n%2.").arg(path).arg(file.errorString()));
+ this, tr("File Open Error"),
+ tr("The file \"%1\" could not be opened.").arg(info.fileName()));
+ return;
}
+ QByteArray file_data = file.read(1024);
file.close();
+
+ if (file_data.contains('\0')) {
+ QMessageBox::warning(this, tr("Binary File Detected"),
+ tr("The file \"%1\" appears to be a binary file "
+ "and will not be opened.")
+ .arg(info.fileName()));
+ return;
+ }
+ tab_widget_->SlotOpenFile(path);
}
void TextEdit::SlotOpen() {
@@ -168,19 +147,17 @@ void TextEdit::SlotSave() {
// docname.remove(0,2);
SlotSaveAs();
} else {
- save_file(file_name);
+ saveFile(file_name);
}
}
-bool TextEdit::save_file(const QString& fileName) {
- if (fileName.isEmpty()) {
- return false;
- }
+auto TextEdit::saveFile(const QString& file_name) -> bool {
+ if (file_name.isEmpty()) return false;
PlainTextEditorPage* page = SlotCurPageTextEdit();
if (page == nullptr) return false;
- QFile file(fileName);
+ QFile file(file_name);
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream output_stream(&file);
QApplication::setOverrideCursor(Qt::WaitCursor);
@@ -191,8 +168,8 @@ bool TextEdit::save_file(const QString& fileName) {
document->setModified(false);
int cur_index = tab_widget_->currentIndex();
- tab_widget_->setTabText(cur_index, stripped_name(fileName));
- page->SetFilePath(fileName);
+ tab_widget_->setTabText(cur_index, stripped_name(file_name));
+ page->SetFilePath(file_name);
page->NotifyFileSaved();
file.close();
@@ -200,7 +177,7 @@ bool TextEdit::save_file(const QString& fileName) {
}
QMessageBox::warning(
this, tr("Warning"),
- tr("Cannot read file %1:\n%2.").arg(fileName).arg(file.errorString()));
+ tr("Cannot read file %1:\n%2.").arg(file_name).arg(file.errorString()));
return false;
}
@@ -217,7 +194,7 @@ auto TextEdit::SlotSaveAs() -> bool {
path = tab_widget_->tabText(tab_widget_->currentIndex()).remove(0, 2);
}
- return save_file(QFileDialog::getSaveFileName(this, tr("Save file"), path));
+ return saveFile(QFileDialog::getSaveFileName(this, tr("Save file"), path));
}
void TextEdit::SlotCloseTab() {
@@ -294,7 +271,7 @@ auto TextEdit::maybe_save_current_tab(bool askToSave) -> bool {
// docname.remove(0,2);
return SlotSaveAs();
}
- return save_file(file_path);
+ return saveFile(file_path);
}
return result == QMessageBox::Discard;
}
@@ -354,32 +331,25 @@ auto TextEdit::MaybeSaveAnyTab() -> bool {
return false;
}
-PlainTextEditorPage* TextEdit::CurTextPage() const {
- return qobject_cast<PlainTextEditorPage*>(tab_widget_->currentWidget());
-}
-
void TextEdit::SlotAppendText2CurTextPage(const QString& text) {
if (CurTextPage() == nullptr) SlotNewTab();
CurTextPage()->GetTextPage()->appendPlainText(text);
}
-FilePage* TextEdit::CurFilePage() const {
- auto* cur_file_page = qobject_cast<FilePage*>(tab_widget_->currentWidget());
- if (cur_file_page != nullptr) {
- return cur_file_page;
- }
- return nullptr;
+auto TextEdit::CurTextPage() const -> PlainTextEditorPage* {
+ return tab_widget_->CurTextPage();
}
-int TextEdit::TabCount() const { return tab_widget_->count(); }
-
-PlainTextEditorPage* TextEdit::SlotCurPageTextEdit() const {
- auto* cur_page =
- qobject_cast<PlainTextEditorPage*>(tab_widget_->currentWidget());
- return cur_page;
+auto TextEdit::SlotCurPageTextEdit() const -> PlainTextEditorPage* {
+ return tab_widget_->SlotCurPageTextEdit();
}
+auto TextEdit::CurFilePage() const -> FilePage* {
+ return tab_widget_->CurFilePage();
+}
+
+auto TextEdit::TabCount() const -> int { return tab_widget_->count(); }
-FilePage* TextEdit::SlotCurPageFileTreeView() const {
+auto TextEdit::SlotCurPageFileTreeView() const -> FilePage* {
auto* cur_page = qobject_cast<FilePage*>(tab_widget_->currentWidget());
return cur_page;
}
@@ -432,7 +402,7 @@ void TextEdit::LoadFile(const QString& fileName) {
// statusBar()->showMessage(tr("File loaded"), 2000);
}
-QString TextEdit::stripped_name(const QString& full_file_name) {
+auto TextEdit::stripped_name(const QString& full_file_name) -> QString {
return QFileInfo(full_file_name).fileName();
}
@@ -476,7 +446,7 @@ void TextEdit::SlotShowModified(bool changed) const {
// if doc is modified now, add leading * to title,
// otherwise remove the leading * from the title
if (CurTextPage()->GetTextPage()->document()->isModified()) {
- tab_widget_->setTabText(index, title.prepend("* "));
+ tab_widget_->setTabText(index, title.trimmed().prepend("* "));
} else {
tab_widget_->setTabText(index, title.remove(0, 2));
}
@@ -602,56 +572,4 @@ void TextEdit::slot_file_page_path_changed(const QString& path) const {
emit UISignalStation::GetInstance()
-> SignalMainWindowlUpdateBasicalOperaMenu(0);
}
-
-void TextEdit::slot_save_status_to_cache_for_revovery() {
- if (this->text_page_data_modified_count_++ % 8 != 0) return;
-
- auto settings =
- GpgFrontend::GlobalSettingStation::GetInstance().GetSettings();
-
- bool restore_text_editor_page =
- settings.value("basic/restore_text_editor_page", false).toBool();
- if (!restore_text_editor_page) {
- FLOG_D("restore_text_editor_page is false, ignoring...");
- return;
- }
-
- int tab_count = tab_widget_->count();
- std::vector<std::tuple<int, QString, QString>> unsaved_pages;
-
- for (int i = 0; i < tab_count; i++) {
- auto* target_page =
- qobject_cast<PlainTextEditorPage*>(tab_widget_->widget(i));
-
- // if this page is no textedit, there should be nothing to save
- if (target_page == nullptr) {
- continue;
- }
-
- auto* document = target_page->GetTextPage()->document();
- auto tab_title = tab_widget_->tabText(i);
- if (!target_page->ReadDone() || !target_page->isEnabled() ||
- !document->isModified()) {
- continue;
- }
-
- unsaved_pages.emplace_back(i, tab_title, document->toRawText());
- }
-
- CacheObject cache("editor_unsaved_pages");
- QJsonArray unsaved_page_array;
- for (const auto& page : unsaved_pages) {
- const auto [index, title, content] = page;
-
- QJsonObject page_json;
- page_json["index"] = index;
- page_json["title"] = title;
- page_json["content"] = content;
-
- unsaved_page_array.push_back(page_json);
- }
-
- cache.setArray(unsaved_page_array);
-}
-
} // namespace GpgFrontend::UI
diff --git a/src/ui/widgets/TextEdit.h b/src/ui/widgets/TextEdit.h
index 50939d0e..4b893a3a 100644
--- a/src/ui/widgets/TextEdit.h
+++ b/src/ui/widgets/TextEdit.h
@@ -30,20 +30,19 @@
#include "ui/dialog/QuitDialog.h"
#include "ui/widgets/FilePage.h"
-#include "ui/widgets/HelpPage.h"
#include "ui/widgets/PlainTextEditorPage.h"
namespace GpgFrontend::UI {
-/**
- * @brief TextEdit class
- */
+
+class TextEditTabWidget;
+
class TextEdit : public QWidget {
Q_OBJECT
public:
/**
* @brief
*/
- TextEdit(QWidget* parent);
+ explicit TextEdit(QWidget* parent);
/**
* @details Load the content of file into the current textpage
@@ -60,36 +59,37 @@ class TextEdit : public QWidget {
* @return \li false, if the close event should be aborted.
* \li true, otherwise
*/
- bool MaybeSaveAnyTab();
+ auto MaybeSaveAnyTab() -> bool;
/**
* @brief
*
* @return int
*/
- [[nodiscard]] int TabCount() const;
+ [[nodiscard]] auto TabCount() const -> int;
/**
* @details textpage of the currently activated tab
* @return \li reference to QTextEdit if tab has one
* \li 0 otherwise (e.g. if helppage)
*/
- [[nodiscard]] PlainTextEditorPage* CurTextPage() const;
+ [[nodiscard]] auto CurTextPage() const -> PlainTextEditorPage*;
/**
* @brief
*
* @return FilePage*
*/
- [[nodiscard]] FilePage* CurFilePage() const;
+ [[nodiscard]] auto CurFilePage() const -> FilePage*;
/**
* @details List of currently unsaved tabs.
* @returns QHash<int, QString> Hash of tab indexes and title of unsaved tabs.
*/
- [[nodiscard]] QHash<int, QString> UnsavedDocuments() const;
+ [[nodiscard]] auto UnsavedDocuments() const -> QHash<int, QString>;
- QTabWidget* tab_widget_; /** Widget containing the tabs of the editor */
+ TextEditTabWidget*
+ tab_widget_; /** Widget containing the tabs of the editor */
public slots:
@@ -97,13 +97,13 @@ class TextEdit : public QWidget {
* @details Return pointer to the currently activated text edit tab page.
*
*/
- PlainTextEditorPage* SlotCurPageTextEdit() const;
+ [[nodiscard]] auto SlotCurPageTextEdit() const -> PlainTextEditorPage*;
/**
* @details Return pointer to the currently activated file tree view tab page.
*
*/
- FilePage* SlotCurPageFileTreeView() const;
+ [[nodiscard]] auto SlotCurPageFileTreeView() const -> FilePage*;
/**
* @details Insert a ">" at the beginning of every line of current textedit.
@@ -128,7 +128,7 @@ class TextEdit : public QWidget {
*
* @return Return the return value of the savefile method
*/
- bool SlotSaveAs();
+ auto SlotSaveAs() -> bool;
/**
* @details Show an OpenFileDoalog and open the file in a new tab.
@@ -166,7 +166,7 @@ class TextEdit : public QWidget {
* @param title title for the tab
* @param path path for html file to show
*/
- void slotNewHelpTab(const QString& title, const QString& path) const;
+ void SlotNewHelpTab(const QString& title, const QString& path) const;
/**
* New File Tab to do file operation
@@ -197,46 +197,6 @@ class TextEdit : public QWidget {
*/
void SlotSwitchTabDown() const;
- private:
- uint text_page_data_modified_count_ = 0; ///<
-
- /**
- * @details return just a filename stripped of a whole path
- *
- * @param a filename path
- * @return QString containing the filename
- */
- static QString stripped_name(const QString& full_file_name);
-
- /**
- * @brief
- *
- * @param askToSave
- */
- bool maybe_save_current_tab(bool askToSave);
-
- int count_page_; ///< int containing the number of added tabs
-
- private slots:
-
- void slot_file_page_path_changed(const QString& path) const;
-
- /**
- * @details Remove the tab with given index
- *
- * @param index Tab-number to remove
- */
- void slot_remove_tab(int index);
-
- /**
- * @brief
- *
- * @param index
- */
- void slot_save_status_to_cache_for_revovery();
-
- public slots:
-
/**
* @details Cut selected text in current text page.
*/
@@ -295,7 +255,34 @@ class TextEdit : public QWidget {
*
* @param fileName
*/
- bool save_file(const QString& fileName);
+ auto saveFile(const QString& file_name) -> bool;
+
+ private:
+ /**
+ * @details return just a filename stripped of a whole path
+ *
+ * @param a filename path
+ * @return QString containing the filename
+ */
+ static auto stripped_name(const QString& full_file_name) -> QString;
+
+ /**
+ * @brief
+ *
+ * @param askToSave
+ */
+ auto maybe_save_current_tab(bool askToSave) -> bool;
+
+ private slots:
+
+ void slot_file_page_path_changed(const QString& path) const;
+
+ /**
+ * @details Remove the tab with given index
+ *
+ * @param index Tab-number to remove
+ */
+ void slot_remove_tab(int index);
};
} // namespace GpgFrontend::UI
diff --git a/src/ui/widgets/TextEditTabWidget.cpp b/src/ui/widgets/TextEditTabWidget.cpp
new file mode 100644
index 00000000..c782e868
--- /dev/null
+++ b/src/ui/widgets/TextEditTabWidget.cpp
@@ -0,0 +1,242 @@
+/**
+ * Copyright (C) 2021-2024 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GpgFrontend is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#include "TextEditTabWidget.h"
+
+#include "core/function/GlobalSettingStation.h"
+#include "core/model/CacheObject.h"
+#include "ui/widgets/PlainTextEditorPage.h"
+#include "widgets/FilePage.h"
+
+namespace GpgFrontend::UI {
+
+TextEditTabWidget::TextEditTabWidget(QWidget* parent) : QTabWidget(parent) {
+ setAcceptDrops(true);
+}
+
+void TextEditTabWidget::dragEnterEvent(QDragEnterEvent* event) {
+ event->acceptProposedAction();
+}
+
+void TextEditTabWidget::dropEvent(QDropEvent* event) {
+ if (!event->mimeData()->hasUrls()) return;
+
+ auto urls = event->mimeData()->urls();
+
+ for (const auto& url : urls) {
+ QString local_file = url.toLocalFile();
+
+ QFileInfo file_info(local_file);
+ if (file_info.size() > static_cast<qint64>(1024 * 1024)) {
+ QMessageBox::warning(
+ this, tr("File Too Large"),
+ tr("The file \"%1\" is larger than 1MB and will not be opened.")
+ .arg(file_info.fileName()));
+ continue;
+ }
+
+ QFile file(local_file);
+ if (!file.open(QIODevice::ReadOnly)) {
+ QMessageBox::warning(
+ this, tr("File Open Error"),
+ tr("The file \"%1\" could not be opened.").arg(file_info.fileName()));
+ continue;
+ }
+ QByteArray file_data = file.read(1024);
+ file.close();
+
+ if (file_data.contains('\0')) {
+ QMessageBox::warning(this, tr("Binary File Detected"),
+ tr("The file \"%1\" appears to be a binary file "
+ "and will not be opened.")
+ .arg(file_info.fileName()));
+ continue;
+ }
+
+ SlotOpenFile(local_file);
+ }
+
+ event->acceptProposedAction();
+}
+
+void TextEditTabWidget::SlotOpenFile(const QString& path) {
+ QFile file(path);
+ auto result = file.open(QIODevice::ReadOnly | QIODevice::Text);
+ if (result) {
+ auto* page = new PlainTextEditorPage(path);
+ connect(page->GetTextPage()->document(),
+ &QTextDocument::modificationChanged, this,
+ &TextEditTabWidget::SlotShowModified);
+ // connect to cache recovery fucntion
+ connect(page->GetTextPage()->document(), &QTextDocument::contentsChanged,
+ this, &TextEditTabWidget::slot_save_status_to_cache_for_recovery);
+
+ QApplication::setOverrideCursor(Qt::WaitCursor);
+ auto index = this->addTab(page, stripped_name(path));
+ this->setTabIcon(index, QIcon(":/icons/file.png"));
+ this->setCurrentIndex(this->count() - 1);
+ QApplication::restoreOverrideCursor();
+ page->GetTextPage()->setFocus();
+ page->ReadFile();
+ } else {
+ QMessageBox::warning(
+ this, tr("Warning"),
+ tr("Cannot read file %1:\n%2.").arg(path).arg(file.errorString()));
+ }
+
+ file.close();
+}
+void TextEditTabWidget::SlotShowModified(bool changed) {
+ // get current tab
+ int index = this->currentIndex();
+ QString title = this->tabText(index);
+
+ // if changed
+ if (!changed) {
+ this->setTabText(index, title.remove(0, 2));
+ return;
+ }
+
+ // if doc is modified now, add leading * to title,
+ // otherwise remove the leading * from the title
+ if (CurTextPage()->GetTextPage()->document()->isModified()) {
+ this->setTabText(index, title.trimmed().prepend("* "));
+ } else {
+ this->setTabText(index, title.remove(0, 2));
+ }
+}
+auto TextEditTabWidget::CurTextPage() const -> PlainTextEditorPage* {
+ return qobject_cast<PlainTextEditorPage*>(this->currentWidget());
+}
+
+auto TextEditTabWidget::SlotCurPageTextEdit() -> PlainTextEditorPage* {
+ auto* cur_page = qobject_cast<PlainTextEditorPage*>(this->currentWidget());
+ return cur_page;
+}
+
+auto TextEditTabWidget::CurFilePage() const -> FilePage* {
+ auto* cur_file_page = qobject_cast<FilePage*>(this->currentWidget());
+ if (cur_file_page != nullptr) {
+ return cur_file_page;
+ }
+ return nullptr;
+}
+
+auto TextEditTabWidget::stripped_name(const QString& full_file_name)
+ -> QString {
+ return QFileInfo(full_file_name).fileName();
+}
+
+void TextEditTabWidget::slot_save_status_to_cache_for_recovery() {
+ if (this->text_page_data_modified_count_++ % 8 != 0) return;
+
+ auto settings = GlobalSettingStation::GetInstance().GetSettings();
+
+ bool restore_text_editor_page =
+ settings.value("basic/restore_text_editor_page", false).toBool();
+ if (!restore_text_editor_page) {
+ FLOG_D("restore_text_editor_page is false, ignoring...");
+ return;
+ }
+
+ int tab_count = this->count();
+ std::vector<std::tuple<int, QString, QString>> unsaved_pages;
+
+ for (int i = 0; i < tab_count; i++) {
+ auto* target_page = qobject_cast<PlainTextEditorPage*>(this->widget(i));
+
+ // if this page is no textedit, there should be nothing to save
+ if (target_page == nullptr) {
+ continue;
+ }
+
+ auto* document = target_page->GetTextPage()->document();
+ auto tab_title = this->tabText(i);
+ if (!target_page->ReadDone() || !target_page->isEnabled() ||
+ !document->isModified()) {
+ continue;
+ }
+
+ unsaved_pages.emplace_back(i, tab_title, document->toRawText());
+ }
+
+ CacheObject cache("editor_unsaved_pages");
+ QJsonArray unsaved_page_array;
+ for (const auto& page : unsaved_pages) {
+ const auto [index, title, content] = page;
+
+ QJsonObject page_json;
+ page_json["index"] = index;
+ page_json["title"] = title;
+ page_json["content"] = content;
+
+ unsaved_page_array.push_back(page_json);
+ }
+
+ cache.setArray(unsaved_page_array);
+}
+
+void TextEditTabWidget::SlotNewTab() {
+ QString header = tr("untitled") + QString::number(++count_page_) + ".txt";
+
+ auto* page = new PlainTextEditorPage();
+ auto index = this->addTab(page, header);
+ this->setTabIcon(index, QIcon(":/icons/file.png"));
+ this->setCurrentIndex(this->count() - 1);
+ page->GetTextPage()->setFocus();
+ connect(page->GetTextPage()->document(), &QTextDocument::modificationChanged,
+ this, &TextEditTabWidget::SlotShowModified);
+ connect(page->GetTextPage()->document(), &QTextDocument::contentsChanged,
+ this, &TextEditTabWidget::slot_save_status_to_cache_for_recovery);
+}
+void TextEditTabWidget::SlotNewTabWithContent(QString title,
+ const QString& content) {
+ QString header = tr("untitled") + QString::number(++count_page_) + ".txt";
+ if (!title.isEmpty()) {
+ // modify title
+ if (!title.isEmpty() && title[0] == '*') {
+ title.remove(0, 1);
+ }
+ // set title
+ header = title;
+ }
+
+ auto* page = new PlainTextEditorPage();
+ auto index = this->addTab(page, header);
+ this->setTabIcon(index, QIcon(":/icons/file.png"));
+ this->setCurrentIndex(this->count() - 1);
+ page->GetTextPage()->setFocus();
+ connect(page->GetTextPage()->document(), &QTextDocument::modificationChanged,
+ this, &TextEditTabWidget::SlotShowModified);
+ connect(page->GetTextPage()->document(), &QTextDocument::contentsChanged,
+ this, &TextEditTabWidget::slot_save_status_to_cache_for_recovery);
+
+ // set content with modified status
+ page->GetTextPage()->document()->setPlainText(content);
+}
+} // namespace GpgFrontend::UI
diff --git a/src/ui/widgets/TextEditTabWidget.h b/src/ui/widgets/TextEditTabWidget.h
new file mode 100644
index 00000000..7c83a721
--- /dev/null
+++ b/src/ui/widgets/TextEditTabWidget.h
@@ -0,0 +1,118 @@
+/**
+ * Copyright (C) 2021-2024 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GpgFrontend is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#pragma once
+
+#include "widgets/FilePage.h"
+namespace GpgFrontend::UI {
+
+class PlainTextEditorPage;
+
+class TextEditTabWidget : public QTabWidget {
+ Q_OBJECT
+ public:
+ explicit TextEditTabWidget(QWidget* parent = nullptr);
+
+ public slots:
+
+ /**
+ * @brief
+ *
+ */
+ void SlotNewTab();
+
+ /**
+ * @brief
+ *
+ * @param title
+ * @param content
+ */
+ void SlotNewTabWithContent(QString title, const QString& content);
+
+ /**
+ * @details Adds a new tab with opening file by path
+ */
+ void SlotOpenFile(const QString& path);
+
+ /**
+ * @details put a * in front of current tabs title, if current textedit is
+ * modified
+ */
+ void SlotShowModified(bool);
+
+ /**
+ * @brief
+ *
+ * @return PlainTextEditorPage*
+ */
+ [[nodiscard]] auto CurTextPage() const -> PlainTextEditorPage*;
+
+ /**
+ * @brief
+ *
+ * @return PlainTextEditorPage*
+ */
+ auto SlotCurPageTextEdit() -> PlainTextEditorPage*;
+
+ /**
+ * @brief
+ *
+ * @return FilePage*
+ */
+ [[nodiscard]] auto CurFilePage() const -> FilePage*;
+
+ protected:
+ /**
+ * @brief
+ *
+ * @param event
+ */
+ void dragEnterEvent(QDragEnterEvent* event) override;
+
+ /**
+ * @brief
+ *
+ * @param event
+ */
+ void dropEvent(QDropEvent* event) override;
+
+ private slots:
+ /**
+ * @brief
+ *
+ */
+ void slot_save_status_to_cache_for_recovery();
+
+ private:
+ int count_page_ = 0;
+ int text_page_data_modified_count_ = 0;
+
+ static auto stripped_name(const QString& full_file_name) -> QString;
+};
+
+} // namespace GpgFrontend::UI \ No newline at end of file