SeExpr

ExprBrowser.cpp
Go to the documentation of this file.
1 /*
2 * Copyright Disney Enterprises, Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License
6 * and the following modification to it: Section 6 Trademarks.
7 * deleted and replaced with:
8 *
9 * 6. Trademarks. This License does not grant permission to use the
10 * trade names, trademarks, service marks, or product names of the
11 * Licensor and its affiliates, except as required for reproducing
12 * the content of the NOTICE file.
13 *
14 * You may obtain a copy of the License at
15 * http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * @file ExprBrowser.cpp
18 * @brief Qt browser widget for list of expressions
19 * @author aselle
20 */
21 #include <QDir>
22 #include <QFileInfo>
23 #include <QTreeWidget>
24 #include <QTreeWidgetItem>
25 #include <QVBoxLayout>
26 #include <QTabWidget>
27 #include <QHeaderView>
28 #include <QLabel>
29 #include <QTextBrowser>
30 #include <QPushButton>
31 #include <QSpacerItem>
32 #include <QSizePolicy>
33 #include <QSortFilterProxyModel>
34 #include <QFileDialog>
35 #include <QMessageBox>
36
37 #include <cassert>
38 #include "ExprEditor.h"
39 #include "ExprBrowser.h"
40
41 #define P3D_CONFIG_ENVVAR "P3D_CONFIG_PATH"
42
43 class ExprTreeItem {
44 public:
45 ExprTreeItem(ExprTreeItem* parent, const QString& label, const QString& path)
46 : row(-1), parent(parent), label(label), path(path), populated(parent == 0) {}
47
48 ~ExprTreeItem() {
49 for (unsigned int i = 0; i < children.size(); i++) delete children[i];
50 }
51
52 ExprTreeItem* find(QString path) {
53 if (this->path == path)
54 return this;
55 else {
56 populate();
57 for (unsigned int i = 0; i < children.size(); i++) {
58 ExprTreeItem* ret = children[i]->find(path);
59 if (ret) return ret;
60 }
61 }
62 return 0;
63 }
64
65 void clear() {
66 for (unsigned int i = 0; i < children.size(); i++) {
67 delete children[i];
68 }
69 children.clear();
70 }
71
72 void populate() {
73 if (populated) return;
74 populated = true;
75 QFileInfo info(path);
76 if (info.isDir()) {
77 QFileInfoList infos = QDir(path).entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
78
79 // std::cerr<<"is dir and populating "<<path.toStdString()<<std::endl;
80 for (QList<QFileInfo>::ConstIterator it = infos.constBegin(); it != infos.constEnd(); ++it) {
81 const QFileInfo* fi = &*it;
82 if (fi->isDir() || fi->fileName().endsWith(".se")) {
83 addChild(new ExprTreeItem(this, fi->fileName(), fi->filePath()));
84 }
85 }
86 }
87 }
88
89 void addChild(ExprTreeItem* child) {
90 child->row = children.size();
91 children.push_back(child);
92 }
93
94 ExprTreeItem* getChild(const int row) {
95 populate();
96 if (row < 0 || row > (int)children.size()) {
97 assert(false);
98 }
99 return children[row];
100 }
101
102 int getChildCount() {
103 populate();
104 return children.size();
105 }
106
107 void regen() {
108 std::vector<QString> labels, paths;
109 for (unsigned int i = 0; i < children.size(); i++) {
110 labels.push_back(children[i]->label);
111 paths.push_back(children[i]->path);
112 delete children[i];
113 }
114 children.clear();
115
116 for (unsigned int i = 0; i < labels.size(); i++) addChild(new ExprTreeItem(this, labels[i], paths[i]));
117 }
118
119 int row;
120 ExprTreeItem* parent;
121 QString label;
122 QString path;
123
124 private:
125 std::vector<ExprTreeItem*> children;
126 bool populated;
127 };
128
129 class ExprTreeModel : public QAbstractItemModel {
130 ExprTreeItem* root;
131
132 public:
133 ExprTreeModel() : root(new ExprTreeItem(0, "", "")) {}
134
135 ~ExprTreeModel() { delete root; }
136
137 void update()
138 {
139 beginResetModel();
140 endResetModel();
141 }
142
143 void clear() {
144 beginResetModel();
145 root->clear();
146 endResetModel();
147 }
148
149 void addPath(const char* label, const char* path) { root->addChild(new ExprTreeItem(root, label, path)); }
150
151 QModelIndex parent(const QModelIndex& index) const {
152 if (!index.isValid()) return QModelIndex();
153 ExprTreeItem* item = (ExprTreeItem*)(index.internalPointer());
154 ExprTreeItem* parentItem = item->parent;
155 if (parentItem == root)
156 return QModelIndex();
157 else
158 return createIndex(parentItem->row, 0, parentItem);
159 }
160
161 QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const {
162 if (!hasIndex(row, column, parent))
163 return QModelIndex();
164 else if (!parent.isValid())
165 return createIndex(row, column, root->getChild(row));
166 else {
167 ExprTreeItem* item = (ExprTreeItem*)(parent.internalPointer());
168 return createIndex(row, column, item->getChild(row));
169 }
170 }
171
172 int columnCount(const QModelIndex& parent) const {
173 Q_UNUSED(parent);
174 return 1;
175 }
176
177 int rowCount(const QModelIndex& parent = QModelIndex()) const {
178 if (!parent.isValid())
179 return root->getChildCount();
180 else {
181 ExprTreeItem* item = (ExprTreeItem*)(parent.internalPointer());
182 if (!item)
183 return root->getChildCount();
184 else
185 return item->getChildCount();
186 }
187 }
188
189 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const {
190 if (!index.isValid()) return QVariant();
191 if (role != Qt::DisplayRole) return QVariant();
192 ExprTreeItem* item = (ExprTreeItem*)(index.internalPointer());
193 if (!item)
194 return QVariant();
195 else
196 return QVariant(item->label);
197 }
198
199 QModelIndex find(QString path) {
200 ExprTreeItem* item = root->find(path);
201 if (!item) {
202 beginResetModel();
203 root->regen();
204 endResetModel();
205 item = root->find(path);
206 }
207 if (item) {
208 std::cerr << "found it " << std::endl;
209 return createIndex(item->row, 0, item);
210 }
211
212 return QModelIndex();
213 }
214 };
215
216 class ExprTreeFilterModel : public QSortFilterProxyModel {
217 public:
218 ExprTreeFilterModel(QWidget* parent = 0) : QSortFilterProxyModel(parent) {}
219
220 void update()
221 {
222 beginResetModel();
223 endResetModel();
224 }
225
226 bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const {
227 if (sourceParent.isValid() && sourceModel()->data(sourceParent).toString().contains(filterRegExp()))
228 return true;
229 QString data = sourceModel()->data(sourceModel()->index(sourceRow, 0, sourceParent)).toString();
230 bool keep = data.contains(filterRegExp());
231
232 QModelIndex subIndex = sourceModel()->index(sourceRow, 0, sourceParent);
233 if (subIndex.isValid()) {
234 for (int i = 0; i < sourceModel()->rowCount(subIndex); ++i) keep = keep || filterAcceptsRow(i, subIndex);
235 }
236 return keep;
237 }
238 };
239
240 ExprBrowser::~ExprBrowser() { delete treeModel; }
241
242 ExprBrowser::ExprBrowser(QWidget* parent, ExprEditor* editor)
243 : QWidget(parent), editor(editor), _context(""), _searchPath(""), _applyOnSelect(true) {
244 QVBoxLayout* rootLayout = new QVBoxLayout;
245 rootLayout->setMargin(0);
246 this->setLayout(rootLayout);
247 // search and clear widgets
248 QHBoxLayout* searchAndClearLayout = new QHBoxLayout();
249 exprFilter = new QLineEdit();
250 connect(exprFilter, SIGNAL(textChanged(const QString&)), SLOT(filterChanged(const QString&)));
251 searchAndClearLayout->addWidget(exprFilter, 2);
252 QPushButton* clearFilterButton = new QPushButton("X");
253 clearFilterButton->setFixedWidth(24);
254 searchAndClearLayout->addWidget(clearFilterButton, 1);
255 rootLayout->addLayout(searchAndClearLayout);
256 connect(clearFilterButton, SIGNAL(clicked()), SLOT(clearFilter()));
257 // model of tree
258 treeModel = new ExprTreeModel();
259 proxyModel = new ExprTreeFilterModel(this);
260 proxyModel->setSourceModel(treeModel);
261 // tree widget
262 treeNew = new QTreeView;
263 treeNew->setModel(proxyModel);
264 treeNew->hideColumn(1);
265 treeNew->setHeaderHidden(true);
266 rootLayout->addWidget(treeNew);
267 // selection mode and signal
268 treeNew->setSelectionMode(QAbstractItemView::SingleSelection);
269 connect(treeNew->selectionModel(),
270 SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
271 SLOT(handleSelection(const QModelIndex&, const QModelIndex&)));
272 }
273
274 void ExprBrowser::addPath(const std::string& name, const std::string& path) {
275 labels.append(QString::fromStdString(name));
276 paths.append(QString::fromStdString(path));
277 treeModel->addPath(name.c_str(), path.c_str());
278 }
279
280 void ExprBrowser::setSearchPath(const QString& context, const QString& path) {
281 _context = context.toStdString();
282 _searchPath = path.toStdString();
283 }
284
285 std::string ExprBrowser::getSelectedPath() {
286 QModelIndex sel = treeNew->currentIndex();
287 if (sel.isValid()) {
288 QModelIndex realCurrent = proxyModel->mapToSource(sel);
289 ExprTreeItem* item = (ExprTreeItem*)realCurrent.internalPointer();
290 return item->path.toStdString();
291 }
292 return std::string("");
293 }
294
295 void ExprBrowser::selectPath(const char* path) {
296 QModelIndex index = treeModel->find(path);
297 treeNew->setCurrentIndex(proxyModel->mapFromSource(index));
298 }
299
300 void ExprBrowser::update() {
301 treeModel->update();
302 proxyModel->update();
303 }
304
305 void ExprBrowser::handleSelection(const QModelIndex& current, const QModelIndex& previous) {
306 Q_UNUSED(previous)
307 if (current.isValid()) {
308 QModelIndex realCurrent = proxyModel->mapToSource(current);
309 ExprTreeItem* item = (ExprTreeItem*)realCurrent.internalPointer();
310 QString path = item->path;
311 if (path.endsWith(".se")) {
312 std::ifstream file(path.toStdString().c_str());
313 std::string fileContents((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
314 editor->setExpr(fileContents, _applyOnSelect);
315 }
316 }
317 }
318
319 void ExprBrowser::clear() {
320 labels.clear();
321 paths.clear();
322 clearSelection();
323
324 treeModel->clear();
325 }
326
327 void ExprBrowser::clearSelection() { treeNew->clearSelection(); }
328
329 void ExprBrowser::clearFilter() { exprFilter->clear(); }
330
331 void ExprBrowser::filterChanged(const QString& str) {
332 proxyModel->setFilterRegExp(QRegExp(str));
333 proxyModel->setFilterKeyColumn(0);
334 if (str != "") {
335 treeNew->expandAll();
336 } else {
337 treeNew->collapseAll();
338 }
339 }
340
341 void ExprBrowser::saveExpressionAs() {
342 QString path = QFileDialog::getSaveFileName(this, "Save Expression", QString::fromStdString(_userExprDir), "*.se");
343
344 if (path.length() > 0) {
345 std::ofstream file(path.toStdString().c_str());
346 if (!file) {
347 QString msg = QString("Could not open file %1 for writing").arg(path);
348 QMessageBox::warning(this, "Error", QString("<font face=fixed>%1</font>").arg(msg));
349 return;
350 }
351 file << editor->getExpr();
352 file.close();
353
354 update();
355 selectPath(path.toStdString().c_str());
356 }
357 }
358
359 void ExprBrowser::saveLocalExpressionAs() {
360 QString path = QFileDialog::getSaveFileName(this, "Save Expression", QString::fromStdString(_localExprDir), "*.se");
361
362 if (path.length() > 0) {
363 std::ofstream file(path.toStdString().c_str());
364 if (!file) {
365 QString msg = QString("Could not open file %1 for writing").arg(path);
366 QMessageBox::warning(this, "Error", QString("<font face=fixed>%1</font>").arg(msg));
367 return;
368 }
369 file << editor->getExpr();
370 file.close();
371
372 update();
373 selectPath(path.toStdString().c_str());
374 }
375 }
376
377 void ExprBrowser::saveExpression() {
378 std::string path = getSelectedPath();
379 if (path.length() == 0) {
380 saveExpressionAs();
381 return;
382 }
383 std::ofstream file(path.c_str());
384 if (!file) {
385 QString msg =
386 QString("Could not open file %1 for writing. Is it read-only?").arg(QString::fromStdString(path));
387 QMessageBox::warning(this, "Error", QString("<font face=fixed>%1</font>").arg(msg));
388 return;
389 }
390 file << editor->getExpr();
391 file.close();
392 }
393
394 void ExprBrowser::expandAll() { treeNew->expandAll(); }
395
396 void ExprBrowser::expandToDepth(int depth) { treeNew->expandToDepth(depth); }
397
398 // Location for storing user's expression files
399 void ExprBrowser::addUserExpressionPath(const std::string& context) {
400 char* homepath = getenv("HOME");
401 if (homepath) {
402 std::string path = std::string(homepath) + "/" + context + "/expressions/";
403 if (QDir(QString(path.c_str())).exists()) {
404 _userExprDir = path;
405 addPath("My Expressions", path);
406 }
407 }
408 }
409
410 /*
411 * NOTE: The hard-coded paint3d assumptions can be removed once
412 * it (and bonsai?) are adjusted to call setSearchPath(context, path)
413 */
414
415 bool ExprBrowser::getExpressionDirs() {
416 const char* env;
417 bool enableLocal = false;
418 /*bool homeFound = false; -- for xgen's config.txt UserRepo section below */
419
420 if (_searchPath.length() > 0)
421 env = _searchPath.c_str();
422 else
423 env = getenv(P3D_CONFIG_ENVVAR); /* For backwards compatibility */
424
425 if (!env) return enableLocal;
426
427 std::string context;
428 if (_context.length() > 0) {
429 context = _context;
430 } else {
431 context = "paint3d"; /* For backwards compatibility */
432 }
433
434 clear();
435
436 std::string configFile = std::string(env) + "/config.txt";
437 std::ifstream file(configFile.c_str());
438 if (file) {
439
440 std::string key;
441 while (file) {
442 file >> key;
443
444 if (key[0] == '#') {
445 char buffer[1024];
446 file.getline(buffer, 1024);
447 } else {
448 if (key == "ExpressionDir") {
449 std::string label, path;
450 file >> label;
451 file >> path;
452 if (QDir(QString(path.c_str())).exists()) addPath(label, path);
453 } else if (key == "ExpressionSubDir") {
454 std::string path;
455 file >> path;
456 _localExprDir = path;
457 if (QDir(QString(path.c_str())).exists()) {
458 addPath("Local", _localExprDir);
459 enableLocal = true;
460 }
461 /* These are for compatibility with xgen.
462 * Long-term, xgen should use the same format.
463 * Longer-term, we should use JSON or something */
464 } else if (key == "GlobalRepo") {
465 std::string path;
466 file >> path;
467 path += "/expressions/";
468 if (QDir(QString(path.c_str())).exists()) addPath("Global", path);
469 } else if (key == "LocalRepo") {
470 std::string path;
471 file >> path;
472 path += "/expressions/";
473 _localExprDir = path;
474 if (QDir(QString(path.c_str())).exists()) {
475 addPath("Local", _localExprDir);
476 enableLocal = true;
477 }
478
479 /*
480 * xgen's config.txt has a "UserRepo" section but we
481 * intentionally ignore it since we already add the user dir
482 * down where the HOME stuff is handled
483 */
484
485 /*
486 } else if (key == "UserRepo") {
487 std::string path;
488 file>>path;
489 path += "/expressions/";
490
491 size_t found = path.find("${HOME}");
492
493 if (found != std::string::npos) {
494 char *homepath = getenv("HOME");
495 if (homepath) {
496 path.replace(found, strlen("${HOME}"), homepath);
497 } else {
498 continue;
499 }
500 }
501 if(QDir(QString(path.c_str())).exists()){
502 addPath("User", path);
503 homeFound = true;
504 }
505 */
506 } else {
507 char buffer[1024];
508 file.getline(buffer, 1024);
509 }
510 }
511 }
512 }
513 addUserExpressionPath(context);
514 update();
515 return enableLocal;
516 }
ExprBrowser::clearSelection
void clearSelection()
Definition: ExprBrowser.cpp:327
index
The result is computed int int< br >< div style="margin-left: 40px;"> Picks values randomly between loRange and hiRange based on supplied index(which is automatically hashed). &nbsp
ExprTreeItem::parent
ExprTreeItem * parent
Definition: ExprBrowser.cpp:120
ExprBrowser::_localExprDir
std::string _localExprDir
Definition: ExprBrowser.h:54
ExprEditor
Definition: ExprEditor.h:94
ExprTreeItem::clear
void clear()
Definition: ExprBrowser.cpp:65
ExprBrowser::expandAll
void expandAll()
Definition: ExprBrowser.cpp:394
ExprTreeModel::index
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const
Definition: ExprBrowser.cpp:161
ExprBrowser::filterChanged
void filterChanged(const QString &str)
Definition: ExprBrowser.cpp:331
ExprBrowser::update
void update()
Definition: ExprBrowser.cpp:300
ExprTreeModel::addPath
void addPath(const char *label, const char *path)
Definition: ExprBrowser.cpp:149
ExprBrowser::saveLocalExpressionAs
void saveLocalExpressionAs()
Definition: ExprBrowser.cpp:359
ExprBrowser::saveExpressionAs
void saveExpressionAs()
Definition: ExprBrowser.cpp:341
ExprTreeItem::children
std::vector< ExprTreeItem * > children
Definition: ExprBrowser.cpp:125
ExprBrowser::_applyOnSelect
bool _applyOnSelect
Definition: ExprBrowser.h:57
ExprTreeItem::addChild
void addChild(ExprTreeItem *child)
Definition: ExprBrowser.cpp:89
ExprBrowser::handleSelection
void handleSelection(const QModelIndex &current, const QModelIndex &previous)
Definition: ExprBrowser.cpp:305
ExprTreeItem::regen
void regen()
Definition: ExprBrowser.cpp:107
ExprBrowser::setSearchPath
void setSearchPath(const QString &context, const QString &path)
Definition: ExprBrowser.cpp:280
ExprTreeModel::rowCount
int rowCount(const QModelIndex &parent=QModelIndex()) const
Definition: ExprBrowser.cpp:177
ExprTreeItem::label
QString label
Definition: ExprBrowser.cpp:121
ExprBrowser::expandToDepth
void expandToDepth(int depth)
Definition: ExprBrowser.cpp:396
ExprBrowser::proxyModel
ExprTreeFilterModel * proxyModel
Definition: ExprBrowser.h:50
ExprTreeItem::populated
bool populated
Definition: ExprBrowser.cpp:126
ExprBrowser::treeNew
QTreeView * treeNew
Definition: ExprBrowser.h:51
ExprTreeItem::find
ExprTreeItem * find(QString path)
Definition: ExprBrowser.cpp:52
ExprTreeItem
Definition: ExprBrowser.cpp:43
ExprTreeItem::path
QString path
Definition: ExprBrowser.cpp:122
ExprTreeItem::populate
void populate()
Definition: ExprBrowser.cpp:72
ExprBrowser::_searchPath
std::string _searchPath
Definition: ExprBrowser.h:56
ExprTreeItem::getChild
ExprTreeItem * getChild(const int row)
Definition: ExprBrowser.cpp:94
ExprTreeModel::ExprTreeModel
ExprTreeModel()
Definition: ExprBrowser.cpp:133
ExprTreeModel::find
QModelIndex find(QString path)
Definition: ExprBrowser.cpp:199
ExprTreeModel::~ExprTreeModel
~ExprTreeModel()
Definition: ExprBrowser.cpp:135
ExprBrowser::saveExpression
void saveExpression()
Definition: ExprBrowser.cpp:377
ExprTreeModel::columnCount
int columnCount(const QModelIndex &parent) const
Definition: ExprBrowser.cpp:172
ExprBrowser::_context
std::string _context
Definition: ExprBrowser.h:55
ExprEditor::getExpr
std::string getExpr()
Definition: ExprEditor.cpp:373
ExprBrowser::ExprBrowser
ExprBrowser(QWidget *parent, ExprEditor *editor)
Definition: ExprBrowser.cpp:242
it
you may not use this file except in compliance with the License and the following modification to it
Definition: license.txt:10
ExprTreeItem::row
int row
Definition: ExprBrowser.cpp:119
ExprBrowser::labels
QList< QString > labels
Definition: ExprBrowser.h:47
ExprBrowser.h
ExprBrowser::_userExprDir
std::string _userExprDir
Definition: ExprBrowser.h:53
ExprTreeModel::update
void update()
Definition: ExprBrowser.cpp:137
ExprTreeFilterModel
Definition: ExprBrowser.cpp:216
file
file(GLOB io_cpp "*.cpp") file(GLOB to_remove "ExprLLVMCodeGeneration.cpp") list(REMOVE_ITEM io_cpp $
Definition: CMakeLists.txt:17
ExprTreeItem::getChildCount
int getChildCount()
Definition: ExprBrowser.cpp:102
ExprTreeFilterModel::filterAcceptsRow
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
Definition: ExprBrowser.cpp:226
ExprBrowser::selectPath
void selectPath(const char *path)
Definition: ExprBrowser.cpp:295
ExprTreeFilterModel::ExprTreeFilterModel
ExprTreeFilterModel(QWidget *parent=0)
Definition: ExprBrowser.cpp:218
ExprEditor::setExpr
void setExpr(const std::string &expression, const bool apply=false)
Definition: ExprEditor.cpp:375
ExprTreeModel::parent
QModelIndex parent(const QModelIndex &index) const
Definition: ExprBrowser.cpp:151
ExprTreeModel::clear
void clear()
Definition: ExprBrowser.cpp:143
ExprTreeModel::root
ExprTreeItem * root
Definition: ExprBrowser.cpp:130
ExprBrowser::getSelectedPath
std::string getSelectedPath()
Definition: ExprBrowser.cpp:285
ExprBrowser::paths
QList< QString > paths
Definition: ExprBrowser.h:48
ExprTreeItem::ExprTreeItem
ExprTreeItem(ExprTreeItem *parent, const QString &label, const QString &path)
Definition: ExprBrowser.cpp:45
P3D_CONFIG_ENVVAR
#define P3D_CONFIG_ENVVAR
Definition: ExprBrowser.cpp:41
ExprTreeFilterModel::update
void update()
Definition: ExprBrowser.cpp:220
ExprBrowser::clearFilter
void clearFilter()
Definition: ExprBrowser.cpp:329
ExprEditor.h
ExprBrowser::addPath
void addPath(const std::string &name, const std::string &path)
Definition: ExprBrowser.cpp:274
ExprBrowser::getExpressionDirs
bool getExpressionDirs()
Definition: ExprBrowser.cpp:415
context
If a scalar is used in a vector context
Definition: userdoc.txt:436
ExprBrowser::clear
void clear()
Definition: ExprBrowser.cpp:319
ExprBrowser::exprFilter
QLineEdit * exprFilter
Definition: ExprBrowser.h:52
ExprTreeModel
Definition: ExprBrowser.cpp:129
ExprBrowser::addUserExpressionPath
void addUserExpressionPath(const std::string &context)
Definition: ExprBrowser.cpp:399
ExprBrowser::~ExprBrowser
~ExprBrowser()
Definition: ExprBrowser.cpp:240
ExprTreeItem::~ExprTreeItem
~ExprTreeItem()
Definition: ExprBrowser.cpp:48
ExprTreeModel::data
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const
Definition: ExprBrowser.cpp:189
ExprBrowser::editor
ExprEditor * editor
Definition: ExprBrowser.h:46
ExprBrowser::treeModel
ExprTreeModel * treeModel
Definition: ExprBrowser.h:49