diff options
author | Saturneric <[email protected]> | 2022-01-12 03:04:46 +0000 |
---|---|---|
committer | Saturneric <[email protected]> | 2022-01-12 03:04:46 +0000 |
commit | d24f0251e8c8964bf42f4bf028023f02f4e96933 (patch) | |
tree | b0d1fe016143274a66898ec6ca0913115400c033 | |
parent | <refactor>(ui): Adjust src/ui/function to src/ui/thread (diff) | |
download | GpgFrontend-d24f0251e8c8964bf42f4bf028023f02f4e96933.tar.gz GpgFrontend-d24f0251e8c8964bf42f4bf028023f02f4e96933.zip |
<refactor, feat>(ui): Text editor improvements.
1. Add binary display mode
2. Add information bar
3. Added character code recognition function.
4. Identify text encoding and line breaks
5. Count the number of characters
6. Code reconstruction
-rw-r--r-- | src/ui/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/ui/FindWidget.cpp | 52 | ||||
-rw-r--r-- | src/ui/FindWidget.h | 8 | ||||
-rw-r--r-- | src/ui/details/VerifyDetailsDialog.h | 2 | ||||
-rw-r--r-- | src/ui/encoding/TextEncodingDetect.cpp | 313 | ||||
-rw-r--r-- | src/ui/encoding/TextEncodingDetect.h | 85 | ||||
-rw-r--r-- | src/ui/main_window/MainWindowSlotFunction.cpp | 33 | ||||
-rw-r--r-- | src/ui/main_window/MainWindowSlotUI.cpp | 8 | ||||
-rw-r--r-- | src/ui/thread/FileReadThread.cpp | 24 | ||||
-rw-r--r-- | src/ui/thread/FileReadThread.h | 2 | ||||
-rw-r--r-- | src/ui/widgets/EditorPage.cpp | 154 | ||||
-rw-r--r-- | src/ui/widgets/InfoBoardWidget.h | 2 | ||||
-rw-r--r-- | src/ui/widgets/PlainTextEditorPage.cpp | 257 | ||||
-rw-r--r-- | src/ui/widgets/PlainTextEditorPage.h (renamed from src/ui/widgets/EditorPage.h) | 27 | ||||
-rw-r--r-- | src/ui/widgets/TextEdit.cpp | 60 | ||||
-rw-r--r-- | src/ui/widgets/TextEdit.h | 6 | ||||
-rw-r--r-- | ui/FilePage.ui | 15 | ||||
-rw-r--r-- | ui/PlainTextEditor.ui | 79 |
18 files changed, 869 insertions, 259 deletions
diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 59cc99bf..32edb262 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -10,6 +10,7 @@ aux_source_directory(./settings UI_SOURCE) aux_source_directory(./thread UI_SOURCE) aux_source_directory(./details UI_SOURCE) aux_source_directory(./data_struct UI_SOURCE) +aux_source_directory(./encoding UI_SOURCE) if (SMTP_SUPPORT) aux_source_directory(./smtp UI_SOURCE) diff --git a/src/ui/FindWidget.cpp b/src/ui/FindWidget.cpp index 6326b119..b95859a1 100644 --- a/src/ui/FindWidget.cpp +++ b/src/ui/FindWidget.cpp @@ -26,8 +26,8 @@ namespace GpgFrontend::UI { -FindWidget::FindWidget(QWidget* parent, QTextEdit* edit) : QWidget(parent) { - mTextpage = edit; +FindWidget::FindWidget(QWidget* parent, PlainTextEditorPage* edit) + : QWidget(parent), mTextpage(edit) { findEdit = new QLineEdit(this); auto* closeButton = new QPushButton( this->style()->standardIcon(QStyle::SP_TitleBarCloseButton), QString(), @@ -55,12 +55,13 @@ FindWidget::FindWidget(QWidget* parent, QTextEdit* edit) : QWidget(parent) { } void FindWidget::setBackground() { - QTextCursor cursor = mTextpage->textCursor(); + auto cursor = mTextpage->getTextPage()->textCursor(); // if match is found set background of QLineEdit to white, otherwise to red QPalette bgPalette(findEdit->palette()); if (!findEdit->text().isEmpty() && - mTextpage->document()->find(findEdit->text()).position() < 0) { + mTextpage->getTextPage()->document()->find(findEdit->text()).position() < + 0) { bgPalette.setColor(QPalette::Base, "#ececba"); } else { bgPalette.setColor(QPalette::Base, Qt::white); @@ -69,45 +70,45 @@ void FindWidget::setBackground() { } void FindWidget::slotFindNext() { - QTextCursor cursor = mTextpage->textCursor(); - cursor = mTextpage->document()->find(findEdit->text(), cursor, - QTextDocument::FindCaseSensitively); + QTextCursor cursor = mTextpage->getTextPage()->textCursor(); + cursor = mTextpage->getTextPage()->document()->find( + findEdit->text(), cursor, QTextDocument::FindCaseSensitively); // if end of document is reached, restart search from beginning if (cursor.position() == -1) { - cursor = mTextpage->document()->find(findEdit->text(), cursor, - QTextDocument::FindCaseSensitively); + cursor = mTextpage->getTextPage()->document()->find( + findEdit->text(), cursor, QTextDocument::FindCaseSensitively); } // cursor should not stay at -1, otherwise text is not editable // todo: check how gedit handles this if (cursor.position() != -1) { - mTextpage->setTextCursor(cursor); + mTextpage->getTextPage()->setTextCursor(cursor); } this->setBackground(); } void FindWidget::slotFind() { - QTextCursor cursor = mTextpage->textCursor(); + QTextCursor cursor = mTextpage->getTextPage()->textCursor(); if (cursor.anchor() == -1) { - cursor = mTextpage->document()->find(findEdit->text(), cursor, - QTextDocument::FindCaseSensitively); + cursor = mTextpage->getTextPage()->document()->find( + findEdit->text(), cursor, QTextDocument::FindCaseSensitively); } else { - cursor = mTextpage->document()->find(findEdit->text(), cursor.anchor(), - QTextDocument::FindCaseSensitively); + cursor = mTextpage->getTextPage()->document()->find( + findEdit->text(), cursor.anchor(), QTextDocument::FindCaseSensitively); } // if end of document is reached, restart search from beginning if (cursor.position() == -1) { - cursor = mTextpage->document()->find(findEdit->text(), cursor, - QTextDocument::FindCaseSensitively); + cursor = mTextpage->getTextPage()->document()->find( + findEdit->text(), cursor, QTextDocument::FindCaseSensitively); } // cursor should not stay at -1, otherwise text is not editable // todo: check how gedit handles this if (cursor.position() != -1) { - mTextpage->setTextCursor(cursor); + mTextpage->getTextPage()->setTextCursor(cursor); } this->setBackground(); } @@ -117,19 +118,20 @@ void FindWidget::slotFindPrevious() { flags |= QTextDocument::FindBackward; flags |= QTextDocument::FindCaseSensitively; - QTextCursor cursor = mTextpage->textCursor(); - cursor = mTextpage->document()->find(findEdit->text(), cursor, flags); + QTextCursor cursor = mTextpage->getTextPage()->textCursor(); + cursor = mTextpage->getTextPage()->document()->find(findEdit->text(), cursor, + flags); // if begin of document is reached, restart search from end if (cursor.position() == -1) { - cursor = - mTextpage->document()->find(findEdit->text(), QTextCursor::End, flags); + cursor = mTextpage->getTextPage()->document()->find( + findEdit->text(), QTextCursor::End, flags); } // cursor should not stay at -1, otherwise text is not editable // todo: check how gedit handles this if (cursor.position() != -1) { - mTextpage->setTextCursor(cursor); + mTextpage->getTextPage()->setTextCursor(cursor); } this->setBackground(); } @@ -150,11 +152,11 @@ void FindWidget::keyPressEvent(QKeyEvent* e) { } void FindWidget::slotClose() { - QTextCursor cursor = mTextpage->textCursor(); + QTextCursor cursor = mTextpage->getTextPage()->textCursor(); if (cursor.position() == -1) { cursor.setPosition(0); - mTextpage->setTextCursor(cursor); + mTextpage->getTextPage()->setTextCursor(cursor); } mTextpage->setFocus(); close(); diff --git a/src/ui/FindWidget.h b/src/ui/FindWidget.h index e4cbdaab..bc412012 100644 --- a/src/ui/FindWidget.h +++ b/src/ui/FindWidget.h @@ -26,7 +26,7 @@ #define FINDWIDGET_H #include "ui/GpgFrontendUI.h" -#include "ui/widgets/EditorPage.h" +#include "ui/widgets/PlainTextEditorPage.h" namespace GpgFrontend::UI { @@ -42,7 +42,7 @@ class FindWidget : public QWidget { * * @param parent The parent widget */ - explicit FindWidget(QWidget* parent, QTextEdit* edit); + explicit FindWidget(QWidget* parent, PlainTextEditorPage* edit); private: void keyPressEvent(QKeyEvent* e) override; @@ -53,8 +53,8 @@ class FindWidget : public QWidget { */ void setBackground(); - QTextEdit* mTextpage; /** Textedit associated to the notification */ - QLineEdit* findEdit; /** Label holding the text shown in infoBoard */ + PlainTextEditorPage* mTextpage; /** Textedit associated to the notification */ + QLineEdit* findEdit; /** Label holding the text shown in infoBoard */ private slots: diff --git a/src/ui/details/VerifyDetailsDialog.h b/src/ui/details/VerifyDetailsDialog.h index 5de648f6..21affdb4 100644 --- a/src/ui/details/VerifyDetailsDialog.h +++ b/src/ui/details/VerifyDetailsDialog.h @@ -26,7 +26,7 @@ #define __VERIFYDETAILSDIALOG_H__ #include "ui/GpgFrontendUI.h" -#include "ui/widgets/EditorPage.h" +#include "ui/widgets/PlainTextEditorPage.h" #include "ui/widgets/VerifyKeyDetailBox.h" namespace GpgFrontend::UI { diff --git a/src/ui/encoding/TextEncodingDetect.cpp b/src/ui/encoding/TextEncodingDetect.cpp new file mode 100644 index 00000000..22ae5897 --- /dev/null +++ b/src/ui/encoding/TextEncodingDetect.cpp @@ -0,0 +1,313 @@ +// +// Copyright 2015-2016 Jonathan Bennett <[email protected]> +// +// https://www.autoitscript.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Includes +#include "TextEncodingDetect.h" + +using namespace AutoIt::Common; + +static const unsigned char TextEncodingDetect_UTF16_BOM_LE[] = { + (unsigned char)(0xFF), (unsigned char)(0xFE)}; +static const unsigned char TextEncodingDetect_UTF16_BOM_BE[] = { + (unsigned char)(0xFE), (unsigned char)(0xFF)}; +static const unsigned char TextEncodingDetect_UTF8_BOM[] = { + (unsigned char)(0xEF), (unsigned char)(0xBB), (unsigned char)(0xBF)}; + +const unsigned char *TextEncodingDetect::utf16_bom_le_ = + TextEncodingDetect_UTF16_BOM_LE; +const unsigned char *TextEncodingDetect::utf16_bom_be_ = + TextEncodingDetect_UTF16_BOM_BE; +const unsigned char *TextEncodingDetect::utf8_bom_ = + TextEncodingDetect_UTF8_BOM; + +/////////////////////////////////////////////////////////////////////////////// +// Constructor() +// Default constructor +/////////////////////////////////////////////////////////////////////////////// + +TextEncodingDetect::TextEncodingDetect() { + // By default, assume nulls can't appear in ANSI/ASCII/UTF8 text files + null_suggests_binary_ = true; + + // Set defaults for utf16 detection based the use of odd/even nulls + utf16_expected_null_percent_ = 70; + utf16_unexpected_null_percent_ = 10; +} + +/////////////////////////////////////////////////////////////////////////////// +// Set the percentages used in utf16 detection using nulls. +/////////////////////////////////////////////////////////////////////////////// + +void TextEncodingDetect::SetUtf16UnexpectedNullPercent(int percent) { + if (percent > 0 && percent < 100) utf16_expected_null_percent_ = percent; +} + +void TextEncodingDetect::SetUtf16ExpectedNullPercent(int percent) { + if (percent > 0 && percent < 100) utf16_unexpected_null_percent_ = percent; +} + +/////////////////////////////////////////////////////////////////////////////// +// Simple function to return the length of the BOM for a particular encoding +// mode. +/////////////////////////////////////////////////////////////////////////////// + +int TextEncodingDetect::GetBOMLengthFromEncodingMode(Encoding encoding) { + int length = 0; + + if (encoding == UTF16_BE_BOM || encoding == UTF16_LE_BOM) + length = 2; + else if (encoding == UTF8_BOM) + length = 3; + + return length; +} + +/////////////////////////////////////////////////////////////////////////////// +// Checks if a buffer contains a valid BOM and returns the encoding based on it. +// Returns encoding "None" if there is no BOM. +/////////////////////////////////////////////////////////////////////////////// + +TextEncodingDetect::Encoding TextEncodingDetect::CheckBOM( + const unsigned char *pBuffer, size_t size) { + // Check for BOM + if (size >= 2 && pBuffer[0] == utf16_bom_le_[0] && + pBuffer[1] == utf16_bom_le_[1]) { + return UTF16_LE_BOM; + } else if (size >= 2 && pBuffer[0] == utf16_bom_be_[0] && + pBuffer[1] == utf16_bom_be_[1]) { + return UTF16_BE_BOM; + } else if (size >= 3 && pBuffer[0] == utf8_bom_[0] && + pBuffer[1] == utf8_bom_[1] && pBuffer[2] == utf8_bom_[2]) { + return UTF8_BOM; + } else { + return None; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Checks if a buffer contains a valid BOM and returns the encoding based on it. +// If it doesn't contain a BOM it tries to guess what the encoding is or +// "None" if it just looks like binary data. +/////////////////////////////////////////////////////////////////////////////// + +TextEncodingDetect::Encoding TextEncodingDetect::DetectEncoding( + const unsigned char *pBuffer, size_t size) const { + // First check if we have a BOM and return that if so + Encoding encoding = CheckBOM(pBuffer, size); + if (encoding != None) return encoding; + + // Now check for valid UTF8 + encoding = CheckUTF8(pBuffer, size); + if (encoding != None) return encoding; + + // Now try UTF16 + encoding = CheckUTF16NewlineChars(pBuffer, size); + if (encoding != None) return encoding; + + encoding = CheckUTF16ASCII(pBuffer, size); + if (encoding != None) return encoding; + + // ANSI or None (binary) then + if (!DoesContainNulls(pBuffer, size)) + return ANSI; + else { + // Found a null, return based on the preference in null_suggests_binary_ + if (null_suggests_binary_) + return None; + else + return ANSI; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Checks if a buffer contains valid utf8. Returns: +// None - not valid utf8 +// UTF8_NOBOM - valid utf8 encodings and multibyte sequences +// ASCII - Only data in the 0-127 range. +/////////////////////////////////////////////////////////////////////////////// + +TextEncodingDetect::Encoding TextEncodingDetect::CheckUTF8( + const unsigned char *pBuffer, size_t size) const { + // UTF8 Valid sequences + // 0xxxxxxx ASCII + // 110xxxxx 10xxxxxx 2-byte + // 1110xxxx 10xxxxxx 10xxxxxx 3-byte + // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 4-byte + // + // Width in UTF8 + // Decimal Width + // 0-127 1 byte + // 194-223 2 bytes + // 224-239 3 bytes + // 240-244 4 bytes + // + // Subsequent chars are in the range 128-191 + + bool only_saw_ascii_range = true; + size_t pos = 0; + int more_chars; + + while (pos < size) { + unsigned char ch = pBuffer[pos++]; + + if (ch == 0 && null_suggests_binary_) { + return None; + } else if (ch <= 127) { + // 1 byte + more_chars = 0; + } else if (ch >= 194 && ch <= 223) { + // 2 Byte + more_chars = 1; + } else if (ch >= 224 && ch <= 239) { + // 3 Byte + more_chars = 2; + } else if (ch >= 240 && ch <= 244) { + // 4 Byte + more_chars = 3; + } else { + return None; // Not utf8 + } + + // Check secondary chars are in range if we are expecting any + while (more_chars && pos < size) { + only_saw_ascii_range = false; // Seen non-ascii chars now + + ch = pBuffer[pos++]; + if (ch < 128 || ch > 191) return None; // Not utf8 + + --more_chars; + } + } + + // If we get to here then only valid UTF-8 sequences have been processed + + // If we only saw chars in the range 0-127 then we can't assume UTF8 (the + // caller will need to decide) + if (only_saw_ascii_range) + return ASCII; + else + return UTF8_NOBOM; +} + +/////////////////////////////////////////////////////////////////////////////// +// Checks if a buffer contains text that looks like utf16 by scanning for +// newline chars that would be present even in non-english text. +// Returns: +// None - not valid utf16 +// UTF16_LE_NOBOM - looks like utf16 le +// UTF16_BE_NOBOM - looks like utf16 be +/////////////////////////////////////////////////////////////////////////////// + +TextEncodingDetect::Encoding TextEncodingDetect::CheckUTF16NewlineChars( + const unsigned char *pBuffer, size_t size) { + if (size < 2) return None; + + // Reduce size by 1 so we don't need to worry about bounds checking for pairs + // of bytes + size--; + + int le_control_chars = 0; + int be_control_chars = 0; + unsigned char ch1, ch2; + + size_t pos = 0; + while (pos < size) { + ch1 = pBuffer[pos++]; + ch2 = pBuffer[pos++]; + + if (ch1 == 0) { + if (ch2 == 0x0a || ch2 == 0x0d) ++be_control_chars; + } else if (ch2 == 0) { + if (ch1 == 0x0a || ch1 == 0x0d) ++le_control_chars; + } + + // If we are getting both LE and BE control chars then this file is not + // utf16 + if (le_control_chars && be_control_chars) return None; + } + + if (le_control_chars) + return UTF16_LE_NOBOM; + else if (be_control_chars) + return UTF16_BE_NOBOM; + else + return None; +} + +/////////////////////////////////////////////////////////////////////////////// +// Checks if a buffer contains text that looks like utf16. This is done based +// the use of nulls which in ASCII/script like text can be useful to identify. +// Returns: +// None - not valid utf16 +// UTF16_LE_NOBOM - looks like utf16 le +// UTF16_BE_NOBOM - looks like utf16 be +/////////////////////////////////////////////////////////////////////////////// + +TextEncodingDetect::Encoding TextEncodingDetect::CheckUTF16ASCII( + const unsigned char *pBuffer, size_t size) const { + int num_odd_nulls = 0; + int num_even_nulls = 0; + + // Get even nulls + size_t pos = 0; + while (pos < size) { + if (pBuffer[pos] == 0) num_even_nulls++; + + pos += 2; + } + + // Get odd nulls + pos = 1; + while (pos < size) { + if (pBuffer[pos] == 0) num_odd_nulls++; + + pos += 2; + } + + double even_null_threshold = (num_even_nulls * 2.0) / size; + double odd_null_threshold = (num_odd_nulls * 2.0) / size; + double expected_null_threshold = utf16_expected_null_percent_ / 100.0; + double unexpected_null_threshold = utf16_unexpected_null_percent_ / 100.0; + + // Lots of odd nulls, low number of even nulls + if (even_null_threshold < unexpected_null_threshold && + odd_null_threshold > expected_null_threshold) + return UTF16_LE_NOBOM; + + // Lots of even nulls, low number of odd nulls + if (odd_null_threshold < unexpected_null_threshold && + even_null_threshold > expected_null_threshold) + return UTF16_BE_NOBOM; + + // Don't know + return None; +} + +/////////////////////////////////////////////////////////////////////////////// +// Checks if a buffer contains any nulls. Used to check for binary vs text data. +/////////////////////////////////////////////////////////////////////////////// + +bool TextEncodingDetect::DoesContainNulls(const unsigned char *pBuffer, + size_t size) { + size_t pos = 0; + while (pos < size) { + if (pBuffer[pos++] == 0) return true; + } + + return false; +} diff --git a/src/ui/encoding/TextEncodingDetect.h b/src/ui/encoding/TextEncodingDetect.h new file mode 100644 index 00000000..6d861716 --- /dev/null +++ b/src/ui/encoding/TextEncodingDetect.h @@ -0,0 +1,85 @@ +#pragma once +#ifndef TEXT_ENCODING_DETECT_H_ +#define TEXT_ENCODING_DETECT_H_ + +// +// Copyright 2015 Jonathan Bennett <[email protected]> +// +// https://www.autoitscript.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Includes +#include <stddef.h> + +namespace AutoIt::Common { +class TextEncodingDetect { + public: + enum Encoding { + None, // Unknown or binary + ANSI, // 0-255 + ASCII, // 0-127 + UTF8_BOM, // UTF8 with BOM + UTF8_NOBOM, // UTF8 without BOM + UTF16_LE_BOM, // UTF16 LE with BOM + UTF16_LE_NOBOM, // UTF16 LE without BOM + UTF16_BE_BOM, // UTF16-BE with BOM + UTF16_BE_NOBOM, // UTF16-BE without BOM + }; + + TextEncodingDetect(); + ~TextEncodingDetect() = default; + + static Encoding CheckBOM( + const unsigned char *pBuffer, + size_t size); // Just check if there is a BOM and return + Encoding DetectEncoding(const unsigned char *pBuffer, size_t size) + const; // Check BOM and also guess if there is no BOM + static int GetBOMLengthFromEncodingMode( + Encoding encoding); // Just return the BOM length of a given mode + + void SetNullSuggestsBinary(bool null_suggests_binary) { + null_suggests_binary_ = null_suggests_binary; + } + void SetUtf16UnexpectedNullPercent(int percent); + void SetUtf16ExpectedNullPercent(int percent); + + private: + TextEncodingDetect(const TextEncodingDetect &); + const TextEncodingDetect &operator=(const TextEncodingDetect &); + + static const unsigned char *utf16_bom_le_; + static const unsigned char *utf16_bom_be_; + static const unsigned char *utf8_bom_; + + bool null_suggests_binary_; + int utf16_expected_null_percent_; + int utf16_unexpected_null_percent_; + + Encoding CheckUTF8(const unsigned char *pBuffer, + size_t size) const; // Check for valid UTF8 with no BOM + static Encoding CheckUTF16NewlineChars( + const unsigned char *pBuffer, + size_t size); // Check for valid UTF16 with no BOM via control chars + Encoding CheckUTF16ASCII(const unsigned char *pBuffer, size_t size) + const; // Check for valid UTF16 with no BOM via null distribution + static bool DoesContainNulls(const unsigned char *pBuffer, + size_t size); // Check for nulls +}; + +} // namespace AutoIt::Common + +////////////////////////////////////////////////////////////////////// + +#endif diff --git a/src/ui/main_window/MainWindowSlotFunction.cpp b/src/ui/main_window/MainWindowSlotFunction.cpp index d2ad177a..593fe6fe 100644 --- a/src/ui/main_window/MainWindowSlotFunction.cpp +++ b/src/ui/main_window/MainWindowSlotFunction.cpp @@ -69,7 +69,8 @@ void MainWindow::slotEncrypt() { process_operation(this, _("Symmetrically Encrypting"), [&]() { try { - auto buffer = edit->curTextPage()->toPlainText().toUtf8().toStdString(); + auto buffer = + edit->curTextPage()->getTextPage()->toPlainText().toStdString(); error = GpgFrontend::BasicOperator::GetInstance().EncryptSymmetric( buffer, tmp, result); } catch (const std::runtime_error& e) { @@ -94,7 +95,8 @@ void MainWindow::slotEncrypt() { process_operation(this, _("Encrypting"), [&]() { try { - auto buffer = edit->curTextPage()->toPlainText().toUtf8().toStdString(); + auto buffer = + edit->curTextPage()->getTextPage()->toPlainText().toStdString(); error = GpgFrontend::BasicOperator::GetInstance().Encrypt( std::move(keys), buffer, tmp, result); } catch (const std::runtime_error& e) { @@ -114,7 +116,8 @@ void MainWindow::slotEncrypt() { infoBoard->resetOptionActionsMenu(); #ifdef SMTP_SUPPORT if (check_gpg_error_2_err_code(error) == GPG_ERR_NO_ERROR) - send_an_email(this, infoBoard, edit->curTextPage()->toPlainText()); + send_an_email(this, infoBoard, + edit->curTextPage()->getTextPage()->toPlainText()); #endif } else { QMessageBox::critical(this, _("Error"), @@ -157,7 +160,11 @@ void MainWindow::slotSign() { process_operation(this, _("Signing"), [&]() { try { - auto buffer = edit->curTextPage()->toPlainText().toUtf8().toStdString(); + auto buffer = edit->curTextPage() + ->getTextPage() + ->toPlainText() + .toUtf8() + .toStdString(); error = GpgFrontend::BasicOperator::GetInstance().Sign( std::move(keys), buffer, tmp, GPGME_SIG_MODE_CLEAR, result); } catch (const std::runtime_error& e) { @@ -183,7 +190,7 @@ void MainWindow::slotDecrypt() { if (edit->tabCount() == 0 || edit->slotCurPageTextEdit() == nullptr) return; auto decrypted = std::make_unique<ByteArray>(); - QByteArray text = edit->curTextPage()->toPlainText().toUtf8(); + QByteArray text = edit->curTextPage()->getTextPage()->toPlainText().toUtf8(); if (text.trimmed().startsWith(GpgConstants::GPG_FRONTEND_SHORT_CRYPTO_HEAD)) { QMessageBox::critical( @@ -234,7 +241,7 @@ void MainWindow::slotFind() { void MainWindow::slotVerify() { if (edit->tabCount() == 0 || edit->slotCurPageTextEdit() == nullptr) return; - auto text = edit->curTextPage()->toPlainText().toUtf8(); + auto text = edit->curTextPage()->getTextPage()->toPlainText().toUtf8(); // TODO(Saturneric) PreventNoDataErr auto sig_buffer = std::make_unique<ByteArray>(); @@ -317,7 +324,11 @@ void MainWindow::slotEncryptSign() { auto tmp = std::make_unique<ByteArray>(); process_operation(this, _("Encrypting and Signing"), [&]() { try { - auto buffer = edit->curTextPage()->toPlainText().toUtf8().toStdString(); + auto buffer = edit->curTextPage() + ->getTextPage() + ->toPlainText() + .toUtf8() + .toStdString(); error = GpgFrontend::BasicOperator::GetInstance().EncryptSign( std::move(keys), std::move(signer_keys), buffer, tmp, encr_result, sign_result); @@ -352,7 +363,8 @@ void MainWindow::slotEncryptSign() { infoBoard->resetOptionActionsMenu(); #ifdef SMTP_SUPPORT if (check_gpg_error_2_err_code(error) == GPG_ERR_NO_ERROR) - send_an_email(this, infoBoard, edit->curTextPage()->toPlainText(), false); + send_an_email(this, infoBoard, + edit->curTextPage()->getTextPage()->toPlainText(), false); #endif #ifdef ADVANCE_SUPPORT @@ -376,7 +388,7 @@ void MainWindow::slotEncryptSign() { void MainWindow::slotDecryptVerify() { if (edit->tabCount() == 0 || edit->slotCurPageTextEdit() == nullptr) return; - QString plainText = edit->curTextPage()->toPlainText(); + QString plainText = edit->curTextPage()->getTextPage()->toPlainText(); #ifdef ADVANCE_SUPPORT if (plainText.trimmed().startsWith( @@ -453,7 +465,8 @@ void MainWindow::slotAppendSelectedKeys() { auto key_ids = mKeyList->getSelected(); GpgKeyImportExporter::GetInstance().ExportKeys(key_ids, exported); - edit->curTextPage()->append(QString::fromStdString(*exported)); + edit->curTextPage()->getTextPage()->appendPlainText( + QString::fromStdString(*exported)); } void MainWindow::slotCopyMailAddressToClipboard() { diff --git a/src/ui/main_window/MainWindowSlotUI.cpp b/src/ui/main_window/MainWindowSlotUI.cpp index 0c0204f4..9f40ec9d 100644 --- a/src/ui/main_window/MainWindowSlotUI.cpp +++ b/src/ui/main_window/MainWindowSlotUI.cpp @@ -41,7 +41,7 @@ void MainWindow::slotStartWizard() { void MainWindow::slotImportKeyFromEdit() { if (edit->tabCount() == 0 || edit->slotCurPageTextEdit() == nullptr) return; CommonUtils::GetInstance()->slotImportKeys( - this, edit->curTextPage()->toPlainText().toStdString()); + this, edit->curTextPage()->getTextPage()->toPlainText().toStdString()); } void MainWindow::slotOpenKeyManagement() { @@ -139,7 +139,7 @@ void MainWindow::slotCleanDoubleLinebreaks() { return; } - QString content = edit->curTextPage()->toPlainText(); + QString content = edit->curTextPage()->getTextPage()->toPlainText(); content.replace("\n\n", "\n"); edit->slotFillTextEditWithText(content); } @@ -149,7 +149,7 @@ void MainWindow::slotAddPgpHeader() { return; } - QString content = edit->curTextPage()->toPlainText().trimmed(); + QString content = edit->curTextPage()->getTextPage()->toPlainText().trimmed(); content.prepend("\n\n").prepend(GpgConstants::PGP_CRYPT_BEGIN); content.append("\n").append(GpgConstants::PGP_CRYPT_END); @@ -162,7 +162,7 @@ void MainWindow::slotCutPgpHeader() { return; } - QString content = edit->curTextPage()->toPlainText(); + QString content = edit->curTextPage()->getTextPage()->toPlainText(); int start = content.indexOf(GpgConstants::PGP_CRYPT_BEGIN); int end = content.indexOf(GpgConstants::PGP_CRYPT_END); diff --git a/src/ui/thread/FileReadThread.cpp b/src/ui/thread/FileReadThread.cpp index 270f50e7..04f713bd 100644 --- a/src/ui/thread/FileReadThread.cpp +++ b/src/ui/thread/FileReadThread.cpp @@ -29,39 +29,41 @@ namespace GpgFrontend::UI { -FileReadThread::FileReadThread(std::string path) : path(std::move(path)) {} +FileReadThread::FileReadThread(std::string path) : path(std::move(path)) { + qRegisterMetaType<std::string>("std::string"); +} void FileReadThread::run() { - LOG(INFO) << "Started"; + LOG(INFO) << "started"; boost::filesystem::path read_file_path(this->path); if (is_regular_file(read_file_path)) { - LOG(INFO) << "Read Open"; + LOG(INFO) << "read open"; - auto fp = fopen(read_file_path.string().c_str(), "r"); + auto fp = fopen(read_file_path.string().c_str(), "rb"); size_t read_size; - LOG(INFO) << "Thread Start Reading"; + LOG(INFO) << "thread start reading"; - char buffer[8192]; + char buffer[4096]; while ((read_size = fread(buffer, sizeof(char), sizeof buffer, fp)) > 0) { // Check isInterruptionRequested if (QThread::currentThread()->isInterruptionRequested()) { - LOG(INFO) << "Read Thread isInterruptionRequested "; + LOG(INFO) << "thread is interruption requested "; fclose(fp); return; } - LOG(INFO) << "Read Thread Read block size " << read_size; + LOG(INFO) << "block size " << read_size; std::string buffer_str(buffer, read_size); - emit sendReadBlock(QString::fromStdString(buffer_str)); + emit sendReadBlock(buffer_str); #ifdef RELEASE QThread::msleep(32); #else - QThread::msleep(48); + QThread::msleep(128); #endif } fclose(fp); emit readDone(); - LOG(INFO) << "Thread End Reading"; + LOG(INFO) << "thread end reading"; } } diff --git a/src/ui/thread/FileReadThread.h b/src/ui/thread/FileReadThread.h index 46ed6cbc..65982848 100644 --- a/src/ui/thread/FileReadThread.h +++ b/src/ui/thread/FileReadThread.h @@ -37,7 +37,7 @@ class FileReadThread : public QThread { signals: - void sendReadBlock(const QString& block); + void sendReadBlock(const std::string& block); void readDone(); diff --git a/src/ui/widgets/EditorPage.cpp b/src/ui/widgets/EditorPage.cpp deleted file mode 100644 index 8c26fe71..00000000 --- a/src/ui/widgets/EditorPage.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/** - * 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. - * - * Foobar 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 Foobar. If not, see <https://www.gnu.org/licenses/>. - * - * The initial version of the source code is inherited from gpg4usb-team. - * Their source code version also complies with GNU General Public License. - * - * The source code version of this software was modified and released - * by Saturneric<[email protected]><[email protected]> starting on May 12, 2021. - * - */ - -#include "ui/widgets/EditorPage.h" - -#include <boost/filesystem.hpp> -#include <utility> - -#include "ui/thread/FileReadThread.h" - -namespace GpgFrontend::UI { - -EditorPage::EditorPage(QString filePath, QWidget* parent) - : QWidget(parent), full_file_path_(std::move(filePath)) { - // Set the Textedit properties - textPage = new QTextEdit(); - textPage->setAcceptRichText(false); - - // Set the layout style - mainLayout = new QVBoxLayout(); - mainLayout->setSpacing(0); - mainLayout->addWidget(textPage); - mainLayout->setContentsMargins(0, 0, 0, 0); - setLayout(mainLayout); - - textPage->setFocus(); - - // Front in same width - this->setFont({"Courier"}); - this->setAttribute(Qt::WA_DeleteOnClose); -} - -const QString& EditorPage::getFilePath() const { return full_file_path_; } - -QTextEdit* EditorPage::getTextPage() { return textPage; } - -void EditorPage::setFilePath(const QString& filePath) { - full_file_path_ = filePath; -} - -void EditorPage::showNotificationWidget(QWidget* widget, - const char* className) { - widget->setProperty(className, true); - mainLayout->addWidget(widget); -} - -void EditorPage::closeNoteByClass(const char* className) { - QList<QWidget*> widgets = findChildren<QWidget*>(); - for (QWidget* widget : widgets) { - if (widget->property(className) == true) { - widget->close(); - } - } -} - -void EditorPage::slotFormatGpgHeader() { - QString content = textPage->toPlainText(); - - // Get positions of the gpg-headers, if they exist - int start = content.indexOf(GpgFrontend::GpgConstants::PGP_SIGNED_BEGIN); - int startSig = - content.indexOf(GpgFrontend::GpgConstants::PGP_SIGNATURE_BEGIN); - int endSig = content.indexOf(GpgFrontend::GpgConstants::PGP_SIGNATURE_END); - - if (start < 0 || startSig < 0 || endSig < 0 || signMarked) { - return; - } - - signMarked = true; - - // Set the fontstyle for the header - QTextCharFormat signFormat; - signFormat.setForeground(QBrush(QColor::fromRgb(80, 80, 80))); - signFormat.setFontPointSize(9); - - // set font style for the signature - QTextCursor cursor(textPage->document()); - cursor.setPosition(startSig, QTextCursor::MoveAnchor); - cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, endSig); - cursor.setCharFormat(signFormat); - - // set the font style for the header - int headEnd = content.indexOf("\n\n", start); - cursor.setPosition(start, QTextCursor::MoveAnchor); - cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, headEnd); - cursor.setCharFormat(signFormat); -} - -void EditorPage::ReadFile() { - LOG(INFO) << "Called"; - - read_done_ = false; - - auto text_page = this->getTextPage(); - text_page->setReadOnly(true); - auto thread = new FileReadThread(this->full_file_path_.toStdString()); - - connect(thread, &FileReadThread::sendReadBlock, this, - &EditorPage::slotInsertText); - - connect(thread, &FileReadThread::readDone, this, [=]() { - LOG(INFO) << "thread read done"; - text_page->document()->setModified(false); - text_page->setReadOnly(false); - }); - - connect(thread, &FileReadThread::finished, this, [=]() { - LOG(INFO) << "thread finished"; - thread->deleteLater(); - read_done_ = true; - read_hread_ = nullptr; - }); - - connect(this, &EditorPage::destroyed, [=]() { - LOG(INFO) << "request interruption for read thread"; - thread->requestInterruption(); - read_hread_ = nullptr; - }); - this->read_hread_ = thread; - thread->start(); -} - -void EditorPage::slotInsertText(const QString& text) { - this->getTextPage()->insertPlainText(text); -} -void EditorPage::PrepareToDestroy() { - if (read_hread_) { - read_hread_->requestInterruption(); - read_hread_ = nullptr; - } -} - -} // namespace GpgFrontend::UI diff --git a/src/ui/widgets/InfoBoardWidget.h b/src/ui/widgets/InfoBoardWidget.h index 8d37be6c..816da849 100644 --- a/src/ui/widgets/InfoBoardWidget.h +++ b/src/ui/widgets/InfoBoardWidget.h @@ -25,7 +25,7 @@ #ifndef __VERIFYNOTIFICATION_H__ #define __VERIFYNOTIFICATION_H__ -#include "EditorPage.h" +#include "PlainTextEditorPage.h" #include "gpg/result_analyse/VerifyResultAnalyse.h" #include "ui/details/VerifyDetailsDialog.h" diff --git a/src/ui/widgets/PlainTextEditorPage.cpp b/src/ui/widgets/PlainTextEditorPage.cpp new file mode 100644 index 00000000..1a99c2a6 --- /dev/null +++ b/src/ui/widgets/PlainTextEditorPage.cpp @@ -0,0 +1,257 @@ +/** + * 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. + * + * Foobar 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 Foobar. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from gpg4usb-team. + * Their source code version also complies with GNU General Public License. + * + * The source code version of this software was modified and released + * by Saturneric<[email protected]><[email protected]> starting on May 12, 2021. + * + */ + +#include "ui/widgets/PlainTextEditorPage.h" + +#include <boost/filesystem.hpp> +#include <boost/format.hpp> +#include <utility> + +#include "ui/encoding/TextEncodingDetect.h" +#include "ui/thread/FileReadThread.h" +#include "ui_PlainTextEditor.h" + +namespace GpgFrontend::UI { + +PlainTextEditorPage::PlainTextEditorPage(QString filePath, QWidget* parent) + : QWidget(parent), + ui(std::make_shared<Ui_PlainTextEditor>()), + full_file_path_(std::move(filePath)) { + ui->setupUi(this); + + if (full_file_path_.isEmpty()) read_done_ = true; + + ui->textPage->setFocus(); + ui->loadingLabel->setHidden(true); + + // Front in same width + this->setFont({"Courier"}); + this->setAttribute(Qt::WA_DeleteOnClose); + + this->ui->characterLabel->setText(_("0 character")); + this->ui->lfLabel->setText(_("None")); + this->ui->encodingLabel->setText(_("Binary")); + + connect(ui->textPage, &QPlainTextEdit::textChanged, this, [=]() { + if (!read_done_) return; + + auto text = ui->textPage->document()->toPlainText(); + auto str = boost::format(_("%1% character(s)")) % text.size(); + this->ui->characterLabel->setText(str.str().c_str()); + + detect_encoding(text.toStdString()); + }); + + ui->loadingLabel->setText(_("Loading...")); +} + +const QString& PlainTextEditorPage::getFilePath() const { + return full_file_path_; +} + +QPlainTextEdit* PlainTextEditorPage::getTextPage() { return ui->textPage; } + +void PlainTextEditorPage::setFilePath(const QString& filePath) { + full_file_path_ = filePath; +} + +void PlainTextEditorPage::showNotificationWidget(QWidget* widget, + const char* className) { + widget->setProperty(className, true); + ui->verticalLayout->addWidget(widget); +} + +void PlainTextEditorPage::closeNoteByClass(const char* className) { + QList<QWidget*> widgets = findChildren<QWidget*>(); + for (QWidget* widget : widgets) { + if (widget->property(className) == true) { + widget->close(); + } + } +} + +void PlainTextEditorPage::slotFormatGpgHeader() { + QString content = ui->textPage->toPlainText(); + + // Get positions of the gpg-headers, if they exist + int start = content.indexOf(GpgFrontend::GpgConstants::PGP_SIGNED_BEGIN); + int startSig = + content.indexOf(GpgFrontend::GpgConstants::PGP_SIGNATURE_BEGIN); + int endSig = content.indexOf(GpgFrontend::GpgConstants::PGP_SIGNATURE_END); + + if (start < 0 || startSig < 0 || endSig < 0 || signMarked) { + return; + } + + signMarked = true; + + // Set the fontstyle for the header + QTextCharFormat signFormat; + signFormat.setForeground(QBrush(QColor::fromRgb(80, 80, 80))); + signFormat.setFontPointSize(9); + + // set font style for the signature + QTextCursor cursor(ui->textPage->document()); + cursor.setPosition(startSig, QTextCursor::MoveAnchor); + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, endSig); + cursor.setCharFormat(signFormat); + + // set the font style for the header + int headEnd = content.indexOf("\n\n", start); + cursor.setPosition(start, QTextCursor::MoveAnchor); + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, headEnd); + cursor.setCharFormat(signFormat); +} + +void PlainTextEditorPage::ReadFile() { + LOG(INFO) << "called"; + + read_done_ = false; + read_bytes_ = 0; + ui->textPage->setEnabled(false); + ui->textPage->setReadOnly(true); + ui->textPage->blockSignals(true); + ui->loadingLabel->setHidden(false); + ui->textPage->document()->blockSignals(true); + + auto text_page = this->getTextPage(); + text_page->setReadOnly(true); + auto thread = new FileReadThread(this->full_file_path_.toStdString()); + + connect(thread, &FileReadThread::sendReadBlock, this, + &PlainTextEditorPage::slotInsertText); + + connect(thread, &FileReadThread::readDone, this, [=]() { + LOG(INFO) << "thread read done"; + if (!binary_mode_) { + text_page->setReadOnly(false); + } + }); + + connect(thread, &FileReadThread::finished, this, [=]() { + LOG(INFO) << "thread finished"; + thread->deleteLater(); + read_done_ = true; + read_thread_ = nullptr; + ui->textPage->setEnabled(true); + text_page->document()->setModified(false); + ui->textPage->blockSignals(false); + ui->textPage->document()->blockSignals(false); + ui->loadingLabel->setHidden(true); + }); + + connect(this, &PlainTextEditorPage::destroyed, [=]() { + LOG(INFO) << "request interruption for read thread"; + thread->requestInterruption(); + read_thread_ = nullptr; + }); + this->read_thread_ = thread; + thread->start(); +} + +std::string binary_to_string(const std::string& source) { + static char syms[] = "0123456789ABCDEF"; + std::stringstream ss; + for (unsigned char c : source) + ss << syms[((c >> 4) & 0xf)] << syms[c & 0xf] << " "; + return ss.str(); +} + +void PlainTextEditorPage::slotInsertText(const std::string& data) { + LOG(INFO) << "data size" << data.size(); + read_bytes_ += data.size(); + // If binary format is detected, the entire file is converted to binary format + // for display + bool if_last_binary_mode = binary_mode_; + if (!binary_mode_) { + detect_encoding(data); + } + + if (binary_mode_) { + if (if_last_binary_mode != binary_mode_) { + auto text_buffer = + ui->textPage->document()->toRawText().toLocal8Bit().toStdString(); + ui->textPage->clear(); + this->getTextPage()->insertPlainText( + binary_to_string(text_buffer).c_str()); + this->ui->lfLabel->setText("None"); + } + this->getTextPage()->insertPlainText(binary_to_string(data).c_str()); + + auto str = boost::format(_("%1% byte(s)")) % read_bytes_; + this->ui->characterLabel->setText(str.str().c_str()); + } else { + this->getTextPage()->insertPlainText(data.c_str()); + + auto text = this->getTextPage()->toPlainText(); + auto str = boost::format(_("%1% character(s)")) % text.size(); + this->ui->characterLabel->setText(str.str().c_str()); + detect_cr_lf(text); + } +} + +void PlainTextEditorPage::PrepareToDestroy() { + if (read_thread_) { + read_thread_->requestInterruption(); + read_thread_ = nullptr; + } +} + +void PlainTextEditorPage::detect_encoding(const std::string& data) { + AutoIt::Common::TextEncodingDetect text_detect; + AutoIt::Common::TextEncodingDetect::Encoding encoding = + text_detect.DetectEncoding((unsigned char*)(data.data()), data.size()); + + if (encoding == AutoIt::Common::TextEncodingDetect::None) { + binary_mode_ = true; + ui->encodingLabel->setText(_("Binary")); + } else if (encoding == AutoIt::Common::TextEncodingDetect::ASCII) { + ui->encodingLabel->setText(_("ASCII(7 bits)")); + } else if (encoding == AutoIt::Common::TextEncodingDetect::ANSI) { + ui->encodingLabel->setText(_("ASCII(8 bits)")); + } else if (encoding == AutoIt::Common::TextEncodingDetect::UTF8_BOM || + encoding == AutoIt::Common::TextEncodingDetect::UTF8_NOBOM) { + ui->encodingLabel->setText(_("UTF-8")); + } else if (encoding == AutoIt::Common::TextEncodingDetect::UTF16_LE_BOM || + encoding == AutoIt::Common::TextEncodingDetect::UTF16_LE_NOBOM) { + ui->encodingLabel->setText(_("UTF-16")); + } else if (encoding == AutoIt::Common::TextEncodingDetect::UTF16_BE_BOM || + encoding == AutoIt::Common::TextEncodingDetect::UTF16_BE_NOBOM) { + ui->encodingLabel->setText(_("UTF-16(BE)")); + } +} + +void PlainTextEditorPage::detect_cr_lf(const QString& data) { + if (binary_mode_) { + this->ui->lfLabel->setText("None"); + return; + } + if (data.contains("\r\n")) { + this->ui->lfLabel->setText("CRLF"); + } else { + this->ui->lfLabel->setText("LF"); + } +} + +} // namespace GpgFrontend::UI diff --git a/src/ui/widgets/EditorPage.h b/src/ui/widgets/PlainTextEditorPage.h index d1bc1ac2..24823c06 100644 --- a/src/ui/widgets/EditorPage.h +++ b/src/ui/widgets/PlainTextEditorPage.h @@ -28,12 +28,7 @@ #include "gpg/GpgConstants.h" #include "ui/GpgFrontendUI.h" -QT_BEGIN_NAMESPACE -class QVBoxLayout; -class QHBoxLayout; -class QString; -class QLabel; -QT_END_NAMESPACE +class Ui_PlainTextEditor; namespace GpgFrontend::UI { @@ -41,7 +36,7 @@ namespace GpgFrontend::UI { * @brief Class for handling a single tab of the tabwidget * */ -class EditorPage : public QWidget { +class PlainTextEditorPage : public QWidget { Q_OBJECT public: /** @@ -50,7 +45,8 @@ class EditorPage : public QWidget { * @param filePath Path of the file handled in this tab * @param parent Pointer to the parent widget */ - explicit EditorPage(QString filePath = "", QWidget* parent = nullptr); + explicit PlainTextEditorPage(QString filePath = "", + QWidget* parent = nullptr); /** * @details Get the filepath of the currently activated tab. @@ -67,7 +63,7 @@ class EditorPage : public QWidget { /** * @details Return pointer tp the textedit of the currently activated tab. */ - QTextEdit* getTextPage(); + QPlainTextEdit* getTextPage(); /** * @details Show additional widget at buttom of currently active tab @@ -91,12 +87,17 @@ class EditorPage : public QWidget { void PrepareToDestroy(); private: - QTextEdit* textPage; /** The textedit of the tab */ - QVBoxLayout* mainLayout; /** The layout for the tab */ + std::shared_ptr<Ui_PlainTextEditor> ui; QString full_file_path_; /** The path to the file handled in the tab */ bool signMarked{}; /** true, if the signed header is marked, false if not */ bool read_done_ = false; - QThread* read_hread_ = nullptr; + QThread* read_thread_ = nullptr; + bool binary_mode_ = false; + size_t read_bytes_ = 0; + + void detect_encoding(const std::string& data); + + void detect_cr_lf(const QString& data); private slots: @@ -105,7 +106,7 @@ class EditorPage : public QWidget { */ void slotFormatGpgHeader(); - void slotInsertText(const QString& text); + void slotInsertText(const std::string& data); }; } // namespace GpgFrontend::UI diff --git a/src/ui/widgets/TextEdit.cpp b/src/ui/widgets/TextEdit.cpp index be6ec181..a8ff8d73 100644 --- a/src/ui/widgets/TextEdit.cpp +++ b/src/ui/widgets/TextEdit.cpp @@ -50,7 +50,7 @@ TextEdit::TextEdit(QWidget* parent) : QWidget(parent) { void TextEdit::slotNewTab() { QString header = _("untitled") + QString::number(++countPage) + ".txt"; - auto* page = new EditorPage(); + auto* page = new PlainTextEditorPage(); auto index = tabWidget->addTab(page, header); tabWidget->setTabIcon(index, QIcon(":file.png")); tabWidget->setCurrentIndex(tabWidget->count() - 1); @@ -80,11 +80,11 @@ void TextEdit::slotOpenFile(QString& path) { LOG(INFO) << "path" << path.toStdString(); auto result = file.open(QIODevice::ReadOnly | QIODevice::Text); if (result) { - auto* page = new EditorPage(path); + auto* page = new PlainTextEditorPage(path); connect(page->getTextPage()->document(), &QTextDocument::modificationChanged, this, &TextEdit::slotShowModified); - + QApplication::setOverrideCursor(Qt::WaitCursor); auto index = tabWidget->addTab(page, strippedName(path)); tabWidget->setTabIcon(index, QIcon(":file.png")); @@ -111,7 +111,7 @@ void TextEdit::slotOpen() { QFile file(fileName); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { - auto* page = new EditorPage(fileName); + auto* page = new PlainTextEditorPage(fileName); QTextStream in(&file); QApplication::setOverrideCursor(Qt::WaitCursor); @@ -165,7 +165,7 @@ bool TextEdit::saveFile(const QString& fileName) { QFile file(fileName); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { - EditorPage* page = slotCurPageTextEdit(); + PlainTextEditorPage* page = slotCurPageTextEdit(); QTextStream outputStream(&file); QApplication::setOverrideCursor(Qt::WaitCursor); @@ -197,7 +197,7 @@ bool TextEdit::slotSaveAs() { return true; } - EditorPage* page = slotCurPageTextEdit(); + PlainTextEditorPage* page = slotCurPageTextEdit(); QString path; if (!page->getFilePath().isEmpty()) { path = page->getFilePath(); @@ -250,7 +250,7 @@ void TextEdit::removeTab(int index) { * If it returns false, the close event should be aborted. */ bool TextEdit::maybeSaveCurrentTab(bool askToSave) { - EditorPage* page = slotCurPageTextEdit(); + PlainTextEditorPage* page = slotCurPageTextEdit(); // if this page is no textedit, there should be nothing to save if (page == nullptr) { return true; @@ -352,13 +352,8 @@ bool TextEdit::maybeSaveAnyTab() { return false; } -QTextEdit* TextEdit::curTextPage() const { - auto* curTextPage = qobject_cast<EditorPage*>(tabWidget->currentWidget()); - if (curTextPage != nullptr) { - return curTextPage->getTextPage(); - } else { - return nullptr; - } +PlainTextEditorPage* TextEdit::curTextPage() const { + return qobject_cast<PlainTextEditorPage*>(tabWidget->currentWidget()); } FilePage* TextEdit::curFilePage() const { @@ -372,8 +367,9 @@ FilePage* TextEdit::curFilePage() const { int TextEdit::tabCount() const { return tabWidget->count(); } -EditorPage* TextEdit::slotCurPageTextEdit() const { - auto* curPage = qobject_cast<EditorPage*>(tabWidget->currentWidget()); +PlainTextEditorPage* TextEdit::slotCurPageTextEdit() const { + auto* curPage = + qobject_cast<PlainTextEditorPage*>(tabWidget->currentWidget()); return curPage; } @@ -387,7 +383,7 @@ void TextEdit::slotQuote() const { return; } - QTextCursor cursor(curTextPage()->document()); + QTextCursor cursor(curTextPage()->getTextPage()->document()); // beginEditBlock and endEditBlock() let operation look like single undo/redo // operation @@ -405,10 +401,10 @@ void TextEdit::slotQuote() const { } void TextEdit::slotFillTextEditWithText(const QString& text) const { - QTextCursor cursor(curTextPage()->document()); + QTextCursor cursor(curTextPage()->getTextPage()->document()); cursor.beginEditBlock(); - this->curTextPage()->selectAll(); - this->curTextPage()->insertPlainText(text); + this->curTextPage()->getTextPage()->selectAll(); + this->curTextPage()->getTextPage()->insertPlainText(text); cursor.endEditBlock(); } @@ -425,7 +421,7 @@ void TextEdit::loadFile(const QString& fileName) { } QTextStream in(&file); QApplication::setOverrideCursor(Qt::WaitCursor); - curTextPage()->setPlainText(in.readAll()); + curTextPage()->getTextPage()->setPlainText(in.readAll()); QApplication::restoreOverrideCursor(); slotCurPageTextEdit()->setFilePath(fileName); tabWidget->setTabText(tabWidget->currentIndex(), strippedName(fileName)); @@ -445,7 +441,7 @@ void TextEdit::slotPrint() { #ifndef QT_NO_PRINTER QTextDocument* document; if (curTextPage() != nullptr) { - document = curTextPage()->document(); + document = curTextPage()->getTextPage()->document(); } QPrinter printer; @@ -464,7 +460,7 @@ void TextEdit::slotShowModified() const { QString title = tabWidget->tabText(index); // if doc is modified now, add leading * to title, // otherwise remove the leading * from the title - if (curTextPage()->document()->isModified()) { + if (curTextPage()->getTextPage()->document()->isModified()) { tabWidget->setTabText(index, title.prepend("* ")); } else { tabWidget->setTabText(index, title.remove(0, 2)); @@ -494,7 +490,7 @@ QHash<int, QString> TextEdit::unsavedDocuments() const { // gedit like "unsaved changed"-dialog for (int i = 0; i < tabWidget->count(); i++) { - auto* ep = qobject_cast<EditorPage*>(tabWidget->widget(i)); + auto* ep = qobject_cast<PlainTextEditorPage*>(tabWidget->widget(i)); if (ep != nullptr && ep->ReadDone() && ep->getTextPage()->document()->isModified()) { QString doc_name = tabWidget->tabText(i); @@ -513,7 +509,7 @@ void TextEdit::slotCut() const { return; } - curTextPage()->cut(); + curTextPage()->getTextPage()->cut(); } void TextEdit::slotCopy() const { @@ -522,7 +518,7 @@ void TextEdit::slotCopy() const { } if (curTextPage() != nullptr) { - curTextPage()->copy(); + curTextPage()->getTextPage()->copy(); } } @@ -531,7 +527,7 @@ void TextEdit::slotPaste() const { return; } - curTextPage()->paste(); + curTextPage()->getTextPage()->paste(); } void TextEdit::slotUndo() const { @@ -539,7 +535,7 @@ void TextEdit::slotUndo() const { return; } - curTextPage()->undo(); + curTextPage()->getTextPage()->undo(); } void TextEdit::slotRedo() const { @@ -547,7 +543,7 @@ void TextEdit::slotRedo() const { return; } - curTextPage()->redo(); + curTextPage()->getTextPage()->redo(); } void TextEdit::slotZoomIn() const { @@ -556,7 +552,7 @@ void TextEdit::slotZoomIn() const { } if (curTextPage() != nullptr) { - curTextPage()->zoomIn(); + curTextPage()->getTextPage()->zoomIn(); } } @@ -566,7 +562,7 @@ void TextEdit::slotZoomOut() const { } if (curTextPage() != nullptr) { - curTextPage()->zoomOut(); + curTextPage()->getTextPage()->zoomOut(); } } @@ -574,7 +570,7 @@ void TextEdit::slotSelectAll() const { if (tabWidget->count() == 0 || curTextPage() == nullptr) { return; } - curTextPage()->selectAll(); + curTextPage()->getTextPage()->selectAll(); } void TextEdit::slotFilePagePathChanged(const QString& path) const { diff --git a/src/ui/widgets/TextEdit.h b/src/ui/widgets/TextEdit.h index e877ccc1..c1f44969 100644 --- a/src/ui/widgets/TextEdit.h +++ b/src/ui/widgets/TextEdit.h @@ -26,9 +26,9 @@ #define __TEXTEDIT_H__ #include "ui/QuitDialog.h" -#include "ui/widgets/EditorPage.h" #include "ui/widgets/FilePage.h" #include "ui/widgets/HelpPage.h" +#include "ui/widgets/PlainTextEditorPage.h" namespace GpgFrontend::UI { /** @@ -66,7 +66,7 @@ class TextEdit : public QWidget { * @return \li reference to QTextEdit if tab has one * \li 0 otherwise (e.g. if helppage) */ - [[nodiscard]] QTextEdit* curTextPage() const; + [[nodiscard]] PlainTextEditorPage* curTextPage() const; [[nodiscard]] FilePage* curFilePage() const; @@ -84,7 +84,7 @@ class TextEdit : public QWidget { * @details Return pointer to the currently activated text edit tab page. * */ - EditorPage* slotCurPageTextEdit() const; + PlainTextEditorPage* slotCurPageTextEdit() const; /** * @details Return pointer to the currently activated file treeview tab page. diff --git a/ui/FilePage.ui b/ui/FilePage.ui index cb4517b1..4467e154 100644 --- a/ui/FilePage.ui +++ b/ui/FilePage.ui @@ -26,6 +26,21 @@ <string>Form</string> </property> <layout class="QGridLayout" name="gridLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> <item row="0" column="0"> <layout class="QVBoxLayout" name="verticalLayout"> <property name="sizeConstraint"> diff --git a/ui/PlainTextEditor.ui b/ui/PlainTextEditor.ui new file mode 100644 index 00000000..bcccdee1 --- /dev/null +++ b/ui/PlainTextEditor.ui @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PlainTextEditor</class> + <widget class="QWidget" name="PlainTextEditor"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1007</width> + <height>701</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="QPlainTextEdit" name="textPage"/> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>12</number> + </property> + <item> + <widget class="QLabel" name="loadingLabel"> + <property name="text"> + <string>Loading...</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="characterLabel"> + <property name="text"> + <string>Character</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="lfLabel"> + <property name="text"> + <string>LF</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="encodingLabel"> + <property name="text"> + <string>UTF-8</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> |