Merge lp:~widelands-dev/widelands/bug-1610507-disallowed-filename-characters into lp:widelands

Proposed by GunChleoc
Status: Merged
Merged at revision: 8722
Proposed branch: lp:~widelands-dev/widelands/bug-1610507-disallowed-filename-characters
Merge into: lp:widelands
Diff against target: 473 lines (+148/-56)
16 files modified
src/editor/ui_menus/main_menu_save_map.cc (+5/-2)
src/editor/ui_menus/main_menu_save_map.h (+1/-0)
src/editor/ui_menus/main_menu_save_map_make_directory.cc (+6/-3)
src/editor/ui_menus/main_menu_save_map_make_directory.h (+1/-0)
src/graphic/text_layout.cc (+7/-0)
src/graphic/text_layout.h (+4/-0)
src/io/CMakeLists.txt (+0/-21)
src/io/filesystem/CMakeLists.txt (+23/-0)
src/io/filesystem/filesystem.cc (+70/-0)
src/io/filesystem/filesystem.h (+4/-0)
src/io/filesystem/layered_filesystem.cc (+0/-16)
src/io/filesystem/layered_filesystem.h (+1/-2)
src/ui_basic/panel.cc (+20/-9)
src/ui_basic/panel.h (+1/-1)
src/wui/game_main_menu_save_game.cc (+4/-2)
src/wui/game_main_menu_save_game.h (+1/-0)
To merge this branch: bzr merge lp:~widelands-dev/widelands/bug-1610507-disallowed-filename-characters
Reviewer Review Type Date Requested Status
Notabilis Approve
Review via email: mp+346442@code.launchpad.net

Commit message

Added tooltip to file save and make directory edit boxes if illegal filename is being entered. Also, some refactoring:

- New function as_listitem in text layout
- Fixed tooltip drawing for modal panels
- Some code style tweaks to Panel
- Filename checks now live directly in Filesystem

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

Continuous integration builds have changed state:

Travis build 3534. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/382014340.
Appveyor build 3338. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_bug_1610507_disallowed_filename_characters-3338.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 3540. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/382500797.
Appveyor build 3344. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_bug_1610507_disallowed_filename_characters-3344.

Revision history for this message
Notabilis (notabilis27) wrote :

Nice change, I like the tooltip! Works as intended and code is looking good.

Some remarks:
- Maybe add the tooltip to the OK button as well? At least for me that would be the more likely place where I would notice it.
- Is the smaller font for the list in the tooltip intentional?
- Maybe calculate the tooltip text only once and store it as static string in the file system class?

Not really related: I noticed that in the editor->make directory dialog the OK button is initially disabled, even though it should (?) be enabled. This is done intentionally by calling set_enabled(false) when creating the dialog, even though the directory name is valid and can be used. Is there a reason for disabling it?

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

It is disabled because the text is always empty at the start, and empty names aren't allowed.

I like your suggestions and we should implement them before merging this.

The smaller text in the list is intentional - I have another branch for the editor info tool that uses the new listitem function too, and I wanted a consistent look.

Revision history for this message
Notabilis (notabilis27) wrote :

In the case of the "make directory" dialog the default value of the edit box is "unnamed". This is a valid name (changing and changing it back is accepted) but the OK button is initially disabled anyway.

Revision history for this message
GunChleoc (gunchleoc) wrote :

OK, that's a bug then.

Revision history for this message
GunChleoc (gunchleoc) wrote :

OK button is fixed. Thanks for the review and testing :)

@bunnybot merge

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-05-22 06:54:12 +0000
3+++ src/editor/ui_menus/main_menu_save_map.cc 2018-05-31 17:12:19 +0000
4@@ -68,7 +68,8 @@
5 UI::ButtonStyle::kWuiPrimary,
6 _("Map Options")),
7 editbox_label_(
8- this, padding_, tabley_ + tableh_ + 3 * padding_, butw_, buth_, _("Filename:")) {
9+ this, padding_, tabley_ + tableh_ + 3 * padding_, butw_, buth_, _("Filename:")),
10+ illegal_filename_tooltip_(FileSystem::illegal_filename_tooltip()) {
11 set_current_directory(curdir_);
12
13 // Make room for edit_options_ button
14@@ -220,7 +221,9 @@
15 */
16 void MainMenuSaveMap::edit_box_changed() {
17 // Prevent the user from creating nonsense file names, like e.g. ".." or "...".
18- ok_.set_enabled(LayeredFileSystem::is_legal_filename(editbox_->text()));
19+ const bool is_legal_filename = FileSystem::is_legal_filename(editbox_->text());
20+ ok_.set_enabled(is_legal_filename);
21+ editbox_->set_tooltip(is_legal_filename ? "" : illegal_filename_tooltip_);
22 }
23
24 void MainMenuSaveMap::reset_editbox_or_die(const std::string& current_filename) {
25
26=== modified file 'src/editor/ui_menus/main_menu_save_map.h'
27--- src/editor/ui_menus/main_menu_save_map.h 2018-05-10 15:57:43 +0000
28+++ src/editor/ui_menus/main_menu_save_map.h 2018-05-31 17:12:19 +0000
29@@ -55,6 +55,7 @@
30
31 UI::Textarea editbox_label_;
32 UI::EditBox* editbox_;
33+ const std::string illegal_filename_tooltip_;
34 };
35
36 #endif // end of include guard: WL_EDITOR_UI_MENUS_MAIN_MENU_SAVE_MAP_H
37
38=== modified file 'src/editor/ui_menus/main_menu_save_map_make_directory.cc'
39--- src/editor/ui_menus/main_menu_save_map_make_directory.cc 2018-04-27 06:11:05 +0000
40+++ src/editor/ui_menus/main_menu_save_map_make_directory.cc 2018-05-31 17:12:19 +0000
41@@ -54,7 +54,8 @@
42 butw_,
43 buth_,
44 UI::ButtonStyle::kWuiSecondary,
45- _("Cancel")) {
46+ _("Cancel")),
47+ illegal_filename_tooltip_(FileSystem::illegal_filename_tooltip()) {
48
49 vbox_.add(&label_);
50 vbox_.add_space(padding_);
51@@ -66,7 +67,7 @@
52 ok_button_.sigclicked.connect(
53 boost::bind(&MainMenuSaveMapMakeDirectory::end_modal<UI::Panel::Returncodes>,
54 boost::ref(*this), UI::Panel::Returncodes::kOk));
55- ok_button_.set_enabled(false);
56+ ok_button_.set_enabled(!dirname_.empty());
57 cancel_button_.sigclicked.connect(
58 boost::bind(&MainMenuSaveMapMakeDirectory::end_modal<UI::Panel::Returncodes>,
59 boost::ref(*this), UI::Panel::Returncodes::kBack));
60@@ -80,6 +81,8 @@
61 void MainMenuSaveMapMakeDirectory::edit_changed() {
62 const std::string& text = edit_.text();
63 // Prevent the user from creating nonsense directory names, like e.g. ".." or "...".
64- ok_button_.set_enabled(LayeredFileSystem::is_legal_filename(text));
65+ const bool is_legal_filename = FileSystem::is_legal_filename(text);
66+ ok_button_.set_enabled(is_legal_filename);
67+ edit_.set_tooltip(is_legal_filename ? "" : illegal_filename_tooltip_);
68 dirname_ = text;
69 }
70
71=== modified file 'src/editor/ui_menus/main_menu_save_map_make_directory.h'
72--- src/editor/ui_menus/main_menu_save_map_make_directory.h 2018-04-07 16:59:00 +0000
73+++ src/editor/ui_menus/main_menu_save_map_make_directory.h 2018-05-31 17:12:19 +0000
74@@ -50,6 +50,7 @@
75 UI::EditBox edit_;
76 UI::Button ok_button_;
77 UI::Button cancel_button_;
78+ const std::string illegal_filename_tooltip_;
79 void edit_changed();
80 };
81
82
83=== modified file 'src/graphic/text_layout.cc'
84--- src/graphic/text_layout.cc 2018-05-05 17:10:37 +0000
85+++ src/graphic/text_layout.cc 2018-05-31 17:12:19 +0000
86@@ -141,6 +141,13 @@
87 return f.str();
88 }
89
90+/// Bullet list item
91+std::string as_listitem(const std::string& txt, int ptsize, const RGBColor& clr) {
92+ static boost::format f("<div width=100%%><div><p><font size=%d color=%s>•</font></p></div><div><p><space gap=6></p></div><div width=*><p><font size=%d color=%s>%s<vspace gap=6></font></p></div></div>");
93+ f % ptsize % clr.hex_value() % ptsize % clr.hex_value() % txt;
94+ return f.str();
95+}
96+
97 std::string as_richtext(const std::string& txt) {
98 static boost::format f("<rt>%s</rt>");
99 f % txt;
100
101=== modified file 'src/graphic/text_layout.h'
102--- src/graphic/text_layout.h 2018-04-07 16:59:00 +0000
103+++ src/graphic/text_layout.h 2018-05-31 17:12:19 +0000
104@@ -84,6 +84,10 @@
105 const RGBColor& clr = UI_FONT_CLR_FG,
106 UI::FontSet::Face face = UI::FontSet::Face::kSans);
107
108+std::string as_listitem(const std::string&,
109+ int ptsize = UI_FONT_SIZE_SMALL,
110+ const RGBColor& clr = UI_FONT_CLR_FG);
111+
112 std::string as_richtext(const std::string&);
113 std::string as_tooltip(const std::string&);
114 std::string as_waresinfo(const std::string&);
115
116=== modified file 'src/io/CMakeLists.txt'
117--- src/io/CMakeLists.txt 2016-02-06 11:11:24 +0000
118+++ src/io/CMakeLists.txt 2018-05-31 17:12:19 +0000
119@@ -23,24 +23,3 @@
120 io_filesystem
121 io_stream
122 )
123-
124-wl_library(io_filesystem
125- SRCS
126- filesystem/disk_filesystem.cc
127- filesystem/disk_filesystem.h
128- filesystem/filesystem.cc
129- filesystem/filesystem.h
130- filesystem/filesystem_exceptions.h
131- filesystem/layered_filesystem.cc
132- filesystem/layered_filesystem.h
133- filesystem/zip_exceptions.h
134- filesystem/zip_filesystem.cc
135- filesystem/zip_filesystem.h
136- DEPENDS
137- base_exceptions
138- base_log
139- base_macros
140- io_fileread
141- io_stream
142- third_party_minizip
143-)
144
145=== modified file 'src/io/filesystem/CMakeLists.txt'
146--- src/io/filesystem/CMakeLists.txt 2012-02-15 21:25:34 +0000
147+++ src/io/filesystem/CMakeLists.txt 2018-05-31 17:12:19 +0000
148@@ -1,1 +1,24 @@
149 add_subdirectory(test)
150+
151+wl_library(io_filesystem
152+ SRCS
153+ disk_filesystem.cc
154+ disk_filesystem.h
155+ filesystem.cc
156+ filesystem.h
157+ filesystem_exceptions.h
158+ layered_filesystem.cc
159+ layered_filesystem.h
160+ zip_exceptions.h
161+ zip_filesystem.cc
162+ zip_filesystem.h
163+ DEPENDS
164+ base_exceptions
165+ base_i18n
166+ base_log
167+ base_macros
168+ graphic_text_layout
169+ io_fileread
170+ io_stream
171+ third_party_minizip
172+)
173
174=== modified file 'src/io/filesystem/filesystem.cc'
175--- src/io/filesystem/filesystem.cc 2018-05-12 04:17:53 +0000
176+++ src/io/filesystem/filesystem.cc 2018-05-31 17:12:19 +0000
177@@ -32,6 +32,9 @@
178 #include <string>
179 #include <vector>
180
181+// We have to add Boost to this block to make codecheck happy
182+#include <boost/algorithm/string/predicate.hpp>
183+#include <boost/format.hpp>
184 #ifdef _WIN32
185 #include <direct.h>
186 #include <io.h>
187@@ -43,8 +46,10 @@
188 #include <sys/stat.h>
189 #include <unistd.h>
190
191+#include "base/i18n.h"
192 #include "base/log.h"
193 #include "config.h"
194+#include "graphic/text_layout.h"
195 #include "io/filesystem/disk_filesystem.h"
196 #include "io/filesystem/layered_filesystem.h"
197 #include "io/filesystem/zip_exceptions.h"
198@@ -60,6 +65,34 @@
199 #define PATH_MAX MAX_PATH
200 #endif
201
202+
203+namespace {
204+// Characters that are allowed in filenames, but not at the beginning
205+const std::vector<std::string> illegal_filename_starting_characters{
206+ ".",
207+ "-",
208+ " ", // Keep the blank last
209+};
210+
211+// Characters that are disallowed anywhere in a filename
212+// No potential file separators or other potentially illegal characters
213+// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
214+// http://www.linfo.org/file_name.html
215+// https://support.apple.com/en-us/HT202808
216+// We can't just regex for word & digit characters here because of non-Latin scripts.
217+const std::vector<std::string> illegal_filename_characters{
218+ "<",
219+ ">",
220+ ":",
221+ "\"",
222+ "|",
223+ "?",
224+ "*",
225+ "/",
226+ "\\",
227+};
228+} // namespace
229+
230 /**
231 * \param path A file or directory name
232 * \return True if ref path is absolute and within this FileSystem, false otherwise
233@@ -148,6 +181,43 @@
234 #endif
235 }
236
237+bool FileSystem::is_legal_filename(const std::string& filename) {
238+ if (filename.empty()) {
239+ return false;
240+ }
241+ for (const std::string& illegal_start : illegal_filename_starting_characters) {
242+ if (boost::starts_with(filename, illegal_start)) {
243+ return false;
244+ }
245+ }
246+ for (const std::string& illegal_char : illegal_filename_characters) {
247+ if (boost::contains(filename, illegal_char)) {
248+ return false;
249+ }
250+ }
251+ return true;
252+}
253+
254+std::string FileSystem::illegal_filename_tooltip() {
255+ std::vector<std::string> starting_characters;
256+ for (const std::string& character : illegal_filename_starting_characters) {
257+ if (character == " ") {
258+ /** TRANSLATORS: Part of tooltip entry for characters in illegal filenames. replaces tha blank space in a list of illegal characters */
259+ starting_characters.push_back(pgettext("illegal_filename_characters", "blank space"));
260+ } else {
261+ starting_characters.push_back(character);
262+ }
263+ }
264+ /** TRANSLATORS: Tooltip entry for characters in illegal filenames. %s is a list of illegal characters */
265+ const std::string illegal_start(as_listitem((boost::format(pgettext("illegal_filename_characters", "%s at the start of the filename")) % richtext_escape(i18n::localize_list(starting_characters, i18n::ConcatenateWith::OR))).str(), UI_FONT_SIZE_MESSAGE));
266+
267+ /** TRANSLATORS: Tooltip entry for characters in illegal filenames. %s is a list of illegal characters */
268+ const std::string illegal(as_listitem((boost::format(pgettext("illegal_filename_characters", "%s anywhere in the filename")) % richtext_escape(i18n::localize_list(illegal_filename_characters, i18n::ConcatenateWith::OR))).str(), UI_FONT_SIZE_MESSAGE));
269+
270+ /** TRANSLATORS: Tooltip header for characters in illegal filenames. This is followed by a list of bullet points */
271+ return (boost::format("%s%s%s") % pgettext("illegal_filename_characters", "The following characters are not allowed:") % illegal_start % illegal).str();
272+}
273+
274 // TODO(unknown): Write homedir detection for non-getenv-systems
275 std::string FileSystem::get_homedir() {
276 std::string homedir;
277
278=== modified file 'src/io/filesystem/filesystem.h'
279--- src/io/filesystem/filesystem.h 2018-04-07 16:59:00 +0000
280+++ src/io/filesystem/filesystem.h 2018-05-31 17:12:19 +0000
281@@ -108,6 +108,10 @@
282 std::string canonicalize_name(std::string path) const;
283 bool is_path_absolute(const std::string& path) const;
284
285+ /// Returns true if the filename is legal in all operating systems
286+ static bool is_legal_filename(const std::string& filename);
287+ static std::string illegal_filename_tooltip();
288+
289 // Returns the path separator, i.e. \ on windows and / everywhere else.
290 static char file_separator();
291
292
293=== modified file 'src/io/filesystem/layered_filesystem.cc'
294--- src/io/filesystem/layered_filesystem.cc 2018-04-07 16:59:00 +0000
295+++ src/io/filesystem/layered_filesystem.cc 2018-05-31 17:12:19 +0000
296@@ -22,16 +22,12 @@
297 #include <cstdio>
298 #include <memory>
299
300-#include <boost/algorithm/string/predicate.hpp>
301-#include <boost/regex.hpp>
302-
303 #include "base/log.h"
304 #include "base/wexception.h"
305 #include "io/fileread.h"
306 #include "io/streamread.h"
307
308 LayeredFileSystem* g_fs;
309-
310 LayeredFileSystem::LayeredFileSystem() : home_(nullptr) {
311 }
312
313@@ -41,18 +37,6 @@
314 LayeredFileSystem::~LayeredFileSystem() {
315 }
316
317-bool LayeredFileSystem::is_legal_filename(const std::string& filename) {
318- // No potential file separators or other potentially illegal characters
319- // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
320- // http://www.linfo.org/file_name.html
321- // https://support.apple.com/en-us/HT202808
322- // We can't just regex for word & digit characters here because of non-Latin scripts.
323- boost::regex re(".*[<>:\"|?*/\\\\].*");
324- return !filename.empty() && !boost::starts_with(filename, ".") &&
325- !boost::starts_with(filename, " ") && !boost::starts_with(filename, "-") &&
326- !boost::regex_match(filename, re);
327-}
328-
329 /**
330 * Just assume that at least one of our child FSs is writable
331 */
332
333=== modified file 'src/io/filesystem/layered_filesystem.h'
334--- src/io/filesystem/layered_filesystem.h 2018-04-07 16:59:00 +0000
335+++ src/io/filesystem/layered_filesystem.h 2018-05-31 17:12:19 +0000
336@@ -58,8 +58,7 @@
337
338 std::set<std::string> list_directory(const std::string& path) override;
339
340- /// Returns true if the filename is legal in all operating systems
341- static bool is_legal_filename(const std::string& filename);
342+
343 bool is_writable() const override;
344 bool file_exists(const std::string& path) override;
345 bool is_directory(const std::string& path) override;
346
347=== modified file 'src/ui_basic/panel.cc'
348--- src/ui_basic/panel.cc 2018-05-14 06:59:39 +0000
349+++ src/ui_basic/panel.cc 2018-05-31 17:12:19 +0000
350@@ -143,8 +143,9 @@
351 app->set_mouse_lock(false); // more paranoia :-)
352
353 Panel* forefather = this;
354- while (Panel* const p = forefather->parent_)
355- forefather = p;
356+ while (forefather->parent_ != nullptr) {
357+ forefather = forefather->parent_;
358+ }
359
360 default_cursor_ = g_gr->images().get("images/ui_basic/cursor.png");
361 default_cursor_click_ = g_gr->images().get("images/ui_basic/cursor_click.png");
362@@ -175,13 +176,16 @@
363 app->handle_input(&input_callback);
364
365 if (start_time >= next_think_time) {
366- if (app->should_die())
367+ if (app->should_die()) {
368 end_modal<Returncodes>(Returncodes::kBack);
369+ }
370
371 do_think();
372
373- if (flags_ & pf_child_die)
374+ if (flags_ & pf_child_die) {
375 check_child_death();
376+ }
377+
378 next_think_time = start_time + kGameLogicDelay;
379 }
380
381@@ -190,7 +194,13 @@
382 forefather->do_draw(rt);
383 rt.blit((app->get_mouse_position() - Vector2i(3, 7)),
384 app->is_mouse_pressed() ? default_cursor_click_ : default_cursor_);
385- forefather->do_tooltip();
386+
387+ if (is_modal()) {
388+ do_tooltip();
389+ } else {
390+ forefather->do_tooltip();
391+ }
392+
393 g_gr->refresh();
394 next_draw_time = start_time + draw_delay;
395 }
396@@ -594,8 +604,7 @@
397 * false otherwise.
398 */
399 bool Panel::handle_tooltip() {
400- RenderTarget& rt = *g_gr->get_render_target();
401- return draw_tooltip(rt, tooltip());
402+ return draw_tooltip(tooltip());
403 }
404
405 /**
406@@ -1060,13 +1069,15 @@
407 /**
408 * Draw the tooltip. Return true on success
409 */
410-bool Panel::draw_tooltip(RenderTarget& dst, const std::string& text) {
411+bool Panel::draw_tooltip(const std::string& text) {
412 if (text.empty()) {
413 return false;
414 }
415+
416+ RenderTarget& dst = *g_gr->get_render_target();
417 std::string text_to_render = text;
418 if (!is_richtext(text_to_render)) {
419- text_to_render = as_tooltip(text);
420+ text_to_render = as_tooltip(text_to_render);
421 }
422
423 constexpr uint32_t kTipWidthMax = 360;
424
425=== modified file 'src/ui_basic/panel.h'
426--- src/ui_basic/panel.h 2018-04-27 06:11:05 +0000
427+++ src/ui_basic/panel.h 2018-05-31 17:12:19 +0000
428@@ -311,7 +311,7 @@
429 static void play_new_chat_member();
430 static void play_new_chat_message();
431
432- static bool draw_tooltip(RenderTarget&, const std::string& text);
433+ static bool draw_tooltip(const std::string& text);
434 void draw_background(RenderTarget& dst, const UI::PanelStyleInfo&);
435 void draw_background(RenderTarget& dst, Recti rect, const UI::PanelStyleInfo&);
436
437
438=== modified file 'src/wui/game_main_menu_save_game.cc'
439--- src/wui/game_main_menu_save_game.cc 2018-05-22 06:54:12 +0000
440+++ src/wui/game_main_menu_save_game.cc 2018-05-31 17:12:19 +0000
441@@ -65,7 +65,8 @@
442 cancel_(&buttons_box_, "cancel", 0, 0, 0, 0, UI::ButtonStyle::kWuiSecondary, _("Cancel")),
443 ok_(&buttons_box_, "ok", 0, 0, 0, 0, UI::ButtonStyle::kWuiPrimary, _("OK")),
444
445- curdir_(kSaveDir) {
446+ curdir_(kSaveDir),
447+ illegal_filename_tooltip_(FileSystem::illegal_filename_tooltip()) {
448
449 layout();
450
451@@ -133,8 +134,9 @@
452
453 void GameMainMenuSaveGame::edit_box_changed() {
454 // Prevent the user from creating nonsense directory names, like e.g. ".." or "...".
455- const bool is_legal_filename = LayeredFileSystem::is_legal_filename(filename_editbox_.text());
456+ const bool is_legal_filename = FileSystem::is_legal_filename(filename_editbox_.text());
457 ok_.set_enabled(is_legal_filename);
458+ filename_editbox_.set_tooltip(is_legal_filename ? "" : illegal_filename_tooltip_);
459 load_or_save_.delete_button()->set_enabled(false);
460 load_or_save_.clear_selections();
461 }
462
463=== modified file 'src/wui/game_main_menu_save_game.h'
464--- src/wui/game_main_menu_save_game.h 2018-05-10 15:57:43 +0000
465+++ src/wui/game_main_menu_save_game.h 2018-05-31 17:12:19 +0000
466@@ -79,6 +79,7 @@
467 UI::Button cancel_, ok_;
468
469 std::string curdir_;
470+ const std::string illegal_filename_tooltip_;
471 };
472
473 #endif // end of include guard: WL_WUI_GAME_MAIN_MENU_SAVE_GAME_H

Subscribers

People subscribed via source and target branches

to status/vote changes: