gcsx_filedialog.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. /* GCSx
  2. ** FILEDIALOG.CPP
  3. **
  4. ** Standard file open/save/save as dialogs
  5. */
  6. /*****************************************************************************
  7. ** Copyright (C) 2003-2006 Janson
  8. **
  9. ** This program is free software; you can redistribute it and/or modify
  10. ** it under the terms of the GNU General Public License as published by
  11. ** the Free Software Foundation; either version 2 of the License, or
  12. ** (at your option) any later version.
  13. **
  14. ** This program is distributed in the hope that it will be useful,
  15. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ** GNU General Public License for more details.
  18. **
  19. ** You should have received a copy of the GNU General Public License
  20. ** along with this program; if not, write to the Free Software
  21. ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
  22. *****************************************************************************/
  23. #include "all.h"
  24. // Last-used directories
  25. string* previousDirectories = NULL;
  26. // File type associated extensions for saving, and some for loading
  27. vector<string>* fileExtensions = NULL;
  28. // Standard file dialog
  29. class FileDirDialog : public Dialog {
  30. private:
  31. int initialized;
  32. int initializedH;
  33. int initializedW;
  34. int selection;
  35. int saveAs;
  36. int openForWrite;
  37. string file;
  38. DirectoryView* tree;
  39. WListBox* list;
  40. WTextBox* filename;
  41. const vector<string>* currentExtensions;
  42. int treeviewEvent(int code, int command, int check);
  43. static int treeviewEventWrap(void* ptr, int code, int command, int check);
  44. void refillList(const string* itemToSelect = NULL);
  45. #if !FILESYSTEM_CASE_SENSITIVE
  46. struct stringCmpI : public binary_function<string, string, bool> {
  47. bool operator()(const string& x, const string& y) { return myStricmp(x.c_str(), y.c_str()) < 0; }
  48. };
  49. #endif
  50. public:
  51. enum {
  52. ID_TREE = 1,
  53. ID_LIST,
  54. ID_LABEL,
  55. ID_ENTRY,
  56. ID_OK,
  57. ID_CANCEL,
  58. };
  59. FileDirDialog();
  60. virtual ~FileDirDialog();
  61. void childModified(Window* modified);
  62. virtual ButtonAction verifyEntry(int buttonId, ButtonAction buttonType);
  63. void firstControl();
  64. int run(int isSaveAs, int isOpenForWrite, const vector<string>& extensions, string& storeFilename);
  65. } *fileDialog = NULL;
  66. // Initializes extensions, last-used directories and dialog
  67. void initFileSystems() { start_func
  68. if (!fileExtensions) {
  69. previousDirectories = new string[FILETYPE_COUNT];
  70. fileExtensions = new vector<string>[FILETYPE_COUNT];
  71. fileDialog = new FileDirDialog;
  72. char cwd[PATH_MAX];
  73. getcwd(cwd, PATH_MAX);
  74. for (int pos = 0; pos < FILETYPE_COUNT; ++pos) {
  75. previousDirectories[pos] = cwd;
  76. }
  77. initFileExtensions(fileExtensions);
  78. }
  79. }
  80. // Cleanup
  81. void destroyFileDialogGlobals() { start_func
  82. if (previousDirectories) delete[] previousDirectories;
  83. if (fileExtensions) delete[] fileExtensions;
  84. if (fileDialog) delete fileDialog;
  85. previousDirectories = NULL;
  86. fileExtensions = NULL;
  87. fileDialog = NULL;
  88. }
  89. // Directory treeview
  90. DirectoryView::DirectoryView(const string& name, const char* fullpath, void* tPtr, int (*tAction)(void* ptr, int code, int command, int check), int topLevel) : TreeView(name, tPtr, 0, tAction, topLevel, FILESYSTEM_CASE_SENSITIVE), path(blankString) { start_func
  91. path = fullpath;
  92. hasSubDirs = existFiles(fullpath, NULL);
  93. if ((FILESYSTEM_DRIVE_SEP) && (name[1] == FILESYSTEM_DRIVE_SEP) && (name.length() == 2)) {
  94. iconOpen = 2;
  95. iconClosed = 2;
  96. }
  97. else {
  98. iconOpen = 0;
  99. iconClosed = 1;
  100. }
  101. }
  102. DirectoryView::~DirectoryView() { start_func
  103. }
  104. void DirectoryView::redo() { start_func
  105. removeAll();
  106. hasSubDirs = existFiles(path.c_str(), NULL);
  107. expand(1);
  108. if (hasSubItems()) {
  109. subtree[0]->selectMe(); // (start with top item selected)
  110. }
  111. else {
  112. selectMe(); // (start with self selected)
  113. }
  114. }
  115. int DirectoryView::hasSubItems() const { start_func
  116. return (subitems > 0) || (hasSubDirs);
  117. }
  118. void DirectoryView::expand(int state) { start_func
  119. if ((hasSubDirs) && (state)) {
  120. vector<string> subdirs;
  121. if (findFiles(path.c_str(), NULL, subdirs)) {
  122. vector<string>::iterator end = subdirs.end();
  123. DirectoryView* subdir = NULL;
  124. for (vector<string>::iterator pos = subdirs.begin(); pos != end; ++pos) {
  125. string newpath;
  126. createDirname(path.c_str(), (*pos).c_str(), newpath);
  127. subdir = new DirectoryView(*pos, newpath.c_str(), ptr, action);
  128. insert(subdir, 0);
  129. subdir = NULL;
  130. }
  131. hasSubDirs = 0;
  132. }
  133. else {
  134. hasSubDirs = 0;
  135. // We thought we had subdirs, but we didn't; however
  136. // we're dirty as our icon needs to vanish
  137. setDirty();
  138. return;
  139. }
  140. }
  141. TreeView::expand(state);
  142. }
  143. // Standard file dialog
  144. FileDirDialog::FileDirDialog() : Dialog(blankString), file(blankString) { start_func
  145. initialized = 0;
  146. tree = NULL;
  147. list = NULL;
  148. filename = NULL;
  149. currentExtensions = NULL;
  150. }
  151. FileDirDialog::~FileDirDialog() { start_func
  152. // (dialog/wscroll will not delete tree for us)
  153. // (we must clear widgets first, so our tree doesn't get referenced in ~Dialog())
  154. filename = NULL;
  155. list = NULL;
  156. clearWidgets();
  157. delete tree;
  158. }
  159. void FileDirDialog::firstControl() { start_func
  160. changeInputFocus(NULL);
  161. nextControl();
  162. if (saveAs) {
  163. nextControl();
  164. nextControl();
  165. }
  166. }
  167. void FileDirDialog::childModified(Window* modified) { start_func
  168. Dialog::childModified(modified);
  169. if ((modified == list) && (filename)) {
  170. // Refill textbox
  171. list->apply();
  172. const ListEntry* entry = list->findEntry(selection);
  173. if (entry) filename->state(entry->label);
  174. }
  175. }
  176. void FileDirDialog::refillList(const string* itemToSelect) { start_func
  177. DirectoryView* sel = dynamic_cast<DirectoryView*>(tree->findSelected());
  178. list->clear();
  179. if (sel) {
  180. // Fill listbox
  181. vector<string> subfiles;
  182. const char* path = sel->getFullpath();
  183. if (currentExtensions) {
  184. vector<string>::const_iterator extEnd = currentExtensions->end();
  185. for (vector<string>::const_iterator extPos = currentExtensions->begin(); extPos != extEnd; ++extPos) {
  186. string extMask(FILESYSTEM_EXTENSIONSEP);
  187. extMask += *extPos;
  188. findFiles(path, extMask.c_str(), subfiles);
  189. }
  190. }
  191. if (!subfiles.empty()) {
  192. #if FILESYSTEM_CASE_SENSITIVE
  193. sort(subfiles.begin(), subfiles.end());
  194. #else
  195. sort(subfiles.begin(), subfiles.end(), stringCmpI());
  196. #endif
  197. vector<string>::iterator end = subfiles.end();
  198. int id = 0;
  199. for (vector<string>::iterator pos = subfiles.begin(); pos != end; ++pos) {
  200. list->addItem(ListEntry(*pos, ++id));
  201. #if FILESYSTEM_CASE_SENSITIVE
  202. if ((itemToSelect) && (!strcmp(itemToSelect->c_str(), (*pos).c_str()))) selection = id;
  203. #else
  204. if ((itemToSelect) && (!myStricmp(itemToSelect->c_str(), (*pos).c_str()))) selection = id;
  205. #endif
  206. }
  207. }
  208. // Empty textbox
  209. if (filename) filename->state(blankString);
  210. }
  211. }
  212. int FileDirDialog::treeviewEvent(int code, int command, int check) { start_func
  213. if (check) return Window::COMMAND_HIDE;
  214. if (command == LV_MOVE) {
  215. refillList();
  216. return 1;
  217. }
  218. return 0;
  219. }
  220. int FileDirDialog::treeviewEventWrap(void* ptr, int code, int command, int check) { start_func
  221. return ((FileDirDialog*)ptr)->treeviewEvent(code, command, check);
  222. }
  223. Dialog::ButtonAction FileDirDialog::verifyEntry(int buttonId, ButtonAction buttonType) { start_func
  224. if ((buttonType != BUTTON_APPLY) && (buttonType != BUTTON_OK)) return BUTTON_DEFAULT;
  225. DirectoryView* sel = dynamic_cast<DirectoryView*>(tree->findSelected());
  226. const char* path = sel->getFullpath();
  227. // Allows entry of name by itself or a relative pathname or an absolute pathname
  228. string fullname;
  229. createFilename(path, filename->state().c_str(), fullname);
  230. // Add extension if none given and saveas
  231. if ((currentExtensions) && (saveAs)) {
  232. string::size_type found = filename->state().find_last_of(FILESYSTEM_SEPARATORS);
  233. if (found >= string::npos) found = 0;
  234. if (filename->state().find_first_of(FILESYSTEM_EXTENSIONSEP) >= string::npos) {
  235. fullname += FILESYSTEM_EXTENSIONSEP;
  236. fullname += (*currentExtensions)[0];
  237. }
  238. }
  239. // Store entry back in textbox
  240. filename->state(fullname);
  241. if (saveAs) {
  242. // Does file exist?
  243. FILE* exist = fopen(fullname.c_str(), "rb");
  244. if (exist) {
  245. int result = guiConfirmBox("File already exists- Overwrite?", "Confirm Overwrite");
  246. fclose(exist);
  247. if (!result) return BUTTON_NOTHING;
  248. // Attempt to write to file
  249. FILE* attempt = fopen(fullname.c_str(), "rb+");
  250. if (!attempt) {
  251. guiErrorBox(formatString("Error writing to file- %s", strerror(errno)), errorTitleException);
  252. return BUTTON_NOTHING;
  253. }
  254. fclose(attempt);
  255. }
  256. else {
  257. // Attempt to create file
  258. FILE* attempt = fopen(fullname.c_str(), "wb+");
  259. if (!attempt) {
  260. guiErrorBox(formatString("Error creating file- %s", strerror(errno)), errorTitleException);
  261. return BUTTON_NOTHING;
  262. }
  263. fclose(attempt);
  264. }
  265. }
  266. else {
  267. // Ensure file exists, and if applicable, can be written to
  268. FILE* exist = fopen(fullname.c_str(), openForWrite ? "rb+" : "rb");
  269. if (!exist) {
  270. guiErrorBox(formatString("Error opening file- %s", strerror(errno)), errorTitleException);
  271. return BUTTON_NOTHING;
  272. }
  273. fclose(exist);
  274. }
  275. return Dialog::verifyEntry(buttonId, buttonType);
  276. }
  277. int FileDirDialog::run(int isSaveAs, int isOpenForWrite, const vector<string>& extensions, string& storeFilename) { start_func
  278. // Different screen height?
  279. if ((initialized) && ((initializedH != screenHeight) || (initializedW != screenWidth))) {
  280. filename = NULL;
  281. list = NULL;
  282. clearWidgets();
  283. delete tree;
  284. tree = NULL;
  285. initialized = 0;
  286. }
  287. if (!initialized) {
  288. initializedH = screenHeight;
  289. initializedW = screenWidth;
  290. Widget* w = NULL;
  291. int innerHeight = initializedH - (fontHeight() * 10);
  292. if (innerHeight > 400) innerHeight = 400;
  293. int innerWidth = initializedW - 40;
  294. if (innerWidth > 400) innerWidth = 400;
  295. w = new WStatic(ID_LABEL, "Directory:");
  296. w->addTo(this);
  297. w = new WStatic(ID_LABEL, "Files:");
  298. w->addTo(this);
  299. // w contains pointer so it gets deleted if it doesn't get added
  300. // Must create list here because creating directoryview accesses it
  301. // (in resulting treeview events)
  302. w = list = new WListBox(ID_LIST, &selection, 1, innerWidth / 2, innerHeight / fontHeight());
  303. tree = new DirectoryView(blankString, topDir(), this, treeviewEventWrap, 1);
  304. tree->addTo(this, innerWidth / 2, innerHeight / tree->getItemHeight());
  305. // (wscroll will not delete tree for us)
  306. w->addTo(this);
  307. makePretty(2, 1, 1, 0);
  308. w = new WStatic(ID_LABEL, "Filename:");
  309. innerWidth -= w->getWidth();
  310. w->addTo(this);
  311. w = filename = new WTextBox(ID_ENTRY, &file, 0, innerWidth);
  312. w->addTo(this);
  313. w = new WButton(ID_OK, isSaveAs ? "Save" : "Open", BUTTON_OK);
  314. w->addTo(this);
  315. w = new WButton(ID_CANCEL, messageBoxCancel, BUTTON_CANCEL);
  316. w->addTo(this);
  317. makePretty();
  318. initialized = 1;
  319. }
  320. else {
  321. // Redo treeview
  322. assert(tree);
  323. tree->redo();
  324. // Redo buttons
  325. dynamic_cast<WButton*>(findWidget(ID_OK))->changeText(isSaveAs ? "Save" : "Open");
  326. }
  327. setTitle(isSaveAs ? "Save As" : "Open File");
  328. selection = 0;
  329. file = blankString;
  330. saveAs = isSaveAs;
  331. openForWrite = isOpenForWrite;
  332. currentExtensions = &extensions;
  333. // Split filename and select within tree
  334. vector<string> split;
  335. splitFilename(storeFilename.c_str(), split);
  336. vector<string>::iterator end = split.end();
  337. DirectoryView* currentTree = tree;
  338. int refilledList = 0;
  339. for (vector<string>::iterator pos = split.begin(); pos != end; ++pos) {
  340. currentTree->expand(1);
  341. currentTree = dynamic_cast<DirectoryView*>(currentTree->find(*pos));
  342. if (currentTree) {
  343. currentTree->selectMe();
  344. }
  345. else {
  346. // This will be a final item, check as a filename
  347. refillList(&(*pos));
  348. refilledList = 1;
  349. break;
  350. }
  351. }
  352. // List should be filled if it wasn't during filename scan
  353. if (!refilledList) refillList();
  354. if (runModal() == ID_OK) {
  355. storeFilename = file;
  356. currentExtensions = NULL;
  357. return 1;
  358. }
  359. currentExtensions = NULL;
  360. return 0;
  361. }
  362. int fileOpen(int type, int requireWriteAccess, string& filename) { start_func
  363. assert(type >= 0);
  364. assert(type < FILETYPE_COUNT);
  365. initFileSystems();
  366. if (filename.empty()) filename = previousDirectories[type];
  367. if (fileDialog->run(0, requireWriteAccess, fileExtensions[type], filename)) {
  368. getPathname(filename.c_str(), previousDirectories[type]);
  369. return 1;
  370. }
  371. return 0;
  372. }
  373. int fileSaveAs(int type, string& filename) { start_func
  374. assert(type >= 0);
  375. assert(type < FILETYPE_COUNT);
  376. initFileSystems();
  377. if (filename.empty()) filename = previousDirectories[type];
  378. if (fileDialog->run(1, 1, fileExtensions[type], filename)) {
  379. getPathname(filename.c_str(), previousDirectories[type]);
  380. return 1;
  381. }
  382. return 0;
  383. }