gui2/tfile_dialog: Initial port of the filechooser dialog to GUI2
This provides, as far as I can tell, all the filechooser functionality that is actually in use in Open File mode except for the "type-ahead" option that is used to set filename extension hints. There's some newly-introduced border cases courtesy of Boost.Filesystem that I really feel we shouldn't worry about for now: * // is handled weirdly thanks to BFS honoring the POSIX provision for implementation-defined behavior regarding it. * UNCs on Windows are not supported. Just like in mostly everywhere else in Wesnoth. Same applies to \\.\, \\?\ and \??\. * Non-directory path components on Windows are not handled very gracefully (particularly obvious with volumes mounted as NTFS junction points, or symbolic links for the Documents folder on Wine) due to quirks in BFS's path::canonical() method and how it relies on dereferencing individual path components to resolve dot entries. Haven't tested all callers yet, they are still using the original filechooser entry points for now. I need to remove those and make everyone use gui2::tfile_dialog directly before this can be merged to master.
This commit is contained in:
parent
d301f8027f
commit
6e1f6bb686
8 changed files with 1124 additions and 2 deletions
346
data/gui/window/file_dialog.cfg
Normal file
346
data/gui/window/file_dialog.cfg
Normal file
|
@ -0,0 +1,346 @@
|
|||
#textdomain wesnoth-lib
|
||||
###
|
||||
### Definition of the file browser dialog used in the Map Editor
|
||||
### and in Preferences.
|
||||
###
|
||||
|
||||
[window]
|
||||
id = "file_dialog"
|
||||
description = "Common file browser dialog."
|
||||
|
||||
[resolution]
|
||||
definition = "default"
|
||||
|
||||
automatic_placement = "true"
|
||||
vertical_placement = "center"
|
||||
horizontal_placement = "center"
|
||||
|
||||
maximum_width = 800
|
||||
|
||||
[linked_group]
|
||||
id = "icon"
|
||||
fixed_width = "true"
|
||||
[/linked_group]
|
||||
|
||||
[linked_group]
|
||||
id = "file"
|
||||
fixed_width = "true"
|
||||
[/linked_group]
|
||||
|
||||
[tooltip]
|
||||
id = "tooltip_large"
|
||||
[/tooltip]
|
||||
|
||||
[helptip]
|
||||
id = "tooltip_large"
|
||||
[/helptip]
|
||||
|
||||
[grid]
|
||||
|
||||
[row]
|
||||
grow_factor = 0
|
||||
|
||||
[column]
|
||||
grow_factor = 1
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_alignment = "left"
|
||||
|
||||
[label]
|
||||
id = "title"
|
||||
definition = "title"
|
||||
label = ""
|
||||
[/label]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[row]
|
||||
grow_factor = 0
|
||||
|
||||
[column]
|
||||
grow_factor = 1
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_grow = true
|
||||
|
||||
[label]
|
||||
id = "message"
|
||||
definition = "default"
|
||||
label = ""
|
||||
wrap = true
|
||||
[/label]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[row]
|
||||
|
||||
[column]
|
||||
grow_factor = 1
|
||||
horizontal_grow = true
|
||||
|
||||
[grid]
|
||||
|
||||
[row]
|
||||
|
||||
[column]
|
||||
grow_factor = 0
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_alignment = "left"
|
||||
|
||||
[label]
|
||||
definition = "default"
|
||||
label = _ "Location:"
|
||||
[/label]
|
||||
|
||||
[/column]
|
||||
|
||||
[column]
|
||||
grow_factor = 1
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_grow = "true"
|
||||
|
||||
[label]
|
||||
id = "current_dir"
|
||||
definition = "default"
|
||||
label = ""
|
||||
[/label]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[/grid]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[row]
|
||||
grow_factor = 1
|
||||
|
||||
[column]
|
||||
grow_factor = 1
|
||||
|
||||
horizontal_grow = "true"
|
||||
vertical_grow = "true"
|
||||
|
||||
border = "all"
|
||||
border_size = 5
|
||||
|
||||
[listbox]
|
||||
id = "filelist"
|
||||
definition = "default"
|
||||
|
||||
[list_definition]
|
||||
|
||||
[row]
|
||||
|
||||
[column]
|
||||
vertical_grow = "true"
|
||||
horizontal_grow = "true"
|
||||
|
||||
[toggle_panel]
|
||||
# Needed for double-click event handling!
|
||||
id = "item_panel"
|
||||
definition = "default"
|
||||
|
||||
[grid]
|
||||
|
||||
[row]
|
||||
|
||||
[column]
|
||||
grow_factor = 0
|
||||
horizontal_alignment = "left"
|
||||
border = "all"
|
||||
border_size = 5
|
||||
|
||||
[image]
|
||||
id = "icon"
|
||||
definition = "default"
|
||||
linked_group = "icon"
|
||||
[/image]
|
||||
|
||||
[/column]
|
||||
|
||||
[column]
|
||||
grow_factor = 1
|
||||
horizontal_grow = "true"
|
||||
border = "all"
|
||||
border_size = 5
|
||||
|
||||
[label]
|
||||
id = "file"
|
||||
definition = "default"
|
||||
linked_group = "file"
|
||||
[/label]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[/grid]
|
||||
|
||||
[/toggle_panel]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[/list_definition]
|
||||
|
||||
[/listbox]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[row]
|
||||
grow_factor = 0
|
||||
|
||||
[column]
|
||||
grow_factor = 1
|
||||
horizontal_grow = "true"
|
||||
|
||||
[grid]
|
||||
|
||||
[row]
|
||||
grow_factor = 0
|
||||
|
||||
[column]
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_alignment = "left"
|
||||
|
||||
[button]
|
||||
id = "new_dir"
|
||||
definition = "default"
|
||||
label = _ "New Folder"
|
||||
[/button]
|
||||
|
||||
[/column]
|
||||
|
||||
[column]
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_alignment = "right"
|
||||
|
||||
[button]
|
||||
id = "delete_file"
|
||||
definition = "default"
|
||||
label = _ "Delete File"
|
||||
[/button]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[/grid]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[row]
|
||||
grow_factor = 0
|
||||
|
||||
[column]
|
||||
grow_factor = 1
|
||||
horizontal_grow = "true"
|
||||
|
||||
[grid]
|
||||
|
||||
[row]
|
||||
grow_factor = 0
|
||||
|
||||
[column]
|
||||
grow_factor = 0
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_alignment = "left"
|
||||
|
||||
[label]
|
||||
definition = "default"
|
||||
label = _ "File:"
|
||||
[/label]
|
||||
|
||||
[/column]
|
||||
|
||||
[column]
|
||||
grow_factor = 1
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_grow = "true"
|
||||
|
||||
[text_box]
|
||||
id = "filename"
|
||||
definition = "default"
|
||||
label = ""
|
||||
[/text_box]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[/grid]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[row]
|
||||
grow_factor = 0
|
||||
|
||||
[column]
|
||||
grow_factor = 1
|
||||
horizontal_alignment = "right"
|
||||
|
||||
[grid]
|
||||
|
||||
[row]
|
||||
grow_factor = 0
|
||||
|
||||
[column]
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_alignment = "right"
|
||||
|
||||
[button]
|
||||
id = "ok"
|
||||
definition = "default"
|
||||
label = _ "OK"
|
||||
[/button]
|
||||
|
||||
[/column]
|
||||
|
||||
[column]
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_alignment = "right"
|
||||
|
||||
[button]
|
||||
id = "cancel"
|
||||
definition = "default"
|
||||
label = _ "Cancel"
|
||||
[/button]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[/grid]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[/grid]
|
||||
|
||||
[/resolution]
|
||||
|
||||
[/window]
|
|
@ -553,6 +553,8 @@
|
|||
<Unit filename="../../src/gui/dialogs/editor/set_starting_position.hpp" />
|
||||
<Unit filename="../../src/gui/dialogs/end_credits.cpp" />
|
||||
<Unit filename="../../src/gui/dialogs/end_credits.hpp" />
|
||||
<Unit filename="../../src/gui/dialogs/file_dialog.cpp" />
|
||||
<Unit filename="../../src/gui/dialogs/file_dialog.hpp" />
|
||||
<Unit filename="../../src/gui/dialogs/folder_create.cpp" />
|
||||
<Unit filename="../../src/gui/dialogs/folder_create.hpp" />
|
||||
<Unit filename="../../src/gui/dialogs/formula_debugger.cpp" />
|
||||
|
|
|
@ -787,6 +787,7 @@ set(wesnoth-main_SRC
|
|||
gui/dialogs/editor/resize_map.cpp
|
||||
gui/dialogs/editor/set_starting_position.cpp
|
||||
gui/dialogs/end_credits.cpp
|
||||
gui/dialogs/file_dialog.cpp
|
||||
gui/dialogs/folder_create.cpp
|
||||
gui/dialogs/formula_debugger.cpp
|
||||
gui/dialogs/game_cache_options.cpp
|
||||
|
|
|
@ -377,6 +377,7 @@ wesnoth_sources = Split("""
|
|||
gui/dialogs/editor/resize_map.cpp
|
||||
gui/dialogs/editor/set_starting_position.cpp
|
||||
gui/dialogs/end_credits.cpp
|
||||
gui/dialogs/file_dialog.cpp
|
||||
gui/dialogs/folder_create.cpp
|
||||
gui/dialogs/formula_debugger.cpp
|
||||
gui/dialogs/game_cache_options.cpp
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include "video.hpp"
|
||||
#include "gettext.hpp"
|
||||
#include "gui/dialogs/file_dialog.hpp"
|
||||
#include "gui/dialogs/folder_create.hpp"
|
||||
#include "gui/dialogs/transient_message.hpp"
|
||||
#include "filechooser.hpp"
|
||||
|
@ -30,7 +31,19 @@ namespace dialogs
|
|||
int show_file_chooser_dialog(CVideo& video, std::string &filename,
|
||||
std::string const &title, bool show_directory_buttons,
|
||||
const std::string& type_a_head,
|
||||
int xloc, int yloc) {
|
||||
int xloc, int yloc)
|
||||
{
|
||||
if(true) {
|
||||
gui2::tfile_dialog dlg;
|
||||
dlg.set_path(filename).set_title(title).set_read_only(!show_directory_buttons).set_filename(type_a_head);
|
||||
if(dlg.show(video)) {
|
||||
filename = dlg.path();
|
||||
std::cerr << filename << '\n';
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
file_dialog d(video, filename, title, "", show_directory_buttons);
|
||||
if (!type_a_head.empty())
|
||||
|
@ -46,7 +59,19 @@ int show_file_chooser_dialog_save(CVideo& video, std::string &filename,
|
|||
const std::string& default_file_name,
|
||||
bool show_directory_buttons,
|
||||
const std::string& type_a_head,
|
||||
int xloc, int yloc) {
|
||||
int xloc, int yloc)
|
||||
{
|
||||
if(true) {
|
||||
gui2::tfile_dialog dlg;
|
||||
dlg.set_path(filename).set_title(title).set_read_only(!show_directory_buttons).set_filename(type_a_head).set_save_mode(true);
|
||||
if(dlg.show(video)) {
|
||||
filename = dlg.path();
|
||||
std::cerr << filename << '\n';
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
file_dialog d(video, filename, title, default_file_name, show_directory_buttons);
|
||||
d.set_autocomplete(false);
|
||||
|
|
488
src/gui/dialogs/file_dialog.cpp
Normal file
488
src/gui/dialogs/file_dialog.cpp
Normal file
|
@ -0,0 +1,488 @@
|
|||
/*
|
||||
Copyright (C) 2011, 2016 by Ignacio R. Morelle <shadowm2006@gmail.com>
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#define GETTEXT_DOMAIN "wesnoth-lib"
|
||||
|
||||
#include "gui/dialogs/file_dialog.hpp"
|
||||
|
||||
#include "cursor.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "formula/string_utils.hpp"
|
||||
#include "gui/auxiliary/find_widget.hpp"
|
||||
#include "gui/dialogs/folder_create.hpp"
|
||||
#include "gui/dialogs/helper.hpp"
|
||||
#include "gui/dialogs/transient_message.hpp"
|
||||
#include "gui/widgets/button.hpp"
|
||||
#ifdef GUI2_EXPERIMENTAL_LISTBOX
|
||||
#include "gui/widgets/list.hpp"
|
||||
#else
|
||||
#include "gui/widgets/listbox.hpp"
|
||||
#endif
|
||||
#include "gui/widgets/settings.hpp"
|
||||
#include "gui/widgets/text_box.hpp"
|
||||
#include "gui/widgets/toggle_panel.hpp"
|
||||
#include "gui/widgets/window.hpp"
|
||||
#include "gettext.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
||||
static lg::log_domain log_filedlg{"gui/dialogs/file_dialog"};
|
||||
#define ERR_FILEDLG LOG_STREAM(err, log_filedlg)
|
||||
#define WRN_FILEDLG LOG_STREAM(warn, log_filedlg)
|
||||
#define LOG_FILEDLG LOG_STREAM(info, log_filedlg)
|
||||
#define DBG_FILEDLG LOG_STREAM(debug, log_filedlg)
|
||||
|
||||
namespace fs = filesystem;
|
||||
|
||||
namespace
|
||||
{
|
||||
const std::string icon_dir = "misc/folder-icon.png";
|
||||
const std::string icon_file = "";
|
||||
const std::string icon_parent = "";
|
||||
// NOTE: Does not need to be the same as PARENT_DIR! Use PARENT_DIR to build
|
||||
// relative paths for non-presentational purposes instead.
|
||||
const std::string label_parent = "..";
|
||||
|
||||
const std::string CURRENT_DIR = ".";
|
||||
const std::string PARENT_DIR = "..";
|
||||
|
||||
const int FILE_DIALOG_ITEM_RETVAL = 9001;
|
||||
const int FILE_DIALOG_MAX_ENTRY_LENGTH = 42;
|
||||
|
||||
inline std::string concat_path(const std::string& a, const std::string& b)
|
||||
{
|
||||
//
|
||||
// As of Boost 1.61, normalize_path() displays unusual behavior when passing
|
||||
// it paths with extra path separators (e.g. //opt becomes
|
||||
// //opt/home/shadowm/src/wesnoth, where the extraneous sequence is probably
|
||||
// the current working dir), so avoid leaving those around.
|
||||
//
|
||||
// TODO: Maybe handle this corner case in filesystem::normalize_path()
|
||||
// instead, really.
|
||||
//
|
||||
if((a.empty() || !fs::is_path_sep(a.back())) && (b.empty() || !fs::is_path_sep(b.front()))) {
|
||||
return a + fs::path_separator() + b;
|
||||
} else {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string filesystem_root()
|
||||
{
|
||||
// TODO: Multiple drives support (may require cooperation from the caller).
|
||||
return std::string(1, fs::path_separator());
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
namespace gui2
|
||||
{
|
||||
|
||||
REGISTER_DIALOG(file_dialog)
|
||||
|
||||
tfile_dialog::tfile_dialog()
|
||||
: title_(_("Find File"))
|
||||
, msg_()
|
||||
, current_entry_()
|
||||
, current_dir_()
|
||||
, read_only_(false)
|
||||
, save_mode_(false)
|
||||
, dir_files_()
|
||||
, dir_subdirs_()
|
||||
{
|
||||
}
|
||||
|
||||
std::string tfile_dialog::path() const
|
||||
{
|
||||
const std::string& dir_norm = fs::normalize_path(current_dir_, true);
|
||||
|
||||
if(current_entry_.empty() || current_entry_ == CURRENT_DIR) {
|
||||
return dir_norm;
|
||||
} else if(current_entry_ == PARENT_DIR) {
|
||||
return fs::directory_name(dir_norm);
|
||||
}
|
||||
|
||||
return concat_path(dir_norm, current_entry_);
|
||||
}
|
||||
|
||||
tfile_dialog& tfile_dialog::set_path(const std::string& value)
|
||||
{
|
||||
if(value.empty()) {
|
||||
current_dir_ = filesystem_root();
|
||||
}
|
||||
|
||||
const std::string& norm = fs::normalize_path(value, true);
|
||||
|
||||
if(fs::is_directory(norm)) {
|
||||
current_dir_ = norm;
|
||||
} else {
|
||||
current_dir_ = fs::nearest_extant_parent(norm);
|
||||
if(current_dir_.empty()) {
|
||||
current_dir_ = filesystem_root();
|
||||
}
|
||||
// The file may or may not exist. We'll find out eventually when setting up
|
||||
// the dialog.
|
||||
current_entry_ = fs::base_name(norm);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
tfile_dialog& tfile_dialog::set_filename(const std::string& value)
|
||||
{
|
||||
current_entry_ = value;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void tfile_dialog::pre_show(twindow& window)
|
||||
{
|
||||
tcontrol& title = find_widget<tcontrol>(&window, "title", false);
|
||||
tcontrol& message = find_widget<tcontrol>(&window, "message", false);
|
||||
|
||||
title.set_label(title_);
|
||||
|
||||
if(msg_.empty()) {
|
||||
message.set_visible(gui2::twidget::tvisible::invisible);
|
||||
} else {
|
||||
message.set_label(msg_);
|
||||
message.set_use_markup(true);
|
||||
}
|
||||
|
||||
tlistbox& filelist = find_widget<tlistbox>(&window, "filelist", false);
|
||||
|
||||
#ifdef GUI2_EXPERIMENTAL_LISTBOX
|
||||
connect_signal_notify_modified(filelist, std::bind(
|
||||
&tfile_dialog::on_row_selected
|
||||
, *this
|
||||
, std::ref(window)));
|
||||
#else
|
||||
filelist.set_callback_value_change(
|
||||
dialog_callback<tfile_dialog, &tfile_dialog::on_row_selected>);
|
||||
#endif
|
||||
|
||||
tbutton& mkdir_button = find_widget<tbutton>(&window, "new_dir", false);
|
||||
tbutton& rm_button = find_widget<tbutton>(&window, "delete_file", false);
|
||||
|
||||
connect_signal_mouse_left_click(mkdir_button,
|
||||
std::bind(&tfile_dialog::on_dir_create_cmd, this, std::ref(window)));
|
||||
connect_signal_mouse_left_click(rm_button,
|
||||
std::bind(&tfile_dialog::on_file_delete_cmd, this, std::ref(window)));
|
||||
|
||||
if(read_only_) {
|
||||
mkdir_button.set_active(false);
|
||||
rm_button.set_active(false);
|
||||
|
||||
mkdir_button.set_visible(twidget::tvisible::hidden);
|
||||
rm_button.set_visible(twidget::tvisible::hidden);
|
||||
}
|
||||
|
||||
refresh_fileview(window);
|
||||
|
||||
window.keyboard_capture(&filelist);
|
||||
window.set_exit_hook(std::bind(&tfile_dialog::on_exit, this, std::ref(window)));
|
||||
}
|
||||
|
||||
bool tfile_dialog::on_exit(twindow& window)
|
||||
{
|
||||
if(window.get_retval() == FILE_DIALOG_ITEM_RETVAL) {
|
||||
// Attempting to exit by double clicking items -- only proceeds if the item
|
||||
// was a file.
|
||||
if(process_fileview_submit(window)) {
|
||||
window.set_retval(twindow::OK, false);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(window.get_retval() == twindow::OK) {
|
||||
// Attempting to exit by pressing Enter/clicking OK -- only proceeds if the
|
||||
// textbox was not altered by the user to point to a different directory.
|
||||
return process_textbox_submit(window);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tfile_dialog::is_selection_type_acceptable(tfile_dialog::SELECTION_TYPE stype) const
|
||||
{
|
||||
// TODO: Adapt for implementing directory selection mode.
|
||||
return save_mode_
|
||||
? stype != SELECTION_IS_DIR && stype != SELECTION_PARENT_NOT_FOUND
|
||||
: stype == SELECTION_IS_FILE;
|
||||
}
|
||||
|
||||
bool tfile_dialog::process_submit_common(twindow& window, const std::string& name)
|
||||
{
|
||||
const auto stype = register_new_selection(name);
|
||||
|
||||
//DBG_FILEDLG << "current_dir_=" << current_dir_ << " current_entry_=" << current_entry_ << '\n';
|
||||
|
||||
if(is_selection_type_acceptable(stype)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch(stype) {
|
||||
case SELECTION_IS_DIR:
|
||||
// TODO: Adapt for implementing directory selection mode.
|
||||
refresh_fileview(window);
|
||||
break;
|
||||
case SELECTION_PARENT_NOT_FOUND:
|
||||
// We get here in save mode or not. Use the file creation language only in
|
||||
// save mode.
|
||||
if(save_mode_) {
|
||||
show_transient_error_message(window.video(), vgettext("The file or folder $path cannot be created.", {{"path", name}}));
|
||||
break;
|
||||
}
|
||||
case SELECTION_NOT_FOUND:
|
||||
// We only get here if we aren't in save mode.
|
||||
show_transient_error_message(window.video(), vgettext("The file or folder $path does not exist.", {{"path", name}}));
|
||||
break;
|
||||
case SELECTION_IS_FILE:
|
||||
// TODO: Adapt for implementing directory selection mode.
|
||||
default:
|
||||
assert(false && "Unimplemented selection mode or semantics");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool tfile_dialog::process_fileview_submit(twindow& window)
|
||||
{
|
||||
tlistbox& filelist = find_widget<tlistbox>(&window, "filelist", false);
|
||||
const std::string& selected_name = get_filelist_selection(filelist);
|
||||
return process_submit_common(window, selected_name);
|
||||
}
|
||||
|
||||
bool tfile_dialog::process_textbox_submit(twindow& window)
|
||||
{
|
||||
ttext_box& file_textbox = find_widget<ttext_box>(&window, "filename", false);
|
||||
const std::string& input_name = file_textbox.get_value();
|
||||
return !input_name.empty() && process_submit_common(window, input_name);
|
||||
}
|
||||
|
||||
std::string tfile_dialog::get_filelist_selection(tlistbox& filelist)
|
||||
{
|
||||
const int row = filelist.get_selected_row();
|
||||
|
||||
if(row == -1) {
|
||||
// Shouldn't happen...
|
||||
return "";
|
||||
}
|
||||
|
||||
const bool i_am_root = fs::is_root(current_dir_);
|
||||
|
||||
if(row == 0 && !i_am_root) {
|
||||
return PARENT_DIR;
|
||||
} else {
|
||||
size_t n = i_am_root ? row : row - 1;
|
||||
|
||||
if(n < dir_subdirs_.size()) {
|
||||
return dir_subdirs_[n];
|
||||
} else {
|
||||
n -= dir_subdirs_.size();
|
||||
|
||||
if(n < dir_files_.size()) {
|
||||
return dir_files_[n];
|
||||
} else {
|
||||
assert(false && "File list selection is out of range!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
tfile_dialog::SELECTION_TYPE tfile_dialog::register_new_selection(const std::string& name)
|
||||
{
|
||||
std::string new_path, new_parent;
|
||||
|
||||
if(fs::is_relative(name)) {
|
||||
// On Windows, \ represents a path relative to the root of the process'
|
||||
// current working drive specified by the current working dir, so we get
|
||||
// here. This makes it the only platform where is_relative() and is_root()
|
||||
// aren't mutually exclusive.
|
||||
if(fs::is_root(name)) {
|
||||
DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' is relative to a root resource\n";
|
||||
// Using the browsed dir's root drive instead of the cwd's makes the most
|
||||
// sense for users.
|
||||
new_parent = fs::root_name(current_dir_);
|
||||
new_path = fs::normalize_path(concat_path(new_parent, name), true, true);
|
||||
} else {
|
||||
DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' seems relative\n";
|
||||
new_parent = current_dir_;
|
||||
new_path = fs::normalize_path(concat_path(current_dir_, name), true, true);
|
||||
}
|
||||
} else {
|
||||
DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' seems absolute\n";
|
||||
new_parent = fs::directory_name(name);
|
||||
new_path = fs::normalize_path(name, true, true);
|
||||
DBG_FILEDLG << "register_new_selection(): new selection is " << new_path << '\n';
|
||||
}
|
||||
|
||||
if(!new_path.empty()) {
|
||||
if(fs::is_directory(new_path)) {
|
||||
DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' is a directory: " << new_path << '\n';
|
||||
current_dir_ = new_path;
|
||||
current_entry_.clear();
|
||||
return SELECTION_IS_DIR;
|
||||
} else if(fs::file_exists(new_path)) {
|
||||
// FIXME: Perhaps redundant since the three-params call to normalize_path()
|
||||
// above necessarily validates existence.
|
||||
DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' is a file, symbolic link, or special: " << new_path << '\n';
|
||||
current_dir_ = fs::directory_name(new_path);
|
||||
current_entry_ = fs::base_name(new_path);
|
||||
return SELECTION_IS_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
// The path does not exist, at least not entirely. See if the parent does
|
||||
// (in save mode non-existent files are accepted as long as the parent dir
|
||||
// exists).
|
||||
const std::string& absolute_parent = fs::normalize_path(new_parent, true, true);
|
||||
if(!absolute_parent.empty()) {
|
||||
DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' does not exist or is not accessible, but parent exists\n";
|
||||
current_dir_ = absolute_parent;
|
||||
current_entry_ = fs::base_name(name);
|
||||
return SELECTION_NOT_FOUND;
|
||||
}
|
||||
|
||||
DBG_FILEDLG << "register_new_selection(): new selection '" << name << "' does not exist or is not accessible\n";
|
||||
return SELECTION_PARENT_NOT_FOUND;
|
||||
}
|
||||
|
||||
void tfile_dialog::refresh_fileview(twindow& window)
|
||||
{
|
||||
cursor::setter cur{cursor::WAIT};
|
||||
|
||||
dir_files_.clear();
|
||||
dir_subdirs_.clear();
|
||||
|
||||
// TODO: Need to detect and handle cases where we don't have search permission
|
||||
// on current_dir_, otherwise things may get weird.
|
||||
filesystem::get_files_in_dir(current_dir_, &dir_files_, &dir_subdirs_, filesystem::FILE_NAME_ONLY);
|
||||
|
||||
//
|
||||
// Clear and refill the filelist box.
|
||||
//
|
||||
|
||||
tlistbox& filelist = find_widget<tlistbox>(&window, "filelist", false);
|
||||
|
||||
filelist.clear();
|
||||
|
||||
// Parent entry
|
||||
if(!fs::is_root(current_dir_)) {
|
||||
// label_parent may not necessarily be always ".." in the future, so push
|
||||
// with check_selection = false and check the selection ourselves here.
|
||||
push_fileview_row(filelist, label_parent, icon_parent, false);
|
||||
if(current_entry_ == PARENT_DIR || current_entry_.empty()) {
|
||||
filelist.select_row(0, true);
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto& dir : dir_subdirs_) {
|
||||
push_fileview_row(filelist, dir, icon_dir);
|
||||
}
|
||||
|
||||
for(const auto& file : dir_files_) {
|
||||
push_fileview_row(filelist, file, icon_file);
|
||||
}
|
||||
|
||||
find_widget<tcontrol>(&window, "current_dir", false).set_label(current_dir_);
|
||||
find_widget<ttext_box>(&window, "filename", false).set_value(current_entry_);
|
||||
}
|
||||
|
||||
void tfile_dialog::push_fileview_row(tlistbox& filelist, const std::string& name, const std::string& icon, bool check_selection)
|
||||
{
|
||||
// TODO: Hopefully some day GUI2 will allow us to make labels be ellipsized
|
||||
// dynamically at layout/rendering time.
|
||||
std::string label = name;
|
||||
utils::ellipsis_truncate(label, FILE_DIALOG_MAX_ENTRY_LENGTH);
|
||||
|
||||
std::map<std::string, string_map> data;
|
||||
data["icon"]["label"] = icon;
|
||||
data["file"]["label"] = label;
|
||||
filelist.add_row(data);
|
||||
|
||||
const unsigned last_pos = filelist.get_item_count() - 1;
|
||||
tgrid* const last_grid = filelist.get_row_grid(last_pos);
|
||||
assert(last_grid);
|
||||
|
||||
//
|
||||
// Crummy hack around the lack of an option to hook into row double click
|
||||
// events for all rows using the GUI2 listbox API. Assign a special retval to
|
||||
// each row that triggers a special check during dialog exit.
|
||||
//
|
||||
find_widget<ttoggle_panel>(last_grid, "item_panel", false)
|
||||
.set_retval(FILE_DIALOG_ITEM_RETVAL);
|
||||
|
||||
if(check_selection && name == current_entry_) {
|
||||
filelist.select_row(last_pos, true);
|
||||
}
|
||||
}
|
||||
|
||||
void tfile_dialog::on_row_selected(twindow& window)
|
||||
{
|
||||
tlistbox& filelist = find_widget<tlistbox>(&window, "filelist", false);
|
||||
ttext_box& file_textbox = find_widget<ttext_box>(&window, "filename", false);
|
||||
|
||||
// Don't use register_new_selection() here, we don't want any parsing to be
|
||||
// performed at this point.
|
||||
current_entry_ = get_filelist_selection(filelist);
|
||||
|
||||
// Clear the textbox when selecting ..
|
||||
if(current_entry_ != PARENT_DIR) {
|
||||
file_textbox.set_value(current_entry_);
|
||||
} else {
|
||||
file_textbox.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void tfile_dialog::on_dir_create_cmd(twindow& window)
|
||||
{
|
||||
std::string new_dir_name;
|
||||
|
||||
if(tfolder_create::execute(new_dir_name, window.video())) {
|
||||
const std::string& new_path = concat_path(current_dir_, new_dir_name);
|
||||
|
||||
if(!fs::make_directory(new_path)) {
|
||||
show_transient_error_message(window.video(),
|
||||
vgettext("Could not create a new folder at $path|. Make sure you have the appropriate permissions to write to this location.",
|
||||
{{"path", new_path}}));
|
||||
} else {
|
||||
refresh_fileview(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tfile_dialog::on_file_delete_cmd(twindow& window)
|
||||
{
|
||||
if(current_entry_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string& selection = concat_path(current_dir_, current_entry_);
|
||||
if(!fs::delete_file(selection)) {
|
||||
show_transient_error_message(window.video(),
|
||||
vgettext("Could not delete $path|. Make sure you have the appropriate permissions to write to this location.",
|
||||
{{"path", selection}}));
|
||||
} else {
|
||||
refresh_fileview(window);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gui2
|
248
src/gui/dialogs/file_dialog.hpp
Normal file
248
src/gui/dialogs/file_dialog.hpp
Normal file
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
Copyright (C) 2011, 2016 by Ignacio R. Morelle <shadowm2006@gmail.com>
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#ifndef GUI_DIALOGS_FILE_DIALOG_HPP_INCLUDED
|
||||
#define GUI_DIALOGS_FILE_DIALOG_HPP_INCLUDED
|
||||
|
||||
#include "gui/dialogs/dialog.hpp"
|
||||
|
||||
/**
|
||||
* Generic file dialog.
|
||||
*
|
||||
* This provides UI elements for browsing the filesystem and choosing a file
|
||||
* path to open or create, and optionally allows creating new directories or
|
||||
* deleting existing files and directories.
|
||||
*
|
||||
* Because of the sheer amount of unrelated options provided by this dialog,
|
||||
* no parameter-based constructors or static @a execute() functions are
|
||||
* provided. Use individual property setters after construction and before
|
||||
* invoking show(), instead.
|
||||
*/
|
||||
namespace gui2
|
||||
{
|
||||
|
||||
class tfile_dialog : public tdialog
|
||||
{
|
||||
public:
|
||||
tfile_dialog();
|
||||
|
||||
/**
|
||||
* Gets the current dialog title text.
|
||||
*/
|
||||
const std::string& title() const
|
||||
{
|
||||
return title_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current dialog title text.
|
||||
*/
|
||||
tfile_dialog& set_title(const std::string& value)
|
||||
{
|
||||
title_ = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current dialog instructions/message text.
|
||||
*/
|
||||
const std::string& message() const
|
||||
{
|
||||
return msg_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current dialog instructions/message text.
|
||||
*
|
||||
* The message text may contain Pango markup.
|
||||
*/
|
||||
tfile_dialog& set_message(const std::string& value)
|
||||
{
|
||||
msg_ = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current file selection.
|
||||
*
|
||||
* @returns An absolute path to the selected file.
|
||||
*/
|
||||
std::string path() const;
|
||||
|
||||
/**
|
||||
* Sets the initial file selection.
|
||||
*
|
||||
* If the path is found to refer to a file (more specifically, any
|
||||
* non-directory object), that file is initially selected on the directory
|
||||
* contents view and the file name box is set to contain its name. If the file
|
||||
* does not exist, but the path leading up to it does, the directory contents
|
||||
* view displays that path and there isn't an initial file selection or name
|
||||
* set unless set_filename() is used first.
|
||||
*
|
||||
* If you want to set an initial file name hint/template, use set_filename()
|
||||
* <b>after</b> calling this method.
|
||||
*/
|
||||
tfile_dialog& set_path(const std::string& value);
|
||||
|
||||
/**
|
||||
* Sets the initial file name input but not the path.
|
||||
*
|
||||
* The file name needs not exist in the initial path selected with set_path().
|
||||
*
|
||||
* If this is used before set_path() and the path passed there points to a
|
||||
* file, that file name will replace the one given here.
|
||||
*/
|
||||
tfile_dialog& set_filename(const std::string& value);
|
||||
|
||||
/**
|
||||
* Whether user interface elements for manipulating existing objects are provided.
|
||||
*/
|
||||
bool read_only() const
|
||||
{
|
||||
return read_only_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to provide user interface elements for manipulating existing objects.
|
||||
*/
|
||||
tfile_dialog& set_read_only(bool value)
|
||||
{
|
||||
read_only_ = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether save mode is enabled.
|
||||
*
|
||||
* See set_save_mode() for more information.
|
||||
*/
|
||||
bool save_mode() const
|
||||
{
|
||||
return save_mode_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dialog's behavior on non-existent file name inputs.
|
||||
*
|
||||
* When save mode is enabled, file names entered into the dialog by the user
|
||||
* need not exist already (but their parent directories still do). Otherwise,
|
||||
* the user is only able to select existing files.
|
||||
*/
|
||||
tfile_dialog& set_save_mode(bool value)
|
||||
{
|
||||
save_mode_ = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string title_;
|
||||
std::string msg_;
|
||||
|
||||
std::string current_entry_;
|
||||
std::string current_dir_;
|
||||
|
||||
bool read_only_;
|
||||
bool save_mode_;
|
||||
|
||||
std::vector<std::string> dir_files_;
|
||||
std::vector<std::string> dir_subdirs_;
|
||||
|
||||
/** Inherited from tdialog, implemented by REGISTER_DIALOG. */
|
||||
virtual const std::string& window_id() const;
|
||||
|
||||
/** Inherited from tdialog. */
|
||||
void pre_show(twindow& window);
|
||||
|
||||
/** Handles dialog exit events and decides whether to proceed or not. */
|
||||
bool on_exit(twindow& window);
|
||||
/** Handles file/directory selection on single-click. */
|
||||
void on_row_selected(twindow& window);
|
||||
/** Handles New Folder button press events. */
|
||||
void on_dir_create_cmd(twindow& window);
|
||||
/** Handles Delete button press events. */
|
||||
void on_file_delete_cmd(twindow& window);
|
||||
|
||||
/**
|
||||
* Processes file view selection in reaction to row double-click events.
|
||||
*
|
||||
* It takes care of synchronizing the state, browsing to the new selection,
|
||||
* and/or displaying an error message if appropriate
|
||||
*
|
||||
* @returns Whether to exit the dialog successfully (@a true) or continue
|
||||
* (@a false).
|
||||
*/
|
||||
bool process_fileview_submit(twindow& window);
|
||||
|
||||
/**
|
||||
* Processes textbox input in reaction to OK button/Enter key events.
|
||||
*
|
||||
* It takes care of synchronizing the state, browsing to the new selection,
|
||||
* and/or displaying an error message if appropriate
|
||||
*
|
||||
* @returns Whether to exit the dialog successfully (@a true) or continue
|
||||
* (@a false).
|
||||
*/
|
||||
bool process_textbox_submit(twindow& window);
|
||||
|
||||
bool process_submit_common(twindow& window, const std::string& name);
|
||||
|
||||
std::string get_filelist_selection(class tlistbox& filelist);
|
||||
|
||||
enum SELECTION_TYPE
|
||||
{
|
||||
SELECTION_NOT_FOUND,
|
||||
SELECTION_PARENT_NOT_FOUND,
|
||||
SELECTION_IS_DIR,
|
||||
SELECTION_IS_FILE,
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the given selection type is acceptable for closing the dialog.
|
||||
*
|
||||
* @todo This currently never returns @a true for SELECTION_IS_DIR, awaiting
|
||||
* a need to implement a directory selection mode.
|
||||
*/
|
||||
bool is_selection_type_acceptable(SELECTION_TYPE stype) const;
|
||||
|
||||
/**
|
||||
* Updates the internal state and returns the type of the selection.
|
||||
*
|
||||
* If the given @a name refers to a non-existent object, the internal state is
|
||||
* unchanged.
|
||||
*/
|
||||
SELECTION_TYPE register_new_selection(const std::string& name);
|
||||
|
||||
/**
|
||||
* Updates the dialog contents to match the internal state.
|
||||
*/
|
||||
void refresh_fileview(twindow& window);
|
||||
|
||||
/**
|
||||
* Row building helper for refresh_fileview().
|
||||
*
|
||||
* @param filelist Target for adding the new row.
|
||||
* @param name Label, assumed to be a file name if
|
||||
* check_selection = true.
|
||||
* @param icon Row icon.
|
||||
* @param check_selection Whether to set the row to selected if the current
|
||||
* file name in the internal state matches the row's
|
||||
* label/name.
|
||||
*/
|
||||
void push_fileview_row(class tlistbox& filelist, const std::string& name, const std::string& icon, bool check_selection = true);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -51,6 +51,7 @@
|
|||
#include "gui/dialogs/editor/resize_map.hpp"
|
||||
#include "gui/dialogs/editor/set_starting_position.hpp"
|
||||
#include "gui/dialogs/end_credits.hpp"
|
||||
#include "gui/dialogs/file_dialog.hpp"
|
||||
#include "gui/dialogs/folder_create.hpp"
|
||||
#include "gui/dialogs/formula_debugger.hpp"
|
||||
#include "gui/dialogs/game_cache_options.hpp"
|
||||
|
@ -388,6 +389,7 @@ BOOST_AUTO_TEST_CASE(test_gui2)
|
|||
test<gui2::teditor_resize_map>();
|
||||
test<gui2::teditor_set_starting_position>();
|
||||
test<gui2::tfaction_select>();
|
||||
test<gui2::tfile_dialog>();
|
||||
test<gui2::tfolder_create>();
|
||||
test<gui2::tformula_debugger>();
|
||||
test<gui2::tgame_cache_options>();
|
||||
|
@ -1018,6 +1020,15 @@ struct twrapper<gui2::teditor_resize_map>
|
|||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct twrapper<gui2::tfile_dialog>
|
||||
{
|
||||
gui2::tfile_dialog* create()
|
||||
{
|
||||
return new gui2::tfile_dialog();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct twrapper<gui2::tfolder_create>
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue