ACMX 2.139.0
Dual-Backend Real-Time GPU Video Synthesis
Loading...
Searching...
No Matches
find-shader.cpp
Go to the documentation of this file.
1#include "find-shader.hpp"
2
3#include <QApplication>
4#include <QCheckBox>
5#include <QCloseEvent>
6#include <QDialogButtonBox>
7#include <QDir>
8#include <QDirIterator>
9#include <QFile>
10#include <QFileInfo>
11#include <QHBoxLayout>
12#include <QHeaderView>
13#include <QLabel>
14#include <QLineEdit>
15#include <QPushButton>
16#include <QRegularExpression>
17#include <QSettings>
18#include <QTextStream>
19#include <QTreeWidget>
20#include <QVBoxLayout>
21
22namespace {
23 constexpr int FILE_PATH_ROLE = Qt::UserRole;
24 constexpr int LINE_NUMBER_ROLE = Qt::UserRole + 1;
25 constexpr int COLUMN_NUMBER_ROLE = Qt::UserRole + 2;
26 constexpr int MATCH_LENGTH_ROLE = Qt::UserRole + 3;
27 constexpr int MAX_RESULTS = 10000;
28} // namespace
29
30FindShaderDialog::FindShaderDialog(const QString &shaderPath, QWidget *parent) : QDialog(parent), shaderPath(QFileInfo(shaderPath).absoluteFilePath()) {
31 setWindowTitle(tr("Find in Shader Files"));
32 setAttribute(Qt::WA_DeleteOnClose);
33 setModal(false);
34
35 auto *layout = new QVBoxLayout(this);
36 layout->addWidget(new QLabel(tr("Search the active shader library using a regular expression:"), this));
37 auto *pathLabel = new QLabel(tr("Library: %1").arg(this->shaderPath), this);
38 pathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
39 layout->addWidget(pathLabel);
40
41 auto *searchRow = new QHBoxLayout();
42 patternEdit = new QLineEdit(this);
43 patternEdit->setPlaceholderText(tr("Regular expression, for example: uniform\\s+float"));
44 patternEdit->setClearButtonEnabled(true);
45 auto *searchButton = new QPushButton(tr("Search"), this);
46 searchButton->setDefault(true);
47 searchRow->addWidget(patternEdit, 1);
48 searchRow->addWidget(searchButton);
49 layout->addLayout(searchRow);
50
51 caseSensitiveCheck = new QCheckBox(tr("Case sensitive"), this);
52 layout->addWidget(caseSensitiveCheck);
53
54 resultsTree = new QTreeWidget(this);
55 resultsTree->setColumnCount(4);
56 resultsTree->setHeaderLabels({tr("Shader"), tr("Line"), tr("Match"), tr("Source")});
57 resultsTree->setRootIsDecorated(false);
58 resultsTree->setAlternatingRowColors(false);
59 resultsTree->setSelectionBehavior(QAbstractItemView::SelectRows);
60 resultsTree->setSelectionMode(QAbstractItemView::SingleSelection);
61 resultsTree->setSortingEnabled(true);
62 resultsTree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
63 resultsTree->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
64 resultsTree->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
65 resultsTree->header()->setSectionResizeMode(3, QHeaderView::Stretch);
66 layout->addWidget(resultsTree, 1);
67
68 statusLabel = new QLabel(tr("Enter a regular expression to search."), this);
69 layout->addWidget(statusLabel);
70
71 auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
72 openButton = buttonBox->addButton(tr("Open Result"), QDialogButtonBox::ActionRole);
73 openButton->setEnabled(false);
74 layout->addWidget(buttonBox);
75
76 connect(searchButton, &QPushButton::clicked, this, &FindShaderDialog::performSearch);
77 connect(patternEdit, &QLineEdit::returnPressed, this, &FindShaderDialog::performSearch);
78 connect(patternEdit, &QLineEdit::textChanged, this, [this]() { statusLabel->clear(); });
79 connect(resultsTree, &QTreeWidget::itemSelectionChanged, this, &FindShaderDialog::updateOpenButton);
80 connect(resultsTree, &QTreeWidget::itemActivated, this, [this](QTreeWidgetItem *item, int) { openResult(item); });
81 connect(openButton, &QPushButton::clicked, this, [this]() { openResult(resultsTree->currentItem()); });
82 connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::close);
83
84 QSettings settings("LostSideDead");
85 caseSensitiveCheck->setChecked(settings.value("findInFiles/caseSensitive", false).toBool());
86 if (!restoreGeometry(settings.value("findInFiles/geometry").toByteArray()))
87 resize(950, 600);
88 patternEdit->setFocus();
89}
90
91void FindShaderDialog::closeEvent(QCloseEvent *event) {
92 QSettings("LostSideDead").setValue("findInFiles/geometry", saveGeometry());
93 QDialog::closeEvent(event);
94}
95
97 resultsTree->clear();
99
100 const QString pattern = patternEdit->text();
101 if (pattern.isEmpty()) {
102 statusLabel->setText(tr("Enter a regular expression to search."));
103 return;
104 }
105
106 QRegularExpression::PatternOptions options;
107 if (!caseSensitiveCheck->isChecked())
108 options |= QRegularExpression::CaseInsensitiveOption;
109 const QRegularExpression expression(pattern, options);
110 if (!expression.isValid()) {
111 statusLabel->setText(tr("Invalid regular expression at position %1: %2").arg(expression.patternErrorOffset()).arg(expression.errorString()));
112 return;
113 }
114
115 QSettings settings("LostSideDead");
116 settings.setValue("findInFiles/caseSensitive", caseSensitiveCheck->isChecked());
117 settings.setValue("findInFiles/geometry", saveGeometry());
118
119 QApplication::setOverrideCursor(Qt::WaitCursor);
120 resultsTree->setSortingEnabled(false);
121
122 const QStringList filters = {"*.glsl", "*.frag", "*.vert", "*.comp"};
123 QDirIterator files(shaderPath, filters, QDir::Files | QDir::Readable, QDirIterator::Subdirectories);
124 int fileCount = 0;
125 int resultCount = 0;
126 bool limitReached = false;
127 const QDir root(shaderPath);
128
129 while (files.hasNext() && !limitReached) {
130 const QString filePath = files.next();
131 QFile file(filePath);
132 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
133 continue;
134 ++fileCount;
135
136 QTextStream input(&file);
137 int lineNumber = 0;
138 while (!input.atEnd() && !limitReached) {
139 const QString line = input.readLine();
140 ++lineNumber;
141 QRegularExpressionMatchIterator matches = expression.globalMatch(line);
142 while (matches.hasNext()) {
143 const QRegularExpressionMatch match = matches.next();
144 auto *item = new QTreeWidgetItem(resultsTree);
145 item->setText(0, root.relativeFilePath(filePath));
146 item->setText(1, QString::number(lineNumber));
147 item->setText(2, match.captured(0).isEmpty() ? tr("(zero-length match)") : match.captured(0));
148 item->setText(3, line.trimmed());
149 item->setData(0, FILE_PATH_ROLE, QFileInfo(filePath).absoluteFilePath());
150 item->setData(0, LINE_NUMBER_ROLE, lineNumber);
151 item->setData(0, COLUMN_NUMBER_ROLE, match.capturedStart());
152 item->setData(0, MATCH_LENGTH_ROLE, match.capturedLength());
153 ++resultCount;
154 if (resultCount >= MAX_RESULTS) {
155 limitReached = true;
156 break;
157 }
158 }
159 }
160 }
161
162 resultsTree->setSortingEnabled(true);
163 resultsTree->sortItems(0, Qt::AscendingOrder);
164 QApplication::restoreOverrideCursor();
165
166 if (limitReached) {
167 statusLabel->setText(tr("Showing the first %1 matches from %2 shader files.").arg(resultCount).arg(fileCount));
168 } else {
169 statusLabel->setText(tr("Found %1 match(es) in %2 shader file(s).").arg(resultCount).arg(fileCount));
170 }
171}
172
173void FindShaderDialog::openResult(QTreeWidgetItem *item) {
174 if (!item)
175 return;
176 emit resultActivated(item->data(0, FILE_PATH_ROLE).toString(), item->data(0, LINE_NUMBER_ROLE).toInt(), item->data(0, COLUMN_NUMBER_ROLE).toInt(), item->data(0, MATCH_LENGTH_ROLE).toInt());
177}
178
179void FindShaderDialog::updateOpenButton() { openButton->setEnabled(resultsTree->currentItem() != nullptr); }
void resultActivated(const QString &filePath, int lineNumber, int columnNumber, int matchLength)
Emitted when the user opens one search result.
QTreeWidget * resultsTree
QPushButton * openButton
void openResult(QTreeWidgetItem *item)
FindShaderDialog(const QString &shaderPath, QWidget *parent=nullptr)
QLineEdit * patternEdit
void closeEvent(QCloseEvent *event) override
QCheckBox * caseSensitiveCheck
Regular-expression search dialog for shader source libraries.