1/**************************************************************************/
2/* editor_locale_dialog.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "editor_locale_dialog.h"
32
33#include "core/config/project_settings.h"
34#include "editor/editor_scale.h"
35#include "editor/editor_undo_redo_manager.h"
36#include "scene/gui/check_button.h"
37#include "scene/gui/line_edit.h"
38#include "scene/gui/option_button.h"
39#include "scene/gui/tree.h"
40
41void EditorLocaleDialog::_bind_methods() {
42 ADD_SIGNAL(MethodInfo("locale_selected", PropertyInfo(Variant::STRING, "locale")));
43}
44
45void EditorLocaleDialog::ok_pressed() {
46 if (edit_filters->is_pressed()) {
47 return; // Do not update, if in filter edit mode.
48 }
49
50 String locale;
51 if (lang_code->get_text().is_empty()) {
52 return; // Language code is required.
53 }
54 locale = lang_code->get_text();
55
56 if (!script_code->get_text().is_empty()) {
57 locale += "_" + script_code->get_text();
58 }
59 if (!country_code->get_text().is_empty()) {
60 locale += "_" + country_code->get_text();
61 }
62 if (!variant_code->get_text().is_empty()) {
63 locale += "_" + variant_code->get_text();
64 }
65
66 emit_signal(SNAME("locale_selected"), TranslationServer::get_singleton()->standardize_locale(locale));
67 hide();
68}
69
70void EditorLocaleDialog::_item_selected() {
71 if (updating_lists) {
72 return;
73 }
74
75 if (edit_filters->is_pressed()) {
76 return; // Do not update, if in filter edit mode.
77 }
78
79 TreeItem *l = lang_list->get_selected();
80 if (l) {
81 lang_code->set_text(l->get_metadata(0).operator String());
82 }
83
84 TreeItem *s = script_list->get_selected();
85 if (s) {
86 script_code->set_text(s->get_metadata(0).operator String());
87 }
88
89 TreeItem *c = cnt_list->get_selected();
90 if (c) {
91 country_code->set_text(c->get_metadata(0).operator String());
92 }
93}
94
95void EditorLocaleDialog::_toggle_advanced(bool p_checked) {
96 if (!p_checked) {
97 script_code->set_text("");
98 variant_code->set_text("");
99 }
100 _update_tree();
101}
102
103void EditorLocaleDialog::_post_popup() {
104 ConfirmationDialog::_post_popup();
105
106 if (!locale_set) {
107 lang_code->set_text("");
108 script_code->set_text("");
109 country_code->set_text("");
110 variant_code->set_text("");
111 }
112 edit_filters->set_pressed(false);
113 _update_tree();
114}
115
116void EditorLocaleDialog::_filter_lang_option_changed() {
117 TreeItem *t = lang_list->get_edited();
118 String lang = t->get_metadata(0);
119 bool checked = t->is_checked(0);
120
121 Variant prev;
122 Array f_lang_all;
123
124 if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/language_filter")) {
125 f_lang_all = GLOBAL_GET("internationalization/locale/language_filter");
126 prev = f_lang_all;
127 }
128
129 int l_idx = f_lang_all.find(lang);
130
131 if (checked) {
132 if (l_idx == -1) {
133 f_lang_all.append(lang);
134 }
135 } else {
136 if (l_idx != -1) {
137 f_lang_all.remove_at(l_idx);
138 }
139 }
140
141 f_lang_all.sort();
142
143 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
144 undo_redo->create_action(TTR("Changed Locale Language Filter"));
145 undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/language_filter", f_lang_all);
146 undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/language_filter", prev);
147 undo_redo->commit_action();
148}
149
150void EditorLocaleDialog::_filter_script_option_changed() {
151 TreeItem *t = script_list->get_edited();
152 String scr_code = t->get_metadata(0);
153 bool checked = t->is_checked(0);
154
155 Variant prev;
156 Array f_script_all;
157
158 if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/script_filter")) {
159 f_script_all = GLOBAL_GET("internationalization/locale/script_filter");
160 prev = f_script_all;
161 }
162
163 int l_idx = f_script_all.find(scr_code);
164
165 if (checked) {
166 if (l_idx == -1) {
167 f_script_all.append(scr_code);
168 }
169 } else {
170 if (l_idx != -1) {
171 f_script_all.remove_at(l_idx);
172 }
173 }
174
175 f_script_all.sort();
176
177 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
178 undo_redo->create_action(TTR("Changed Locale Script Filter"));
179 undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/script_filter", f_script_all);
180 undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/script_filter", prev);
181 undo_redo->commit_action();
182}
183
184void EditorLocaleDialog::_filter_cnt_option_changed() {
185 TreeItem *t = cnt_list->get_edited();
186 String cnt = t->get_metadata(0);
187 bool checked = t->is_checked(0);
188
189 Variant prev;
190 Array f_cnt_all;
191
192 if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/country_filter")) {
193 f_cnt_all = GLOBAL_GET("internationalization/locale/country_filter");
194 prev = f_cnt_all;
195 }
196
197 int l_idx = f_cnt_all.find(cnt);
198
199 if (checked) {
200 if (l_idx == -1) {
201 f_cnt_all.append(cnt);
202 }
203 } else {
204 if (l_idx != -1) {
205 f_cnt_all.remove_at(l_idx);
206 }
207 }
208
209 f_cnt_all.sort();
210
211 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
212 undo_redo->create_action(TTR("Changed Locale Country Filter"));
213 undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/country_filter", f_cnt_all);
214 undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/country_filter", prev);
215 undo_redo->commit_action();
216}
217
218void EditorLocaleDialog::_filter_mode_changed(int p_mode) {
219 int f_mode = filter_mode->get_selected_id();
220 Variant prev;
221
222 if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/locale_filter_mode")) {
223 prev = GLOBAL_GET("internationalization/locale/locale_filter_mode");
224 }
225
226 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
227 undo_redo->create_action(TTR("Changed Locale Filter Mode"));
228 undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/locale_filter_mode", f_mode);
229 undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/locale_filter_mode", prev);
230 undo_redo->commit_action();
231
232 _update_tree();
233}
234
235void EditorLocaleDialog::_edit_filters(bool p_checked) {
236 _update_tree();
237}
238
239void EditorLocaleDialog::_update_tree() {
240 updating_lists = true;
241
242 int filter = SHOW_ALL_LOCALES;
243 if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/locale_filter_mode")) {
244 filter = GLOBAL_GET("internationalization/locale/locale_filter_mode");
245 }
246 Array f_lang_all;
247 if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/language_filter")) {
248 f_lang_all = GLOBAL_GET("internationalization/locale/language_filter");
249 }
250 Array f_cnt_all;
251 if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/country_filter")) {
252 f_cnt_all = GLOBAL_GET("internationalization/locale/country_filter");
253 }
254 Array f_script_all;
255 if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/script_filter")) {
256 f_script_all = GLOBAL_GET("internationalization/locale/script_filter");
257 }
258 bool is_edit_mode = edit_filters->is_pressed();
259
260 filter_mode->select(filter);
261
262 // Hide text advanced edit and disable OK button if in filter edit mode.
263 advanced->set_visible(!is_edit_mode);
264 hb_locale->set_visible(!is_edit_mode && advanced->is_pressed());
265 vb_script_list->set_visible(advanced->is_pressed());
266 get_ok_button()->set_disabled(is_edit_mode);
267
268 // Update language list.
269 lang_list->clear();
270 TreeItem *l_root = lang_list->create_item(nullptr);
271 lang_list->set_hide_root(true);
272
273 Vector<String> languages = TranslationServer::get_singleton()->get_all_languages();
274 for (const String &E : languages) {
275 if (is_edit_mode || (filter == SHOW_ALL_LOCALES) || f_lang_all.has(E) || f_lang_all.is_empty()) {
276 const String &lang = TranslationServer::get_singleton()->get_language_name(E);
277 TreeItem *t = lang_list->create_item(l_root);
278 if (is_edit_mode) {
279 t->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
280 t->set_editable(0, true);
281 t->set_checked(0, f_lang_all.has(E));
282 } else if (lang_code->get_text() == E) {
283 t->select(0);
284 }
285 t->set_text(0, vformat("%s [%s]", lang, E));
286 t->set_metadata(0, E);
287 }
288 }
289
290 // Update script list.
291 script_list->clear();
292 TreeItem *s_root = script_list->create_item(nullptr);
293 script_list->set_hide_root(true);
294
295 if (!is_edit_mode) {
296 TreeItem *t = script_list->create_item(s_root);
297 t->set_text(0, TTR("[Default]"));
298 t->set_metadata(0, "");
299 }
300
301 Vector<String> scripts = TranslationServer::get_singleton()->get_all_scripts();
302 for (const String &E : scripts) {
303 if (is_edit_mode || (filter == SHOW_ALL_LOCALES) || f_script_all.has(E) || f_script_all.is_empty()) {
304 const String &scr_code = TranslationServer::get_singleton()->get_script_name(E);
305 TreeItem *t = script_list->create_item(s_root);
306 if (is_edit_mode) {
307 t->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
308 t->set_editable(0, true);
309 t->set_checked(0, f_script_all.has(E));
310 } else if (script_code->get_text() == E) {
311 t->select(0);
312 }
313 t->set_text(0, vformat("%s [%s]", scr_code, E));
314 t->set_metadata(0, E);
315 }
316 }
317
318 // Update country list.
319 cnt_list->clear();
320 TreeItem *c_root = cnt_list->create_item(nullptr);
321 cnt_list->set_hide_root(true);
322
323 if (!is_edit_mode) {
324 TreeItem *t = cnt_list->create_item(c_root);
325 t->set_text(0, "[Default]");
326 t->set_metadata(0, "");
327 }
328
329 Vector<String> countries = TranslationServer::get_singleton()->get_all_countries();
330 for (const String &E : countries) {
331 if (is_edit_mode || (filter == SHOW_ALL_LOCALES) || f_cnt_all.has(E) || f_cnt_all.is_empty()) {
332 const String &cnt = TranslationServer::get_singleton()->get_country_name(E);
333 TreeItem *t = cnt_list->create_item(c_root);
334 if (is_edit_mode) {
335 t->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
336 t->set_editable(0, true);
337 t->set_checked(0, f_cnt_all.has(E));
338 } else if (country_code->get_text() == E) {
339 t->select(0);
340 }
341 t->set_text(0, vformat("%s [%s]", cnt, E));
342 t->set_metadata(0, E);
343 }
344 }
345 updating_lists = false;
346}
347
348void EditorLocaleDialog::set_locale(const String &p_locale) {
349 const String &locale = TranslationServer::get_singleton()->standardize_locale(p_locale);
350 if (locale.is_empty()) {
351 locale_set = false;
352
353 lang_code->set_text("");
354 script_code->set_text("");
355 country_code->set_text("");
356 variant_code->set_text("");
357 } else {
358 locale_set = true;
359
360 Vector<String> locale_elements = p_locale.split("_");
361 lang_code->set_text(locale_elements[0]);
362 if (locale_elements.size() >= 2) {
363 if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) {
364 script_code->set_text(locale_elements[1]);
365 advanced->set_pressed(true);
366 }
367 if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) {
368 country_code->set_text(locale_elements[1]);
369 }
370 }
371 if (locale_elements.size() >= 3) {
372 if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) {
373 country_code->set_text(locale_elements[2]);
374 } else {
375 variant_code->set_text(locale_elements[2].to_lower());
376 advanced->set_pressed(true);
377 }
378 }
379 if (locale_elements.size() >= 4) {
380 variant_code->set_text(locale_elements[3].to_lower());
381 advanced->set_pressed(true);
382 }
383 }
384}
385
386void EditorLocaleDialog::popup_locale_dialog() {
387 popup_centered_clamped(Size2(1050, 700) * EDSCALE, 0.8);
388}
389
390EditorLocaleDialog::EditorLocaleDialog() {
391 set_title(TTR("Select a Locale"));
392
393 VBoxContainer *vb = memnew(VBoxContainer);
394 {
395 HBoxContainer *hb_filter = memnew(HBoxContainer);
396 {
397 filter_mode = memnew(OptionButton);
398 filter_mode->add_item(TTR("Show All Locales"), SHOW_ALL_LOCALES);
399 filter_mode->set_h_size_flags(Control::SIZE_EXPAND_FILL);
400 filter_mode->add_item(TTR("Show Selected Locales Only"), SHOW_ONLY_SELECTED_LOCALES);
401 filter_mode->select(0);
402 filter_mode->connect("item_selected", callable_mp(this, &EditorLocaleDialog::_filter_mode_changed));
403 hb_filter->add_child(filter_mode);
404 }
405 {
406 edit_filters = memnew(CheckButton);
407 edit_filters->set_text(TTR("Edit Filters"));
408 edit_filters->set_toggle_mode(true);
409 edit_filters->set_pressed(false);
410 edit_filters->connect("toggled", callable_mp(this, &EditorLocaleDialog::_edit_filters));
411 hb_filter->add_child(edit_filters);
412 }
413 {
414 advanced = memnew(CheckButton);
415 advanced->set_text(TTR("Advanced"));
416 advanced->set_toggle_mode(true);
417 advanced->set_pressed(false);
418 advanced->connect("toggled", callable_mp(this, &EditorLocaleDialog::_toggle_advanced));
419 hb_filter->add_child(advanced);
420 }
421 vb->add_child(hb_filter);
422 }
423 {
424 HBoxContainer *hb_lists = memnew(HBoxContainer);
425 hb_lists->set_v_size_flags(Control::SIZE_EXPAND_FILL);
426 {
427 VBoxContainer *vb_lang_list = memnew(VBoxContainer);
428 vb_lang_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
429 {
430 Label *lang_lbl = memnew(Label);
431 lang_lbl->set_text(TTR("Language:"));
432 vb_lang_list->add_child(lang_lbl);
433 }
434 {
435 lang_list = memnew(Tree);
436 lang_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
437 lang_list->connect("cell_selected", callable_mp(this, &EditorLocaleDialog::_item_selected));
438 lang_list->set_columns(1);
439 lang_list->connect("item_edited", callable_mp(this, &EditorLocaleDialog::_filter_lang_option_changed));
440 vb_lang_list->add_child(lang_list);
441 }
442 hb_lists->add_child(vb_lang_list);
443 }
444 {
445 vb_script_list = memnew(VBoxContainer);
446 vb_script_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
447 {
448 Label *script_lbl = memnew(Label);
449 // TRANSLATORS: This is the label for a list of writing systems.
450 script_lbl->set_text(TTR("Script:", "Locale"));
451 vb_script_list->add_child(script_lbl);
452 }
453 {
454 script_list = memnew(Tree);
455 script_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
456 script_list->connect("cell_selected", callable_mp(this, &EditorLocaleDialog::_item_selected));
457 script_list->set_columns(1);
458 script_list->connect("item_edited", callable_mp(this, &EditorLocaleDialog::_filter_script_option_changed));
459 vb_script_list->add_child(script_list);
460 }
461 hb_lists->add_child(vb_script_list);
462 }
463 {
464 VBoxContainer *vb_cnt_list = memnew(VBoxContainer);
465 vb_cnt_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
466 {
467 Label *cnt_lbl = memnew(Label);
468 cnt_lbl->set_text(TTR("Country:"));
469 vb_cnt_list->add_child(cnt_lbl);
470 }
471 {
472 cnt_list = memnew(Tree);
473 cnt_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
474 cnt_list->connect("cell_selected", callable_mp(this, &EditorLocaleDialog::_item_selected));
475 cnt_list->set_columns(1);
476 cnt_list->connect("item_edited", callable_mp(this, &EditorLocaleDialog::_filter_cnt_option_changed));
477 vb_cnt_list->add_child(cnt_list);
478 }
479 hb_lists->add_child(vb_cnt_list);
480 }
481 vb->add_child(hb_lists);
482 }
483 {
484 hb_locale = memnew(HBoxContainer);
485 hb_locale->set_h_size_flags(Control::SIZE_EXPAND_FILL);
486 {
487 {
488 VBoxContainer *vb_language = memnew(VBoxContainer);
489 vb_language->set_h_size_flags(Control::SIZE_EXPAND_FILL);
490 {
491 Label *language_lbl = memnew(Label);
492 language_lbl->set_text(TTR("Language"));
493 vb_language->add_child(language_lbl);
494 }
495 {
496 lang_code = memnew(LineEdit);
497 lang_code->set_max_length(3);
498 lang_code->set_tooltip_text("Language");
499 vb_language->add_child(lang_code);
500 }
501 hb_locale->add_child(vb_language);
502 }
503 {
504 VBoxContainer *vb_script = memnew(VBoxContainer);
505 vb_script->set_h_size_flags(Control::SIZE_EXPAND_FILL);
506 {
507 Label *script_lbl = memnew(Label);
508 // TRANSLATORS: This refers to a writing system.
509 script_lbl->set_text(TTR("Script", "Locale"));
510 vb_script->add_child(script_lbl);
511 }
512 {
513 script_code = memnew(LineEdit);
514 script_code->set_max_length(4);
515 script_code->set_tooltip_text("Script");
516 vb_script->add_child(script_code);
517 }
518 hb_locale->add_child(vb_script);
519 }
520 {
521 VBoxContainer *vb_country = memnew(VBoxContainer);
522 vb_country->set_h_size_flags(Control::SIZE_EXPAND_FILL);
523 {
524 Label *country_lbl = memnew(Label);
525 country_lbl->set_text(TTR("Country"));
526 vb_country->add_child(country_lbl);
527 }
528 {
529 country_code = memnew(LineEdit);
530 country_code->set_max_length(2);
531 country_code->set_tooltip_text("Country");
532 vb_country->add_child(country_code);
533 }
534 hb_locale->add_child(vb_country);
535 }
536 {
537 VBoxContainer *vb_variant = memnew(VBoxContainer);
538 vb_variant->set_h_size_flags(Control::SIZE_EXPAND_FILL);
539 {
540 Label *variant_lbl = memnew(Label);
541 variant_lbl->set_text(TTR("Variant"));
542 vb_variant->add_child(variant_lbl);
543 }
544 {
545 variant_code = memnew(LineEdit);
546 variant_code->set_h_size_flags(Control::SIZE_EXPAND_FILL);
547 variant_code->set_placeholder("Variant");
548 variant_code->set_tooltip_text("Variant");
549 vb_variant->add_child(variant_code);
550 }
551 hb_locale->add_child(vb_variant);
552 }
553 }
554 vb->add_child(hb_locale);
555 }
556 add_child(vb);
557 _update_tree();
558
559 set_ok_button_text(TTR("Select"));
560}
561