The Battle for Wesnoth  1.19.2+dev
file_dialog.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2011 - 2024
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"
27 #include "gui/dialogs/message.hpp"
29 #include "gui/widgets/button.hpp"
30 #include "gui/widgets/listbox.hpp"
31 #include "gui/widgets/text_box.hpp"
33 #include "gui/widgets/window.hpp"
34 #include "gettext.hpp"
35 #include "log.hpp"
37 
38 #include <functional>
39 
40 static lg::log_domain log_filedlg{"gui/dialogs/file_dialog"};
41 #define ERR_FILEDLG LOG_STREAM(err, log_filedlg)
42 #define WRN_FILEDLG LOG_STREAM(warn, log_filedlg)
43 #define LOG_FILEDLG LOG_STREAM(info, log_filedlg)
44 #define DBG_FILEDLG LOG_STREAM(debug, log_filedlg)
45 
46 namespace fs = filesystem;
47 
48 namespace
49 {
50 const std::string icon_dir = "icons/action/browse_25.png";
51 const std::string icon_parent = "icons/action/undo_25.png";
52 const std::string icon_file = "misc/file.png";
53 // NOTE: Does not need to be the same as PARENT_DIR! Use PARENT_DIR to build
54 // relative paths for non-presentational purposes instead.
55 const std::string label_parent = "..";
56 
57 const std::string CURRENT_DIR = ".";
58 const std::string PARENT_DIR = "..";
59 
60 const int FILE_DIALOG_ITEM_RETVAL = 9001;
61 const int FILE_DIALOG_MAX_ENTRY_LENGTH = 42;
62 
63 inline std::string concat_path(const std::string& a, const std::string& b)
64 {
65  //
66  // As of Boost 1.61, normalize_path() displays unusual behavior when passing
67  // it paths with extra path separators (e.g. //opt becomes
68  // //opt/home/shadowm/src/wesnoth, where the extraneous sequence is probably
69  // the current working dir), so avoid leaving those around.
70  //
71  // TODO: Maybe handle this corner case in filesystem::normalize_path()
72  // instead, really.
73  //
74  if((a.empty() || !fs::is_path_sep(a.back())) && (b.empty() || !fs::is_path_sep(b.front()))) {
75  return a + fs::path_separator() + b;
76  } else {
77  return a + b;
78  }
79 }
80 
81 inline std::string filesystem_root()
82 {
83  // TODO: Multiple drives support (may require cooperation from the caller).
84  return std::string(1, fs::path_separator());
85 }
86 
87 inline void isort_dir_entries(std::vector<std::string>& entries)
88 {
89  // Yes, this uses Wesnoth's locale and not the filesystem/OS locale. Yes, this
90  // isn't ideal. No, we don't really need to worry about it. It's just a
91  // cosmetic procedure anyway.
92  std::sort(entries.begin(), entries.end(),
93  [](const std::string& a, const std::string& b) { return translation::icompare(a, b) < 0; });
94 }
95 
96 } // unnamed namespace
97 
98 namespace gui2::dialogs
99 {
100 
101 REGISTER_DIALOG(file_dialog)
102 
104  : modal_dialog(window_id())
105  , title_(_("Find File"))
106  , msg_()
107  , ok_label_()
108  , extension_()
109  , current_entry_()
110  , current_dir_()
111  , read_only_(false)
112  , save_mode_(false)
113  , dir_files_()
114  , dir_subdirs_()
115  , bookmark_paths_()
116  , current_bookmark_()
117  , user_bookmarks_begin_()
118  , extra_paths_()
119 {
120 }
121 
122 std::string file_dialog::path() const
123 {
124  const std::string& dir_norm = fs::normalize_path(current_dir_, true);
125 
126  if(current_entry_.empty() || current_entry_ == CURRENT_DIR) {
127  return dir_norm;
128  } else if(current_entry_ == PARENT_DIR) {
129  return fs::directory_name(dir_norm);
130  }
131 
132  return concat_path(dir_norm, current_entry_);
133 }
134 
135 file_dialog& file_dialog::set_path(const std::string& value)
136 {
137  if(value.empty()) {
138  current_dir_ = filesystem_root();
139  }
140 
141  const std::string& norm = fs::normalize_path(value, true);
142 
143  if(fs::is_directory(norm)) {
144  current_dir_ = norm;
145  } else {
147  if(current_dir_.empty()) {
148  current_dir_ = filesystem_root();
149  }
150  // The file may or may not exist. We'll find out eventually when setting up
151  // the dialog.
153  }
154 
155  return *this;
156 }
157 
158 file_dialog& file_dialog::set_filename(const std::string& value)
159 {
160  current_entry_ = value;
161  return *this;
162 }
163 
165  if(!save_mode_) {
166  return;
167  }
168 
169  text_box& file_textbox = find_widget<text_box>(get_window(), "filename", false);
170  button& save_btn = find_widget<button>(get_window(), "ok", false);
171 
172  // empty filename
173  std::string filename = file_textbox.get_value();
174  styled_widget& validation_msg = find_widget<styled_widget>(get_window(), "validation_msg", false);
175 
176  bool stat_invalid = filename.empty() || (filename.substr(0,1) == ".");
177  bool wrong_ext = false;
178 
179  if (stat_invalid) {
180  validation_msg.set_label("<span color='#00dcff' size='small'>please enter a filename</span>");
181  save_btn.set_active(false);
182  } else {
183  // wrong extension check
184  for (const auto& extension : extensions_) {
185  if (filename.size() >= extension.size()) {
186  std::string ext = filename.substr(filename.size()-extension.size());
187  if (ext == extension) {
188  wrong_ext = false;
189  break;
190  } else {
191  wrong_ext = true;
192  }
193  } else {
194  // size of allowed extensions and the one typed don't match
195  wrong_ext = true;
196  }
197  }
198 
199  if (wrong_ext) {
200  validation_msg.set_label("<span color='red' face='DejaVuSans'>✘</span><span color='red' size='small'>wrong extension, use " + utils::join(extensions_, ", ") + "</span>");
201  save_btn.set_active(false);
202  } else {
203  validation_msg.set_label("");
204  save_btn.set_active(true);
205  }
206  }
207 }
208 
210 {
211  styled_widget& title = find_widget<styled_widget>(&window, "title", false);
212  styled_widget& message = find_widget<styled_widget>(&window, "message", false);
213  styled_widget& ok = find_widget<styled_widget>(&window, "ok", false);
214 
215  title.set_label(title_);
216 
217  if(msg_.empty()) {
219  } else {
221  message.set_use_markup(true);
222  }
223 
224  if(ok_label_.empty()) {
225  ok.set_label(save_mode_ ? _("Save") : _("Open"));
226  } else {
227  ok.set_label(ok_label_);
228  }
229 
230  listbox& bookmarks_bar = find_widget<listbox>(&window, "bookmarks", false);
231 
232  find_widget<styled_widget>(&window, "current_dir", false).set_text_ellipse_mode(PANGO_ELLIPSIZE_START);
233 
234  //
235  // Push hard-coded bookmarks.
236  //
237 
240  std::vector<desktop::path_info> bookmarks = desktop::game_paths(extra_paths_);
242  bookmarks.insert(bookmarks.end(), sys_paths.begin(), sys_paths.end());
243 
244  bookmark_paths_.clear();
246 
248 
249  for(const auto& pinfo : bookmarks) {
250  bookmark_paths_.push_back(pinfo.path);
251  data["bookmark"]["label"] = pinfo.display_name();
252  bookmarks_bar.add_row(data);
253  }
254 
255  //
256  // Push user-defined bookmarks.
257  //
258 
259  const std::vector<desktop::bookmark_info>& user_bookmarks = desktop::user_bookmarks();
260 
261  if(!user_bookmarks.empty()) {
263  }
264 
265  for(const auto& bookmark : user_bookmarks) {
266  bookmark_paths_.push_back(bookmark.path);
267  data["bookmark"]["label"] = bookmark.label;
268  bookmarks_bar.add_row(data);
269  }
270 
272 
273  listbox& filelist = find_widget<listbox>(&window, "filelist", false);
274  text_box& file_textbox = find_widget<text_box>(get_window(), "filename", false);
275 
277  std::bind(&file_dialog::on_row_selected, this));
278  connect_signal_notify_modified(bookmarks_bar,
279  std::bind(&file_dialog::on_bookmark_selected, this));
280  connect_signal_notify_modified(file_textbox,
281  std::bind(&file_dialog::check_filename, this));
282 
283  check_filename();
284 
285  button& mkdir_button = find_widget<button>(&window, "new_dir", false);
286  button& rm_button = find_widget<button>(&window, "delete_file", false);
287  button& bookmark_add_button = find_widget<button>(&window, "add_bookmark", false);
288  button& bookmark_del_button = find_widget<button>(&window, "remove_bookmark", false);
289  button& open_ext_button = find_widget<button>(&window, "open_ext", false);
290 
291  connect_signal_mouse_left_click(mkdir_button,
292  std::bind(&file_dialog::on_dir_create_cmd, this));
294  std::bind(&file_dialog::on_file_delete_cmd, this));
295  connect_signal_mouse_left_click(bookmark_add_button,
296  std::bind(&file_dialog::on_bookmark_add_cmd, this));
297  connect_signal_mouse_left_click(bookmark_del_button,
298  std::bind(&file_dialog::on_bookmark_del_cmd, this));
299 
301  connect_signal_mouse_left_click(open_ext_button,
302  std::bind([this](){ desktop::open_object(path()); }));
303  } else {
304  open_ext_button.set_active(false);
305  open_ext_button.set_tooltip(_("Opening files is not supported, contact your packager"));
306  }
307 
308  if(read_only_) {
309  mkdir_button.set_active(false);
310  rm_button.set_active(false);
311 
314  }
315 
317 
318  //window.keyboard_capture(find_widget<text_box>(&window, "filename", false, true));
319  window.keyboard_capture(&file_textbox);
320  window.add_to_keyboard_chain(&filelist);
321  window.set_exit_hook(window::exit_hook::on_all, std::bind(&file_dialog::on_exit, this, std::placeholders::_1));
322 }
323 
325 {
326  if(window.get_retval() == FILE_DIALOG_ITEM_RETVAL) {
327  // Attempting to exit by double clicking items -- only proceeds if the item
328  // was a file.
330  window.set_retval(retval::OK, false);
331  return true;
332  } else {
333  return false;
334  }
335  }
336 
337  if(window.get_retval() == retval::OK) {
338  // Attempting to exit by pressing Enter/clicking OK -- only proceeds if the
339  // textbox was not altered by the user to point to a different directory.
340  return process_textbox_submit();
341  }
342 
343  return true;
344 }
345 
347 {
348  // TODO: Adapt for implementing directory selection mode.
349  return save_mode_
350  ? stype != SELECTION_IS_DIR && stype != SELECTION_PARENT_NOT_FOUND
351  : stype == SELECTION_IS_FILE;
352 }
353 
355 {
356  // TODO: Adapt for implementing directory selection mode.
357  if(stype != SELECTION_IS_FILE) {
358  return true;
359  }
360 
361  const std::string& message
362  = _("The file already exists. Do you wish to overwrite it?");
364 }
365 
366 bool file_dialog::process_submit_common(const std::string& name)
367 {
368  const auto stype = register_new_selection(name);
369 
370  //DBG_FILEDLG << "current_dir_=" << current_dir_ << " current_entry_=" << current_entry_;
371 
372  if(is_selection_type_acceptable(stype)) {
373  return save_mode_ ? confirm_overwrite(stype) : true;
374  }
375 
376  switch(stype) {
377  case SELECTION_IS_DIR:
378  // TODO: Adapt for implementing directory selection mode.
381  break;
383  // We get here in save mode or not. Use the file creation language only in
384  // save mode.
385  if(save_mode_) {
386  show_transient_error_message(VGETTEXT("The file or folder $path cannot be created.", {{"path", name}}));
387  break;
388  }
389  [[fallthrough]];
390  case SELECTION_NOT_FOUND:
391  // We only get here if we aren't in save mode.
392  show_transient_error_message(VGETTEXT("The file or folder $path does not exist.", {{"path", name}}));
393  break;
394  case SELECTION_IS_FILE:
395  // TODO: Adapt for implementing directory selection mode.
396  default:
397  assert(false && "Unimplemented selection mode or semantics");
398  }
399 
400  return false;
401 }
402 
404 {
405  listbox& filelist = find_widget<listbox>(get_window(), "filelist", false);
406  const std::string& selected_name = get_filelist_selection(filelist);
407  return process_submit_common(selected_name);
408 }
409 
411 {
412  text_box& file_textbox = find_widget<text_box>(get_window(), "filename", false);
413  const std::string& input_name = file_textbox.get_value();
414  return !input_name.empty() && process_submit_common(input_name);
415 }
416 
418 {
419  const int row = filelist.get_selected_row();
420 
421  if(row == -1) {
422  // Shouldn't happen...
423  return "";
424  }
425 
426  const bool i_am_root = fs::is_root(current_dir_);
427 
428  if(row == 0 && !i_am_root) {
429  return PARENT_DIR;
430  } else {
431  std::size_t n = i_am_root ? row : row - 1;
432 
433  if(n < dir_subdirs_.size()) {
434  return dir_subdirs_[n];
435  } else {
436  n -= dir_subdirs_.size();
437 
438  if(n < dir_files_.size()) {
439  return dir_files_[n];
440  } else {
441  assert(false && "File list selection is out of range!");
442  }
443  }
444  }
445 
446  return "";
447 }
448 
450 {
451  std::string new_path, new_parent;
452 
453  if(fs::is_relative(name)) {
454  // On Windows, \ represents a path relative to the root of the process'
455  // current working drive specified by the current working dir, so we get
456  // here. This makes it the only platform where is_relative() and is_root()
457  // aren't mutually exclusive.
458  if(fs::is_root(name)) {
459  DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' is relative to a root resource";
460  // Using the browsed dir's root drive instead of the cwd's makes the most
461  // sense for users.
462  new_parent = fs::root_name(current_dir_);
463  new_path = fs::normalize_path(concat_path(new_parent, name), true, true);
464  } else {
465  DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' seems relative";
466  new_parent = current_dir_;
467  new_path = fs::normalize_path(concat_path(current_dir_, name), true, true);
468  }
469  } else {
470  DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' seems absolute";
471  new_parent = fs::directory_name(name);
472  new_path = fs::normalize_path(name, true, true);
473  DBG_FILEDLG << "register_new_selection(): new selection is " << new_path;
474  }
475 
476  if(!new_path.empty()) {
477  if(fs::is_directory(new_path)) {
478  DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' is a directory: " << new_path;
479  current_dir_ = new_path;
480  current_entry_.clear();
481  return SELECTION_IS_DIR;
482  } else if(fs::file_exists(new_path)) {
483  // FIXME: Perhaps redundant since the three-params call to normalize_path()
484  // above necessarily validates existence.
485  DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' is a file, symbolic link, or special: " << new_path;
486  current_dir_ = fs::directory_name(new_path);
487  current_entry_ = fs::base_name(new_path);
488  return SELECTION_IS_FILE;
489  }
490  }
491 
492  // The path does not exist, at least not entirely. See if the parent does
493  // (in save mode non-existent files are accepted as long as the parent dir
494  // exists).
495  const std::string& absolute_parent = fs::normalize_path(new_parent, true, true);
496  if(!absolute_parent.empty()) {
497  DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' does not exist or is not accessible, but parent exists";
498  current_dir_ = absolute_parent;
500  return SELECTION_NOT_FOUND;
501  }
502 
503  DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' does not exist or is not accessible";
505 }
506 
507 void file_dialog::set_input_text(text_box& t, const std::string& value)
508 {
509  if(value.empty()) {
511  return;
512  }
513 
514  t.set_value(value);
515 
516  const std::size_t vallen = t.get_length();
517  const std::size_t extlen = utf8::size(extension_);
518 
519  if(save_mode_ && extlen && vallen > extlen) {
520  // Highlight everything but the extension if it matches
521  if(value.substr(vallen - extlen) == extension_) {
522  t.set_selection(0, vallen - extlen);
523  }
524  }
525 }
526 
528 {
529  if(save_mode_ && !extension_.empty()) {
530  t.set_value(extension_);
531  t.set_selection(0, 0);
532  } else {
533  t.clear();
534  }
535 }
536 
538 {
540 
541  dir_files_.clear();
542  dir_subdirs_.clear();
543 
544  // TODO: Need to detect and handle cases where we don't have search permission
545  // on current_dir_, otherwise things may get weird.
547  isort_dir_entries(dir_files_);
548  isort_dir_entries(dir_subdirs_);
549 
550  //
551  // Clear and refill the filelist box.
552  //
553 
554  listbox& filelist = find_widget<listbox>(get_window(), "filelist", false);
555  button& rm_button = find_widget<button>(get_window(), "delete_file", false);
556 
557  filelist.clear();
558 
559  // Parent entry
560  if(!fs::is_root(current_dir_)) {
561  // label_parent may not necessarily be always ".." in the future, so push
562  // with check_selection = false and check the selection ourselves here.
563  push_fileview_row(filelist, label_parent, icon_parent, false);
564  if(current_entry_ == PARENT_DIR || current_entry_.empty()) {
565  filelist.select_row(0, true);
566  rm_button.set_active(false);
567  } else {
568  rm_button.set_active(true);
569  }
570  }
571 
572  for(const auto& dir : dir_subdirs_) {
573  push_fileview_row(filelist, dir, icon_dir);
574  }
575 
576  for(const auto& file : dir_files_) {
577  push_fileview_row(filelist, file, icon_file);
578  }
579 
580  find_widget<styled_widget>(get_window(), "current_dir", false).set_label(current_dir_);
581  set_input_text(find_widget<text_box>(get_window(), "filename", false), current_entry_);
582 
583  on_row_selected();
584 }
585 
586 void file_dialog::push_fileview_row(listbox& filelist, const std::string& name, const std::string& icon, bool check_selection)
587 {
588  // TODO: Hopefully some day GUI2 will allow us to make labels be ellipsized
589  // dynamically at layout/rendering time.
590  std::string label = name;
591  utils::ellipsis_truncate(label, FILE_DIALOG_MAX_ENTRY_LENGTH);
592 
594  data["icon"]["label"] = icon;
595  data["file"]["label"] = label;
596 
597  grid& last_grid = filelist.add_row(data);
598 
599  //
600  // Crummy hack around the lack of an option to hook into row double click
601  // events for all rows using the GUI2 listbox API. Assign a special retval to
602  // each row that triggers a special check during dialog exit.
603  //
604  find_widget<toggle_panel>(&last_grid, "item_panel", false)
605  .set_retval(FILE_DIALOG_ITEM_RETVAL);
606 
607  if(check_selection && name == current_entry_) {
608  filelist.select_last_row(true);
609  }
610 }
611 
613 {
614  listbox& bookmarks_bar = find_widget<listbox>(get_window(), "bookmarks", false);
615 
616  // Internal state has normalized path delimiters but dot entries aren't
617  // resolved after callers call set_path(), so compare against a canonical
618  // version. The bookmark paths are already canonical, though.
619  const std::string& canon_current_dir = fs::normalize_path(current_dir_, true, true);
620 
621  // Go backwards so we can match user-defined bookmarks first (otherwise it may
622  // become impossible for the user to delete them if they match any of the
623  // predefined paths).
624  auto it = std::find(bookmark_paths_.rbegin(), bookmark_paths_.rend(), canon_current_dir);
625 
626  if(it == bookmark_paths_.rend()) {
627  if(current_bookmark_ >= 0) {
628  bookmarks_bar.select_row(static_cast<unsigned>(current_bookmark_), false);
629  }
630  current_bookmark_ = -1;
631  } else {
632  const int new_selection = static_cast<int>(std::distance(bookmark_paths_.begin(), it.base()) - 1);
633  if(new_selection != current_bookmark_) {
634  assert(static_cast<unsigned>(new_selection) < bookmarks_bar.get_item_count());
635  if(current_bookmark_ >= 0) {
636  bookmarks_bar.select_row(static_cast<unsigned>(current_bookmark_), false);
637  }
638  bookmarks_bar.select_row(static_cast<unsigned>(new_selection), true);
639  current_bookmark_ = new_selection;
640  }
641  }
642 
643  // Update bookmark edit controls.
644  button& del_button = find_widget<button>(get_window(), "remove_bookmark", false);
645 
646  if(user_bookmarks_begin_ == -1) {
647  del_button.set_active(false);
648  } else {
650  }
651 }
652 
654 {
655  listbox& filelist = find_widget<listbox>(get_window(), "filelist", false);
656  text_box& file_textbox = find_widget<text_box>(get_window(), "filename", false);
657  button& rm_button = find_widget<button>(get_window(), "delete_file", false);
658 
659  // Don't use register_new_selection() here, we don't want any parsing to be
660  // performed at this point.
662 
663  // Clear the textbox when selecting ..
664  if(current_entry_ != PARENT_DIR) {
665  set_input_text(file_textbox, current_entry_);
666  rm_button.set_active(true);
667  } else {
668  clear_input_text(file_textbox);
669  rm_button.set_active(false);
670  }
671 
672  // Need to do this every time so that input can still be sent to the
673  // textbox without clicking on it.
674  get_window()->keyboard_capture(&file_textbox);
675 }
676 
678 {
679  // Don't let us steal the focus from the primary widgets.
680  text_box& file_textbox = find_widget<text_box>(get_window(), "filename", false);
681  get_window()->keyboard_capture(&file_textbox);
682 
683  listbox& bookmarks_bar = find_widget<listbox>(get_window(), "bookmarks", false);
684  const int new_selection = bookmarks_bar.get_selected_row();
685 
686  if(new_selection < 0) {
687  if(current_bookmark_ >= 0) {
688  // Don't allow the user to deselect the selected bookmark. That wouldn't
689  // make any sense.
690  bookmarks_bar.select_row(static_cast<unsigned>(current_bookmark_));
691  }
692 
693  return;
694  }
695 
696  assert(static_cast<unsigned>(new_selection) < bookmark_paths_.size());
697  current_bookmark_ = new_selection;
698  set_path(bookmark_paths_[new_selection]);
700 
701  // Update bookmark edit controls.
702  button& del_button = find_widget<button>(get_window(), "remove_bookmark", false);
703  del_button.set_active(user_bookmarks_begin_ >= 0
705 }
706 
708 {
709  const std::string& default_label = fs::base_name(current_dir_);
710 
711  std::string label = default_label;
712 
713  const bool confirm = bookmark_create::execute(label);
714  if(!confirm) {
715  return;
716  }
717 
718  if(label.empty()) {
719  label = default_label;
720  }
721 
722  listbox& bookmarks_bar = find_widget<listbox>(get_window(), "bookmarks", false);
723 
725  bookmark_paths_.push_back(current_dir_);
726  const unsigned top_bookmark = bookmark_paths_.size() - 1;
727 
728  if(user_bookmarks_begin_ == -1) {
729  user_bookmarks_begin_ = top_bookmark;
730  }
731 
733  data["bookmark"]["label"] = label;
734  bookmarks_bar.add_row(data);
735 
736  current_bookmark_ = -1;
737 
739 }
740 
742 {
743  assert(user_bookmarks_begin_ >= 0
744  && current_bookmark_ >= 0
746  && current_bookmark_ < static_cast<int>(bookmark_paths_.size()));
747 
748  listbox& bookmarks_bar = find_widget<listbox>(get_window(), "bookmarks", false);
751  bookmarks_bar.remove_row(current_bookmark_);
752 
753  current_bookmark_ = -1;
754 
756 }
757 
759 {
760  std::string new_dir_name;
761 
762  if(folder_create::execute(new_dir_name)) {
763  const std::string& new_path = concat_path(current_dir_, new_dir_name);
764 
765  if(!fs::make_directory(new_path)) {
767  VGETTEXT("Could not create a new folder at $path|. Make sure you have the appropriate permissions to write to this location.",
768  {{"path", new_path}}));
769  } else {
771  }
772  }
773 }
774 
776 {
777  if(current_entry_.empty()) {
778  return;
779  }
780 
781  const std::string& selection = concat_path(current_dir_, current_entry_);
782  const bool is_dir = fs::is_directory(selection);
783 
784  const std::string& message = (is_dir
785  ? _("The following folder and its contents will be permanently deleted:")
786  : _("The following file will be permanently deleted:"))
787  + "\n\n" + selection + "\n\n" + _("Do you wish to continue?");
788 
790  return;
791  }
792 
793  const bool result = is_dir
794  ? fs::delete_directory(selection)
795  : fs::delete_file(selection);
796 
797  if(!result) {
799  VGETTEXT("Could not delete $path|. Make sure you have the appropriate permissions to write to this location.",
800  {{"path", selection}}));
801  } else {
803  }
804 }
805 
806 } // 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.
std::vector< std::string > bookmark_paths_
void check_filename()
Check if the filename is valid and disable save button if invalid.
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.
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)
virtual void pre_show(window &window) override
Actions to be taken before showing the window.
bool on_exit(window &window)
Handles dialog exit events and decides whether to proceed or not.
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.
window * get_window()
Returns a pointer to the dialog's window.
Base container class.
Definition: grid.hpp:32
The listbox class.
Definition: listbox.hpp:43
bool select_last_row(const bool select=true)
Does exactly as advertised: selects the list's last row.
Definition: listbox.hpp:189
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:59
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:243
void remove_row(const unsigned row, unsigned count=1)
Removes a row in the listbox.
Definition: listbox.cpp:79
void clear()
Removes all the rows in the listbox, clearing it.
Definition: listbox.cpp:118
int get_selected_row() const
Returns the first selected row.
Definition: listbox.cpp:268
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:124
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:469
@ invisible
The user set the widget invisible, that means:
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:61
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:397
void keyboard_capture(widget *widget)
Definition: window.cpp:1221
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1227
void set_exit_hook(exit_hook mode, std::function< bool(window &)> func)
Sets the window's exit hook.
Definition: window.hpp:451
int get_retval()
Definition: window.hpp:404
@ on_all
Always run hook.
#define DBG_FILEDLG
Definition: file_dialog.cpp:44
static lg::log_domain log_filedlg
Definition: file_dialog.cpp:40
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:93
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:207
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:274
unsigned add_user_bookmark(const std::string &label, const std::string &path)
Definition: paths.cpp:249
bool open_object([[maybe_unused]] const std::string &path_or_url)
Definition: open.cpp:46
@ 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:262
std::vector< path_info > system_paths(std::set< SYSTEM_PATH_TYPES > paths)
Returns a list of system-defined paths.
Definition: paths.cpp:226
constexpr bool open_object_is_supported()
Returns whether open_object() is supported/implemented for the current platform.
Definition: open.hpp:54
@ 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(std::set< GAME_PATH_TYPES > paths)
Returns a list of game-related paths.
Definition: paths.cpp:198
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:410
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:324
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)
Definition: filesystem.cpp:999
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)
Definition: filesystem.cpp:988
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.
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:203
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:177
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:34
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:150
@ 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::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
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 join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
Desktop environment interaction functions.
Desktop paths, storage media and bookmark functions.
std::string_view data
Definition: picture.cpp:194
static map_location::DIRECTION n
#define a
#define b