<commit_before>
//
// This source file is part of appleseed.
// Visit http://appleseedhq.net/ for additional information and resources.
//
// This software is released under the MIT license.
//
// Copyright (c) 2010-2013 <NAME>, Jupiter Jazz Limited
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

// Interface header.
#include "mainwindow.h"

// UI definition header.
#include "ui_mainwindow.h"

// appleseed.studio headers.
#include "mainwindow/logwidget.h"
#include "utility/interop.h"
#include "utility/settingskeys.h"
#include "utility/tweaks.h"

// appleseed.shared headers.
#include "application/application.h"

// appleseed.renderer headers.
#include "renderer/api/aov.h"
#include "renderer/api/frame.h"
#include "renderer/api/log.h"
#include "renderer/api/project.h"
#include "renderer/api/surfaceshader.h"

// appleseed.foundation headers.
#include "foundation/core/appleseed.h"
#include "foundation/image/canvasproperties.h"
#include "foundation/image/color.h"
#include "foundation/image/image.h"
#include "foundation/platform/compiler.h"
#include "foundation/platform/system.h"
#include "foundation/platform/types.h"
#include "foundation/utility/containers/dictionary.h"
#include "foundation/utility/autoreleaseptr.h"
#include "foundation/utility/foreach.h"
#include "foundation/utility/settings.h"
#include "foundation/utility/string.h"

// Qt headers.
#include <QAction>
#include <QActionGroup>
#include <QApplication>
#include <QDir>
#include <QFileDialog>
#include <QGridLayout>
#include <QIcon>
#include <QLabel>
#include <QMessageBox>
#include <QMetaType>
#include <QSettings>
#include <QStatusBar>
#include <QString>
#include <QStringList>
#include <QWidget>

// boost headers.
#include "boost/filesystem/path.hpp"

// Standard headers.
#include <algorithm>
#include <cassert>

using namespace appleseed::shared;
using namespace boost;
using namespace foundation;
using namespace renderer;
using namespace std;

namespace appleseed {
namespace studio {

//
// MainWindow class implementation.
//

MainWindow::MainWindow(QWidget* parent)
  : QMainWindow(parent)
  , m_ui(new Ui::MainWindow())
  , m_rendering_manager(m_status_bar)
{
    m_ui->setupUi(this);

    statusBar()->addWidget(&m_status_bar);

    build_menus();
    build_toolbar();
    build_log();
    build_project_explorer();

    build_connections();

    print_startup_information();
    slot_load_settings();

    update_project_explorer();
    remove_render_widgets();
    update_workspace();

    showMaximized();
}

MainWindow::~MainWindow()
{
    delete m_ui;
}

void MainWindow::slot_recreate_render_widgets()
{
    recreate_render_widgets();
}

void MainWindow::build_menus()
{
    //
    // File menu.
    //

    m_ui->action_file_new_project->setShortcut(QKeySequence::New);
    connect(m_ui->action_file_new_project, SIGNAL(triggered()), SLOT(slot_new_project()));

    m_ui->action_file_open_project->setShortcut(QKeySequence::Open);
    connect(m_ui->action_file_open_project, SIGNAL(triggered()), SLOT(slot_open_project()));

    build_recent_files_menu();

    connect(m_ui->action_file_open_builtin_project_cornellbox, SIGNAL(triggered()), SLOT(slot_open_cornellbox_builtin_project()));
    connect(m_ui->action_file_reload_project, SIGNAL(triggered()), SLOT(slot_reload_project()));

    m_ui->action_file_save_project->setShortcut(QKeySequence::Save);
    connect(m_ui->action_file_save_project, SIGNAL(triggered()), SLOT(slot_save_project()));

    m_ui->action_file_save_project_as->setShortcut(QKeySequence::SaveAs);
    connect(m_ui->action_file_save_project_as, SIGNAL(triggered()), this, SLOT(slot_save_project_as()));

    m_ui->action_file_exit->setShortcut(QKeySequence::Quit);
    connect(m_ui->action_file_exit, SIGNAL(triggered()), SLOT(close()));

    //
    // View menu.
    //

    m_ui->menu_view->addAction(m_ui->project_explorer->toggleViewAction());
    m_ui->menu_view->addAction(m_ui->log->toggleViewAction());

    //
    // Rendering menu.
    //

    connect(m_ui->action_rendering_start_interactive_rendering, SIGNAL(triggered()), SLOT(slot_start_interactive_rendering()));
    connect(m_ui->action_rendering_start_final_rendering, SIGNAL(triggered()), SLOT(slot_start_final_rendering()));
    connect(m_ui->action_rendering_stop_rendering, SIGNAL(triggered()), SLOT(slot_stop_rendering()));
    connect(m_ui->action_rendering_render_settings, SIGNAL(triggered()), SLOT(slot_show_render_settings_window()));

    //
    // Diagnostics menu.
    //

    build_override_shading_menu_item();

    //
    // Debug menu.
    //

    connect(m_ui->action_debug_tests, SIGNAL(triggered()), SLOT(slot_show_test_window()));
    connect(m_ui->action_debug_benchmarks, SIGNAL(triggered()), SLOT(slot_show_benchmark_window()));

    //
    // Tools menu.
    //

    connect(m_ui->action_tools_save_settings, SIGNAL(triggered()), SLOT(slot_save_settings()));
    connect(m_ui->action_tools_reload_settings, SIGNAL(triggered()), SLOT(slot_load_settings()));

    //
    // Help menu.
    //

    connect(m_ui->action_help_about, SIGNAL(triggered()), SLOT(slot_show_about_window()));
}

void MainWindow::build_override_shading_menu_item()
{
    QActionGroup* action_group = new QActionGroup(this);

    connect(
        m_ui->action_diagnostics_override_shading_no_override, SIGNAL(triggered()),
        &m_rendering_manager, SLOT(slot_clear_shading_override()));

    action_group->addAction(m_ui->action_diagnostics_override_shading_no_override);

    for (int i = 0; i < DiagnosticSurfaceShader::ShadingModeCount; ++i)
    {
        const char* shading_mode_value = DiagnosticSurfaceShader::ShadingModeNames[i].m_key;
        const char* shading_mode_name = DiagnosticSurfaceShader::ShadingModeNames[i].m_value;

        QAction* action = new QAction(this);

        action->setCheckable(true);

        action->setObjectName(
            QString::fromAscii("action_diagnostics_override_shading_") + shading_mode_value);

        action->setText(
            QApplication::translate(
                objectName().toAscii().constData(),
                shading_mode_name,
                0,
                QApplication::UnicodeUTF8));

        const int shortcut_number = i + 1;

        if (shortcut_number <= 9)
        {
            const QString shortcut =
                QApplication::translate(
                    objectName().toAscii().constData(),
                    QString::fromAscii("Ctrl+Shift+%1").arg(shortcut_number).toAscii().constData(),
                    0,
                    QApplication::UnicodeUTF8);

            action->setShortcut(shortcut);
        }

        action->setData(shading_mode_value);

        connect(
            action, SIGNAL(triggered()),
            &m_rendering_manager, SLOT(slot_set_shading_override()));

        m_ui->menu_diagnostics_override_shading->addAction(action);

        action_group->addAction(action);
    }
}

void MainWindow::update_override_shading_menu_item()
{
    const ParamArray project_params = get_project_params("interactive");
    const ParamArray shading_engine_params = project_params.child("shading_engine");

    if (shading_engine_params.dictionaries().exist("override_shading"))
    {
        const string shading_mode =
            shading_engine_params.child("override_shading").get_optional<string>("mode", "coverage");

        for (const_each<QList<QAction*> > i = m_ui->menu_diagnostics_override_shading->actions(); i; ++i)
        {
            QAction* action = *i;

            if (action->data().toString().toStdString() == shading_mode)
            {
                action->activate(QAction::Trigger);
                break;
            }
        }
    }
    else
    {
        m_ui->action_diagnostics_override_shading_no_override->activate(QAction::Trigger);
    }
}

namespace
{
    const int MaxRecentlyOpenedFiles = 5;
    const char* SettingsOrgString = "com.appleseed.studio";
    const char* SettingsRecentFilesEntryString = "appleseed.studio Recent Files";
    const char* SettingsRecentFileListString = "recent_file_list";
}

void MainWindow::build_recent_files_menu()
{
    assert(m_recently_opened.empty());
    m_recently_opened.reserve(MaxRecentlyOpenedFiles);

    for (int i = 0; i < MaxRecentlyOpenedFiles; ++i)
    {
        QAction* action = new QAction(this);
        action->setVisible(false);

        connect(action, SIGNAL(triggered()), SLOT(slot_open_recent()));

        m_ui->menu_open_recent->addAction(action);
        m_recently_opened.push_back(action);
    }

    QSettings settings(SettingsOrgString, SettingsRecentFilesEntryString);
    QStringList files = settings.value(SettingsRecentFileListString).toStringList();
    update_recent_files_menu(files);

    m_ui->menu_open_recent->addSeparator();
    m_clear_open_recent_menu = new QAction(this);
    m_clear_open_recent_menu->setText("&Clear Menu");
    connect(m_clear_open_recent_menu, SIGNAL(triggered()), SLOT(slot_clear_open_recent_files_menu()));
    m_ui->menu_open_recent->addAction(m_clear_open_recent_menu);
}

void MainWindow::update_recent_files_menu(const QString& filepath)
{
    QSettings settings(SettingsOrgString, SettingsRecentFilesEntryString);
    QStringList files = settings.value(SettingsRecentFileListString).toStringList();
    files.removeAll(filepath);
    files.prepend(filepath);

    while (files.size() > MaxRecentlyOpenedFiles)
        files.removeLast();

    settings.setValue(SettingsRecentFileListString, files);
    update_recent_files_menu(files);
}

void MainWindow::update_recent_files_menu(const QStringList& files)
{
    const int num_recent_files = min(files.size(), MaxRecentlyOpenedFiles);

    for (int i = 0; i < num_recent_files; ++i)
    {
        const QString filepath = files[i];
        const QString text = tr("&%1 %2").arg(i + 1).arg(filepath);

        m_recently_opened[i]->setText(text);
        m_recently_opened[i]->setData(filepath);
        m_recently_opened[i]->setVisible(true);
    }

    for (int i = num_recent_files; i < MaxRecentlyOpenedFiles; ++i)
        m_recently_opened[i]->setVisible(false);
}

void MainWindow::build_toolbar()
{
    m_action_new_project = new QAction(QIcon(":/icons/page_white.png"), "&New", this);
    connect(m_action_new_project, SIGNAL(triggered()), this, SLOT(slot_new_project()));
    m_ui->main_toolbar->addAction(m_action_new_project);

    m_action_open_project = new QAction(QIcon(":/icons/folder.png"), "&Open", this);
    connect(m_action_open_project, SIGNAL(triggered()), this, SLOT(slot_open_project()));
    m_ui->main_toolbar->addAction(m_action_open_project);

    m_action_save_project = new QAction(QIcon(":/icons/disk.png"), "&Save", this);
    connect(m_action_save_project, SIGNAL(triggered()), this, SLOT(slot_save_project()));
    m_ui->main_toolbar->addAction(m_action_save_project);

    m_ui->main_toolbar->addSeparator();

    m_action_start_interactive_rendering = new QAction(QIcon(":/icons/film_go.png"), "Start &Interactive Rendering", this);
    connect(m_action_start_interactive_rendering, SIGNAL(triggered()), this, SLOT(slot_start_interactive_rendering()));
    m_ui->main_toolbar->addAction(m_action_start_interactive_rendering);

    m_action_start_final_rendering = new QAction(QIcon(":/icons/cog_go.png"), "Start &Final Rendering", this);
    connect(m_action_start_final_rendering, SIGNAL(triggered()), this, SLOT(slot_start_final_rendering()));
    m_ui->main_toolbar->addAction(m_action_start_final_rendering);

    m_action_stop_rendering = new QAction(QIcon(":/icons/cross.png"), "S&top Rendering", this);
    connect(m_action_stop_rendering, SIGNAL(triggered()), this, SLOT(slot_stop_rendering()));
    m_ui->main_toolbar->addAction(m_action_stop_rendering);
}

LogWidget* MainWindow::create_log_widget() const
{
    LogWidget* log_widget = new LogWidget(m_ui->log_contents);
    m_ui->log_contents->layout()->addWidget(log_widget);

    log_widget->setObjectName(QString::fromUtf8("textedit_log"));
    log_widget->setUndoRedoEnabled(false);
    log_widget->setLineWrapMode(QTextEdit::NoWrap);
    log_widget->setReadOnly(true);
    log_widget->setTextInteractionFlags(Qt::TextSelectableByMouse);
    log_widget->setStyleSheet("QTextEdit { border: 0px; }");

    QFont font;
    font.setStyleHint(QFont::TypeWriter);
#if defined _WIN32
    font.setFamily(QString::fromUtf8("Consolas"));
#elif defined __APPLE__
    font.setFamily(QString::fromUtf8("Monaco"));
#else
    font.setFamily(QString::fromUtf8("Courier New"));
#endif
    font.setPixelSize(11);
    log_widget->setFont(font);

    return log_widget;
}

void MainWindow::build_log()
{
    LogWidget* log_widget = create_log_widget();

    m_log_target.reset(new QtLogTarget(log_widget));

    global_logger().add_target(m_log_target.get());
}

void MainWindow::build_project_explorer()
{
    m_ui->treewidget_project_explorer_scene->setColumnWidth(0, 220);    // name
    m_ui->treewidget_project_explorer_scene->setColumnWidth(1, 75);     // render layer

    disable_mac_focus_rect(m_ui->treewidget_project_explorer_scene);
    disable_mac_focus_rect(m_ui->treewidget_project_explorer_renders);

    connect(
        m_ui->lineedit_filter, SIGNAL(textChanged(const QString&)),
        this, SLOT(slot_filter_text_changed(const QString&)));

    connect(
        m_ui->pushbutton_clear_filter, SIGNAL(clicked()),
        this, SLOT(slot_clear_filter()));

    m_ui->pushbutton_clear_filter->setEnabled(false);
}

void MainWindow::build_connections()
{
    connect(
        &m_project_manager, SIGNAL(signal_load_project_async_complete(const QString&, const bool)),
        this, SLOT(slot_open_project_complete(const QString&, const bool)));

    connect(
        &m_rendering_manager, SIGNAL(signal_rendering_end()),
        this, SLOT(slot_rendering_end()));

    connect(
        &m_rendering_manager, SIGNAL(signal_camera_changed()),
        this, SLOT(slot_camera_changed()));
}

void MainWindow::print_startup_information()
{
    RENDERER_LOG_INFO(
        "%s, %s configuration\n"
        "compiled on %s at %s using %s version %s",
        Appleseed::get_synthetic_version_string(),
        Appleseed::get_lib_configuration(),
        Appleseed::get_lib_compilation_date(),
        Appleseed::get_lib_compilation_time(),
        Compiler::get_compiler_name(),
        Compiler::get_compiler_version());

    RENDERER_LOG_INFO(
        "system information:\n"
        "  L1 data cache    size %s, line size %s\n"
        "  L2 cache         size %s, line size %s\n"
        "  L3 cache         size %s, line size %s\n",
        pretty_size(System::get_l1_data_cache_size()).c_str(),
        pretty_size(System::get_l1_data_cache_line_size()).c_str(),
        pretty_size(System::get_l2_cache_size()).c_str(),
        pretty_size(System::get_l2_cache_line_size()).c_str(),
        pretty_size(System::get_l3_cache_size()).c_str(),
        pretty_size(System::get_l3_cache_line_size()).c_str());
}

ParamArray MainWindow::get_project_params(const char* configuration_name) const
{
    ParamArray params;

    Configuration* configuration =
        m_project_manager.is_project_open()
            ? m_project_manager.get_project()->configurations().get_by_name(configuration_name)
            : 0;

    if (configuration && configuration->get_base())
        params = configuration->get_base()->get_parameters();

    params.merge(m_settings);

    if (configuration)
        params.merge(configuration->get_parameters());

    return params;
}

namespace
{
    int show_modified_project_message_box(QWidget* parent)
    {
        QMessageBox msgbox(parent);
        msgbox.setWindowTitle("Save Changes?");
        msgbox.setIcon(QMessageBox::Question);
        msgbox.setText("The project has been modified.");
        msgbox.setInformativeText("Do you want to save your changes?");
        msgbox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
        msgbox.setDefaultButton(QMessageBox::Save);
        return msgbox.exec();
    }

    void show_project_file_loading_failed_message_box(QWidget* parent, const QString& filepath)
    {
        QMessageBox msgbox(parent);
        msgbox.setWindowTitle("Loading Error");
        msgbox.setIcon(QMessageBox::Critical);
        msgbox.setText("Failed to load the project file " + filepath + ".");
        msgbox.setInformativeText(
            "The project file may be invalid, corrupted or missing. "
            "Please look at the Log window for details.");
        msgbox.setStandardButtons(QMessageBox::Ok);
        msgbox.exec();
    }
}

void MainWindow::open_project(const QString& filepath)
{
    set_project_widgets_enabled(false);
    set_rendering_widgets_enabled(false, false);

    const filesystem::path path(filepath.toStdString());

    m_settings.insert_path(
        LAST_DIRECTORY_SETTINGS_KEY,
        path.parent_path().string());

    m_project_manager.load_project(filepath.toAscii().constData());
}

bool MainWindow::can_close_project()
{
    // No project open: no problem.
    if (!m_project_manager.is_project_open())
        return true;

    // Unmodified project: no problem.
    if (!m_project_manager.is_project_dirty())
        return true;

    // The current project has been modified, ask the user what to do.
    switch (show_modified_project_message_box(this))
    {
      case QMessageBox::Save:
        slot_save_project();
        return true;

      case QMessageBox::Discard:
        return true;

      case QMessageBox::Cancel:
        return false;
    }

    assert(!"Should never be reached.");
    return false;
}

void MainWindow::on_project_change()
{
    update_project_explorer();
    recreate_render_widgets();

    update_override_shading_menu_item();

    if (m_render_settings_window.get())
        m_render_settings_window->reload();

    m_status_bar.clear();

    update_workspace();
}

void MainWindow::update_workspace()
{
    update_window_title();
    set_project_widgets_enabled(true);
    set_rendering_widgets_enabled(true, false);
}

void MainWindow::update_project_explorer()
{
    m_ui->lineedit_filter->clear();
    m_ui->treewidget_project_explorer_scene->clear();

    if (m_project_manager.is_project_open())
    {
        m_project_explorer.reset(
            new ProjectExplorer(
                m_ui->treewidget_project_explorer_scene,    
                *m_project_manager.get_project(),
                m_settings));

        connect(
            m_project_explorer.get(), SIGNAL(signal_project_modified()),
            this, SLOT(slot_project_modified()));

        connect(
            m_project_explorer.get(), SIGNAL(signal_frame_modified()),
            this, SLOT(slot_recreate_render_widgets()));
    }
    else
    {
        m_project_explorer.reset();
    }
}

void MainWindow::update_window_title()
{
    QString title;

    if (m_project_manager.is_project_open())
    {
        if (m_project_manager.is_project_dirty())
            title.append("* ");

        title.append(QString::fromStdString(m_project_manager.get_project_display_name()));
        title.append(" - ");
    }

    title.append("appleseed.studio");

    setWindowTitle(title);
}

void MainWindow::set_project_widgets_enabled(const bool is_enabled)
{
    const bool is_project_open = is_enabled && m_project_manager.is_project_open();

    // File -> New Project.
    m_ui->action_file_new_project->setEnabled(is_enabled);
    m_action_new_project->setEnabled(is_enabled);

    // File -> Open Project.
    m_ui->action_file_open_project->setEnabled(is_enabled);
    m_action_open_project->setEnabled(is_enabled);

    // File -> Open Recent.
    m_ui->menu_open_recent->setEnabled(is_enabled);

    // File -> Open Built-in Project.
    m_ui->menu_file_open_builtin_project->setEnabled(is_enabled);

    // File -> Reload Project.
    m_ui->action_file_reload_project->setEnabled(
        is_project_open &&
        m_project_manager.get_project()->has_path());

    // File -> Save Project and Save Project As.
    m_ui->action_file_save_project->setEnabled(is_project_open);
    m_action_save_project->setEnabled(is_project_open);
    m_ui->action_file_save_project_as->setEnabled(is_project_open);

    // Project Explorer.
    m_ui->label_filter->setEnabled(is_project_open);
    m_ui->lineedit_filter->setEnabled(is_project_open);
    m_ui->pushbutton_clear_filter->setEnabled(is_project_open);
    m_ui->treewidget_project_explorer_scene->setEnabled(is_project_open);
}

void MainWindow::set_rendering_widgets_enabled(const bool is_enabled, const bool is_rendering)
{
    const bool is_project_open = is_enabled && m_project_manager.is_project_open();
    const bool allow_starting_rendering = is_project_open && !is_rendering;
    const bool allow_stopping_rendering = is_project_open && is_rendering;

    // Rendering -> Start Interactive Rendering.
    m_ui->action_rendering_start_interactive_rendering->setEnabled(allow_starting_rendering);
    m_action_start_interactive_rendering->setEnabled(allow_starting_rendering);

    // Rendering -> Start Final Rendering.
    m_ui->action_rendering_start_final_rendering->setEnabled(allow_starting_rendering);
    m_action_start_final_rendering->setEnabled(allow_starting_rendering);

    // Rendering -> Stop Rendering.
    m_ui->action_rendering_stop_rendering->setEnabled(allow_stopping_rendering);
    m_action_stop_rendering->setEnabled(allow_stopping_rendering);

    // Rendering -> Render Settings.
    m_ui->action_rendering_render_settings->setEnabled(allow_starting_rendering);
}

void MainWindow::recreate_render_widgets()
{
    remove_render_widgets();

    if (m_project_manager.is_project_open())
        add_render_widgets();
}

void MainWindow::remove_render_widgets()
{
    for (const_each<RenderWidgetCollection> i = m_render_widgets; i; ++i)
        delete i->second;

    m_render_widgets.clear();

    while (m_ui->tab_render_channels->count() > 0)
        m_ui->tab_render_channels->removeTab(0);
}

void MainWindow::add_render_widgets()
{
    const Project* project = m_project_manager.get_project();
    const Frame* frame = project->get_frame();
    const CanvasProperties& props = frame->image().properties();

    const int width = static_cast<int>(props.m_canvas_width);
    const int height = static_cast<int>(props.m_canvas_height);

    add_render_widget(width, height, "RGB");
    add_render_widget(width, height, "Alpha");
    add_render_widget(width, height, "Depth");
    add_render_widget(width, height, "Anomalies");
}

void MainWindow::add_render_widget(
    const int       width,
    const int       height,
    const QString&  label)
{
    // Create a render widget.
    RenderWidget* render_widget = new RenderWidget(width, height);

    // Encapsulate the render widget into another widget that adds a margin around it.
    QWidget* render_widget_wrapper = new QWidget();
    render_widget_wrapper->setLayout(new QGridLayout());
    render_widget_wrapper->layout()->setSizeConstraint(QLayout::SetFixedSize);
    render_widget_wrapper->layout()->setContentsMargins(20, 20, 20, 20);
    render_widget_wrapper->layout()->addWidget(render_widget);

    // Wrap the render widget with a scroll area.
    QScrollArea* scroll_area = new QScrollArea();
    scroll_area->setAlignment(Qt::AlignCenter);
    scroll_area->setWidget(render_widget_wrapper);

    // Create a lebel to display various information such as mouse coordinates, etc.
    QLabel* info_label = new QLabel();
    info_label->setObjectName("info_label");

    // Encapsulate the scroll area inside a tab.
    QWidget* tab = new QWidget();
    tab->setLayout(new QGridLayout());
    tab->layout()->addWidget(scroll_area);
    tab->layout()->addWidget(info_label);

    // Add the tab to the render channels tab bar.
    m_ui->tab_render_channels->addTab(tab, label);

    // Attach a bunch of handlers to the render widget.
    RenderWidgetRecord* record = new RenderWidgetRecord();
    record->m_render_widget = render_widget;
    record->m_zoom_handler.reset(
        new WidgetZoomHandler(
            scroll_area,
            render_widget,
            width,
            height));
    record->m_pan_handler.reset(
        new ScrollAreaPanHandler(
            scroll_area));
    record->m_mouse_tracker.reset(
        new MouseCoordinatesTracker(
            render_widget,
            info_label,
            width,
            height));
    record->m_picking_handler.reset(
        new ScenePickingHandler(
            *record->m_mouse_tracker.get(),
            *m_project_explorer.get(),
            *m_project_manager.get_project()));
    m_render_widgets[label.toStdString()] = record;

    // Attach a contextual menu to the render widget.
    render_widget->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(
        render_widget, SIGNAL(customContextMenuRequested(const QPoint&)),
        this, SLOT(slot_render_widget_context_menu(const QPoint&)));

}

void MainWindow::start_rendering(const bool interactive)
{
    assert(m_project_manager.is_project_open());

    set_project_widgets_enabled(false);
    set_rendering_widgets_enabled(true, true);

    // Internally, clear the main image to transparent black and delete all AOV images.
    Project* project = m_project_manager.get_project();
    project->get_frame()->clear_main_image();
    project->get_frame()->aov_images().clear();

    // In the UI, darken all render widgets.
    for (const_each<RenderWidgetCollection> i = m_render_widgets; i; ++i)
        i->second->m_render_widget->multiply(0.2f);

    const char* configuration_name = interactive ? "interactive" : "final";
    const ParamArray params = get_project_params(configuration_name);

    m_rendering_manager.start_rendering(
        project,
        params,
        interactive,
        m_render_widgets["RGB"]->m_render_widget);
}

namespace
{
    int ask_abort_rendering_confirmation(QWidget* parent)
    {
        QMessageBox msgbox(parent);
        msgbox.setWindowTitle("Abort Rendering?");
        msgbox.setIcon(QMessageBox::Question);
        msgbox.setText("Rendering is in progress.");
        msgbox.setInformativeText("Do you want to abort rendering?");
        msgbox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
        msgbox.setDefaultButton(QMessageBox::No);
        return msgbox.exec();
    }
}

void MainWindow::closeEvent(QCloseEvent* event)
{
    if (m_rendering_manager.is_rendering())
    {
        if (ask_abort_rendering_confirmation(this) != QMessageBox::Yes)
        {
            event->ignore();
            return;
        }

        m_rendering_manager.abort_rendering();
        m_rendering_manager.wait_until_rendering_end();
    }

    if (!can_close_project())
    {
        event->ignore();
        return;
    }

    m_project_manager.close_project();

    slot_save_settings();

    event->accept();
}

void MainWindow::slot_new_project()
{
    if (!can_close_project())
        return;

    m_project_manager.create_project();

    on_project_change();
}

void MainWindow::slot_open_project()
{
    if (!can_close_project())
        return;

    QFileDialog::Options options;
    QString selected_filter;

    QString filepath =
        QFileDialog::getOpenFileName(
            this,
            "Open...",
            m_settings.get_path_optional<QString>(LAST_DIRECTORY_SETTINGS_KEY),
            "Project Files (*.appleseed);;All Files (*.*)",
            &selected_filter,
            options);

    if (!filepath.isEmpty())
    {
        filepath = QDir::toNativeSeparators(filepath);

        open_project(filepath);
        update_recent_files_menu(filepath);
    }
}

void MainWindow::slot_open_recent()
{
    if (!can_close_project())
        return;

    QAction* action = qobject_cast<QAction*>(sender());

    if (action)
    {
        const QString filepath = action->data().toString();
        open_project(filepath);
    }
}

void MainWindow::slot_clear_open_recent_files_menu()
{
    QSettings settings(SettingsOrgString, SettingsRecentFilesEntryString);
    QStringList files;
    settings.setValue(SettingsRecentFileListString, files);
    update_recent_files_menu(files);
}

void MainWindow::slot_open_cornellbox_builtin_project()
{
    if (!can_close_project())
        return;

    const bool successful = m_project_manager.load_builtin_project("cornell_box");
    assert(successful);

    on_project_change();
}

void MainWindow::slot_reload_project()
{
    assert(m_project_manager.is_project_open());
    assert(m_project_manager.get_project()->has_path());

    if (!can_close_project())
        return;

    open_project(m_project_manager.get_project()->get_path());
}

void MainWindow::slot_open_project_complete(const QString& filepath, const bool successful)
{
    if (successful)
        on_project_change();
    else show_project_file_loading_failed_message_box(this, filepath);
}

void MainWindow::slot_save_project()
{
    assert(m_project_manager.is_project_open());

    if (!m_project_manager.get_project()->has_path())
    {
        slot_save_project_as();
        return;
    }

    m_project_manager.save_project();
<commit_msg>fixed a regression introduced with asynchronous loading of project files: enable again ui widgets after a project failed to load.<commit_after>
//
// This source file is part of appleseed.
// Visit http://appleseedhq.net/ for additional information and resources.
//
// This software is released under the MIT license.
//
// Copyright (c) 2010-2013 <NAME>, Jupiter Jazz Limited
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

// Interface header.
#include "mainwindow.h"

// UI definition header.
#include "ui_mainwindow.h"

// appleseed.studio headers.
#include "mainwindow/logwidget.h"
#include "utility/interop.h"
#include "utility/settingskeys.h"
#include "utility/tweaks.h"

// appleseed.shared headers.
#include "application/application.h"

// appleseed.renderer headers.
#include "renderer/api/aov.h"
#include "renderer/api/frame.h"
#include "renderer/api/log.h"
#include "renderer/api/project.h"
#include "renderer/api/surfaceshader.h"

// appleseed.foundation headers.
#include "foundation/core/appleseed.h"
#include "foundation/image/canvasproperties.h"
#include "foundation/image/color.h"
#include "foundation/image/image.h"
#include "foundation/platform/compiler.h"
#include "foundation/platform/system.h"
#include "foundation/platform/types.h"
#include "foundation/utility/containers/dictionary.h"
#include "foundation/utility/autoreleaseptr.h"
#include "foundation/utility/foreach.h"
#include "foundation/utility/settings.h"
#include "foundation/utility/string.h"

// Qt headers.
#include <QAction>
#include <QActionGroup>
#include <QApplication>
#include <QDir>
#include <QFileDialog>
#include <QGridLayout>
#include <QIcon>
#include <QLabel>
#include <QMessageBox>
#include <QMetaType>
#include <QSettings>
#include <QStatusBar>
#include <QString>
#include <QStringList>
#include <QWidget>

// boost headers.
#include "boost/filesystem/path.hpp"

// Standard headers.
#include <algorithm>
#include <cassert>

using namespace appleseed::shared;
using namespace boost;
using namespace foundation;
using namespace renderer;
using namespace std;

namespace appleseed {
namespace studio {

//
// MainWindow class implementation.
//

MainWindow::MainWindow(QWidget* parent)
  : QMainWindow(parent)
  , m_ui(new Ui::MainWindow())
  , m_rendering_manager(m_status_bar)
{
    m_ui->setupUi(this);

    statusBar()->addWidget(&m_status_bar);

    build_menus();
    build_toolbar();
    build_log();
    build_project_explorer();

    build_connections();

    print_startup_information();
    slot_load_settings();

    update_project_explorer();
    remove_render_widgets();
    update_workspace();

    showMaximized();
}

MainWindow::~MainWindow()
{
    delete m_ui;
}

void MainWindow::slot_recreate_render_widgets()
{
    recreate_render_widgets();
}

void MainWindow::build_menus()
{
    //
    // File menu.
    //

    m_ui->action_file_new_project->setShortcut(QKeySequence::New);
    connect(m_ui->action_file_new_project, SIGNAL(triggered()), SLOT(slot_new_project()));

    m_ui->action_file_open_project->setShortcut(QKeySequence::Open);
    connect(m_ui->action_file_open_project, SIGNAL(triggered()), SLOT(slot_open_project()));

    build_recent_files_menu();

    connect(m_ui->action_file_open_builtin_project_cornellbox, SIGNAL(triggered()), SLOT(slot_open_cornellbox_builtin_project()));
    connect(m_ui->action_file_reload_project, SIGNAL(triggered()), SLOT(slot_reload_project()));

    m_ui->action_file_save_project->setShortcut(QKeySequence::Save);
    connect(m_ui->action_file_save_project, SIGNAL(triggered()), SLOT(slot_save_project()));

    m_ui->action_file_save_project_as->setShortcut(QKeySequence::SaveAs);
    connect(m_ui->action_file_save_project_as, SIGNAL(triggered()), this, SLOT(slot_save_project_as()));

    m_ui->action_file_exit->setShortcut(QKeySequence::Quit);
    connect(m_ui->action_file_exit, SIGNAL(triggered()), SLOT(close()));

    //
    // View menu.
    //

    m_ui->menu_view->addAction(m_ui->project_explorer->toggleViewAction());
    m_ui->menu_view->addAction(m_ui->log->toggleViewAction());

    //
    // Rendering menu.
    //

    connect(m_ui->action_rendering_start_interactive_rendering, SIGNAL(triggered()), SLOT(slot_start_interactive_rendering()));
    connect(m_ui->action_rendering_start_final_rendering, SIGNAL(triggered()), SLOT(slot_start_final_rendering()));
    connect(m_ui->action_rendering_stop_rendering, SIGNAL(triggered()), SLOT(slot_stop_rendering()));
    connect(m_ui->action_rendering_render_settings, SIGNAL(triggered()), SLOT(slot_show_render_settings_window()));

    //
    // Diagnostics menu.
    //

    build_override_shading_menu_item();

    //
    // Debug menu.
    //

    connect(m_ui->action_debug_tests, SIGNAL(triggered()), SLOT(slot_show_test_window()));
    connect(m_ui->action_debug_benchmarks, SIGNAL(triggered()), SLOT(slot_show_benchmark_window()));

    //
    // Tools menu.
    //

    connect(m_ui->action_tools_save_settings, SIGNAL(triggered()), SLOT(slot_save_settings()));
    connect(m_ui->action_tools_reload_settings, SIGNAL(triggered()), SLOT(slot_load_settings()));

    //
    // Help menu.
    //

    connect(m_ui->action_help_about, SIGNAL(triggered()), SLOT(slot_show_about_window()));
}

void MainWindow::build_override_shading_menu_item()
{
    QActionGroup* action_group = new QActionGroup(this);

    connect(
        m_ui->action_diagnostics_override_shading_no_override, SIGNAL(triggered()),
        &m_rendering_manager, SLOT(slot_clear_shading_override()));

    action_group->addAction(m_ui->action_diagnostics_override_shading_no_override);

    for (int i = 0; i < DiagnosticSurfaceShader::ShadingModeCount; ++i)
    {
        const char* shading_mode_value = DiagnosticSurfaceShader::ShadingModeNames[i].m_key;
        const char* shading_mode_name = DiagnosticSurfaceShader::ShadingModeNames[i].m_value;

        QAction* action = new QAction(this);

        action->setCheckable(true);

        action->setObjectName(
            QString::fromAscii("action_diagnostics_override_shading_") + shading_mode_value);

        action->setText(
            QApplication::translate(
                objectName().toAscii().constData(),
                shading_mode_name,
                0,
                QApplication::UnicodeUTF8));

        const int shortcut_number = i + 1;

        if (shortcut_number <= 9)
        {
            const QString shortcut =
                QApplication::translate(
                    objectName().toAscii().constData(),
                    QString::fromAscii("Ctrl+Shift+%1").arg(shortcut_number).toAscii().constData(),
                    0,
                    QApplication::UnicodeUTF8);

            action->setShortcut(shortcut);
        }

        action->setData(shading_mode_value);

        connect(
            action, SIGNAL(triggered()),
            &m_rendering_manager, SLOT(slot_set_shading_override()));

        m_ui->menu_diagnostics_override_shading->addAction(action);

        action_group->addAction(action);
    }
}

void MainWindow::update_override_shading_menu_item()
{
    const ParamArray project_params = get_project_params("interactive");
    const ParamArray shading_engine_params = project_params.child("shading_engine");

    if (shading_engine_params.dictionaries().exist("override_shading"))
    {
        const string shading_mode =
            shading_engine_params.child("override_shading").get_optional<string>("mode", "coverage");

        for (const_each<QList<QAction*> > i = m_ui->menu_diagnostics_override_shading->actions(); i; ++i)
        {
            QAction* action = *i;

            if (action->data().toString().toStdString() == shading_mode)
            {
                action->activate(QAction::Trigger);
                break;
            }
        }
    }
    else
    {
        m_ui->action_diagnostics_override_shading_no_override->activate(QAction::Trigger);
    }
}

namespace
{
    const int MaxRecentlyOpenedFiles = 5;
    const char* SettingsOrgString = "com.appleseed.studio";
    const char* SettingsRecentFilesEntryString = "appleseed.studio Recent Files";
    const char* SettingsRecentFileListString = "recent_file_list";
}

void MainWindow::build_recent_files_menu()
{
    assert(m_recently_opened.empty());
    m_recently_opened.reserve(MaxRecentlyOpenedFiles);

    for (int i = 0; i < MaxRecentlyOpenedFiles; ++i)
    {
        QAction* action = new QAction(this);
        action->setVisible(false);

        connect(action, SIGNAL(triggered()), SLOT(slot_open_recent()));

        m_ui->menu_open_recent->addAction(action);
        m_recently_opened.push_back(action);
    }

    QSettings settings(SettingsOrgString, SettingsRecentFilesEntryString);
    QStringList files = settings.value(SettingsRecentFileListString).toStringList();
    update_recent_files_menu(files);

    m_ui->menu_open_recent->addSeparator();
    m_clear_open_recent_menu = new QAction(this);
    m_clear_open_recent_menu->setText("&Clear Menu");
    connect(m_clear_open_recent_menu, SIGNAL(triggered()), SLOT(slot_clear_open_recent_files_menu()));
    m_ui->menu_open_recent->addAction(m_clear_open_recent_menu);
}

void MainWindow::update_recent_files_menu(const QString& filepath)
{
    QSettings settings(SettingsOrgString, SettingsRecentFilesEntryString);
    QStringList files = settings.value(SettingsRecentFileListString).toStringList();
    files.removeAll(filepath);
    files.prepend(filepath);

    while (files.size() > MaxRecentlyOpenedFiles)
        files.removeLast();

    settings.setValue(SettingsRecentFileListString, files);
    update_recent_files_menu(files);
}

void MainWindow::update_recent_files_menu(const QStringList& files)
{
    const int num_recent_files = min(files.size(), MaxRecentlyOpenedFiles);

    for (int i = 0; i < num_recent_files; ++i)
    {
        const QString filepath = files[i];
        const QString text = tr("&%1 %2").arg(i + 1).arg(filepath);

        m_recently_opened[i]->setText(text);
        m_recently_opened[i]->setData(filepath);
        m_recently_opened[i]->setVisible(true);
    }

    for (int i = num_recent_files; i < MaxRecentlyOpenedFiles; ++i)
        m_recently_opened[i]->setVisible(false);
}

void MainWindow::build_toolbar()
{
    m_action_new_project = new QAction(QIcon(":/icons/page_white.png"), "&New", this);
    connect(m_action_new_project, SIGNAL(triggered()), this, SLOT(slot_new_project()));
    m_ui->main_toolbar->addAction(m_action_new_project);

    m_action_open_project = new QAction(QIcon(":/icons/folder.png"), "&Open", this);
    connect(m_action_open_project, SIGNAL(triggered()), this, SLOT(slot_open_project()));
    m_ui->main_toolbar->addAction(m_action_open_project);

    m_action_save_project = new QAction(QIcon(":/icons/disk.png"), "&Save", this);
    connect(m_action_save_project, SIGNAL(triggered()), this, SLOT(slot_save_project()));
    m_ui->main_toolbar->addAction(m_action_save_project);

    m_ui->main_toolbar->addSeparator();

    m_action_start_interactive_rendering = new QAction(QIcon(":/icons/film_go.png"), "Start &Interactive Rendering", this);
    connect(m_action_start_interactive_rendering, SIGNAL(triggered()), this, SLOT(slot_start_interactive_rendering()));
    m_ui->main_toolbar->addAction(m_action_start_interactive_rendering);

    m_action_start_final_rendering = new QAction(QIcon(":/icons/cog_go.png"), "Start &Final Rendering", this);
    connect(m_action_start_final_rendering, SIGNAL(triggered()), this, SLOT(slot_start_final_rendering()));
    m_ui->main_toolbar->addAction(m_action_start_final_rendering);

    m_action_stop_rendering = new QAction(QIcon(":/icons/cross.png"), "S&top Rendering", this);
    connect(m_action_stop_rendering, SIGNAL(triggered()), this, SLOT(slot_stop_rendering()));
    m_ui->main_toolbar->addAction(m_action_stop_rendering);
}

LogWidget* MainWindow::create_log_widget() const
{
    LogWidget* log_widget = new LogWidget(m_ui->log_contents);
    m_ui->log_contents->layout()->addWidget(log_widget);

    log_widget->setObjectName(QString::fromUtf8("textedit_log"));
    log_widget->setUndoRedoEnabled(false);
    log_widget->setLineWrapMode(QTextEdit::NoWrap);
    log_widget->setReadOnly(true);
    log_widget->setTextInteractionFlags(Qt::TextSelectableByMouse);
    log_widget->setStyleSheet("QTextEdit { border: 0px; }");

    QFont font;
    font.setStyleHint(QFont::TypeWriter);
#if defined _WIN32
    font.setFamily(QString::fromUtf8("Consolas"));
#elif defined __APPLE__
    font.setFamily(QString::fromUtf8("Monaco"));
#else
    font.setFamily(QString::fromUtf8("Courier New"));
#endif
    font.setPixelSize(11);
    log_widget->setFont(font);

    return log_widget;
}

void MainWindow::build_log()
{
    LogWidget* log_widget = create_log_widget();

    m_log_target.reset(new QtLogTarget(log_widget));

    global_logger().add_target(m_log_target.get());
}

void MainWindow::build_project_explorer()
{
    m_ui->treewidget_project_explorer_scene->setColumnWidth(0, 220);    // name
    m_ui->treewidget_project_explorer_scene->setColumnWidth(1, 75);     // render layer

    disable_mac_focus_rect(m_ui->treewidget_project_explorer_scene);
    disable_mac_focus_rect(m_ui->treewidget_project_explorer_renders);

    connect(
        m_ui->lineedit_filter, SIGNAL(textChanged(const QString&)),
        this, SLOT(slot_filter_text_changed(const QString&)));

    connect(
        m_ui->pushbutton_clear_filter, SIGNAL(clicked()),
        this, SLOT(slot_clear_filter()));

    m_ui->pushbutton_clear_filter->setEnabled(false);
}

void MainWindow::build_connections()
{
    connect(
        &m_project_manager, SIGNAL(signal_load_project_async_complete(const QString&, const bool)),
        this, SLOT(slot_open_project_complete(const QString&, const bool)));

    connect(
        &m_rendering_manager, SIGNAL(signal_rendering_end()),
        this, SLOT(slot_rendering_end()));

    connect(
        &m_rendering_manager, SIGNAL(signal_camera_changed()),
        this, SLOT(slot_camera_changed()));
}

void MainWindow::print_startup_information()
{
    RENDERER_LOG_INFO(
        "%s, %s configuration\n"
        "compiled on %s at %s using %s version %s",
        Appleseed::get_synthetic_version_string(),
        Appleseed::get_lib_configuration(),
        Appleseed::get_lib_compilation_date(),
        Appleseed::get_lib_compilation_time(),
        Compiler::get_compiler_name(),
        Compiler::get_compiler_version());

    RENDERER_LOG_INFO(
        "system information:\n"
        "  L1 data cache    size %s, line size %s\n"
        "  L2 cache         size %s, line size %s\n"
        "  L3 cache         size %s, line size %s\n",
        pretty_size(System::get_l1_data_cache_size()).c_str(),
        pretty_size(System::get_l1_data_cache_line_size()).c_str(),
        pretty_size(System::get_l2_cache_size()).c_str(),
        pretty_size(System::get_l2_cache_line_size()).c_str(),
        pretty_size(System::get_l3_cache_size()).c_str(),
        pretty_size(System::get_l3_cache_line_size()).c_str());
}

ParamArray MainWindow::get_project_params(const char* configuration_name) const
{
    ParamArray params;

    Configuration* configuration =
        m_project_manager.is_project_open()
            ? m_project_manager.get_project()->configurations().get_by_name(configuration_name)
            : 0;

    if (configuration && configuration->get_base())
        params = configuration->get_base()->get_parameters();

    params.merge(m_settings);

    if (configuration)
        params.merge(configuration->get_parameters());

    return params;
}

namespace
{
    int show_modified_project_message_box(QWidget* parent)
    {
        QMessageBox msgbox(parent);
        msgbox.setWindowTitle("Save Changes?");
        msgbox.setIcon(QMessageBox::Question);
        msgbox.setText("The project has been modified.");
        msgbox.setInformativeText("Do you want to save your changes?");
        msgbox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
        msgbox.setDefaultButton(QMessageBox::Save);
        return msgbox.exec();
    }

    void show_project_file_loading_failed_message_box(QWidget* parent, const QString& filepath)
    {
        QMessageBox msgbox(parent);
        msgbox.setWindowTitle("Loading Error");
        msgbox.setIcon(QMessageBox::Critical);
        msgbox.setText("Failed to load the project file " + filepath + ".");
        msgbox.setInformativeText(
            "The project file may be invalid, corrupted or missing. "
            "Please look at the Log window for details.");
        msgbox.setStandardButtons(QMessageBox::Ok);
        msgbox.exec();
    }
}

void MainWindow::open_project(const QString& filepath)
{
    set_project_widgets_enabled(false);
    set_rendering_widgets_enabled(false, false);

    const filesystem::path path(filepath.toStdString());

    m_settings.insert_path(
        LAST_DIRECTORY_SETTINGS_KEY,
        path.parent_path().string());

    m_project_manager.load_project(filepath.toAscii().constData());
}

bool MainWindow::can_close_project()
{
    // No project open: no problem.
    if (!m_project_manager.is_project_open())
        return true;

    // Unmodified project: no problem.
    if (!m_project_manager.is_project_dirty())
        return true;

    // The current project has been modified, ask the user what to do.
    switch (show_modified_project_message_box(this))
    {
      case QMessageBox::Save:
        slot_save_project();
        return true;

      case QMessageBox::Discard:
        return true;

      case QMessageBox::Cancel:
        return false;
    }

    assert(!"Should never be reached.");
    return false;
}

void MainWindow::on_project_change()
{
    update_project_explorer();
    recreate_render_widgets();

    update_override_shading_menu_item();

    if (m_render_settings_window.get())
        m_render_settings_window->reload();

    m_status_bar.clear();

    update_workspace();
}

void MainWindow::update_workspace()
{
    update_window_title();
    set_project_widgets_enabled(true);
    set_rendering_widgets_enabled(true, false);
}

void MainWindow::update_project_explorer()
{
    m_ui->lineedit_filter->clear();
    m_ui->treewidget_project_explorer_scene->clear();

    if (m_project_manager.is_project_open())
    {
        m_project_explorer.reset(
            new ProjectExplorer(
                m_ui->treewidget_project_explorer_scene,    
                *m_project_manager.get_project(),
                m_settings));

        connect(
            m_project_explorer.get(), SIGNAL(signal_project_modified()),
            this, SLOT(slot_project_modified()));

        connect(
            m_project_explorer.get(), SIGNAL(signal_frame_modified()),
            this, SLOT(slot_recreate_render_widgets()));
    }
    else
    {
        m_project_explorer.reset();
    }
}

void MainWindow::update_window_title()
{
    QString title;

    if (m_project_manager.is_project_open())
    {
        if (m_project_manager.is_project_dirty())
            title.append("* ");

        title.append(QString::fromStdString(m_project_manager.get_project_display_name()));
        title.append(" - ");
    }

    title.append("appleseed.studio");

    setWindowTitle(title);
}

void MainWindow::set_project_widgets_enabled(const bool is_enabled)
{
    const bool is_project_open = is_enabled && m_project_manager.is_project_open();

    // File -> New Project.
    m_ui->action_file_new_project->setEnabled(is_enabled);
    m_action_new_project->setEnabled(is_enabled);

    // File -> Open Project.
    m_ui->action_file_open_project->setEnabled(is_enabled);
    m_action_open_project->setEnabled(is_enabled);

    // File -> Open Recent.
    m_ui->menu_open_recent->setEnabled(is_enabled);

    // File -> Open Built-in Project.
    m_ui->menu_file_open_builtin_project->setEnabled(is_enabled);

    // File -> Reload Project.
    m_ui->action_file_reload_project->setEnabled(
        is_project_open &&
        m_project_manager.get_project()->has_path());

    // File -> Save Project and Save Project As.
    m_ui->action_file_save_project->setEnabled(is_project_open);
    m_action_save_project->setEnabled(is_project_open);
    m_ui->action_file_save_project_as->setEnabled(is_project_open);

    // Project Explorer.
    m_ui->label_filter->setEnabled(is_project_open);
    m_ui->lineedit_filter->setEnabled(is_project_open);
    m_ui->pushbutton_clear_filter->setEnabled(is_project_open);
    m_ui->treewidget_project_explorer_scene->setEnabled(is_project_open);
}

void MainWindow::set_rendering_widgets_enabled(const bool is_enabled, const bool is_rendering)
{
    const bool is_project_open = is_enabled && m_project_manager.is_project_open();
    const bool allow_starting_rendering = is_project_open && !is_rendering;
    const bool allow_stopping_rendering = is_project_open && is_rendering;

    // Rendering -> Start Interactive Rendering.
    m_ui->action_rendering_start_interactive_rendering->setEnabled(allow_starting_rendering);
    m_action_start_interactive_rendering->setEnabled(allow_starting_rendering);

    // Rendering -> Start Final Rendering.
    m_ui->action_rendering_start_final_rendering->setEnabled(allow_starting_rendering);
    m_action_start_final_rendering->setEnabled(allow_starting_rendering);

    // Rendering -> Stop Rendering.
    m_ui->action_rendering_stop_rendering->setEnabled(allow_stopping_rendering);
    m_action_stop_rendering->setEnabled(allow_stopping_rendering);

    // Rendering -> Render Settings.
    m_ui->action_rendering_render_settings->setEnabled(allow_starting_rendering);
}

void MainWindow::recreate_render_widgets()
{
    remove_render_widgets();

    if (m_project_manager.is_project_open())
        add_render_widgets();
}

void MainWindow::remove_render_widgets()
{
    for (const_each<RenderWidgetCollection> i = m_render_widgets; i; ++i)
        delete i->second;

    m_render_widgets.clear();

    while (m_ui->tab_render_channels->count() > 0)
        m_ui->tab_render_channels->removeTab(0);
}

void MainWindow::add_render_widgets()
{
    const Project* project = m_project_manager.get_project();
    const Frame* frame = project->get_frame();
    const CanvasProperties& props = frame->image().properties();

    const int width = static_cast<int>(props.m_canvas_width);
    const int height = static_cast<int>(props.m_canvas_height);

    add_render_widget(width, height, "RGB");
    add_render_widget(width, height, "Alpha");
    add_render_widget(width, height, "Depth");
    add_render_widget(width, height, "Anomalies");
}

void MainWindow::add_render_widget(
    const int       width,
    const int       height,
    const QString&  label)
{
    // Create a render widget.
    RenderWidget* render_widget = new RenderWidget(width, height);

    // Encapsulate the render widget into another widget that adds a margin around it.
    QWidget* render_widget_wrapper = new QWidget();
    render_widget_wrapper->setLayout(new QGridLayout());
    render_widget_wrapper->layout()->setSizeConstraint(QLayout::SetFixedSize);
    render_widget_wrapper->layout()->setContentsMargins(20, 20, 20, 20);
    render_widget_wrapper->layout()->addWidget(render_widget);

    // Wrap the render widget with a scroll area.
    QScrollArea* scroll_area = new QScrollArea();
    scroll_area->setAlignment(Qt::AlignCenter);
    scroll_area->setWidget(render_widget_wrapper);

    // Create a lebel to display various information such as mouse coordinates, etc.
    QLabel* info_label = new QLabel();
    info_label->setObjectName("info_label");

    // Encapsulate the scroll area inside a tab.
    QWidget* tab = new QWidget();
    tab->setLayout(new QGridLayout());
    tab->layout()->addWidget(scroll_area);
    tab->layout()->addWidget(info_label);

    // Add the tab to the render channels tab bar.
    m_ui->tab_render_channels->addTab(tab, label);

    // Attach a bunch of handlers to the render widget.
    RenderWidgetRecord* record = new RenderWidgetRecord();
    record->m_render_widget = render_widget;
    record->m_zoom_handler.reset(
        new WidgetZoomHandler(
            scroll_area,
            render_widget,
            width,
            height));
    record->m_pan_handler.reset(
        new ScrollAreaPanHandler(
            scroll_area));
    record->m_mouse_tracker.reset(
        new MouseCoordinatesTracker(
            render_widget,
            info_label,
            width,
            height));
    record->m_picking_handler.reset(
        new ScenePickingHandler(
            *record->m_mouse_tracker.get(),
            *m_project_explorer.get(),
            *m_project_manager.get_project()));
    m_render_widgets[label.toStdString()] = record;

    // Attach a contextual menu to the render widget.
    render_widget->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(
        render_widget, SIGNAL(customContextMenuRequested(const QPoint&)),
        this, SLOT(slot_render_widget_context_menu(const QPoint&)));

}

void MainWindow::start_rendering(const bool interactive)
{
    assert(m_project_manager.is_project_open());

    set_project_widgets_enabled(false);
    set_rendering_widgets_enabled(true, true);

    // Internally, clear the main image to transparent black and delete all AOV images.
    Project* project = m_project_manager.get_project();
    project->get_frame()->clear_main_image();
    project->get_frame()->aov_images().clear();

    // In the UI, darken all render widgets.
    for (const_each<RenderWidgetCollection> i = m_render_widgets; i; ++i)
        i->second->m_render_widget->multiply(0.2f);

    const char* configuration_name = interactive ? "interactive" : "final";
    const ParamArray params = get_project_params(configuration_name);

    m_rendering_manager.start_rendering(
        project,
        params,
        interactive,
        m_render_widgets["RGB"]->m_render_widget);
}

namespace
{
    int ask_abort_rendering_confirmation(QWidget* parent)
    {
        QMessageBox msgbox(parent);
        msgbox.setWindowTitle("Abort Rendering?");
        msgbox.setIcon(QMessageBox::Question);
        msgbox.setText("Rendering is in progress.");
        msgbox.setInformativeText("Do you want to abort rendering?");
        msgbox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
        msgbox.setDefaultButton(QMessageBox::No);
        return msgbox.exec();
    }
}

void MainWindow::closeEvent(QCloseEvent* event)
{
    if (m_rendering_manager.is_rendering())
    {
        if (ask_abort_rendering_confirmation(this) != QMessageBox::Yes)
        {
            event->ignore();
            return;
        }

        m_rendering_manager.abort_rendering();
        m_rendering_manager.wait_until_rendering_end();
    }

    if (!can_close_project())
    {
        event->ignore();
        return;
    }

    m_project_manager.close_project();

    slot_save_settings();

    event->accept();
}

void MainWindow::slot_new_project()
{
    if (!can_close_project())
        return;

    m_project_manager.create_project();

    on_project_change();
}

void MainWindow::slot_open_project()
{
    if (!can_close_project())
        return;

    QFileDialog::Options options;
    QString selected_filter;

    QString filepath =
        QFileDialog::getOpenFileName(
            this,
            "Open...",
            m_settings.get_path_optional<QString>(LAST_DIRECTORY_SETTINGS_KEY),
            "Project Files (*.appleseed);;All Files (*.*)",
            &selected_filter,
            options);

    if (!filepath.isEmpty())
    {
        filepath = QDir::toNativeSeparators(filepath);

        open_project(filepath);
        update_recent_files_menu(filepath);
    }
}

void MainWindow::slot_open_recent()
{
    if (!can_close_project())
        return;

    QAction* action = qobject_cast<QAction*>(sender());

    if (action)
    {
        const QString filepath = action->data().toString();
        open_project(filepath);
    }
}

void MainWindow::slot_clear_open_recent_files_menu()
{
    QSettings settings(SettingsOrgString, SettingsRecentFilesEntryString);
    QStringList files;
    settings.setValue(SettingsRecentFileListString, files);
    update_recent_files_menu(files);
}

void MainWindow::slot_open_cornellbox_builtin_project()
{
    if (!can_close_project())
        return;

    const bool successful = m_project_manager.load_builtin_project("cornell_box");
    assert(successful);

    on_project_change();
}

void MainWindow::slot_reload_project()
{
    assert(m_project_manager.is_project_open());
    assert(m_project_manager.get_project()->has_path());

    if (!can_close_project())
        return;

    open_project(m_project_manager.get_project()->get_path());
}

void MainWindow::slot_open_project_complete(const QString& filepath, const bool successful)
{
    if (successful)
        on_project_change();
    else
    {
        show_project_file_loading_failed_message_box(this, filepath);
        update_workspace();
    }
}

void MainWindow::slot_save_project()
{
    assert(m_project_manager.is_project_open());

    if (!m_project_manager.get_project()->has_path())
    {
        slot_save_project_as();
        return;
    }

    m_project_manager.save_project();

{'fraction_non_alphanumeric': 0.11779610492845786, 'fraction_numerical': 0.001987281399046105, 'mean_word_length': 3.9666886000987005, 'pattern_counts': {'":': 12, '<': 67, '<?xml version=': 0, '>': 469, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6638518', 'n_tokens_mistral': 19226, 'n_tokens_neox': 18556, 'n_words': 3118}