Merge lp:~widelands-dev/widelands/handling-various-fs-errors into lp:widelands

Proposed by Arty
Status: Merged
Merged at revision: 8929
Proposed branch: lp:~widelands-dev/widelands/handling-various-fs-errors
Merge into: lp:widelands
Diff against target: 512 lines (+213/-44)
9 files modified
src/editor/ui_menus/main_menu_save_map.cc (+39/-10)
src/editor/ui_menus/main_menu_save_map_make_directory.cc (+34/-5)
src/editor/ui_menus/main_menu_save_map_make_directory.h (+3/-0)
src/logic/game.cc (+7/-1)
src/logic/map.cc (+13/-9)
src/network/gameclient.cc (+25/-6)
src/ui_fsmenu/loadgame.cc (+2/-1)
src/wui/load_or_save_game.cc (+83/-11)
src/wui/load_or_save_game.h (+7/-1)
To merge this branch: bzr merge lp:~widelands-dev/widelands/handling-various-fs-errors
Reviewer Review Type Date Requested Status
GunChleoc Approve
Review via email: mp+358767@code.launchpad.net

Commit message

Improved error handling for file save dialogs

In Load/Save menu for games/replays:
- All deletion errors are caught, player gets a message.
- When deleting multiplayer replays, also the corresponding syncstream file is deleted.
- After deleting replays, the table now respects the Show Filenames setting.

In Save menu for maps:
- Errors when trying to create a directory are now caught, player gets a message.
- Directory creation now doesn't allow names with map extensions, because the game would just trying to interpret them as maps and not show as directories. A tooltip is shown.
- Directory creation can now also be triggered by pressing Enter in the name edit box.

Other file errors are caught and logged:
- syncstream deletion in SyncWrapper destructor
- in Map::get_correct_loader
- file deletion/renaming in GameClient::handle_packet

To post a comment you must log in.
Revision history for this message
Arty (artydent) wrote :

Some of the "handling" is just catching and logging.

In GameClient::handle_packet, when renaming a file to a backup fails, we could possibly handle this by finding another filename to save to, but I think this would have to be agreed upon by the participating clients. I didn't attempt this, because I am not yet familiar with the network code.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4226. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/454985800.
Appveyor build 4022. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_handling_various_fs_errors-4022.

Revision history for this message
GunChleoc (gunchleoc) wrote :

I have done a code review via diff comments. Not tested yet.

How would you feel about trying your hand at code reviews too?

https://code.launchpad.net/widelands/+activereviews

Your C++ is already a lot better than mine was when I started doing them :)

review: Needs Fixing
Revision history for this message
Arty (artydent) wrote :

Fixed those things.

Sure, I can do code reviews. I have looked at other active reviews before but mostly ignored them because they were either trivial one-line fixes or part of the code I wasn't familiar with at all yet.
Anything in particular I should look out for in code reviews?

Revision history for this message
GunChleoc (gunchleoc) wrote :

Just look at it to see if anything catches your attention that doesn't look right and test the change.

You can also do the code review only and leave the testing to somebody else if you don't have time to test as well.

The purpose of reviews is to help avoid introducing new bugs and to prevent spaghetti code and to make the code easy to read by having a consistent code style.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Code LGTM, still needs testing.

I have also added a reply to one of your comments.

review: Approve
Revision history for this message
GunChleoc (gunchleoc) wrote :

Testing done:

- When a directory with the name already exists, the "Create directory" dialog is closed. It would be good to reopen it with the name pre-filled.

review: Needs Fixing
Revision history for this message
GunChleoc (gunchleoc) wrote :

Tested and all good now :)

@bunnybot merge

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/editor/ui_menus/main_menu_save_map.cc'
2--- src/editor/ui_menus/main_menu_save_map.cc 2018-11-12 06:52:50 +0000
3+++ src/editor/ui_menus/main_menu_save_map.cc 2018-11-19 20:04:35 +0000
4@@ -155,15 +155,42 @@
5 void MainMenuSaveMap::clicked_make_directory() {
6 /** TRANSLATORS: A folder that hasn't been given a name yet */
7 MainMenuSaveMapMakeDirectory md(this, _("unnamed"));
8- if (md.run<UI::Panel::Returncodes>() == UI::Panel::Returncodes::kOk) {
9- g_fs->ensure_directory_exists(curdir_);
10- // Create directory.
11- std::string fullname = curdir_ + g_fs->file_separator() + md.get_dirname();
12- // Trim it for preceding/trailing whitespaces in user input
13- boost::trim(fullname);
14- g_fs->make_directory(fullname);
15- fill_table();
16+ bool open_dialogue = true;
17+ while (open_dialogue) {
18+ open_dialogue = false;
19+ if (md.run<UI::Panel::Returncodes>() == UI::Panel::Returncodes::kOk) {
20+ std::string fullname = curdir_ + g_fs->file_separator() + md.get_dirname();
21+ // Trim it for preceding/trailing whitespaces in user input
22+ boost::trim(fullname);
23+ if (g_fs->file_exists(fullname)) {
24+ const std::string s =
25+ _("A file or directory with that name already exists.");
26+ UI::WLMessageBox mbox(this, _("Error Creating Directory!"), s,
27+ UI::WLMessageBox::MBoxType::kOk);
28+ mbox.run<UI::Panel::Returncodes>();
29+ open_dialogue = true;
30+ } else {
31+ try {
32+ g_fs->ensure_directory_exists(curdir_);
33+ // Create directory.
34+ g_fs->make_directory(fullname);
35+ } catch (const FileError& e) {
36+ log("directory creation failed in MainMenuSaveMap::"
37+ "clicked_make_directory: %s\n", e.what());
38+ const std::string s =
39+ (boost::format(_("Error while creating directory ā€˜%sā€™."))
40+ % fullname).str();
41+ UI::WLMessageBox mbox(this, _("Error Creating Directory!"), s,
42+ UI::WLMessageBox::MBoxType::kOk);
43+ mbox.run<UI::Panel::Returncodes>();
44+ }
45+ fill_table();
46+ }
47+ }
48 }
49+ table_.focus();
50+ // TODO(Arty): In case of successful dir creation we should select the
51+ // new dir in the table.
52 }
53
54 void MainMenuSaveMap::clicked_edit_options() {
55@@ -256,8 +283,9 @@
56 boost::trim(filename);
57
58 // OK, first check if the extension matches (ignoring case).
59- if (!boost::iends_with(filename, kWidelandsMapExtension))
60+ if (!boost::iends_with(filename, kWidelandsMapExtension)) {
61 filename += kWidelandsMapExtension;
62+ }
63
64 // Append directory name.
65 const std::string complete_filename = curdir_ + g_fs->file_separator() + filename;
66@@ -269,8 +297,9 @@
67 FileSystem::fs_filename(filename.c_str()))
68 .str();
69 UI::WLMessageBox mbox(this, _("Error Saving Map!"), s, UI::WLMessageBox::MBoxType::kOkCancel);
70- if (mbox.run<UI::Panel::Returncodes>() == UI::Panel::Returncodes::kBack)
71+ if (mbox.run<UI::Panel::Returncodes>() == UI::Panel::Returncodes::kBack) {
72 return false;
73+ }
74 }
75
76 // Try deleting file (if it exists). If it fails, give a message and let the player choose a new
77
78=== modified file 'src/editor/ui_menus/main_menu_save_map_make_directory.cc'
79--- src/editor/ui_menus/main_menu_save_map_make_directory.cc 2018-07-08 13:53:45 +0000
80+++ src/editor/ui_menus/main_menu_save_map_make_directory.cc 2018-11-19 20:04:35 +0000
81@@ -19,9 +19,12 @@
82
83 #include "editor/ui_menus/main_menu_save_map_make_directory.h"
84
85+#include <boost/algorithm/string.hpp>
86+
87 #include "base/i18n.h"
88 #include "graphic/font_handler.h"
89 #include "io/filesystem/layered_filesystem.h"
90+#include "logic/filesystem_constants.h"
91
92 MainMenuSaveMapMakeDirectory::MainMenuSaveMapMakeDirectory(UI::Panel* const parent,
93 char const* dirname)
94@@ -63,15 +66,23 @@
95 vbox_.set_size(get_inner_w() - 2 * padding_, get_inner_h() - 3 * padding_ - buth_);
96
97 edit_.set_text(dirname_);
98- edit_.changed.connect(boost::bind(&MainMenuSaveMapMakeDirectory::edit_changed, this));
99+ edit_.changed.connect(
100+ boost::bind(&MainMenuSaveMapMakeDirectory::edit_changed, this));
101+ edit_.ok.connect(
102+ boost::bind(&MainMenuSaveMapMakeDirectory::clicked_ok, this));
103+ edit_.cancel.connect(
104+ boost::bind(&MainMenuSaveMapMakeDirectory::end_modal<UI::Panel::Returncodes>,
105+ boost::ref(*this), UI::Panel::Returncodes::kBack));
106 ok_button_.sigclicked.connect(
107- boost::bind(&MainMenuSaveMapMakeDirectory::end_modal<UI::Panel::Returncodes>,
108- boost::ref(*this), UI::Panel::Returncodes::kOk));
109+ boost::bind(&MainMenuSaveMapMakeDirectory::clicked_ok, this));
110 ok_button_.set_enabled(!dirname_.empty());
111 cancel_button_.sigclicked.connect(
112 boost::bind(&MainMenuSaveMapMakeDirectory::end_modal<UI::Panel::Returncodes>,
113 boost::ref(*this), UI::Panel::Returncodes::kBack));
114 center_to_parent();
115+}
116+
117+void MainMenuSaveMapMakeDirectory::start() {
118 edit_.focus();
119 }
120
121@@ -82,7 +93,25 @@
122 const std::string& text = edit_.text();
123 // Prevent the user from creating nonsense directory names, like e.g. ".." or "...".
124 const bool is_legal_filename = FileSystem::is_legal_filename(text);
125- ok_button_.set_enabled(is_legal_filename);
126- edit_.set_tooltip(is_legal_filename ? "" : illegal_filename_tooltip_);
127+ // Prevent the user from creating directory names that the game would
128+ // try to interpret as maps
129+ const bool has_map_extension =
130+ boost::iends_with(text, kWidelandsMapExtension) ||
131+ boost::iends_with(text, kS2MapExtension1) ||
132+ boost::iends_with(text, kS2MapExtension2);
133+ ok_button_.set_enabled(is_legal_filename && !has_map_extension);
134+ edit_.set_tooltip(is_legal_filename ?
135+ (has_map_extension ? _("This extension is reserved!") : "" ) :
136+ illegal_filename_tooltip_);
137 dirname_ = text;
138 }
139+
140+/**
141+ * Clicked OK button oder pressed Enter in edit box
142+ */
143+void MainMenuSaveMapMakeDirectory::clicked_ok() {
144+ if (!ok_button_.enabled()) {
145+ return;
146+ }
147+ end_modal<UI::Panel::Returncodes>(UI::Panel::Returncodes::kOk);
148+}
149
150=== modified file 'src/editor/ui_menus/main_menu_save_map_make_directory.h'
151--- src/editor/ui_menus/main_menu_save_map_make_directory.h 2018-05-18 05:45:50 +0000
152+++ src/editor/ui_menus/main_menu_save_map_make_directory.h 2018-11-19 20:04:35 +0000
153@@ -37,6 +37,8 @@
154 struct MainMenuSaveMapMakeDirectory : public UI::Window {
155 MainMenuSaveMapMakeDirectory(UI::Panel*, char const*);
156
157+ void start() override;
158+
159 char const* get_dirname() {
160 return dirname_.c_str();
161 }
162@@ -52,6 +54,7 @@
163 UI::Button cancel_button_;
164 const std::string illegal_filename_tooltip_;
165 void edit_changed();
166+ void clicked_ok();
167 };
168
169 #endif // end of include guard: WL_EDITOR_UI_MENUS_MAIN_MENU_SAVE_MAP_MAKE_DIRECTORY_H
170
171=== modified file 'src/logic/game.cc'
172--- src/logic/game.cc 2018-10-21 09:42:03 +0000
173+++ src/logic/game.cc 2018-11-19 20:04:35 +0000
174@@ -75,7 +75,13 @@
175 Game::SyncWrapper::~SyncWrapper() {
176 if (dump_ != nullptr) {
177 if (!syncstreamsave_)
178- g_fs->fs_unlink(dumpfname_);
179+ try {
180+ g_fs->fs_unlink(dumpfname_);
181+ } catch (const FileError& e) {
182+ // not really a problem if deletion fails, but we'll log it
183+ log("Deleting synchstream file %s failed: %s\n",
184+ dumpfname_.c_str(), e.what());
185+ }
186 }
187 }
188
189
190=== modified file 'src/logic/map.cc'
191--- src/logic/map.cc 2018-05-22 11:29:41 +0000
192+++ src/logic/map.cc 2018-11-19 20:04:35 +0000
193@@ -32,6 +32,7 @@
194 #include "build_info.h"
195 #include "economy/flag.h"
196 #include "economy/road.h"
197+#include "io/filesystem/filesystem_exceptions.h"
198 #include "io/filesystem/layered_filesystem.h"
199 #include "logic/filesystem_constants.h"
200 #include "logic/findimmovable.h"
201@@ -1566,16 +1567,19 @@
202 std::string lower_filename = filename;
203 boost::algorithm::to_lower(lower_filename);
204
205- if (boost::algorithm::ends_with(lower_filename, kWidelandsMapExtension)) {
206- try {
207- result.reset(new WidelandsMapLoader(g_fs->make_sub_file_system(filename), this));
208- } catch (...) {
209- // If this fails, it is an illegal file.
210- // TODO(unknown): catchall hides real errors! Replace with more specific code
211+ try {
212+ if (boost::algorithm::ends_with(lower_filename, kWidelandsMapExtension)) {
213+ result.reset(
214+ new WidelandsMapLoader(g_fs->make_sub_file_system(filename), this));
215+ } else if (boost::algorithm::ends_with(lower_filename, kS2MapExtension1) ||
216+ boost::algorithm::ends_with(lower_filename, kS2MapExtension2)) {
217+ result.reset(new S2MapLoader(filename, *this));
218 }
219- } else if (boost::algorithm::ends_with(lower_filename, kS2MapExtension1) ||
220- boost::algorithm::ends_with(lower_filename, kS2MapExtension2)) {
221- result.reset(new S2MapLoader(filename, *this));
222+ } catch (const FileError& e) {
223+ // file might not have existed
224+ log("Map::get_correct_loader: File error: %s\n", e.what());
225+ } catch (std::exception& e) {
226+ log("Map::get_correct_loader: Unknown error: %s\n", e.what());
227 }
228 return result;
229 }
230
231=== modified file 'src/network/gameclient.cc'
232--- src/network/gameclient.cc 2018-11-06 17:05:10 +0000
233+++ src/network/gameclient.cc 2018-11-19 20:04:35 +0000
234@@ -25,6 +25,7 @@
235 #include <boost/format.hpp>
236
237 #include "base/i18n.h"
238+#include "base/log.h"
239 #include "base/warning.h"
240 #include "base/wexception.h"
241 #include "build_info.h"
242@@ -32,6 +33,7 @@
243 #include "game_io/game_loader.h"
244 #include "helper.h"
245 #include "io/fileread.h"
246+#include "io/filesystem/filesystem_exceptions.h"
247 #include "io/filewrite.h"
248 #include "logic/filesystem_constants.h"
249 #include "logic/game.h"
250@@ -618,7 +620,14 @@
251 }
252 }
253 // Don't overwrite the file, better rename the original one
254- g_fs->fs_rename(path, backup_file_name(path));
255+ try {
256+ g_fs->fs_rename(path, backup_file_name(path));
257+ } catch (const FileError& e) {
258+ log("file error in GameClient::handle_packet: case NETCMD_FILE_PART: "
259+ "%s\n", e.what());
260+ // TODO(Arty): What now? It just means the next step will fail
261+ // or possibly result in some corrupt file
262+ }
263 }
264
265 // Yes we need the file!
266@@ -707,7 +716,12 @@
267 s.unsigned_8(NETCMD_CHAT);
268 s.string(_("/me 's file failed md5 checksumming."));
269 d->net->send(s);
270- g_fs->fs_unlink(file_->filename);
271+ try {
272+ g_fs->fs_unlink(file_->filename);
273+ } catch (const FileError& e) {
274+ log("file error in GameClient::handle_packet: case NETCMD_FILE_PART: "
275+ "%s\n", e.what());
276+ }
277 }
278 // Check file for validity
279 bool invalid = false;
280@@ -727,10 +741,15 @@
281 invalid = true;
282 }
283 if (invalid) {
284- g_fs->fs_unlink(file_->filename);
285- // Restore original file, if there was one before
286- if (g_fs->file_exists(backup_file_name(file_->filename)))
287- g_fs->fs_rename(backup_file_name(file_->filename), file_->filename);
288+ try {
289+ g_fs->fs_unlink(file_->filename);
290+ // Restore original file, if there was one before
291+ if (g_fs->file_exists(backup_file_name(file_->filename)))
292+ g_fs->fs_rename(backup_file_name(file_->filename), file_->filename);
293+ } catch (const FileError& e) {
294+ log("file error in GameClient::handle_packet: case NETCMD_FILE_PART: "
295+ "%s\n", e.what());
296+ }
297 s.reset();
298 s.unsigned_8(NETCMD_CHAT);
299 s.string(_("/me checked the received file. Although md5 check summing succeeded, "
300
301=== modified file 'src/ui_fsmenu/loadgame.cc'
302--- src/ui_fsmenu/loadgame.cc 2018-06-08 16:36:29 +0000
303+++ src/ui_fsmenu/loadgame.cc 2018-11-19 20:04:35 +0000
304@@ -158,7 +158,8 @@
305 }
306
307 void FullscreenMenuLoadGame::fill_table() {
308- load_or_save_.fill_table(showing_filenames_);
309+ load_or_save_.set_show_filenames(showing_filenames_);
310+ load_or_save_.fill_table();
311 }
312
313 const std::string& FullscreenMenuLoadGame::filename() const {
314
315=== modified file 'src/wui/load_or_save_game.cc'
316--- src/wui/load_or_save_game.cc 2018-10-26 07:09:29 +0000
317+++ src/wui/load_or_save_game.cc 2018-11-19 20:04:35 +0000
318@@ -32,6 +32,7 @@
319 #include "game_io/game_preload_packet.h"
320 #include "graphic/font_handler.h"
321 #include "helper.h"
322+#include "io/filesystem/filesystem_exceptions.h"
323 #include "io/filesystem/layered_filesystem.h"
324 #include "logic/filesystem_constants.h"
325 #include "logic/game_controller.h"
326@@ -74,6 +75,7 @@
327 table_box_(new UI::Box(parent, 0, 0, UI::Box::Vertical)),
328 table_(table_box_, 0, 0, 0, 0, style, UI::TableRows::kMultiDescending),
329 filetype_(filetype),
330+ show_filenames_(false),
331 localize_autosave_(localize_autosave),
332 // Savegame description
333 game_details_(
334@@ -136,8 +138,13 @@
335 }
336
337 const std::string LoadOrSaveGame::filename_list_string() const {
338+ return filename_list_string(table_.selections());
339+}
340+
341+const std::string
342+LoadOrSaveGame::filename_list_string(const std::set<uint32_t>& selections) const {
343 boost::format message;
344- for (const uint32_t index : table_.selections()) {
345+ for (const uint32_t index : selections) {
346 const SavegameData& gamedata = games_data_[table_.get(table_.get_record(index))];
347
348 if (gamedata.errormessage.empty()) {
349@@ -263,18 +270,75 @@
350
351 UI::WLMessageBox confirmationBox(
352 parent_->get_parent()->get_parent(),
353- ngettext("Confirm Deleting File", "Confirm Deleting Files", no_selections), message,
354+ no_selections == 1 ? _("Confirm Deleting File") : _("Confirm Deleting Files"),
355+ message,
356 UI::WLMessageBox::MBoxType::kOkCancel);
357 do_delete = confirmationBox.run<UI::Panel::Returncodes>() == UI::Panel::Returncodes::kOk;
358 table_.focus();
359 }
360 if (do_delete) {
361+ // Failed deletions aren't a serious problem, we just catch the errors
362+ // and keep track to notify the player.
363+ std::set<uint32_t> failed_selections;
364+ bool failed;
365 for (const uint32_t index : selections) {
366+ failed = false;
367 const std::string& deleteme = get_filename(index);
368- g_fs->fs_unlink(deleteme);
369- if (filetype_ == FileType::kReplay) {
370- g_fs->fs_unlink(deleteme + kSavegameExtension);
371- }
372+ try {
373+ g_fs->fs_unlink(deleteme);
374+ } catch (const FileError& e) {
375+ log("player-requested file deletion failed: %s", e.what());
376+ failed = true;
377+ }
378+ if (filetype_ == FileType::kReplay) {
379+ try {
380+ g_fs->fs_unlink(deleteme + kSavegameExtension);
381+ // If at least one of the two relevant files of a replay are
382+ // successfully deleted then count it as success.
383+ // (From the player perspective the replay is gone.)
384+ failed = false;
385+ // If it was a multiplayer replay, also delete the synchstream. Do
386+ // it here, so it's only attempted if replay deletion was successful.
387+ if (g_fs->file_exists(deleteme + kSyncstreamExtension)) {
388+ g_fs->fs_unlink(deleteme + kSyncstreamExtension);
389+ }
390+ } catch (const FileError& e) {
391+ log("player-requested file deletion failed: %s", e.what());
392+ }
393+ }
394+ if (failed) {
395+ failed_selections.insert(index);
396+ }
397+ }
398+ if (!failed_selections.empty()) {
399+ const uint32_t no_failed = failed_selections.size();
400+ // Notify the player.
401+ const std::string caption = (no_failed == 1) ?
402+ _("Error Deleting File!") : _("Error Deleting Files!");
403+ if (filetype_ == FileType::kReplay) {
404+ if (selections.size() == 1) {
405+ header = _("The replay could not be deleted.");
406+ } else {
407+ header =
408+ (boost::format(ngettext("%d replay could not be deleted.",
409+ "%d replays could not be deleted.", no_failed)) % no_failed).str();
410+ }
411+ } else {
412+ if (selections.size() == 1) {
413+ header = _("The game could not be deleted.");
414+ } else {
415+ header =
416+ (boost::format(ngettext("%d game could not be deleted.",
417+ "%d games could not be deleted.", no_failed)) % no_failed).str();
418+ }
419+ }
420+ std::string message = (boost::format("%s\n%s") % header
421+ % filename_list_string(failed_selections)).str();
422+ UI::WLMessageBox msgBox(
423+ parent_->get_parent()->get_parent(),
424+ caption, message,
425+ UI::WLMessageBox::MBoxType::kOk);
426+ msgBox.run<UI::Panel::Returncodes>();
427 }
428 fill_table();
429
430@@ -297,7 +361,7 @@
431 return delete_;
432 }
433
434-void LoadOrSaveGame::fill_table(bool show_filenames) {
435+void LoadOrSaveGame::fill_table() {
436
437 clear_selections();
438 table_.clear();
439@@ -309,8 +373,9 @@
440 return boost::ends_with(fn, kReplayExtension);
441 });
442 // Update description column title for replays
443- table_.set_column_tooltip(2, show_filenames ? _("Filename: Map name (start of replay)") :
444- _("Map name (start of replay)"));
445+ table_.set_column_tooltip(2, show_filenames_ ?
446+ _("Filename: Map name (start of replay)") :
447+ _("Map name (start of replay)"));
448 } else {
449 gamefiles = g_fs->list_directory(kSaveDir);
450 }
451@@ -459,7 +524,7 @@
452 te.set_string(1, gametypestring);
453 if (filetype_ == FileType::kReplay) {
454 const std::string map_basename =
455- show_filenames ?
456+ show_filenames_ ?
457 map_filename(gamedata.filename, gamedata.mapname, localize_autosave_) :
458 gamedata.mapname;
459 te.set_string(2, (boost::format(pgettext("mapname_gametime", "%1% (%2%)")) %
460@@ -472,7 +537,7 @@
461 } else {
462 te.set_string(1, map_filename(gamedata.filename, gamedata.mapname, localize_autosave_));
463 }
464- } catch (const WException& e) {
465+ } catch (const std::exception& e) {
466 std::string errormessage = e.what();
467 boost::replace_all(errormessage, "\n", "<br>");
468 gamedata.errormessage =
469@@ -501,3 +566,10 @@
470 table_.sort();
471 table_.focus();
472 }
473+
474+void LoadOrSaveGame::set_show_filenames(bool show_filenames) {
475+ if (filetype_ != FileType::kReplay) {
476+ return;
477+ }
478+ show_filenames_ = show_filenames;
479+}
480
481=== modified file 'src/wui/load_or_save_game.h'
482--- src/wui/load_or_save_game.h 2018-10-25 04:00:51 +0000
483+++ src/wui/load_or_save_game.h 2018-11-19 20:04:35 +0000
484@@ -57,7 +57,10 @@
485 void select_by_name(const std::string& name);
486
487 /// Read savegame/replay files and fill the table and games data.
488- void fill_table(bool show_filenames = false);
489+ void fill_table();
490+
491+ /// Set whether to show filenames. Has only an effect for Replays.
492+ void set_show_filenames(bool);
493
494 /// The table panel
495 UI::Table<uintptr_t const>& table();
496@@ -80,6 +83,8 @@
497
498 /// Formats the current table selection as a list of filenames with savedate information.
499 const std::string filename_list_string() const;
500+ /// Formats a given table selection as a list of filenames with savedate information.
501+ const std::string filename_list_string(const std::set<uint32_t>& selections) const;
502
503 /// Reverse default sort order for save date column
504 bool compare_date_descending(uint32_t, uint32_t) const;
505@@ -88,6 +93,7 @@
506 UI::Box* table_box_;
507 UI::Table<uintptr_t const> table_;
508 FileType filetype_;
509+ bool show_filenames_;
510 bool localize_autosave_;
511 std::vector<SavegameData> games_data_;
512 GameDetails game_details_;

Subscribers

People subscribed via source and target branches

to status/vote changes: