/*************************************************************************** * Copyright (C) 2008-2012 by Heiko Koehn - KoehnHeiko@googlemail.com * * Copyright (C) 2014 by Ahmed Charles - acharles@outlook.com * * * * This program 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 2 of the License, or * * (at your option) any later version. * * * * This program 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 this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "TCommandLine.h" #include "Host.h" #include "TConsole.h" #include "TSplitter.h" #include "TTextEdit.h" #include "mudlet.h" #include "pre_guard.h" #include #include #include #include "post_guard.h" TCommandLine::TCommandLine(Host* pHost, TConsole* pConsole, QWidget* parent) : QPlainTextEdit(parent) , mpHost(pHost) , mpConsole(pConsole) , mSelectedText() , mSelectionStart(0) , mTabCompletion() , mTabCompletionCount() , mAutoCompletionCount() , mUserKeptOnTyping() , mHunspellSuggestionNumber() , mpHunspellSuggestionList() { QString path; #ifdef Q_OS_LINUX if (QFile::exists("/usr/share/hunspell/" + pHost->mSpellDic + ".aff")) { path = "/usr/share/hunspell/"; } else { path = "./"; } #elif defined(Q_OS_MAC) path = QCoreApplication::applicationDirPath() + "/../Resources/"; #else path = "./"; #endif QString spell_aff = path + pHost->mSpellDic + ".aff"; QString spell_dic = path + pHost->mSpellDic + ".dic"; mpHunspell = Hunspell_create(spell_aff.toLatin1().data(), spell_dic.toLatin1().data()); mpKeyUnit = mpHost->getKeyUnit(); setAutoFillBackground(true); setFocusPolicy(Qt::StrongFocus); QFont font = mpHost->mDisplayFont; setFont(font); mRegularPalette.setColor(QPalette::Text, mpHost->mCommandLineFgColor); //QColor(0,0,192)); mRegularPalette.setColor(QPalette::Highlight, QColor(0, 0, 192)); mRegularPalette.setColor(QPalette::HighlightedText, QColor(Qt::white)); mRegularPalette.setColor(QPalette::Base, mpHost->mCommandLineBgColor); //QColor(255,255,225)); setPalette(mRegularPalette); mTabCompletionPalette.setColor(QPalette::Text, QColor(0, 0, 192)); mTabCompletionPalette.setColor(QPalette::Highlight, QColor(0, 0, 192)); mTabCompletionPalette.setColor(QPalette::HighlightedText, QColor(Qt::white)); mTabCompletionPalette.setColor(QPalette::Base, QColor(235, 255, 235)); mAutoCompletionPalette.setColor(QPalette::Text, QColor(0, 0, 192)); mAutoCompletionPalette.setColor(QPalette::Highlight, QColor(0, 0, 192)); mAutoCompletionPalette.setColor(QPalette::HighlightedText, QColor(Qt::white)); mAutoCompletionPalette.setColor(QPalette::Base, QColor(255, 235, 235)); mHistoryBuffer = 0; mAutoCompletion = false; setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setCenterOnScroll(false); setWordWrapMode(QTextOption::WrapAnywhere); setContentsMargins(0, 0, 0, 0); } TCommandLine::~TCommandLine() { Hunspell_destroy(mpHunspell); } void TCommandLine::slot_textChanged(const QString& text) { } bool TCommandLine::event(QEvent* event) { if (event->type() == QEvent::KeyPress) { QKeyEvent* ke = static_cast(event); // Shortcut for keypad keys if ((ke->modifiers() & Qt::KeypadModifier) && mpKeyUnit->processDataStream(ke->key(), (int)ke->modifiers())) { ke->accept(); return true; } switch (ke->key()) { case Qt::Key_Space: mTabCompletionCount = -1; mAutoCompletionCount = -1; mTabCompletionTyped = ""; mAutoCompletionTyped = ""; if (mpHost->mAutoClearCommandLineAfterSend) { mHistoryBuffer = -1; } else { mHistoryBuffer = 0; } mLastCompletion = ""; break; case Qt::Key_Backtab: if (ke->modifiers() & Qt::ControlModifier) { int currentIndex = mudlet::self()->mpTabBar->currentIndex(); int count = mudlet::self()->mpTabBar->count(); if (currentIndex - 1 < 0) { mudlet::self()->mpTabBar->setCurrentIndex(count - 1); } else { mudlet::self()->mpTabBar->setCurrentIndex(currentIndex - 1); } } else { handleTabCompletion(false); adjustHeight(); } ke->accept(); return true; case Qt::Key_Tab: if (ke->modifiers() & Qt::ControlModifier) { int currentIndex = mudlet::self()->mpTabBar->currentIndex(); int count = mudlet::self()->mpTabBar->count(); if (currentIndex + 1 < count) { mudlet::self()->mpTabBar->setCurrentIndex(currentIndex + 1); } else { mudlet::self()->mpTabBar->setCurrentIndex(0); } } else { handleTabCompletion(true); } ke->accept(); return true; case Qt::Key_unknown: qWarning() << "ERROR: key unknown!"; break; case Qt::Key_Backspace: if (mpHost->mAutoClearCommandLineAfterSend) { mHistoryBuffer = -1; } else { mHistoryBuffer = 0; } if (mTabCompletionTyped.size() >= 1) { mTabCompletionTyped.chop(1); mAutoCompletionTyped.chop(1); mTabCompletionCount = -1; mAutoCompletionCount = -1; mLastCompletion = ""; } else { mTabCompletionCount = -1; mAutoCompletionCount = -1; mLastCompletion = ""; } QPlainTextEdit::event(event); adjustHeight(); return true; case Qt::Key_Delete: if (mpHost->mAutoClearCommandLineAfterSend) { mHistoryBuffer = -1; } else { mHistoryBuffer = 0; } if (mTabCompletionTyped.size() >= 1) { mTabCompletionTyped.chop(1); mAutoCompletionTyped.chop(1); mTabCompletionCount = -1; mAutoCompletionCount = -1; mLastCompletion = ""; } else { mTabCompletionCount = -1; mAutoCompletionCount = -1; mLastCompletion = ""; mTabCompletionTyped = ""; mAutoCompletionTyped = ""; mUserKeptOnTyping = false; mTabCompletionCount = -1; mAutoCompletionCount = -1; } QPlainTextEdit::event(event); adjustHeight(); return true; case Qt::Key_Return: if (ke->modifiers() & Qt::ControlModifier) { mpConsole->console2->mCursorY = mpConsole->buffer.size(); mpConsole->console2->hide(); mpConsole->buffer.mCursorY = mpConsole->buffer.size(); mpConsole->console->mCursorY = mpConsole->buffer.size(); mpConsole->console->mIsTailMode = true; mpConsole->console->updateScreenView(); mpConsole->console->forceUpdate(); ke->accept(); return true; } else if (ke->modifiers() & Qt::ShiftModifier) { textCursor().insertBlock(); ke->accept(); return true; } else { enterCommand(ke); mTabCompletionCount = -1; mAutoCompletionCount = -1; mLastCompletion = ""; mTabCompletionTyped = ""; mAutoCompletionTyped = ""; mUserKeptOnTyping = false; mTabCompletionCount = -1; mAutoCompletionCount = -1; if (mpHost->mAutoClearCommandLineAfterSend) { clear(); mHistoryBuffer = -1; } else { mHistoryBuffer = 0; } adjustHeight(); ke->accept(); return true; } case Qt::Key_Enter: enterCommand(ke); mTabCompletionCount = -1; mAutoCompletionCount = -1; mLastCompletion = ""; mTabCompletionTyped = ""; mAutoCompletionTyped = ""; mUserKeptOnTyping = false; mTabCompletionCount = -1; mAutoCompletionCount = -1; if (mpHost->mAutoClearCommandLineAfterSend) { clear(); mHistoryBuffer = -1; } else { mHistoryBuffer = 0; } adjustHeight(); ke->accept(); return true; case Qt::Key_Down: if (ke->modifiers() & Qt::ControlModifier) { moveCursor(QTextCursor::Down, QTextCursor::MoveAnchor); } else { historyDown(ke); } ke->accept(); return true; case Qt::Key_Up: if (ke->modifiers() & Qt::ControlModifier) { moveCursor(QTextCursor::Up, QTextCursor::MoveAnchor); } else { historyUp(ke); } ke->accept(); return true; case Qt::Key_Escape: selectAll(); mAutoCompletion = false; mTabCompletion = false; mTabCompletionTyped = ""; mAutoCompletionTyped = ""; mUserKeptOnTyping = false; mTabCompletionCount = -1; mAutoCompletionCount = -1; setPalette(mRegularPalette); if (mpHost->mAutoClearCommandLineAfterSend) { mHistoryBuffer = -1; } else { mHistoryBuffer = 0; } ke->accept(); return true; case Qt::Key_PageUp: mpConsole->scrollUp(mpHost->mScreenHeight); ke->accept(); return true; case Qt::Key_PageDown: mpConsole->scrollDown(mpHost->mScreenHeight); ke->accept(); return true; case Qt::Key_C: if (ke->modifiers() & Qt::ControlModifier) { if (mpConsole->console->mSelectedRegion != QRegion(0, 0, 0, 0)) { mpConsole->console->copySelectionToClipboard(); ke->accept(); return true; } } break; default: if (mpKeyUnit->processDataStream(ke->key(), (int)ke->modifiers())) { ke->accept(); return true; } else { QPlainTextEdit::event(event); adjustHeight(); if (mpHost->mAutoClearCommandLineAfterSend) { mHistoryBuffer = -1; } else { mHistoryBuffer = 0; } if (mTabCompletionOld != toPlainText()) { mUserKeptOnTyping = true; mAutoCompletionCount = -1; } else { mUserKeptOnTyping = false; } spellCheck(); return false; } } } return QPlainTextEdit::event(event); } void TCommandLine::focusInEvent(QFocusEvent* event) { textCursor().movePosition(QTextCursor::Start); textCursor().movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, mSelectedText.length()); mpConsole->console->forceUpdate(); mpConsole->console2->forceUpdate(); QPlainTextEdit::focusInEvent(event); } void TCommandLine::focusOutEvent(QFocusEvent* event) { if (textCursor().hasSelection()) { mSelectionStart = textCursor().selectionStart(); mSelectedText = textCursor().selectedText(); } else { mSelectionStart = 0; mSelectedText.clear(); } QPlainTextEdit::focusOutEvent(event); } void TCommandLine::adjustHeight() { int lines = document()->size().height(); int fontH = QFontMetrics(mpHost->mDisplayFont).height(); if (lines < 1) { lines = 1; } if (lines > 10) { lines = 10; } int _baseHeight = fontH * lines; int _height = _baseHeight + fontH; if (_height < mpHost->commandLineMinimumHeight) { _height = mpHost->commandLineMinimumHeight; } if (_height > height() || _height < height()) { mpConsole->layerCommandLine->setMinimumHeight(_height); mpConsole->layerCommandLine->setMaximumHeight(_height); int x = mpConsole->width(); int y = mpConsole->height(); QSize s = QSize(x, y); QResizeEvent event(s, s); QApplication::sendEvent(mpConsole, &event); } } void TCommandLine::spellCheck() { if (!mpHost->mEnableSpellCheck) { return; } QTextCursor oldCursor = textCursor(); QTextCharFormat f; auto cred = QColor(Qt::red); f.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); f.setUnderlineColor(cred); QTextCursor c = textCursor(); c.select(QTextCursor::WordUnderCursor); if (!Hunspell_spell(mpHunspell, c.selectedText().toLatin1().data())) { f.setFontUnderline(true); c.setCharFormat(f); } else { f.setFontUnderline(false); c.setCharFormat(f); } setTextCursor(c); f.setFontUnderline(false); oldCursor.setCharFormat(f); setTextCursor(oldCursor); } void TCommandLine::slot_popupMenu() { QAction* pA = (QAction*)sender(); if (!pA) { return; } QString t = pA->text(); QTextCursor c = cursorForPosition(mPopupPosition); c.select(QTextCursor::WordUnderCursor); c.removeSelectedText(); c.insertText(t); c.clearSelection(); Hunspell_free_list(mpHunspell, &mpHunspellSuggestionList, mHunspellSuggestionNumber); } void TCommandLine::mousePressEvent(QMouseEvent* event) { if (event->button() == Qt::RightButton) { QTextCursor c = cursorForPosition(event->pos()); c.select(QTextCursor::WordUnderCursor); if (!Hunspell_spell(mpHunspell, c.selectedText().toLatin1().data())) { char** sl; mHunspellSuggestionNumber = Hunspell_suggest(mpHunspell, &sl, c.selectedText().toLatin1().data()); auto popup = new QMenu(this); for (int i = 0; i < mHunspellSuggestionNumber; i++) { QAction* pA; pA = popup->addAction(sl[i]); connect(pA, SIGNAL(triggered()), this, SLOT(slot_popupMenu())); } mpHunspellSuggestionList = sl; mPopupPosition = event->pos(); popup->popup(event->globalPos()); } event->accept(); return; } QPlainTextEdit::mousePressEvent(event); } void TCommandLine::enterCommand(QKeyEvent* event) { QString _t = toPlainText(); mAutoCompletion = false; mTabCompletion = false; mTabCompletionCount = -1; mAutoCompletionCount = -1; mTabCompletionTyped = ""; QStringList _l = _t.split("\n"); for (int i = 0; i < _l.size(); i++) { mpHost->send(_l[i]); } if (!toPlainText().isEmpty()) { mHistoryBuffer = 0; setPalette(mRegularPalette); mHistoryList.removeAll(toPlainText()); mHistoryList.push_front(toPlainText()); } if (mpHost->mAutoClearCommandLineAfterSend) { clear(); } else { selectAll(); } adjustHeight(); } void TCommandLine::slot_sendCommand(const char* pS) { mpHost->sendRaw(QString(pS)); } // TAB completion mode gets turned on by the tab key. // This mode tries to find suitable matches for the letters being typed by the user // in the output buffer of data being sent by the MUD. This helps the user // to quickly type complicated names by only having to type the first letters. // You can cycle through all possible matches of the currently typed letters // with by repeatedly pressing tab or shift+tab. ESC-key brings you back into regular mode void TCommandLine::handleTabCompletion(bool direction) { if ((mTabCompletionCount < 0) || (mUserKeptOnTyping)) { mTabCompletionTyped = toPlainText(); if (mTabCompletionTyped.size() == 0) { return; } mUserKeptOnTyping = false; mTabCompletionCount = -1; } int amount = mpHost->mpConsole->buffer.size(); if (amount > 500) { amount = 500; } QStringList bufferList = mpHost->mpConsole->buffer.getEndLines(amount); QString buffer = bufferList.join(" "); buffer.replace(QChar(0x21af), "\n"); buffer.replace(QChar('\n'), " "); QStringList wordList = buffer.split(QRegExp(R"(\b)"), QString::SkipEmptyParts); if (direction) { mTabCompletionCount++; } else { mTabCompletionCount--; } if (wordList.size() > 0) { if (mTabCompletionTyped.endsWith(" ")) { return; } QString lastWord; QRegExp reg = QRegExp(R"(\b(\w+)$)"); int typePosition = reg.indexIn(mTabCompletionTyped); if (reg.captureCount() >= 1) { lastWord = reg.cap(1); } else { lastWord = ""; } QStringList filterList = wordList.filter(QRegExp("^" + lastWord + R"(\w+)", Qt::CaseInsensitive)); if (filterList.size() < 1) { return; } int offset = 0; for (;;) { QString tmp = filterList.back(); filterList.removeAll(tmp); filterList.insert(offset, tmp); ++offset; if (offset >= filterList.size()) { break; } } if (filterList.size() > 0) { if (mTabCompletionCount >= filterList.size()) { mTabCompletionCount = filterList.size() - 1; } if (mTabCompletionCount < 0) { mTabCompletionCount = 0; } QString proposal = filterList[mTabCompletionCount]; QString userWords = mTabCompletionTyped.left(typePosition); setPlainText(QString(userWords + proposal).trimmed()); moveCursor(QTextCursor::End, QTextCursor::MoveAnchor); mTabCompletionOld = toPlainText(); } } } // Hitting the cursor up key gets you in autocompletion mode. // in this mode mudlet tries to find matching substrings in the user's // command history. Repeated usage of the cursor up key cycles through // all possible matches. If the user keeps on typing more letters the // the set of possible matches is getting smaller. The ESC key brings you back to regular mode void TCommandLine::handleAutoCompletion() { QString neu = toPlainText(); neu.chop(textCursor().selectedText().size()); setPlainText(neu); mTabCompletionOld = neu; int oldLength = toPlainText().size(); if (mAutoCompletionCount >= mHistoryList.size()) { mAutoCompletionCount = mHistoryList.size() - 1; } if (mAutoCompletionCount < 0) { mAutoCompletionCount = 0; } for (int i = mAutoCompletionCount; i < mHistoryList.size(); i++) { QString h = mHistoryList[i].mid(0, neu.size()); if (neu == h) { mAutoCompletionCount = i; mLastCompletion = mHistoryList[i]; setPlainText(mHistoryList[i]); moveCursor(QTextCursor::Start); for (int k = 0; k < oldLength; k++) { moveCursor(QTextCursor::Right, QTextCursor::MoveAnchor); } moveCursor(QTextCursor::End, QTextCursor::KeepAnchor); return; } else { moveCursor(QTextCursor::End, QTextCursor::MoveAnchor); } } mAutoCompletionCount = -1; } // cursor down: cycles chronologically through the command history. void TCommandLine::historyDown(QKeyEvent* event) { if (mHistoryList.size() < 1) { return; } if ((textCursor().selectedText().size() == toPlainText().size()) || (toPlainText().size() == 0)) { mHistoryBuffer--; if (mHistoryBuffer >= mHistoryList.size()) { mHistoryBuffer = mHistoryList.size() - 1; } if (mHistoryBuffer < 0) { mHistoryBuffer = 0; } setPlainText(mHistoryList[mHistoryBuffer]); selectAll(); adjustHeight(); } else { mAutoCompletionCount--; handleAutoCompletion(); } } // cursor up: turns on autocompletion mode and cycles through all possible matches // In case nothing has been typed it cycles through the command history in // reverse order compared to cursor down. void TCommandLine::historyUp(QKeyEvent* event) { if (mHistoryList.size() < 1) { return; } if ((textCursor().selectedText().size() == toPlainText().size()) || (toPlainText().size() == 0)) { if (toPlainText().size() != 0) { mHistoryBuffer++; } if (mHistoryBuffer >= mHistoryList.size()) { mHistoryBuffer = mHistoryList.size() - 1; } if (mHistoryBuffer < 0) { mHistoryBuffer = 0; } setPlainText(mHistoryList[mHistoryBuffer]); selectAll(); adjustHeight(); } else { mAutoCompletionCount++; handleAutoCompletion(); } }