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 "tools.h"
32 
33 // appleseed.studio headers.
34 #include "mainwindow/project/entityeditorwindow.h"
35 #include "utility/doubleslider.h"
36 #include "utility/interop.h"
37 
38 // appleseed.foundation headers.
39 #include "foundation/utility/containers/dictionary.h"
40 
41 // Qt headers.
42 #include <QInputDialog>
43 #include <QLineEdit>
44 #include <QMessageBox>
45 #include <QObject>
46 #include <QString>
47 
48 // Standard headers.
49 #include <cmath>
50 #include <utility>
51 
52 using namespace foundation;
53 using namespace renderer;
54 using namespace std;
55 
56 namespace appleseed {
57 namespace studio {
58 
get_entity_name_dialog(QWidget * parent,const string & title,const string & label,const string & text)59 string get_entity_name_dialog(
60     QWidget*        parent,
61     const string&   title,
62     const string&   label,
63     const string&   text)
64 {
65     QString result;
66     bool ok;
67 
68     do
69     {
70         result =
71             QInputDialog::getText(
72                 parent,
73                 QString::fromStdString(title),
74                 QString::fromStdString(label),
75                 QLineEdit::Normal,
76                 QString::fromStdString(text),
77                 &ok);
78     } while (ok && result.isEmpty());
79 
80     return ok ? result.toStdString() : string();
81 }
82 
open_entity_editor(QWidget * parent,const string & window_title,const Project & project,renderer::ParamArray & settings,unique_ptr<EntityEditor::IFormFactory> form_factory,unique_ptr<EntityEditor::IEntityBrowser> entity_browser,unique_ptr<CustomEntityUI> custom_entity_ui,const Dictionary & values,QObject * receiver,const char * slot_apply,const char * slot_accept,const char * slot_cancel)83 void open_entity_editor(
84     QWidget*                                    parent,
85     const string&                               window_title,
86     const Project&                              project,
87     renderer::ParamArray&                       settings,
88     unique_ptr<EntityEditor::IFormFactory>      form_factory,
89     unique_ptr<EntityEditor::IEntityBrowser>    entity_browser,
90     unique_ptr<CustomEntityUI>                  custom_entity_ui,
91     const Dictionary&                           values,
92     QObject*                                    receiver,
93     const char*                                 slot_apply,
94     const char*                                 slot_accept,
95     const char*                                 slot_cancel)
96 {
97     EntityEditorWindow* editor_window =
98         new EntityEditorWindow(
99             parent,
100             window_title,
101             project,
102             settings,
103             move(form_factory),
104             move(entity_browser),
105             move(custom_entity_ui),
106             values);
107 
108     QObject::connect(
109         editor_window, SIGNAL(signal_applied(foundation::Dictionary)),
110         receiver, slot_apply);
111 
112     QObject::connect(
113         editor_window, SIGNAL(signal_accepted(foundation::Dictionary)),
114         receiver, slot_accept);
115 
116     QObject::connect(
117         editor_window, SIGNAL(signal_canceled(foundation::Dictionary)),
118         receiver, slot_cancel);
119 
120     editor_window->showNormal();
121     editor_window->activateWindow();
122 }
123 
open_entity_editor(QWidget * parent,const string & window_title,const Project & project,renderer::ParamArray & settings,unique_ptr<EntityEditor::IFormFactory> form_factory,unique_ptr<EntityEditor::IEntityBrowser> entity_browser,const Dictionary & values,QObject * receiver,const char * slot_apply,const char * slot_accept,const char * slot_cancel)124 void open_entity_editor(
125     QWidget*                                    parent,
126     const string&                               window_title,
127     const Project&                              project,
128     renderer::ParamArray&                       settings,
129     unique_ptr<EntityEditor::IFormFactory>      form_factory,
130     unique_ptr<EntityEditor::IEntityBrowser>    entity_browser,
131     const Dictionary&                           values,
132     QObject*                                    receiver,
133     const char*                                 slot_apply,
134     const char*                                 slot_accept,
135     const char*                                 slot_cancel)
136 {
137     open_entity_editor(
138         parent,
139         window_title,
140         project,
141         settings,
142         move(form_factory),
143         move(entity_browser),
144         unique_ptr<CustomEntityUI>(),
145         values,
146         receiver,
147         slot_apply,
148         slot_accept,
149         slot_cancel);
150 }
151 
open_entity_editor(QWidget * parent,const string & window_title,const Project & project,renderer::ParamArray & settings,unique_ptr<EntityEditor::IFormFactory> form_factory,unique_ptr<EntityEditor::IEntityBrowser> entity_browser,QObject * receiver,const char * slot_apply,const char * slot_accept,const char * slot_cancel)152 void open_entity_editor(
153     QWidget*                                    parent,
154     const string&                               window_title,
155     const Project&                              project,
156     renderer::ParamArray&                       settings,
157     unique_ptr<EntityEditor::IFormFactory>      form_factory,
158     unique_ptr<EntityEditor::IEntityBrowser>    entity_browser,
159     QObject*                                    receiver,
160     const char*                                 slot_apply,
161     const char*                                 slot_accept,
162     const char*                                 slot_cancel)
163 {
164     open_entity_editor(
165         parent,
166         window_title,
167         project,
168         settings,
169         move(form_factory),
170         move(entity_browser),
171         unique_ptr<CustomEntityUI>(),
172         Dictionary(),
173         receiver,
174         slot_apply,
175         slot_accept,
176         slot_cancel);
177 }
178 
show_error_message_box(const string & title,const string & text)179 void show_error_message_box(const string& title, const string& text)
180 {
181     QMessageBox msgbox;
182     msgbox.setWindowTitle(QString::fromStdString(title));
183     msgbox.setIcon(QMessageBox::Critical);
184     msgbox.setText(QString::fromStdString(text));
185     msgbox.setStandardButtons(QMessageBox::Ok);
186     msgbox.exec();
187 }
188 
189 
190 //
191 // LineEditSliderAdaptor class implementation.
192 //
193 
LineEditSliderAdaptor(QLineEdit * line_edit,QSlider * slider)194 LineEditSliderAdaptor::LineEditSliderAdaptor(
195     QLineEdit*      line_edit,
196     QSlider*        slider)
197   : QObject(line_edit)
198   , m_line_edit(line_edit)
199   , m_slider(slider)
200 {
201     slot_set_slider_value(m_line_edit->text());
202 
203     // When the slider is moved, update the line edit's value.
204     connect(
205         m_slider, SIGNAL(valueChanged(const int)),
206         SLOT(slot_set_line_edit_value(const int)));
207 
208     // When the line edit value is changed, update the slider's position.
209     connect(
210         m_line_edit, SIGNAL(textChanged(const QString&)),
211         SLOT(slot_set_slider_value(const QString&)));
212 
213     // When enter is pressed in the line edit, update the slider's position.
214     connect(
215         m_line_edit, SIGNAL(editingFinished()),
216         SLOT(slot_apply_line_edit_value()));
217 }
218 
slot_set_line_edit_value(const int value)219 void LineEditSliderAdaptor::slot_set_line_edit_value(const int value)
220 {
221     const QString new_line_edit_value = QString("%1").arg(value);
222 
223     // Don't block signals here, for live edit to work we want the line edit to signal changes.
224     m_line_edit->setText(new_line_edit_value);
225 }
226 
slot_set_slider_value(const QString & value)227 void LineEditSliderAdaptor::slot_set_slider_value(const QString& value)
228 {
229     if (value.isEmpty())
230         return;
231 
232     const bool were_signals_blocked = m_slider->blockSignals(true);
233     m_slider->setValue(value.toInt());
234     m_slider->blockSignals(were_signals_blocked);
235 }
236 
slot_apply_line_edit_value()237 void LineEditSliderAdaptor::slot_apply_line_edit_value()
238 {
239     slot_set_slider_value(m_line_edit->text());
240 }
241 
242 
243 //
244 // LineEditDoubleSliderAdaptor class implementation.
245 //
246 
LineEditDoubleSliderAdaptor(QLineEdit * line_edit,DoubleSlider * slider)247 LineEditDoubleSliderAdaptor::LineEditDoubleSliderAdaptor(
248     QLineEdit*      line_edit,
249     DoubleSlider*   slider)
250   : QObject(line_edit)
251   , m_line_edit(line_edit)
252   , m_slider(slider)
253 {
254     slot_set_slider_value(m_line_edit->text());
255 
256     // When the slider is moved, update the line edit's value.
257     connect(
258         m_slider, SIGNAL(valueChanged(const double)),
259         SLOT(slot_set_line_edit_value(const double)));
260 
261     // When the line edit value is changed, update the slider's position.
262     connect(
263         m_line_edit, SIGNAL(textChanged(const QString&)),
264         SLOT(slot_set_slider_value(const QString&)));
265 
266     // When enter is pressed in the line edit, update the slider's position.
267     connect(
268         m_line_edit, SIGNAL(editingFinished()),
269         SLOT(slot_apply_line_edit_value()));
270 }
271 
slot_set_line_edit_value(const double value)272 void LineEditDoubleSliderAdaptor::slot_set_line_edit_value(const double value)
273 {
274     // Format integer values such as 2 as "2.0" instead of "2".
275     const bool is_integer = floor(value) == value;
276     const QString format_string = is_integer ? "%1.0" : "%1";
277     const QString new_line_edit_value = format_string.arg(value);
278 
279     // Don't block signals here, for live edit to work we want the line edit to signal changes.
280     m_line_edit->setText(new_line_edit_value);
281 }
282 
slot_set_slider_value(const QString & value)283 void LineEditDoubleSliderAdaptor::slot_set_slider_value(const QString& value)
284 {
285     if (value.isEmpty())
286         return;
287 
288     const bool were_signals_blocked = m_slider->blockSignals(true);
289 
290     const double new_value = value.toDouble();
291 
292     // Adjust range if the new value is outside the current range.
293     // The test is intentionally different than the one in slot_apply_line_edit_value().
294     if (new_value < m_slider->minimum() ||
295         new_value > m_slider->maximum())
296         adjust_slider(new_value);
297 
298     m_slider->setValue(new_value);
299 
300     m_slider->blockSignals(were_signals_blocked);
301 }
302 
slot_apply_line_edit_value()303 void LineEditDoubleSliderAdaptor::slot_apply_line_edit_value()
304 {
305     const bool were_signals_blocked = m_slider->blockSignals(true);
306 
307     const double new_value = m_line_edit->text().toDouble();
308 
309     // Adjust range if the new value is outside the current range,
310     // or if a value of a significantly smaller magnitude was entered.
311     if (new_value < m_slider->minimum() ||
312         new_value > m_slider->maximum() ||
313         abs(new_value) < (m_slider->maximum() - m_slider->minimum()) / 3.0)
314         adjust_slider(new_value);
315 
316     m_slider->setValue(new_value);
317 
318     // Force reformatting of the value in the QLineEdit if necessary.
319     if (!m_line_edit->text().isEmpty())
320         slot_set_line_edit_value(new_value);
321 
322     m_slider->blockSignals(were_signals_blocked);
323 }
324 
adjust_slider(const double new_value)325 void LineEditDoubleSliderAdaptor::adjust_slider(const double new_value)
326 {
327     const double new_min = new_value >= 0.0 ? 0.0 : -2.0 * abs(new_value);
328     const double new_max = new_value == 0.0 ? 1.0 : +2.0 * abs(new_value);
329     m_slider->setRange(new_min, new_max);
330     m_slider->setPageStep((new_max - new_min) / 10.0);
331 }
332 
333 
334 //
335 // QDoubleValidatorWithDefault class implementation.
336 //
337 
QDoubleValidatorWithDefault(const QString & default_value,QObject * parent)338 QDoubleValidatorWithDefault::QDoubleValidatorWithDefault(const QString& default_value, QObject* parent)
339   : QDoubleValidator(parent)
340   , m_default_value(default_value)
341 {
342 }
343 
QDoubleValidatorWithDefault(const double bottom,const double top,const int decimals,const QString & default_value,QObject * parent)344 QDoubleValidatorWithDefault::QDoubleValidatorWithDefault(
345     const double    bottom,
346     const double    top,
347     const int       decimals,
348     const QString&  default_value,
349     QObject*        parent)
350   : QDoubleValidator(bottom, top, decimals, parent)
351   , m_default_value(default_value)
352 {
353 }
354 
validate(QString & input,int & pos) const355 QValidator::State QDoubleValidatorWithDefault::validate(QString& input, int& pos) const
356 {
357     return
358         input == m_default_value
359             ? QValidator::Acceptable
360             : QDoubleValidator::validate(input, pos);
361 }
362 
363 
364 //
365 // ForwardColorChangedSignal class implementation.
366 //
367 
ForwardColorChangedSignal(QObject * parent,const QString & widget_name,const QColor & initial_color)368 ForwardColorChangedSignal::ForwardColorChangedSignal(
369     QObject*        parent,
370     const QString&  widget_name,
371     const QColor&   initial_color)
372   : QObject(parent)
373   , m_widget_name(widget_name)
374   , m_initial_color(initial_color)
375   , m_current_color(initial_color)
376 {
377 }
378 
slot_color_reset()379 void ForwardColorChangedSignal::slot_color_reset()
380 {
381     if (m_current_color != m_initial_color)
382         emit signal_color_reset(m_widget_name, m_initial_color);
383 }
384 
slot_color_changed(const QColor & color)385 void ForwardColorChangedSignal::slot_color_changed(const QColor& color)
386 {
387     m_current_color = color;
388     emit signal_color_changed(m_widget_name, color);
389 }
390 
391 }   // namespace studio
392 }   // namespace appleseed
393