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 // Interface header.
31 #include "entityeditor.h"
32 
33 // appleseed.studio headers.
34 #include "mainwindow/project/entitybrowserwindow.h"
35 #include "mainwindow/project/entityinputwidget.h"
36 #include "mainwindow/project/tools.h"
37 #include "utility/doubleslider.h"
38 #include "utility/interop.h"
39 #include "utility/miscellaneous.h"
40 #include "utility/mousewheelfocuseventfilter.h"
41 #include "utility/settingskeys.h"
42 
43 // appleseed.renderer headers.
44 #include "renderer/api/project.h"
45 
46 // appleseed.foundation headers.
47 #include "foundation/image/color.h"
48 #include "foundation/math/scalar.h"
49 #include "foundation/utility/foreach.h"
50 #include "foundation/utility/iostreamop.h"
51 #include "foundation/utility/string.h"
52 
53 // Qt headers.
54 #include <QCheckBox>
55 #include <QColor>
56 #include <QColorDialog>
57 #include <QComboBox>
58 #include <QDialogButtonBox>
59 #include <QDoubleValidator>
60 #include <QFileDialog>
61 #include <QFont>
62 #include <QFormLayout>
63 #include <QHBoxLayout>
64 #include <QIntValidator>
65 #include <QLabel>
66 #include <QLineEdit>
67 #include <QPushButton>
68 #include <QShortcut>
69 #include <QSlider>
70 #include <QString>
71 #include <Qt>
72 #include <QToolButton>
73 #include <QVariant>
74 #include <QVBoxLayout>
75 
76 // Boost headers.
77 #include "boost/filesystem/operations.hpp"
78 #include "boost/filesystem/path.hpp"
79 
80 // Standard headers.
81 #include <cassert>
82 #include <cmath>
83 #include <limits>
84 #include <sstream>
85 #include <utility>
86 
87 using namespace foundation;
88 using namespace renderer;
89 using namespace std;
90 namespace bf = boost::filesystem;
91 
92 namespace appleseed {
93 namespace studio {
94 
EntityEditor(QWidget * parent,const Project & project,ParamArray & settings,unique_ptr<IFormFactory> form_factory,unique_ptr<IEntityBrowser> entity_browser,unique_ptr<CustomEntityUI> custom_ui,const Dictionary & values)95 EntityEditor::EntityEditor(
96     QWidget*                        parent,
97     const Project&                  project,
98     ParamArray&                     settings,
99     unique_ptr<IFormFactory>        form_factory,
100     unique_ptr<IEntityBrowser>      entity_browser,
101     unique_ptr<CustomEntityUI>      custom_ui,
102     const Dictionary&               values)
103   : QObject(parent)
104   , m_parent(parent)
105   , m_project(project)
106   , m_settings(settings)
107   , m_form_factory(move(form_factory))
108   , m_entity_browser(move(entity_browser))
109   , m_custom_ui(move(custom_ui))
110 {
111     if (m_parent->layout() != nullptr)
112     {
113         clear_layout(m_parent->layout());
114         delete m_parent->layout();
115     }
116 
117     m_top_layout = new QVBoxLayout(m_parent);
118     m_top_layout->setMargin(7);
119 
120     create_connections();
121     rebuild_form(values);
122 }
123 
get_values() const124 Dictionary EntityEditor::get_values() const
125 {
126     Dictionary values;
127 
128     if (m_custom_ui.get())
129         values.merge(m_custom_ui->get_values());
130 
131     values.merge(m_widget_proxies.get_values());
132 
133     return values;
134 }
135 
rebuild_form(const Dictionary & values)136 void EntityEditor::rebuild_form(const Dictionary& values)
137 {
138     // The mappings were removed when the widgets were deleted.
139     clear_layout(m_top_layout);
140     m_widget_proxies.clear();
141 
142     // Collect input metadata.
143     m_form_factory->update(values, m_input_metadata);
144 
145     // Create corresponding input widgets.
146     create_form_layout();
147     for (const_each<InputMetadataCollection> i = m_input_metadata; i; ++i)
148     {
149         const bool input_widget_visible = is_input_widget_visible(*i, values);
150         create_input_widgets(*i, input_widget_visible);
151     }
152 
153     if (m_custom_ui.get())
154         m_custom_ui->create_widgets(m_top_layout, values);
155 }
156 
create_form_layout()157 void EntityEditor::create_form_layout()
158 {
159     m_form_layout = new QFormLayout();
160     m_form_layout->setLabelAlignment(Qt::AlignRight);
161     m_form_layout->setSpacing(10);
162     m_form_layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
163     m_top_layout->addLayout(m_form_layout);
164 }
165 
create_connections()166 void EntityEditor::create_connections()
167 {
168     if (m_custom_ui.get())
169     {
170         connect(
171             m_custom_ui.get(), SIGNAL(signal_apply()),
172             SLOT(slot_apply()));
173     }
174 }
175 
get_input_metadata(const string & name) const176 const Dictionary& EntityEditor::get_input_metadata(const string& name) const
177 {
178     for (const_each<InputMetadataCollection> i = m_input_metadata; i; ++i)
179     {
180         const Dictionary& metadata = *i;
181 
182         if (metadata.get<string>("name") == name)
183             return metadata;
184     }
185 
186     static Dictionary empty_dictionary;
187     return empty_dictionary;
188 }
189 
is_input_widget_visible(const Dictionary & metadata,const Dictionary & values) const190 bool EntityEditor::is_input_widget_visible(const Dictionary& metadata, const Dictionary& values) const
191 {
192     if (!metadata.dictionaries().exist("visible_if"))
193         return true;
194 
195     // Conditions in a visible_if clause must be all true for the input widget to be visible
196     // (in other words they are combined with AND operations).
197 
198     const StringDictionary& visible_if = metadata.dictionary("visible_if").strings();
199 
200     for (const auto& condition : visible_if)
201     {
202         const char* key = condition.key();
203         const char* value = condition.value();
204 
205         const bool condition_met =
206             values.strings().exist(key)
207                 ? values.strings().get<string>(key) == value
208                 : get_input_metadata(key).get<string>("default") == value;
209 
210         if (!condition_met)
211             return false;
212     }
213 
214     return true;
215 }
216 
create_input_widgets(const Dictionary & metadata,const bool input_widget_visible)217 void EntityEditor::create_input_widgets(const Dictionary& metadata, const bool input_widget_visible)
218 {
219     const string input_name = metadata.get<string>("name");
220     const string input_type = metadata.get<string>("type");
221 
222     unique_ptr<IInputWidgetProxy> widget_proxy =
223         input_type == "text" ? create_text_input_widgets(metadata, input_widget_visible) :
224         input_type == "numeric" ? create_numeric_input_widgets(metadata, input_widget_visible) :
225         input_type == "integer" ? create_integer_input_widgets(metadata, input_widget_visible) :
226         input_type == "boolean" ? create_boolean_input_widgets(metadata, input_widget_visible) :
227         input_type == "enumeration" ? create_enumeration_input_widgets(metadata, input_widget_visible) :
228         input_type == "color" ? create_color_input_widgets(metadata, input_widget_visible) :
229         input_type == "colormap" ? create_colormap_input_widgets(metadata, input_widget_visible) :
230         input_type == "entity" ? create_entity_input_widgets(metadata, input_widget_visible) :
231         input_type == "file" ? create_file_input_widgets(metadata, input_widget_visible) :
232         unique_ptr<IInputWidgetProxy>(nullptr);
233 
234     assert(widget_proxy.get());
235 
236     const bool rebuild_form =
237         metadata.strings().exist("on_change") &&
238         metadata.get<string>("on_change") == "rebuild_form";
239 
240     connect(
241         widget_proxy.get(),
242         SIGNAL(signal_changed()),
243         rebuild_form ? SLOT(slot_rebuild_form()) : SLOT(slot_apply()));
244 
245     m_widget_proxies.insert(input_name, move(widget_proxy));
246 }
247 
248 namespace
249 {
create_label(const Dictionary & metadata)250     QLabel* create_label(const Dictionary& metadata)
251     {
252         QLabel* label = new QLabel(metadata.get<QString>("label") + ":");
253 
254         if (metadata.get<QString>("use") == "required")
255         {
256             QFont font;
257             font.setBold(true);
258             label->setFont(font);
259         }
260 
261         return label;
262     }
263 
should_be_focused(const Dictionary & metadata)264     bool should_be_focused(const Dictionary& metadata)
265     {
266         return
267             metadata.strings().exist("focus") &&
268             metadata.strings().get<bool>("focus");
269     }
270 }
271 
create_text_input_widgets(const Dictionary & metadata,const bool input_widget_visible)272 unique_ptr<IInputWidgetProxy> EntityEditor::create_text_input_widgets(const Dictionary& metadata, const bool input_widget_visible)
273 {
274     QLineEdit* line_edit = new QLineEdit(m_parent);
275 
276     if (should_be_focused(metadata))
277     {
278         line_edit->selectAll();
279         line_edit->setFocus();
280     }
281 
282     if (input_widget_visible)
283         m_form_layout->addRow(create_label(metadata), line_edit);
284     else line_edit->hide();
285 
286     unique_ptr<IInputWidgetProxy> widget_proxy(new LineEditProxy(line_edit));
287     widget_proxy->set(metadata.strings().get<string>("value"));
288 
289     return widget_proxy;
290 }
291 
create_numeric_input_widgets(const Dictionary & metadata,const bool input_widget_visible)292 unique_ptr<IInputWidgetProxy> EntityEditor::create_numeric_input_widgets(const Dictionary& metadata, const bool input_widget_visible)
293 {
294     const Dictionary& min = metadata.dictionary("min");
295     const Dictionary& max = metadata.dictionary("max");
296 
297     const double slider_min = min.get<double>("value");
298     const double slider_max = max.get<double>("value");
299 
300     const double validator_min = min.get<string>("type") == "hard" ? slider_min : -numeric_limits<double>::max();
301     const double validator_max = max.get<string>("type") == "hard" ? slider_max : +numeric_limits<double>::max();
302 
303     QLineEdit* line_edit = new QLineEdit(m_parent);
304     line_edit->setMaximumWidth(60);
305     line_edit->setValidator(new QDoubleValidator(validator_min, validator_max, 16, line_edit));
306 
307     DoubleSlider* slider = new DoubleSlider(Qt::Horizontal, m_parent);
308     slider->setRange(slider_min, slider_max);
309     slider->setPageStep((slider_max - slider_min) / 10.0);
310     new MouseWheelFocusEventFilter(slider);
311     auto adaptor = new LineEditDoubleSliderAdaptor(line_edit, slider);
312     connect(slider, SIGNAL(valueChanged(int)), SLOT(slot_apply()));
313 
314     if (should_be_focused(metadata))
315     {
316         line_edit->selectAll();
317         line_edit->setFocus();
318     }
319 
320     if (input_widget_visible)
321     {
322         QHBoxLayout* layout = new QHBoxLayout();
323         layout->setSpacing(6);
324         layout->addWidget(line_edit);
325         layout->addWidget(slider);
326         m_form_layout->addRow(create_label(metadata), layout);
327     }
328     else
329     {
330         line_edit->hide();
331         slider->hide();
332     }
333 
334     const string value = metadata.strings().get<string>("value");
335     if (!value.empty())
336     {
337         // Keep the first value if there is more than one.
338         istringstream istr(value);
339         double val;
340         istr >> val;
341         adaptor->slot_set_line_edit_value(val);
342     }
343 
344     unique_ptr<IInputWidgetProxy> widget_proxy(new LineEditProxy(line_edit));
345 
346     return widget_proxy;
347 }
348 
create_integer_input_widgets(const Dictionary & metadata,const bool input_widget_visible)349 unique_ptr<IInputWidgetProxy> EntityEditor::create_integer_input_widgets(const Dictionary& metadata, const bool input_widget_visible)
350 {
351     const Dictionary& min = metadata.dictionary("min");
352     const Dictionary& max = metadata.dictionary("max");
353 
354     const int slider_min = min.get<int>("value");
355     const int slider_max = max.get<int>("value");
356 
357     const int validator_min = slider_min;
358     const int validator_max = slider_max;
359 
360     QLineEdit* line_edit = new QLineEdit(m_parent);
361     line_edit->setMaximumWidth(60);
362     line_edit->setValidator(new QIntValidator(validator_min, validator_max, line_edit));
363 
364     QSlider* slider = new QSlider(Qt::Horizontal, m_parent);
365     slider->setRange(slider_min, slider_max);
366     new MouseWheelFocusEventFilter(slider);
367     new LineEditSliderAdaptor(line_edit, slider);
368     connect(slider, SIGNAL(valueChanged(int)), SLOT(slot_apply()));
369 
370     if (should_be_focused(metadata))
371     {
372         line_edit->selectAll();
373         line_edit->setFocus();
374     }
375 
376     if (input_widget_visible)
377     {
378         QHBoxLayout* layout = new QHBoxLayout();
379         layout->setSpacing(6);
380         layout->addWidget(line_edit);
381         layout->addWidget(slider);
382         m_form_layout->addRow(create_label(metadata), layout);
383     }
384     else
385     {
386         line_edit->hide();
387         slider->hide();
388     }
389 
390     unique_ptr<IInputWidgetProxy> widget_proxy(new LineEditProxy(line_edit));
391     widget_proxy->set(metadata.strings().get<string>("value"));
392 
393     return widget_proxy;
394 }
395 
create_boolean_input_widgets(const Dictionary & metadata,const bool input_widget_visible)396 unique_ptr<IInputWidgetProxy> EntityEditor::create_boolean_input_widgets(const Dictionary& metadata, const bool input_widget_visible)
397 {
398     QCheckBox* checkbox = new QCheckBox(m_parent);
399 
400     if (should_be_focused(metadata))
401         checkbox->setFocus();
402 
403     if (input_widget_visible)
404         m_form_layout->addRow(create_label(metadata), checkbox);
405     else checkbox->hide();
406 
407     unique_ptr<IInputWidgetProxy> widget_proxy(new CheckBoxProxy(checkbox));
408     widget_proxy->set(metadata.strings().get<string>("value"));
409 
410     return widget_proxy;
411 }
412 
create_enumeration_input_widgets(const Dictionary & metadata,const bool input_widget_visible)413 unique_ptr<IInputWidgetProxy> EntityEditor::create_enumeration_input_widgets(const Dictionary& metadata, const bool input_widget_visible)
414 {
415     QComboBox* combo_box = new QComboBox(m_parent);
416     combo_box->setEditable(false);
417     new MouseWheelFocusEventFilter(combo_box);
418 
419     const StringDictionary& items = metadata.dictionaries().get("items").strings();
420     for (const_each<StringDictionary> i = items; i; ++i)
421         combo_box->addItem(i->key(), i->value<QString>());
422 
423     const QString value = metadata.strings().get<QString>("value");
424     combo_box->setCurrentIndex(combo_box->findData(QVariant::fromValue(value)));
425 
426     if (should_be_focused(metadata))
427         combo_box->setFocus();
428 
429     if (input_widget_visible)
430         m_form_layout->addRow(create_label(metadata), combo_box);
431     else combo_box->hide();
432 
433     unique_ptr<IInputWidgetProxy> widget_proxy(new ComboBoxProxy(combo_box));
434 
435     return widget_proxy;
436 }
437 
create_color_input_widgets(const Dictionary & metadata,const bool input_widget_visible)438 unique_ptr<IInputWidgetProxy> EntityEditor::create_color_input_widgets(const Dictionary& metadata, const bool input_widget_visible)
439 {
440     QLineEdit* line_edit = new QLineEdit(m_parent);
441 
442     QToolButton* picker_button = new QToolButton(m_parent);
443     picker_button->setObjectName("color_picker");
444     const string name = metadata.get<string>("name");
445     connect(picker_button, &QToolButton::clicked, [=]() { emit slot_open_color_picker(QString::fromStdString(name)); });
446 
447     if (should_be_focused(metadata))
448     {
449         line_edit->selectAll();
450         line_edit->setFocus();
451     }
452 
453     if (input_widget_visible)
454     {
455         QHBoxLayout* layout = new QHBoxLayout();
456         layout->setSpacing(6);
457         layout->addWidget(line_edit);
458         layout->addWidget(picker_button);
459         m_form_layout->addRow(create_label(metadata), layout);
460     }
461     else
462     {
463         line_edit->hide();
464         picker_button->hide();
465     }
466 
467     unique_ptr<ColorPickerProxy> widget_proxy(new ColorPickerProxy(line_edit, picker_button));
468 
469     if (metadata.strings().exist("value") &&
470         metadata.strings().exist("wavelength_range_widget"))
471     {
472         const string value = metadata.strings().get<string>("value");
473         const string wr_widget = metadata.get<string>("wavelength_range_widget");
474         const IInputWidgetProxy* wr_widget_proxy = m_widget_proxies.get(wr_widget);
475         if (wr_widget_proxy)
476             widget_proxy->set(value, wr_widget_proxy->get());
477         else widget_proxy->set(value);
478     }
479     else widget_proxy->set("0.0 0.0 0.0");
480 
481     return unique_ptr<IInputWidgetProxy>(move(widget_proxy));
482 }
483 
create_colormap_input_widgets(const Dictionary & metadata,const bool input_widget_visible)484 unique_ptr<IInputWidgetProxy> EntityEditor::create_colormap_input_widgets(const Dictionary& metadata, const bool input_widget_visible)
485 {
486     const string name = metadata.get<string>("name");
487 
488     double validator_min, validator_max;
489 
490     if (metadata.dictionaries().exist("min"))
491     {
492         const Dictionary& min = metadata.dictionary("min");
493         validator_min = min.get<string>("type") == "hard" ? min.get<double>("value") : -numeric_limits<double>::max();
494     }
495     else
496         validator_min = -numeric_limits<double>::max();
497 
498     if (metadata.dictionaries().exist("max"))
499     {
500         const Dictionary& max = metadata.dictionary("max");
501         validator_max = max.get<string>("type") == "hard" ? max.get<double>("value") : +numeric_limits<double>::max();
502     }
503     else
504         validator_max = +numeric_limits<double>::max();
505 
506     const QString default_value = metadata.strings().exist("default") ? metadata.get<QString>("default") : "";
507 
508     ColorMapInputWidget* input_widget = new ColorMapInputWidget(m_parent);
509     input_widget->set_validator(new QDoubleValidatorWithDefault(validator_min, validator_max, 16, default_value));
510     input_widget->set_default_value(default_value);
511 
512     connect(input_widget, &ColorMapInputWidget::signal_bind_button_clicked, [=]() { emit slot_open_entity_browser(QString::fromStdString(name)); });
513 
514     if (should_be_focused(metadata))
515         input_widget->set_focus();
516 
517     if (input_widget_visible)
518         m_form_layout->addRow(create_label(metadata), input_widget);
519     else input_widget->hide();
520 
521     unique_ptr<IInputWidgetProxy> widget_proxy(new ColorMapInputProxy(input_widget));
522     widget_proxy->set(metadata.strings().get<string>("value"));
523 
524     return widget_proxy;
525 }
526 
create_entity_input_widgets(const Dictionary & metadata,const bool input_widget_visible)527 unique_ptr<IInputWidgetProxy> EntityEditor::create_entity_input_widgets(const Dictionary& metadata, const bool input_widget_visible)
528 {
529     const string name = metadata.get<string>("name");
530 
531     EntityInputWidget* input_widget = new EntityInputWidget(m_parent);
532     connect(input_widget, &EntityInputWidget::signal_bind_button_clicked, [=]() { emit slot_open_entity_browser(QString::fromStdString(name)); });
533 
534     if (should_be_focused(metadata))
535         input_widget->set_focus();
536 
537     if (input_widget_visible)
538         m_form_layout->addRow(create_label(metadata), input_widget);
539     else input_widget->hide();
540 
541     unique_ptr<IInputWidgetProxy> widget_proxy(new EntityInputProxy(input_widget));
542     widget_proxy->set(metadata.strings().get<string>("value"));
543 
544     return widget_proxy;
545 }
546 
create_file_input_widgets(const Dictionary & metadata,const bool input_widget_visible)547 unique_ptr<IInputWidgetProxy> EntityEditor::create_file_input_widgets(const Dictionary& metadata, const bool input_widget_visible)
548 {
549     const string name = metadata.get<string>("name");
550 
551     QLineEdit* line_edit = new QLineEdit(m_parent);
552 
553     QPushButton* browse_button = new QPushButton("Browse", m_parent);
554     browse_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
555     connect(browse_button, &QPushButton::clicked, [=]() { emit slot_open_file_picker(QString::fromStdString(name)); });
556 
557     if (should_be_focused(metadata))
558     {
559         line_edit->selectAll();
560         line_edit->setFocus();
561     }
562 
563     if (input_widget_visible)
564     {
565         QHBoxLayout* layout = new QHBoxLayout();
566         layout->setSpacing(6);
567         layout->addWidget(line_edit);
568         layout->addWidget(browse_button);
569         m_form_layout->addRow(create_label(metadata), layout);
570     }
571     else
572     {
573         line_edit->hide();
574         browse_button->hide();
575     }
576 
577     unique_ptr<IInputWidgetProxy> widget_proxy(new LineEditProxy(line_edit));
578     widget_proxy->set(metadata.strings().get<string>("value"));
579 
580     return widget_proxy;
581 }
582 
slot_rebuild_form()583 void EntityEditor::slot_rebuild_form()
584 {
585     rebuild_form(get_values());
586 
587     emit signal_applied(get_values());
588 }
589 
590 namespace
591 {
592     class ForwardAcceptedSignal
593       : public QObject
594     {
595         Q_OBJECT
596 
597       public:
ForwardAcceptedSignal(QObject * parent,const QString & widget_name)598         ForwardAcceptedSignal(QObject* parent, const QString& widget_name)
599           : QObject(parent)
600           , m_widget_name(widget_name)
601         {
602         }
603 
604       public slots:
slot_accept(const QString & page_name,const QString & entity_name)605         void slot_accept(
606             const QString&  page_name,
607             const QString&  entity_name)
608         {
609             emit signal_accepted(m_widget_name, page_name, entity_name);
610         }
611 
612       signals:
613         void signal_accepted(
614             const QString&  widget_name,
615             const QString&  page_name,
616             const QString&  entity_name);
617 
618       private:
619         const QString m_widget_name;
620     };
621 }
622 
slot_open_entity_browser(const QString & widget_name)623 void EntityEditor::slot_open_entity_browser(const QString& widget_name)
624 {
625     const Dictionary& metadata = get_input_metadata(widget_name.toStdString());
626 
627     EntityBrowserWindow* browser_window =
628         new EntityBrowserWindow(
629             m_parent,
630             metadata.get<string>("label"));
631 
632     const Dictionary& entity_types = metadata.dictionaries().get("entity_types");
633 
634     for (const_each<StringDictionary> i = entity_types.strings(); i; ++i)
635     {
636         const string entity_type = i->key();
637         const string entity_label = i->value<string>();
638         const StringDictionary entities = m_entity_browser->get_entities(entity_type);
639         browser_window->add_items_page(entity_type, entity_label, entities);
640     }
641 
642     ForwardAcceptedSignal* forward_signal =
643         new ForwardAcceptedSignal(browser_window, widget_name);
644     connect(
645         browser_window, SIGNAL(signal_accepted(const QString&, const QString&)),
646         forward_signal, SLOT(slot_accept(const QString&, const QString&)));
647     connect(
648         forward_signal, SIGNAL(signal_accepted(const QString&, const QString&, const QString&)),
649         SLOT(slot_entity_browser_accept(const QString&, const QString&, const QString&)));
650 
651     browser_window->showNormal();
652     browser_window->activateWindow();
653 }
654 
slot_entity_browser_accept(QString widget_name,QString page_name,QString entity_name)655 void EntityEditor::slot_entity_browser_accept(QString widget_name, QString page_name, QString entity_name)
656 {
657     IInputWidgetProxy* widget_proxy = m_widget_proxies.get(widget_name.toStdString());
658     widget_proxy->set(entity_name.toStdString());
659     widget_proxy->emit_signal_changed();
660 
661     // Close the entity browser.
662     qobject_cast<QWidget*>(sender()->parent())->close();
663 }
664 
slot_open_color_picker(const QString & widget_name)665 void EntityEditor::slot_open_color_picker(const QString& widget_name)
666 {
667     IInputWidgetProxy* widget_proxy = m_widget_proxies.get(widget_name.toStdString());
668 
669     const Dictionary& metadata = get_input_metadata(widget_name.toStdString());
670     const string wr_widget = metadata.get<string>("wavelength_range_widget");
671     const IInputWidgetProxy* wr_widget_proxy = m_widget_proxies.get(wr_widget);
672     const Color3d initial_color =
673         wr_widget_proxy
674             ? ColorPickerProxy::get_color_from_string(widget_proxy->get(), wr_widget_proxy->get())
675             : ColorPickerProxy::get_color_from_string(widget_proxy->get());
676 
677     QColorDialog* dialog =
678         new QColorDialog(
679             color_to_qcolor(initial_color),
680             m_parent);
681     dialog->setWindowTitle("Pick Color");
682     dialog->setOptions(QColorDialog::DontUseNativeDialog);
683 
684     ForwardColorChangedSignal* forward_signal =
685         new ForwardColorChangedSignal(dialog, widget_name, color_to_qcolor(initial_color));
686     connect(
687         dialog, SIGNAL(currentColorChanged(const QColor&)),
688         forward_signal, SLOT(slot_color_changed(const QColor&)));
689     connect(
690         forward_signal, SIGNAL(signal_color_changed(const QString&, const QColor&)),
691         SLOT(slot_color_changed(const QString&, const QColor&)));
692     connect(
693         dialog, SIGNAL(rejected()),
694         forward_signal, SLOT(slot_color_reset()));
695     connect(
696         forward_signal, SIGNAL(signal_color_reset(const QString&, const QColor&)),
697         SLOT(slot_color_changed(const QString&, const QColor&)));
698 
699     dialog->exec();
700 }
701 
slot_color_changed(const QString & widget_name,const QColor & color)702 void EntityEditor::slot_color_changed(const QString& widget_name, const QColor& color)
703 {
704     IInputWidgetProxy* widget_proxy = m_widget_proxies.get(widget_name.toStdString());
705     widget_proxy->set(to_string(qcolor_to_color<Color3d>(color)));
706     widget_proxy->emit_signal_changed();
707 }
708 
slot_open_file_picker(const QString & widget_name)709 void EntityEditor::slot_open_file_picker(const QString& widget_name)
710 {
711     IInputWidgetProxy* widget_proxy = m_widget_proxies.get(widget_name.toStdString());
712 
713     const Dictionary& metadata = get_input_metadata(widget_name.toStdString());
714 
715     if (metadata.get<string>("file_picker_mode") == "open")
716     {
717         const QString file_picker_type = metadata.get<QString>("file_picker_type");
718         const QString filter =
719             file_picker_type == "image" ? get_oiio_image_files_filter() :
720             file_picker_type == "project" ? get_project_files_filter() :
721             QString();
722 
723         const QString settings_key =
724             file_picker_type == "image" ? SETTINGS_FILE_DIALOG_OIIO_TEXTURES :
725             SETTINGS_FILE_DIALOG_ENTITIES;
726 
727         const bf::path project_root_path = bf::path(m_project.get_path()).parent_path();
728         const bf::path file_path = absolute(widget_proxy->get(), project_root_path);
729         const bf::path file_root_path = file_path.parent_path();
730 
731         QFileDialog::Options options;
732 
733         QString filepath =
734             get_open_filename(
735                 m_parent,
736                 "Open...",
737                 filter,
738                 m_settings,
739                 settings_key,
740                 options);
741 
742         if (!filepath.isEmpty())
743         {
744             widget_proxy->set(QDir::toNativeSeparators(filepath).toStdString());
745             widget_proxy->emit_signal_changed();
746         }
747     }
748 }
749 
slot_apply()750 void EntityEditor::slot_apply()
751 {
752     emit signal_applied(get_values());
753 }
754 
755 }   // namespace studio
756 }   // namespace appleseed
757 
758 #include "mainwindow/project/moc_cpp_entityeditor.cxx"
759