1 /*
2 * Copyright (C) 2014-2018 Christopho, Solarus - http://www.solarus-games.org
3 *
4 * Solarus Quest Editor is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * Solarus Quest Editor is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17 #include "widgets/gui_tools.h"
18 #include "widgets/map_view.h"
19 #include "widgets/shader_editor.h"
20 #include "widgets/text_editor.h"
21 #include "editor_exception.h"
22 #include "editor_settings.h"
23 #include "map_model.h"
24 #include "quest.h"
25 #include "shader_model.h"
26 #include "sprite_model.h"
27 #include <QFileDialog>
28 #include <QInputDialog>
29 #include <QUndoStack>
30
31 namespace SolarusEditor {
32
33 namespace {
34
35 /**
36 * @brief Parent class of all undoable commands of the shader editor.
37 */
38 class ShaderEditorCommand : public QUndoCommand {
39
40 public:
41
ShaderEditorCommand(ShaderEditor & editor,const QString & text)42 ShaderEditorCommand(ShaderEditor& editor, const QString& text) :
43 QUndoCommand(text),
44 editor(editor) {
45 }
46
get_editor() const47 ShaderEditor& get_editor() const {
48 return editor;
49 }
50
get_shader() const51 ShaderModel& get_shader() const {
52 return editor.get_shader();
53 }
54
55 private:
56
57 ShaderEditor& editor;
58
59 };
60
61 /**
62 * @brief Changing the scaling factor of a shader.
63 */
64 class SetScalingFactorCommand : public ShaderEditorCommand {
65
66 public:
SetScalingFactorCommand(ShaderEditor & editor,double scaling_factor)67 SetScalingFactorCommand(ShaderEditor& editor, double scaling_factor) :
68 ShaderEditorCommand(editor, ShaderEditor::tr("Scaling factor")),
69 before(get_shader().get_scaling_factor()),
70 after(scaling_factor) {
71 }
72
undo()73 void undo() override {
74 get_shader().set_scaling_factor(before);
75 }
76
redo()77 void redo() override {
78 get_shader().set_scaling_factor(after);
79 }
80
81 private:
82 double before, after;
83 };
84
85 /**
86 * @brief Changing a source file in a shader program.
87 */
88 class SetGlslFileCommand : public ShaderEditorCommand {
89
90 public:
SetGlslFileCommand(ShaderEditor & editor,WhichGlslEditor which,const QString & file_name)91 SetGlslFileCommand(ShaderEditor& editor, WhichGlslEditor which, const QString& file_name) :
92 ShaderEditorCommand(editor, ShaderEditor::tr("Shader file")),
93 which(which),
94 before(),
95 after(file_name) {
96
97 switch (which) {
98 case WhichGlslEditor::VERTEX_EDITOR:
99 before = get_shader().get_vertex_file();
100 break;
101 case WhichGlslEditor::FRAGMENT_EDITOR:
102 before = get_shader().get_fragment_file();
103 break;
104 }
105 }
106
undo()107 void undo() override {
108 switch (which) {
109 case WhichGlslEditor::VERTEX_EDITOR:
110 get_shader().set_vertex_file(before);
111 break;
112 case WhichGlslEditor::FRAGMENT_EDITOR:
113 get_shader().set_fragment_file(before);
114 break;
115 }
116 }
117
redo()118 void redo() override {
119 switch (which) {
120 case WhichGlslEditor::VERTEX_EDITOR:
121 get_shader().set_vertex_file(after);
122 break;
123 case WhichGlslEditor::FRAGMENT_EDITOR:
124 get_shader().set_fragment_file(after);
125 break;
126 }
127 }
128
129 private:
130 WhichGlslEditor which;
131 QString before, after;
132 };
133
134 } // Anonymous namespace.
135
136 /**
137 * @brief Creates a shader editor.
138 * @param quest The quest containing the file.
139 * @param path Path of the shader data file to open.
140 * @param parent The parent object or nullptr.
141 * @throws EditorException If the file could not be opened.
142 */
ShaderEditor(Quest & quest,const QString & path,QWidget * parent)143 ShaderEditor::ShaderEditor(Quest& quest, const QString& path, QWidget* parent) :
144 Editor(quest, path, parent),
145 shader(nullptr),
146 quest(quest),
147 vertex_editor(nullptr),
148 fragment_editor(nullptr) {
149
150 ui.setupUi(this);
151
152 // Get the shader.
153 ResourceType resource_type;
154 QString shader_id;
155 quest.check_exists(path);
156 if (!quest.is_resource_element(path, resource_type, shader_id) ||
157 resource_type != ResourceType::SHADER) {
158 throw EditorException(tr("File '%1' is not a shader").arg(path));
159 }
160 this->shader_id = shader_id;
161
162 // Editor properties.
163 set_title(tr("Shader %1").arg(get_file_name_without_extension()));
164 set_icon(QIcon(":/images/icon_resource_shader.png"));
165 set_close_confirm_message(
166 tr("Shader '%1' has been modified. Save changes?").arg(shader_id));
167 set_zoom_supported(true);
168 ui.shader_previewer->set_view_settings(get_view_settings());
169
170 // Open the file.
171 shader = std::unique_ptr<ShaderModel>(new ShaderModel(quest, shader_id, this));
172 get_undo_stack().setClean();
173
174 // Prepare the GUI.
175 ui.scaling_factor_check_box->setAttribute(Qt::WA_LayoutUsesWidgetRect);
176 ui.preview_mode_selector->setAttribute(Qt::WA_LayoutUsesWidgetRect);
177
178 ui.preview_picture_page->layout()->setAlignment(ui.preview_picture_field_layout, Qt::AlignTop);
179 ui.preview_map_page->layout()->setAlignment(ui.preview_map_field, Qt::AlignTop);
180 ui.preview_map_field->set_resource_type(ResourceType::MAP);
181 ui.preview_map_field->set_quest(quest);
182 ui.preview_sprite_field->set_resource_type(ResourceType::SPRITE);
183 ui.preview_sprite_field->set_quest(quest);
184
185 ui.vertex_file_check_box->setAttribute(Qt::WA_LayoutUsesWidgetRect);
186 ui.vertex_file_field->setAttribute(Qt::WA_LayoutUsesWidgetRect);
187 ui.vertex_shader_page->layout()->setAlignment(ui.vertex_file_check_box, Qt::AlignTop);
188 ui.fragment_file_check_box->setAttribute(Qt::WA_LayoutUsesWidgetRect);
189 ui.fragment_file_field->setAttribute(Qt::WA_LayoutUsesWidgetRect);
190 ui.fragment_shader_page->layout()->setAlignment(ui.fragment_file_check_box, Qt::AlignTop);
191
192 const int side_width = 300;
193 ui.main_splitter->setSizes({ side_width, width() - side_width });
194 ui.main_splitter->setStretchFactor(0, 0);
195 ui.main_splitter->setStretchFactor(1, 1);
196 const int preview_height = 400;
197 ui.right_splitter->setSizes({ preview_height, height() - preview_height });
198 ui.right_splitter->setStretchFactor(0, 1);
199 ui.right_splitter->setStretchFactor(1, 1);
200
201 ui.shader_previewer->set_model(shader.get());
202
203 QString vertex_file = shader->get_vertex_file();
204 QString vertex_file_path = quest.get_shader_code_file_path(vertex_file);
205 if (!quest.exists(vertex_file_path) || !quest.is_shader_code_file(vertex_file_path)) {
206 vertex_file_path = QString();
207 }
208 vertex_editor = new TextEditor(
209 quest, vertex_file_path, this);
210 ui.vertex_editor_layout->addWidget(vertex_editor);
211 ui.vertex_editor_layout->removeItem(ui.vertex_editor_placeholder);
212
213 QString fragment_file = shader->get_fragment_file();
214 QString fragment_file_path = quest.get_shader_code_file_path(fragment_file);
215 if (!quest.exists(fragment_file_path) || !quest.is_shader_code_file(fragment_file_path)) {
216 fragment_file_path = QString();
217 }
218 fragment_editor = new TextEditor(
219 quest, fragment_file_path, this);
220 ui.fragment_editor_layout->addWidget(fragment_editor);
221 ui.fragment_editor_layout->removeItem(ui.fragment_editor_placeholder);
222
223 update();
224 reload_settings();
225
226 // Make connections.
227 connect(&get_database(), &QuestDatabase::element_description_changed,
228 this, &ShaderEditor::update_description_to_gui);
229 connect(ui.description_field, &QLineEdit::editingFinished,
230 this, &ShaderEditor::set_description_from_gui);
231
232 connect(shader.get(), &ShaderModel::scaling_factor_changed,
233 this, &ShaderEditor::update_scaling_factor_field);
234 connect(ui.scaling_factor_check_box, &QCheckBox::clicked,
235 this, &ShaderEditor::scaling_factor_check_box_changed);
236 connect(ui.scaling_factor_field, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
237 this, &ShaderEditor::scaling_factor_field_changed);
238
239 connect(ui.preview_mode_selector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
240 [this]() {
241 ui.shader_previewer->set_preview_mode(ui.preview_mode_selector->get_selected_value());
242 });
243
244 connect(ui.preview_picture_radio, &QRadioButton::clicked,
245 this, &ShaderEditor::preview_radio_changed);
246 connect(ui.preview_picture_browse_button, &QToolButton::clicked,
247 this, &ShaderEditor::browse_preview_picture);
248 connect(ui.preview_map_radio, &QRadioButton::clicked,
249 this, &ShaderEditor::preview_radio_changed);
250 connect(ui.preview_map_field, static_cast<void (QComboBox::*)(int)>(&ResourceSelector::currentIndexChanged),
251 this, &ShaderEditor::update_preview_image);
252 connect(ui.preview_sprite_radio, &QRadioButton::clicked,
253 this, &ShaderEditor::preview_radio_changed);
254 connect(ui.preview_sprite_field, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
255 this, &ShaderEditor::preview_selected_sprite_changed);
256 connect(ui.preview_sprite_animation_field, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
257 this, &ShaderEditor::preview_sprite_animation_changed);
258 connect(ui.preview_sprite_direction_field, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
259 this, &ShaderEditor::update_preview_image);
260
261 connect(ui.vertex_file_check_box, &QCheckBox::clicked, [this]() {
262 source_file_check_box_changed(WhichGlslEditor::VERTEX_EDITOR);
263 });
264 connect(ui.vertex_file_new_button, &QToolButton::clicked, [this]() {
265 new_source_file(WhichGlslEditor::VERTEX_EDITOR);
266 });
267 connect(ui.vertex_file_browse_button, &QToolButton::clicked, [this]() {
268 browse_source_file(WhichGlslEditor::VERTEX_EDITOR);
269 });
270 connect(ui.vertex_file_save_button, &QToolButton::clicked, [this]() {
271 save_source_file(WhichGlslEditor::VERTEX_EDITOR);
272 });
273 connect(shader.get(), &ShaderModel::vertex_file_changed, [this]() {
274 update_source_editor_tab(WhichGlslEditor::VERTEX_EDITOR);
275 });
276 connect(&vertex_editor->get_undo_stack(), &QUndoStack::cleanChanged, [this](bool clean) {
277 source_editor_modification_state_changed(WhichGlslEditor::VERTEX_EDITOR, clean);
278 });
279
280 connect(ui.fragment_file_check_box, &QCheckBox::clicked, [this]() {
281 source_file_check_box_changed(WhichGlslEditor::FRAGMENT_EDITOR);
282 });
283 connect(ui.fragment_file_new_button, &QToolButton::clicked, [this]() {
284 new_source_file(WhichGlslEditor::FRAGMENT_EDITOR);
285 });
286 connect(ui.fragment_file_browse_button, &QToolButton::clicked, [this]() {
287 browse_source_file(WhichGlslEditor::FRAGMENT_EDITOR);
288 });
289 connect(ui.fragment_file_save_button, &QToolButton::clicked, [this]() {
290 save_source_file(WhichGlslEditor::FRAGMENT_EDITOR);
291 });
292 connect(shader.get(), &ShaderModel::fragment_file_changed, [this]() {
293 update_source_editor_tab(WhichGlslEditor::FRAGMENT_EDITOR);
294 });
295 connect(&fragment_editor->get_undo_stack(), &QUndoStack::cleanChanged, [this](bool clean) {
296 source_editor_modification_state_changed(WhichGlslEditor::FRAGMENT_EDITOR, clean);
297 });
298
299 connect(ui.shader_previewer, &ShaderPreviewer::shader_compilation_started,
300 this, &ShaderEditor::clear_console);
301 connect(ui.shader_previewer, &ShaderPreviewer::shader_error,
302 [this](const QString& message) {
303 emit log_message_to_console("Error", message);
304 });
305 connect(ui.shader_previewer, &ShaderPreviewer::shader_warning,
306 [this](const QString& message) {
307 emit log_message_to_console("Warning", message);
308 });
309 }
310
311 /**
312 * @brief Destructor.
313 */
~ShaderEditor()314 ShaderEditor::~ShaderEditor() {
315 }
316
317 /**
318 * @brief Returns the shader model being edited.
319 * @return The shader model.
320 */
get_shader()321 ShaderModel& ShaderEditor::get_shader() {
322 return *shader;
323 }
324
325 /**
326 * @copydoc Editor::save
327 */
save()328 void ShaderEditor::save() {
329
330 shader->save();
331 save_source_file(WhichGlslEditor::VERTEX_EDITOR);
332 save_source_file(WhichGlslEditor::FRAGMENT_EDITOR);
333 }
334
335 /**
336 * @copydoc Editor::has_unsaved_changes
337 */
has_unsaved_changes() const338 bool ShaderEditor::has_unsaved_changes() const {
339
340 return Editor::has_unsaved_changes() ||
341 vertex_editor->has_unsaved_changes() ||
342 fragment_editor->has_unsaved_changes();
343 }
344
345 /**
346 * @copydoc Editor::reload_settings
347 */
reload_settings()348 void ShaderEditor::reload_settings() {
349
350 EditorSettings settings;
351
352 QString file = settings.get_value_string(EditorSettings::shader_preview_picture_file);
353 ui.preview_picture_field->setText(file);
354 QString map_id = settings.get_value_string(EditorSettings::shader_preview_map_id);
355 ui.preview_map_field->set_selected_id(map_id);
356 QString sprite_id = settings.get_value_string(EditorSettings::shader_preview_sprite_id);
357 QString animation = settings.get_value_string(EditorSettings::shader_preview_sprite_animation);
358 int direction = settings.get_value_int(EditorSettings::shader_preview_sprite_direction);
359 ui.preview_sprite_field->set_selected_id(sprite_id);
360 preview_selected_sprite_changed();
361 ui.preview_sprite_animation_field->setCurrentText(animation);
362 preview_sprite_animation_changed();
363 ui.preview_sprite_direction_field->setValue(direction);
364
365 QString preview_type = settings.get_value_string(EditorSettings::shader_preview_type);
366 if (preview_type.isEmpty() || preview_type == "picture") {
367 ui.preview_picture_radio->setChecked(true);
368 ui.preview_file_widget->setCurrentWidget(ui.preview_picture_page);
369 }
370 else if (preview_type == "map") {
371 ui.preview_map_radio->setChecked(true);
372 ui.preview_file_widget->setCurrentWidget(ui.preview_map_page);
373 }
374 else if (preview_type == "sprite") {
375 ui.preview_sprite_radio->setChecked(true);
376 ui.preview_file_widget->setCurrentWidget(ui.preview_sprite_page);
377 }
378 update_preview_image();
379 }
380
381 /**
382 * @brief Updates everything in the GUI.
383 */
update()384 void ShaderEditor::update() {
385
386 update_shader_id_field();
387 update_description_to_gui();
388 update_scaling_factor_field();
389 update_source_editor_tab(WhichGlslEditor::VERTEX_EDITOR);
390 update_source_editor_tab(WhichGlslEditor::FRAGMENT_EDITOR);
391 }
392
393 /**
394 * @brief Updates the shader id displaying.
395 */
update_shader_id_field()396 void ShaderEditor::update_shader_id_field() {
397
398 ui.shader_id_field->setText(shader_id);
399 }
400
401 /**
402 * @brief Updates the content of the shader description text edit.
403 */
update_description_to_gui()404 void ShaderEditor::update_description_to_gui() {
405
406 QString description = get_database().get_description(ResourceType::SHADER, shader_id);
407 if (ui.description_field->text() != description) {
408 ui.description_field->setText(description);
409 }
410 }
411
412 /**
413 * @brief Modifies the shader description in the quest database with
414 * the new text entered by the user.
415 *
416 * If the new description is invalid, an error dialog is shown.
417 */
set_description_from_gui()418 void ShaderEditor::set_description_from_gui() {
419
420 QString description = ui.description_field->text();
421 if (description == get_database().get_description(ResourceType::SHADER, shader_id)) {
422 return;
423 }
424
425 if (description.isEmpty()) {
426 GuiTools::error_dialog(tr("Invalid description"));
427 update_description_to_gui();
428 return;
429 }
430
431 const bool was_blocked = blockSignals(true);
432 try {
433 get_database().set_description(ResourceType::SHADER, shader_id, description);
434 get_database().save();
435 }
436 catch (const EditorException& ex) {
437 ex.print_message();
438 }
439 update_description_to_gui();
440 blockSignals(was_blocked);
441 }
442
443 /**
444 * @brief Updates the scaling factor from data to the GUI.
445 */
update_scaling_factor_field()446 void ShaderEditor::update_scaling_factor_field() {
447
448 double scaling_factor = get_shader().get_scaling_factor();
449 if (scaling_factor == 0.0) {
450 ui.scaling_factor_check_box->setChecked(false);
451 ui.scaling_factor_field->setEnabled(false);
452 }
453 else {
454 ui.scaling_factor_check_box->setChecked(true);
455 ui.scaling_factor_field->setEnabled(true);
456 ui.scaling_factor_field->setValue(scaling_factor);
457 }
458 }
459
460 /**
461 * @brief Called when the user changes the scaling factor check box.
462 */
scaling_factor_check_box_changed()463 void ShaderEditor::scaling_factor_check_box_changed() {
464
465 double old_scaling_factor = get_shader().get_scaling_factor();
466 double new_scaling_factor = 0.0;
467 if (ui.scaling_factor_check_box->isChecked()) {
468 new_scaling_factor = ui.scaling_factor_field->value();;
469 }
470
471 if (new_scaling_factor != old_scaling_factor) {
472 try_command(new SetScalingFactorCommand(*this, new_scaling_factor));
473 }
474 }
475
476 /**
477 * @brief Called when the user changes the scaling factor value.
478 */
scaling_factor_field_changed()479 void ShaderEditor::scaling_factor_field_changed() {
480
481 double scaling_factor = ui.scaling_factor_field->value();
482 if (scaling_factor == get_shader().get_scaling_factor()) {
483 return;
484 }
485 try_command(new SetScalingFactorCommand(*this, scaling_factor));
486 }
487
488 /**
489 * @brief Returns the specified GLSL code editor widget.
490 * @param which Which GLSL editor to return.
491 * @return The corresponding code editor.
492 */
get_glsl_editor(WhichGlslEditor which)493 TextEditor* ShaderEditor::get_glsl_editor(WhichGlslEditor which) {
494
495 TextEditor* glsl_editor = nullptr;
496 switch (which) {
497 case WhichGlslEditor::VERTEX_EDITOR:
498 glsl_editor = vertex_editor;
499 break;
500 case WhichGlslEditor::FRAGMENT_EDITOR:
501 glsl_editor = fragment_editor;
502 break;
503 }
504
505 return glsl_editor;
506 }
507
508 /**
509 * @brief Updates a GLSL editor tab from the data.
510 * @param which The GLSL editor tab to update.
511 */
update_source_editor_tab(WhichGlslEditor which)512 void ShaderEditor::update_source_editor_tab(WhichGlslEditor which) {
513
514 if (shader == nullptr) {
515 return;
516 }
517
518 QString file_name;
519 QLineEdit* file_name_field = nullptr;
520 QCheckBox* check_box = nullptr;
521 QStackedWidget* stacked_widget = nullptr;
522 QToolButton* save_button = nullptr;
523 TextEditor* glsl_editor = get_glsl_editor(which);
524
525 switch (which) {
526
527 case WhichGlslEditor::VERTEX_EDITOR:
528 file_name = shader->get_vertex_file();
529 file_name_field = ui.vertex_file_field;
530 check_box = ui.vertex_file_check_box;
531 stacked_widget = ui.vertex_editor_stacked_widget;
532 save_button = ui.vertex_file_save_button;
533 break;
534
535 case WhichGlslEditor::FRAGMENT_EDITOR:
536 file_name = shader->get_fragment_file();
537 file_name_field = ui.fragment_file_field;
538 check_box = ui.fragment_file_check_box;
539 stacked_widget = ui.fragment_editor_stacked_widget;
540 save_button = ui.fragment_file_save_button;
541 break;
542 }
543
544 QString path = get_quest().get_shader_code_file_path(file_name);
545 if (!file_name.isEmpty() &&
546 quest.exists(path) &&
547 quest.is_shader_code_file(path)) {
548 file_name_field->setText(file_name);
549 stacked_widget->setCurrentIndex(1); // Normal page.
550 get_glsl_editor(which)->set_file_path(path);
551 glsl_editor->setEnabled(true);
552 save_button->setEnabled(true);
553 check_box->setChecked(true);
554 }
555 else {
556 file_name_field->setText(QString());
557 stacked_widget->setCurrentIndex(0); // Empty page.
558 glsl_editor->setEnabled(false);
559 save_button->setEnabled(false);
560 check_box->setChecked(false);
561 }
562 }
563
564 /**
565 * @brief Called when the user clicks a source file check box.
566 * @param which The GLSL editor whose check box has changed.
567 */
source_file_check_box_changed(WhichGlslEditor which)568 void ShaderEditor::source_file_check_box_changed(WhichGlslEditor which) {
569
570 QCheckBox* check_box = nullptr;
571 QString* last_file_name = nullptr;
572 QString current_file_name;
573 QStackedWidget* stacked_widget = nullptr;
574
575 switch (which) {
576
577 case WhichGlslEditor::VERTEX_EDITOR:
578 last_file_name = &last_vertex_file;
579 current_file_name = shader->get_vertex_file();
580 check_box = ui.vertex_file_check_box;
581 stacked_widget = ui.vertex_editor_stacked_widget;
582 break;
583
584 case WhichGlslEditor::FRAGMENT_EDITOR:
585 last_file_name = &last_fragment_file;
586 current_file_name = shader->get_fragment_file();
587 check_box = ui.fragment_file_check_box;
588 stacked_widget = ui.fragment_editor_stacked_widget;
589 break;
590 }
591
592 const bool checked = check_box->isChecked();
593 if (checked) {
594 if (current_file_name.isEmpty() &&
595 !last_file_name->isEmpty()) {
596 // Use the previous file name.
597 try_command(new SetGlslFileCommand(*this, which, *last_file_name));
598 }
599 else {
600 stacked_widget->setCurrentIndex(1); // Normal page.
601 }
602 }
603 else {
604 if (!current_file_name.isEmpty()) {
605 // Remove the value but remember it.
606 *last_file_name = current_file_name;
607 try_command(new SetGlslFileCommand(*this, which, ""));
608 }
609 else {
610 stacked_widget->setCurrentIndex(0); // Empty page.
611 }
612 }
613 }
614
615 /**
616 * @brief Lets the user choose a GLSL source file to create.
617 * @param which The GLSL editor where to create a file.
618 */
new_source_file(WhichGlslEditor which)619 void ShaderEditor::new_source_file(WhichGlslEditor which) {
620
621 if (shader == nullptr) {
622 return;
623 }
624
625 try {
626 TextEditor* glsl_editor = get_glsl_editor(which);
627 if (!glsl_editor->confirm_before_closing()) {
628 return;
629 }
630
631 QString suffix;
632 QString content;
633 switch (which) {
634 case WhichGlslEditor::VERTEX_EDITOR:
635 suffix = ".vert.glsl";
636 content = ShaderModel::get_default_vertex_source();
637 break;
638 case WhichGlslEditor::FRAGMENT_EDITOR:
639 suffix = ".frag.glsl";
640 content = ShaderModel::get_default_fragment_source();
641 break;
642 }
643
644 bool ok = false;
645 QString file_name = QInputDialog::getText(
646 this,
647 tr("New GLSL file"),
648 tr("File name:"),
649 QLineEdit::Normal,
650 shader_id + suffix,
651 &ok);
652
653 if (ok) {
654 // Automatically add .glsl extension if not present.
655 if (!file_name.contains(".")) {
656 file_name = file_name + ".lua";
657 }
658 Quest::check_valid_file_name(file_name);
659 const QString& shaders_path = get_quest().get_resource_path(ResourceType::SHADER);
660 QString file_path = shaders_path + '/' + file_name;
661
662 get_quest().create_file_from_string(file_path, content);
663
664 file_name = file_name.right(shaders_path.size() + 1);
665 try_command(new SetGlslFileCommand(*this, which, file_name));
666 }
667 }
668 catch (const EditorException& ex) {
669 ex.show_dialog();
670 }
671 }
672
673 /**
674 * @brief Lets the user choose a GLSL code file to open.
675 * @param which The GLSL editor where to browse a file.
676 */
browse_source_file(WhichGlslEditor which)677 void ShaderEditor::browse_source_file(WhichGlslEditor which) {
678
679 if (shader == nullptr) {
680 return;
681 }
682
683 try {
684 if (get_glsl_editor(which)->confirm_before_closing()) {
685 const QString& directory = QFileInfo(get_file_path()).dir().path();
686 QString file_name = QFileDialog::getOpenFileName(
687 this,
688 tr("Open a GLSL file"),
689 directory,
690 tr("GLSL shader file (*.glsl)")
691 );
692
693 if (!file_name.isEmpty()) {
694 const QString& shaders_path = get_quest().get_resource_path(ResourceType::SHADER);
695 if (!file_name.startsWith(shaders_path)) {
696 throw EditorException(tr("Shader GLSL files must be in the shaders directory"));
697 }
698
699 file_name = file_name.right(file_name.size() - shaders_path.size() - 1);
700 if (!file_name.isEmpty()) {
701 try_command(new SetGlslFileCommand(*this, which, file_name));
702 }
703 }
704 }
705 }
706 catch (const EditorException& ex) {
707 ex.show_dialog();
708 }
709 }
710
711 /**
712 * @brief Saves the fragment shader GLSL file.
713 * @param which The GLSL editor to save.
714 */
save_source_file(WhichGlslEditor which)715 void ShaderEditor::save_source_file(WhichGlslEditor which) {
716
717 if (shader == nullptr) {
718 return;
719 }
720
721 TextEditor* glsl_editor = get_glsl_editor(which);
722 glsl_editor->save();
723 glsl_editor->get_undo_stack().setClean();
724 }
725
726 /**
727 * @brief Called when the is-modified state of a GLSL editor has changed.
728 * @param which The GLSL editor whose file has changed.
729 * @param clean @c true if the file is now clean, @c false if it is now
730 * modified.
731 */
source_editor_modification_state_changed(WhichGlslEditor which,bool clean)732 void ShaderEditor::source_editor_modification_state_changed(WhichGlslEditor which, bool clean) {
733
734 QString title;
735 int tab_index = 0;
736 switch (which) {
737 case WhichGlslEditor::VERTEX_EDITOR:
738 title = "Vertex shader";
739 tab_index = 0;
740 break;
741 case WhichGlslEditor::FRAGMENT_EDITOR:
742 title = "Fragment shader";
743 tab_index = 1;
744 break;
745 }
746
747 if (!clean) {
748 title += '*';
749 }
750 ui.shader_files_tab_widget->setTabText(tab_index, title);
751
752 if (clean) {
753 // A GLSL file was just saved: tell the preview to update.
754 ui.shader_previewer->on_source_changed();
755 }
756 }
757
758 /**
759 * @brief Shows the appropriate preview settings page depending on the radio
760 * button checked.
761 */
preview_radio_changed()762 void ShaderEditor::preview_radio_changed() {
763
764 if (ui.preview_picture_radio->isChecked()) {
765 ui.preview_file_widget->setCurrentWidget(ui.preview_picture_page);
766 }
767 else if (ui.preview_map_radio->isChecked()) {
768 ui.preview_file_widget->setCurrentWidget(ui.preview_map_page);
769 }
770 else if (ui.preview_sprite_radio->isChecked()) {
771 ui.preview_file_widget->setCurrentWidget(ui.preview_sprite_page);
772 preview_selected_sprite_changed();
773 }
774
775 update_preview_image();
776 }
777
778 /**
779 * @brief Sets up the sprite animation and direction fields according
780 * to the selected sprite.
781 */
preview_selected_sprite_changed()782 void ShaderEditor::preview_selected_sprite_changed() {
783
784 ui.preview_sprite_animation_field->clear();
785
786 const QString& sprite_id = ui.preview_sprite_field->get_selected_id();
787 if (sprite_id.isEmpty() ||
788 !quest.get_database().exists(ResourceType::SPRITE, sprite_id) ||
789 !quest.exists(quest.get_sprite_path(sprite_id))) {
790 update_preview_image();
791 return;
792 }
793
794 SpriteModel sprite(get_quest(), sprite_id);
795 ui.preview_sprite_animation_field->addItems(sprite.get_animation_names());
796 ui.preview_sprite_animation_field->setCurrentText(sprite.get_default_animation_name());
797 }
798
799 /**
800 * @brief Called when the user selects a sprite animation.
801 */
preview_sprite_animation_changed()802 void ShaderEditor::preview_sprite_animation_changed() {
803
804 const QString& sprite_id = ui.preview_sprite_field->get_selected_id();
805 if (!sprite_id.isEmpty() &&
806 quest.get_database().exists(ResourceType::SPRITE, sprite_id) &&
807 quest.exists(quest.get_sprite_path(sprite_id))) {
808 SpriteModel sprite(get_quest(), sprite_id);
809 const QString& animation = ui.preview_sprite_animation_field->currentText();
810 int num_directions = sprite.get_animation_num_directions({animation, 0});
811 if (num_directions > 0) {
812 ui.preview_sprite_direction_field->setMaximum(num_directions - 1);
813 }
814 }
815 }
816
817 /**
818 * @brief Lets the user choose a PNG file for the preview.
819 */
browse_preview_picture()820 void ShaderEditor::browse_preview_picture() {
821
822 if (shader == nullptr) {
823 return;
824 }
825
826 try {
827 const QString& directory = get_quest().get_resource_path(ResourceType::SPRITE);
828 QString file_name = QFileDialog::getOpenFileName(
829 this,
830 tr("Open a PNG picture"),
831 directory,
832 tr("PNG file (*.png)")
833 );
834
835 if (file_name.isEmpty()) {
836 return;
837 }
838
839 ui.preview_picture_field->setText(file_name);
840 update_preview_image();
841 }
842 catch (const EditorException& ex) {
843 ex.show_dialog();
844 }
845 }
846
847 /**
848 * @brief Updates the image to be displayed in the preview widget.
849 */
update_preview_image()850 void ShaderEditor::update_preview_image() {
851
852 QImage image;
853 EditorSettings settings;
854
855 const QString& picture_file_name = ui.preview_picture_field->text();
856 if (!picture_file_name.isEmpty()) {
857 settings.set_value(EditorSettings::shader_preview_picture_file, picture_file_name);
858 }
859 const QString& map_id = ui.preview_map_field->get_selected_id();
860 if (!map_id.isEmpty()) {
861 settings.set_value(EditorSettings::shader_preview_map_id, map_id);
862 }
863 const QString& sprite_id = ui.preview_sprite_field->get_selected_id();
864 if (!sprite_id.isEmpty()) {
865 settings.set_value(EditorSettings::shader_preview_sprite_id, sprite_id);
866 }
867
868 if (ui.preview_picture_radio->isChecked()) {
869 if (!picture_file_name.isEmpty()) {
870 image = QImage(picture_file_name);
871 }
872 settings.set_value(EditorSettings::shader_preview_type, "picture");
873 }
874 else if (ui.preview_map_radio->isChecked()) {
875 if (!map_id.isEmpty()) {
876 MapModel map(get_quest(), map_id);
877 MapView view;
878 view.set_map(&map);
879 image = view.export_to_image();
880 }
881 settings.set_value(EditorSettings::shader_preview_type, "map");
882 }
883 else if (ui.preview_sprite_radio->isChecked()) {
884 if (!sprite_id.isEmpty() &&
885 quest.get_database().exists(ResourceType::SPRITE, sprite_id) &&
886 quest.exists(quest.get_sprite_path(sprite_id))) {
887 SpriteModel sprite(get_quest(), sprite_id);
888 QString animation = ui.preview_sprite_animation_field->currentText();
889 int direction = ui.preview_sprite_direction_field->value();
890 if (!sprite.animation_exists(animation)) {
891 animation = sprite.get_default_animation_name();
892 }
893 if (direction < 0 || direction >= sprite.get_animation_num_directions({animation, 0})) {
894 direction = 0;
895 }
896 QPixmap pixmap = sprite.get_direction_first_frame({ animation, direction });
897 image = pixmap.toImage();
898 settings.set_value(EditorSettings::shader_preview_sprite_animation, animation);
899 settings.set_value(EditorSettings::shader_preview_sprite_direction, direction);
900 }
901 settings.set_value(EditorSettings::shader_preview_type, "sprite");
902 }
903
904 ui.shader_previewer->set_preview_image(image);
905 }
906
907 }
908