The Battle for Wesnoth  1.19.13+dev
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"
24 #include "formula/string_utils.hpp"
26 #include "gui/dialogs/message.hpp"
28 #include "gui/widgets/button.hpp"
29 #include "gui/widgets/listbox.hpp"
30 #include "gui/widgets/text_box.hpp"
32 #include "gui/widgets/window.hpp"
33 #include "gettext.hpp"
34 #include "log.hpp"
35 #include "serialization/markup.hpp"
37 
38 #include <boost/filesystem/path.hpp>
39 
40 #include <algorithm>
41 #include <functional>
42 
43 static 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 
49 namespace fs = filesystem;
50 
51 namespace
52 {
53 const std::string icon_dir = "icons/action/browse_25.png";
54 const std::string icon_parent = "icons/action/undo_25.png";
55 const 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.
58 const std::string label_parent = "..";
59 
60 const std::string CURRENT_DIR = ".";
61 const std::string PARENT_DIR = "..";
62 
63 // Some fonts used for internationalization don't define this glyph
64 const std::string error_marker = markup::span_attribute("face", "DejaVuSans", "✘");
65 
66 const int FILE_DIALOG_ITEM_RETVAL = 9001;
67 const int FILE_DIALOG_MAX_ENTRY_LENGTH = 42;
68 
69 inline 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 
87 inline 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 
93 inline 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 
102 inline 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 
109 namespace gui2::dialogs
110 {
111 
112 REGISTER_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 
133 std::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 
146 file_dialog& file_dialog::set_path(const std::string& value)
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 
169 file_dialog& file_dialog::set_filename(const std::string& value)
170 {
171  current_entry_ = value;
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 {
233  message.set_use_markup(true);
234  }
235 
236  if(ok_label_.empty()) {
237  ok.set_label(save_mode_ ? _("Save") : _("Open"));
238  } else {
239  ok.set_label(ok_label_);
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 
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 
291  check_filename();
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));
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 
374 bool 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 
380  if(is_selection_type_acceptable(stype)) {
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]];
399  case SELECTION_NOT_FOUND:
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;
403  case SELECTION_IS_FILE:
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;
495  current_dir_ = fs::directory_name(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 
516 void 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);
524  check_filename();
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
570  if(!fs::is_root(current_dir_)) {
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 
593  on_row_selected();
594 }
595 
596 void 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  }
640  current_bookmark_ = -1;
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 
746  current_bookmark_ = -1;
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 
763  current_bookmark_ = -1;
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
Definition: astarsearch.cpp:63
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.
const std::string & title() const
Gets the current dialog title text.
Definition: file_dialog.hpp:51
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_
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:289
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
void set_visible(const visibility visible)
Definition: widget.cpp:479
@ invisible
The user set the widget invisible, that means:
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_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:395
void keyboard_capture(widget *widget)
Definition: window.cpp:1201
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1210
void set_exit_hook(exit_hook mode, const Func &hook)
Sets the window's exit hook.
Definition: window.hpp:448
int get_retval()
Definition: window.hpp:402
#define DBG_FILEDLG
Definition: file_dialog.cpp:47
static lg::log_domain log_filedlg
Definition: file_dialog.cpp:43
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:97
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:201
This file contains the window object, this object is a top level container which has the event manage...
Standard logging facilities (interface).
@ 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
bool open_object([[maybe_unused]] const std::string &path_or_url)
Definition: open.cpp:50
@ 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
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 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, name_mode mode, filter_mode filter, reorder_mode reorder, file_tree_checksum *checksum)
Get a list of all files and/or directories in a given directory.
Definition: filesystem.cpp:463
std::string base_name(const std::string &file, const bool remove_extension)
Returns the base filename of a file, with directory name stripped.
bool delete_file(const std::string &filename)
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:341
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
bool delete_directory(const std::string &dirname, const bool keep_pbl)
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 is_path_sep(char c)
Returns whether c is a path separator.
std::string normalize_path(const std::string &fpath, bool normalize_separators, bool resolve_dot_entries)
Returns the absolute path of a file.
std::string path
Definition: filesystem.cpp:106
REGISTER_DIALOG(editor_edit_unit)
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:189
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:163
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:85
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:86
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
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:140
Desktop environment interaction functions.
Desktop paths, storage media and bookmark functions.
std::string_view data
Definition: picture.cpp:188
std::string filename
Filename.
static map_location::direction n
#define b