Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1 | pmbaty | 1 | /* |
| 2 | * Portions of this file are copyright Rebirth contributors and licensed as |
||
| 3 | * described in COPYING.txt. |
||
| 4 | * Portions of this file are copyright Parallax Software and licensed |
||
| 5 | * according to the Parallax license below. |
||
| 6 | * See COPYING.txt for license details. |
||
| 7 | |||
| 8 | THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX |
||
| 9 | SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO |
||
| 10 | END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A |
||
| 11 | ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS |
||
| 12 | IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS |
||
| 13 | SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE |
||
| 14 | FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE |
||
| 15 | CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS |
||
| 16 | AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE. |
||
| 17 | COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. |
||
| 18 | */ |
||
| 19 | |||
| 20 | #include <stdlib.h> |
||
| 21 | #include <string.h> |
||
| 22 | |||
| 23 | #include "event.h" |
||
| 24 | #include "maths.h" |
||
| 25 | #include "pstypes.h" |
||
| 26 | #include "gr.h" |
||
| 27 | #include "key.h" |
||
| 28 | #include "mouse.h" |
||
| 29 | #include "strutil.h" |
||
| 30 | #include "ui.h" |
||
| 31 | #include "window.h" |
||
| 32 | #include "u_mem.h" |
||
| 33 | #include "physfsx.h" |
||
| 34 | #include "physfs_list.h" |
||
| 35 | |||
| 36 | #include "compiler-range_for.h" |
||
| 37 | #include "d_range.h" |
||
| 38 | #include <memory> |
||
| 39 | |||
| 40 | namespace dcx { |
||
| 41 | |||
| 42 | bool isDirectory(const char *fname) // Pierre-Marie Baty -- work around PHYSFS_isDirectory() deprecation |
||
| 43 | { |
||
| 44 | PHYSFS_Stat statbuf; |
||
| 45 | if (!PHYSFS_stat(fname, &statbuf)) return false; |
||
| 46 | return (statbuf.filetype == PHYSFS_FILETYPE_DIRECTORY); |
||
| 47 | } |
||
| 48 | |||
| 49 | static PHYSFSX_counted_list file_getdirlist(const char *dir) |
||
| 50 | { |
||
| 51 | ntstring<PATH_MAX - 1> path; |
||
| 52 | auto dlen = path.copy_if(dir); |
||
| 53 | if ((!dlen && dir[0] != '\0') || !path.copy_if(dlen, "/")) |
||
| 54 | return nullptr; |
||
| 55 | ++ dlen; |
||
| 56 | PHYSFSX_counted_list list{PHYSFS_enumerateFiles(dir)}; |
||
| 57 | if (!list) |
||
| 58 | return nullptr; |
||
| 59 | const auto predicate = [&](char *i) -> bool { |
||
| 60 | if (path.copy_if(dlen, i) && /*PHYSFS_*/isDirectory(path)) // Pierre-Marie Baty -- work around PHYSFS_isDirectory() deprecation |
||
| 61 | return false; |
||
| 62 | free(i); |
||
| 63 | return true; |
||
| 64 | }; |
||
| 65 | auto j = std::remove_if(list.begin(), list.end(), predicate); |
||
| 66 | *j = NULL; |
||
| 67 | auto NumDirs = j.get() - list.get(); |
||
| 68 | qsort(list.get(), NumDirs, sizeof(char *), string_array_sort_func); |
||
| 69 | if (*dir) |
||
| 70 | { |
||
| 71 | // Put the 'go to parent directory' sequence '..' first |
||
| 72 | ++NumDirs; |
||
| 73 | auto r = reinterpret_cast<char **>(realloc(list.get(), sizeof(char *)*(NumDirs + 1))); |
||
| 74 | if (!r) |
||
| 75 | return list; |
||
| 76 | list.release(); |
||
| 77 | list.reset(r); |
||
| 78 | std::move_backward(r, r + NumDirs, r + NumDirs + 1); |
||
| 79 | list[0] = d_strdup(".."); |
||
| 80 | } |
||
| 81 | list.set_count(NumDirs); |
||
| 82 | return list; |
||
| 83 | } |
||
| 84 | |||
| 85 | static PHYSFSX_counted_list file_getfilelist(const char *filespec, const char *dir) |
||
| 86 | { |
||
| 87 | PHYSFSX_counted_list list{PHYSFS_enumerateFiles(dir)}; |
||
| 88 | if (!list) |
||
| 89 | return nullptr; |
||
| 90 | |||
| 91 | if (*filespec == '*') |
||
| 92 | filespec++; |
||
| 93 | |||
| 94 | const auto predicate = [&](char *i) -> bool { |
||
| 95 | auto ext = strrchr(i, '.'); |
||
| 96 | if (ext && (!d_stricmp(ext, filespec))) |
||
| 97 | return false; |
||
| 98 | free(i); |
||
| 99 | return true; |
||
| 100 | }; |
||
| 101 | auto j = std::remove_if(list.begin(), list.end(), predicate); |
||
| 102 | *j = NULL; |
||
| 103 | auto NumFiles = j.get() - list.get(); |
||
| 104 | list.set_count(NumFiles); |
||
| 105 | qsort(list.get(), NumFiles, sizeof(char *), string_array_sort_func); |
||
| 106 | return list; |
||
| 107 | } |
||
| 108 | |||
| 109 | namespace { |
||
| 110 | |||
| 111 | struct ui_file_browser |
||
| 112 | { |
||
| 113 | char *filename; |
||
| 114 | const char *filespec; |
||
| 115 | const char *message; |
||
| 116 | PHYSFSX_counted_list filename_list, directory_list; |
||
| 117 | std::unique_ptr<UI_GADGET_BUTTON> button1, button2, help_button; |
||
| 118 | std::unique_ptr<UI_GADGET_LISTBOX> listbox1, listbox2; |
||
| 119 | std::unique_ptr<UI_GADGET_INPUTBOX> user_file; |
||
| 120 | std::array<char, 35> spaces; |
||
| 121 | char view_dir[PATH_MAX]; |
||
| 122 | }; |
||
| 123 | |||
| 124 | } |
||
| 125 | |||
| 126 | static window_event_result browser_handler(UI_DIALOG *const dlg, const d_event &event, ui_file_browser *const b) |
||
| 127 | { |
||
| 128 | window_event_result rval = window_event_result::ignored; |
||
| 129 | |||
| 130 | if (event.type == EVENT_UI_DIALOG_DRAW) |
||
| 131 | { |
||
| 132 | ui_dputs_at( dlg, 10, 5, b->message ); |
||
| 133 | |||
| 134 | ui_dprintf_at( dlg, 20, 32,"N&ame" ); |
||
| 135 | ui_dprintf_at( dlg, 20, 86,"&Files" ); |
||
| 136 | ui_dprintf_at( dlg, 210, 86,"&Dirs" ); |
||
| 137 | |||
| 138 | ui_dputs_at(dlg, 20, 60, b->spaces.data()); |
||
| 139 | ui_dputs_at( dlg, 20, 60, b->view_dir ); |
||
| 140 | |||
| 141 | return window_event_result::handled; |
||
| 142 | } |
||
| 143 | |||
| 144 | if (GADGET_PRESSED(b->button2.get())) |
||
| 145 | { |
||
| 146 | b->filename_list.reset(); |
||
| 147 | b->directory_list.reset(); |
||
| 148 | return window_event_result::close; |
||
| 149 | } |
||
| 150 | |||
| 151 | if (GADGET_PRESSED(b->help_button.get())) |
||
| 152 | { |
||
| 153 | ui_messagebox( -1, -1, 1, "Sorry, no help is available!", "Ok" ); |
||
| 154 | rval = window_event_result::handled; |
||
| 155 | } |
||
| 156 | |||
| 157 | if (event.type == EVENT_UI_LISTBOX_MOVED) |
||
| 158 | { |
||
| 159 | if ((ui_event_get_gadget(event) == b->listbox1.get()) && (b->listbox1->current_item >= 0) && b->filename_list[b->listbox1->current_item]) |
||
| 160 | ui_inputbox_set_text(b->user_file.get(), b->filename_list[b->listbox1->current_item]); |
||
| 161 | |||
| 162 | if ((ui_event_get_gadget(event) == b->listbox2.get()) && (b->listbox2->current_item >= 0) && b->directory_list[b->listbox2->current_item]) |
||
| 163 | ui_inputbox_set_text(b->user_file.get(), b->directory_list[b->listbox2->current_item]); |
||
| 164 | |||
| 165 | rval = window_event_result::handled; |
||
| 166 | } |
||
| 167 | |||
| 168 | if (GADGET_PRESSED(b->button1.get()) || GADGET_PRESSED(b->user_file.get()) || event.type == EVENT_UI_LISTBOX_SELECTED) |
||
| 169 | { |
||
| 170 | char *p; |
||
| 171 | |||
| 172 | if (ui_event_get_gadget(event) == b->listbox2.get()) |
||
| 173 | strcpy(b->user_file->text.get(), b->directory_list[b->listbox2->current_item]); |
||
| 174 | |||
| 175 | strncpy(b->filename, b->view_dir, PATH_MAX); |
||
| 176 | |||
| 177 | p = b->user_file->text.get(); |
||
| 178 | while (!strncmp(p, "..", 2)) // shorten the path manually |
||
| 179 | { |
||
| 180 | char *sep = strrchr(b->filename, '/'); |
||
| 181 | if (sep) |
||
| 182 | *sep = 0; |
||
| 183 | else |
||
| 184 | *b->filename = 0; // look directly in search paths |
||
| 185 | |||
| 186 | p += 2; |
||
| 187 | if (*p == '/') |
||
| 188 | p++; |
||
| 189 | } |
||
| 190 | |||
| 191 | if (*b->filename && *p) |
||
| 192 | strncat(b->filename, "/", PATH_MAX - strlen(b->filename)); |
||
| 193 | strncat(b->filename, p, PATH_MAX - strlen(b->filename)); |
||
| 194 | |||
| 195 | if (!/*PHYSFS_*/isDirectory(b->filename)) // Pierre-Marie Baty -- work around PHYSFS_isDirectory() deprecation |
||
| 196 | { |
||
| 197 | if (RAIIPHYSFS_File{PHYSFS_openRead(b->filename)}) |
||
| 198 | { |
||
| 199 | // Looks like a valid filename that already exists! |
||
| 200 | return window_event_result::close; |
||
| 201 | } |
||
| 202 | |||
| 203 | // File doesn't exist, but can we create it? |
||
| 204 | if (RAIIPHYSFS_File TempFile{PHYSFS_openWrite(b->filename)}) |
||
| 205 | { |
||
| 206 | TempFile.reset(); |
||
| 207 | // Looks like a valid filename! |
||
| 208 | PHYSFS_delete(b->filename); |
||
| 209 | return window_event_result::close; |
||
| 210 | } |
||
| 211 | } |
||
| 212 | else |
||
| 213 | { |
||
| 214 | if (b->filename[strlen(b->filename) - 1] == '/') // user typed a separator on the end |
||
| 215 | b->filename[strlen(b->filename) - 1] = 0; |
||
| 216 | |||
| 217 | strcpy(b->view_dir, b->filename); |
||
| 218 | b->filename_list = file_getfilelist(b->filespec, b->view_dir); |
||
| 219 | if (!b->filename_list) |
||
| 220 | { |
||
| 221 | b->directory_list.reset(); |
||
| 222 | return window_event_result::close; |
||
| 223 | } |
||
| 224 | |||
| 225 | ui_inputbox_set_text(b->user_file.get(), b->filespec); |
||
| 226 | b->directory_list = file_getdirlist(b->view_dir); |
||
| 227 | if (!b->directory_list) |
||
| 228 | { |
||
| 229 | b->filename_list.reset(); |
||
| 230 | return window_event_result::close; |
||
| 231 | } |
||
| 232 | |||
| 233 | ui_listbox_change(dlg, b->listbox1.get(), b->filename_list.get_count(), b->filename_list.get()); |
||
| 234 | ui_listbox_change(dlg, b->listbox2.get(), b->directory_list.get_count(), b->directory_list.get()); |
||
| 235 | |||
| 236 | //i = TICKER; |
||
| 237 | //while ( TICKER < i+2 ); |
||
| 238 | |||
| 239 | } |
||
| 240 | |||
| 241 | rval = window_event_result::handled; |
||
| 242 | } |
||
| 243 | |||
| 244 | return rval; |
||
| 245 | } |
||
| 246 | |||
| 247 | int ui_get_filename(char (&filename)[PATH_MAX], const char *const filespec, const char *const message) |
||
| 248 | { |
||
| 249 | char InputText[PATH_MAX]; |
||
| 250 | char *p; |
||
| 251 | UI_DIALOG *dlg; |
||
| 252 | int rval = 0; |
||
| 253 | auto b = std::make_unique<ui_file_browser>(); |
||
| 254 | if ((p = strrchr(filename, '/'))) |
||
| 255 | { |
||
| 256 | *p++ = 0; |
||
| 257 | strcpy(b->view_dir, filename); |
||
| 258 | strcpy(InputText, p); |
||
| 259 | } |
||
| 260 | else |
||
| 261 | { |
||
| 262 | strcpy(b->view_dir, ""); |
||
| 263 | strcpy(InputText, filename); |
||
| 264 | } |
||
| 265 | |||
| 266 | b->filename_list = file_getfilelist(filespec, b->view_dir); |
||
| 267 | if (!b->filename_list) |
||
| 268 | { |
||
| 269 | return 0; |
||
| 270 | } |
||
| 271 | |||
| 272 | b->directory_list = file_getdirlist(b->view_dir); |
||
| 273 | if (!b->directory_list) |
||
| 274 | { |
||
| 275 | b->filename_list.reset(); |
||
| 276 | return 0; |
||
| 277 | } |
||
| 278 | |||
| 279 | //ui_messagebox( -2,-2, 1,"DEBUG:0", "Ok" ); |
||
| 280 | range_for (const int i, xrange(35u)) |
||
| 281 | b->spaces[i] = ' '; |
||
| 282 | b->spaces[34] = 0; |
||
| 283 | |||
| 284 | dlg = ui_create_dialog( 200, 100, 400, 370, static_cast<dialog_flags>(DF_DIALOG | DF_MODAL), browser_handler, b.get()); |
||
| 285 | |||
| 286 | b->user_file = ui_add_gadget_inputbox<40>(dlg, 60, 30, InputText); |
||
| 287 | |||
| 288 | b->listbox1 = ui_add_gadget_listbox(dlg, 20, 110, 125, 200, b->filename_list.get_count(), b->filename_list.get()); |
||
| 289 | b->listbox2 = ui_add_gadget_listbox(dlg, 210, 110, 100, 200, b->directory_list.get_count(), b->directory_list.get()); |
||
| 290 | |||
| 291 | b->button1 = ui_add_gadget_button( dlg, 20, 330, 60, 25, "Ok", NULL ); |
||
| 292 | b->button2 = ui_add_gadget_button( dlg, 100, 330, 60, 25, "Cancel", NULL ); |
||
| 293 | b->help_button = ui_add_gadget_button( dlg, 180, 330, 60, 25, "Help", NULL ); |
||
| 294 | |||
| 295 | dlg->keyboard_focus_gadget = b->user_file.get(); |
||
| 296 | |||
| 297 | b->button1->hotkey = KEY_CTRLED + KEY_ENTER; |
||
| 298 | b->button2->hotkey = KEY_ESC; |
||
| 299 | b->help_button->hotkey = KEY_F1; |
||
| 300 | b->listbox1->hotkey = KEY_ALTED + KEY_F; |
||
| 301 | b->listbox2->hotkey = KEY_ALTED + KEY_D; |
||
| 302 | b->user_file->hotkey = KEY_ALTED + KEY_A; |
||
| 303 | |||
| 304 | ui_gadget_calc_keys(dlg); |
||
| 305 | |||
| 306 | b->filename = filename; |
||
| 307 | b->filespec = filespec; |
||
| 308 | b->message = message; |
||
| 309 | |||
| 310 | event_process_all(); |
||
| 311 | |||
| 312 | //key_flush(); |
||
| 313 | |||
| 314 | rval = static_cast<bool>(b->filename_list); |
||
| 315 | b->filename_list.reset(); |
||
| 316 | b->directory_list.reset(); |
||
| 317 | return rval; |
||
| 318 | } |
||
| 319 | |||
| 320 | int ui_get_file( char * filename, const char * Filespec ) |
||
| 321 | { |
||
| 322 | int x; |
||
| 323 | auto list = file_getfilelist(Filespec, ""); |
||
| 324 | if (!list) return 0; |
||
| 325 | x = MenuX(-1, -1, list.get_count(), list.get()); |
||
| 326 | if (x > 0) |
||
| 327 | strcpy(filename, list[x - 1]); |
||
| 328 | return (x > 0); |
||
| 329 | } |
||
| 330 | |||
| 331 | } |