diff --git a/qrenderdoc/Code/Interface/PersistantConfig.cpp b/qrenderdoc/Code/Interface/PersistantConfig.cpp index ab24e503e..baa99dd00 100644 --- a/qrenderdoc/Code/Interface/PersistantConfig.cpp +++ b/qrenderdoc/Code/Interface/PersistantConfig.cpp @@ -163,6 +163,18 @@ struct LegacyData QVariantMap _ConfigSettings; }; +static rdcarray> &GetCustomStorage() +{ + static rdcarray> ret; + return ret; +} + +CustomPersistentStorage::CustomPersistentStorage(rdcstr name) +{ + if(!name.empty()) + GetCustomStorage().push_back({name, this}); +} + QVariantMap PersistantConfig::storeValues() const { QVariantMap ret; @@ -186,6 +198,11 @@ QVariantMap PersistantConfig::storeValues() const ret[lit("ShaderViewer_FriendlyNaming")] = m_Legacy->_ShaderViewer_FriendlyNaming; ret[lit("ConfigSettings")] = m_Legacy->_ConfigSettings; + for(const rdcpair &ps : GetCustomStorage()) + { + ps.second->save(ret[QString(ps.first)]); + } + return ret; } @@ -295,6 +312,9 @@ void PersistantConfig::applyValues(const QVariantMap &values) if(saveConfig) RENDERDOC_SaveConfigSettings(); + + for(const rdcpair &ps : GetCustomStorage()) + ps.second->load(values[QString(ps.first)]); } static QMutex RemoteHostLock; diff --git a/qrenderdoc/Code/Interface/PersistantConfig.h b/qrenderdoc/Code/Interface/PersistantConfig.h index c0d3e9ffd..2849c90f2 100644 --- a/qrenderdoc/Code/Interface/PersistantConfig.h +++ b/qrenderdoc/Code/Interface/PersistantConfig.h @@ -702,6 +702,19 @@ void RemoveRecentFile(rdcarray &recentList, const rdcstr &file); struct LegacyData; +#if !defined(SWIG) +class QVariant; + +// not exposed to swig - allow windows to have completely custom persistent storage that aren't +// "settings". +struct CustomPersistentStorage +{ + CustomPersistentStorage(rdcstr name); + virtual void save(QVariant &v) const = 0; + virtual void load(const QVariant &v) = 0; +}; +#endif + DOCUMENT(R"(A persistant config file that is automatically loaded and saved, which contains any settings and information that needs to be preserved from one run to the next. diff --git a/qrenderdoc/Windows/EventBrowser.cpp b/qrenderdoc/Windows/EventBrowser.cpp index 6c5214586..0ad41df79 100644 --- a/qrenderdoc/Windows/EventBrowser.cpp +++ b/qrenderdoc/Windows/EventBrowser.cpp @@ -26,10 +26,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -50,6 +52,59 @@ #include "scintilla/include/qt/ScintillaEdit.h" #include "ui_EventBrowser.h" +struct EventBrowserPersistentStorage : public CustomPersistentStorage +{ + EventBrowserPersistentStorage() : CustomPersistentStorage(rdcstr()) {} + EventBrowserPersistentStorage(rdcstr name) : CustomPersistentStorage(name) {} + void save(QVariant &v) const + { + QVariantMap settings; + + settings[lit("current")] = CurrentFilter; + + QVariantList filters; + for(const QPair &f : SavedFilters) + filters << QVariant(QVariantList({f.first, f.second})); + + settings[lit("filters")] = filters; + + v = settings; + } + + void load(const QVariant &v) + { + QVariantMap settings = v.toMap(); + + QVariant current = settings[lit("current")]; + if(current.isValid() && current.type() == QVariant::String) + CurrentFilter = current.toString(); + + QVariant saved = settings[lit("filters")]; + if(saved.isValid() && saved.type() == QVariant::List) + { + QVariantList filters = saved.toList(); + for(QVariant filter : filters) + { + QVariantList filterPair = filter.toList(); + if(filterPair.count() == 2 && filterPair[0].type() == QVariant::String && + filterPair[1].type() == QVariant::String) + { + QString name = filterPair[0].toString(); + QString expr = filterPair[1].toString(); + + if(!name.isEmpty()) + SavedFilters.push_back({name, expr}); + } + } + } + } + + QString CurrentFilter = lit("$draw()"); + QList> SavedFilters; +}; + +static EventBrowserPersistentStorage persistantStorage("EventBrowser"); + enum { COL_NAME, @@ -3084,8 +3139,34 @@ EventBrowser::EventBrowser(ICaptureContext &ctx, QWidget *parent) ui->filterExpression->enableCompletion(); ui->filterExpression->setAcceptRichText(false); - // set default filter, include only draws that aren't pop markers - ui->filterExpression->setText(lit("$draw()")); + ui->filterExpression->setText(persistantStorage.CurrentFilter); + + m_SavedCompleter = new QCompleter(this); + m_SavedCompleter->setWidget(ui->filterExpression); + m_SavedCompleter->setCompletionMode(QCompleter::UnfilteredPopupCompletion); + m_SavedCompleter->setWrapAround(false); + m_SavedCompletionModel = new QStringListModel(this); + m_SavedCompleter->setModel(m_SavedCompletionModel); + m_SavedCompleter->setCompletionRole(Qt::DisplayRole); + + QObject::connect(m_SavedCompleter, OverloadedSlot::of(&QCompleter::activated), + [this](const QModelIndex &idx) { + int i = idx.row(); + if(i >= 0 && i < persistantStorage.SavedFilters.count()) + { + m_CurrentFilterText->setPlainText(persistantStorage.SavedFilters[i].second); + + QTextCursor c = m_CurrentFilterText->textCursor(); + c.movePosition(QTextCursor::EndOfLine); + m_CurrentFilterText->setTextCursor(c); + } + }); + + QObject::connect(ui->filterExpression, &RDTextEdit::keyPress, this, + &EventBrowser::savedFilter_keyPress); + + QObject::connect(ui->recentFilters, &QToolButton::clicked, + [this]() { ShowSavedFilterCompleter(ui->filterExpression); }); QObject::connect(ui->filterSettings, &QToolButton::clicked, this, &EventBrowser::filterSettings_clicked); @@ -3317,7 +3398,7 @@ void EventBrowser::CreateFilterDialog() m_FilterSettings.FuncList = new QListWidget(this); m_FilterSettings.Explanation = new RDTreeWidget(this); - m_FilterSettings.Dialog->setWindowTitle(lit("Event Filter Configuration")); + m_FilterSettings.Dialog->setWindowTitle(tr("Event Filter Configuration")); m_FilterSettings.Dialog->setWindowFlags(m_FilterSettings.Dialog->windowFlags() & ~Qt::WindowContextHelpButtonHint); m_FilterSettings.Dialog->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); @@ -3376,6 +3457,151 @@ void EventBrowser::CreateFilterDialog() QObject::connect(m_FilterSettings.Filter, &RDTextEdit::completionEnd, m_FilterSettings.Filter, &RDTextEdit::textChanged); + QObject::connect(m_FilterSettings.Filter, &RDTextEdit::keyPress, this, + &EventBrowser::savedFilter_keyPress); + + QObject::connect(recentFilters, &QToolButton::clicked, + [this]() { ShowSavedFilterCompleter(m_FilterSettings.Filter); }); + + QObject::connect(saveFilter, &QToolButton::clicked, [this]() { + QDialog *dialog = new QDialog(this); + + dialog->setWindowTitle(tr("Save filters")); + dialog->setWindowFlags(dialog->windowFlags() & ~Qt::WindowContextHelpButtonHint); + dialog->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + RDLineEdit saveName; + saveName.setPlaceholderText(tr("Name of filter")); + + RDListWidget filters; + for(const QPair &f : persistantStorage.SavedFilters) + filters.addItem(f.first); + + QPushButton saveButton; + saveButton.setText(tr("Save")); + saveButton.setIcon(Icons::save()); + QPushButton deleteButton; + deleteButton.setText(tr("Delete")); + + QDialogButtonBox saveDialogButtons; + saveDialogButtons.addButton(QDialogButtonBox::Ok); + QObject::connect(&saveDialogButtons, &QDialogButtonBox::accepted, dialog, &QDialog::accept); + + QGridLayout grid; + grid.addWidget(&saveName, 0, 0, 1, 1); + grid.addWidget(&saveButton, 0, 1, 1, 1); + grid.addWidget(&filters, 1, 0, 1, 1); + grid.addWidget(&deleteButton, 1, 1, 1, 1, Qt::AlignHCenter | Qt::AlignTop); + grid.addWidget(&saveDialogButtons, 2, 0, 1, 2); + + dialog->setLayout(&grid); + + auto enterCallback = [this, &saveButton, &deleteButton](QKeyEvent *e) { + if(e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) + { + saveButton.click(); + e->accept(); + } + + if(e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete) + { + deleteButton.click(); + e->accept(); + } + }; + + QObject::connect(&saveName, &RDLineEdit::keyPress, enterCallback); + QObject::connect(&filters, &RDListWidget::keyPress, enterCallback); + QObject::connect(&filters, &RDListWidget::itemActivated, + [&saveButton](QListWidgetItem *) { saveButton.click(); }); + + QObject::connect(&saveName, &RDLineEdit::textChanged, [this, &saveName, &filters]() { + for(int i = 0; i < persistantStorage.SavedFilters.count(); i++) + { + if(saveName.text().trimmed().toLower() == persistantStorage.SavedFilters[i].first.toLower()) + { + if(filters.currentRow() != i) + filters.setCurrentRow(i); + return; + } + } + + filters.setCurrentItem(NULL); + }); + + QObject::connect(&filters, &QListWidget::currentRowChanged, [this, &saveName](int row) { + if(row >= 0 && row < persistantStorage.SavedFilters.count()) + saveName.setText(persistantStorage.SavedFilters[row].first); + }); + + QObject::connect(&saveButton, &QPushButton::clicked, [this, dialog, &saveName, &filters]() { + QString n = saveName.text().trimmed(); + QString f = m_FilterSettings.Filter->toPlainText(); + + for(int i = 0; i < persistantStorage.SavedFilters.count(); i++) + { + if(n.toLower() == persistantStorage.SavedFilters[i].first.toLower()) + { + if(persistantStorage.SavedFilters[i].second.trimmed() == f.trimmed()) + { + dialog->accept(); + return; + } + + QMessageBox::StandardButton res = RDDialog::question( + dialog, tr("Delete filter?"), + tr("Are you sure you want to overwrite the %1 filter? From:\n\n%2\n\nTo:\n\n%3") + .arg(persistantStorage.SavedFilters[i].first) + .arg(persistantStorage.SavedFilters[i].second) + .arg(f), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + + if(res != QMessageBox::Yes) + return; + + delete filters.takeItem(i); + persistantStorage.SavedFilters.erase(persistantStorage.SavedFilters.begin() + i); + break; + } + } + + persistantStorage.SavedFilters.insert(0, {n, f}); + + dialog->accept(); + }); + + QObject::connect(&deleteButton, &QPushButton::clicked, [this, dialog, &filters]() { + QListWidgetItem *item = filters.currentItem(); + if(!item) + return; + + for(int i = 0; i < persistantStorage.SavedFilters.count(); i++) + { + if(item->text().trimmed().toLower() == persistantStorage.SavedFilters[i].first.toLower()) + { + QMessageBox::StandardButton res = + RDDialog::question(dialog, tr("Delete filter?"), + tr("Are you sure you want to delete the %1 filter?\n\n%2") + .arg(persistantStorage.SavedFilters[i].first) + .arg(persistantStorage.SavedFilters[i].second), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + + if(res == QMessageBox::Yes) + { + delete filters.takeItem(i); + persistantStorage.SavedFilters.erase(persistantStorage.SavedFilters.begin() + i); + } + + return; + } + } + }); + + RDDialog::show(dialog); + + dialog->deleteLater(); + }); + recentFilters->setAutoRaise(true); recentFilters->setIcon(Icons::filter_reapply()); recentFilters->setToolTip(tr("Load saved filters")); @@ -3438,6 +3664,11 @@ void EventBrowser::CreateFilterDialog() m_FilterSettings.Timeout->stop(); m_FilterSettings.Timeout->timeout({}); } + + if(e->key() == Qt::Key_Down) + { + ShowSavedFilterCompleter(m_FilterSettings.Filter); + } }); QObject::connect(m_FilterSettings.Explanation, &RDTreeWidget::currentItemChanged, this, @@ -3683,6 +3914,7 @@ void EventBrowser::filter_apply() ui->events->updateExpansion(m_EventsExpansion, keygen); QString expression = ui->filterExpression->toPlainText(); + persistantStorage.CurrentFilter = expression; rdcarray filters; *m_ParseTrace = m_FilterModel->ParseExpressionToFilters(expression, filters); @@ -3872,6 +4104,11 @@ void EventBrowser::on_filterExpression_keyPress(QKeyEvent *e) filter_apply(); } + + if(e->key() == Qt::Key_Down) + { + ShowSavedFilterCompleter(ui->filterExpression); + } } void EventBrowser::filter_forceCompletion_keyPress(QKeyEvent *e) @@ -3911,6 +4148,48 @@ void EventBrowser::filter_forceCompletion_keyPress(QKeyEvent *e) } } +void EventBrowser::ShowSavedFilterCompleter(RDTextEdit *filter) +{ + QStringList strs; + + for(QPair &f : persistantStorage.SavedFilters) + strs << f.first + lit(": ") + f.second; + + m_SavedCompletionModel->setStringList(strs); + + m_SavedCompleter->setWidget(filter); + + QRect r = filter->rect(); + m_SavedCompleter->complete(r); + + m_CurrentFilterText = filter; +} + +void EventBrowser::savedFilter_keyPress(QKeyEvent *e) +{ + if(!m_SavedCompleter->popup()->isVisible()) + return; + + switch(e->key()) + { + // if a completion is in progress ignore any events the completer will process + case Qt::Key_Return: + case Qt::Key_Enter: + m_SavedCompleter->activated(m_SavedCompleter->popup()->selectionModel()->currentIndex()); + m_SavedCompleter->popup()->hide(); + return; + // allow key scrolling + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_PageUp: + case Qt::Key_PageDown: return; + default: break; + } + + // all other keys close the popup + m_SavedCompleter->popup()->hide(); +} + void EventBrowser::on_filterExpression_textChanged() { if(!ui->filterExpression->completionInProgress()) diff --git a/qrenderdoc/Windows/EventBrowser.h b/qrenderdoc/Windows/EventBrowser.h index 55ba44bab..393006814 100644 --- a/qrenderdoc/Windows/EventBrowser.h +++ b/qrenderdoc/Windows/EventBrowser.h @@ -47,6 +47,8 @@ class RDTreeWidgetItem; class RDTextEdit; class QListWidget; class QCheckBox; +class QCompleter; +class QStringListModel; typedef QSet RDTreeViewExpansionState; @@ -167,6 +169,7 @@ private slots: void settings_filterApply(); void filterSettings_clicked(); void filter_forceCompletion_keyPress(QKeyEvent *e); + void savedFilter_keyPress(QKeyEvent *e); void filter_CompletionBegin(QString prefix); void filter_apply(); void events_keyPress(QKeyEvent *event); @@ -190,6 +193,7 @@ private: int FindEvent(QString filter, uint32_t after, bool forward); void Find(bool forward); + void ShowSavedFilterCompleter(RDTextEdit *filter); void CreateFilterDialog(); void AddFilterExplanations(QString parentFunc, RDTreeWidgetItem *root, @@ -237,6 +241,10 @@ private: QTimer *Timeout; } m_FilterSettings; + QCompleter *m_SavedCompleter; + QStringListModel *m_SavedCompletionModel; + RDTextEdit *m_CurrentFilterText; + void RefreshShaderMessages(); Ui::EventBrowser *ui; ICaptureContext &m_Ctx;