diff --git a/qrenderdoc/Widgets/FindReplace.cpp b/qrenderdoc/Widgets/FindReplace.cpp new file mode 100644 index 000000000..2ec3affbf --- /dev/null +++ b/qrenderdoc/Widgets/FindReplace.cpp @@ -0,0 +1,194 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2017 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "FindReplace.h" +#include +#include +#include "ui_FindReplace.h" + +FindReplace::FindReplace(QWidget *parent) : QFrame(parent), ui(new Ui::FindReplace) +{ + ui->setupUi(this); + + ui->searchContext->setCurrentIndex(FindReplace::File); + + // default to just find + setReplaceMode(false); + setDirection(FindReplace::Down); + + QObject::connect(ui->findText->lineEdit(), &QLineEdit::returnPressed, this, + &FindReplace::on_find_clicked); + QObject::connect(ui->replaceText->lineEdit(), &QLineEdit::returnPressed, this, + &FindReplace::on_replace_clicked); +} + +FindReplace::~FindReplace() +{ + delete ui; +} + +bool FindReplace::replaceMode() +{ + return ui->replaceMode->isChecked(); +} + +FindReplace::SearchContext FindReplace::context() +{ + return (FindReplace::SearchContext)ui->searchContext->currentIndex(); +} + +FindReplace::SearchDirection FindReplace::direction() +{ + return ui->searchUp->isChecked() ? FindReplace::Up : FindReplace::Down; +} + +bool FindReplace::matchCase() +{ + return ui->matchCase->isChecked(); +} + +bool FindReplace::matchWord() +{ + return ui->matchWord->isChecked(); +} + +bool FindReplace::regexp() +{ + return ui->regexp->isChecked(); +} + +QString FindReplace::findText() +{ + return ui->findText->currentText(); +} + +QString FindReplace::replaceText() +{ + return ui->replaceText->currentText(); +} + +void FindReplace::allowUserModeChange(bool allow) +{ + ui->modeChangeFrame->setVisible(allow); +} + +void FindReplace::setReplaceMode(bool replacing) +{ + ui->replaceLabel->setVisible(replacing); + ui->replaceText->setVisible(replacing); + ui->replace->setVisible(replacing); + ui->replaceAll->setVisible(replacing); + + ui->findMode->setChecked(!replacing); + ui->replaceMode->setChecked(replacing); + + setWindowTitle(replacing ? tr("Find && Replace") : tr("Find")); +} + +void FindReplace::setDirection(SearchDirection dir) +{ + if(dir == FindReplace::Up) + ui->searchUp->setChecked(true); + else + ui->searchDown->setChecked(true); +} + +void FindReplace::takeFocus() +{ + ui->findText->setFocus(); +} + +void FindReplace::keyPressEvent(QKeyEvent *event) +{ + if(event->key() == Qt::Key_F3) + { + SearchDirection dir = direction(); + + if(event->modifiers() & Qt::ShiftModifier) + ui->searchUp->setChecked(true); + else + ui->searchDown->setChecked(true); + + emit performFind(); + + if(dir == FindReplace::Up) + ui->searchUp->setChecked(true); + else + ui->searchDown->setChecked(true); + } +} + +void FindReplace::addHistory(QComboBox *combo) +{ + QString text = combo->currentText(); + + for(int i = 0; i < combo->count(); i++) + { + if(combo->itemText(i) == text) + { + // remove the item so we can bump it up to the top of the list + combo->removeItem(i); + break; + } + } + + combo->insertItem(0, text); + combo->setCurrentText(text); +} + +void FindReplace::on_find_clicked() +{ + addHistory(ui->findText); + emit performFind(); +} + +void FindReplace::on_findAll_clicked() +{ + addHistory(ui->findText); + emit performFindAll(); +} + +void FindReplace::on_replace_clicked() +{ + addHistory(ui->findText); + addHistory(ui->replaceText); + emit performReplace(); +} + +void FindReplace::on_replaceAll_clicked() +{ + addHistory(ui->findText); + addHistory(ui->replaceText); + emit performReplaceAll(); +} + +void FindReplace::on_findMode_clicked() +{ + setReplaceMode(false); +} + +void FindReplace::on_replaceMode_clicked() +{ + setReplaceMode(true); +} \ No newline at end of file diff --git a/qrenderdoc/Widgets/FindReplace.h b/qrenderdoc/Widgets/FindReplace.h new file mode 100644 index 000000000..8e6fdf7f1 --- /dev/null +++ b/qrenderdoc/Widgets/FindReplace.h @@ -0,0 +1,94 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2017 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +#include + +namespace Ui +{ +class FindReplace; +} + +class QComboBox; + +class FindReplace : public QFrame +{ + Q_OBJECT + +public: + explicit FindReplace(QWidget *parent = 0); + ~FindReplace(); + + enum SearchContext + { + File, + AllFiles, + }; + + enum SearchDirection + { + Up, + Down, + }; + + bool replaceMode(); + + SearchContext context(); + SearchDirection direction(); + bool matchCase(); + bool matchWord(); + bool regexp(); + + QString findText(); + QString replaceText(); + +public slots: + void allowUserModeChange(bool allow); + void setReplaceMode(bool replacing); + void setDirection(SearchDirection dir); + void takeFocus(); + +signals: + void performFind(); + void performFindAll(); + void performReplace(); + void performReplaceAll(); + +private slots: + // automatic slots + void on_find_clicked(); + void on_findAll_clicked(); + void on_replace_clicked(); + void on_replaceAll_clicked(); + void on_findMode_clicked(); + void on_replaceMode_clicked(); + +private: + void keyPressEvent(QKeyEvent *event) override; + + Ui::FindReplace *ui; + + void addHistory(QComboBox *combo); +}; diff --git a/qrenderdoc/Widgets/FindReplace.ui b/qrenderdoc/Widgets/FindReplace.ui new file mode 100644 index 000000000..035df53ea --- /dev/null +++ b/qrenderdoc/Widgets/FindReplace.ui @@ -0,0 +1,335 @@ + + + FindReplace + + + + 0 + 0 + 321 + 352 + + + + + + + Context + + + + + + Search in: + + + + + + + Match case + + + + + + + QComboBox::NoInsert + + + QComboBox::AdjustToContents + + + + Current File + + + + + All Files + + + + + + + + Use Regular Expressions + + + + + + + Match whole word + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Replace All + + + + + + + Replace + + + + + + + Find Next + + + + + + + Find All + + + + + + + + + + QFrame::Panel + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Find + + + true + + + true + + + true + + + + + + + Replace + + + true + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 40 + 20 + + + + + + + + Direction + + + + + + Up + + + + + + + Down + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 1 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Find + + + + + + + Replace with + + + + + + + + 1 + 0 + + + + true + + + QComboBox::InsertAtTop + + + + + + + + 1 + 0 + + + + true + + + QComboBox::InsertAtTop + + + + + + + + + + + diff --git a/qrenderdoc/Windows/ShaderViewer.cpp b/qrenderdoc/Windows/ShaderViewer.cpp index 5d1872eea..6b9ee0c55 100644 --- a/qrenderdoc/Windows/ShaderViewer.cpp +++ b/qrenderdoc/Windows/ShaderViewer.cpp @@ -29,7 +29,9 @@ #include "3rdparty/scintilla/include/SciLexer.h" #include "3rdparty/scintilla/include/qt/ScintillaEdit.h" #include "3rdparty/toolwindowmanager/ToolWindowManager.h" +#include "3rdparty/toolwindowmanager/ToolWindowManagerArea.h" #include "Code/ScintillaSyntax.h" +#include "Widgets/FindReplace.h" #include "Windows/PipelineState/PipelineStateViewer.h" #include "ui_ShaderViewer.h" @@ -57,9 +59,34 @@ ShaderViewer::ShaderViewer(CaptureContext &ctx, QWidget *parent) { ui->setupUi(this); + // we create this up front so its state stays persistent as much as possible. + m_FindReplace = new FindReplace(this); + + m_FindResults = MakeEditor("findresults", "", SCLEX_NULL); + m_FindResults->setReadOnly(true); + m_FindResults->setWindowTitle("Find Results"); + + // remove margins + m_FindResults->setMarginWidthN(0, 0); + m_FindResults->setMarginWidthN(1, 0); + m_FindResults->setMarginWidthN(2, 0); + + QObject::connect(m_FindReplace, &FindReplace::performFind, this, &ShaderViewer::performFind); + QObject::connect(m_FindReplace, &FindReplace::performFindAll, this, &ShaderViewer::performFindAll); + QObject::connect(m_FindReplace, &FindReplace::performReplace, this, &ShaderViewer::performReplace); + QObject::connect(m_FindReplace, &FindReplace::performReplaceAll, this, + &ShaderViewer::performReplaceAll); + + ui->docking->addToolWindow(m_FindReplace, ToolWindowManager::NoArea); + ui->docking->setToolWindowProperties(m_FindReplace, ToolWindowManager::HideOnClose); + + ui->docking->addToolWindow(m_FindResults, ToolWindowManager::NoArea); + ui->docking->setToolWindowProperties(m_FindResults, ToolWindowManager::HideOnClose); + { m_DisassemblyView = - MakeEditor("scintillaDisassem", "", m_Ctx.APIProps().pipelineType == eGraphicsAPI_Vulkan); + MakeEditor("scintillaDisassem", "", + m_Ctx.APIProps().pipelineType == eGraphicsAPI_Vulkan ? SCLEX_GLSL : SCLEX_HLSL); m_DisassemblyView->setReadOnly(true); m_DisassemblyView->setWindowTitle(tr("Disassembly")); @@ -89,10 +116,11 @@ ShaderViewer::ShaderViewer(CaptureContext &ctx, QWidget *parent) m_Scintillas.push_back(m_DisassemblyView); ui->docking->addToolWindow(m_DisassemblyView, ToolWindowManager::EmptySpace); - ui->docking->setToolWindowProperties(m_DisassemblyView, ToolWindowManager::HideCloseButton); + ui->docking->setToolWindowProperties( + m_DisassemblyView, + ToolWindowManager::HideCloseButton | ToolWindowManager::DisallowFloatWindow); } - ui->docking->setAllowFloatingWindow(false); ui->docking->setRubberBandLineWidth(50); QVBoxLayout *layout = new QVBoxLayout(this); @@ -139,8 +167,13 @@ void ShaderViewer::editShader(bool customShader, const QString &entryPoint, cons ScintillaEdit *scintilla = AddFileScintilla(name, text); scintilla->setReadOnly(false); - QObject::connect(m_DisassemblyView, &ScintillaEdit::keyPressed, this, - &ShaderViewer::editable_keyPressed); + QObject::connect(scintilla, &ScintillaEdit::keyPressed, this, &ShaderViewer::editable_keyPressed); + + QObject::connect(scintilla, &ScintillaEdit::modified, [this](int type, int, int, int, + const QByteArray &, int, int, int) { + if(type & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT | SC_MOD_BEFOREINSERT | SC_MOD_BEFOREDELETE)) + m_FindState = FindState(); + }); // TODO - shortcuts QObject::connect(new QShortcut(QKeySequence(Qt::Key_S | Qt::ControlModifier), scintilla), @@ -164,7 +197,7 @@ void ShaderViewer::editShader(bool customShader, const QString &entryPoint, cons if(files.count() > 2) addFileList(); - m_Errors = MakeEditor("errors", "", false); + m_Errors = MakeEditor("errors", "", SCLEX_NULL); m_Errors->setReadOnly(true); m_Errors->setWindowTitle("Errors"); @@ -178,7 +211,8 @@ void ShaderViewer::editShader(bool customShader, const QString &entryPoint, cons ui->docking->addToolWindow( m_Errors, ToolWindowManager::AreaReference(ToolWindowManager::BottomOf, ui->docking->areaOf(m_Scintillas.front()), 0.2f)); - ui->docking->setToolWindowProperties(m_Errors, ToolWindowManager::HideCloseButton); + ui->docking->setToolWindowProperties( + m_Errors, ToolWindowManager::HideCloseButton | ToolWindowManager::DisallowFloatWindow); } void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderReflection *shader, @@ -190,6 +224,9 @@ void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderR m_Trace = trace; m_Stage = stage; + // no replacing allowed, stay in find mode + m_FindReplace->allowUserModeChange(false); + if(!shader || !bind) m_Trace = NULL; @@ -272,19 +309,22 @@ void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderR ui->docking->addToolWindow( ui->watch, ToolWindowManager::AreaReference(ToolWindowManager::BottomOf, ui->docking->areaOf(m_DisassemblyView), 0.25f)); - ui->docking->setToolWindowProperties(ui->watch, ToolWindowManager::HideCloseButton); + ui->docking->setToolWindowProperties( + ui->watch, ToolWindowManager::HideCloseButton | ToolWindowManager::DisallowFloatWindow); ui->variables->setWindowTitle(tr("Variables")); ui->docking->addToolWindow( ui->variables, ToolWindowManager::AreaReference(ToolWindowManager::AddTo, ui->docking->areaOf(ui->watch))); - ui->docking->setToolWindowProperties(ui->variables, ToolWindowManager::HideCloseButton); + ui->docking->setToolWindowProperties( + ui->variables, ToolWindowManager::HideCloseButton | ToolWindowManager::DisallowFloatWindow); ui->constants->setWindowTitle(tr("Constants && Resources")); ui->docking->addToolWindow( ui->constants, ToolWindowManager::AreaReference(ToolWindowManager::LeftOf, ui->docking->areaOf(ui->variables), 0.5f)); - ui->docking->setToolWindowProperties(ui->constants, ToolWindowManager::HideCloseButton); + ui->docking->setToolWindowProperties( + ui->constants, ToolWindowManager::HideCloseButton | ToolWindowManager::DisallowFloatWindow); m_DisassemblyView->setMarginWidthN(1, 20); @@ -390,13 +430,15 @@ void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderR ui->docking->addToolWindow(ui->inputSig, ToolWindowManager::AreaReference( ToolWindowManager::BottomOf, ui->docking->areaOf(m_DisassemblyView), 0.2f)); - ui->docking->setToolWindowProperties(ui->inputSig, ToolWindowManager::HideCloseButton); + ui->docking->setToolWindowProperties( + ui->inputSig, ToolWindowManager::HideCloseButton | ToolWindowManager::DisallowFloatWindow); ui->outputSig->setWindowTitle(tr("Output Signature")); ui->docking->addToolWindow( ui->outputSig, ToolWindowManager::AreaReference(ToolWindowManager::RightOf, ui->docking->areaOf(ui->inputSig), 0.5f)); - ui->docking->setToolWindowProperties(ui->outputSig, ToolWindowManager::HideCloseButton); + ui->docking->setToolWindowProperties( + ui->outputSig, ToolWindowManager::HideCloseButton | ToolWindowManager::DisallowFloatWindow); } } @@ -426,7 +468,9 @@ void ShaderViewer::OnEventChanged(uint32_t eventID) ScintillaEdit *ShaderViewer::AddFileScintilla(const QString &name, const QString &text) { - ScintillaEdit *scintilla = MakeEditor("scintilla" + name, text, true); + ScintillaEdit *scintilla = + MakeEditor("scintilla" + name, text, + m_Ctx.APIProps().localRenderer == eGraphicsAPI_OpenGL ? SCLEX_GLSL : SCLEX_HLSL); scintilla->setReadOnly(true); scintilla->setWindowTitle(name); ((QWidget *)scintilla)->setProperty("name", name); @@ -440,14 +484,15 @@ ScintillaEdit *ShaderViewer::AddFileScintilla(const QString &name, const QString ui->docking->areaOf(m_Scintillas[0])); ui->docking->addToolWindow(scintilla, ref); - ui->docking->setToolWindowProperties(scintilla, ToolWindowManager::HideCloseButton); + ui->docking->setToolWindowProperties( + scintilla, ToolWindowManager::HideCloseButton | ToolWindowManager::DisallowFloatWindow); m_Scintillas.push_back(scintilla); return scintilla; } -ScintillaEdit *ShaderViewer::MakeEditor(const QString &name, const QString &text, bool src) +ScintillaEdit *ShaderViewer::MakeEditor(const QString &name, const QString &text, int lang) { ScintillaEdit *ret = new ScintillaEdit(this); @@ -468,11 +513,16 @@ ScintillaEdit *ShaderViewer::MakeEditor(const QString &name, const QString &text ret->setObjectName(name); // C# DarkGreen - ret->indicSetFore(4, SCINTILLA_COLOUR(0, 100, 0)); - ret->indicSetStyle(4, INDIC_ROUNDBOX); + ret->indicSetFore(INDICATOR_REGHIGHLIGHT, SCINTILLA_COLOUR(0, 100, 0)); + ret->indicSetStyle(INDICATOR_REGHIGHLIGHT, INDIC_ROUNDBOX); - ConfigureSyntax(ret, - m_Ctx.APIProps().localRenderer == eGraphicsAPI_OpenGL ? SCLEX_GLSL : SCLEX_HLSL); + // set up find result highlight style + ret->indicSetFore(INDICATOR_FINDRESULT, SCINTILLA_COLOUR(200, 200, 127)); + ret->indicSetStyle(INDICATOR_FINDRESULT, INDIC_FULLBOX); + ret->indicSetAlpha(INDICATOR_FINDRESULT, 50); + ret->indicSetOutlineAlpha(INDICATOR_FINDRESULT, 80); + + ConfigureSyntax(ret, lang); ret->setTabWidth(4); @@ -488,12 +538,25 @@ ScintillaEdit *ShaderViewer::MakeEditor(const QString &name, const QString &text void ShaderViewer::readonly_keyPressed(QKeyEvent *event) { - // TODO find + if(event->key() == Qt::Key_F && (event->modifiers() & Qt::ControlModifier)) + { + m_FindReplace->setReplaceMode(false); + on_findReplace_clicked(); + } + + if(event->key() == Qt::Key_F3) + { + find((event->modifiers() & Qt::ShiftModifier) == 0); + } } void ShaderViewer::editable_keyPressed(QKeyEvent *event) { - // TODO replace + if(event->key() == Qt::Key_H && (event->modifiers() & Qt::ControlModifier)) + { + m_FindReplace->setReplaceMode(true); + on_findReplace_clicked(); + } } void ShaderViewer::disassembly_buttonReleased(QMouseEvent *event) @@ -697,7 +760,8 @@ void ShaderViewer::addFileList() ui->docking->addToolWindow( list, ToolWindowManager::AreaReference(ToolWindowManager::LeftOf, ui->docking->areaOf(m_Scintillas.front()), 0.2f)); - ui->docking->setToolWindowProperties(list, ToolWindowManager::HideCloseButton); + ui->docking->setToolWindowProperties( + list, ToolWindowManager::HideCloseButton | ToolWindowManager::DisallowFloatWindow); } void ShaderViewer::updateDebugging() @@ -733,12 +797,7 @@ void ShaderViewer::updateDebugging() int pos = m_DisassemblyView->positionFromLine(i); m_DisassemblyView->setSelection(pos, pos); - int firstLine = m_DisassemblyView->firstVisibleLine(); - int linesVisible = m_DisassemblyView->linesOnScreen(); - - if(m_DisassemblyView->isVisible() && (i < firstLine || i > (firstLine + linesVisible))) - m_DisassemblyView->scrollCaret(); - + ensureLineScrolled(m_DisassemblyView, i); break; } } @@ -907,6 +966,15 @@ void ShaderViewer::updateDebugging() // TODO watch registers } +void ShaderViewer::ensureLineScrolled(ScintillaEdit *s, int line) +{ + int firstLine = s->firstVisibleLine(); + int linesVisible = s->linesOnScreen(); + + if(s->isVisible() && (line < firstLine || line > (firstLine + linesVisible))) + s->scrollCaret(); +} + int ShaderViewer::currentStep() { return m_CurrentStep; @@ -990,6 +1058,18 @@ void ShaderViewer::showErrors(const QString &errors) void ShaderViewer::on_findReplace_clicked() { + if(m_FindReplace->isVisible()) + { + ToolWindowManager::raiseToolWindow(m_FindReplace); + } + else + { + ui->docking->moveToolWindow( + m_FindReplace, ToolWindowManager::AreaReference(ToolWindowManager::NewFloatingArea)); + ui->docking->setToolWindowProperties(m_FindReplace, ToolWindowManager::HideOnClose); + } + ui->docking->areaOf(m_FindReplace)->parentWidget()->activateWindow(); + m_FindReplace->takeFocus(); } void ShaderViewer::on_save_clicked() @@ -1027,3 +1107,381 @@ void ShaderViewer::on_floatView_clicked() updateDebugging(); } + +ScintillaEdit *ShaderViewer::currentScintilla() +{ + ScintillaEdit *cur = qobject_cast(QApplication::focusWidget()); + + if(cur == NULL) + { + for(ScintillaEdit *s : m_Scintillas) + { + if(s->isVisible()) + { + cur = s; + break; + } + } + } + + return cur; +} + +ScintillaEdit *ShaderViewer::nextScintilla(ScintillaEdit *cur) +{ + for(int i = 0; i < m_Scintillas.count(); i++) + { + if(m_Scintillas[i] == cur) + { + if(i + 1 < m_Scintillas.count()) + return m_Scintillas[i + 1]; + + return m_Scintillas[0]; + } + } + + if(!m_Scintillas.isEmpty()) + return m_Scintillas[0]; + + return NULL; +} + +void ShaderViewer::find(bool down) +{ + ScintillaEdit *cur = currentScintilla(); + + if(!cur) + return; + + QString find = m_FindReplace->findText(); + + sptr_t flags = 0; + + if(m_FindReplace->matchCase()) + flags |= SCFIND_MATCHCASE; + if(m_FindReplace->matchWord()) + flags |= SCFIND_WHOLEWORD; + if(m_FindReplace->regexp()) + flags |= SCFIND_REGEXP | SCFIND_POSIX; + + FindReplace::SearchContext context = m_FindReplace->context(); + + QString findHash = QString("%1%2%3").arg(find).arg(flags).arg((int)context); + + if(findHash != m_FindState.hash) + { + m_FindState.hash = findHash; + m_FindState.start = 0; + m_FindState.end = cur->length(); + m_FindState.offset = cur->currentPos(); + } + + int start = m_FindState.start + m_FindState.offset; + int end = m_FindState.end; + + if(!down) + end = m_FindState.start; + + QPair result = cur->findText(flags, find.toUtf8().data(), start, end); + + m_FindState.prevResult = result; + + if(result.first == -1) + { + sptr_t maxOffset = down ? 0 : m_FindState.end; + + // if we're at offset 0 searching down, there are no results. Same for offset max and searching + // up + if(m_FindState.offset == maxOffset) + return; + + // otherwise, we can wrap the search around + + if(context == FindReplace::AllFiles) + { + cur = nextScintilla(cur); + ToolWindowManager::raiseToolWindow(cur); + cur->activateWindow(); + cur->QWidget::setFocus(); + } + + m_FindState.offset = maxOffset; + + start = m_FindState.start + m_FindState.offset; + end = m_FindState.end; + + if(!down) + end = m_FindState.start; + + result = cur->findText(flags, find.toUtf8().data(), start, end); + + m_FindState.prevResult = result; + + if(result.first == -1) + return; + } + + cur->setSelection(result.first, result.second); + + ensureLineScrolled(cur, cur->lineFromPosition(result.first)); + + if(down) + m_FindState.offset = result.second - m_FindState.start; + else + m_FindState.offset = result.first - m_FindState.start; +} + +void ShaderViewer::performFind() +{ + find(m_FindReplace->direction() == FindReplace::Down); +} + +void ShaderViewer::performFindAll() +{ + ScintillaEdit *cur = currentScintilla(); + + if(!cur) + return; + + QString find = m_FindReplace->findText(); + + sptr_t flags = 0; + + QString results = tr("Find all \"%1\"").arg(find); + + if(m_FindReplace->matchCase()) + { + flags |= SCFIND_MATCHCASE; + results += tr(", Match case"); + } + + if(m_FindReplace->matchWord()) + { + flags |= SCFIND_WHOLEWORD; + results += tr(", Match whole word"); + } + + if(m_FindReplace->regexp()) + { + flags |= SCFIND_REGEXP | SCFIND_POSIX; + results += tr(", with Regular Expressions"); + } + + FindReplace::SearchContext context = m_FindReplace->context(); + + if(context == FindReplace::File) + results += tr(", in current file\n"); + else + results += tr(", in all files\n"); + + // trash the find state for any incremental finds + m_FindState = FindState(); + + QList scintillas = m_Scintillas; + + if(context == FindReplace::File) + scintillas = {cur}; + + QList> resultList; + + QByteArray findUtf8 = find.toUtf8(); + + for(ScintillaEdit *s : scintillas) + { + sptr_t start = 0; + sptr_t end = s->length(); + + s->setIndicatorCurrent(INDICATOR_FINDRESULT); + s->indicatorClearRange(start, end); + + if(findUtf8.isEmpty()) + continue; + + QPair result; + + do + { + result = s->findText(flags, findUtf8.data(), start, end); + + if(result.first >= 0) + { + int line = s->lineFromPosition(result.first); + sptr_t lineStart = s->positionFromLine(line); + sptr_t lineEnd = s->lineEndPosition(line); + + s->indicatorFillRange(result.first, result.second - result.first); + + QString lineText = QString::fromUtf8(s->textRange(lineStart, lineEnd)); + + results += QString(" %1(%2): ").arg(s->windowTitle()).arg(line, 4); + int startPos = results.length(); + + results += lineText; + results += "\n"; + + resultList.push_back( + qMakePair(result.first - lineStart + startPos, result.second - lineStart + startPos)); + } + + start = result.second; + + } while(result.first >= 0); + } + + if(findUtf8.isEmpty()) + return; + + results += tr("Matching lines: %1").arg(resultList.count()); + + m_FindResults->setReadOnly(false); + m_FindResults->setText(results.toUtf8().data()); + + m_FindResults->setIndicatorCurrent(INDICATOR_FINDRESULT); + + for(QPair r : resultList) + m_FindResults->indicatorFillRange(r.first, r.second - r.first); + + m_FindResults->setReadOnly(true); + + if(m_FindResults->isVisible()) + { + ToolWindowManager::raiseToolWindow(m_FindResults); + } + else + { + ui->docking->moveToolWindow(m_FindResults, + ToolWindowManager::AreaReference(ToolWindowManager::BottomOf, + ui->docking->areaOf(cur), 0.2f)); + ui->docking->setToolWindowProperties(m_FindResults, ToolWindowManager::HideOnClose); + } +} + +void ShaderViewer::performReplace() +{ + ScintillaEdit *cur = currentScintilla(); + + if(!cur) + return; + + QString find = m_FindReplace->findText(); + + if(find.isEmpty()) + return; + + sptr_t flags = 0; + + if(m_FindReplace->matchCase()) + flags |= SCFIND_MATCHCASE; + if(m_FindReplace->matchWord()) + flags |= SCFIND_WHOLEWORD; + if(m_FindReplace->regexp()) + flags |= SCFIND_REGEXP | SCFIND_POSIX; + + FindReplace::SearchContext context = m_FindReplace->context(); + + QString findHash = QString("%1%2%3").arg(find).arg(flags).arg((int)context); + + // if we didn't have a valid previous find, just do a find and bail + if(findHash != m_FindState.hash) + { + performFind(); + return; + } + + if(m_FindState.prevResult.first == -1) + return; + + cur->setTargetRange(m_FindState.prevResult.first, m_FindState.prevResult.second); + + FindState save = m_FindState; + + QString replaceText = m_FindReplace->replaceText(); + + // otherwise we have a valid previous find. Do the replace now + // note this will invalidate the find state (as most user operations would), so we save/restore + // the state + if(m_FindReplace->regexp()) + cur->replaceTargetRE(-1, replaceText.toUtf8().data()); + else + cur->replaceTarget(-1, replaceText.toUtf8().data()); + + m_FindState = save; + + // adjust the offset if we replaced text and it went up or down in size + m_FindState.offset += (replaceText.count() - find.count()); + + // move to the next result + performFind(); +} + +void ShaderViewer::performReplaceAll() +{ + ScintillaEdit *cur = currentScintilla(); + + if(!cur) + return; + + QString find = m_FindReplace->findText(); + QString replace = m_FindReplace->replaceText(); + + if(find.isEmpty()) + return; + + sptr_t flags = 0; + + if(m_FindReplace->matchCase()) + flags |= SCFIND_MATCHCASE; + if(m_FindReplace->matchWord()) + flags |= SCFIND_WHOLEWORD; + if(m_FindReplace->regexp()) + flags |= SCFIND_REGEXP | SCFIND_POSIX; + + FindReplace::SearchContext context = m_FindReplace->context(); + + (void)context; + + // trash the find state for any incremental finds + m_FindState = FindState(); + + QList scintillas = m_Scintillas; + + if(context == FindReplace::File) + scintillas = {cur}; + + int numReplacements = 1; + + for(ScintillaEdit *s : scintillas) + { + sptr_t start = 0; + sptr_t end = s->length(); + + QPair result; + + QByteArray findUtf8 = find.toUtf8(); + QByteArray replaceUtf8 = replace.toUtf8(); + + do + { + result = s->findText(flags, findUtf8.data(), start, end); + + if(result.first >= 0) + { + s->setTargetRange(result.first, result.second); + + if(m_FindReplace->regexp()) + s->replaceTargetRE(-1, replaceUtf8.data()); + else + s->replaceTarget(-1, replaceUtf8.data()); + + numReplacements++; + } + + start = result.second + (replaceUtf8.count() - findUtf8.count()); + + } while(result.first >= 0); + } + + RDDialog::information( + this, tr("Replace all"), + tr("%1 replacements made in %2 files").arg(numReplacements).arg(scintillas.count())); +} diff --git a/qrenderdoc/Windows/ShaderViewer.h b/qrenderdoc/Windows/ShaderViewer.h index 55c5aaebd..d1a82b495 100644 --- a/qrenderdoc/Windows/ShaderViewer.h +++ b/qrenderdoc/Windows/ShaderViewer.h @@ -36,6 +36,7 @@ class ShaderViewer; struct ShaderDebugTrace; struct ShaderReflection; class ScintillaEdit; +class FindReplace; // from Scintilla typedef intptr_t sptr_t; @@ -101,6 +102,10 @@ private slots: void readonly_keyPressed(QKeyEvent *event); void editable_keyPressed(QKeyEvent *event); void disassembly_buttonReleased(QMouseEvent *event); + void performFind(); + void performFindAll(); + void performReplace(); + void performReplaceAll(); public slots: bool stepBack(); @@ -124,8 +129,27 @@ private: ShaderStageType m_Stage; ScintillaEdit *m_DisassemblyView = NULL; ScintillaEdit *m_Errors = NULL; + ScintillaEdit *m_FindResults = NULL; QList m_Scintillas; + FindReplace *m_FindReplace; + + struct FindState + { + // hash identifies when the search has changed + QString hash; + + // the range identified when the search first occurred (for incremental find/replace) + sptr_t start = 0; + sptr_t end = 0; + + // the current offset where to search from next time, relative to above range + sptr_t offset = 0; + + // the last result + QPair prevResult; + } m_FindState; + SaveMethod m_SaveCallback; CloseMethod m_CloseCallback; @@ -137,14 +161,25 @@ private: static const int BREAKPOINT_MARKER = 2; static const int FINISHED_MARKER = 4; + static const int INDICATOR_FINDRESULT = 0; + static const int INDICATOR_REGHIGHLIGHT = 1; + void addFileList(); - ScintillaEdit *MakeEditor(const QString &name, const QString &text, bool src); + ScintillaEdit *MakeEditor(const QString &name, const QString &text, int lang); ScintillaEdit *AddFileScintilla(const QString &name, const QString &text); + ScintillaEdit *currentScintilla(); + ScintillaEdit *nextScintilla(ScintillaEdit *cur); + int instructionForLine(sptr_t line); void updateDebugging(); + + void ensureLineScrolled(ScintillaEdit *s, int i); + + void find(bool down); + void runTo(int runToInstruction, bool forward, ShaderDebugStateFlags condition = eShaderDbg_None); QString stringRep(const ShaderVariable &var, bool useType); diff --git a/qrenderdoc/qrenderdoc.pro b/qrenderdoc/qrenderdoc.pro index 23fd98cc3..87ec7152b 100644 --- a/qrenderdoc/qrenderdoc.pro +++ b/qrenderdoc/qrenderdoc.pro @@ -147,7 +147,8 @@ SOURCES += Code/qrenderdoc.cpp \ Windows/Dialogs/RemoteManager.cpp \ Windows/PixelHistoryView.cpp \ Widgets/PipelineFlowChart.cpp \ - Windows/Dialogs/EnvironmentEditor.cpp + Windows/Dialogs/EnvironmentEditor.cpp \ + Widgets/FindReplace.cpp HEADERS += Code/CaptureContext.h \ Code/qprocessinfo.h \ @@ -197,7 +198,8 @@ HEADERS += Code/CaptureContext.h \ Windows/Dialogs/RemoteManager.h \ Windows/PixelHistoryView.h \ Widgets/PipelineFlowChart.h \ - Windows/Dialogs/EnvironmentEditor.h + Windows/Dialogs/EnvironmentEditor.h \ + Widgets/FindReplace.h FORMS += Windows/Dialogs/AboutDialog.ui \ Windows/MainWindow.ui \ @@ -226,7 +228,8 @@ FORMS += Windows/Dialogs/AboutDialog.ui \ Windows/Dialogs/VirtualFileDialog.ui \ Windows/Dialogs/RemoteManager.ui \ Windows/PixelHistoryView.ui \ - Windows/Dialogs/EnvironmentEditor.ui + Windows/Dialogs/EnvironmentEditor.ui \ + Widgets/FindReplace.ui RESOURCES += Resources/resources.qrc diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index b0ac6c4cf..767250349 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -631,6 +631,7 @@ + @@ -687,6 +688,7 @@ + @@ -850,6 +852,7 @@ + @@ -895,6 +898,12 @@ MOC %(Filename).h $(IntDir)generated\moc_%(Filename).cpp + + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe;%(AdditionalInputs) + $(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe -DUNICODE -DWIN32 -DWIN64 -D_WIN32 -D_WIN64 -DRENDERDOC_PLATFORM_WIN32 -DSCINTILLA_QT=1 -DSCI_LEXER=1 -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -D_MSC_VER=1900 -I$(ProjectDir) -I$(SolutionDir)\renderdoc\api\replay -I$(ProjectDir)3rdparty\qt\$(Platform)\mkspecs/win32-msvc2015 -I$(ProjectDir)3rdparty\qt\$(Platform)\include -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtWidgets -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtGui -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtCore %(Fullpath) -o $(IntDir)generated\moc_%(Filename).cpp + MOC %(Filename).h + $(IntDir)generated\moc_%(Filename).cpp + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe;%(AdditionalInputs) $(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe -DUNICODE -DWIN32 -DWIN64 -D_WIN32 -D_WIN64 -DRENDERDOC_PLATFORM_WIN32 -DSCINTILLA_QT=1 -DSCI_LEXER=1 -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -D_MSC_VER=1900 -I$(ProjectDir) -I$(SolutionDir)\renderdoc\api\replay -I$(ProjectDir)3rdparty\qt\$(Platform)\mkspecs/win32-msvc2015 -I$(ProjectDir)3rdparty\qt\$(Platform)\include -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtWidgets -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtGui -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtCore %(Fullpath) -o $(IntDir)generated\moc_%(Filename).cpp @@ -1143,6 +1152,12 @@ UIC %(Filename).ui $(IntDir)generated\ui_%(Filename).h + + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe;%(AdditionalInputs) + $(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe %(Fullpath) -o $(IntDir)generated\ui_%(Filename).h + UIC %(Filename).ui + $(IntDir)generated\ui_%(Filename).h + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe;%(AdditionalInputs) $(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe %(Fullpath) -o $(IntDir)generated\ui_%(Filename).h diff --git a/qrenderdoc/qrenderdoc_local.vcxproj.filters b/qrenderdoc/qrenderdoc_local.vcxproj.filters index 30b5a80fc..c4ddbd36c 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj.filters +++ b/qrenderdoc/qrenderdoc_local.vcxproj.filters @@ -537,6 +537,12 @@ Windows\Dialogs + + Widgets + + + Generated Files + @@ -830,6 +836,9 @@ Generated Files + + Generated Files + @@ -1247,5 +1256,11 @@ Windows\Dialogs + + Widgets + + + Widgets + \ No newline at end of file