1 /*
2 * Copyright Disney Enterprises, Inc.  All rights reserved.
3 * Copyright (C) 2020 L. E. Segovia <amy@amyspark.me>
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License
7 * and the following modification to it: Section 6 Trademarks.
8 * deleted and replaced with:
9 *
10 * 6. Trademarks. This License does not grant permission to use the
11 * trade names, trademarks, service marks, or product names of the
12 * Licensor and its affiliates, except as required for reproducing
13 * the content of the NOTICE file.
14 *
15 * You may obtain a copy of the License at
16 * http://www.apache.org/licenses/LICENSE-2.0
17 *
18 * @file ExprDialog.cpp
19 * @brief A basic editor/browser/previewer for expression editing
20 * @author  jlacewel
21 */
22 
23 #include <QApplication>
24 #include <QDir>
25 #include <QLabel>
26 #include <QTextBrowser>
27 #include <fstream>
28 #include <iostream>
29 
30 #include "ExprBrowser.h"
31 #include "ExprDialog.h"
32 #include "ExprControlCollection.h"
33 #include "ExprGrapher2d.h"
34 
35 #define P3D_CONFIG_ENVVAR "P3D_CONFIG_PATH"
36 
ExprDialog(QWidget * parent)37 ExprDialog::ExprDialog(QWidget* parent) : QDialog(parent), _currentEditorIdx(0) {
38     this->setMinimumWidth(600);
39     QVBoxLayout* rootLayout = new QVBoxLayout(0);
40     rootLayout->setMargin(2);
41     this->setLayout(rootLayout);
42 
43     showEditorTimer = new QTimer();
44     connect(showEditorTimer, SIGNAL(timeout()), SLOT(_showEditor()));
45 
46     QSplitter* vsplitter = new QSplitter(Qt::Vertical, this);
47     rootLayout->addWidget(vsplitter);
48 
49     QTabWidget* topTabWidget = new QTabWidget();
50     vsplitter->addWidget(topTabWidget);
51 
52     QWidget* previewLibraryWidget = new QWidget();
53     QHBoxLayout* previewLibraryLayout = new QHBoxLayout();
54     previewLibraryWidget->setLayout(previewLibraryLayout);
55     topTabWidget->addTab(previewLibraryWidget, tr("Preview / Library"));
56 
57     QWidget* bottomWidget = new QWidget();
58     vsplitter->addWidget(bottomWidget);
59     QVBoxLayout* bottomLayout = new QVBoxLayout();
60     bottomLayout->setMargin(1);
61     bottomWidget->setLayout(bottomLayout);
62 
63     // setup preview
64     QWidget* leftWidget = new QWidget();
65     leftWidget->setFixedWidth(450);
66     QVBoxLayout* leftLayout = new QVBoxLayout();
67     leftLayout->setMargin(0);
68     leftWidget->setLayout(leftLayout);
69     QHBoxLayout* previewLayout = new QHBoxLayout();
70     grapher = new ExprGrapherWidget(this, 200, 200);
71     previewLayout->addWidget(grapher, 0);
72     previewCommentLabel = new QLabel();
73     previewLayout->addWidget(previewCommentLabel, 1, Qt::AlignLeft | Qt::AlignTop);
74     leftLayout->addLayout(previewLayout);
75     previewLibraryLayout->addWidget(leftWidget);
76 
77     // setup button bar
78     // QWidget* buttonBarWidget=new QWidget();
79     QHBoxLayout* buttonBarLayout = new QHBoxLayout();
80     // buttonBarWidget->setLayout(buttonBarLayout);
81     buttonBarLayout->setMargin(1);
82     previewButton = new QPushButton(tr("Preview"));
83     buttonBarLayout->addWidget(previewButton);
84     saveButton = new QPushButton(tr("Save"));
85     buttonBarLayout->addWidget(saveButton);
86     saveAsButton = new QPushButton(tr("Save As"));
87     buttonBarLayout->addWidget(saveAsButton);
88     saveLocalButton = new QPushButton(tr("Save Local"));
89     saveLocalButton->setEnabled(false);
90     buttonBarLayout->addWidget(saveLocalButton);
91     clearButton = new QPushButton(tr("Clear"));
92     buttonBarLayout->addWidget(clearButton);
93     bottomLayout->addLayout(buttonBarLayout);
94 
95     controls = new ExprControlCollection();
96 
97     // controls
98     QScrollArea* scrollArea = new QScrollArea();
99     scrollArea->setWidget(controls);
100     // scrollArea->setWidget(new QLabel("test\nweird\nfds\nfdsahsha\nfsdajdlsa\nfasdjjhsafd\nfasdhjdfsa\nfdasjdfsha"));
101     scrollArea->setFocusPolicy(Qt::NoFocus);
102     scrollArea->setMinimumHeight(100);
103     scrollArea->setFixedWidth(450);
104     scrollArea->setWidgetResizable(true);
105     leftLayout->addWidget(scrollArea, 1);
106 
107     // make button bar
108     editor = new ExprEditor(this, controls);
109     connect(editor, SIGNAL(apply()), SLOT(verifiedApply()));
110     connect(editor, SIGNAL(preview()), SLOT(previewExpression()));
111     connect(grapher, SIGNAL(preview()), SLOT(previewExpression()));
112     bottomLayout->addWidget(editor);
113 
114     // make expression library browser
115     browser = new ExprBrowser(0, editor);
116     previewLibraryLayout->addWidget(browser);
117 
118     // dialog buttons
119     QHBoxLayout* buttonLayout = new QHBoxLayout(0);
120     buttonLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Minimum));
121     applyButton = new QPushButton(tr("Apply"));
122     buttonLayout->addWidget(applyButton);
123     acceptButton = new QPushButton(tr("Accept"));
124     buttonLayout->addWidget(acceptButton);
125     cancelButton = new QPushButton(tr("Cancel"));
126     buttonLayout->addWidget(cancelButton);
127     connect(applyButton, SIGNAL(clicked()), this, SLOT(verifiedApply()));
128     connect(acceptButton, SIGNAL(clicked()), this, SLOT(verifiedAccept()));
129     connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject()));
130     rootLayout->addLayout(buttonLayout);
131 
132     setupHelp(topTabWidget);
133 
134     // connect buttons
135     connect(previewButton, SIGNAL(clicked()), SLOT(previewExpression()));
136     connect(clearButton, SIGNAL(clicked()), SLOT(clearExpression()));
137     connect(saveButton, SIGNAL(clicked()), browser, SLOT(saveExpression()));
138     connect(saveAsButton, SIGNAL(clicked()), browser, SLOT(saveExpressionAs()));
139     connect(saveLocalButton, SIGNAL(clicked()), browser, SLOT(saveLocalExpressionAs()));
140 }
141 
showEditor(int idx)142 void ExprDialog::showEditor(int idx) {
143     _currentEditorIdx = idx;
144     showEditorTimer->setSingleShot(true);
145     showEditorTimer->start();
146 }
147 
_showEditor()148 void ExprDialog::_showEditor() { controls->showEditor(_currentEditorIdx); }
149 
show()150 void ExprDialog::show() {
151     // populate the expressions
152     browser->getExpressionDirs();
153     browser->expandAll();
154     QDialog::show();
155 }
156 
exec()157 int ExprDialog::exec() {
158     // populate the expressions
159     browser->getExpressionDirs();
160     browser->expandAll();
161     return QDialog::exec();
162 }
163 
keyPressEvent(QKeyEvent * event)164 void ExprDialog::keyPressEvent(QKeyEvent* event) {
165     if (event->key() == Qt::Key_Escape) return;
166     return QDialog::keyPressEvent(event);
167 }
168 
closeEvent(QCloseEvent * event)169 void ExprDialog::closeEvent(QCloseEvent* event) {
170     emit dialogClosed();
171     QDialog::closeEvent(event);
172 }
173 
reject()174 void ExprDialog::reject() {
175     emit dialogClosed();
176     QDialog::reject();
177 }
178 
verifiedApply()179 void ExprDialog::verifiedApply() {
180     applyExpression();
181     if (grapher->expr.isValid()) {
182         emit expressionApplied();
183     } else {
184         QMessageBox msgBox;
185         msgBox.setText(tr("Your expression had possible errors."));
186         msgBox.setInformativeText(tr("Do you want to accept your expression anyways?"));
187         msgBox.setStandardButtons(QMessageBox::Cancel | QMessageBox::Ok);
188         switch (msgBox.exec()) {
189             case QMessageBox::Ok:
190                 emit expressionApplied();
191                 break;
192             default:
193                 break;
194         }
195     }
196 }
197 
verifiedAccept()198 void ExprDialog::verifiedAccept() {
199     applyExpression();
200     if (grapher->expr.isValid()) {
201         emit expressionApplied();
202         emit dialogClosed();
203         accept();
204     } else {
205         QMessageBox msgBox;
206         msgBox.setText(tr("Your expression had possible errors."));
207         msgBox.setInformativeText(tr("Do you want to accept your expression anyways?"));
208         msgBox.setStandardButtons(QMessageBox::Cancel | QMessageBox::Ok);
209         switch (msgBox.exec()) {
210             case QMessageBox::Ok:
211                 emit expressionApplied();
212                 emit dialogClosed();
213                 accept();
214                 break;
215             default:
216                 break;
217         }
218     }
219 }
220 
setupHelp(QTabWidget * tab)221 void ExprDialog::setupHelp(QTabWidget* tab) {
222     QWidget* browserspace = new QWidget(tab);
223     helpBrowser = new QTextBrowser(browserspace);
224     tab->addTab(browserspace, tr("Help"));
225 
226     // Locate help docs relative to location of the app itself
227     QFile* helpDoc = new QFile(QString::fromLatin1("%1/../share/doc/SeExpr2/SeExpressions.html").arg(QCoreApplication::applicationDirPath()));
228     if (helpDoc->exists()) {
229         QString sheet =
230             QString::fromLatin1("body {background-color: #eeeeee; color: #000000;} \na {color: #3333ff; text-decoration: none;}\n");
231         helpBrowser->document()->setDefaultStyleSheet(sheet);
232         helpBrowser->setSource(QUrl::fromLocalFile(helpDoc->fileName()));
233     }
234 
235     QPushButton* backPb = new QPushButton(tr("Back"));
236     // backPb->setIcon(QApplication::style()->standardIcon(QStyle::SP_ArrowLeft));
237     backPb->setEnabled(false);
238     QPushButton* forwardPb = new QPushButton(tr("Forward"));
239     //    forwardPb->setIcon(QApplication::style()->standardIcon(QStyle::SP_ArrowRight));
240     forwardPb->setEnabled(false);
241 
242     QVBoxLayout* helpLayout = new QVBoxLayout(browserspace);
243     QHBoxLayout* helpPbLayout = new QHBoxLayout;
244     helpLayout->addLayout(helpPbLayout);
245     helpPbLayout->addWidget(backPb);
246     helpPbLayout->addWidget(forwardPb);
247     // helpPbLayout->addItem(new QSpacerItem(0,0, QSizePolicy::MinimumExpanding,
248     //                            QSizePolicy::Minimum));
249     QHBoxLayout* findBar = new QHBoxLayout();
250     helpPbLayout->addWidget(new QLabel(tr("Find")), /*stretch*/ false);
251     helpFindBox = new QLineEdit;
252     helpPbLayout->addWidget(helpFindBox, /*stretch*/ false);
253     connect(helpFindBox, SIGNAL(returnPressed()), this, SLOT(findNextInHelp()));
254     QPushButton* nextButton = new QPushButton(tr("Find Next"));
255     QPushButton* prevButton = new QPushButton(tr("Find Prev"));
256     helpPbLayout->addWidget(nextButton, /*stretch*/ false);
257     helpPbLayout->addWidget(prevButton, /*stretch*/ false);
258     connect(nextButton, SIGNAL(clicked()), this, SLOT(findNextInHelp()));
259     connect(prevButton, SIGNAL(clicked()), this, SLOT(findPrevInHelp()));
260     helpPbLayout->addLayout(findBar, /*stretch*/ false);
261     helpLayout->addWidget(helpBrowser, /*stretch*/ true);
262     helpBrowser->setMinimumHeight(120);
263 
264     // wire up help browser forward/back buttons
265     connect(backPb, SIGNAL(clicked()), helpBrowser, SLOT(backward()));
266     connect(forwardPb, SIGNAL(clicked()), helpBrowser, SLOT(forward()));
267     connect(helpBrowser, SIGNAL(backwardAvailable(bool)), backPb, SLOT(setEnabled(bool)));
268     connect(helpBrowser, SIGNAL(forwardAvailable(bool)), forwardPb, SLOT(setEnabled(bool)));
269 }
270 
findHelper(QTextDocument::FindFlags flags)271 void ExprDialog::findHelper(QTextDocument::FindFlags flags) {
272     QTextDocument* doc = helpBrowser->document();
273     if (prevFind != helpFindBox->text()) {
274         prevFind = helpFindBox->text();
275         helpBrowser->setTextCursor(QTextCursor(doc));
276     }
277     QTextCursor blah = doc->find(helpFindBox->text(), helpBrowser->textCursor(), flags);
278     helpBrowser->setTextCursor(blah);
279 }
280 
findNextInHelp()281 void ExprDialog::findNextInHelp() { findHelper(0); }
282 
findPrevInHelp()283 void ExprDialog::findPrevInHelp() { findHelper(QTextDocument::FindBackward); }
284 
previewExpression()285 void ExprDialog::previewExpression() {
286     applyExpression();
287     emit preview();
288 }
289 
applyExpression()290 void ExprDialog::applyExpression() {
291     editor->clearErrors();
292     // set new expression
293     grapher->expr.setExpr(editor->getExpr().toStdString());
294     grapher->update();
295 
296     // set the label widget to mention that variables will not be previewed
297     bool empty = true;
298     if (grapher->expr.varmap.size() > 0) {
299         std::stringstream s;
300         s << tr("<b>Variables not supported in preview (assumed zero):</b><br>").toStdString();
301         int count = 0;
302         for (BasicExpression::VARMAP::iterator i = grapher->expr.varmap.begin(); i != grapher->expr.varmap.end(); ++i) {
303             count++;
304             s << "$" << i->first << " ";
305             if (count % 4 == 0) s << "<br>";
306         }
307         previewCommentLabel->setText(QString::fromStdString(s.str()));
308         empty = false;
309     } else
310         previewCommentLabel->setText(QString());
311     // set the label widget to mention that variables will not be previewed
312     if (grapher->expr.funcmap.size() > 0) {
313         std::stringstream s;
314         s << tr("<b>Functions not supported in preview (assumed zero):</b><br>").toStdString();
315         int count = 0;
316         for (BasicExpression::FUNCMAP::iterator i = grapher->expr.funcmap.begin(); i != grapher->expr.funcmap.end();
317              ++i) {
318             count++;
319             s << "" << i->first << "() ";
320             if (count % 4 == 0) s << "<br>";
321         }
322         previewCommentLabel->setText(QString::fromStdString(s.str()));
323         empty = false;
324     }
325     if (empty) previewCommentLabel->setText(QString());
326 
327     // put errors into editor module
328     bool valid = grapher->expr.isValid();
329     if (!valid) {
330         const std::vector<SeExpr2::Expression::Error>& errors = grapher->expr.getErrors();
331         for (unsigned int i = 0; i < errors.size(); i++) {
332             editor->addError(errors[i].startPos, errors[i].endPos, errors[i].error);
333         }
334         editor->nextError();
335     }
336 }
337 
clearExpression()338 void ExprDialog::clearExpression() {
339     browser->clearSelection();
340     editor->setExpr(QString(), false);
341     grapher->expr.setExpr(std::string());
342     grapher->update();
343 }
344