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) 2010-2013 Francois Beaune, Jupiter Jazz Limited
9 // Copyright (c) 2014-2018 Francois Beaune, The appleseedhq Organization
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining a copy
12 // of this software and associated documentation files (the "Software"), to deal
13 // in the Software without restriction, including without limitation the rights
14 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 // copies of the Software, and to permit persons to whom the Software is
16 // furnished to do so, subject to the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be included in
19 // all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 // THE SOFTWARE.
28 //
29 
30 // XXX: must be included before Qt headers because of `slot' redefine
31 #include "mainwindow/pythonconsole/pythonconsolewidget.h"
32 
33 // Interface header.
34 #include "mainwindow.h"
35 
36 // UI definition header.
37 #include "ui_mainwindow.h"
38 
39 // appleseed.studio headers.
40 #include "help/about/aboutwindow.h"
41 #include "mainwindow/logwidget.h"
42 #include "mainwindow/minimizebutton.h"
43 #include "mainwindow/project/attributeeditor.h"
44 #include "mainwindow/project/projectexplorer.h"
45 #include "mainwindow/rendering/lightpathstab.h"
46 #include "mainwindow/rendering/materialdrophandler.h"
47 #include "mainwindow/rendering/renderwidget.h"
48 #include "utility/interop.h"
49 #include "utility/miscellaneous.h"
50 #include "utility/settingskeys.h"
51 
52 // appleseed.shared headers.
53 #include "application/application.h"
54 
55 // appleseed.renderer headers.
56 #include "renderer/api/aov.h"
57 #include "renderer/api/frame.h"
58 #include "renderer/api/lighting.h"
59 #include "renderer/api/log.h"
60 #include "renderer/api/postprocessing.h"
61 #include "renderer/api/project.h"
62 #include "renderer/api/rendering.h"
63 #include "renderer/api/surfaceshader.h"
64 
65 // appleseed.foundation headers.
66 #include "foundation/core/appleseed.h"
67 #include "foundation/math/aabb.h"
68 #include "foundation/math/vector.h"
69 #include "foundation/platform/compiler.h"
70 #include "foundation/platform/path.h"
71 #include "foundation/platform/python.h"
72 #include "foundation/platform/system.h"
73 #include "foundation/utility/containers/dictionary.h"
74 #include "foundation/utility/foreach.h"
75 #include "foundation/utility/log/logmessage.h"
76 
77 // Qt headers.
78 #include <QAction>
79 #include <QActionGroup>
80 #include <QApplication>
81 #include <QCloseEvent>
82 #include <QDir>
83 #include <QDragEnterEvent>
84 #include <QFileInfo>
85 #include <QFileSystemWatcher>
86 #include <QLabel>
87 #include <QLayout>
88 #include <QLineEdit>
89 #include <QMenu>
90 #include <QMessageBox>
91 #include <QMimeData>
92 #include <QRect>
93 #include <QSettings>
94 #include <QStatusBar>
95 #include <QString>
96 #include <QStringList>
97 #include <Qt>
98 #include <QUrl>
99 #include <QVariant>
100 
101 // Boost headers.
102 #include "boost/filesystem/path.hpp"
103 
104 // Standard headers.
105 #include <algorithm>
106 #include <cassert>
107 #include <cstdlib>
108 
109 using namespace appleseed::shared;
110 using namespace foundation;
111 using namespace renderer;
112 using namespace std;
113 namespace bf = boost::filesystem;
114 
115 namespace appleseed {
116 namespace studio {
117 
118 //
119 // MainWindow class implementation.
120 //
121 
122 namespace
123 {
124     const int MaxRecentlyOpenedFiles = 15;
125 }
126 
MainWindow(QWidget * parent)127 MainWindow::MainWindow(QWidget* parent)
128   : QMainWindow(parent)
129   , m_ui(new Ui::MainWindow())
130   , m_rendering_manager(m_status_bar)
131   , m_project_explorer(nullptr)
132   , m_attribute_editor(nullptr)
133   , m_project_file_watcher(nullptr)
134   , m_light_paths_tab(nullptr)
135 {
136     initialize_ocio();
137 
138     m_ui->setupUi(this);
139 
140     build_menus();
141     build_status_bar();
142     build_toolbar();
143     build_log_panel();
144     build_python_console_panel();
145     build_project_explorer();
146     build_connections();
147 
148     slot_load_application_settings();
149     slot_check_fullscreen();
150 
151     update_project_explorer();
152     update_workspace();
153 
154     setAcceptDrops(true);
155 }
156 
~MainWindow()157 MainWindow::~MainWindow()
158 {
159     delete m_project_explorer;
160     delete m_ui;
161 }
162 
163 namespace
164 {
165     class CustomSignalMapper
166       : public QObject
167     {
168         Q_OBJECT
169 
170       public:
CustomSignalMapper(QObject * parent,const QString & configuration)171         CustomSignalMapper(QObject* parent, const QString& configuration)
172           : QObject(parent)
173           , m_configuration(configuration)
174         {
175         }
176 
177       signals:
178         void mapped(const QString& filepath, const QString& config, const bool success);
179 
180       public slots:
map(const QString & filepath,const bool success)181         void map(const QString& filepath, const bool success)
182         {
183             emit mapped(filepath, m_configuration, success);
184         }
185 
186       private:
187         const QString m_configuration;
188     };
189 }
190 
new_project()191 void MainWindow::new_project()
192 {
193     m_project_manager.create_project();
194     on_project_change();
195 }
196 
open_project(const QString & filepath)197 bool MainWindow::open_project(const QString& filepath)
198 {
199     save_state_before_project_open();
200 
201     if (m_rendering_manager.is_rendering())
202     {
203         m_rendering_manager.abort_rendering();
204         m_rendering_manager.wait_until_rendering_end();
205     }
206 
207     remove_render_tabs();
208 
209     set_file_widgets_enabled(false, NotRendering);
210     set_project_explorer_enabled(false);
211     set_rendering_widgets_enabled(false, NotRendering);
212     set_diagnostics_widgets_enabled(false, NotRendering);
213 
214     const bool successful = m_project_manager.load_project(filepath.toUtf8().constData());
215 
216     if (successful)
217     {
218         on_project_change();
219     }
220     else
221     {
222         recreate_render_tabs();
223         update_workspace();
224     }
225 
226     return successful;
227 }
228 
open_project_async(const QString & filepath)229 void MainWindow::open_project_async(const QString& filepath)
230 {
231     save_state_before_project_open();
232 
233     if (m_rendering_manager.is_rendering())
234     {
235         m_rendering_manager.abort_rendering();
236         m_rendering_manager.wait_until_rendering_end();
237     }
238 
239     remove_render_tabs();
240 
241     set_file_widgets_enabled(false, NotRendering);
242     set_project_explorer_enabled(false);
243     set_rendering_widgets_enabled(false, NotRendering);
244     set_diagnostics_widgets_enabled(false, NotRendering);
245 
246     m_project_manager.load_project_async(filepath.toUtf8().constData());
247 }
248 
open_and_render_project(const QString & filepath,const QString & configuration)249 void MainWindow::open_and_render_project(const QString& filepath, const QString& configuration)
250 {
251     CustomSignalMapper* mapper = new CustomSignalMapper(this, configuration);
252 
253     connect(
254         &m_project_manager, SIGNAL(signal_load_project_async_complete(const QString&, const bool)),
255         mapper, SLOT(map(const QString&, const bool)));
256 
257     connect(
258         mapper, SIGNAL(mapped(const QString&, const QString&, const bool)),
259         SLOT(slot_start_rendering_once(const QString&, const QString&, const bool)));
260 
261     open_project_async(filepath);
262 }
263 
save_project(QString filepath)264 bool MainWindow::save_project(QString filepath)
265 {
266     if (!m_project_manager.is_project_open())
267         return false;
268 
269     const QString Extension = "appleseed";
270 
271     if (QFileInfo(filepath).suffix() != Extension)
272         filepath += "." + Extension;
273 
274     if (m_project_file_watcher)
275         stop_monitoring_project_file();
276 
277     const bool successful = m_project_manager.save_project_as(filepath.toUtf8().constData());
278 
279     if (m_project_file_watcher)
280         start_monitoring_project_file();
281 
282     if (successful)
283         update_recent_files_menu(filepath);
284     update_workspace();
285 
286     return successful;
287 }
288 
pack_project(QString filepath)289 bool MainWindow::pack_project(QString filepath)
290 {
291     if (!m_project_manager.is_project_open())
292         return false;
293 
294     const QString Extension = "appleseedz";
295 
296     if (QFileInfo(filepath).suffix() != Extension)
297         filepath += "." + Extension;
298 
299     return m_project_manager.pack_project_as(filepath.toUtf8().constData());
300 }
301 
close_project()302 void MainWindow::close_project()
303 {
304     m_project_manager.close_project();
305     on_project_change();
306 }
307 
get_project_manager()308 ProjectManager* MainWindow::get_project_manager()
309 {
310     return &m_project_manager;
311 }
312 
get_application_settings()313 ParamArray& MainWindow::get_application_settings()
314 {
315     return m_application_settings;
316 }
317 
create_dock_widget(const char * dock_name)318 QDockWidget* MainWindow::create_dock_widget(const char* dock_name)
319 {
320     QDockWidget* dock_widget = new QDockWidget(this);
321 
322     const QString object_name = QString(dock_name).toLower().split(' ').join("_");
323     dock_widget->setObjectName(object_name);
324     dock_widget->setWindowTitle(dock_name);
325 
326     const QList<QAction*> actions = m_ui->menu_view->actions();
327     QAction* menu_separator = actions.last();
328     for (int i = actions.size() - 2; i != 0; --i)
329     {
330         if (actions[i]->isSeparator())
331         {
332             menu_separator = actions[i];
333             break;
334         }
335     }
336 
337     m_ui->menu_view->insertAction(
338         menu_separator,
339         dock_widget->toggleViewAction());
340 
341     m_minimize_buttons.push_back(new MinimizeButton(dock_widget));
342 
343     statusBar()->insertPermanentWidget(
344         static_cast<int>(m_minimize_buttons.size()),
345         m_minimize_buttons.back());
346 
347     return dock_widget;
348 }
349 
build_menus()350 void MainWindow::build_menus()
351 {
352     //
353     // File menu.
354     //
355 
356     m_ui->action_file_new_project->setShortcut(QKeySequence::New);
357     connect(m_ui->action_file_new_project, SIGNAL(triggered()), SLOT(slot_new_project()));
358 
359     m_ui->action_file_open_project->setShortcut(QKeySequence::Open);
360     connect(m_ui->action_file_open_project, SIGNAL(triggered()), SLOT(slot_open_project()));
361 
362     build_recent_files_menu();
363 
364     connect(m_ui->action_file_open_builtin_project_cornellbox, SIGNAL(triggered()), SLOT(slot_open_cornellbox_builtin_project()));
365     connect(m_ui->action_file_reload_project, SIGNAL(triggered()), SLOT(slot_reload_project()));
366 
367     connect(m_ui->action_file_monitor_project, SIGNAL(toggled(bool)), SLOT(slot_toggle_project_file_monitoring(const bool)));
368 
369     m_ui->action_file_save_project->setShortcut(QKeySequence::Save);
370     connect(m_ui->action_file_save_project, SIGNAL(triggered()), SLOT(slot_save_project()));
371 
372     m_ui->action_file_save_project_as->setShortcut(QKeySequence::SaveAs);
373     connect(m_ui->action_file_save_project_as, SIGNAL(triggered()), SLOT(slot_save_project_as()));
374 
375     connect(m_ui->action_file_pack_project_as, SIGNAL(triggered()), SLOT(slot_pack_project_as()));
376 
377     m_ui->action_file_close_project->setShortcut(QKeySequence::Close);
378     connect(m_ui->action_file_close_project, SIGNAL(triggered()), SLOT(slot_close_project()));
379 
380     m_ui->action_file_exit->setShortcut(QKeySequence::Quit);
381     connect(m_ui->action_file_exit, SIGNAL(triggered()), SLOT(close()));
382 
383     //
384     // View menu.
385     //
386 
387     m_ui->menu_view->addAction(m_ui->project_explorer->toggleViewAction());
388     m_ui->menu_view->addAction(m_ui->attribute_editor->toggleViewAction());
389     m_ui->menu_view->addAction(m_ui->log->toggleViewAction());
390     m_ui->menu_view->addAction(m_ui->python_console->toggleViewAction());
391     m_ui->menu_view->addSeparator();
392 
393     m_action_fullscreen = m_ui->menu_view->addAction("Fullscreen");
394     m_action_fullscreen->setCheckable(true);
395     m_action_fullscreen->setShortcut(Qt::Key_F11);
396 
397     for (const auto dock_widget : findChildren<QDockWidget*>())
398         connect(dock_widget->toggleViewAction(), SIGNAL(triggered()), SLOT(slot_check_fullscreen()));
399 
400     connect(m_action_fullscreen, SIGNAL(triggered()), SLOT(slot_fullscreen()));
401 
402     //
403     // Rendering menu.
404     //
405 
406     connect(m_ui->action_rendering_start_interactive_rendering, SIGNAL(triggered()), SLOT(slot_start_interactive_rendering()));
407     connect(m_ui->action_rendering_start_final_rendering, SIGNAL(triggered()), SLOT(slot_start_final_rendering()));
408     connect(m_ui->action_rendering_pause_resume_rendering, SIGNAL(toggled(bool)), SLOT(slot_pause_or_resume_rendering(const bool)));
409     connect(m_ui->action_rendering_stop_rendering, SIGNAL(triggered()), &m_rendering_manager, SLOT(slot_abort_rendering()));
410     connect(m_ui->action_rendering_rendering_settings, SIGNAL(triggered()), SLOT(slot_show_rendering_settings_window()));
411 
412     //
413     // Diagnostics menu.
414     //
415 
416     build_override_shading_menu_item();
417 
418     connect(m_ui->action_diagnostics_false_colors, SIGNAL(triggered()), SLOT(slot_show_false_colors_window()));
419 
420     //
421     // Debug menu.
422     //
423 
424     connect(m_ui->action_debug_tests, SIGNAL(triggered()), SLOT(slot_show_test_window()));
425     connect(m_ui->action_debug_benchmarks, SIGNAL(triggered()), SLOT(slot_show_benchmark_window()));
426 
427     //
428     // Tools menu.
429     //
430 
431     connect(m_ui->action_tools_settings, SIGNAL(triggered()), SLOT(slot_show_application_settings_window()));
432     connect(m_ui->action_tools_save_settings, SIGNAL(triggered()), SLOT(slot_save_application_settings()));
433     connect(m_ui->action_tools_reload_settings, SIGNAL(triggered()), SLOT(slot_load_application_settings()));
434 
435     //
436     // Help menu.
437     //
438 
439     connect(m_ui->action_help_about, SIGNAL(triggered()), SLOT(slot_show_about_window()));
440 }
441 
build_override_shading_menu_item()442 void MainWindow::build_override_shading_menu_item()
443 {
444     QActionGroup* action_group = new QActionGroup(this);
445 
446     // No Override.
447     connect(
448         m_ui->action_diagnostics_override_shading_no_override, SIGNAL(triggered()),
449         SLOT(slot_clear_shading_override()));
450     action_group->addAction(m_ui->action_diagnostics_override_shading_no_override);
451 
452     for (int i = 0; i < DiagnosticSurfaceShader::ShadingModeCount; ++i)
453     {
454         const char* shading_mode_value = DiagnosticSurfaceShader::ShadingModeNames[i].m_key;
455         const char* shading_mode_name = DiagnosticSurfaceShader::ShadingModeNames[i].m_value;
456 
457         QAction* action = new QAction(this);
458         action->setObjectName(
459             QString::fromUtf8("action_diagnostics_override_shading_") + shading_mode_value);
460         action->setCheckable(true);
461         action->setText(shading_mode_name);
462 
463         const int shortcut_number = i + 1;
464         if (shortcut_number <= 9)
465         {
466             action->setShortcut(
467                 QKeySequence(QString::fromUtf8("Ctrl+Shift+%1").arg(shortcut_number)));
468         }
469 
470         action->setData(shading_mode_value);
471 
472         connect(
473             action, SIGNAL(triggered()),
474             SLOT(slot_set_shading_override()));
475 
476         m_ui->menu_diagnostics_override_shading->addAction(action);
477         action_group->addAction(action);
478     }
479 }
480 
update_override_shading_menu_item()481 void MainWindow::update_override_shading_menu_item()
482 {
483     const ParamArray project_params = get_project_params("interactive");
484     const ParamArray shading_engine_params = project_params.child("shading_engine");
485 
486     if (shading_engine_params.dictionaries().exist("override_shading"))
487     {
488         const string shading_mode =
489             shading_engine_params.child("override_shading").get_optional<string>("mode", "coverage");
490 
491         for (const_each<QList<QAction*>> i = m_ui->menu_diagnostics_override_shading->actions(); i; ++i)
492         {
493             QAction* action = *i;
494 
495             if (action->data().toString().toStdString() == shading_mode)
496             {
497                 action->activate(QAction::Trigger);
498                 break;
499             }
500         }
501     }
502     else
503     {
504         m_ui->action_diagnostics_override_shading_no_override->activate(QAction::Trigger);
505     }
506 }
507 
build_recent_files_menu()508 void MainWindow::build_recent_files_menu()
509 {
510     assert(m_recently_opened.empty());
511     m_recently_opened.reserve(MaxRecentlyOpenedFiles);
512 
513     for (int i = 0; i < MaxRecentlyOpenedFiles; ++i)
514     {
515         QAction* action = new QAction(this);
516         action->setVisible(false);
517 
518         connect(action, SIGNAL(triggered()), SLOT(slot_open_recent()));
519 
520         m_ui->menu_open_recent->addAction(action);
521         m_recently_opened.push_back(action);
522     }
523 
524     QSettings settings(SETTINGS_ORGANIZATION, SETTINGS_APPLICATION);
525     QStringList files = settings.value("recent_file_list").toStringList();
526 
527     update_recent_files_menu(files);
528 
529     m_ui->menu_open_recent->addSeparator();
530 
531     QAction* clear_missing_files = new QAction(this);
532     clear_missing_files->setText("Clear &Missing Files");
533     connect(clear_missing_files, SIGNAL(triggered()), SLOT(slot_clear_recent_missing_project_files()));
534     m_ui->menu_open_recent->addAction(clear_missing_files);
535 
536     QAction* clear_all_files = new QAction(this);
537     clear_all_files->setText("Clear &All Files");
538     connect(clear_all_files, SIGNAL(triggered()), SLOT(slot_clear_all_recent_project_files()));
539     m_ui->menu_open_recent->addAction(clear_all_files);
540 }
541 
update_recent_files_menu(const QString & filepath)542 void MainWindow::update_recent_files_menu(const QString& filepath)
543 {
544     QSettings settings(SETTINGS_ORGANIZATION, SETTINGS_APPLICATION);
545     QStringList files = settings.value("recent_file_list").toStringList();
546 
547     files.removeAll(filepath);
548     files.prepend(filepath);
549 
550     while (files.size() > MaxRecentlyOpenedFiles)
551         files.removeLast();
552 
553     update_recent_files_menu(files);
554 
555     settings.setValue("recent_file_list", files);
556 }
557 
update_recent_files_menu(const QStringList & files)558 void MainWindow::update_recent_files_menu(const QStringList& files)
559 {
560     const int recent_file_count = min(files.size(), MaxRecentlyOpenedFiles);
561 
562     for (int i = 0; i < recent_file_count; ++i)
563     {
564         const int number = i + 1;
565         const QString filepath = files[i];
566         const QString format = number <= 9 ? "&%1 %2" : "%1 %2";
567         const QString text = format.arg(number).arg(filepath);
568 
569         m_recently_opened[i]->setText(text);
570         m_recently_opened[i]->setData(filepath);
571         m_recently_opened[i]->setVisible(true);
572     }
573 
574     for (int i = recent_file_count; i < MaxRecentlyOpenedFiles; ++i)
575         m_recently_opened[i]->setVisible(false);
576 }
577 
update_pause_resume_checkbox(const bool checked)578 void MainWindow::update_pause_resume_checkbox(const bool checked)
579 {
580     bool old_state = m_action_pause_resume_rendering->blockSignals(true);
581     m_action_pause_resume_rendering->setChecked(checked);
582     m_action_pause_resume_rendering->blockSignals(old_state);
583 
584     old_state = m_ui->action_rendering_pause_resume_rendering->blockSignals(true);
585     m_ui->action_rendering_pause_resume_rendering->setChecked(checked);
586     m_ui->action_rendering_pause_resume_rendering->blockSignals(old_state);
587 }
588 
build_status_bar()589 void MainWindow::build_status_bar()
590 {
591     statusBar()->addWidget(&m_status_bar);
592 
593     m_minimize_buttons.push_back(new MinimizeButton(m_ui->project_explorer));
594     m_minimize_buttons.push_back(new MinimizeButton(m_ui->attribute_editor));
595     m_minimize_buttons.push_back(new MinimizeButton(m_ui->log));
596     m_minimize_buttons.push_back(new MinimizeButton(m_ui->python_console));
597 
598     for (size_t i = 0; i < m_minimize_buttons.size(); ++i)
599     {
600         statusBar()->insertPermanentWidget(
601             static_cast<int>(i + 1),
602             m_minimize_buttons[i]);
603     }
604 }
605 
build_toolbar()606 void MainWindow::build_toolbar()
607 {
608     //
609     // File actions.
610     //
611 
612     m_action_new_project = new QAction(load_icons("project_new"), combine_name_and_shortcut("New Project", m_ui->action_file_new_project->shortcut()), this);
613     connect(m_action_new_project, SIGNAL(triggered()), SLOT(slot_new_project()));
614     m_ui->main_toolbar->addAction(m_action_new_project);
615 
616     m_action_open_project = new QAction(load_icons("project_open"), combine_name_and_shortcut("Open Project...", m_ui->action_file_open_project->shortcut()), this);
617     connect(m_action_open_project, SIGNAL(triggered()), SLOT(slot_open_project()));
618     m_ui->main_toolbar->addAction(m_action_open_project);
619 
620     m_action_save_project = new QAction(load_icons("project_save") , combine_name_and_shortcut("Save Project", m_ui->action_file_save_project->shortcut()), this);
621     connect(m_action_save_project, SIGNAL(triggered()), SLOT(slot_save_project()));
622     m_ui->main_toolbar->addAction(m_action_save_project);
623 
624     m_action_reload_project = new QAction(load_icons("project_reload"), combine_name_and_shortcut("Reload Project", m_ui->action_file_reload_project->shortcut()), this);
625     connect(m_action_reload_project, SIGNAL(triggered()), SLOT(slot_reload_project()));
626     m_ui->main_toolbar->addAction(m_action_reload_project);
627 
628     m_action_monitor_project_file = new QAction(load_icons("project_monitor"), "Toggle Project File Monitoring", this);
629     m_action_monitor_project_file->setCheckable(true);
630     connect(m_action_monitor_project_file, SIGNAL(toggled(bool)), SLOT(slot_toggle_project_file_monitoring(const bool)));
631     m_ui->main_toolbar->addAction(m_action_monitor_project_file);
632 
633     m_ui->main_toolbar->addSeparator();
634 
635     //
636     // Rendering actions.
637     //
638 
639     m_action_start_interactive_rendering = new QAction(load_icons("rendering_start_interactive"), combine_name_and_shortcut("Start Interactive Rendering", m_ui->action_rendering_start_interactive_rendering->shortcut()), this);
640     connect(m_action_start_interactive_rendering, SIGNAL(triggered()), SLOT(slot_start_interactive_rendering()));
641     m_ui->main_toolbar->addAction(m_action_start_interactive_rendering);
642 
643     m_action_start_final_rendering = new QAction(load_icons("rendering_start_final"), combine_name_and_shortcut("Start Final Rendering", m_ui->action_rendering_start_final_rendering->shortcut()), this);
644     connect(m_action_start_final_rendering, SIGNAL(triggered()), SLOT(slot_start_final_rendering()));
645     m_ui->main_toolbar->addAction(m_action_start_final_rendering);
646 
647     m_action_pause_resume_rendering = new QAction(load_icons("rendering_pause_resume"), combine_name_and_shortcut("Pause/Resume Rendering", m_ui->action_rendering_pause_resume_rendering->shortcut()), this);
648     m_action_pause_resume_rendering->setCheckable(true);
649     connect(m_action_pause_resume_rendering, SIGNAL(toggled(bool)), SLOT(slot_pause_or_resume_rendering(const bool)));
650     m_ui->main_toolbar->addAction(m_action_pause_resume_rendering);
651 
652     m_action_stop_rendering = new QAction(load_icons("rendering_stop"), combine_name_and_shortcut("Stop Rendering", m_ui->action_rendering_stop_rendering->shortcut()), this);
653     connect(m_action_stop_rendering, SIGNAL(triggered()), &m_rendering_manager, SLOT(slot_abort_rendering()));
654     m_ui->main_toolbar->addAction(m_action_stop_rendering);
655 
656     m_action_rendering_settings = new QAction(load_icons("rendering_settings"), combine_name_and_shortcut("Rendering Settings...", m_ui->action_rendering_rendering_settings->shortcut()), this);
657     connect(m_action_rendering_settings, SIGNAL(triggered()), SLOT(slot_show_rendering_settings_window()));
658     m_ui->main_toolbar->addAction(m_action_rendering_settings);
659 }
660 
build_log_panel()661 void MainWindow::build_log_panel()
662 {
663     LogWidget* log_widget = new LogWidget(m_ui->log_contents);
664     log_widget->setObjectName("textedit_log");
665     log_widget->setUndoRedoEnabled(false);
666     log_widget->setLineWrapMode(QTextEdit::NoWrap);
667     log_widget->setReadOnly(true);
668     log_widget->setTextInteractionFlags(Qt::TextSelectableByMouse);
669     m_ui->log_contents->layout()->addWidget(log_widget);
670 
671     m_log_target.reset(new QtLogTarget(log_widget));
672     global_logger().add_target(m_log_target.get());
673 
674     RENDERER_LOG_INFO(
675         "%s, %s configuration\n"
676         "compiled on %s at %s using %s version %s",
677         Appleseed::get_synthetic_version_string(),
678         Appleseed::get_lib_configuration(),
679         Appleseed::get_lib_compilation_date(),
680         Appleseed::get_lib_compilation_time(),
681         Compiler::get_compiler_name(),
682         Compiler::get_compiler_version());
683 
684     System::print_information(global_logger());
685 }
686 
build_python_console_panel()687 void MainWindow::build_python_console_panel()
688 {
689     char* python_home = Py_EncodeLocale(Py_GetPythonHome(), nullptr);
690     if (python_home == nullptr)
691         RENDERER_LOG_INFO("Python home not set.");
692     else RENDERER_LOG_INFO("Python home set to %s.", python_home);
693 
694     PythonConsoleWidget* python_console_widget = new PythonConsoleWidget(m_ui->python_console_contents);
695     python_console_widget->setObjectName("textedit_python_console");
696     m_ui->python_console_contents->layout()->addWidget(python_console_widget);
697 }
698 
build_project_explorer()699 void MainWindow::build_project_explorer()
700 {
701     m_ui->treewidget_project_explorer_scene->setColumnWidth(0, 295);    // name
702 
703     disable_osx_focus_rect(m_ui->treewidget_project_explorer_scene);
704 
705     connect(
706         m_ui->lineedit_filter, SIGNAL(textChanged(const QString&)),
707         SLOT(slot_filter_text_changed(const QString&)));
708 
709     connect(
710         m_ui->pushbutton_clear_filter, SIGNAL(clicked()),
711         SLOT(slot_clear_filter()));
712 
713     m_ui->pushbutton_clear_filter->setEnabled(false);
714 }
715 
build_connections()716 void MainWindow::build_connections()
717 {
718     connect(
719         m_action_monitor_project_file, SIGNAL(toggled(bool)),
720         m_ui->action_file_monitor_project, SLOT(setChecked(bool)));
721 
722     connect(
723         m_ui->action_file_monitor_project, SIGNAL(toggled(bool)),
724         m_action_monitor_project_file, SLOT(setChecked(bool)));
725 
726     connect(
727         &m_project_manager, SIGNAL(signal_load_project_async_complete(const QString&, const bool)),
728         SLOT(slot_open_project_complete(const QString&, const bool)));
729 
730     connect(
731         &m_rendering_manager, SIGNAL(signal_rendering_end()),
732         SLOT(slot_rendering_end()));
733 }
734 
update_workspace()735 void MainWindow::update_workspace()
736 {
737     update_window_title();
738 
739     // Enable/disable menus and widgets appropriately.
740     set_file_widgets_enabled(true, NotRendering);
741     set_project_explorer_enabled(true);
742     set_rendering_widgets_enabled(true, NotRendering);
743     set_diagnostics_widgets_enabled(true, NotRendering);
744     update_pause_resume_checkbox(false);
745     m_ui->attribute_editor_scrollarea_contents->setEnabled(true);
746 
747     // Add/remove light paths tab.
748     if (m_project_manager.is_project_open() &&
749         m_project_manager.get_project()->get_light_path_recorder().get_light_path_count() > 0)
750         add_light_paths_tab();
751     else remove_light_paths_tab();
752 }
753 
update_project_explorer()754 void MainWindow::update_project_explorer()
755 {
756     delete m_project_explorer;
757     m_project_explorer = nullptr;
758 
759     delete m_attribute_editor;
760     m_attribute_editor = nullptr;
761 
762     if (m_project_manager.is_project_open())
763     {
764         m_attribute_editor =
765             new AttributeEditor(
766                 m_ui->attribute_editor_scrollarea_contents,
767                 *m_project_manager.get_project(),
768                 m_application_settings);
769 
770         m_project_explorer =
771             new ProjectExplorer(
772                 m_ui->treewidget_project_explorer_scene,
773                 m_attribute_editor,
774                 *m_project_manager.get_project(),
775                 m_project_manager,
776                 m_rendering_manager,
777                 m_application_settings);
778 
779         connect(
780             m_project_explorer, SIGNAL(signal_project_modified()),
781             SLOT(slot_project_modified()));
782 
783         connect(
784             m_project_explorer, SIGNAL(signal_frame_modified()),
785             SLOT(slot_frame_modified()));
786     }
787 
788     m_ui->lineedit_filter->clear();
789 }
790 
update_window_title()791 void MainWindow::update_window_title()
792 {
793     QString title;
794 
795     if (m_project_manager.is_project_open())
796     {
797         if (m_project_manager.is_project_dirty())
798             title.append("* ");
799 
800         title.append(QString::fromStdString(m_project_manager.get_project_display_name()));
801         title.append(" - ");
802     }
803 
804     title.append("appleseed.studio");
805 
806     setWindowTitle(title);
807 }
808 
set_file_widgets_enabled(const bool is_enabled,const RenderingMode rendering_mode)809 void MainWindow::set_file_widgets_enabled(const bool is_enabled, const RenderingMode rendering_mode)
810 {
811     const bool is_project_open = m_project_manager.is_project_open();
812     const bool project_has_path = is_project_open && m_project_manager.get_project()->has_path();
813 
814     // File -> New Project.
815     m_ui->action_file_new_project->setEnabled(is_enabled);
816     m_action_new_project->setEnabled(is_enabled);
817 
818     // File -> Open Project.
819     m_ui->action_file_open_project->setEnabled(is_enabled);
820     m_action_open_project->setEnabled(is_enabled);
821 
822     // File -> Open Recent.
823     m_ui->menu_open_recent->setEnabled(is_enabled);
824 
825     // File -> Open Built-in Project.
826     m_ui->menu_file_open_builtin_project->setEnabled(is_enabled);
827 
828     // File -> Reload Project.
829     const bool allow_reload = (is_enabled || rendering_mode == InteractiveRendering) && project_has_path;
830     m_ui->action_file_reload_project->setEnabled(allow_reload);
831     m_action_reload_project->setEnabled(allow_reload);
832 
833     // File -> Monitor Project.
834     const bool allow_monitor = (is_enabled || rendering_mode == InteractiveRendering) && project_has_path;
835     m_ui->action_file_monitor_project->setEnabled(allow_monitor);
836     m_action_monitor_project_file->setEnabled(allow_monitor);
837 
838     // File -> Save Project, Save Project As and Pack Project As.
839     const bool allow_save = is_enabled && is_project_open;
840     m_ui->action_file_save_project->setEnabled(allow_save);
841     m_action_save_project->setEnabled(allow_save);
842     m_ui->action_file_save_project_as->setEnabled(allow_save);
843     m_ui->action_file_pack_project_as->setEnabled(allow_save);
844 
845     // File -> Close Project.
846     const bool allow_close = is_enabled && is_project_open;
847     m_ui->action_file_close_project->setEnabled(allow_close);
848 
849     // File -> Exit.
850     m_ui->action_file_exit->setEnabled(is_enabled);
851 }
852 
set_project_explorer_enabled(const bool is_enabled)853 void MainWindow::set_project_explorer_enabled(const bool is_enabled)
854 {
855     const bool is_project_open = m_project_manager.is_project_open();
856 
857     m_ui->label_filter->setEnabled(is_enabled && is_project_open);
858     m_ui->lineedit_filter->setEnabled(is_enabled && is_project_open);
859     m_ui->pushbutton_clear_filter->setEnabled(is_enabled && is_project_open && !m_ui->lineedit_filter->text().isEmpty());
860     m_ui->treewidget_project_explorer_scene->setEnabled(is_enabled && is_project_open);
861 }
862 
set_rendering_widgets_enabled(const bool is_enabled,const RenderingMode rendering_mode)863 void MainWindow::set_rendering_widgets_enabled(const bool is_enabled, const RenderingMode rendering_mode)
864 {
865     const bool is_project_open = m_project_manager.is_project_open();
866     const bool allow_start = is_enabled && is_project_open && rendering_mode == NotRendering;
867     const bool allow_stop = is_enabled && is_project_open && rendering_mode != NotRendering;
868 
869     // Rendering -> Rendering Settings.
870     m_ui->action_rendering_rendering_settings->setEnabled(allow_start);
871     m_action_rendering_settings->setEnabled(allow_start);
872 
873     // Rendering -> Start Interactive Rendering.
874     m_ui->action_rendering_start_interactive_rendering->setEnabled(allow_start);
875     m_action_start_interactive_rendering->setEnabled(allow_start);
876 
877     // Rendering -> Start Final Rendering.
878     m_ui->action_rendering_start_final_rendering->setEnabled(allow_start);
879     m_action_start_final_rendering->setEnabled(allow_start);
880 
881     // Rendering -> Pause/Resume Rendering.
882     m_ui->action_rendering_pause_resume_rendering->setEnabled(allow_stop);
883     m_action_pause_resume_rendering->setEnabled(allow_stop);
884 
885     // Rendering -> Stop Rendering.
886     m_ui->action_rendering_stop_rendering->setEnabled(allow_stop);
887     m_action_stop_rendering->setEnabled(allow_stop);
888 
889     // Rendering -> Render Settings.
890     m_ui->action_rendering_rendering_settings->setEnabled(allow_start);
891 
892     // Render tab buttons.
893     const int current_tab_index = m_ui->tab_render_channels->currentIndex();
894     if (current_tab_index != -1)
895     {
896         const auto render_tab_it = m_tab_index_to_render_tab.find(current_tab_index);
897         if (render_tab_it != m_tab_index_to_render_tab.end())
898         {
899             RenderTab* render_tab = render_tab_it->second;
900 
901             // Clear frame.
902             render_tab->set_clear_frame_button_enabled(
903                 is_enabled && is_project_open && rendering_mode == NotRendering);
904 
905             // Set/clear rendering region.
906             render_tab->set_render_region_buttons_enabled(
907                 is_enabled && is_project_open && rendering_mode != FinalRendering);
908 
909             // Scene picker.
910             render_tab->get_scene_picking_handler()->set_enabled(
911                 is_enabled && is_project_open && rendering_mode != FinalRendering);
912         }
913     }
914 }
915 
set_diagnostics_widgets_enabled(const bool is_enabled,const RenderingMode rendering_mode)916 void MainWindow::set_diagnostics_widgets_enabled(const bool is_enabled, const RenderingMode rendering_mode)
917 {
918     const bool is_project_open = m_project_manager.is_project_open();
919 
920     m_ui->menu_diagnostics_override_shading->setEnabled(is_enabled && is_project_open);
921     m_ui->action_diagnostics_false_colors->setEnabled(is_enabled && is_project_open && rendering_mode == NotRendering);
922 }
923 
save_state_before_project_open()924 void MainWindow::save_state_before_project_open()
925 {
926     m_state_before_project_open.reset(new StateBeforeProjectOpen());
927 
928     m_state_before_project_open->m_is_rendering = m_rendering_manager.is_rendering();
929 
930     for (const_each<RenderTabCollection> i = m_render_tabs; i; ++i)
931         m_state_before_project_open->m_render_tab_states[i->first] = i->second->save_state();
932 }
933 
restore_state_after_project_open()934 void MainWindow::restore_state_after_project_open()
935 {
936     if (m_state_before_project_open.get())
937     {
938         for (const_each<RenderTabCollection> i = m_render_tabs; i; ++i)
939         {
940             const RenderTabStateCollection& tab_states = m_state_before_project_open->m_render_tab_states;
941             const RenderTabStateCollection::const_iterator tab_state_it = tab_states.find(i->first);
942 
943             if (tab_state_it != tab_states.end())
944                 i->second->load_state(tab_state_it->second);
945         }
946 
947         if (m_state_before_project_open->m_is_rendering)
948             start_rendering(InteractiveRendering);
949     }
950 }
951 
recreate_render_tabs()952 void MainWindow::recreate_render_tabs()
953 {
954     remove_render_tabs();
955 
956     if (m_project_manager.is_project_open())
957         add_render_tab("RGB");
958 }
959 
remove_render_tabs()960 void MainWindow::remove_render_tabs()
961 {
962     for (const_each<RenderTabCollection> i = m_render_tabs; i; ++i)
963         delete i->second;
964 
965     m_render_tabs.clear();
966     m_tab_index_to_render_tab.clear();
967 
968     while (m_ui->tab_render_channels->count() > 0)
969         m_ui->tab_render_channels->removeTab(0);
970 }
971 
add_render_tab(const QString & label)972 void MainWindow::add_render_tab(const QString& label)
973 {
974     // Create render tab.
975     RenderTab* render_tab =
976         new RenderTab(
977             *m_project_explorer,
978             *m_project_manager.get_project(),
979             m_rendering_manager,
980             m_ocio_config);
981 
982     // Connect the render tab to the main window and the rendering manager.
983     connect(
984         render_tab, SIGNAL(signal_render_widget_context_menu(const QPoint&)),
985         SLOT(slot_render_widget_context_menu(const QPoint&)));
986     connect(
987         render_tab, SIGNAL(signal_set_render_region(const QRect&)),
988         SLOT(slot_set_render_region(const QRect&)));
989     connect(
990         render_tab, SIGNAL(signal_clear_render_region()),
991         SLOT(slot_clear_render_region()));
992     connect(
993         render_tab, SIGNAL(signal_save_frame_and_aovs()),
994         SLOT(slot_save_frame_and_aovs()));
995     connect(
996         render_tab, SIGNAL(signal_quicksave_frame_and_aovs()),
997         SLOT(slot_quicksave_frame_and_aovs()));
998     connect(
999         render_tab, SIGNAL(signal_reset_zoom()),
1000         SLOT(slot_reset_zoom()));
1001     connect(
1002         render_tab, SIGNAL(signal_clear_frame()),
1003         SLOT(slot_clear_frame()));
1004     connect(
1005         render_tab, SIGNAL(signal_entity_picked(renderer::ScenePicker::PickingResult)),
1006         SLOT(slot_clear_filter()));
1007     connect(
1008         render_tab, SIGNAL(signal_camera_change_begin()),
1009         &m_rendering_manager, SLOT(slot_camera_change_begin()));
1010     connect(
1011         render_tab, SIGNAL(signal_camera_change_end()),
1012         &m_rendering_manager, SLOT(slot_camera_change_end()));
1013     connect(
1014         render_tab, SIGNAL(signal_camera_changed()),
1015         &m_rendering_manager, SLOT(slot_camera_changed()));
1016 
1017     // Add the render tab to the tab bar.
1018     const int tab_index = m_ui->tab_render_channels->addTab(render_tab, label);
1019 
1020     // Update mappings.
1021     m_render_tabs[label.toStdString()] = render_tab;
1022     m_tab_index_to_render_tab[tab_index] = render_tab;
1023 }
1024 
add_light_paths_tab()1025 void MainWindow::add_light_paths_tab()
1026 {
1027     if (m_light_paths_tab == nullptr)
1028     {
1029         // Create light paths tab.
1030         m_light_paths_tab =
1031             new LightPathsTab(
1032                 *m_project_manager.get_project(),
1033                 m_application_settings);
1034 
1035         // Connect render tabs to the light paths tab.
1036         for (const auto& kv : m_render_tabs)
1037         {
1038             connect(
1039                 kv.second, SIGNAL(signal_entity_picked(renderer::ScenePicker::PickingResult)),
1040                 m_light_paths_tab, SLOT(slot_entity_picked(renderer::ScenePicker::PickingResult)));
1041             connect(
1042                 kv.second, SIGNAL(signal_rectangle_selection(const QRect&)),
1043                 m_light_paths_tab, SLOT(slot_rectangle_selection(const QRect&)));
1044         }
1045 
1046         // Add the light paths tab to the tab bar.
1047         m_ui->tab_render_channels->addTab(m_light_paths_tab, "Light Paths");
1048     }
1049 }
1050 
remove_light_paths_tab()1051 void MainWindow::remove_light_paths_tab()
1052 {
1053     if (m_light_paths_tab != nullptr)
1054     {
1055         delete m_light_paths_tab;
1056         m_light_paths_tab = nullptr;
1057     }
1058 }
1059 
get_project_params(const char * configuration_name) const1060 ParamArray MainWindow::get_project_params(const char* configuration_name) const
1061 {
1062     ParamArray params;
1063 
1064     Configuration* configuration =
1065         m_project_manager.is_project_open()
1066             ? m_project_manager.get_project()->configurations().get_by_name(configuration_name)
1067             : nullptr;
1068 
1069     // Start with the parameters from the base configuration.
1070     if (configuration && configuration->get_base())
1071         params = configuration->get_base()->get_parameters();
1072 
1073     // Override with application settings.
1074     params.merge(m_application_settings);
1075 
1076     // Override with parameters from the configuration.
1077     if (configuration)
1078         params.merge(configuration->get_parameters());
1079 
1080     return params;
1081 }
1082 
1083 namespace
1084 {
show_modified_project_message_box(QWidget * parent)1085     int show_modified_project_message_box(QWidget* parent)
1086     {
1087         QMessageBox msgbox(parent);
1088         msgbox.setWindowTitle("Save Changes?");
1089         msgbox.setIcon(QMessageBox::Question);
1090         msgbox.setText("The project has been modified.");
1091         msgbox.setInformativeText("Do you want to save your changes?");
1092         msgbox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
1093         msgbox.setDefaultButton(QMessageBox::Save);
1094         return msgbox.exec();
1095     }
1096 }
1097 
can_close_project()1098 bool MainWindow::can_close_project()
1099 {
1100     // Project being loaded: can't close.
1101     if (m_project_manager.is_project_loading())
1102         return false;
1103 
1104     // No project open: no problem.
1105     if (!m_project_manager.is_project_open())
1106         return true;
1107 
1108     // Unmodified project: no problem.
1109     if (!m_project_manager.is_project_dirty())
1110         return true;
1111 
1112     // The current project has been modified, ask the user what to do.
1113     switch (show_modified_project_message_box(this))
1114     {
1115       case QMessageBox::Save:
1116         slot_save_project();
1117         return true;
1118 
1119       case QMessageBox::Discard:
1120         return true;
1121 
1122       case QMessageBox::Cancel:
1123         return false;
1124     }
1125 
1126     assert(!"Should never be reached.");
1127     return false;
1128 }
1129 
on_project_change()1130 void MainWindow::on_project_change()
1131 {
1132     update_project_explorer();
1133     recreate_render_tabs();
1134 
1135     update_override_shading_menu_item();
1136     m_false_colors_window.reset();
1137 
1138     if (m_rendering_settings_window.get() != nullptr &&
1139         m_project_manager.get_project() != nullptr)
1140         m_rendering_settings_window->reload();
1141 
1142     m_status_bar.clear();
1143 
1144     update_workspace();
1145 
1146     restore_state_after_project_open();
1147 
1148     if (m_project_file_watcher)
1149         start_monitoring_project_file();
1150 }
1151 
enable_project_file_monitoring()1152 void MainWindow::enable_project_file_monitoring()
1153 {
1154     if (m_project_file_watcher == nullptr)
1155     {
1156         m_project_file_watcher = new QFileSystemWatcher(this);
1157 
1158         connect(
1159             m_project_file_watcher,
1160             SIGNAL(fileChanged(const QString&)),
1161             SLOT(slot_project_file_changed(const QString&)));
1162 
1163         RENDERER_LOG_INFO("project file monitoring is now enabled.");
1164 
1165         start_monitoring_project_file();
1166     }
1167 }
1168 
disable_project_file_monitoring()1169 void MainWindow::disable_project_file_monitoring()
1170 {
1171     if (m_project_file_watcher)
1172     {
1173         delete m_project_file_watcher;
1174         m_project_file_watcher = nullptr;
1175 
1176         RENDERER_LOG_INFO("project file monitoring is now disabled.");
1177     }
1178 }
1179 
start_monitoring_project_file()1180 void MainWindow::start_monitoring_project_file()
1181 {
1182     assert(m_project_file_watcher);
1183 
1184     if (m_project_manager.is_project_open() &&
1185         m_project_manager.get_project()->has_path())
1186     {
1187         m_project_file_watcher->addPath(m_project_manager.get_project()->get_path());
1188     }
1189 }
1190 
stop_monitoring_project_file()1191 void MainWindow::stop_monitoring_project_file()
1192 {
1193     assert(m_project_file_watcher);
1194 
1195     if (m_project_manager.is_project_open() &&
1196         m_project_manager.get_project()->has_path())
1197     {
1198         m_project_file_watcher->removePath(m_project_manager.get_project()->get_path());
1199     }
1200 }
1201 
dragEnterEvent(QDragEnterEvent * event)1202 void MainWindow::dragEnterEvent(QDragEnterEvent* event)
1203 {
1204     if (event->mimeData()->hasFormat("text/uri-list"))
1205         event->acceptProposedAction();
1206 }
1207 
dropEvent(QDropEvent * event)1208 void MainWindow::dropEvent(QDropEvent* event)
1209 {
1210     if (event->mimeData()->hasFormat("text/uri-list"))
1211     {
1212         const QList<QUrl> urls = event->mimeData()->urls();
1213         QApplication::sendEvent(this, new QCloseEvent());
1214         open_project_async(urls[0].toLocalFile());
1215         event->acceptProposedAction();
1216     }
1217 }
1218 
start_rendering(const RenderingMode rendering_mode)1219 void MainWindow::start_rendering(const RenderingMode rendering_mode)
1220 {
1221     assert(m_project_manager.is_project_open());
1222 
1223     // Don't start a new render until the previous has completely ended.
1224     if (m_rendering_manager.is_rendering())
1225         return;
1226 
1227     m_false_colors_window.reset();
1228 
1229     // Enable/disable menus and widgets appropriately.
1230     set_file_widgets_enabled(false, rendering_mode);
1231     set_project_explorer_enabled(rendering_mode == InteractiveRendering);
1232     set_rendering_widgets_enabled(true, rendering_mode);
1233     set_diagnostics_widgets_enabled(rendering_mode == InteractiveRendering, rendering_mode);
1234     m_ui->attribute_editor_scrollarea_contents->setEnabled(rendering_mode == InteractiveRendering);
1235 
1236     // Remove light paths tab.
1237     remove_light_paths_tab();
1238 
1239     // Stop monitoring the project file in Final rendering mode.
1240     if (rendering_mode == FinalRendering)
1241     {
1242         if (m_project_file_watcher)
1243             stop_monitoring_project_file();
1244     }
1245 
1246     Project* project = m_project_manager.get_project();
1247     Frame* frame = project->get_frame();
1248 
1249     frame->clear_main_and_aov_images();
1250 
1251     // Darken render widgets.
1252     for (const_each<RenderTabCollection> i = m_render_tabs; i; ++i)
1253     {
1254         i->second->darken();
1255         i->second->update();
1256     }
1257 
1258     // Retrieve the appropriate rendering configuration.
1259     const char* configuration_name =
1260         rendering_mode == InteractiveRendering ? "interactive" : "final";
1261     const ParamArray params = get_project_params(configuration_name);
1262 
1263     // Effectively start rendering.
1264     m_rendering_manager.start_rendering(
1265         project,
1266         params,
1267         rendering_mode == InteractiveRendering
1268             ? RenderingManager::InteractiveRendering
1269             : RenderingManager::FinalRendering,
1270         m_render_tabs["RGB"]);
1271 }
1272 
apply_false_colors_settings()1273 void MainWindow::apply_false_colors_settings()
1274 {
1275     Project* project = m_project_manager.get_project();
1276     assert(project != nullptr);
1277 
1278     Frame* frame = project->get_frame();
1279     assert(frame != nullptr);
1280 
1281     const ParamArray& false_colors_params = m_application_settings.child("false_colors");
1282     const bool false_colors_enabled = false_colors_params.get_optional<bool>("enabled", false);
1283 
1284     if (false_colors_enabled)
1285     {
1286         // Make a temporary copy of the frame.
1287         // Render info, AOVs and other data are not copied.
1288         // todo: creating a frame with denoising enabled is very expensive, see benchmark_frame.cpp.
1289         auto_release_ptr<Frame> working_frame =
1290             FrameFactory::create(
1291                 (string(frame->get_name()) + "_copy").c_str(),
1292                 frame->get_parameters()
1293                     .remove_path("denoiser"));
1294         working_frame->image().copy_from(frame->image());
1295 
1296         // Create post-processing stage.
1297         auto_release_ptr<PostProcessingStage> stage(
1298             ColorMapPostProcessingStageFactory().create(
1299                 "__false_colors_post_processing_stage",
1300                 false_colors_params));
1301 
1302         // Apply post-processing stage.
1303         apply_post_processing_stage(stage.ref(), working_frame.ref());
1304     }
1305     else
1306     {
1307         // Blit the regular frame into the render widget.
1308         for (const_each<RenderTabCollection> i = m_render_tabs; i; ++i)
1309         {
1310             i->second->get_render_widget()->blit_frame(*frame);
1311             i->second->get_render_widget()->update();
1312         }
1313     }
1314 }
1315 
apply_post_processing_stage(PostProcessingStage & stage,Frame & working_frame)1316 void MainWindow::apply_post_processing_stage(
1317     PostProcessingStage&        stage,
1318     Frame&                      working_frame)
1319 {
1320     Project* project = m_project_manager.get_project();
1321     assert(project != nullptr);
1322 
1323     // Prepare the post-processing stage.
1324     OnFrameBeginRecorder recorder;
1325     if (stage.on_frame_begin(*project, nullptr, recorder, nullptr))
1326     {
1327         // Execute the post-processing stage.
1328         stage.execute(working_frame);
1329 
1330         // Blit the frame copy into the render widget.
1331         for (const_each<RenderTabCollection> i = m_render_tabs; i; ++i)
1332         {
1333             i->second->get_render_widget()->blit_frame(working_frame);
1334             i->second->get_render_widget()->update();
1335         }
1336     }
1337 }
1338 
1339 namespace
1340 {
ask_abort_rendering_confirmation(QWidget * parent)1341     int ask_abort_rendering_confirmation(QWidget* parent)
1342     {
1343         QMessageBox msgbox(parent);
1344         msgbox.setWindowTitle("Abort Rendering?");
1345         msgbox.setIcon(QMessageBox::Question);
1346         msgbox.setText("Rendering is in progress.");
1347         msgbox.setInformativeText("Do you want to abort rendering?");
1348         msgbox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
1349         msgbox.setDefaultButton(QMessageBox::No);
1350         return msgbox.exec();
1351     }
1352 }
1353 
closeEvent(QCloseEvent * event)1354 void MainWindow::closeEvent(QCloseEvent* event)
1355 {
1356     if (m_rendering_manager.is_rendering())
1357     {
1358         if (ask_abort_rendering_confirmation(this) != QMessageBox::Yes)
1359         {
1360             event->ignore();
1361             return;
1362         }
1363 
1364         m_rendering_manager.abort_rendering();
1365         m_rendering_manager.wait_until_rendering_end();
1366     }
1367 
1368     if (!can_close_project())
1369     {
1370         event->ignore();
1371         return;
1372     }
1373 
1374     slot_save_application_settings();
1375 
1376     if (m_test_window.get())
1377         m_test_window->close();
1378 
1379     if (m_benchmark_window.get())
1380         m_benchmark_window->close();
1381 
1382     remove_render_tabs();
1383 
1384     m_project_manager.close_project();
1385 
1386     event->accept();
1387 }
1388 
slot_new_project()1389 void MainWindow::slot_new_project()
1390 {
1391     if (!can_close_project())
1392         return;
1393 
1394     new_project();
1395 }
1396 
slot_open_project()1397 void MainWindow::slot_open_project()
1398 {
1399     if (!can_close_project())
1400         return;
1401 
1402     QString filepath =
1403         get_open_filename(
1404             this,
1405             "Open...",
1406             get_project_files_filter(),
1407             m_application_settings,
1408             SETTINGS_FILE_DIALOG_PROJECTS);
1409 
1410     if (!filepath.isEmpty())
1411     {
1412         filepath = QDir::toNativeSeparators(filepath);
1413 
1414         open_project_async(filepath);
1415         update_recent_files_menu(filepath);
1416     }
1417 }
1418 
slot_open_recent()1419 void MainWindow::slot_open_recent()
1420 {
1421     if (!can_close_project())
1422         return;
1423 
1424     QAction* action = qobject_cast<QAction*>(sender());
1425 
1426     if (action)
1427     {
1428         const QString filepath = action->data().toString();
1429         open_project_async(filepath);
1430     }
1431 }
1432 
slot_clear_all_recent_project_files()1433 void MainWindow::slot_clear_all_recent_project_files()
1434 {
1435     QSettings settings(SETTINGS_ORGANIZATION, SETTINGS_APPLICATION);
1436     settings.setValue("recent_file_list", QStringList());
1437 
1438     update_recent_files_menu(QStringList());
1439 }
1440 
slot_clear_recent_missing_project_files()1441 void MainWindow::slot_clear_recent_missing_project_files()
1442 {
1443     QSettings settings(SETTINGS_ORGANIZATION, SETTINGS_APPLICATION);
1444     QStringList files = settings.value("recent_file_list").toStringList();
1445     QStringList existing_files;
1446 
1447     for (int i = 0; i < files.size(); i++)
1448     {
1449         if (QFileInfo(files[i]).isFile())
1450             existing_files << files[i];
1451     }
1452 
1453     settings.setValue("recent_file_list", existing_files);
1454     update_recent_files_menu(existing_files);
1455 }
1456 
slot_open_cornellbox_builtin_project()1457 void MainWindow::slot_open_cornellbox_builtin_project()
1458 {
1459     if (!can_close_project())
1460         return;
1461 
1462     APPLESEED_UNUSED const bool successful = m_project_manager.load_builtin_project("cornell_box");
1463     assert(successful);
1464 
1465     on_project_change();
1466 }
1467 
slot_reload_project()1468 void MainWindow::slot_reload_project()
1469 {
1470     assert(m_project_manager.is_project_open());
1471     assert(m_project_manager.get_project()->has_path());
1472 
1473     if (!can_close_project())
1474         return;
1475 
1476     open_project_async(m_project_manager.get_project()->get_path());
1477 }
1478 
1479 namespace
1480 {
show_project_file_loading_failed_message_box(QWidget * parent,const QString & filepath)1481     void show_project_file_loading_failed_message_box(QWidget* parent, const QString& filepath)
1482     {
1483         QMessageBox msgbox(parent);
1484         msgbox.setWindowTitle("Loading Error");
1485         msgbox.setIcon(QMessageBox::Critical);
1486         msgbox.setText("Failed to load the project file " + filepath + ".");
1487         msgbox.setInformativeText(
1488             "The project file may be invalid, corrupted or missing. "
1489             "Please look at the Log window for details.");
1490         msgbox.setStandardButtons(QMessageBox::Ok);
1491         msgbox.exec();
1492     }
1493 }
1494 
slot_open_project_complete(const QString & filepath,const bool successful)1495 void MainWindow::slot_open_project_complete(const QString& filepath, const bool successful)
1496 {
1497     if (successful)
1498         on_project_change();
1499     else
1500     {
1501         show_project_file_loading_failed_message_box(this, filepath);
1502         recreate_render_tabs();
1503         update_workspace();
1504     }
1505 }
1506 
slot_save_project()1507 void MainWindow::slot_save_project()
1508 {
1509     assert(m_project_manager.is_project_open());
1510 
1511     if (!m_project_manager.get_project()->has_path())
1512         slot_save_project_as();
1513     else save_project(m_project_manager.get_project()->get_path());
1514 }
1515 
slot_save_project_as()1516 void MainWindow::slot_save_project_as()
1517 {
1518     assert(m_project_manager.is_project_open());
1519 
1520     QString filepath =
1521         get_save_filename(
1522             this,
1523             "Save As...",
1524             get_project_files_filter(ProjectFilesFilterPlainProjects),
1525             m_application_settings,
1526             SETTINGS_FILE_DIALOG_PROJECTS);
1527 
1528     if (!filepath.isEmpty())
1529     {
1530         filepath = QDir::toNativeSeparators(filepath);
1531 
1532         save_project(filepath);
1533     }
1534 }
1535 
slot_pack_project_as()1536 void MainWindow::slot_pack_project_as()
1537 {
1538     assert(m_project_manager.is_project_open());
1539 
1540     QString filepath =
1541         get_save_filename(
1542             this,
1543             "Pack As...",
1544             get_project_files_filter(ProjectFilesFilterPackedProjects),
1545             m_application_settings,
1546             SETTINGS_FILE_DIALOG_PROJECTS);
1547 
1548     if (!filepath.isEmpty())
1549     {
1550         filepath = QDir::toNativeSeparators(filepath);
1551 
1552         pack_project(filepath);
1553 
1554         // Don't update the Recent Files menu.
1555     }
1556 }
1557 
slot_close_project()1558 void MainWindow::slot_close_project()
1559 {
1560     if (!can_close_project())
1561         return;
1562 
1563     close_project();
1564 }
1565 
initialize_ocio()1566 void MainWindow::initialize_ocio()
1567 {
1568     // Try first a user specified OCIO config.
1569     if (getenv("OCIO"))
1570     {
1571         try
1572         {
1573             m_ocio_config = OCIO::GetCurrentConfig();
1574             RENDERER_LOG_INFO("using ocio configuration: %s", getenv("OCIO"));
1575             return;
1576         }
1577         catch (const OCIO::Exception&)
1578         {
1579         }
1580     }
1581 
1582     // Try the bundled default OCIO config.
1583     const bf::path root_path(Application::get_root_path());
1584     const string default_ocio_config = (root_path / "ocio" / "config.ocio").string();
1585 
1586     try
1587     {
1588         m_ocio_config = OCIO::Config::CreateFromFile(default_ocio_config.c_str());
1589         RENDERER_LOG_INFO("using ocio configuration: %s", default_ocio_config.c_str());
1590         OCIO::SetCurrentConfig(m_ocio_config);
1591         return;
1592     }
1593     catch (const OCIO::Exception&)
1594     {
1595     }
1596 
1597     // Default to an empty OCIO config if everything else fails.
1598     m_ocio_config = OCIO::GetCurrentConfig();
1599     RENDERER_LOG_ERROR("could not find an ocio configuration, using empty configuration.");
1600 }
1601 
slot_project_modified()1602 void MainWindow::slot_project_modified()
1603 {
1604     assert(m_project_manager.is_project_open());
1605 
1606     m_project_manager.set_project_dirty_flag();
1607 
1608     update_window_title();
1609 }
1610 
slot_toggle_project_file_monitoring(const bool checked)1611 void MainWindow::slot_toggle_project_file_monitoring(const bool checked)
1612 {
1613     if (checked)
1614         enable_project_file_monitoring();
1615     else disable_project_file_monitoring();
1616 
1617     m_application_settings.insert_path(
1618         SETTINGS_WATCH_FILE_CHANGES,
1619         m_project_file_watcher != nullptr);
1620 }
1621 
slot_project_file_changed(const QString & filepath)1622 void MainWindow::slot_project_file_changed(const QString& filepath)
1623 {
1624     RENDERER_LOG_INFO("project file changed on disk, reloading it.");
1625 
1626     assert(m_project_file_watcher);
1627     m_project_file_watcher->removePath(filepath);
1628 
1629     slot_reload_project();
1630 }
1631 
slot_load_application_settings()1632 void MainWindow::slot_load_application_settings()
1633 {
1634     const QSettings qt_settings(SETTINGS_ORGANIZATION, SETTINGS_APPLICATION);
1635     restoreGeometry(qt_settings.value("main_window_geometry").toByteArray());
1636     restoreState(qt_settings.value("main_window_state").toByteArray());
1637     m_ui->treewidget_project_explorer_scene->header()->restoreGeometry(
1638         qt_settings.value("main_window_project_explorer_geometry").toByteArray());
1639     m_ui->treewidget_project_explorer_scene->header()->restoreState(
1640         qt_settings.value("main_window_project_explorer_state").toByteArray());
1641 
1642     Dictionary settings;
1643     if (Application::load_settings("appleseed.studio.xml", settings, global_logger(), LogMessage::Info))
1644     {
1645         m_application_settings = settings;
1646         slot_apply_application_settings();
1647     }
1648 }
1649 
slot_save_application_settings()1650 void MainWindow::slot_save_application_settings()
1651 {
1652     QSettings settings(SETTINGS_ORGANIZATION, SETTINGS_APPLICATION);
1653     settings.setValue("main_window_geometry", saveGeometry());
1654     settings.setValue("main_window_state", saveState());
1655     settings.setValue("main_window_project_explorer_geometry",
1656         m_ui->treewidget_project_explorer_scene->header()->saveGeometry());
1657     settings.setValue("main_window_project_explorer_state",
1658         m_ui->treewidget_project_explorer_scene->header()->saveState());
1659 
1660     Application::save_settings("appleseed.studio.xml", m_application_settings, global_logger(), LogMessage::Info);
1661 }
1662 
slot_apply_application_settings()1663 void MainWindow::slot_apply_application_settings()
1664 {
1665     if (m_application_settings.strings().exist(SETTINGS_MESSAGE_VERBOSITY))
1666     {
1667         const char* level_name = m_application_settings.get(SETTINGS_MESSAGE_VERBOSITY);
1668         const LogMessage::Category level = LogMessage::get_category_value(level_name);
1669 
1670         if (level < LogMessage::NumMessageCategories)
1671             global_logger().set_verbosity_level(level);
1672         else RENDERER_LOG_ERROR("invalid message verbosity level \"%s\".", level_name);
1673     }
1674 
1675     if (m_application_settings.get_optional<bool>(SETTINGS_WATCH_FILE_CHANGES))
1676     {
1677         m_action_monitor_project_file->setChecked(true);
1678         enable_project_file_monitoring();
1679     }
1680     else
1681     {
1682         m_action_monitor_project_file->setChecked(false);
1683         disable_project_file_monitoring();
1684     }
1685 
1686     emit signal_application_settings_modified();
1687 }
1688 
slot_start_interactive_rendering()1689 void MainWindow::slot_start_interactive_rendering()
1690 {
1691     start_rendering(InteractiveRendering);
1692 }
1693 
slot_start_final_rendering()1694 void MainWindow::slot_start_final_rendering()
1695 {
1696     start_rendering(FinalRendering);
1697 }
1698 
slot_start_rendering_once(const QString & filepath,const QString & configuration,const bool successful)1699 void MainWindow::slot_start_rendering_once(const QString& filepath, const QString& configuration, const bool successful)
1700 {
1701     sender()->deleteLater();
1702 
1703     if (successful)
1704     {
1705         if (configuration == "interactive")
1706             start_rendering(InteractiveRendering);
1707         else start_rendering(FinalRendering);
1708     }
1709 }
1710 
slot_pause_or_resume_rendering(const bool checked)1711 void MainWindow::slot_pause_or_resume_rendering(const bool checked)
1712 {
1713     if (checked)
1714     {
1715         assert(!m_rendering_manager.is_rendering_paused());
1716         m_rendering_manager.pause_rendering();
1717     }
1718     else
1719     {
1720         m_rendering_manager.resume_rendering();
1721     }
1722 
1723     update_pause_resume_checkbox(checked);
1724 }
1725 
slot_rendering_end()1726 void MainWindow::slot_rendering_end()
1727 {
1728     apply_false_colors_settings();
1729 
1730     update_workspace();
1731 
1732     // Restart monitoring the project file if monitoring was enabled
1733     // (monitoring would have been stopped if rendering in Final mode).
1734     if (m_project_file_watcher)
1735         start_monitoring_project_file();
1736 }
1737 
slot_camera_changed()1738 void MainWindow::slot_camera_changed()
1739 {
1740     m_project_manager.set_project_dirty_flag();
1741 }
1742 
1743 namespace
1744 {
1745     class ClearShadingOverrideAction
1746       : public RenderingManager::IStickyAction
1747     {
1748       public:
operator ()(MasterRenderer & master_renderer,Project & project)1749         void operator()(
1750             MasterRenderer& master_renderer,
1751             Project&        project) override
1752         {
1753             master_renderer.get_parameters()
1754                 .push("shading_engine")
1755                 .dictionaries().remove("override_shading");
1756         }
1757     };
1758 
1759     class SetShadingOverrideAction
1760       : public RenderingManager::IStickyAction
1761     {
1762       public:
SetShadingOverrideAction(const string & shading_mode)1763         explicit SetShadingOverrideAction(const string& shading_mode)
1764           : m_shading_mode(shading_mode)
1765         {
1766         }
1767 
operator ()(MasterRenderer & master_renderer,Project & project)1768         void operator()(
1769             MasterRenderer& master_renderer,
1770             Project&        project) override
1771         {
1772             master_renderer.get_parameters()
1773                 .push("shading_engine")
1774                 .push("override_shading")
1775                 .insert("mode", m_shading_mode);
1776         }
1777 
1778       private:
1779         const string m_shading_mode;
1780     };
1781 }
1782 
slot_clear_shading_override()1783 void MainWindow::slot_clear_shading_override()
1784 {
1785     m_rendering_manager.set_sticky_action(
1786         "override_shading",
1787         unique_ptr<RenderingManager::IStickyAction>(
1788             new ClearShadingOverrideAction()));
1789 
1790     m_rendering_manager.reinitialize_rendering();
1791 }
1792 
slot_set_shading_override()1793 void MainWindow::slot_set_shading_override()
1794 {
1795     QAction* action = qobject_cast<QAction*>(sender());
1796     const string shading_mode = action->data().toString().toStdString();
1797 
1798     m_rendering_manager.set_sticky_action(
1799         "override_shading",
1800         unique_ptr<RenderingManager::IStickyAction>(
1801             new SetShadingOverrideAction(shading_mode)));
1802 
1803     m_rendering_manager.reinitialize_rendering();
1804 }
1805 
slot_show_false_colors_window()1806 void MainWindow::slot_show_false_colors_window()
1807 {
1808     if (m_false_colors_window.get() == nullptr)
1809     {
1810         m_false_colors_window.reset(new FalseColorsWindow(this));
1811 
1812         QObject::connect(
1813             m_false_colors_window.get(), SIGNAL(signal_applied(foundation::Dictionary)),
1814             SLOT(slot_apply_false_colors_settings_changes(foundation::Dictionary)));
1815 
1816         QObject::connect(
1817             m_false_colors_window.get(), SIGNAL(signal_accepted(foundation::Dictionary)),
1818             SLOT(slot_apply_false_colors_settings_changes(foundation::Dictionary)));
1819 
1820         QObject::connect(
1821             m_false_colors_window.get(), SIGNAL(signal_canceled(foundation::Dictionary)),
1822             SLOT(slot_apply_false_colors_settings_changes(foundation::Dictionary)));
1823     }
1824 
1825     Project* project = m_project_manager.get_project();
1826     assert(project);
1827 
1828     m_false_colors_window->initialize(
1829         *project,
1830         m_application_settings,
1831         m_application_settings.child("false_colors"));
1832 
1833     m_false_colors_window->showNormal();
1834     m_false_colors_window->activateWindow();
1835 }
1836 
slot_apply_false_colors_settings_changes(Dictionary values)1837 void MainWindow::slot_apply_false_colors_settings_changes(Dictionary values)
1838 {
1839     m_application_settings.push("false_colors").merge(values);
1840     apply_false_colors_settings();
1841 }
1842 
1843 namespace
1844 {
1845     class ClearRenderRegionAction
1846       : public RenderingManager::IScheduledAction
1847     {
1848       public:
ClearRenderRegionAction(const AttributeEditor * attribute_editor)1849         explicit ClearRenderRegionAction(const AttributeEditor* attribute_editor)
1850           : m_attribute_editor(attribute_editor)
1851         {
1852         }
1853 
operator ()(Project & project)1854         void operator()(
1855             Project&        project) override
1856         {
1857             project.get_frame()->reset_crop_window();
1858 
1859             m_attribute_editor->refresh();
1860         }
1861 
1862       private:
1863         const AttributeEditor* m_attribute_editor;
1864     };
1865 
1866     class SetRenderRegionAction
1867       : public RenderingManager::IScheduledAction
1868     {
1869       public:
SetRenderRegionAction(const QRect & rect,const AttributeEditor * attribute_editor)1870         SetRenderRegionAction(
1871             const QRect&            rect,
1872             const AttributeEditor*  attribute_editor)
1873           : m_rect(rect),
1874             m_attribute_editor(attribute_editor)
1875         {
1876         }
1877 
operator ()(Project & project)1878         void operator()(
1879             Project&        project) override
1880         {
1881             const int w = m_rect.width();
1882             const int h = m_rect.height();
1883             const int x0 = m_rect.x();
1884             const int y0 = m_rect.y();
1885             const int x1 = x0 + w - 1;
1886             const int y1 = y0 + h - 1;
1887 
1888             assert(x0 >= 0);
1889             assert(y0 >= 0);
1890             assert(x0 <= x1);
1891             assert(y0 <= y1);
1892 
1893             project.get_frame()->set_crop_window(
1894                 AABB2i(
1895                     Vector2i(x0, y0),
1896                     Vector2i(x1, y1)));
1897 
1898             m_attribute_editor->refresh();
1899         }
1900 
1901       private:
1902         const QRect m_rect;
1903         const AttributeEditor* m_attribute_editor;
1904     };
1905 }
1906 
slot_clear_render_region()1907 void MainWindow::slot_clear_render_region()
1908 {
1909     unique_ptr<RenderingManager::IScheduledAction> clear_render_region_action(
1910         new ClearRenderRegionAction(m_attribute_editor));
1911 
1912     if (m_rendering_manager.is_rendering())
1913         m_rendering_manager.schedule(std::move(clear_render_region_action));
1914     else clear_render_region_action.get()->operator()(*m_project_manager.get_project());
1915 
1916     m_rendering_manager.reinitialize_rendering();
1917 }
1918 
slot_set_render_region(const QRect & rect)1919 void MainWindow::slot_set_render_region(const QRect& rect)
1920 {
1921     unique_ptr<RenderingManager::IScheduledAction> set_render_region_action(
1922         new SetRenderRegionAction(rect, m_attribute_editor));
1923 
1924     if (!m_rendering_manager.is_rendering())
1925     {
1926         set_render_region_action.get()->operator()(*m_project_manager.get_project());
1927 
1928         if (m_application_settings.get_path_optional<bool>(SETTINGS_RENDER_REGION_TRIGGERS_RENDERING))
1929             start_rendering(InteractiveRendering);
1930     }
1931     else
1932     {
1933         m_rendering_manager.schedule(std::move(set_render_region_action));
1934         m_rendering_manager.reinitialize_rendering();
1935     }
1936 }
1937 
slot_render_widget_context_menu(const QPoint & point)1938 void MainWindow::slot_render_widget_context_menu(const QPoint& point)
1939 {
1940     if (!(QApplication::keyboardModifiers() & Qt::ShiftModifier))
1941         return;
1942 
1943     if (m_rendering_manager.is_rendering())
1944         return;
1945 
1946     QMenu* menu = new QMenu(this);
1947     menu->addAction("Save Frame...", this, SLOT(slot_save_frame()));
1948     menu->addAction("Save Frame and AOVs...", this, SLOT(slot_save_frame_and_aovs()));
1949     menu->addSeparator();
1950     menu->addAction("Save Render Widget Content...", this, SLOT(slot_save_render_widget_content()));
1951     menu->addSeparator();
1952     menu->addAction("Clear All", this, SLOT(slot_clear_frame()));
1953     menu->exec(point);
1954 }
1955 
1956 namespace
1957 {
ask_frame_save_file_path(QWidget * parent,const QString & caption,const QString & filter,const QString & default_ext,ParamArray & settings)1958     QString ask_frame_save_file_path(
1959         QWidget*        parent,
1960         const QString&  caption,
1961         const QString&  filter,
1962         const QString&  default_ext,
1963         ParamArray&     settings)
1964     {
1965         QString filepath =
1966             get_save_filename(
1967                 parent,
1968                 caption,
1969                 filter,
1970                 settings,
1971                 SETTINGS_FILE_DIALOG_FRAMES);
1972 
1973         if (!filepath.isEmpty())
1974         {
1975             if (QFileInfo(filepath).suffix().isEmpty())
1976                 filepath += default_ext;
1977 
1978             filepath = QDir::toNativeSeparators(filepath);
1979         }
1980 
1981         return filepath;
1982     }
1983 }
1984 
slot_save_frame()1985 void MainWindow::slot_save_frame()
1986 {
1987     assert(m_project_manager.is_project_open());
1988     assert(!m_rendering_manager.is_rendering());
1989 
1990     const QString filepath =
1991         ask_frame_save_file_path(
1992             this,
1993             "Save Frame As...",
1994             get_oiio_image_files_filter(),
1995             ".exr",
1996             m_application_settings);
1997 
1998     if (filepath.isEmpty())
1999         return;
2000 
2001     const Frame* frame = m_project_manager.get_project()->get_frame();
2002     frame->write_main_image(filepath.toUtf8().constData());
2003 }
2004 
slot_save_frame_and_aovs()2005 void MainWindow::slot_save_frame_and_aovs()
2006 {
2007     assert(m_project_manager.is_project_open());
2008     assert(!m_rendering_manager.is_rendering());
2009 
2010     const QString filepath =
2011         ask_frame_save_file_path(
2012             this,
2013             "Save Frame and AOVs As...",
2014             get_oiio_image_files_filter(),
2015             ".exr",
2016             m_application_settings);
2017 
2018     if (filepath.isEmpty())
2019         return;
2020 
2021     const Frame* frame = m_project_manager.get_project()->get_frame();
2022     frame->write_main_image(filepath.toUtf8().constData());
2023     frame->write_aov_images(filepath.toUtf8().constData());
2024 }
2025 
2026 namespace
2027 {
write_main_and_aov_images(const Project & project,const bf::path & image_path)2028     void write_main_and_aov_images(
2029         const Project&  project,
2030         const bf::path& image_path)
2031     {
2032         bf::create_directories(image_path.parent_path());
2033 
2034         const Frame* frame = project.get_frame();
2035         frame->write_main_image(image_path.string().c_str());
2036         frame->write_aov_images(image_path.string().c_str());
2037     }
2038 }
2039 
slot_quicksave_frame_and_aovs()2040 void MainWindow::slot_quicksave_frame_and_aovs()
2041 {
2042     assert(m_project_manager.is_project_open());
2043     assert(!m_rendering_manager.is_rendering());
2044 
2045     const Project& project = *m_project_manager.get_project();
2046 
2047     const bf::path project_path(project.get_path());
2048     const bf::path quicksave_dir = project_path.parent_path() / "quicksaves";
2049 
2050     write_main_and_aov_images(
2051         project,
2052         bf::absolute(
2053             quicksave_dir / "quicksave.exr"));
2054 
2055     write_main_and_aov_images(
2056         project,
2057         bf::absolute(
2058             find_next_available_path(quicksave_dir / "quicksave####.exr")));
2059 }
2060 
slot_save_render_widget_content()2061 void MainWindow::slot_save_render_widget_content()
2062 {
2063     assert(m_project_manager.is_project_open());
2064     assert(!m_rendering_manager.is_rendering());
2065 
2066     const QString filepath =
2067         ask_frame_save_file_path(
2068             this,
2069             "Save Render Widget Content As...",
2070             g_qt_image_files_filter,
2071             ".png",
2072             m_application_settings);
2073 
2074     if (filepath.isEmpty())
2075         return;
2076 
2077     // todo: this is sketchy. The render tab should be retrieved from the signal.
2078     m_render_tabs["RGB"]->get_render_widget()->capture().save(filepath);
2079 
2080     RENDERER_LOG_INFO("wrote image file %s.", filepath.toStdString().c_str());
2081 }
2082 
slot_clear_frame()2083 void MainWindow::slot_clear_frame()
2084 {
2085     Frame* frame = m_project_manager.get_project()->get_frame();
2086     frame->clear_main_and_aov_images();
2087 
2088     // In the UI, clear all render widgets to black.
2089     for (const_each<RenderTabCollection> i = m_render_tabs; i; ++i)
2090         i->second->clear();
2091 }
2092 
slot_reset_zoom()2093 void MainWindow::slot_reset_zoom()
2094 {
2095     const int current_tab_index = m_ui->tab_render_channels->currentIndex();
2096     const auto render_tab_it = m_tab_index_to_render_tab.find(current_tab_index);
2097     if (render_tab_it != m_tab_index_to_render_tab.end())
2098         render_tab_it->second->reset_zoom();
2099 }
2100 
slot_filter_text_changed(const QString & pattern)2101 void MainWindow::slot_filter_text_changed(const QString& pattern)
2102 {
2103     m_ui->pushbutton_clear_filter->setEnabled(!pattern.isEmpty());
2104     m_project_explorer->filter_items(pattern);
2105 }
2106 
slot_clear_filter()2107 void MainWindow::slot_clear_filter()
2108 {
2109     m_ui->lineedit_filter->clear();
2110 }
2111 
slot_frame_modified()2112 void MainWindow::slot_frame_modified()
2113 {
2114     for (each<RenderTabCollection> i = m_render_tabs; i; ++i)
2115         i->second->update_size();
2116 }
2117 
slot_fullscreen()2118 void MainWindow::slot_fullscreen()
2119 {
2120     m_fullscreen = !m_fullscreen;
2121 
2122     bool all_minimized = true;
2123     bool not_minimized = false;
2124     for (const MinimizeButton* button : m_minimize_buttons)
2125     {
2126         all_minimized = all_minimized && button->is_on();
2127         not_minimized = not_minimized || !button->is_on();
2128     }
2129 
2130     // All were manually minimized, exit full screen mode.
2131     if (all_minimized)
2132         m_fullscreen = false;
2133 
2134     // At least one is on screen, enter full screen mode.
2135     if (not_minimized)
2136         m_fullscreen = true;
2137 
2138     for (MinimizeButton* button : m_minimize_buttons)
2139         button->set_fullscreen(m_fullscreen);
2140 }
2141 
slot_check_fullscreen()2142 void MainWindow::slot_check_fullscreen()
2143 {
2144     const QList<QDockWidget*> dock_widgets = findChildren<QDockWidget*>();
2145 
2146     const bool is_fullscreen = all_of(dock_widgets.cbegin(),
2147                                       dock_widgets.cend(),
2148                                       [](QDockWidget* dock) {return dock->isHidden();});
2149 
2150     m_action_fullscreen->setChecked(is_fullscreen);
2151 }
2152 
slot_show_application_settings_window()2153 void MainWindow::slot_show_application_settings_window()
2154 {
2155     if (m_application_settings_window.get() == nullptr)
2156     {
2157         m_application_settings_window.reset(
2158             new ApplicationSettingsWindow(m_application_settings, this));
2159 
2160         connect(
2161             m_application_settings_window.get(), SIGNAL(signal_application_settings_modified()),
2162             SLOT(slot_save_application_settings()));
2163 
2164         connect(
2165             m_application_settings_window.get(), SIGNAL(signal_application_settings_modified()),
2166             SLOT(slot_apply_application_settings()));
2167 
2168         connect(
2169             this, SIGNAL(signal_application_settings_modified()),
2170             m_application_settings_window.get(), SLOT(slot_reload_application_settings()));
2171     }
2172 
2173     m_application_settings_window->showNormal();
2174     m_application_settings_window->activateWindow();
2175 }
2176 
slot_show_rendering_settings_window()2177 void MainWindow::slot_show_rendering_settings_window()
2178 {
2179     assert(m_project_manager.is_project_open());
2180 
2181     if (m_rendering_settings_window.get() == nullptr)
2182     {
2183         m_rendering_settings_window.reset(
2184             new RenderingSettingsWindow(
2185                 m_project_manager,
2186                 m_application_settings,
2187                 this));
2188 
2189         connect(
2190             m_rendering_settings_window.get(), SIGNAL(signal_rendering_settings_modified()),
2191             SLOT(slot_project_modified()));
2192 
2193         connect(
2194             this, SIGNAL(signal_application_settings_modified()),
2195             m_rendering_settings_window.get(), SLOT(slot_reload_application_settings()));
2196     }
2197 
2198     m_rendering_settings_window->showNormal();
2199     m_rendering_settings_window->activateWindow();
2200 }
2201 
slot_show_test_window()2202 void MainWindow::slot_show_test_window()
2203 {
2204     if (m_test_window.get() == nullptr)
2205         m_test_window.reset(new TestWindow(this));
2206 
2207     m_test_window->showNormal();
2208     m_test_window->activateWindow();
2209 }
2210 
slot_show_benchmark_window()2211 void MainWindow::slot_show_benchmark_window()
2212 {
2213     if (m_benchmark_window.get() == nullptr)
2214         m_benchmark_window.reset(new BenchmarkWindow(this));
2215 
2216     m_benchmark_window->showNormal();
2217     m_benchmark_window->activateWindow();
2218 }
2219 
slot_show_about_window()2220 void MainWindow::slot_show_about_window()
2221 {
2222     AboutWindow* about_window = new AboutWindow(this);
2223     about_window->showNormal();
2224     about_window->activateWindow();
2225 }
2226 
2227 }   // namespace studio
2228 }   // namespace appleseed
2229 
2230 #include "mainwindow/moc_cpp_mainwindow.cxx"
2231