KSeExpr 6.0.0.0
ExprBrowser.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2011-2019 Disney Enterprises, Inc.
2// SPDX-License-Identifier: LicenseRef-Apache-2.0
3// SPDX-FileCopyrightText: 2020 L. E. Segovia <amy@amyspark.me>
4// SPDX-License-Identifier: GPL-3.0-or-later
5/*
6 * @file ExprBrowser.cpp
7 * @brief Qt browser widget for list of expressions
8 * @author aselle
9 */
10#include <array>
11#include <cassert>
12
13#include <QDir>
14#include <QFileDialog>
15#include <QFileInfo>
16#include <QHeaderView>
17#include <QLabel>
18#include <QMessageBox>
19#include <QPushButton>
20#include <QSizePolicy>
21#include <QSortFilterProxyModel>
22#include <QSpacerItem>
23#include <QTabWidget>
24#include <QTextBrowser>
25#include <QTextStream>
26#include <QTreeWidget>
27#include <QTreeWidgetItem>
28#include <QVBoxLayout>
29
30
31#include "Debug.h"
32#include "ExprBrowser.h"
33#include "ExprEditor.h"
34
35#define P3D_CONFIG_ENVVAR "P3D_CONFIG_PATH"
36
38{
39public:
40 ExprTreeItem(ExprTreeItem *parent, const QString &label, const QString &path)
41 : row(-1)
42 , parent(parent)
43 , label(label)
44 , path(path)
45 , populated(parent == nullptr)
46 {
47 }
48
50 {
51 for (unsigned int i = 0; i < children.size(); i++)
52 delete children[i];
53 }
54
56 {
57 if (this->path == path)
58 return this;
59 else {
60 populate();
61 for (auto & i : children) {
62 ExprTreeItem *ret = i->find(path);
63 if (ret)
64 return ret;
65 }
66 }
67 return nullptr;
68 }
69
70 void clear()
71 {
72 for (unsigned int i = 0; i < children.size(); i++) {
73 delete children[i];
74 }
75 children.clear();
76 }
77
78 void populate()
79 {
80 if (populated)
81 return;
82 populated = true;
83 QFileInfo info(path);
84 if (info.isDir()) {
85 QFileInfoList infos = QDir(path).entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
86
87 // dbgSeExpr <<"is dir and populating "<<path.toStdString();
88 for (QList<QFileInfo>::ConstIterator it = infos.constBegin(); it != infos.constEnd(); ++it) {
89 const QFileInfo *fi = &*it;
90 if (fi->isDir() || fi->fileName().endsWith(QString::fromLatin1(".se"))) {
91 addChild(new ExprTreeItem(this, fi->fileName(), fi->filePath()));
92 }
93 }
94 }
95 }
96
98 {
99 child->row = children.size();
100 children.push_back(child);
101 }
102
104 {
105 populate();
106 if (row < 0 || row > (int)children.size()) {
107 assert(false);
108 }
109 return children[row];
110 }
111
113 {
114 populate();
115 return children.size();
116 }
117
118 void regen()
119 {
120 std::vector<QString> labels;
121 std::vector<QString> paths;
122 for (auto & i : children) {
123 labels.push_back(i->label);
124 paths.push_back(i->path);
125 delete i;
126 }
127 children.clear();
128
129 for (unsigned int i = 0; i < labels.size(); i++)
130 addChild(new ExprTreeItem(this, labels[i], paths[i]));
131 }
132
133 int row;
135 QString label;
136 QString path;
137
138private:
139 std::vector<ExprTreeItem *> children;
141};
142
143class ExprTreeModel : public QAbstractItemModel
144{
146
147public:
149 : root(new ExprTreeItem(nullptr, QString(), QString()))
150 {
151 }
152
153 ~ExprTreeModel() override
154 {
155 delete root;
156 }
157
158 void update()
159 {
160 beginResetModel();
161 endResetModel();
162 }
163
164 void clear()
165 {
166 beginResetModel();
167 root->clear();
168 endResetModel();
169 }
170
171 void addPath(const char *label, const char *path)
172 {
173 root->addChild(new ExprTreeItem(root, QString::fromLatin1(label), QString::fromLatin1(path)));
174 }
175
176 QModelIndex parent(const QModelIndex &index) const override
177 {
178 if (!index.isValid())
179 return {};
180 auto *item = (ExprTreeItem *)(index.internalPointer());
181 ExprTreeItem *parentItem = item->parent;
182 if (parentItem == root)
183 return {};
184 else
185 return createIndex(parentItem->row, 0, parentItem);
186 }
187
188 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
189 {
190 if (!hasIndex(row, column, parent))
191 return {};
192 else if (!parent.isValid())
193 return createIndex(row, column, root->getChild(row));
194 else {
195 auto *item = (ExprTreeItem *)(parent.internalPointer());
196 return createIndex(row, column, item->getChild(row));
197 }
198 }
199
200 int columnCount(const QModelIndex &) const override
201 {
202 return 1;
203 }
204
205 int rowCount(const QModelIndex &parent = QModelIndex()) const override
206 {
207 if (!parent.isValid())
208 return root->getChildCount();
209 else {
210 auto *item = (ExprTreeItem *)(parent.internalPointer());
211 if (!item)
212 return root->getChildCount();
213 else
214 return item->getChildCount();
215 }
216 }
217
218 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
219 {
220 if (!index.isValid())
221 return QVariant();
222 if (role != Qt::DisplayRole)
223 return QVariant();
224 auto *item = (ExprTreeItem *)(index.internalPointer());
225 if (!item)
226 return QVariant();
227 else
228 return QVariant(item->label);
229 }
230
231 QModelIndex find(QString path)
232 {
233 ExprTreeItem *item = root->find(path);
234 if (!item) {
235 beginResetModel();
236 root->regen();
237 endResetModel();
238 item = root->find(path);
239 }
240 if (item) {
241 dbgSeExpr << "found it ";
242 return createIndex(item->row, 0, item);
243 }
244
245 return {};
246 }
247};
248
249class ExprTreeFilterModel : public QSortFilterProxyModel
250{
251public:
252 ExprTreeFilterModel(QWidget *parent = nullptr)
253 : QSortFilterProxyModel(parent)
254 {
255 }
256
257 void update()
258 {
259 beginResetModel();
260 endResetModel();
261 }
262
263 bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
264 {
265 if (sourceParent.isValid() && sourceModel()->data(sourceParent).toString().contains(filterRegularExpression()))
266 return true;
267 QString data = sourceModel()->data(sourceModel()->index(sourceRow, 0, sourceParent)).toString();
268 bool keep = data.contains(filterRegularExpression());
269
270 QModelIndex subIndex = sourceModel()->index(sourceRow, 0, sourceParent);
271 if (subIndex.isValid()) {
272 for (int i = 0; i < sourceModel()->rowCount(subIndex); ++i)
273 keep = keep || filterAcceptsRow(i, subIndex);
274 }
275 return keep;
276 }
277};
278
280{
281 delete treeModel;
282}
283
284ExprBrowser::ExprBrowser(QWidget *parent, ExprEditor *editor)
285 : QWidget(parent)
286 , editor(editor)
287 , _context(QString())
288 , _searchPath(QString())
289 , _applyOnSelect(true)
290{
291 auto *rootLayout = new QVBoxLayout;
292 rootLayout->setContentsMargins({});
293 this->setLayout(rootLayout);
294 // search and clear widgets
295 auto *searchAndClearLayout = new QHBoxLayout();
296 exprFilter = new QLineEdit();
297 connect(exprFilter, SIGNAL(textChanged(const QString &)), SLOT(filterChanged(const QString &)));
298 searchAndClearLayout->addWidget(exprFilter, 2);
299 auto *clearFilterButton = new QPushButton(tr("X"));
300 clearFilterButton->setFixedWidth(24);
301 searchAndClearLayout->addWidget(clearFilterButton, 1);
302 rootLayout->addLayout(searchAndClearLayout);
303 connect(clearFilterButton, SIGNAL(clicked()), SLOT(clearFilter()));
304 // model of tree
305 treeModel = new ExprTreeModel();
307 proxyModel->setSourceModel(treeModel);
308 // tree widget
309 treeNew = new QTreeView;
310 treeNew->setModel(proxyModel);
311 treeNew->hideColumn(1);
312 treeNew->setHeaderHidden(true);
313 rootLayout->addWidget(treeNew);
314 // selection mode and signal
315 treeNew->setSelectionMode(QAbstractItemView::SingleSelection);
316 connect(treeNew->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), SLOT(handleSelection(const QModelIndex &, const QModelIndex &)));
317}
318
319void ExprBrowser::addPath(const std::string &name, const std::string &path)
320{
321 labels.append(QString::fromStdString(name));
322 paths.append(QString::fromStdString(path));
323 treeModel->addPath(name.c_str(), path.c_str());
324}
325
326void ExprBrowser::setSearchPath(const QString &context, const QString &path)
327{
328 _context = context;
329 _searchPath = path;
330}
331
333{
334 QModelIndex sel = treeNew->currentIndex();
335 if (sel.isValid()) {
336 QModelIndex realCurrent = proxyModel->mapToSource(sel);
337 auto *item = (ExprTreeItem *)realCurrent.internalPointer();
338 return item->path.toStdString();
339 }
340 return std::string("");
341}
342
343void ExprBrowser::selectPath(const char *path)
344{
345 QModelIndex index = treeModel->find(QString::fromLatin1(path));
346 treeNew->setCurrentIndex(proxyModel->mapFromSource(index));
347}
348
350{
351 treeModel->update();
353}
354
355void ExprBrowser::handleSelection(const QModelIndex &current, const QModelIndex &previous)
356{
357 Q_UNUSED(previous)
358 if (current.isValid()) {
359 QModelIndex realCurrent = proxyModel->mapToSource(current);
360 auto *item = (ExprTreeItem *)realCurrent.internalPointer();
361 QString path = item->path;
362 if (path.endsWith(QString::fromLatin1(".se"))) {
363 QFile file(path);
364 if (file.open(QIODevice::ReadOnly)) {
365 QTextStream fileContents(&file);
366 editor->setExpr(fileContents.readAll(), _applyOnSelect);
367 }
368 }
369 }
370}
371
373{
374 labels.clear();
375 paths.clear();
377
378 treeModel->clear();
379}
380
382{
383 treeNew->clearSelection();
384}
385
387{
388 exprFilter->clear();
389}
390
391void ExprBrowser::filterChanged(const QString &str)
392{
393 proxyModel->setFilterRegularExpression(QRegularExpression(str));
394 proxyModel->setFilterKeyColumn(0);
395 if (!str.isEmpty()) {
396 treeNew->expandAll();
397 } else {
398 treeNew->collapseAll();
399 }
400}
401
403{
404 QString path = QFileDialog::getSaveFileName(this, tr("Save Expression"), QString::fromStdString(_userExprDir), tr("*.se"));
405
406 if (path.length() > 0) {
407 std::ofstream file(path.toStdString().c_str());
408 if (!file) {
409 QString msg = tr("Could not open file %1 for writing").arg(path);
410 QMessageBox::warning(this, tr("Error"), QString::fromLatin1("<font face=fixed>%1</font>").arg(msg));
411 return;
412 }
413 file << editor->getExpr().toStdString();
414 file.close();
415
416 update();
417 selectPath(path.toStdString().c_str());
418 }
419}
420
422{
423 QString path = QFileDialog::getSaveFileName(this, tr("Save Expression"), QString::fromStdString(_localExprDir), tr("*.se"));
424
425 if (path.length() > 0) {
426 std::ofstream file(path.toStdString().c_str());
427 if (!file) {
428 QString msg = tr("Could not open file %1 for writing").arg(path);
429 QMessageBox::warning(this, tr("Error"), QString::fromLatin1("<font face=fixed>%1</font>").arg(msg));
430 return;
431 }
432 file << editor->getExpr().toStdString();
433 file.close();
434
435 update();
436 selectPath(path.toStdString().c_str());
437 }
438}
439
441{
442 std::string path = getSelectedPath();
443 if (path.length() == 0) {
445 return;
446 }
447 std::ofstream file(path.c_str());
448 if (!file) {
449 QString msg = tr("Could not open file %1 for writing. Is it read-only?").arg(QString::fromStdString(path));
450 QMessageBox::warning(this, tr("Error"), tr("<font face=fixed>%1</font>").arg(msg));
451 return;
452 }
453 file << editor->getExpr().toStdString();
454 file.close();
455}
456
458{
459 treeNew->expandAll();
460}
461
463{
464 treeNew->expandToDepth(depth);
465}
466
467// Location for storing user's expression files
468void ExprBrowser::addUserExpressionPath(const std::string &context)
469{
470 char *homepath = getenv("HOME");
471 if (homepath) {
472 std::string path = std::string(homepath) + "/" + context + "/expressions/";
473 if (QDir(QString::fromStdString(path)).exists()) {
474 _userExprDir = path;
475 addPath("My Expressions", path);
476 }
477 }
478}
479
480/*
481 * NOTE: The hard-coded paint3d assumptions can be removed once
482 * it (and bonsai?) are adjusted to call setSearchPath(context, path)
483 */
484
486{
487 const char *env = nullptr;
488 bool enableLocal = false;
489 /*bool homeFound = false; -- for xgen's config.txt UserRepo section below */
490
491 if (_searchPath.length() > 0)
492 env = _searchPath.toStdString().c_str();
493 else
494 env = getenv(P3D_CONFIG_ENVVAR); /* For backwards compatibility */
495
496 if (!env)
497 return enableLocal;
498
499 std::string context;
500 if (_context.length() > 0) {
501 context = _context.toStdString();
502 } else {
503 context = "paint3d"; /* For backwards compatibility */
504 }
505
506 clear();
507
508 std::string configFile = std::string(env) + "/config.txt";
509 std::ifstream file(configFile.c_str());
510 if (file) {
511 std::string key;
512 while (file) {
513 file >> key;
514
515 if (key[0] == '#') {
516 std::array<char, 1024> buffer{};
517 file.getline(buffer.data(), 1024);
518 } else {
519 if (key == "ExpressionDir") {
520 std::string label;
521 std::string path;
522 file >> label;
523 file >> path;
524 if (QDir(QString::fromStdString(path)).exists())
525 addPath(label, path);
526 } else if (key == "ExpressionSubDir") {
527 std::string path;
528 file >> path;
529 _localExprDir = path;
530 if (QDir(QString::fromStdString(path)).exists()) {
531 addPath("Local", _localExprDir);
532 enableLocal = true;
533 }
534 /* These are for compatibility with xgen.
535 * Long-term, xgen should use the same format.
536 * Longer-term, we should use JSON or something */
537 } else if (key == "GlobalRepo") {
538 std::string path;
539 file >> path;
540 path += "/expressions/";
541 if (QDir(QString::fromStdString(path)).exists())
542 addPath("Global", path);
543 } else if (key == "LocalRepo") {
544 std::string path;
545 file >> path;
546 path += "/expressions/";
547 _localExprDir = path;
548 if (QDir(QString::fromStdString(path)).exists()) {
549 addPath("Local", _localExprDir);
550 enableLocal = true;
551 }
552
553 /*
554 * xgen's config.txt has a "UserRepo" section but we
555 * intentionally ignore it since we already add the user dir
556 * down where the HOME stuff is handled
557 */
558
559 /*
560 } else if (key == "UserRepo") {
561 std::string path;
562 file>>path;
563 path += "/expressions/";
564
565 size_t found = path.find("${HOME}");
566
567 if (found != std::string::npos) {
568 char *homepath = getenv("HOME");
569 if (homepath) {
570 path.replace(found, strlen("${HOME}"), homepath);
571 } else {
572 continue;
573 }
574 }
575 if(QDir(QString(path.c_str())).exists()){
576 addPath("User", path);
577 homeFound = true;
578 }
579 */
580 } else {
581 std::array<char, 1024> buffer {};
582 file.getline(buffer.data(), 1024);
583 }
584 }
585 }
586 }
587 addUserExpressionPath(context);
588 update();
589 return enableLocal;
590}
#define dbgSeExpr
Definition Debug.h:17
#define P3D_CONFIG_ENVVAR
ExprEditor * editor
Definition ExprBrowser.h:36
~ExprBrowser() override
QTreeView * treeNew
Definition ExprBrowser.h:41
void expandToDepth(int depth)
void setSearchPath(const QString &context, const QString &path)
void saveExpression()
void handleSelection(const QModelIndex &current, const QModelIndex &previous)
void saveLocalExpressionAs()
bool _applyOnSelect
Definition ExprBrowser.h:47
QString _searchPath
Definition ExprBrowser.h:46
void filterChanged(const QString &str)
std::string _userExprDir
Definition ExprBrowser.h:43
ExprBrowser(QWidget *parent, ExprEditor *editor)
bool getExpressionDirs()
QList< QString > paths
Definition ExprBrowser.h:38
void clearSelection()
QLineEdit * exprFilter
Definition ExprBrowser.h:42
QList< QString > labels
Definition ExprBrowser.h:37
void addUserExpressionPath(const std::string &context)
void addPath(const std::string &name, const std::string &path)
std::string getSelectedPath()
ExprTreeFilterModel * proxyModel
Definition ExprBrowser.h:40
void selectPath(const char *path)
void clearFilter()
void saveExpressionAs()
std::string _localExprDir
Definition ExprBrowser.h:44
ExprTreeModel * treeModel
Definition ExprBrowser.h:39
QString _context
Definition ExprBrowser.h:45
void setExpr(const QString &expression, bool apply=false)
QString getExpr()
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
ExprTreeFilterModel(QWidget *parent=nullptr)
ExprTreeItem * getChild(const int row)
ExprTreeItem(ExprTreeItem *parent, const QString &label, const QString &path)
std::vector< ExprTreeItem * > children
void addChild(ExprTreeItem *child)
ExprTreeItem * find(QString path)
ExprTreeItem * parent
~ExprTreeModel() override
QModelIndex parent(const QModelIndex &index) const override
int rowCount(const QModelIndex &parent=QModelIndex()) const override
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
QModelIndex find(QString path)
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
int columnCount(const QModelIndex &) const override
void addPath(const char *label, const char *path)
ExprTreeItem * root