The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
file_dialog.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2011 - 2025
3 by Iris Morelle <shadowm2006@gmail.com>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
16#define GETTEXT_DOMAIN "wesnoth-lib"
17
19
20#include "cursor.hpp"
21#include "desktop/paths.hpp"
22#include "desktop/open.hpp"
23#include "filesystem.hpp"
33#include "gettext.hpp"
34#include "log.hpp"
37
38#include <boost/filesystem/path.hpp>
39
40#include <algorithm>
41#include <functional>
42
43static lg::log_domain log_filedlg{"gui/dialogs/file_dialog"};
44#define ERR_FILEDLG LOG_STREAM(err, log_filedlg)
45#define WRN_FILEDLG LOG_STREAM(warn, log_filedlg)
46#define LOG_FILEDLG LOG_STREAM(info, log_filedlg)
47#define DBG_FILEDLG LOG_STREAM(debug, log_filedlg)
48
49namespace fs = filesystem;
50
51namespace
52{
53const std::string icon_dir = "icons/action/browse_25.png";
54const std::string icon_parent = "icons/action/undo_25.png";
55const std::string icon_file = "misc/file.png";
56// NOTE: Does not need to be the same as PARENT_DIR! Use PARENT_DIR to build
57// relative paths for non-presentational purposes instead.
58const std::string label_parent = "..";
59
60const std::string CURRENT_DIR = ".";
61const std::string PARENT_DIR = "..";
62
63// Some fonts used for internationalization don't define this glyph
64const std::string error_marker = markup::span_attribute("face", "DejaVuSans", "✘");
65
66const int FILE_DIALOG_ITEM_RETVAL = 9001;
67const int FILE_DIALOG_MAX_ENTRY_LENGTH = 42;
68
69inline std::string concat_path(const std::string& a, const std::string& b)
70{
71 //
72 // As of Boost 1.61, normalize_path() displays unusual behavior when passing
73 // it paths with extra path separators (e.g. //opt becomes
74 // //opt/home/shadowm/src/wesnoth, where the extraneous sequence is probably
75 // the current working dir), so avoid leaving those around.
76 //
77 // TODO: Maybe handle this corner case in filesystem::normalize_path()
78 // instead, really.
79 //
80 if((a.empty() || !fs::is_path_sep(a.back())) && (b.empty() || !fs::is_path_sep(b.front()))) {
81 return a + fs::path_separator() + b;
82 } else {
83 return a + b;
84 }
85}
86
87inline std::string filesystem_root()
88{
89 // TODO: Multiple drives support (may require cooperation from the caller).
90 return std::string(1, fs::path_separator());
91}
92
93inline void isort_dir_entries(std::vector<std::string>& entries)
94{
95 // Yes, this uses Wesnoth's locale and not the filesystem/OS locale. Yes, this
96 // isn't ideal. No, we don't really need to worry about it. It's just a
97 // cosmetic procedure anyway.
98 std::sort(entries.begin(), entries.end(),
99 [](const std::string& a, const std::string& b) { return translation::icompare(a, b) < 0; });
100}
101
102inline bool path_contains_space(const std::string& path)
103{
104 return std::find_if(path.begin(), path.end(), utils::portable_isspace) != path.end();
105}
106
107} // unnamed namespace
108
109namespace gui2::dialogs
110{
111
112REGISTER_DIALOG(file_dialog)
113
115 : modal_dialog(window_id())
116 , title_(_("Find File"))
117 , msg_()
118 , ok_label_()
119 , extension_()
120 , current_entry_()
121 , current_dir_()
122 , read_only_(false)
123 , save_mode_(false)
124 , dir_files_()
125 , dir_subdirs_()
126 , bookmark_paths_()
127 , current_bookmark_()
128 , user_bookmarks_begin_()
129 , extra_paths_()
130{
131}
132
133std::string file_dialog::path() const
134{
135 const std::string& dir_norm = fs::normalize_path(current_dir_, true);
136
137 if(current_entry_.empty() || current_entry_ == CURRENT_DIR) {
138 return dir_norm;
139 } else if(current_entry_ == PARENT_DIR) {
140 return fs::directory_name(dir_norm);
141 }
142
143 return concat_path(dir_norm, current_entry_);
144}
145
147{
148 if(value.empty()) {
149 current_dir_ = filesystem_root();
150 }
151
152 const std::string& norm = fs::normalize_path(value, true);
153
154 if(fs::is_directory(norm)) {
155 current_dir_ = norm;
156 } else {
158 if(current_dir_.empty()) {
159 current_dir_ = filesystem_root();
160 }
161 // The file may or may not exist. We'll find out eventually when setting up
162 // the dialog.
164 }
165
166 return *this;
167}
168
170{
172 return *this;
173}
174
176{
177 if(!save_mode_) {
178 return true;
179 }
180
181 auto& validation_msg = find_widget<styled_widget>("validation_msg");
182 auto& save_btn = find_widget<button>("ok");
183
184 // Most codepaths want to disable the button; do it preemptively.
185 save_btn.set_active(false);
186
187 boost::filesystem::path filename(find_widget<text_box>("filename").get_value());
188
189 // Empty filename
190 if(filename.stem().empty()) {
191 validation_msg.set_label(markup::span_color("#00dcff", _("enter filename")));
192 return false;
193 }
194
195 // Invalid extension
196 if(!utils::contains(extensions_, filename.extension())) {
197 // TODO: make extensions_ itself a vector<t_string> if possible
198 auto as_tstrings = std::vector<t_string>(extensions_.begin(), extensions_.end());
199 utils::string_map i18n_strings{{"extensions", utils::format_disjunct_list("", as_tstrings)}};
200
201 std::string message = VGETTEXT("invalid extension (use $extensions)", i18n_strings);
202 validation_msg.set_label(markup::span_color("red", error_marker, ' ', message));
203 return false;
204 }
205
206 // Whitespace
207 if(path_contains_space(filename.string())) {
208 std::string message = _("filename contains whitespace");
209 validation_msg.set_label(markup::span_color("red", error_marker, ' ', message));
210 return false;
211 }
212
213 // Ensure there's always *some* value in the validation message (hence, whitespace)
214 // so it doesn't get hidden if there's no validation message when the dialog is opened.
215 // Since this function is called from pre_show, it's fine to do this here.
216 validation_msg.set_label(" ");
217 save_btn.set_active(true);
218 return true;
219}
220
222{
223 styled_widget& title = find_widget<styled_widget>("title");
224 styled_widget& message = find_widget<styled_widget>("message");
225 styled_widget& ok = find_widget<styled_widget>("ok");
226
227 title.set_label(title_);
228
229 if(msg_.empty()) {
231 } else {
234 }
235
236 if(ok_label_.empty()) {
237 ok.set_label(save_mode_ ? _("Save") : _("Open"));
238 } else {
240 }
241
242 listbox& bookmarks_bar = find_widget<listbox>("bookmarks");
243
244 find_widget<styled_widget>("current_dir").set_text_ellipse_mode(PANGO_ELLIPSIZE_START);
245
246 //
247 // Push hard-coded bookmarks.
248 //
249
252 std::vector<desktop::path_info> bookmarks = desktop::game_paths(extra_paths_);
254 bookmarks.insert(bookmarks.end(), sys_paths.begin(), sys_paths.end());
255
256 bookmark_paths_.clear();
258
259 for(const auto& pinfo : bookmarks) {
260 bookmark_paths_.push_back(pinfo.path);
261 bookmarks_bar.add_row(widget_data{{ "bookmark", {{ "label", pinfo.display_name() }}}});
262 }
263
264 //
265 // Push user-defined bookmarks.
266 //
267
268 const std::vector<desktop::bookmark_info>& user_bookmarks = desktop::user_bookmarks();
269
270 if(!user_bookmarks.empty()) {
272 }
273
274 for(const auto& bookmark : user_bookmarks) {
275 bookmark_paths_.push_back(bookmark.path);
276 bookmarks_bar.add_row(widget_data{{ "bookmark", {{ "label", bookmark.label }}}});
277 }
278
280
281 listbox& filelist = find_widget<listbox>("filelist");
282 text_box& file_textbox = find_widget<text_box>("filename");
283
284 connect_signal_notify_modified(filelist,
285 std::bind(&file_dialog::on_row_selected, this));
286 connect_signal_notify_modified(bookmarks_bar,
287 std::bind(&file_dialog::on_bookmark_selected, this));
288 connect_signal_notify_modified(file_textbox,
289 std::bind(&file_dialog::check_filename, this));
290
292
293 button& mkdir_button = find_widget<button>("new_dir");
294 button& rm_button = find_widget<button>("delete_file");
295 button& bookmark_add_button = find_widget<button>("add_bookmark");
296 button& bookmark_del_button = find_widget<button>("remove_bookmark");
297 button& open_ext_button = find_widget<button>("open_ext");
298
299 connect_signal_mouse_left_click(mkdir_button,
300 std::bind(&file_dialog::on_dir_create_cmd, this));
301 connect_signal_mouse_left_click(rm_button,
302 std::bind(&file_dialog::on_file_delete_cmd, this));
303 connect_signal_mouse_left_click(bookmark_add_button,
304 std::bind(&file_dialog::on_bookmark_add_cmd, this));
305 connect_signal_mouse_left_click(bookmark_del_button,
306 std::bind(&file_dialog::on_bookmark_del_cmd, this));
307
309 connect_signal_mouse_left_click(open_ext_button,
310 [this](auto&&...) { desktop::open_object(path()); });
311 } else {
312 open_ext_button.set_active(false);
313 open_ext_button.set_tooltip(_("Opening files is not supported, contact your packager"));
314 }
315
316 if(read_only_) {
317 mkdir_button.set_active(false);
318 rm_button.set_active(false);
319
322 }
323
325
326 //window.keyboard_capture(find_widget<text_box>("filename", false, true));
327 keyboard_capture(&file_textbox);
328 add_to_keyboard_chain(&filelist);
329 set_exit_hook(window::exit_hook::always, [this] { return on_exit(); });
330}
331
333{
334 if(get_retval() == FILE_DIALOG_ITEM_RETVAL) {
335 // Attempting to exit by double clicking items -- only proceeds if the item
336 // was a file.
338 set_retval(retval::OK, false);
339 return true;
340 } else {
341 return false;
342 }
343 }
344
345 if(get_retval() == retval::OK) {
346 // Attempting to exit by pressing Enter/clicking OK -- only proceeds if the
347 // textbox was not altered by the user to point to a different directory.
348 return process_textbox_submit();
349 }
350
351 return true;
352}
353
355{
356 // TODO: Adapt for implementing directory selection mode.
357 return save_mode_
358 ? stype != SELECTION_IS_DIR && stype != SELECTION_PARENT_NOT_FOUND
359 : stype == SELECTION_IS_FILE;
360}
361
363{
364 // TODO: Adapt for implementing directory selection mode.
365 if(stype != SELECTION_IS_FILE) {
366 return true;
367 }
368
369 const std::string& message
370 = _("The file already exists. Do you wish to overwrite it?");
372}
373
374bool file_dialog::process_submit_common(const std::string& name)
375{
376 const auto stype = register_new_selection(name);
377
378 //DBG_FILEDLG << "current_dir_=" << current_dir_ << " current_entry_=" << current_entry_;
379
381 // TODO: evaluate if we want to call check_filename() here
382 return save_mode_ ? check_filename() && confirm_overwrite(stype) : true;
383 }
384
385 switch(stype) {
386 case SELECTION_IS_DIR:
387 // TODO: Adapt for implementing directory selection mode.
390 break;
392 // We get here in save mode or not. Use the file creation language only in
393 // save mode.
394 if(save_mode_) {
395 show_transient_error_message(VGETTEXT("The file or folder $path cannot be created.", {{"path", name}}));
396 break;
397 }
398 [[fallthrough]];
400 // We only get here if we aren't in save mode.
401 show_transient_error_message(VGETTEXT("The file or folder $path does not exist.", {{"path", name}}));
402 break;
404 // TODO: Adapt for implementing directory selection mode.
405 default:
406 assert(false && "Unimplemented selection mode or semantics");
407 }
408
409 return false;
410}
411
413{
414 listbox& filelist = find_widget<listbox>("filelist");
415 const std::string& selected_name = get_filelist_selection(filelist);
416 return process_submit_common(selected_name);
417}
418
420{
421 text_box& file_textbox = find_widget<text_box>("filename");
422 const std::string& input_name = file_textbox.get_value();
423 return !input_name.empty() && process_submit_common(input_name);
424}
425
427{
428 const int row = filelist.get_selected_row();
429
430 if(row == -1) {
431 // Shouldn't happen...
432 return "";
433 }
434
435 const bool i_am_root = fs::is_root(current_dir_);
436
437 if(row == 0 && !i_am_root) {
438 return PARENT_DIR;
439 } else {
440 std::size_t n = i_am_root ? row : row - 1;
441
442 if(n < dir_subdirs_.size()) {
443 return dir_subdirs_[n];
444 } else {
445 n -= dir_subdirs_.size();
446
447 if(n < dir_files_.size()) {
448 return dir_files_[n];
449 } else {
450 assert(false && "File list selection is out of range!");
451 }
452 }
453 }
454
455 return "";
456}
457
459{
460 std::string new_path, new_parent;
461
462 if(fs::is_relative(name)) {
463 // On Windows, \ represents a path relative to the root of the process'
464 // current working drive specified by the current working dir, so we get
465 // here. This makes it the only platform where is_relative() and is_root()
466 // aren't mutually exclusive.
467 if(fs::is_root(name)) {
468 DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' is relative to a root resource";
469 // Using the browsed dir's root drive instead of the cwd's makes the most
470 // sense for users.
471 new_parent = fs::root_name(current_dir_);
472 new_path = fs::normalize_path(concat_path(new_parent, name), true, true);
473 } else {
474 DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' seems relative";
475 new_parent = current_dir_;
476 new_path = fs::normalize_path(concat_path(current_dir_, name), true, true);
477 }
478 } else {
479 DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' seems absolute";
480 new_parent = fs::directory_name(name);
481 new_path = fs::normalize_path(name, true, true);
482 DBG_FILEDLG << "register_new_selection(): new selection is " << new_path;
483 }
484
485 if(!new_path.empty()) {
486 if(fs::is_directory(new_path)) {
487 DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' is a directory: " << new_path;
488 current_dir_ = new_path;
489 current_entry_.clear();
490 return SELECTION_IS_DIR;
491 } else if(fs::file_exists(new_path)) {
492 // FIXME: Perhaps redundant since the three-params call to normalize_path()
493 // above necessarily validates existence.
494 DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' is a file, symbolic link, or special: " << new_path;
496 current_entry_ = fs::base_name(new_path);
497 return SELECTION_IS_FILE;
498 }
499 }
500
501 // The path does not exist, at least not entirely. See if the parent does
502 // (in save mode non-existent files are accepted as long as the parent dir
503 // exists).
504 const std::string& absolute_parent = fs::normalize_path(new_parent, true, true);
505 if(!absolute_parent.empty()) {
506 DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' does not exist or is not accessible, but parent exists";
507 current_dir_ = absolute_parent;
509 return SELECTION_NOT_FOUND;
510 }
511
512 DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' does not exist or is not accessible";
514}
515
516void file_dialog::set_input_text(text_box& t, const std::string& value)
517{
518 if(value.empty()) {
520 return;
521 }
522
523 t.set_value(value);
525
526 const std::size_t vallen = t.get_length();
527 const std::size_t extlen = utf8::size(extension_);
528
529 if(save_mode_ && extlen && vallen > extlen) {
530 // Highlight everything but the extension if it matches
531 if(value.substr(vallen - extlen) == extension_) {
532 t.set_selection(0, vallen - extlen);
533 }
534 }
535}
536
538{
539 if(save_mode_ && !extension_.empty()) {
540 t.set_value(extension_);
541 t.set_selection(0, 0);
542 } else {
543 t.clear();
544 }
545}
546
548{
550
551 dir_files_.clear();
552 dir_subdirs_.clear();
553
554 // TODO: Need to detect and handle cases where we don't have search permission
555 // on current_dir_, otherwise things may get weird.
557 isort_dir_entries(dir_files_);
558 isort_dir_entries(dir_subdirs_);
559
560 //
561 // Clear and refill the filelist box.
562 //
563
564 listbox& filelist = find_widget<listbox>("filelist");
565 button& rm_button = find_widget<button>("delete_file");
566
567 filelist.clear();
568
569 // Parent entry
571 // label_parent may not necessarily be always ".." in the future, so push
572 // with check_selection = false and check the selection ourselves here.
573 push_fileview_row(filelist, label_parent, icon_parent, false);
574 if(current_entry_ == PARENT_DIR || current_entry_.empty()) {
575 filelist.select_row(0, true);
576 rm_button.set_active(false);
577 } else {
578 rm_button.set_active(true);
579 }
580 }
581
582 for(const auto& dir : dir_subdirs_) {
583 push_fileview_row(filelist, dir, icon_dir);
584 }
585
586 for(const auto& file : dir_files_) {
587 push_fileview_row(filelist, file, icon_file);
588 }
589
590 find_widget<styled_widget>("current_dir").set_label(current_dir_);
591 set_input_text(find_widget<text_box>("filename"), current_entry_);
592
594}
595
596void file_dialog::push_fileview_row(listbox& filelist, const std::string& name, const std::string& icon, bool check_selection)
597{
598 // TODO: Hopefully some day GUI2 will allow us to make labels be ellipsized
599 // dynamically at layout/rendering time.
600 std::string label = name;
601 utils::ellipsis_truncate(label, FILE_DIALOG_MAX_ENTRY_LENGTH);
602
603 grid& last_grid = filelist.add_row(widget_data{
604 { "icon", {{ "label", icon }}},
605 { "file", {{ "label", label }}},
606 });
607
608 //
609 // Crummy hack around the lack of an option to hook into row double click
610 // events for all rows using the GUI2 listbox API. Assign a special retval to
611 // each row that triggers a special check during dialog exit.
612 //
613 last_grid
614 .find_widget<toggle_panel>("item_panel")
615 .set_retval(FILE_DIALOG_ITEM_RETVAL);
616
617 if(check_selection && name == current_entry_) {
618 filelist.select_last_row(true);
619 }
620}
621
623{
624 listbox& bookmarks_bar = find_widget<listbox>("bookmarks");
625
626 // Internal state has normalized path delimiters but dot entries aren't
627 // resolved after callers call set_path(), so compare against a canonical
628 // version. The bookmark paths are already canonical, though.
629 const std::string& canon_current_dir = fs::normalize_path(current_dir_, true, true);
630
631 // Go backwards so we can match user-defined bookmarks first (otherwise it may
632 // become impossible for the user to delete them if they match any of the
633 // predefined paths).
634 auto it = std::find(bookmark_paths_.rbegin(), bookmark_paths_.rend(), canon_current_dir);
635
636 if(it == bookmark_paths_.rend()) {
637 if(current_bookmark_ >= 0) {
638 bookmarks_bar.select_row(static_cast<unsigned>(current_bookmark_), false);
639 }
641 } else {
642 const int new_selection = static_cast<int>(std::distance(bookmark_paths_.begin(), it.base()) - 1);
643 if(new_selection != current_bookmark_) {
644 assert(static_cast<unsigned>(new_selection) < bookmarks_bar.get_item_count());
645 if(current_bookmark_ >= 0) {
646 bookmarks_bar.select_row(static_cast<unsigned>(current_bookmark_), false);
647 }
648 bookmarks_bar.select_row(static_cast<unsigned>(new_selection), true);
649 current_bookmark_ = new_selection;
650 }
651 }
652
653 // Update bookmark edit controls.
654 button& del_button = find_widget<button>("remove_bookmark");
655
656 if(user_bookmarks_begin_ == -1) {
657 del_button.set_active(false);
658 } else {
660 }
661}
662
664{
665 listbox& filelist = find_widget<listbox>("filelist");
666 text_box& file_textbox = find_widget<text_box>("filename");
667 button& rm_button = find_widget<button>("delete_file");
668
669 // Don't use register_new_selection() here, we don't want any parsing to be
670 // performed at this point.
672
673 // Clear the textbox when selecting ..
674 if(current_entry_ != PARENT_DIR) {
675 set_input_text(file_textbox, current_entry_);
676 rm_button.set_active(true);
677 } else {
678 clear_input_text(file_textbox);
679 rm_button.set_active(false);
680 }
681
682 // Need to do this every time so that input can still be sent to the
683 // textbox without clicking on it.
684 keyboard_capture(&file_textbox);
685}
686
688{
689 // Don't let us steal the focus from the primary widgets.
690 text_box& file_textbox = find_widget<text_box>("filename");
691 keyboard_capture(&file_textbox);
692
693 listbox& bookmarks_bar = find_widget<listbox>("bookmarks");
694 const int new_selection = bookmarks_bar.get_selected_row();
695
696 if(new_selection < 0) {
697 if(current_bookmark_ >= 0) {
698 // Don't allow the user to deselect the selected bookmark. That wouldn't
699 // make any sense.
700 bookmarks_bar.select_row(static_cast<unsigned>(current_bookmark_));
701 }
702
703 return;
704 }
705
706 assert(static_cast<unsigned>(new_selection) < bookmark_paths_.size());
707 current_bookmark_ = new_selection;
708 set_path(bookmark_paths_[new_selection]);
710
711 // Update bookmark edit controls.
712 button& del_button = find_widget<button>("remove_bookmark");
713 del_button.set_active(user_bookmarks_begin_ >= 0
715}
716
718{
719 const std::string& default_label = fs::base_name(current_dir_);
720
721 std::string label = default_label;
722
723 const bool confirm = bookmark_create::execute(label);
724 if(!confirm) {
725 return;
726 }
727
728 if(label.empty()) {
729 label = default_label;
730 }
731
732 listbox& bookmarks_bar = find_widget<listbox>("bookmarks");
733
735 bookmark_paths_.push_back(current_dir_);
736 const unsigned top_bookmark = bookmark_paths_.size() - 1;
737
738 if(user_bookmarks_begin_ == -1) {
739 user_bookmarks_begin_ = top_bookmark;
740 }
741
743 data["bookmark"]["label"] = label;
744 bookmarks_bar.add_row(data);
745
747
749}
750
752{
753 assert(user_bookmarks_begin_ >= 0
754 && current_bookmark_ >= 0
756 && current_bookmark_ < static_cast<int>(bookmark_paths_.size()));
757
758 listbox& bookmarks_bar = find_widget<listbox>("bookmarks");
761 bookmarks_bar.remove_row(current_bookmark_);
762
764
766}
767
769{
770 std::string new_dir_name;
771
772 if(folder_create::execute(new_dir_name)) {
773 const std::string& new_path = concat_path(current_dir_, new_dir_name);
774
775 if(!fs::make_directory(new_path)) {
777 VGETTEXT("Could not create a new folder at $path|. Make sure you have the appropriate permissions to write to this location.",
778 {{"path", new_path}}));
779 } else {
781 }
782 }
783}
784
786{
787 if(current_entry_.empty()) {
788 return;
789 }
790
791 const std::string& selection = concat_path(current_dir_, current_entry_);
792 const bool is_dir = fs::is_directory(selection);
793
794 const std::string& message = (is_dir
795 ? _("The following folder and its contents will be permanently deleted:")
796 : _("The following file will be permanently deleted:"))
797 + "\n\n" + selection + "\n\n" + _("Do you wish to continue?");
798
800 return;
801 }
802
803 const bool result = is_dir
804 ? fs::delete_directory(selection)
805 : fs::delete_file(selection);
806
807 if(!result) {
809 VGETTEXT("Could not delete $path|. Make sure you have the appropriate permissions to write to this location.",
810 {{"path", selection}}));
811 } else {
813 }
814}
815
816} // namespace dialogs
double t
Simple push button.
Definition button.hpp:36
virtual void set_active(const bool active) override
See styled_widget::set_active.
Definition button.cpp:64
static bool execute(std::string &bookmark_name)
The execute function; see modal_dialog for more information.
void sync_bookmarks_bar()
Updates the bookmarks bar state to reflect the internal state.
bool process_submit_common(const std::string &name)
file_dialog & set_path(const std::string &value)
Sets the initial file selection.
void on_bookmark_add_cmd()
Handles Add Bookmark button press events.
bool on_exit()
Handles dialog exit events and decides whether to proceed or not.
bool check_filename()
Check if the filename is valid and disable save button if invalid.
std::vector< std::string > bookmark_paths_
const std::string & title() const
Gets the current dialog title text.
SELECTION_TYPE register_new_selection(const std::string &name)
Updates the internal state and returns the type of the selection.
void set_input_text(class text_box &t, const std::string &value)
void on_file_delete_cmd()
Handles Delete button press events.
std::string path() const
Gets the current file selection.
file_dialog & set_filename(const std::string &value)
Sets the initial file name input but not the path.
std::vector< std::string > dir_files_
void on_bookmark_selected()
Handles selection or deselection of bookmarks.
void clear_input_text(class text_box &t)
bool is_selection_type_acceptable(SELECTION_TYPE stype) const
Returns whether the given selection type is acceptable for closing the dialog.
bool process_fileview_submit()
Processes file view selection in reaction to row double-click events.
bool process_textbox_submit()
Processes textbox input in reaction to OK button/Enter key events.
virtual void pre_show() override
Actions to be taken before showing the window.
void on_dir_create_cmd()
Handles New Folder button press events.
std::set< desktop::GAME_PATH_TYPES > extra_paths_
void on_row_selected()
Handles file/directory selection on single-click.
void push_fileview_row(class listbox &filelist, const std::string &name, const std::string &icon, bool check_selection=true)
Row building helper for refresh_fileview().
bool confirm_overwrite(SELECTION_TYPE stype)
Prompts the user before overwriting an existing file.
void refresh_fileview()
Updates the dialog contents to match the internal state.
std::string get_filelist_selection(class listbox &filelist)
std::vector< std::string > dir_subdirs_
void on_bookmark_del_cmd()
Handles Remove Bookmark button press events.
std::vector< std::string > extensions_
Main class to show messages to the user.
Definition message.hpp:36
@ yes_no_buttons
Shows a yes and no button.
Definition message.hpp:81
Abstract base class for all modal dialogs.
Base container class.
Definition grid.hpp:32
The listbox class.
Definition listbox.hpp:41
bool select_last_row(const bool select=true)
Does exactly as advertised: selects the list's last row.
Definition listbox.hpp:186
grid & add_row(const widget_item &item, const int index=-1)
When an item in the list is selected by the user we need to update the state.
Definition listbox.cpp:92
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition listbox.cpp:267
void remove_row(const unsigned row, unsigned count=1)
Removes a row in the listbox.
Definition listbox.cpp:108
void clear()
Removes all the rows in the listbox, clearing it.
Definition listbox.cpp:147
int get_selected_row() const
Returns the first selected row.
Definition listbox.cpp:290
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition listbox.cpp:153
void set_tooltip(const t_string &tooltip)
virtual void set_label(const t_string &text)
virtual void set_use_markup(bool use_markup)
std::string get_value() const
A widget that allows the user to input text in single line.
Definition text_box.hpp:125
T * find_widget(const std::string_view id, const bool must_be_active, const bool must_exist)
Gets a widget with the wanted id.
Definition widget.hpp:747
void set_visible(const visibility visible)
Definition widget.cpp:479
@ invisible
The user set the widget invisible, that means:
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition window.hpp:387
void keyboard_capture(widget *widget)
Definition window.cpp:1197
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition window.cpp:1211
void set_exit_hook(exit_hook mode, const Func &hook)
Sets the window's exit hook.
Definition window.hpp:435
int get_retval()
Definition window.hpp:394
#define DBG_FILEDLG
static lg::log_domain log_filedlg
Declarations for File-IO.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
static std::string _(const char *str)
Definition gettext.hpp:100
This file contains the window object, this object is a top level container which has the event manage...
Standard logging facilities (interface).
#define REGISTER_DIALOG(window_id)
Wrapper for REGISTER_DIALOG2.
@ WAIT
Definition cursor.hpp:28
std::vector< bookmark_info > user_bookmarks()
Definition paths.cpp:276
unsigned add_user_bookmark(const std::string &label, const std::string &path)
Definition paths.cpp:251
@ GAME_USER_DATA_DIR
User data dir.
Definition paths.hpp:60
@ GAME_CORE_DATA_DIR
Game data dir.
Definition paths.hpp:59
void remove_user_bookmark(unsigned index)
Definition paths.cpp:264
constexpr bool open_object_is_supported()
Returns whether open_object() is supported/implemented for the current platform.
Definition open.hpp:54
std::vector< path_info > system_paths(const std::set< SYSTEM_PATH_TYPES > &paths)
Returns a list of system-defined paths.
Definition paths.cpp:228
@ SYSTEM_USER_PROFILE
Path to the user's profile dir (e.g.
Definition paths.hpp:67
@ SYSTEM_ALL_DRIVES
Paths for each storage media found (Windows), /media and/or /mnt (X11, if non-empty).
Definition paths.hpp:66
@ SYSTEM_ROOTFS
Path to the root of the filesystem hierarchy (ignored on Windows).
Definition paths.hpp:68
bool open_object(const std::string &path_or_url)
Opens the specified object with the default application configured for its type.
Definition open.cpp:50
std::vector< path_info > game_paths(const std::set< GAME_PATH_TYPES > &paths)
Returns a list of game-related paths.
Definition paths.cpp:200
bool is_relative(const std::string &path)
Returns whether the path seems to be relative.
bool file_exists(const std::string &name)
Returns true if a file or directory with such name already exists.
bool is_root(const std::string &path)
Returns whether the path is the root of the file hierarchy.
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs=nullptr, name_mode mode=name_mode::FILE_NAME_ONLY, filter_mode filter=filter_mode::NO_FILTER, reorder_mode reorder=reorder_mode::DONT_REORDER, file_tree_checksum *checksum=nullptr)
Get a list of all files and/or directories in a given directory.
std::string base_name(const std::string &file, const bool remove_extension=false)
Returns the base filename of a file, with directory name stripped.
bool delete_file(const std::string &filename)
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
std::string root_name(const std::string &path)
Returns the name of the root device if included in the given path.
std::string directory_name(const std::string &file)
Returns the directory name of a file, with filename stripped.
std::string nearest_extant_parent(const std::string &file)
Finds the nearest parent in existence for a file or directory.
char path_separator()
Returns the standard path separator for the current platform.
bool make_directory(const std::string &dirname)
bool delete_directory(const std::string &dirname, const bool keep_pbl=false)
bool is_path_sep(char c)
Returns whether c is a path separator.
std::string normalize_path(const std::string &path, bool normalize_separators=false, bool resolve_dot_entries=false)
Returns the absolute path of a file.
std::map< std::string, widget_item > widget_data
Definition widget.hpp:36
void show_transient_error_message(const std::string &message, const std::string &image, const bool message_use_markup)
Shows a transient error message to the user.
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition message.cpp:148
@ OK
Dialog was closed with the OK button.
Definition retval.hpp:35
@ CANCEL
Dialog was closed with the CANCEL button.
Definition retval.hpp:38
std::string span_attribute(std::string_view key, const Value &value, Args &&... data)
Wraps the given data in a span tag with the specified attribute and value.
Definition markup.hpp:92
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition markup.hpp:110
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition unicode.cpp:81
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition general.hpp:88
void ellipsis_truncate(std::string &str, const std::size_t size)
Truncates a string to a given utf-8 character count and then appends an ellipsis.
std::string format_disjunct_list(const t_string &empty, const std::vector< t_string > &elems)
Format a disjunctive list.
bool portable_isspace(const char c)
std::map< std::string, t_string > string_map
Desktop environment interaction functions.
Desktop paths, storage media and bookmark functions.
std::string_view data
Definition picture.cpp:188
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
std::string filename
Filename.
static map_location::direction n
#define b