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