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