/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. #include "StdAfx.h" #include "ConsoleSCBMFC.h" #include "PropertiesDialog.h" #include "QtViewPaneManager.h" #include "Core/QtEditorApplication.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace MFC { static CPropertiesDialog* gPropertiesDlg = nullptr; static CString mfc_popup_helper(HWND hwnd, int x, int y); static CConsoleSCB* s_consoleSCB = nullptr; static QString RemoveColorCode(const QString& text, int& iColorCode) { QString cleanString; cleanString.reserve(text.size()); const int textSize = text.size(); for (int i = 0; i < textSize; ++i) { QChar c = text.at(i); bool isLast = i == textSize - 1; if (c == '$' && !isLast && text.at(i + 1).isDigit()) { if (iColorCode == 0) { iColorCode = text.at(i + 1).digitValue(); } ++i; continue; } if (c == '\r' || c == '\n') { ++i; continue; } cleanString.append(c); } return cleanString; } ConsoleLineEdit::ConsoleLineEdit(QWidget* parent) : QLineEdit(parent) , m_historyIndex(0) , m_bReusedHistory(false) { } void ConsoleLineEdit::mousePressEvent(QMouseEvent* ev) { if (ev->type() == QEvent::MouseButtonPress && ev->button() & Qt::RightButton) { Q_EMIT variableEditorRequested(); } QLineEdit::mousePressEvent(ev); } void ConsoleLineEdit::mouseDoubleClickEvent(QMouseEvent* ev) { Q_EMIT variableEditorRequested(); } bool ConsoleLineEdit::event(QEvent* ev) { // Tab key doesn't go to keyPressEvent(), must be processed here if (ev->type() != QEvent::KeyPress) { return QLineEdit::event(ev); } QKeyEvent* ke = static_cast(ev); if (ke->key() != Qt::Key_Tab) { return QLineEdit::event(ev); } QString inputStr = text(); QString newStr; QStringList tokens = inputStr.split(" "); inputStr = tokens.isEmpty() ? QString() : tokens.first(); IConsole* console = GetIEditor()->GetSystem()->GetIConsole(); const bool ctrlPressed = ke->modifiers() & Qt::ControlModifier; CString cstring = QtUtil::ToCString(inputStr); // TODO: Use QString once the backend stops using QString if (ctrlPressed) { newStr = QtUtil::ToString(console->AutoCompletePrev(cstring)); } else { newStr = QtUtil::ToString(console->ProcessCompletion(cstring)); newStr = QtUtil::ToString(console->AutoComplete(cstring)); if (newStr.isEmpty()) { newStr = QtUtil::ToQString(GetIEditor()->GetCommandManager()->AutoComplete(QtUtil::ToString(newStr))); } } if (!newStr.isEmpty()) { newStr += " "; setText(newStr); } deselect(); return true; } void ConsoleLineEdit::keyPressEvent(QKeyEvent* ev) { IConsole* console = GetIEditor()->GetSystem()->GetIConsole(); auto commandManager = GetIEditor()->GetCommandManager(); console->ResetAutoCompletion(); switch (ev->key()) { case Qt::Key_Enter: case Qt::Key_Return: { QString str = text().trimmed(); if (!str.isEmpty()) { if (commandManager->IsRegistered(QtUtil::ToCString(str))) { commandManager->Execute(QtUtil::ToString(str)); } else { CLogFile::WriteLine(QtUtil::ToCString(str)); GetIEditor()->GetSystem()->GetIConsole()->ExecuteString(QtUtil::ToCString(str)); } // If a history command was reused directly via up arrow enter, do not reset history index if (m_history.size() > 0 && m_historyIndex < m_history.size() && m_history[m_historyIndex] == str) { m_bReusedHistory = true; } else { m_historyIndex = m_history.size(); } // Do not add the same string if it is the top of the stack, but allow duplicate entries otherwise if (m_history.isEmpty() || m_history.back() != str) { m_history.push_back(str); if (!m_bReusedHistory) { m_historyIndex = m_history.size(); } } } else { m_historyIndex = m_history.size(); } setText(QString()); break; } case Qt::Key_AsciiTilde: // ~ case Qt::Key_Agrave: // ` // disable log. GetIEditor()->ShowConsole(false); setText(QString()); m_historyIndex = m_history.size(); break; case Qt::Key_Escape: setText(QString()); m_historyIndex = m_history.size(); break; case Qt::Key_Up: DisplayHistory(false /*bForward*/); break; case Qt::Key_Down: DisplayHistory(true /*bForward*/); break; default: QLineEdit::keyPressEvent(ev); } } void ConsoleLineEdit::DisplayHistory(bool bForward) { if (m_history.isEmpty()) { return; } // Immediately after reusing a history entry, ensure up arrow re-displays command just used if (!m_bReusedHistory || bForward) { m_historyIndex = static_cast(clamp_tpl(static_cast(m_historyIndex) + (bForward ? 1 : -1), 0, m_history.size() - 1)); } m_bReusedHistory = false; setText(m_history[m_historyIndex]); } ConsoleTextEdit::ConsoleTextEdit(QWidget* parent) : QTextEdit(parent) { } Lines CConsoleSCB::s_pendingLines; CConsoleSCB::CConsoleSCB(QWidget* parent) : QWidget(parent) , ui(new Ui::ConsoleMFC()) , m_richEditTextLength(0) , m_backgroundTheme(gSettings.consoleBackgroundColorTheme) { m_lines = s_pendingLines; s_pendingLines.clear(); s_consoleSCB = this; ui->setupUi(this); setMinimumHeight(120); // Setup the color table for the default (light) theme m_colorTable << QColor(0, 0, 0) << QColor(0, 0, 0) << QColor(0, 0, 200) // blue << QColor(0, 200, 0) // green << QColor(200, 0, 0) // red << QColor(0, 200, 200) // cyan << QColor(128, 112, 0) // yellow << QColor(200, 0, 200) // red+blue << QColor(0x000080ff) << QColor(0x008f8f8f); OnStyleSettingsChanged(); connect(ui->button, &QPushButton::clicked, this, &CConsoleSCB::showVariableEditor); connect(ui->lineEdit, &MFC::ConsoleLineEdit::variableEditorRequested, this, &MFC::CConsoleSCB::showVariableEditor); connect(Editor::EditorQtApplication::instance(), &Editor::EditorQtApplication::skinChanged, this, &MFC::CConsoleSCB::OnStyleSettingsChanged); if (GetIEditor()->IsInConsolewMode()) { // Attach / register edit box //CLogFile::AttachEditBox(m_edit.GetSafeHwnd()); // FIXME } } CConsoleSCB::~CConsoleSCB() { s_consoleSCB = nullptr; delete gPropertiesDlg; gPropertiesDlg = nullptr; CLogFile::AttachEditBox(nullptr); } void CConsoleSCB::RegisterViewClass() { QtViewOptions opts; opts.preferedDockingArea = Qt::BottomDockWidgetArea; opts.isDeletable = false; opts.isStandard = true; opts.showInMenu = true; opts.builtInActionId = ID_VIEW_CONSOLEWINDOW; opts.sendViewPaneNameBackToAmazonAnalyticsServers = true; RegisterQtViewPane(GetIEditor(), LyViewPane::Console, LyViewPane::CategoryTools, opts); } void CConsoleSCB::OnStyleSettingsChanged() { ui->button->setIcon(QIcon(QString(":/controls/img/cvar_dark.bmp"))); // Set the debug/warning text colors appropriately for the background theme // (e.g. not have black text on black background) QColor textColor = Qt::black; m_backgroundTheme = gSettings.consoleBackgroundColorTheme; if (m_backgroundTheme == SEditorSettings::ConsoleColorTheme::Dark) { textColor = Qt::white; } m_colorTable[0] = textColor; m_colorTable[1] = textColor; QColor bgColor; if (!GetIEditor()->IsInConsolewMode() && CConsoleSCB::GetCreatedInstance() && m_backgroundTheme == SEditorSettings::ConsoleColorTheme::Dark) { bgColor = Qt::black; } else { bgColor = Qt::white; } ui->textEdit->setStyleSheet(QString("QTextEdit{ background: %1 }").arg(bgColor.name(QColor::HexRgb))); // Clear out the console text when we change our background color since // some of the previous text colors may not be appropriate for the // new background color ui->textEdit->clear(); } void CConsoleSCB::showVariableEditor() { const QPoint cursorPos = QCursor::pos(); CString str = mfc_popup_helper(0, cursorPos.x(), cursorPos.y()); if (!str.IsEmpty()) { ui->lineEdit->setText(QtUtil::ToQString(str)); } } void CConsoleSCB::SetInputFocus() { ui->lineEdit->setFocus(); ui->lineEdit->setText(QString()); } void CConsoleSCB::AddToConsole(const QString& text, bool bNewLine) { m_lines.push_back({ text, bNewLine }); FlushText(); } void CConsoleSCB::FlushText() { if (m_lines.empty()) { return; } // Store our current cursor in case we need to restore it, and check if // the user has scrolled the text edit away from the bottom const QTextCursor oldCursor = ui->textEdit->textCursor(); QScrollBar* scrollBar = ui->textEdit->verticalScrollBar(); const int oldScrollValue = scrollBar->value(); bool scrolledOffBottom = oldScrollValue != scrollBar->maximum(); ui->textEdit->moveCursor(QTextCursor::End); QTextCursor textCursor = ui->textEdit->textCursor(); while (!m_lines.empty()) { ConsoleLine line = m_lines.front(); m_lines.pop_front(); int iColor = 0; QString text = MFC::RemoveColorCode(line.text, iColor); if (iColor < 0 || iColor >= m_colorTable.size()) { iColor = 0; } if (line.newLine) { text = QtUtil::trimRight(text); text = "\r\n" + text; } QTextCharFormat format; const QColor color(m_colorTable[iColor]); format.setForeground(color); if (iColor != 0) { format.setFontWeight(QFont::Bold); } textCursor.setCharFormat(format); textCursor.insertText(text); } // If the user has selected some text in the text edit area or has scrolled // away from the bottom, then restore the previous cursor and keep the scroll // bar in the same location if (oldCursor.hasSelection() || scrolledOffBottom) { ui->textEdit->setTextCursor(oldCursor); scrollBar->setValue(oldScrollValue); } // Otherwise scroll to the bottom so the latest text can be seen else { scrollBar->setValue(scrollBar->maximum()); } } QSize CConsoleSCB::minimumSizeHint() const { return QSize(-1, -1); } QSize CConsoleSCB::sizeHint() const { return QSize(100, 100); } /** static */ void CConsoleSCB::AddToPendingLines(const QString& text, bool bNewLine) { s_pendingLines.push_back({ text, bNewLine }); } static CVarBlock* VarBlockFromConsoleVars() { IConsole* console = GetIEditor()->GetSystem()->GetIConsole(); std::vector cmds; cmds.resize(console->GetNumVars()); size_t cmdCount = console->GetSortedVars(&cmds[0], cmds.size()); CVarBlock* vb = new CVarBlock; IVariable* pVariable = 0; for (int i = 0; i < cmdCount; i++) { ICVar* pCVar = console->GetCVar(cmds[i]); if (!pCVar) { continue; } int varType = pCVar->GetType(); switch (varType) { case CVAR_INT: pVariable = new CVariable(); pVariable->Set(pCVar->GetIVal()); break; case CVAR_FLOAT: pVariable = new CVariable(); pVariable->Set(pCVar->GetFVal()); break; case CVAR_STRING: pVariable = new CVariable(); pVariable->Set(pCVar->GetString()); break; default: assert(0); } pVariable->SetDescription(pCVar->GetHelp()); pVariable->SetName(cmds[i]); if (pVariable) { vb->AddVariable(pVariable); } } return vb; } static void OnConsoleVariableUpdated(IVariable* pVar) { if (!pVar) { return; } CString varName = pVar->GetName(); ICVar* pCVar = GetIEditor()->GetSystem()->GetIConsole()->GetCVar(varName); if (!pCVar) { return; } if (pVar->GetType() == IVariable::INT) { int val; pVar->Get(val); pCVar->Set(val); } else if (pVar->GetType() == IVariable::FLOAT) { float val; pVar->Get(val); pCVar->Set(val); } else if (pVar->GetType() == IVariable::STRING) { CString val; pVar->Get(val); pCVar->Set(val); } } static CString mfc_popup_helper(HWND hwnd, int x, int y) { IConsole* console = GetIEditor()->GetSystem()->GetIConsole(); TSmartPtr vb = VarBlockFromConsoleVars(); XmlNodeRef node; if (!gPropertiesDlg) { gPropertiesDlg = new CPropertiesDialog("Console Variables", node, AfxGetMainWnd(), true); } if (!gPropertiesDlg->m_hWnd) { gPropertiesDlg->Create(CPropertiesDialog::IDD, AfxGetMainWnd()); gPropertiesDlg->SetUpdateCallback(functor(OnConsoleVariableUpdated)); } gPropertiesDlg->ShowWindow(SW_SHOW); gPropertiesDlg->BringWindowToTop(); gPropertiesDlg->GetPropertyCtrl()->AddVarBlock(vb); return ""; } CConsoleSCB* CConsoleSCB::GetCreatedInstance() { return s_consoleSCB; } } // namespace MFC #include