1 
2 //
3 // This source file is part of appleseed.
4 // Visit https://appleseedhq.net/ for additional information and resources.
5 //
6 // This software is released under the MIT license.
7 //
8 // Copyright (c) 2014-2018 Marius Avram, The appleseedhq Organization
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining a copy
11 // of this software and associated documentation files (the "Software"), to deal
12 // in the Software without restriction, including without limitation the rights
13 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 // copies of the Software, and to permit persons to whom the Software is
15 // furnished to do so, subject to the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be included in
18 // all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 // THE SOFTWARE.
27 //
28 
29 // Interface header.
30 #include "expressioneditorwindow.h"
31 
32 // UI definition headers.
33 #include "ui_expressioneditorwindow.h"
34 
35 // appleseed.studio headers.
36 #include "mainwindow/project/tools.h"
37 #include "utility/miscellaneous.h"
38 #include "utility/settingskeys.h"
39 
40 // appleseed.shared headers.
41 #include "application/application.h"
42 
43 // appleseed.renderer headers.
44 #include "renderer/api/log.h"
45 #include "renderer/api/material.h"
46 #include "renderer/api/project.h"
47 #include "renderer/api/utility.h"
48 
49 // SeExpr headers.
50 #pragma warning (push)
51 #pragma warning (disable : 4267)    // conversion from 'size_t' to 'int', possible loss of data
52 #include "SeExpression.h"
53 #include "SeExprEditor/SeExprEditor.h"
54 #include "SeExprEditor/SeExprEdBrowser.h"
55 #include "SeExprEditor/SeExprEdControlCollection.h"
56 #pragma warning (pop)
57 
58 // Qt headers.
59 #include <QCloseEvent>
60 #include <QDesktopServices>
61 #include <QFileInfo>
62 #include <QHBoxLayout>
63 #include <QKeySequence>
64 #include <QLabel>
65 #include <QPushButton>
66 #include <QScrollArea>
67 #include <QShortcut>
68 #include <Qt>
69 #include <QVBoxLayout>
70 
71 // Boost headers.
72 #include "boost/filesystem/operations.hpp"
73 #include "boost/filesystem/path.hpp"
74 
75 // Standard headers.
76 #include <algorithm>
77 #include <fstream>
78 #include <sstream>
79 
80 using namespace appleseed::shared;
81 using namespace foundation;
82 using namespace renderer;
83 using namespace std;
84 namespace bf = boost::filesystem;
85 
86 namespace appleseed {
87 namespace studio {
88 
ExpressionEditorWindow(const Project & project,ParamArray & settings,const QString & widget_name,const string & expression,QWidget * parent)89 ExpressionEditorWindow::ExpressionEditorWindow(
90     const Project&  project,
91     ParamArray&     settings,
92     const QString&  widget_name,
93     const string&   expression,
94     QWidget*        parent)
95   : WindowBase(parent, "expression_editor_window")
96   , m_ui(new Ui::ExpressionEditorWindow())
97   , m_project(project)
98   , m_settings(settings)
99   , m_widget_name(widget_name)
100   , m_show_examples(false)
101 {
102     m_ui->setupUi(this);
103 
104     setAttribute(Qt::WA_DeleteOnClose);
105     setWindowFlags(Qt::Tool);
106 
107     QHBoxLayout* root_layout = new QHBoxLayout(m_ui->scrollarea);
108 
109     QVBoxLayout* left_layout = new QVBoxLayout();
110     QVBoxLayout* right_layout = new QVBoxLayout();
111     root_layout->addLayout(left_layout);
112     root_layout->addLayout(right_layout);
113 
114     // Expression controls.
115     SeExprEdControlCollection* controls = new SeExprEdControlCollection();
116     QScrollArea* controls_scrollarea = new QScrollArea(this);
117     controls_scrollarea->setObjectName("expression_controls");
118     controls_scrollarea->setMinimumHeight(200);
119     controls_scrollarea->setWidgetResizable(true);
120     controls_scrollarea->setWidget(controls);
121     left_layout->addWidget(controls_scrollarea);
122 
123     // Clear button.
124     QShortcut* clear_shortcut = new QShortcut(QKeySequence("Ctrl+N"), this);
125     connect(clear_shortcut, SIGNAL(activated()), SLOT(slot_clear_expression()));
126     QPushButton* clear_button = new QPushButton("Clear");
127     clear_button->setToolTip(clear_shortcut->key().toString(QKeySequence::NativeText));
128     connect(clear_button, SIGNAL(clicked()), SLOT(slot_clear_expression()));
129 
130     // Save button.
131     QShortcut* save_shortcut = new QShortcut(QKeySequence("Ctrl+S"), this);
132     connect(save_shortcut, SIGNAL(activated()), SLOT(slot_save_script()));
133     QPushButton* save_button = new QPushButton("Save");
134     save_button->setToolTip(save_shortcut->key().toString(QKeySequence::NativeText));
135     connect(save_button, SIGNAL(clicked()), SLOT(slot_save_script()));
136 
137     // Load button.
138     QShortcut* load_shortcut = new QShortcut(QKeySequence("Ctrl+O"), this);
139     connect(load_shortcut, SIGNAL(activated()), SLOT(slot_load_script()));
140     QPushButton* load_button = new QPushButton("Load");
141     load_button->setToolTip(load_shortcut->key().toString(QKeySequence::NativeText));
142     connect(load_button, SIGNAL(clicked()), SLOT(slot_load_script()));
143 
144     // Help button.
145     QPushButton* help_button = new QPushButton("Help");
146     connect(help_button, SIGNAL(clicked()), SLOT(slot_show_help()));
147 
148     // Examples button.
149     QPushButton* examples_button = new QPushButton("Examples");
150     connect(examples_button, SIGNAL(clicked()), SLOT(slot_show_examples()));
151 
152     QHBoxLayout* file_buttonbox = new QHBoxLayout();
153     file_buttonbox->addWidget(clear_button);
154     file_buttonbox->addWidget(load_button);
155     file_buttonbox->addWidget(save_button);
156     file_buttonbox->addWidget(help_button);
157     file_buttonbox->addWidget(examples_button);
158     left_layout->addLayout(file_buttonbox);
159 
160     m_editor = new SeExprEditor(this, controls);
161     QTextEdit* text_edit = m_editor->findChild<QTextEdit*>("");
162     text_edit->setObjectName("expression_editor");
163     m_editor->setExpr(expression, true);
164     left_layout->addWidget(m_editor);
165 
166     m_error = new QLabel("Expression has errors. Check log for details.");
167     m_error->setObjectName("error");
168     m_error->hide();
169     left_layout->addWidget(m_error);
170 
171     // Expression browser.
172     m_browser = new SeExprEdBrowser(nullptr, m_editor);
173     const bf::path root_path(Application::get_root_path());
174     const string scripts_path = (root_path / "seexpr").string();
175     m_browser->addPath("Examples", scripts_path);
176     m_browser->update();
177     m_browser->hide();
178     right_layout->addWidget(m_browser);
179 
180     m_ui->buttonbox_layout->addStretch(1);
181     m_ui->buttonbox_layout->setStretch(0, 1);
182     m_ui->buttonbox_layout->setStretch(1, 0);
183 
184     connect(m_ui->buttonbox, SIGNAL(accepted()), SLOT(slot_accept()));
185     connect(m_ui->buttonbox, SIGNAL(rejected()), SLOT(slot_cancel()));
186 
187     connect(
188         m_ui->buttonbox->button(QDialogButtonBox::Apply), SIGNAL(clicked()),
189         SLOT(slot_apply()));
190 
191     connect(
192         create_window_local_shortcut(this, Qt::Key_Escape), SIGNAL(activated()),
193         SLOT(close()));
194 
195     WindowBase::load_settings();
196 }
197 
~ExpressionEditorWindow()198 ExpressionEditorWindow::~ExpressionEditorWindow()
199 {
200     delete m_ui;
201 }
202 
apply_expression()203 void ExpressionEditorWindow::apply_expression()
204 {
205     const string expression = m_editor->getExpr();
206     const SeExpression expr(expression);
207 
208     if (expr.isValid())
209     {
210         m_error->hide();
211         RENDERER_LOG_INFO("expression successfully applied.");
212         emit signal_expression_applied(m_widget_name, QString::fromStdString(expression));
213     }
214     else
215     {
216         m_error->show();
217         RENDERER_LOG_ERROR("expression error: %s", expr.parseError().c_str());
218     }
219 }
220 
slot_accept()221 void ExpressionEditorWindow::slot_accept()
222 {
223     apply_expression();
224     close();
225 }
226 
slot_apply()227 void ExpressionEditorWindow::slot_apply()
228 {
229     apply_expression();
230 }
231 
slot_cancel()232 void ExpressionEditorWindow::slot_cancel()
233 {
234     close();
235 }
236 
slot_clear_expression()237 void ExpressionEditorWindow::slot_clear_expression()
238 {
239     m_editor->setExpr(string());
240 }
241 
slot_save_script()242 void ExpressionEditorWindow::slot_save_script()
243 {
244     if (m_script_filepath.empty())
245     {
246         QString filepath =
247             get_save_filename(
248                 this,
249                 "Save As...",
250                 "Expression Scripts (*.se)",
251                 m_settings,
252                 SETTINGS_FILE_DIALOG_PROJECTS);
253 
254         if (!filepath.isEmpty())
255         {
256             if (QFileInfo(filepath).suffix().isEmpty())
257                 filepath += ".se";
258 
259             filepath = QDir::toNativeSeparators(filepath);
260             m_script_filepath = filepath.toStdString();
261         }
262     }
263 
264     if (!m_script_filepath.empty())
265     {
266         ofstream script_file(m_script_filepath.c_str());
267 
268         if (!script_file.is_open())
269         {
270             show_error_message_box(
271                 "Saving Error",
272                 "Failed to save the expression script file " + m_script_filepath + ".");
273             return;
274         }
275 
276         script_file << m_editor->getExpr();
277         script_file.close();
278     }
279 }
280 
slot_load_script()281 void ExpressionEditorWindow::slot_load_script()
282 {
283     QString filepath =
284         get_open_filename(
285             this,
286             "Open...",
287             "Expression Scripts (*.se);;All Files (*.*)",
288             m_settings,
289             SETTINGS_FILE_DIALOG_PROJECTS);
290 
291     if (!filepath.isEmpty())
292     {
293         filepath = QDir::toNativeSeparators(filepath);
294 
295         // Open script file.
296         ifstream script_file(filepath.toStdString().c_str());
297         if (!script_file.is_open())
298         {
299             show_error_message_box(
300                 "Loading Error",
301                 "Failed to load the expression script file " + filepath.toStdString() + ".");
302             return;
303         }
304 
305         // Read script file into memory.
306         stringstream script_buffer;
307         script_buffer << script_file.rdbuf();
308         script_file.close();
309 
310         // Set script as expression.
311         m_editor->setExpr(script_buffer.str());
312         apply_expression();
313     }
314 }
315 
slot_show_examples()316 void ExpressionEditorWindow::slot_show_examples()
317 {
318     m_show_examples = !m_show_examples;
319 
320     if (m_show_examples)
321     {
322         if (width() < 800)
323             resize(800, height());
324         m_ui->buttonbox_layout->setStretch(0, 1);
325         m_ui->buttonbox_layout->setStretch(1, 1);
326         m_browser->show();
327     }
328     else
329     {
330         if (width() > 400)
331             resize(max(400, width() - 400), height());
332         m_ui->buttonbox_layout->setStretch(1, 0);
333         m_browser->hide();
334     }
335 }
336 
slot_show_help()337 void ExpressionEditorWindow::slot_show_help()
338 {
339     bf::path docs_path = Application::get_root_path();
340     docs_path /= "docs/seexpr/userdoc.html";
341 
342     const QString docs_file = QString::fromStdString(docs_path.string());
343     QDesktopServices::openUrl(QUrl::fromLocalFile(docs_file));
344 }
345 
closeEvent(QCloseEvent * e)346 void ExpressionEditorWindow::closeEvent(QCloseEvent* e)
347 {
348     emit signal_editor_closed();
349     e->accept();
350 }
351 
352 }   // namespace studio
353 }   // namespace appleseed
354 
355 #include "mainwindow/project/moc_cpp_expressioneditorwindow.cxx"
356