mirror of
https://github.com/JS8Call-improved/JS8Call-improved
synced 2026-08-13 17:47:36 -04:00
Rename all header files with .h extension Fix function declaration in HamlibTransceiver.h that overrode member function but was not marked override Remove unused variable in Modulator.cpp Remove unused files from source tree Remove duplicate LazyFillComboBox files Reformat codebase to LLVM C++ standard
514 lines
16 KiB
C++
514 lines
16 KiB
C++
#include "TransmitTextEdit.h"
|
|
|
|
#include "JS8_Include/commons.h"
|
|
#include "varicode.h"
|
|
|
|
#include <QLoggingCategory>
|
|
#include <iterator>
|
|
Q_DECLARE_LOGGING_CATEGORY(transmittextedit_js8)
|
|
|
|
void setTextEditFont(QTextEdit *edit, QFont font) {
|
|
// all uppercase
|
|
font.setCapitalization(QFont::AllUppercase);
|
|
|
|
edit->setFont(font);
|
|
edit->setFontFamily(font.family());
|
|
edit->setFontItalic(font.italic());
|
|
edit->setFontPointSize(font.pointSize());
|
|
edit->setFontUnderline(font.underline());
|
|
edit->setFontWeight(font.weight());
|
|
|
|
auto d = edit->document();
|
|
d->setDefaultFont(font);
|
|
edit->setDocument(d);
|
|
|
|
auto c = edit->textCursor();
|
|
c.select(QTextCursor::Document);
|
|
auto cf = c.blockCharFormat();
|
|
cf.setFont(font);
|
|
cf.setFontCapitalization(QFont::AllUppercase);
|
|
c.mergeBlockCharFormat(cf);
|
|
|
|
edit->updateGeometry();
|
|
}
|
|
|
|
void setTextEditStyle(QTextEdit *edit, QColor fg, QColor bg, QFont font) {
|
|
edit->setStyleSheet(QString("QTextEdit { color:%1; background: %2; %3; }")
|
|
.arg(fg.name())
|
|
.arg(bg.name())
|
|
.arg(font_as_stylesheet(font)));
|
|
|
|
// QTimer::singleShot(10, nullptr, [edit, fg, bg](){
|
|
QPalette p = edit->palette();
|
|
p.setColor(QPalette::Base, bg);
|
|
p.setColor(QPalette::Active, QPalette::Base, bg);
|
|
p.setColor(QPalette::Disabled, QPalette::Base, bg);
|
|
p.setColor(QPalette::Inactive, QPalette::Base, bg);
|
|
|
|
p.setColor(QPalette::Text, fg);
|
|
p.setColor(QPalette::Active, QPalette::Text, fg);
|
|
p.setColor(QPalette::Disabled, QPalette::Text, fg);
|
|
p.setColor(QPalette::Inactive, QPalette::Text, fg);
|
|
|
|
edit->setBackgroundRole(QPalette::Base);
|
|
edit->setForegroundRole(QPalette::Text);
|
|
edit->setPalette(p);
|
|
|
|
edit->updateGeometry();
|
|
edit->update();
|
|
//});
|
|
}
|
|
|
|
void highlightBlock(QTextBlock block, QFont font, QColor foreground,
|
|
QColor background) {
|
|
QTextCursor cursor(block);
|
|
|
|
// Set background color
|
|
QTextBlockFormat blockFormat = cursor.blockFormat();
|
|
blockFormat.setBackground(background);
|
|
cursor.setBlockFormat(blockFormat);
|
|
|
|
// Set font
|
|
/*
|
|
for (QTextBlock::iterator it = cursor.block().begin(); !(it.atEnd()); ++it)
|
|
{ QTextCharFormat charFormat = it.fragment().charFormat();
|
|
charFormat.setFont(font);
|
|
charFormat.setFontCapitalization(QFont::AllUppercase);
|
|
charFormat.setForeground(QBrush(foreground));
|
|
|
|
QTextCursor tempCursor = cursor;
|
|
tempCursor.setPosition(it.fragment().position());
|
|
tempCursor.setPosition(it.fragment().position() + it.fragment().length(),
|
|
QTextCursor::KeepAnchor); tempCursor.setCharFormat(charFormat);
|
|
}
|
|
*/
|
|
cursor.select(QTextCursor::BlockUnderCursor);
|
|
|
|
auto charFormat = cursor.charFormat();
|
|
charFormat.setFont(font);
|
|
charFormat.setFontCapitalization(QFont::AllUppercase);
|
|
charFormat.setForeground(QBrush(foreground));
|
|
cursor.setCharFormat(charFormat);
|
|
}
|
|
|
|
TransmitTextEdit::TransmitTextEdit(QWidget *parent)
|
|
: QTextEdit(parent), m_sent{0}, m_protected{false} {
|
|
connect(this, &QTextEdit::selectionChanged, this,
|
|
&TransmitTextEdit::on_selectionChanged);
|
|
connect(this, &QTextEdit::cursorPositionChanged, this,
|
|
&TransmitTextEdit::on_selectionChanged);
|
|
connect(this->document(), &QTextDocument::contentsChange, this,
|
|
&TransmitTextEdit::on_textContentsChanged);
|
|
installEventFilter(this);
|
|
}
|
|
|
|
void TransmitTextEdit::setCharsSent(int n) {
|
|
// Never can send more than the document length.
|
|
// From the QTextDocument::characterCount() documentation:
|
|
// As a QTextDocument always contains at least one
|
|
// QChar::ParagraphSeparator, this method will return at least 1. We do not
|
|
// send that paragraph separator.
|
|
n = qMin(n, document()->characterCount() - 1);
|
|
|
|
// update sent display
|
|
auto c = textCursor();
|
|
c.movePosition(QTextCursor::Start);
|
|
c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, n);
|
|
|
|
// keep track of sent text
|
|
m_textSent = c.selectedText().toUpper();
|
|
m_sent = n;
|
|
|
|
// highlight the sent text
|
|
highlight();
|
|
}
|
|
|
|
// override
|
|
QString TransmitTextEdit::toPlainText() const {
|
|
return QTextEdit::toPlainText().toUpper();
|
|
}
|
|
|
|
// override
|
|
void TransmitTextEdit::setPlainText(const QString &text) {
|
|
m_textSent.clear();
|
|
m_sent = 0;
|
|
// Do this last, as it may trigger events that, during processing, like to
|
|
// see m_textSent and m_sent reset.
|
|
QTextEdit::setPlainText(text);
|
|
}
|
|
|
|
//
|
|
void TransmitTextEdit::replaceUnsentText(const QString &text, bool keepCursor) {
|
|
auto rel = relativeTextCursorPosition(textCursor());
|
|
auto c = textCursor();
|
|
c.movePosition(QTextCursor::Start);
|
|
c.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, m_sent);
|
|
c.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
|
|
c.removeSelectedText();
|
|
c.insertText(text);
|
|
|
|
// keep cursor
|
|
if (keepCursor) {
|
|
c.movePosition(QTextCursor::End);
|
|
c.movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor,
|
|
rel.first);
|
|
c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor,
|
|
rel.second - rel.first);
|
|
setTextCursor(c);
|
|
}
|
|
}
|
|
|
|
//
|
|
void TransmitTextEdit::replacePlainText(const QString &text, bool keepCursor) {
|
|
auto rel = relativeTextCursorPosition(textCursor());
|
|
auto c = textCursor();
|
|
c.movePosition(QTextCursor::Start);
|
|
c.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
|
|
c.removeSelectedText();
|
|
c.insertText(text);
|
|
|
|
// keep cursor
|
|
if (keepCursor) {
|
|
c.movePosition(QTextCursor::End);
|
|
c.movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor,
|
|
rel.first);
|
|
c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor,
|
|
rel.second - rel.first);
|
|
setTextCursor(c);
|
|
}
|
|
}
|
|
|
|
//
|
|
void TransmitTextEdit::setFont(QFont f) {
|
|
m_font = f;
|
|
|
|
// then rehighlight
|
|
highlight();
|
|
}
|
|
|
|
//
|
|
void TransmitTextEdit::setFont(QFont f, QColor fg, QColor bg) {
|
|
m_font = f;
|
|
m_fg = fg;
|
|
m_bg = bg;
|
|
|
|
// then rehighlight
|
|
highlight();
|
|
}
|
|
|
|
// override
|
|
void TransmitTextEdit::clear() {
|
|
m_textSent.clear();
|
|
m_sent = 0;
|
|
// Do this last, as it may trigger events that, during processing, like to
|
|
// see m_textSent and m_sent reset.
|
|
QTextEdit::clear();
|
|
}
|
|
|
|
void TransmitTextEdit::setProtected(bool protect) { m_protected = protect; }
|
|
|
|
bool TransmitTextEdit::cursorShouldBeProtected(QTextCursor c) {
|
|
int start = c.selectionStart();
|
|
int end = c.selectionEnd();
|
|
if (end < start) {
|
|
int x = end;
|
|
end = start;
|
|
start = x;
|
|
}
|
|
|
|
// qCDebug(transmittextedit_js8) << "selection" << start << end << m_sent;
|
|
|
|
if (m_sent && start <= m_sent) {
|
|
qCDebug(transmittextedit_js8)
|
|
<< "cursor in protected zone" << start << "<=" << m_sent;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// slot
|
|
void TransmitTextEdit::on_selectionChanged() {
|
|
auto c = textCursor();
|
|
|
|
auto shouldProtect = cursorShouldBeProtected(c);
|
|
|
|
if (shouldProtect) {
|
|
blockSignals(true);
|
|
{
|
|
int end = c.selectionEnd();
|
|
c.movePosition(QTextCursor::Start);
|
|
c.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor,
|
|
m_sent);
|
|
c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor,
|
|
qMax(0, end - m_sent));
|
|
setTextCursor(c);
|
|
}
|
|
blockSignals(false);
|
|
}
|
|
|
|
setProtected(shouldProtect);
|
|
}
|
|
|
|
// slot
|
|
void TransmitTextEdit::on_textContentsChanged(int /*pos*/, int rem, int add) {
|
|
if (rem == 0 && add == 0) {
|
|
return;
|
|
}
|
|
|
|
QString text = toPlainText();
|
|
if (text == m_lastText) {
|
|
return;
|
|
}
|
|
|
|
#if JS8_ALLOW_EXTENDED
|
|
QString normalized = text;
|
|
#else
|
|
QString normalized = text.normalized(QString::NormalizationForm_KD);
|
|
#endif
|
|
|
|
QString result;
|
|
std::copy_if(normalized.begin(), normalized.end(),
|
|
std::back_inserter(result), [](auto const c) {
|
|
auto const lc = c.toLatin1();
|
|
return (lc && (lc == 0x10 || lc == 0x1A ||
|
|
((lc >= 32) && (lc <= 127))))
|
|
#if JS8_ALLOW_UNICODE
|
|
|| c.isPrint();
|
|
#elif JS8_ALLOW_EXTENDED
|
|
|| Varicode::extendedChars().contains(c.toUpper())
|
|
#endif
|
|
;
|
|
});
|
|
|
|
if (result != text) {
|
|
bool blocked = signalsBlocked();
|
|
blockSignals(true);
|
|
{
|
|
replacePlainText(result, true);
|
|
text = result;
|
|
}
|
|
blockSignals(blocked);
|
|
}
|
|
|
|
highlight();
|
|
|
|
m_dirty = true;
|
|
m_lastText = text;
|
|
}
|
|
|
|
void TransmitTextEdit::highlightBase() {
|
|
auto d = document();
|
|
if (!d) {
|
|
return;
|
|
}
|
|
|
|
auto block = d->firstBlock();
|
|
while (block.isValid()) {
|
|
highlightBlock(block, m_font, m_fg, m_bg);
|
|
block = block.next();
|
|
}
|
|
}
|
|
|
|
void TransmitTextEdit::highlightCharsSent() {
|
|
if (!m_sent) {
|
|
return;
|
|
}
|
|
|
|
auto d = document();
|
|
if (!d) {
|
|
return;
|
|
}
|
|
|
|
// highlight sent text
|
|
auto c = textCursor();
|
|
if (c.isNull()) {
|
|
return;
|
|
}
|
|
c.movePosition(QTextCursor::Start);
|
|
c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, m_sent);
|
|
|
|
auto ch = c.charFormat();
|
|
ch.setFontStrikeOut(true);
|
|
c.mergeCharFormat(ch);
|
|
}
|
|
|
|
void TransmitTextEdit::highlight() {
|
|
highlightBase();
|
|
highlightCharsSent();
|
|
}
|
|
|
|
QTextCursor::MoveOperation deleteKeyEventToMoveOperation(QKeyEvent *e) {
|
|
QTextCursor::MoveOperation op = QTextCursor::NoMove;
|
|
|
|
#if 0
|
|
if (e == QKeySequence::Delete) {
|
|
op = QTextCursor::PreviousCharacter;
|
|
}
|
|
else
|
|
#endif
|
|
if (e == QKeySequence::DeleteStartOfWord) {
|
|
op = QTextCursor::StartOfWord;
|
|
} else if (e == QKeySequence::DeleteEndOfWord) {
|
|
op = QTextCursor::EndOfWord;
|
|
} else if (e == QKeySequence::DeleteCompleteLine) {
|
|
op = QTextCursor::StartOfLine;
|
|
} else if (e == QKeySequence::DeleteEndOfLine) {
|
|
op = QTextCursor::EndOfLine;
|
|
}
|
|
|
|
return op;
|
|
}
|
|
|
|
bool isDeleteKeyEvent(QKeyEvent *e) {
|
|
return deleteKeyEventToMoveOperation(e) != QTextCursor::NoMove;
|
|
}
|
|
|
|
QTextCursor::MoveOperation movementKeyEventToMoveOperation(QKeyEvent *e) {
|
|
QTextCursor::MoveOperation op = QTextCursor::NoMove;
|
|
|
|
if (e == QKeySequence::Delete) {
|
|
op = QTextCursor::PreviousCharacter;
|
|
} else if (e == QKeySequence::DeleteStartOfWord) {
|
|
op = QTextCursor::StartOfWord;
|
|
} else if (e == QKeySequence::DeleteEndOfWord) {
|
|
op = QTextCursor::EndOfWord;
|
|
} else if (e == QKeySequence::DeleteCompleteLine) {
|
|
op = QTextCursor::StartOfLine;
|
|
} else if (e == QKeySequence::DeleteEndOfLine) {
|
|
op = QTextCursor::EndOfLine;
|
|
} else if (e == QKeySequence::MoveToNextChar) {
|
|
op = QTextCursor::Right;
|
|
} else if (e == QKeySequence::MoveToPreviousChar) {
|
|
op = QTextCursor::Left;
|
|
} else if (e == QKeySequence::SelectNextChar) {
|
|
op = QTextCursor::Right;
|
|
} else if (e == QKeySequence::SelectPreviousChar) {
|
|
op = QTextCursor::Left;
|
|
} else if (e == QKeySequence::SelectNextWord) {
|
|
op = QTextCursor::WordRight;
|
|
} else if (e == QKeySequence::SelectPreviousWord) {
|
|
op = QTextCursor::WordLeft;
|
|
} else if (e == QKeySequence::SelectStartOfLine) {
|
|
op = QTextCursor::StartOfLine;
|
|
} else if (e == QKeySequence::SelectEndOfLine) {
|
|
op = QTextCursor::EndOfLine;
|
|
} else if (e == QKeySequence::SelectStartOfBlock) {
|
|
op = QTextCursor::StartOfBlock;
|
|
} else if (e == QKeySequence::SelectEndOfBlock) {
|
|
op = QTextCursor::EndOfBlock;
|
|
} else if (e == QKeySequence::SelectStartOfDocument) {
|
|
op = QTextCursor::Start;
|
|
} else if (e == QKeySequence::SelectEndOfDocument) {
|
|
op = QTextCursor::End;
|
|
} else if (e == QKeySequence::SelectPreviousLine) {
|
|
op = QTextCursor::Up;
|
|
} else if (e == QKeySequence::SelectNextLine) {
|
|
op = QTextCursor::Down;
|
|
} else if (e == QKeySequence::MoveToNextWord) {
|
|
op = QTextCursor::WordRight;
|
|
} else if (e == QKeySequence::MoveToPreviousWord) {
|
|
op = QTextCursor::WordLeft;
|
|
} else if (e == QKeySequence::MoveToEndOfBlock) {
|
|
op = QTextCursor::EndOfBlock;
|
|
} else if (e == QKeySequence::MoveToStartOfBlock) {
|
|
op = QTextCursor::StartOfBlock;
|
|
} else if (e == QKeySequence::MoveToNextLine) {
|
|
op = QTextCursor::Down;
|
|
} else if (e == QKeySequence::MoveToPreviousLine) {
|
|
op = QTextCursor::Up;
|
|
} else if (e == QKeySequence::MoveToPreviousLine) {
|
|
op = QTextCursor::Up;
|
|
} else if (e == QKeySequence::MoveToStartOfLine) {
|
|
op = QTextCursor::StartOfLine;
|
|
} else if (e == QKeySequence::MoveToEndOfLine) {
|
|
op = QTextCursor::EndOfLine;
|
|
} else if (e == QKeySequence::MoveToStartOfDocument) {
|
|
op = QTextCursor::Start;
|
|
} else if (e == QKeySequence::MoveToEndOfDocument) {
|
|
op = QTextCursor::End;
|
|
}
|
|
|
|
return op;
|
|
}
|
|
|
|
bool isMovementKeyEvent(QKeyEvent *k) {
|
|
return movementKeyEventToMoveOperation(k) != QTextCursor::NoMove;
|
|
}
|
|
|
|
bool TransmitTextEdit::eventFilter(QObject * /*o*/, QEvent *e) {
|
|
if (e->type() != QEvent::KeyPress) {
|
|
return false;
|
|
}
|
|
|
|
// -1. don't filter the escape key, return key, or enter key here
|
|
QKeyEvent *k = static_cast<QKeyEvent *>(e);
|
|
if (k->key() == Qt::Key_Escape) {
|
|
return false;
|
|
}
|
|
|
|
if (k->key() == Qt::Key_Return || k->key() == Qt::Key_Enter) {
|
|
return false;
|
|
}
|
|
|
|
auto c = textCursor();
|
|
|
|
auto c2 = QTextCursor(c);
|
|
|
|
auto op = movementKeyEventToMoveOperation(k);
|
|
c2.movePosition(op);
|
|
|
|
bool shouldBeProtected = cursorShouldBeProtected(c2);
|
|
|
|
// -1. if this is a delete event (like Ctrl+Backspace) and it should be
|
|
// protected, force protection
|
|
if (isDeleteKeyEvent(k)) {
|
|
// if we technically shouldn't be protected...check to see if the
|
|
// previous character is a space if it is, let's try the move operation
|
|
// from before the space, since the space can cause word coallescing in
|
|
// the qtextdocument making the operation not actually move the cursor
|
|
if (!shouldBeProtected) {
|
|
auto c3 = QTextCursor(c);
|
|
c3.movePosition(QTextCursor::PreviousCharacter);
|
|
if (c.document()
|
|
->characterAt(qMin(c3.selectionStart(), c3.selectionEnd()))
|
|
.isSpace()) {
|
|
c3.movePosition(op);
|
|
shouldBeProtected = cursorShouldBeProtected(c3);
|
|
}
|
|
}
|
|
|
|
if (shouldBeProtected) {
|
|
k->ignore();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// 0. only filter when in a protected range
|
|
// 0b. but only if we're not moving/deleting _into_ the protected range :/
|
|
if (!isProtected() && !shouldBeProtected) {
|
|
return false;
|
|
}
|
|
|
|
// 1. do not filter movement sequences
|
|
if (isMovementKeyEvent(k)) {
|
|
return false;
|
|
}
|
|
|
|
// 2. if on the edge, do not filter if not a backspace
|
|
int start = qMin(c.selectionStart(), c.selectionEnd());
|
|
if (start == m_sent && k->key() != Qt::Key_Backspace) {
|
|
return false;
|
|
}
|
|
|
|
// 3. if on the edge, do not filter if a backspace and there is text
|
|
// selected
|
|
int end = qMax(c.selectionStart(), c.selectionEnd());
|
|
if (start == m_sent && start != end && k->key() == Qt::Key_Backspace) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Q_LOGGING_CATEGORY(transmittextedit_js8, "transmittextedit.js8", QtWarningMsg)
|