1 /* GG is a GUI for OpenGL.
2    Copyright (C) 2003-2008 T. Zachary Laine
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public License
6    as published by the Free Software Foundation; either version 2.1
7    of the License, or (at your option) any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public
15    License along with this library; if not, write to the Free
16    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17    02111-1307 USA
18 
19    If you do not wish to comply with the terms of the LGPL please
20    contact the author as other terms are available for a fee.
21 
22    Zach Laine
23    whatwasthataddress@gmail.com */
24 
25 #include <GG/dialogs/FileDlg.h>
26 
27 #include <GG/Button.h>
28 #include <GG/dialogs/ThreeButtonDlg.h>
29 #include <GG/DrawUtil.h>
30 #include <GG/DropDownList.h>
31 #include <GG/Edit.h>
32 #include <GG/GUI.h>
33 #include <GG/StyleFactory.h>
34 #include <GG/TextControl.h>
35 #include <GG/utf8/checked.h>
36 #include <GG/WndEvent.h>
37 
38 #include <boost/cast.hpp>
39 #include <boost/format.hpp>
40 #include <boost/algorithm/string/predicate.hpp>
41 #include <boost/filesystem/operations.hpp>
42 // boost::spirit::classic pulls in windows.h which in turn defines macro
43 // versions of min and max.  Defining NOMINMAX disables the creation of those
44 // macros
45 #define NOMINMAX
46 #include <boost/spirit/include/classic.hpp>
47 #include <boost/spirit/include/classic_dynamic.hpp>
48 #include <boost/system/system_error.hpp>
49 
50 
51 using namespace GG;
52 
53 namespace {
54     using namespace boost::spirit::classic;
55 
56     // these functors are used by the if_p, while_p, and for_p parsers in UpdateList()
57     struct LeadingWildcard
58     {
LeadingWildcard__anon075575d20111::LeadingWildcard59         LeadingWildcard(const std::string& str) : m_value(!str.empty() && *str.begin() == '*') {}
operator ()__anon075575d20111::LeadingWildcard60         bool operator()() const {return m_value;}
61         bool m_value;
62     };
63     struct TrailingWildcard
64     {
TrailingWildcard__anon075575d20111::TrailingWildcard65         TrailingWildcard(const std::string& str) : m_value(!str.empty() && *str.rbegin() == '*') {}
operator ()__anon075575d20111::TrailingWildcard66         bool operator()() const {return m_value;}
67         bool m_value;
68     };
69 
70     struct Index
71     {
Index__anon075575d20111::Index72         Index(int i = 0) : m_initial_value(i) {}
operator ()__anon075575d20111::Index73         void operator()() const {value = m_initial_value;}
74         int m_initial_value;
75         static int value;
76     };
77     int Index::value;
78     struct IndexLess
79     {
IndexLess__anon075575d20111::IndexLess80         IndexLess(int val) : m_value(val) {}
operator ()__anon075575d20111::IndexLess81         bool operator()() const {return Index::value <  m_value;}
82         int m_value;
83     };
84     struct IndexIncr
85     {
operator ()__anon075575d20111::IndexIncr86         void operator()() const {++Index::value;}
87     };
88 
89     struct FrontStringBegin
90     {
FrontStringBegin__anon075575d20111::FrontStringBegin91         FrontStringBegin(const std::shared_ptr<std::vector<std::string>>& strings) :
92             m_strings(strings)
93         {}
94 
operator ()__anon075575d20111::FrontStringBegin95         const char* operator()() const {return m_strings->front().c_str();}
96 
97         std::shared_ptr<std::vector<std::string>> m_strings;
98     };
99     struct FrontStringEnd
100     {
FrontStringEnd__anon075575d20111::FrontStringEnd101         FrontStringEnd(const std::shared_ptr<std::vector<std::string>>& strings) :
102             m_strings(strings)
103         {}
104 
operator ()__anon075575d20111::FrontStringEnd105         const char* operator()() const {return m_strings->front().c_str() + m_strings->front().size();}
106 
107         std::shared_ptr<std::vector<std::string>> m_strings;
108     };
109     struct IndexedStringBegin
110     {
IndexedStringBegin__anon075575d20111::IndexedStringBegin111         IndexedStringBegin(const std::shared_ptr<std::vector<std::string>>& strings) :
112             m_strings(strings)
113         {}
114 
operator ()__anon075575d20111::IndexedStringBegin115         const char* operator()() const {return (*m_strings)[Index::value].c_str();}
116 
117         std::shared_ptr<std::vector<std::string>> m_strings;
118     };
119     struct IndexedStringEnd
120     {
IndexedStringEnd__anon075575d20111::IndexedStringEnd121         IndexedStringEnd(const std::shared_ptr<std::vector<std::string>>& strings) :
122             m_strings(strings)
123         {}
124 
operator ()__anon075575d20111::IndexedStringEnd125         const char* operator()() const {return (*m_strings)[Index::value].c_str() + (*m_strings)[Index::value].size();}
126 
127         std::shared_ptr<std::vector<std::string>> m_strings;
128     };
129 
WindowsRoot(const std::string & root_name)130     bool WindowsRoot(const std::string& root_name)
131     { return root_name.size() == 2 && std::isalpha(root_name[0]) && root_name[1] == ':'; }
132 
Win32Paths()133     bool Win32Paths()
134     { return WindowsRoot(boost::filesystem::initial_path().root_name().string()); }
135 
136     const X H_SPACING(10);
137     const Y V_SPACING(10);
138 }
139 
140 namespace fs = boost::filesystem;
141 
142 // static member definition(s)
143 fs::path FileDlg::s_working_dir = fs::current_path();
144 const X FileDlg::DEFAULT_WIDTH(500);
145 const Y FileDlg::DEFAULT_HEIGHT(450);
146 
147 
FileDlg(const std::string & directory,const std::string & filename,bool save,bool multi,const std::shared_ptr<Font> & font,Clr color,Clr border_color,Clr text_color)148 FileDlg::FileDlg(const std::string& directory, const std::string& filename, bool save, bool multi,
149                  const std::shared_ptr<Font>& font, Clr color, Clr border_color, Clr text_color/* = CLR_BLACK*/) :
150     Wnd((GUI::GetGUI()->AppWidth() - DEFAULT_WIDTH) / 2,
151         (GUI::GetGUI()->AppHeight() - DEFAULT_HEIGHT) / 2,
152         DEFAULT_WIDTH, DEFAULT_HEIGHT, INTERACTIVE | DRAGABLE | MODAL),
153     m_color(color),
154     m_border_color(border_color),
155     m_text_color(text_color),
156     m_font(font),
157     m_save(save),
158     m_save_str(GetStyleFactory()->Translate("Save")),
159     m_open_str(GetStyleFactory()->Translate("Open")),
160     m_init_directory(directory),
161     m_init_filename(filename)
162 {
163     const auto& style = GetStyleFactory();
164 
165     if (m_save)
166         multi = false;
167 
168     // finally, we can create the listbox with the files in it, sized to fill the available space
169     m_files_list = style->NewListBox(m_border_color);
170     m_files_list->SetStyle(LIST_NOSORT | (multi ? LIST_NONE : LIST_SINGLESEL));
171 }
172 
CompleteConstruction()173 void FileDlg::CompleteConstruction()
174 {
175     const auto& style = GetStyleFactory();
176 
177     m_files_edit = style->NewEdit("", m_font, m_border_color, m_text_color);
178     m_filter_list = style->NewDropDownList(3, m_border_color);
179     m_filter_list->SetStyle(LIST_NOSORT);
180 
181     m_curr_dir_text = style->NewTextControl("", m_font, m_text_color, FORMAT_NOWRAP);
182     m_files_label = style->NewTextControl(style->Translate("File(s):"), m_font, m_text_color, FORMAT_RIGHT | FORMAT_VCENTER);
183     m_file_types_label = style->NewTextControl(style->Translate("Type(s):"), m_font, m_text_color, FORMAT_RIGHT | FORMAT_VCENTER);
184 
185     m_ok_button = style->NewButton(m_save ? m_save_str : m_open_str, m_font, m_color, m_text_color);
186     m_cancel_button = style->NewButton(style->Translate("Cancel"), m_font, m_color, m_text_color);
187 
188     DoLayout();
189 
190     AttachChild(m_files_edit);
191     AttachChild(m_filter_list);
192     AttachChild(m_ok_button);
193     AttachChild(m_cancel_button);
194     AttachChild(m_files_list);
195     AttachChild(m_curr_dir_text);
196     AttachChild(m_files_label);
197     AttachChild(m_file_types_label);
198 
199     if (!m_init_directory.empty()) {
200 #if defined(_WIN32)
201         // convert UTF-8 file name to UTF-16
202         boost::filesystem::path::string_type directory_native;
203         utf8::utf8to16(m_init_directory.begin(), m_init_directory.end(), std::back_inserter(directory_native));
204         fs::path dir_path = fs::system_complete(fs::path(directory_native));
205 #else
206         fs::path dir_path = fs::system_complete(fs::path(m_init_directory));
207 #endif
208         if (!fs::exists(dir_path))
209             throw BadInitialDirectory("FileDlg::FileDlg() : Initial directory \"" + dir_path.string() + "\" does not exist.");
210         SetWorkingDirectory(dir_path);
211     }
212 
213     UpdateDirectoryText();
214     PopulateFilters();
215     UpdateList();
216 
217 #if BOOST_VERSION >= 106000
218     using boost::placeholders::_1;
219     using boost::placeholders::_2;
220     using boost::placeholders::_3;
221 #endif
222 
223     m_ok_button->LeftClickedSignal.connect(boost::bind(&FileDlg::OkClicked, this));
224     m_cancel_button->LeftClickedSignal.connect(boost::bind(&FileDlg::CancelClicked, this));
225     m_files_list->SelRowsChangedSignal.connect(boost::bind(&FileDlg::FileSetChanged, this, _1));
226     m_files_list->DoubleClickedRowSignal.connect(boost::bind(&FileDlg::FileDoubleClicked, this, _1, _2, _3));
227     m_files_edit->EditedSignal.connect(boost::bind(&FileDlg::FilesEditChanged, this, _1));
228     m_filter_list->SelChangedSignal.connect(boost::bind(&FileDlg::FilterChanged, this, _1));
229 
230     if (!m_init_filename.empty()) {
231         fs::path filename_path = fs::system_complete(fs::path(m_init_filename));
232         m_files_edit->SetText(filename_path.leaf().string());
233     }
234 }
235 
Result() const236 std::set<std::string> FileDlg::Result() const
237 { return m_result; }
238 
SelectDirectories() const239 bool FileDlg::SelectDirectories() const
240 { return m_select_directories; }
241 
AppendMissingSaveExtension() const242 bool FileDlg::AppendMissingSaveExtension() const
243 { return m_append_missing_save_extension; }
244 
Render()245 void FileDlg::Render()
246 {
247     FlatRectangle(UpperLeft(), LowerRight(), m_color, m_border_color, 1);
248     try {
249         fs::directory_iterator test(s_working_dir);
250     } catch (const fs::filesystem_error&) {
251         // This ctor has been found to throw on Win32 when we attempt to iterate over a path into a drive that has just
252         // been disconnected (e.g. a USB thumb drive).  In this case, we will just cancel the dialog.
253         CancelClicked();
254     }
255 }
256 
KeyPress(Key key,std::uint32_t key_code_point,Flags<ModKey> mod_keys)257 void FileDlg::KeyPress(Key key, std::uint32_t key_code_point, Flags<ModKey> mod_keys)
258 {
259     if (key == GGK_RETURN || key == GGK_KP_ENTER)
260         OkHandler(false);
261     else if (key == GGK_ESCAPE)
262         CancelClicked();
263 }
264 
SelectDirectories(bool directories)265 void FileDlg::SelectDirectories(bool directories)
266 {
267     if (!m_save) {
268         bool refresh_list = directories != m_select_directories;
269         m_select_directories = directories;
270         if (refresh_list)
271             UpdateList();
272     }
273 }
274 
AppendMissingSaveExtension(bool append)275 void FileDlg::AppendMissingSaveExtension(bool append)
276 { m_append_missing_save_extension = append; }
277 
SetFileFilters(const std::vector<std::pair<std::string,std::string>> & filters)278 void FileDlg::SetFileFilters(const std::vector<std::pair<std::string, std::string>>& filters)
279 {
280     m_file_filters = filters;
281     PopulateFilters();
282     UpdateList();
283 }
284 
WorkingDirectory()285 const fs::path& FileDlg::WorkingDirectory()
286 { return s_working_dir; }
287 
StringToPath(const std::string & str)288 const boost::filesystem::path FileDlg::StringToPath(const std::string& str) {
289 #if defined(_WIN32)
290     // convert UTF-8 path string to UTF-16
291     fs::path::string_type str_native;
292     utf8::utf8to16(str.begin(), str.end(), std::back_inserter(str_native));
293     return fs::path(str_native);
294 #else
295     return fs::path(str);
296 #endif
297 }
298 
DoLayout()299 void FileDlg::DoLayout()
300 {
301     X button_width = Width() / 4 - H_SPACING;
302     Y button_height = m_font->Height() + 2 * 5;
303 
304     m_curr_dir_text->MoveTo(GG::Pt(H_SPACING, V_SPACING / 2));
305 
306     m_files_list->MoveTo(Pt(H_SPACING, m_curr_dir_text->Height() + V_SPACING));
307     m_files_list->Resize(Pt(Width() - 2 * H_SPACING,
308                             Height() - (button_height + V_SPACING) * 2 - m_curr_dir_text->Height() - 2 * V_SPACING));
309 
310     // determine the space needed to display both text labels in the chosen font; use this to expand the edit as far as
311     // possible
312     X labels_width = std::max((m_files_label->MinUsableSize()).x,
313                               (m_file_types_label->MinUsableSize()).x) + H_SPACING;
314 
315     m_files_label->MoveTo(Pt(X0, Height() - (button_height + V_SPACING) * 2));
316     m_files_label->Resize(Pt(labels_width - H_SPACING / 2, button_height));
317 
318     m_file_types_label->MoveTo(Pt(X0, Height() - (button_height + V_SPACING) * 1));
319     m_file_types_label->Resize(Pt(labels_width - H_SPACING / 2, button_height));
320 
321     m_files_edit->SizeMove(Pt(labels_width, Height() - (button_height + V_SPACING) * 2),
322                            Pt(Width() - (button_width + 2 * H_SPACING), Height() - (button_height + 2 * V_SPACING)));
323 
324     m_filter_list->SizeMove(Pt(labels_width, Height() - (button_height + V_SPACING)),
325                             Pt(Width() - (button_width + 2 * H_SPACING), Height() - V_SPACING));
326 
327     m_ok_button->MoveTo(Pt(Width() - (button_width + H_SPACING), Height() - (button_height + V_SPACING) * 2));
328     m_ok_button->Resize(Pt(button_width, button_height));
329 
330     m_cancel_button->MoveTo(Pt(Width() - (button_width + H_SPACING), Height() - (button_height + V_SPACING)));
331     m_cancel_button->Resize(Pt(button_width, button_height));
332 }
333 
OkClicked()334 void FileDlg::OkClicked()
335 { OkHandler(false); }
336 
OkHandler(bool double_click)337 void FileDlg::OkHandler(bool double_click)
338 {
339     bool results_valid = false;
340 
341     // parse contents of edit control to determine file names
342     m_result.clear();
343 
344     std::vector<std::string> files;
345     parse(m_files_edit->Text().c_str(), (+anychar_p)[append(files)], space_p);
346     std::sort(files.begin(), files.end());
347 
348     const auto& style = GetStyleFactory();
349 
350     if (m_save) { // file save case
351         if (m_ok_button->Text() != m_save_str) {
352             OpenDirectory();
353         } else if (files.size() == 1) {
354             results_valid = true;
355             std::string save_file = *files.begin();
356             if (m_append_missing_save_extension &&
357                 m_file_filters.size() == 1 &&
358                 std::count(m_file_filters[0].second.begin(), m_file_filters[0].second.end(), '*') == 1 &&
359                 m_file_filters[0].second[0] == '*' &&
360                 !boost::algorithm::ends_with(save_file, m_file_filters[0].second.substr(1)))
361             {
362                 save_file += m_file_filters[0].second.substr(1);
363             }
364 #if defined(_WIN32)
365             // convert UTF-8 file name to UTF-16
366             boost::filesystem::path::string_type file_name_native;
367             utf8::utf8to16(save_file.begin(), save_file.end(), std::back_inserter(file_name_native));
368             fs::path p = s_working_dir / fs::path(file_name_native);
369 #else
370             fs::path p = s_working_dir / fs::path(save_file);
371 #endif
372 
373 #if defined (_WIN32)
374             // convert UTF-16 path back to UTF-8 for storage
375             boost::filesystem::path::string_type path_native = p.native();
376             std::string path_string;
377             utf8::utf16to8(path_native.begin(), path_native.end(), std::back_inserter(path_string));
378             m_result.insert(path_string);
379 #else
380             m_result.insert(p.string());
381 #endif
382             // check to see if file already exists; if so, ask if it's ok to overwrite
383             if (fs::exists(p)) {
384                 std::string msg_str = boost::str(boost::format(style->Translate("%1% exists.\nOk to overwrite it?")) % save_file);
385                 auto dlg =
386                     style->NewThreeButtonDlg(X(300), Y(125), msg_str, m_font, m_color, m_border_color, m_color, m_text_color,
387                                              2, style->Translate("Ok"), style->Translate("Cancel"));
388                 dlg->Run();
389                 results_valid = (dlg->Result() == 0);
390             }
391         }
392     } else { // file open case
393         if (files.empty() || (m_select_directories && double_click)) {
394             OpenDirectory();
395         } else { // ensure the file(s) are valid before returning them
396             for (const std::string& file_name : files) {
397 #if defined(_WIN32)
398                 // convert UTF-8 file name to UTF-16
399                 boost::filesystem::path::string_type file_name_native;
400                 utf8::utf8to16(file_name.begin(), file_name.end(), std::back_inserter(file_name_native));
401                 fs::path p = s_working_dir / fs::path(file_name_native);
402 #else
403                 fs::path p = s_working_dir / fs::path(file_name);
404 #endif
405                 if (fs::exists(p)) {
406                     bool p_is_directory = fs::is_directory(p);
407                     if (!m_select_directories && p_is_directory) {
408                         std::string msg_str = boost::str(boost::format(style->Translate("\"%1%\"\nis a directory.")) % file_name);
409                         auto dlg =
410                             style->NewThreeButtonDlg(X(300), Y(125), msg_str, m_font, m_color, m_border_color, m_color,
411                                                      m_text_color, 1, style->Translate("Ok"));
412                         dlg->Run();
413                         results_valid = false;
414                         break;
415                     }
416 #if defined(_WIN32)
417                     // convert UTF-16 path string to UTF-8
418                     std::string temp;
419                     boost::filesystem::path::string_type file_name_native = p.native();
420                     utf8::utf16to8(file_name_native.begin(), file_name_native.end(), std::back_inserter(temp));
421                     m_result.insert(temp);
422 #else
423                     m_result.insert(p.string());
424 #endif
425                     results_valid = true; // indicate validity only if at least one good file was found
426                 } else {
427                     std::string msg_str = boost::str(boost::format(style->Translate("File \"%1%\"\ndoes not exist.")) % file_name);
428                     auto dlg =
429                         style->NewThreeButtonDlg(X(300), Y(125), msg_str, m_font, m_color, m_border_color, m_color,
430                                                  m_text_color, 1, style->Translate("Ok"));
431                     dlg->Run();
432                     results_valid = false;
433                     break;
434                 }
435             }
436         }
437     }
438     if (results_valid)
439         m_done = true;
440 }
441 
CancelClicked()442 void FileDlg::CancelClicked()
443 {
444     m_done = true;
445     m_result.clear();
446 }
447 
FileSetChanged(const ListBox::SelectionSet & files)448 void FileDlg::FileSetChanged(const ListBox::SelectionSet& files)
449 {
450     std::string all_files;
451     bool dir_selected = false;
452     for (const auto& file : files) {
453         std::string filename = !(**file).empty() ? boost::polymorphic_downcast<TextControl*>((**file).at(0))->Text() : "";
454         if (filename[0] != '[') {
455             if (!all_files.empty())
456                 all_files += " ";
457             all_files += filename;
458         } else {
459             if (m_select_directories) {
460                 if (!all_files.empty())
461                     all_files += " ";
462                 all_files += filename.substr(1, filename.size() - 2);
463             }
464             dir_selected = true;
465         }
466     }
467     *m_files_edit << all_files;
468     if (m_save && !dir_selected && m_ok_button->Text() != m_save_str)
469         m_ok_button->SetText(m_save_str);
470     else if (m_save && dir_selected && m_ok_button->Text() == m_save_str)
471         m_ok_button->SetText(m_open_str);
472 }
473 
FileDoubleClicked(DropDownList::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)474 void FileDlg::FileDoubleClicked(DropDownList::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys)
475 {
476     m_files_list->DeselectAll();
477     m_files_list->SelectRow(it);
478     FileSetChanged(m_files_list->Selections());
479     OkHandler(true);
480 }
481 
FilesEditChanged(const std::string & str)482 void FileDlg::FilesEditChanged(const std::string& str)
483 {
484     if (m_save && m_ok_button->Text() != m_save_str)
485         m_ok_button->SetText(m_save_str);
486 }
487 
FilterChanged(DropDownList::iterator it)488 void FileDlg::FilterChanged(DropDownList::iterator it)
489 { UpdateList(); }
490 
SetWorkingDirectory(const fs::path & p)491 void FileDlg::SetWorkingDirectory(const fs::path& p)
492 {
493     m_files_edit->Clear();
494     FilesEditChanged(m_files_edit->Text());
495     s_working_dir = p;
496     UpdateDirectoryText();
497     UpdateList();
498 }
499 
PopulateFilters()500 void FileDlg::PopulateFilters()
501 {
502     m_filter_list->Clear();
503     if (m_file_filters.empty()) {
504         m_file_types_label->Disable();
505         m_filter_list->Disable();
506     } else {
507         for (auto& file_filter : m_file_filters) {
508             auto row = Wnd::Create<ListBox::Row>();
509             row->push_back(GetStyleFactory()->NewTextControl(file_filter.first, m_font, m_text_color, FORMAT_NOWRAP));
510             m_filter_list->Insert(row);
511         }
512         m_filter_list->Select(m_filter_list->begin());
513         m_filter_list->SelChangedSignal(m_filter_list->CurrentItem());
514     }
515 }
516 
UpdateList()517 void FileDlg::UpdateList()
518 {
519     m_files_list->Clear();
520     fs::directory_iterator end_it;
521 
522     // define a wildcard ('*') as any combination of characters
523     rule<> wildcard = anychar_p;
524 
525     // define file filters based on the filter strings in the filter drop list
526     std::vector<rule<>> file_filters;
527 
528     auto filter_it = m_filter_list->CurrentItem();
529     if (filter_it != m_filter_list->end()) {
530         std::vector<std::string> filter_specs; // the filter specifications (e.g. "*.png")
531         parse(m_file_filters[std::distance(m_filter_list->begin(), filter_it)].second.c_str(),
532               *(!ch_p(',') >> (+(anychar_p - ','))[append(filter_specs)]),
533               space_p);
534         file_filters.resize(filter_specs.size());
535         for (std::size_t i = 0; i < filter_specs.size(); ++i) {
536             auto non_wildcards = std::make_shared<std::vector<std::string>>(); // the parts of the filter spec that are not wildcards
537             parse(filter_specs[i].c_str(), *(*ch_p('*') >> (+(anychar_p - '*'))[append(*non_wildcards)]));
538 
539             if (non_wildcards->empty()) {
540                 file_filters[i] = *anychar_p;
541             } else {
542                 file_filters[i] =
543                     if_p (LeadingWildcard(filter_specs[i])) [
544                         *(wildcard - f_str_p(FrontStringBegin(non_wildcards), FrontStringEnd(non_wildcards)))
545                         >> f_str_p(FrontStringBegin(non_wildcards), FrontStringEnd(non_wildcards))
546                     ] .else_p [
547                         f_str_p(FrontStringBegin(non_wildcards), FrontStringEnd(non_wildcards))
548                     ]
549                     >> for_p (Index(1), IndexLess(static_cast<int>(non_wildcards->size()) - 1), IndexIncr()) [
550                         *(wildcard - f_str_p(IndexedStringBegin(non_wildcards), IndexedStringEnd(non_wildcards)))
551                         >> f_str_p(IndexedStringBegin(non_wildcards), IndexedStringEnd(non_wildcards))
552                     ]
553                     >> if_p (TrailingWildcard(filter_specs[i])) [
554                         *wildcard
555                     ];
556             }
557         }
558     }
559 
560     if (!m_in_win32_drive_selection) {
561         // parent directory selector
562         if ((s_working_dir.string() != s_working_dir.root_path().string() &&
563              !s_working_dir.branch_path().string().empty()) ||
564             Win32Paths())
565         {
566             auto row = Wnd::Create<ListBox::Row>();
567             row->push_back(GetStyleFactory()->NewTextControl("[..]", m_font, m_text_color, FORMAT_NOWRAP));
568             m_files_list->Insert(row);
569         }
570         // current directory selector
571         {
572             auto row = Wnd::Create<ListBox::Row>();
573             row->push_back(GetStyleFactory()->NewTextControl("[.]", m_font, m_text_color, FORMAT_NOWRAP));
574             m_files_list->Insert(row);
575         }
576         try {
577             fs::directory_iterator test(s_working_dir);
578         } catch (const fs::filesystem_error&) {
579             // This ctor has been found to throw on Win32 when we attempt to
580             // iterate over a path into a drive that has just been disconnected
581             // (e.g. a USB thumb drive).  In this case, we will just cancel the
582             // dialog.
583             CancelClicked();
584             return;
585         }
586         // contained directories
587         std::multimap<std::string, std::shared_ptr<ListBox::Row>> sorted_rows;
588         for (fs::directory_iterator it(s_working_dir); it != end_it; ++it) {
589             try {
590                 if (fs::exists(*it) && fs::is_directory(*it) && it->path().filename().native()[0] != '.') {
591                     auto row = Wnd::Create<ListBox::Row>();
592 
593 #if defined(_WIN32)
594                     // convert UTF-16 path to UTF-8 for display
595                     boost::filesystem::path::string_type file_name_native = it->path().filename().native();
596                     std::string temp;
597                     utf8::utf16to8(file_name_native.begin(), file_name_native.end(), std::back_inserter(temp));
598                     std::string row_text = "[" + temp + "]";
599 #else
600                     std::string row_text = "[" + it->path().filename().string() + "]";
601 #endif
602                     row->push_back(GetStyleFactory()->NewTextControl(row_text, m_font, m_text_color, FORMAT_NOWRAP));
603                     sorted_rows.insert({row_text, row});
604                 }
605             } catch (const fs::filesystem_error&) {
606             }
607         }
608 
609         std::vector<std::shared_ptr<ListBox::Row>> rows;
610         rows.reserve(sorted_rows.size());
611         for (auto& row : sorted_rows)
612         { rows.push_back(row.second); }
613         m_files_list->Insert(rows);
614 
615         if (!m_select_directories) {
616             sorted_rows.clear();
617             for (fs::directory_iterator it(s_working_dir); it != end_it; ++it) {
618                 try {
619                     if (fs::exists(*it) && !fs::is_directory(*it) && it->path().filename().native()[0] != '.') {
620                         bool meets_filters = file_filters.empty();
621                         for (std::size_t i = 0; i < file_filters.size() && !meets_filters; ++i) {
622                             if (parse(it->path().filename().string().c_str(), file_filters[i]).full)
623                                 meets_filters = true;
624                         }
625                         if (meets_filters) {
626                             auto row = Wnd::Create<ListBox::Row>();
627                             row->push_back(GetStyleFactory()->NewTextControl(it->path().filename().string(), m_font, m_text_color, FORMAT_NOWRAP));
628                             sorted_rows.insert({it->path().filename().string(), row});
629                         }
630                     }
631                 } catch (const fs::filesystem_error&) {
632                 }
633             }
634             for (const auto& row : sorted_rows) {
635                 m_files_list->Insert(row.second);
636             }
637         }
638     } else {
639         for (char c = 'C'; c <= 'Z'; ++c) {
640             try {
641                 fs::path path(c + std::string(":"));
642                 if (fs::exists(path)) {
643                     auto row = Wnd::Create<ListBox::Row>();
644                     row->push_back(GetStyleFactory()->NewTextControl("[" + path.root_name().string() + "]", m_font, m_text_color, FORMAT_NOWRAP));
645                     m_files_list->Insert(row);
646                 }
647             } catch (const fs::filesystem_error&) {
648             }
649         }
650     }
651 }
652 
UpdateDirectoryText()653 void FileDlg::UpdateDirectoryText()
654 {
655 #if defined(_WIN32)
656     // convert UTF-16 path to UTF-8 for display
657     boost::filesystem::path::string_type working_dir_native = s_working_dir.native();
658     std::string str;
659     utf8::utf16to8(working_dir_native.begin(), working_dir_native.end(), std::back_inserter(str));
660 #else
661     std::string str = s_working_dir.string();
662 #endif
663     m_curr_dir_text->SetText(str);
664     while (m_curr_dir_text->Width() > Width() - 2 * H_SPACING) {
665         std::string::size_type slash_idx = str.find('/', 1);
666         std::string::size_type backslash_idx = str.find('\\', 1);
667         if (slash_idx != std::string::npos) {
668             slash_idx = str.find_first_not_of('/', slash_idx);
669             str = "..." + str.substr(slash_idx);
670         } else if (backslash_idx != std::string::npos) {
671             backslash_idx = str.find_first_not_of('\\', backslash_idx);
672             str = "..." + str.substr(backslash_idx);
673         } else {
674             break;
675         }
676         m_curr_dir_text->SetText(str);
677     }
678     DoLayout();
679 }
680 
OpenDirectory()681 void FileDlg::OpenDirectory()
682 {
683     const auto& style = GetStyleFactory();
684 
685     // see if there is a directory selected; if so open the directory.
686     // if more than one is selected, take the first one
687     const ListBox::SelectionSet& sels = m_files_list->Selections();
688     if (sels.empty())
689         return;
690 
691     std::string directory;
692     directory = !(***sels.begin()).empty() ? boost::polymorphic_downcast<TextControl*>((***sels.begin()).at(0))->Text() : "";
693 
694     if (directory.size() < 2 || directory[0] != '[')
695         return;
696 
697     directory = directory.substr(1, directory.size() - 2); // strip off '[' and ']'
698 
699     if (directory == ".") {
700         // remain in current directory
701         UpdateList();
702 
703     } else if (directory == "..") {
704         // move to parent directory of current directory
705         if (s_working_dir.string() != s_working_dir.root_path().string() &&
706             !s_working_dir.branch_path().string().empty())
707         {
708             // move to new directory
709             SetWorkingDirectory(s_working_dir.branch_path());
710 
711         } else {
712             // switch to drive selection mode
713             m_in_win32_drive_selection = true;
714             m_files_edit->Clear();
715             FilesEditChanged(m_files_edit->Text());
716             m_curr_dir_text->SetText("");
717             DoLayout();
718             UpdateList();
719         }
720 
721     } else {
722         // move to contained directory, which may be a drive selection...
723         if (!m_in_win32_drive_selection) {
724 
725 #if defined(_WIN32)
726             // convert UTF-8 file name to UTF-16
727             boost::filesystem::path::string_type directory_native;
728             utf8::utf8to16(directory.begin(), directory.end(), std::back_inserter(directory_native));
729             SetWorkingDirectory(s_working_dir / fs::path(directory_native));
730 #else
731             SetWorkingDirectory(s_working_dir / fs::path(directory));
732 #endif
733         } else {
734             m_in_win32_drive_selection = false;
735             try {
736                 SetWorkingDirectory(fs::path(directory + "\\"));
737             } catch (const fs::filesystem_error& e) {
738                 if (e.code() == boost::system::errc::io_error) {
739                     m_in_win32_drive_selection = true;
740                     m_files_edit->Clear();
741                     FilesEditChanged(m_files_edit->Text());
742                     m_curr_dir_text->SetText("");
743                     DoLayout();
744                     UpdateList();
745                     auto dlg =
746                         GetStyleFactory()->NewThreeButtonDlg(X(175), Y(75),
747                                                              style->Translate("Device is not ready."),
748                                                              m_font, m_color,
749                                                              m_border_color, m_color,
750                                                              m_text_color, 1);
751                     dlg->Run();
752                 } else {
753                     throw;
754                 }
755             }
756         }
757     }
758 
759     if (m_save && m_ok_button->Text() != m_save_str)
760         m_ok_button->SetText(m_save_str);
761 }
762