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