The Battle for Wesnoth  1.19.7+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"
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"
36 
37 #include <algorithm>
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>("filename");
170  button& save_btn = find_widget<button>("ok");
171 
172  // empty filename
173  const std::string& filename = file_textbox.get_value();
174  styled_widget& validation_msg = find_widget<styled_widget>("validation_msg");
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  utils::string_map i18n_strings;
201  i18n_strings["extensions"] = utils::join(extensions_, ", ");
202  validation_msg.set_label(VGETTEXT("<span color='red'><span face='DejaVuSans'>✘</span> <span size='small'>wrong extension, use $extensions</span></span>", i18n_strings));
203  save_btn.set_active(false);
204  } else if (std::find_if(filename.begin(), filename.end(), isspace) != filename.end()) {
205  validation_msg.set_label(_("<span color='red'><span face='DejaVuSans'>✘</span> <span size='small'>whitespace is not allowed in filename</span></span>"));
206  save_btn.set_active(false);
207  } else {
208  validation_msg.set_label("");
209  save_btn.set_active(true);
210  }
211  }
212 }
213 
215 {
216  styled_widget& title = find_widget<styled_widget>("title");
217  styled_widget& message = find_widget<styled_widget>("message");
218  styled_widget& ok = find_widget<styled_widget>("ok");
219 
220  title.set_label(title_);
221 
222  if(msg_.empty()) {
224  } else {
226  message.set_use_markup(true);
227  }
228 
229  if(ok_label_.empty()) {
230  ok.set_label(save_mode_ ? _("Save") : _("Open"));
231  } else {
232  ok.set_label(ok_label_);
233  }
234 
235  listbox& bookmarks_bar = find_widget<listbox>("bookmarks");
236 
237  find_widget<styled_widget>("current_dir").set_text_ellipse_mode(PANGO_ELLIPSIZE_START);
238 
239  //
240  // Push hard-coded bookmarks.
241  //
242 
245  std::vector<desktop::path_info> bookmarks = desktop::game_paths(extra_paths_);
247  bookmarks.insert(bookmarks.end(), sys_paths.begin(), sys_paths.end());
248 
249  bookmark_paths_.clear();
251 
253 
254  for(const auto& pinfo : bookmarks) {
255  bookmark_paths_.push_back(pinfo.path);
256  data["bookmark"]["label"] = pinfo.display_name();
257  bookmarks_bar.add_row(data);
258  }
259 
260  //
261  // Push user-defined bookmarks.
262  //
263 
264  const std::vector<desktop::bookmark_info>& user_bookmarks = desktop::user_bookmarks();
265 
266  if(!user_bookmarks.empty()) {
268  }
269 
270  for(const auto& bookmark : user_bookmarks) {
271  bookmark_paths_.push_back(bookmark.path);
272  data["bookmark"]["label"] = bookmark.label;
273  bookmarks_bar.add_row(data);
274  }
275 
277 
278  listbox& filelist = find_widget<listbox>("filelist");
279  text_box& file_textbox = find_widget<text_box>("filename");
280 
282  std::bind(&file_dialog::on_row_selected, this));
283  connect_signal_notify_modified(bookmarks_bar,
284  std::bind(&file_dialog::on_bookmark_selected, this));
285  connect_signal_notify_modified(file_textbox,
286  std::bind(&file_dialog::check_filename, this));
287 
288  check_filename();
289 
290  button& mkdir_button = find_widget<button>("new_dir");
291  button& rm_button = find_widget<button>("delete_file");
292  button& bookmark_add_button = find_widget<button>("add_bookmark");
293  button& bookmark_del_button = find_widget<button>("remove_bookmark");
294  button& open_ext_button = find_widget<button>("open_ext");
295 
296  connect_signal_mouse_left_click(mkdir_button,
297  std::bind(&file_dialog::on_dir_create_cmd, this));
299  std::bind(&file_dialog::on_file_delete_cmd, this));
300  connect_signal_mouse_left_click(bookmark_add_button,
301  std::bind(&file_dialog::on_bookmark_add_cmd, this));
302  connect_signal_mouse_left_click(bookmark_del_button,
303  std::bind(&file_dialog::on_bookmark_del_cmd, this));
304 
306  connect_signal_mouse_left_click(open_ext_button,
307  std::bind([this](){ desktop::open_object(path()); }));
308  } else {
309  open_ext_button.set_active(false);
310  open_ext_button.set_tooltip(_("Opening files is not supported, contact your packager"));
311  }
312 
313  if(read_only_) {
314  mkdir_button.set_active(false);
315  rm_button.set_active(false);
316 
319  }
320 
322 
323  //window.keyboard_capture(find_widget<text_box>("filename", false, true));
324  keyboard_capture(&file_textbox);
325  add_to_keyboard_chain(&filelist);
326  set_exit_hook(window::exit_hook::on_all, std::bind(&file_dialog::on_exit, this, std::placeholders::_1));
327 }
328 
330 {
331  if(window.get_retval() == FILE_DIALOG_ITEM_RETVAL) {
332  // Attempting to exit by double clicking items -- only proceeds if the item
333  // was a file.
335  window.set_retval(retval::OK, false);
336  return true;
337  } else {
338  return false;
339  }
340  }
341 
342  if(window.get_retval() == retval::OK) {
343  // Attempting to exit by pressing Enter/clicking OK -- only proceeds if the
344  // textbox was not altered by the user to point to a different directory.
345  return process_textbox_submit();
346  }
347 
348  return true;
349 }
350 
352 {
353  // TODO: Adapt for implementing directory selection mode.
354  return save_mode_
355  ? stype != SELECTION_IS_DIR && stype != SELECTION_PARENT_NOT_FOUND
356  : stype == SELECTION_IS_FILE;
357 }
358 
360 {
361  // TODO: Adapt for implementing directory selection mode.
362  if(stype != SELECTION_IS_FILE) {
363  return true;
364  }
365 
366  const std::string& message
367  = _("The file already exists. Do you wish to overwrite it?");
369 }
370 
371 bool file_dialog::process_submit_common(const std::string& name)
372 {
373  const auto stype = register_new_selection(name);
374 
375  //DBG_FILEDLG << "current_dir_=" << current_dir_ << " current_entry_=" << current_entry_;
376 
377  if(is_selection_type_acceptable(stype)) {
378  return save_mode_ ? confirm_overwrite(stype) : true;
379  }
380 
381  switch(stype) {
382  case SELECTION_IS_DIR:
383  // TODO: Adapt for implementing directory selection mode.
386  break;
388  // We get here in save mode or not. Use the file creation language only in
389  // save mode.
390  if(save_mode_) {
391  show_transient_error_message(VGETTEXT("The file or folder $path cannot be created.", {{"path", name}}));
392  break;
393  }
394  [[fallthrough]];
395  case SELECTION_NOT_FOUND:
396  // We only get here if we aren't in save mode.
397  show_transient_error_message(VGETTEXT("The file or folder $path does not exist.", {{"path", name}}));
398  break;
399  case SELECTION_IS_FILE:
400  // TODO: Adapt for implementing directory selection mode.
401  default:
402  assert(false && "Unimplemented selection mode or semantics");
403  }
404 
405  return false;
406 }
407 
409 {
410  listbox& filelist = find_widget<listbox>("filelist");
411  const std::string& selected_name = get_filelist_selection(filelist);
412  return process_submit_common(selected_name);
413 }
414 
416 {
417  text_box& file_textbox = find_widget<text_box>("filename");
418  const std::string& input_name = file_textbox.get_value();
419  return !input_name.empty() && process_submit_common(input_name);
420 }
421 
423 {
424  const int row = filelist.get_selected_row();
425 
426  if(row == -1) {
427  // Shouldn't happen...
428  return "";
429  }
430 
431  const bool i_am_root = fs::is_root(current_dir_);
432 
433  if(row == 0 && !i_am_root) {
434  return PARENT_DIR;
435  } else {
436  std::size_t n = i_am_root ? row : row - 1;
437 
438  if(n < dir_subdirs_.size()) {
439  return dir_subdirs_[n];
440  } else {
441  n -= dir_subdirs_.size();
442 
443  if(n < dir_files_.size()) {
444  return dir_files_[n];
445  } else {
446  assert(false && "File list selection is out of range!");
447  }
448  }
449  }
450 
451  return "";
452 }
453 
455 {
456  std::string new_path, new_parent;
457 
458  if(fs::is_relative(name)) {
459  // On Windows, \ represents a path relative to the root of the process'
460  // current working drive specified by the current working dir, so we get
461  // here. This makes it the only platform where is_relative() and is_root()
462  // aren't mutually exclusive.
463  if(fs::is_root(name)) {
464  DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' is relative to a root resource";
465  // Using the browsed dir's root drive instead of the cwd's makes the most
466  // sense for users.
467  new_parent = fs::root_name(current_dir_);
468  new_path = fs::normalize_path(concat_path(new_parent, name), true, true);
469  } else {
470  DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' seems relative";
471  new_parent = current_dir_;
472  new_path = fs::normalize_path(concat_path(current_dir_, name), true, true);
473  }
474  } else {
475  DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' seems absolute";
476  new_parent = fs::directory_name(name);
477  new_path = fs::normalize_path(name, true, true);
478  DBG_FILEDLG << "register_new_selection(): new selection is " << new_path;
479  }
480 
481  if(!new_path.empty()) {
482  if(fs::is_directory(new_path)) {
483  DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' is a directory: " << new_path;
484  current_dir_ = new_path;
485  current_entry_.clear();
486  return SELECTION_IS_DIR;
487  } else if(fs::file_exists(new_path)) {
488  // FIXME: Perhaps redundant since the three-params call to normalize_path()
489  // above necessarily validates existence.
490  DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' is a file, symbolic link, or special: " << new_path;
491  current_dir_ = fs::directory_name(new_path);
492  current_entry_ = fs::base_name(new_path);
493  return SELECTION_IS_FILE;
494  }
495  }
496 
497  // The path does not exist, at least not entirely. See if the parent does
498  // (in save mode non-existent files are accepted as long as the parent dir
499  // exists).
500  const std::string& absolute_parent = fs::normalize_path(new_parent, true, true);
501  if(!absolute_parent.empty()) {
502  DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' does not exist or is not accessible, but parent exists";
503  current_dir_ = absolute_parent;
505  return SELECTION_NOT_FOUND;
506  }
507 
508  DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' does not exist or is not accessible";
510 }
511 
512 void file_dialog::set_input_text(text_box& t, const std::string& value)
513 {
514  if(value.empty()) {
516  return;
517  }
518 
519  t.set_value(value);
520  check_filename();
521 
522  const std::size_t vallen = t.get_length();
523  const std::size_t extlen = utf8::size(extension_);
524 
525  if(save_mode_ && extlen && vallen > extlen) {
526  // Highlight everything but the extension if it matches
527  if(value.substr(vallen - extlen) == extension_) {
528  t.set_selection(0, vallen - extlen);
529  }
530  }
531 }
532 
534 {
535  if(save_mode_ && !extension_.empty()) {
536  t.set_value(extension_);
537  t.set_selection(0, 0);
538  } else {
539  t.clear();
540  }
541 }
542 
544 {
546 
547  dir_files_.clear();
548  dir_subdirs_.clear();
549 
550  // TODO: Need to detect and handle cases where we don't have search permission
551  // on current_dir_, otherwise things may get weird.
553  isort_dir_entries(dir_files_);
554  isort_dir_entries(dir_subdirs_);
555 
556  //
557  // Clear and refill the filelist box.
558  //
559 
560  listbox& filelist = find_widget<listbox>("filelist");
561  button& rm_button = find_widget<button>("delete_file");
562 
563  filelist.clear();
564 
565  // Parent entry
566  if(!fs::is_root(current_dir_)) {
567  // label_parent may not necessarily be always ".." in the future, so push
568  // with check_selection = false and check the selection ourselves here.
569  push_fileview_row(filelist, label_parent, icon_parent, false);
570  if(current_entry_ == PARENT_DIR || current_entry_.empty()) {
571  filelist.select_row(0, true);
572  rm_button.set_active(false);
573  } else {
574  rm_button.set_active(true);
575  }
576  }
577 
578  for(const auto& dir : dir_subdirs_) {
579  push_fileview_row(filelist, dir, icon_dir);
580  }
581 
582  for(const auto& file : dir_files_) {
583  push_fileview_row(filelist, file, icon_file);
584  }
585 
586  find_widget<styled_widget>("current_dir").set_label(current_dir_);
587  set_input_text(find_widget<text_box>("filename"), current_entry_);
588 
589  on_row_selected();
590 }
591 
592 void file_dialog::push_fileview_row(listbox& filelist, const std::string& name, const std::string& icon, bool check_selection)
593 {
594  // TODO: Hopefully some day GUI2 will allow us to make labels be ellipsized
595  // dynamically at layout/rendering time.
596  std::string label = name;
597  utils::ellipsis_truncate(label, FILE_DIALOG_MAX_ENTRY_LENGTH);
598 
600  data["icon"]["label"] = icon;
601  data["file"]["label"] = label;
602 
603  grid& last_grid = filelist.add_row(data);
604 
605  //
606  // Crummy hack around the lack of an option to hook into row double click
607  // events for all rows using the GUI2 listbox API. Assign a special retval to
608  // each row that triggers a special check during dialog exit.
609  //
610  last_grid
611  .find_widget<toggle_panel>("item_panel")
612  .set_retval(FILE_DIALOG_ITEM_RETVAL);
613 
614  if(check_selection && name == current_entry_) {
615  filelist.select_last_row(true);
616  }
617 }
618 
620 {
621  listbox& bookmarks_bar = find_widget<listbox>("bookmarks");
622 
623  // Internal state has normalized path delimiters but dot entries aren't
624  // resolved after callers call set_path(), so compare against a canonical
625  // version. The bookmark paths are already canonical, though.
626  const std::string& canon_current_dir = fs::normalize_path(current_dir_, true, true);
627 
628  // Go backwards so we can match user-defined bookmarks first (otherwise it may
629  // become impossible for the user to delete them if they match any of the
630  // predefined paths).
631  auto it = std::find(bookmark_paths_.rbegin(), bookmark_paths_.rend(), canon_current_dir);
632 
633  if(it == bookmark_paths_.rend()) {
634  if(current_bookmark_ >= 0) {
635  bookmarks_bar.select_row(static_cast<unsigned>(current_bookmark_), false);
636  }
637  current_bookmark_ = -1;
638  } else {
639  const int new_selection = static_cast<int>(std::distance(bookmark_paths_.begin(), it.base()) - 1);
640  if(new_selection != current_bookmark_) {
641  assert(static_cast<unsigned>(new_selection) < bookmarks_bar.get_item_count());
642  if(current_bookmark_ >= 0) {
643  bookmarks_bar.select_row(static_cast<unsigned>(current_bookmark_), false);
644  }
645  bookmarks_bar.select_row(static_cast<unsigned>(new_selection), true);
646  current_bookmark_ = new_selection;
647  }
648  }
649 
650  // Update bookmark edit controls.
651  button& del_button = find_widget<button>("remove_bookmark");
652 
653  if(user_bookmarks_begin_ == -1) {
654  del_button.set_active(false);
655  } else {
657  }
658 }
659 
661 {
662  listbox& filelist = find_widget<listbox>("filelist");
663  text_box& file_textbox = find_widget<text_box>("filename");
664  button& rm_button = find_widget<button>("delete_file");
665 
666  // Don't use register_new_selection() here, we don't want any parsing to be
667  // performed at this point.
669 
670  // Clear the textbox when selecting ..
671  if(current_entry_ != PARENT_DIR) {
672  set_input_text(file_textbox, current_entry_);
673  rm_button.set_active(true);
674  } else {
675  clear_input_text(file_textbox);
676  rm_button.set_active(false);
677  }
678 
679  // Need to do this every time so that input can still be sent to the
680  // textbox without clicking on it.
681  get_window()->keyboard_capture(&file_textbox);
682 }
683 
685 {
686  // Don't let us steal the focus from the primary widgets.
687  text_box& file_textbox = find_widget<text_box>("filename");
688  get_window()->keyboard_capture(&file_textbox);
689 
690  listbox& bookmarks_bar = find_widget<listbox>("bookmarks");
691  const int new_selection = bookmarks_bar.get_selected_row();
692 
693  if(new_selection < 0) {
694  if(current_bookmark_ >= 0) {
695  // Don't allow the user to deselect the selected bookmark. That wouldn't
696  // make any sense.
697  bookmarks_bar.select_row(static_cast<unsigned>(current_bookmark_));
698  }
699 
700  return;
701  }
702 
703  assert(static_cast<unsigned>(new_selection) < bookmark_paths_.size());
704  current_bookmark_ = new_selection;
705  set_path(bookmark_paths_[new_selection]);
707 
708  // Update bookmark edit controls.
709  button& del_button = find_widget<button>("remove_bookmark");
710  del_button.set_active(user_bookmarks_begin_ >= 0
712 }
713 
715 {
716  const std::string& default_label = fs::base_name(current_dir_);
717 
718  std::string label = default_label;
719 
720  const bool confirm = bookmark_create::execute(label);
721  if(!confirm) {
722  return;
723  }
724 
725  if(label.empty()) {
726  label = default_label;
727  }
728 
729  listbox& bookmarks_bar = find_widget<listbox>("bookmarks");
730 
732  bookmark_paths_.push_back(current_dir_);
733  const unsigned top_bookmark = bookmark_paths_.size() - 1;
734 
735  if(user_bookmarks_begin_ == -1) {
736  user_bookmarks_begin_ = top_bookmark;
737  }
738 
740  data["bookmark"]["label"] = label;
741  bookmarks_bar.add_row(data);
742 
743  current_bookmark_ = -1;
744 
746 }
747 
749 {
750  assert(user_bookmarks_begin_ >= 0
751  && current_bookmark_ >= 0
753  && current_bookmark_ < static_cast<int>(bookmark_paths_.size()));
754 
755  listbox& bookmarks_bar = find_widget<listbox>("bookmarks");
758  bookmarks_bar.remove_row(current_bookmark_);
759 
760  current_bookmark_ = -1;
761 
763 }
764 
766 {
767  std::string new_dir_name;
768 
769  if(folder_create::execute(new_dir_name)) {
770  const std::string& new_path = concat_path(current_dir_, new_dir_name);
771 
772  if(!fs::make_directory(new_path)) {
774  VGETTEXT("Could not create a new folder at $path|. Make sure you have the appropriate permissions to write to this location.",
775  {{"path", new_path}}));
776  } else {
778  }
779  }
780 }
781 
783 {
784  if(current_entry_.empty()) {
785  return;
786  }
787 
788  const std::string& selection = concat_path(current_dir_, current_entry_);
789  const bool is_dir = fs::is_directory(selection);
790 
791  const std::string& message = (is_dir
792  ? _("The following folder and its contents will be permanently deleted:")
793  : _("The following file will be permanently deleted:"))
794  + "\n\n" + selection + "\n\n" + _("Do you wish to continue?");
795 
797  return;
798  }
799 
800  const bool result = is_dir
801  ? fs::delete_directory(selection)
802  : fs::delete_file(selection);
803 
804  if(!result) {
806  VGETTEXT("Could not delete $path|. Make sure you have the appropriate permissions to write to this location.",
807  {{"path", selection}}));
808  } else {
810  }
811 }
812 
813 } // 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.
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)
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.
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:280
void remove_row(const unsigned row, unsigned count=1)
Removes a row in the listbox.
Definition: listbox.cpp:112
void clear()
Removes all the rows in the listbox, clearing it.
Definition: listbox.cpp:153
int get_selected_row() const
Returns the first selected row.
Definition: listbox.cpp:305
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:159
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
window * get_window()
Get the parent window.
Definition: widget.cpp:117
@ 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:753
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:394
void keyboard_capture(widget *widget)
Definition: window.cpp:1207
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1213
void set_exit_hook(exit_hook mode, std::function< bool(window &)> func)
Sets the window's exit hook.
Definition: window.hpp:448
int get_retval()
Definition: window.hpp:401
@ 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:200
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: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: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:445
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:326
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.
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: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::size_t size(std::string_view 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.
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:178
std::string filename
Filename.
static map_location::direction n
#define b