hbfileselector.cpp 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235
  1. /**
  2. * @file hbfileselector.cpp
  3. * @brief The HBFileSelector class definition
  4. *
  5. * $LicenseInfo:firstyear=2014&license=viewergpl$
  6. *
  7. * Copyright (c) 2014, Henri Beauchamp
  8. *
  9. * Second Life Viewer Source Code
  10. * The source code in this file ("Source Code") is provided by Linden Lab
  11. * to you under the terms of the GNU General Public License, version 2.0
  12. * ("GPL"), unless you have obtained a separate licensing agreement
  13. * ("Other License"), formally executed by you and Linden Lab. Terms of
  14. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  15. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  16. *
  17. * There are special exceptions to the terms and conditions of the GPL as
  18. * it is applied to this Source Code. View the full text of the exception
  19. * in the file doc/FLOSS-exception.txt in this software distribution, or
  20. * online at
  21. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  22. *
  23. * By copying, modifying or distributing this software, you acknowledge
  24. * that you have read and understood your obligations described above,
  25. * and agree to abide by those obligations.
  26. *
  27. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  28. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  29. * COMPLETENESS OR PERFORMANCE.
  30. * $/LicenseInfo$
  31. */
  32. #include "linden_common.h"
  33. #include "hbfileselector.h"
  34. #include "llapp.h" // For isExiting()
  35. #include "llcallbacklist.h"
  36. #include "llbutton.h"
  37. #include "llcheckboxctrl.h"
  38. #include "llcombobox.h"
  39. #include "lldate.h"
  40. #include "lldir.h"
  41. #include "lldiriterator.h"
  42. #include "lllineeditor.h"
  43. #include "llscrolllistctrl.h"
  44. #include "llsdserialize.h"
  45. #include "llstring.h"
  46. #include "lltextbox.h"
  47. #include "lluictrlfactory.h"
  48. // Position in a path string where the root delimiter is found, i.e. position
  49. // of "\" in (e.g.) "C:\" for Windows and position of "/" (0) for other OSes
  50. #if LL_WINDOWS
  51. # define ROOT_DELIMITER_POS 2
  52. #else
  53. # define ROOT_DELIMITER_POS 0
  54. #endif
  55. // Static variables
  56. HBFileSelector* HBFileSelector::sInstance = NULL;
  57. HBFileSelector::context_map_t HBFileSelector::sContextToPathMap;
  58. std::string HBFileSelector::sLastPath;
  59. HBFileSelector::HBFileSelector(ELoadFilter filter,
  60. HBLoadFileCallback callback, void* user_data)
  61. : mLoadFilter(filter),
  62. mSaveFilter(FFSAVE_NONE),
  63. mMultiple(false),
  64. mSavePicker(false),
  65. mDirPicker(false),
  66. mLoadFileCallback(callback),
  67. mLoadFilesCallback(NULL),
  68. mSaveFileCallback(NULL),
  69. mDirPickCallback(NULL)
  70. {
  71. init(user_data);
  72. }
  73. HBFileSelector::HBFileSelector(ELoadFilter filter,
  74. HBLoadFilesCallback callback, void* user_data)
  75. : mLoadFilter(filter),
  76. mSaveFilter(FFSAVE_NONE),
  77. mMultiple(true),
  78. mSavePicker(false),
  79. mDirPicker(false),
  80. mLoadFileCallback(NULL),
  81. mLoadFilesCallback(callback),
  82. mSaveFileCallback(NULL),
  83. mDirPickCallback(NULL)
  84. {
  85. init(user_data);
  86. }
  87. HBFileSelector::HBFileSelector(ESaveFilter filter, std::string& suggestion,
  88. HBSaveFileCallback callback, void* user_data)
  89. : mLoadFilter(FFLOAD_NONE),
  90. mSaveFilter(filter),
  91. mMultiple(false),
  92. mSavePicker(true),
  93. mDirPicker(false),
  94. mLoadFileCallback(NULL),
  95. mLoadFilesCallback(NULL),
  96. mSaveFileCallback(callback),
  97. mDirPickCallback(NULL),
  98. mCurrentEntry(suggestion)
  99. {
  100. init(user_data);
  101. }
  102. HBFileSelector::HBFileSelector(std::string& suggestion,
  103. HBDirPickCallback callback, void* user_data)
  104. : mLoadFilter(FFLOAD_NONE),
  105. mSaveFilter(FFSAVE_NONE),
  106. mMultiple(false),
  107. mSavePicker(false),
  108. mDirPicker(true),
  109. mLoadFileCallback(NULL),
  110. mLoadFilesCallback(NULL),
  111. mSaveFileCallback(NULL),
  112. mDirPickCallback(callback),
  113. mCurrentEntry(suggestion)
  114. {
  115. init(user_data);
  116. }
  117. void HBFileSelector::init(void* user_data)
  118. {
  119. sInstance = this;
  120. mCallbackUserData = user_data;
  121. mCallbackDone = mIsDirty = mCreatingDirectory = false;
  122. mContext = CONTEXT_UNKNOWN;
  123. std::string xui_file = mDirPicker ? "floater_dirselector.xml"
  124. : "floater_fileselector.xml";
  125. LLUICtrlFactory::getInstance()->buildFloater(this, xui_file);
  126. }
  127. HBFileSelector::~HBFileSelector()
  128. {
  129. if (!mCallbackDone && !LLApp::isExiting())
  130. {
  131. mCurrentSelection.clear();
  132. mFiles.clear();
  133. doCallback();
  134. }
  135. sInstance = NULL;
  136. }
  137. bool HBFileSelector::postBuild()
  138. {
  139. mDirectoriesList = getChild<LLScrollListCtrl>("directories");
  140. if (mDirPicker)
  141. {
  142. mDirectoriesList->setCommitOnSelectionChange(true);
  143. mDirectoriesList->setCommitCallback(onSelectDirectory);
  144. }
  145. mDirectoriesList->setDoubleClickCallback(onLevelDown);
  146. mDirectoriesList->setCallbackUserData(this);
  147. if (mDirPicker)
  148. {
  149. mFilesList = NULL;
  150. mShowAllTypesCheck = NULL;
  151. }
  152. else
  153. {
  154. mFilesList = getChild<LLScrollListCtrl>("files");
  155. mFilesList->setAllowMultipleSelection(mMultiple);
  156. mFilesList->setCommitOnSelectionChange(true);
  157. mFilesList->setCommitCallback(onSelectFile);
  158. mFilesList->setDoubleClickCallback(onButtonOK);
  159. mFilesList->setCallbackUserData(this);
  160. mShowAllTypesCheck = getChild<LLCheckBoxCtrl>("all_files");
  161. mShowAllTypesCheck->setCommitCallback(onCommitCheckBox);
  162. mShowAllTypesCheck->setCallbackUserData(this);
  163. setValidExtensions();
  164. }
  165. mPromptTextBox = getChild<LLTextBox>("prompt");
  166. mPathTextBox = getChild<LLTextBox>("path");
  167. mInputLine = getChild<LLLineEditor>("selection");
  168. mInputLine->setOnHandleKeyCallback(onHandleKeyCallback, this);
  169. mInputLine->setKeystrokeCallback(onKeystrokeCallback);
  170. mInputLine->setCallbackUserData(this);
  171. mInputLine->setEnabled(mSavePicker);
  172. mShowHiddenCheck = getChild<LLCheckBoxCtrl>("show_hidden");
  173. mShowHiddenCheck->setCommitCallback(onCommitCheckBox);
  174. mShowHiddenCheck->setCallbackUserData(this);
  175. mDirLevelFlyoutBtn = getChild<LLFlyoutButton>("dir_level");
  176. mDirLevelFlyoutBtn->setCommitCallback(onButtonDirLevel);
  177. mDirLevelFlyoutBtn->setCallbackUserData(this);
  178. mCreateBtn = getChild<LLButton>("create");
  179. mCreateBtn->setClickedCallback(onButtonCreate, this);
  180. mCreateBtn->setEnabled(mSavePicker || mDirPicker);
  181. mRefreshBtn = getChild<LLButton>("refresh");
  182. mRefreshBtn->setClickedCallback(onButtonRefresh, this);
  183. mCancelBtn = getChild<LLButton>("cancel");
  184. mCancelBtn->setClickedCallback(onButtonCancel, this);
  185. mOKBtn = getChild<LLButton>("ok");
  186. mOKBtn->setClickedCallback(onButtonOK, this);
  187. setPathFromContext();
  188. setPrompt();
  189. mIsDirty = true;
  190. return true;
  191. }
  192. void HBFileSelector::draw()
  193. {
  194. if (mIsDirty)
  195. {
  196. mPathTextBox->setText(mCurrentPath);
  197. mDirectoriesList->deleteAllItems();
  198. if (mFilesList)
  199. {
  200. mFilesList->deleteAllItems();
  201. }
  202. std::string timeformat;
  203. if (LLUI::sConfigGroup)
  204. {
  205. timeformat = LLUI::sConfigGroup->getString("ShortDateFormat") +
  206. " " +
  207. LLUI::sConfigGroup->getString("LongTimeFormat");
  208. }
  209. #if !LL_WINDOWS
  210. std::string path = mCurrentPath + LL_DIR_DELIM_STR;
  211. #else
  212. std::string path;
  213. if (!mCurrentPath.empty())
  214. {
  215. path = mCurrentPath + LL_DIR_DELIM_STR;
  216. }
  217. #endif
  218. std::string filename;
  219. LLSD element;
  220. LLUUID id, selected_id;
  221. bool selection_is_dir = false;
  222. #if !LL_WINDOWS
  223. if (!mCurrentEntry.empty() && mCurrentEntry[0] == '.')
  224. {
  225. mShowHiddenCheck->set(true);
  226. }
  227. #endif
  228. bool with_hidden = mShowHiddenCheck->get();
  229. LLDirIterator iter(path, NULL, DI_ALL);
  230. while (iter.next(filename))
  231. {
  232. if (!with_hidden && iter.isHidden())
  233. {
  234. continue; // Do not list hidden entries
  235. }
  236. bool is_dir = iter.isDirectory();
  237. element.clear();
  238. id.generate();
  239. if (filename == mCurrentEntry)
  240. {
  241. selected_id = id;
  242. selection_is_dir = is_dir;
  243. }
  244. element["id"] = id;
  245. if (is_dir)
  246. {
  247. element["columns"][0]["column"] = "dirname_col";
  248. element["columns"][0]["value"] = filename;
  249. if (iter.isLink())
  250. {
  251. element["columns"][0]["font-style"] = "ITALIC";
  252. }
  253. mDirectoriesList->addElement(element, ADD_SORTED);
  254. }
  255. else if (mFilesList && isFileExtensionValid(filename))
  256. {
  257. element["columns"][0]["column"] = "name_col";
  258. element["columns"][0]["value"] = filename;
  259. if (iter.isLink())
  260. {
  261. element["columns"][0]["font-style"] = "ITALIC";
  262. }
  263. element["columns"][1]["column"] = "size_col";
  264. element["columns"][1]["value"] = (LLSD::Integer)iter.getSize();
  265. element["columns"][2]["column"] = "date_col";
  266. element["columns"][2]["type"] = "date";
  267. element["columns"][2]["format"] = timeformat;
  268. element["columns"][2]["value"] = LLDate(iter.getTimeStamp());
  269. mFilesList->addElement(element, ADD_SORTED);
  270. }
  271. }
  272. // Set the current selection, if any:
  273. if (mCurrentEntry.empty())
  274. {
  275. mInputLine->clear();
  276. }
  277. else
  278. {
  279. mInputLine->setText(mCurrentEntry);
  280. mInputLine->setCursorToEnd();
  281. bool got_it = mSavePicker;
  282. if (selected_id.notNull())
  283. {
  284. if (selection_is_dir)
  285. {
  286. if (mDirPicker &&
  287. mDirectoriesList->selectByID(selected_id))
  288. {
  289. mDirectoriesList->scrollToShowSelected();
  290. got_it = true;
  291. }
  292. }
  293. else if (mFilesList && mFilesList->selectByID(selected_id))
  294. {
  295. mFilesList->scrollToShowSelected();
  296. got_it = true;
  297. }
  298. }
  299. if (!got_it)
  300. {
  301. mCurrentEntry.clear();
  302. }
  303. }
  304. mIsDirty = false;
  305. }
  306. LLFloater::draw();
  307. }
  308. void HBFileSelector::setValidExtensions()
  309. {
  310. mContext = CONTEXT_DEFAULT;
  311. mValidExtensions.clear();
  312. if (mSavePicker)
  313. {
  314. mFileTypeDescription = getString("any_file") + " (*.*)";
  315. }
  316. else if (!mDirPicker)
  317. {
  318. mFileTypeDescription = getString("all_files") + " (*.*)";
  319. }
  320. if (!mShowAllTypesCheck || mShowAllTypesCheck->get())
  321. {
  322. return;
  323. }
  324. if (mSavePicker && mSaveFilter != FFSAVE_NONE && mSaveFilter != FFSAVE_ALL)
  325. {
  326. switch (mSaveFilter)
  327. {
  328. case FFSAVE_TXT:
  329. mValidExtensions.emplace_back("txt");
  330. mFileTypeDescription = getString("txt_file") + " (*.txt)";
  331. mContext = CONTEXT_TXT;
  332. break;
  333. case FFSAVE_XML:
  334. mValidExtensions.emplace_back("xml");
  335. mFileTypeDescription = getString("xml_file") + " (*.xml)";
  336. mContext = CONTEXT_XML;
  337. break;
  338. case FFSAVE_XUI:
  339. mValidExtensions.emplace_back("xml");
  340. mFileTypeDescription = getString("xui_file") + " (*.xml)";
  341. mContext = CONTEXT_XUI;
  342. break;
  343. case FFSAVE_LSL:
  344. mValidExtensions.emplace_back("lsl");
  345. mFileTypeDescription = getString("lsl_file") + " (*.lsl)";
  346. mContext = CONTEXT_LSL;
  347. break;
  348. case FFSAVE_WAV:
  349. mValidExtensions.emplace_back("wav");
  350. mFileTypeDescription = getString("wav_file") + " (*.wav)";
  351. mContext = CONTEXT_SOUND;
  352. break;
  353. case FFSAVE_BVH:
  354. mValidExtensions.emplace_back("bvh");
  355. mFileTypeDescription = getString("bvh_file") + " (*.bvh)";
  356. mContext = CONTEXT_ANIM;
  357. break;
  358. case FFSAVE_DAE:
  359. mValidExtensions.emplace_back("dae");
  360. mFileTypeDescription = getString("dae_file") + " (*.dae)";
  361. mContext = CONTEXT_MODEL;
  362. break;
  363. case FFSAVE_OBJ:
  364. mValidExtensions.emplace_back("obj");
  365. mFileTypeDescription = getString("obj_file") + " (*.obj)";
  366. mContext = CONTEXT_MODEL;
  367. break;
  368. case FFSAVE_RAW:
  369. mValidExtensions.emplace_back("raw");
  370. mFileTypeDescription = getString("raw_file") + " (*.raw)";
  371. mContext = CONTEXT_RAW;
  372. break;
  373. case FFSAVE_TGA:
  374. mValidExtensions.emplace_back("tga");
  375. mFileTypeDescription = getString("tga_file") + " (*.tga)";
  376. mContext = CONTEXT_IMAGE;
  377. break;
  378. case FFSAVE_PNG:
  379. mValidExtensions.emplace_back("png");
  380. mFileTypeDescription = getString("png_file") + " (*.png)";
  381. mContext = CONTEXT_IMAGE;
  382. break;
  383. case FFSAVE_JPG:
  384. mValidExtensions.emplace_back("jpg");
  385. mFileTypeDescription = getString("jpg_file") +
  386. " (*.jpg;*.jpeg)";
  387. mContext = CONTEXT_IMAGE;
  388. break;
  389. case FFSAVE_J2C:
  390. mValidExtensions.emplace_back("j2c");
  391. mFileTypeDescription = getString("j2c_file") + " (*.j2c)";
  392. mContext = CONTEXT_IMAGE;
  393. break;
  394. case FFSAVE_BMP:
  395. mValidExtensions.emplace_back("bmp");
  396. mFileTypeDescription = getString("bmp_file") + " (*.bmp)";
  397. mContext = CONTEXT_IMAGE;
  398. break;
  399. case FFSAVE_GLTF:
  400. mValidExtensions.emplace_back("glb");
  401. mFileTypeDescription = getString("gltf_file") + " (*.glb)";
  402. mContext = CONTEXT_MATERIAL;
  403. break;
  404. default:
  405. break;
  406. }
  407. }
  408. else if (mLoadFilter != FFLOAD_NONE && mLoadFilter != FFLOAD_ALL)
  409. {
  410. switch (mLoadFilter)
  411. {
  412. case FFLOAD_TEXT:
  413. mValidExtensions.emplace_back("txt");
  414. mFileTypeDescription = getString("text_files") + " (*.txt)";
  415. mContext = CONTEXT_TXT;
  416. break;
  417. case FFLOAD_XML:
  418. mValidExtensions.emplace_back("xml");
  419. mFileTypeDescription = getString("xml_files") + " (*.xml)";
  420. mContext = CONTEXT_XML;
  421. break;
  422. case FFLOAD_XUI:
  423. mValidExtensions.emplace_back("xml");
  424. mFileTypeDescription = getString("xui_files") + " (*.xml)";
  425. mContext = CONTEXT_XUI;
  426. break;
  427. case FFLOAD_SCRIPT:
  428. mValidExtensions.emplace_back("lsl");
  429. mFileTypeDescription = getString("script_files") + " (*.lsl)";
  430. mContext = CONTEXT_LSL;
  431. break;
  432. case FFLOAD_SOUND:
  433. mValidExtensions.emplace_back("wav");
  434. mValidExtensions.emplace_back("dsf");
  435. mFileTypeDescription = getString("sound_files") +
  436. " (*.wav;*.dsf)";
  437. mContext = CONTEXT_SOUND;
  438. break;
  439. case FFLOAD_ANIM:
  440. mValidExtensions.emplace_back("bvh");
  441. mValidExtensions.emplace_back("anim");
  442. mFileTypeDescription = getString("animation_files") +
  443. " (*.bvh;*.anim)";
  444. mContext = CONTEXT_ANIM;
  445. break;
  446. case FFLOAD_MODEL:
  447. mValidExtensions.emplace_back("dae");
  448. mValidExtensions.emplace_back("glb");
  449. mValidExtensions.emplace_back("gltf");
  450. mFileTypeDescription = getString("model_files") +
  451. " (*.dae;*.glb;*.gltf)";
  452. mContext = CONTEXT_MODEL;
  453. break;
  454. case FFLOAD_TERRAIN:
  455. mValidExtensions.emplace_back("raw");
  456. mFileTypeDescription = getString("raw_files") + " (*.raw)";
  457. mContext = CONTEXT_RAW;
  458. break;
  459. case FFLOAD_IMAGE:
  460. mValidExtensions.emplace_back("tga");
  461. mValidExtensions.emplace_back("png");
  462. mValidExtensions.emplace_back("jpg");
  463. mValidExtensions.emplace_back("jpeg");
  464. mValidExtensions.emplace_back("bmp");
  465. mFileTypeDescription = getString("image_files") +
  466. " (*.tga;*.png;*.jpg;*.jpeg;*.bmp)";
  467. mContext = CONTEXT_IMAGE;
  468. break;
  469. case FFLOAD_HDRI:
  470. mValidExtensions.emplace_back("exr");
  471. mFileTypeDescription = getString("exr_file") + " (*.exr)";
  472. mContext = CONTEXT_IMAGE;
  473. break;
  474. case FFLOAD_LUA:
  475. mValidExtensions.emplace_back("lua");
  476. mValidExtensions.emplace_back("luac");
  477. mFileTypeDescription = getString("lua_files") +
  478. " (*.lua;*.luac)";
  479. mContext = CONTEXT_LUA;
  480. break;
  481. case FFLOAD_GLTF:
  482. mValidExtensions.emplace_back("glb");
  483. mValidExtensions.emplace_back("gltf");
  484. mFileTypeDescription = getString("gltf_files") +
  485. " (*.glb;*.gltf)";
  486. mContext = CONTEXT_MATERIAL;
  487. break;
  488. default:
  489. break;
  490. }
  491. }
  492. }
  493. void HBFileSelector::setPrompt()
  494. {
  495. std::string prompt;
  496. bool got_file_info = !mFileTypeDescription.empty();
  497. if (mCreatingDirectory)
  498. {
  499. prompt = getString("new_directory");
  500. }
  501. else if (mSavePicker && got_file_info)
  502. {
  503. prompt = getString("prompt_save") + " " + mFileTypeDescription;
  504. }
  505. else if (mMultiple && got_file_info)
  506. {
  507. prompt = getString("prompt_load_multiple") + " " +
  508. mFileTypeDescription;
  509. }
  510. else if (!mMultiple && !mSavePicker && got_file_info)
  511. {
  512. prompt = getString("prompt_load_one") + " " + mFileTypeDescription;
  513. }
  514. else
  515. {
  516. prompt = getString("default_prompt");
  517. }
  518. mPromptTextBox->setText(prompt);
  519. if (mSaveFilter == FFSAVE_NONE)
  520. {
  521. mPromptTextBox->setColor(LLColor4::green);
  522. }
  523. else
  524. {
  525. mPromptTextBox->setColor(LLColor4::yellow);
  526. }
  527. }
  528. bool HBFileSelector::isFileExtensionValid(const std::string& filename)
  529. {
  530. if (mValidExtensions.empty() ||
  531. (mShowAllTypesCheck && mShowAllTypesCheck->get()))
  532. {
  533. return true;
  534. }
  535. std::string tmp = filename;
  536. LLStringUtil::toLower(tmp);
  537. size_t j = tmp.rfind('.');
  538. if (j == std::string::npos || j == tmp.length() - 1)
  539. {
  540. return false;
  541. }
  542. tmp = tmp.substr(j + 1);
  543. for (U32 i = 0, count = mValidExtensions.size(); i < count; ++i)
  544. {
  545. if (mValidExtensions[i] == tmp)
  546. {
  547. return true;
  548. }
  549. }
  550. return false;
  551. }
  552. void HBFileSelector::setPathFromContext()
  553. {
  554. if (mDirPicker)
  555. {
  556. std::string tmp = mCurrentEntry;
  557. if (!tmp.empty())
  558. {
  559. // Remove trailing delimiter(s)
  560. while (tmp.length() &&
  561. tmp.rfind(LL_DIR_DELIM_CHR) == tmp.length() - 1)
  562. {
  563. tmp = tmp.substr(0, tmp.length() - 1);
  564. }
  565. size_t i = tmp.rfind(LL_DIR_DELIM_CHR);
  566. if (i != std::string::npos && i >= 1)
  567. {
  568. // Suggested directory selection
  569. mCurrentEntry = tmp.substr(i + 1);
  570. // Parent directory path
  571. mCurrentPath = tmp.substr(0, i);
  572. }
  573. else
  574. {
  575. // Suggested directory selection = root directory
  576. mCurrentEntry = tmp;
  577. mCurrentPath.clear();
  578. }
  579. }
  580. else
  581. {
  582. mCurrentPath = sLastPath;
  583. }
  584. }
  585. else
  586. {
  587. context_map_t::iterator it = sContextToPathMap.find(mContext);
  588. if (it != sContextToPathMap.end())
  589. {
  590. mCurrentPath = it->second;
  591. }
  592. else
  593. {
  594. mCurrentPath.clear();
  595. }
  596. // If the saved path is not valid any more, try to find the deepest
  597. // directory that used to contain it...
  598. while (!mCurrentPath.empty() && !LLFile::exists(mCurrentPath))
  599. {
  600. size_t i = mCurrentPath.rfind(LL_DIR_DELIM_CHR);
  601. if (i == std::string::npos || i == ROOT_DELIMITER_POS)
  602. {
  603. mCurrentPath.clear();
  604. break;
  605. }
  606. mCurrentPath.erase(i);
  607. }
  608. if (mCurrentPath.empty())
  609. {
  610. if (mContext == CONTEXT_XUI)
  611. {
  612. #define XUI_DIR LL_DIR_DELIM_STR "xui" LL_DIR_DELIM_STR "en-us"
  613. mCurrentPath = gDirUtil.getSkinDir() + XUI_DIR;
  614. }
  615. else
  616. {
  617. mCurrentPath = sLastPath;
  618. }
  619. }
  620. }
  621. if (mCurrentPath.empty() || !LLFile::exists(mCurrentPath))
  622. {
  623. if (!sLastPath.empty() && LLFile::exists(sLastPath))
  624. {
  625. mCurrentPath = sLastPath;
  626. }
  627. else
  628. {
  629. mCurrentPath = gDirUtil.getOSUserDir();
  630. }
  631. }
  632. isCurrentPathAtRoot();
  633. }
  634. bool HBFileSelector::isCurrentPathAtRoot()
  635. {
  636. // Remove trailing delimiter(s)
  637. mCurrentPath.erase(mCurrentPath.find_last_not_of(LL_DIR_DELIM_CHR) + 1);
  638. return mCurrentPath.empty();
  639. }
  640. void HBFileSelector::setSelectionData()
  641. {
  642. mFiles.clear();
  643. #if !LL_WINDOWS
  644. std::string path = mCurrentPath + LL_DIR_DELIM_STR;
  645. #else
  646. std::string path;
  647. if (!mCurrentPath.empty())
  648. {
  649. path = mCurrentPath + LL_DIR_DELIM_STR;
  650. }
  651. #endif
  652. std::string filename;
  653. LLScrollListItem* item = NULL;
  654. if (mDirPicker)
  655. {
  656. item = mDirectoriesList->getFirstSelected();
  657. }
  658. else if (mFilesList)
  659. {
  660. if (mMultiple)
  661. {
  662. std::vector<LLScrollListItem*> items;
  663. items = mFilesList->getAllSelected();
  664. if (!items.empty())
  665. {
  666. for (std::vector<LLScrollListItem*>::const_iterator
  667. iter = items.begin(), end = items.end();
  668. iter != end; ++iter)
  669. {
  670. item = *iter;
  671. if (item && item->getColumn(0))
  672. {
  673. filename = item->getColumn(0)->getValue().asString();
  674. mFiles.emplace_back(path + filename);
  675. }
  676. }
  677. }
  678. }
  679. item = mFilesList->getFirstSelected();
  680. }
  681. if (item && item->getColumn(0))
  682. {
  683. filename = item->getColumn(0)->getValue().asString();
  684. mCurrentSelection = path + filename;
  685. mInputLine->setText(filename);
  686. }
  687. else if ((mDirPicker || mSavePicker) && !mInputLine->getText().empty())
  688. {
  689. mCurrentSelection = path + mInputLine->getText();
  690. }
  691. }
  692. void HBFileSelector::doCallback()
  693. {
  694. if (!mCallbackDone)
  695. {
  696. mCallbackDone = true;
  697. if (mLoadFileCallback)
  698. {
  699. mLoadFileCallback(mLoadFilter, mCurrentSelection,
  700. mCallbackUserData);
  701. }
  702. else if (mLoadFilesCallback)
  703. {
  704. mLoadFilesCallback(mLoadFilter, mFiles, mCallbackUserData);
  705. }
  706. else if (mSaveFileCallback)
  707. {
  708. if (!mCurrentSelection.empty() &&
  709. !isFileExtensionValid(mCurrentSelection))
  710. {
  711. mCurrentSelection += "." + mValidExtensions[0];
  712. }
  713. mSaveFileCallback(mSaveFilter, mCurrentSelection,
  714. mCallbackUserData);
  715. }
  716. else if (mDirPickCallback)
  717. {
  718. mDirPickCallback(mCurrentSelection, mCallbackUserData);
  719. }
  720. }
  721. }
  722. //static
  723. void HBFileSelector::loadFile(ELoadFilter filter,
  724. HBLoadFileCallback callback, void* user_data)
  725. {
  726. if (sInstance)
  727. {
  728. llwarns << "Call done while a file selector instance already exists ! Aborting."
  729. << llendl;
  730. llassert(false);
  731. }
  732. else
  733. {
  734. new HBFileSelector(filter, callback, user_data);
  735. }
  736. }
  737. //static
  738. void HBFileSelector::loadFiles(ELoadFilter filter,
  739. HBLoadFilesCallback callback, void* user_data)
  740. {
  741. if (sInstance)
  742. {
  743. llwarns << "Call done while a file selector instance already exists ! Aborting."
  744. << llendl;
  745. llassert(false);
  746. }
  747. else
  748. {
  749. new HBFileSelector(filter, callback, user_data);
  750. }
  751. }
  752. //static
  753. void HBFileSelector::saveFile(ESaveFilter filter, std::string suggestion,
  754. HBSaveFileCallback callback, void* user_data)
  755. {
  756. if (sInstance)
  757. {
  758. llwarns << "Call done while a file selector instance already exists ! Aborting."
  759. << llendl;
  760. llassert(false);
  761. }
  762. else
  763. {
  764. new HBFileSelector(filter, suggestion, callback, user_data);
  765. }
  766. }
  767. //static
  768. void HBFileSelector::pickDirectory(std::string suggestion,
  769. HBDirPickCallback callback, void* user_data)
  770. {
  771. if (sInstance)
  772. {
  773. llwarns << "Call done while a file selector instance already exists ! Aborting."
  774. << llendl;
  775. llassert(false);
  776. }
  777. else
  778. {
  779. new HBFileSelector(suggestion, callback, user_data);
  780. }
  781. }
  782. //static
  783. void HBFileSelector::onButtonRefresh(void* user_data)
  784. {
  785. HBFileSelector* self = (HBFileSelector*)user_data;
  786. if (self)
  787. {
  788. if (self->mSavePicker && !self->mInputLine->getText().empty())
  789. {
  790. self->mCurrentEntry = self->mInputLine->getText();
  791. }
  792. self->mIsDirty = true;
  793. }
  794. }
  795. //static
  796. void HBFileSelector::onButtonDirLevel(LLUICtrl* ctrl, void* user_data)
  797. {
  798. HBFileSelector* self = (HBFileSelector*)user_data;
  799. if (self && ctrl)
  800. {
  801. if (self->mDirPicker)
  802. {
  803. self->mCurrentEntry.clear();
  804. }
  805. std::string operation = ctrl->getValue().asString();
  806. if (operation == "home")
  807. {
  808. self->mCurrentPath = gDirUtil.getOSUserDir();
  809. self->isCurrentPathAtRoot();
  810. self->mIsDirty = true;
  811. }
  812. else if (operation == "suggested")
  813. {
  814. // *TODO: implement for directory selector as well ?
  815. if (!self->mDirPicker)
  816. {
  817. self->setPathFromContext();
  818. self->mIsDirty = true;
  819. }
  820. }
  821. else if (operation == "last")
  822. {
  823. if (!sLastPath.empty() && LLFile::exists(sLastPath))
  824. {
  825. self->mCurrentPath = sLastPath;
  826. self->mIsDirty = true;
  827. }
  828. }
  829. else if (operation == "root")
  830. {
  831. if (!self->isCurrentPathAtRoot())
  832. {
  833. #if LL_WINDOWS
  834. self->mCurrentPath.clear();
  835. #else
  836. self->mCurrentPath = LL_DIR_DELIM_STR;
  837. #endif
  838. self->isCurrentPathAtRoot();
  839. self->mIsDirty = true;
  840. }
  841. }
  842. else if (!self->isCurrentPathAtRoot())
  843. {
  844. // "level_up" operation
  845. size_t i = self->mCurrentPath.rfind(LL_DIR_DELIM_CHR);
  846. if (i != std::string::npos)
  847. {
  848. self->mCurrentPath = self->mCurrentPath.substr(0, i);
  849. self->mIsDirty = true;
  850. }
  851. #if LL_WINDOWS
  852. else
  853. {
  854. self->mCurrentPath.clear();
  855. self->mIsDirty = true;
  856. }
  857. #endif
  858. self->isCurrentPathAtRoot();
  859. }
  860. }
  861. }
  862. //static
  863. void HBFileSelector::onButtonCreate(void* user_data)
  864. {
  865. HBFileSelector* self = (HBFileSelector*)user_data;
  866. if (self)
  867. {
  868. std::string entry = self->mInputLine->getText();
  869. if (!entry.empty())
  870. {
  871. self->mInputLine->clear();
  872. self->mCurrentEntry = entry;
  873. }
  874. self->mInputLine->setEnabled(true);
  875. self->mCreatingDirectory = true;
  876. self->mDirLevelFlyoutBtn->setEnabled(false);
  877. self->mCreateBtn->setEnabled(false);
  878. self->mRefreshBtn->setEnabled(false);
  879. self->mOKBtn->setEnabled(false);
  880. self->mShowHiddenCheck->setEnabled(false);
  881. if (self->mShowAllTypesCheck)
  882. {
  883. self->mShowAllTypesCheck->setEnabled(false);
  884. }
  885. self->mDirectoriesList->setEnabled(false);
  886. if (self->mFilesList)
  887. {
  888. self->mFilesList->setEnabled(false);
  889. }
  890. self->setPrompt();
  891. }
  892. }
  893. static void close_selector(HBFileSelector* floaterp)
  894. {
  895. floaterp->close();
  896. }
  897. //static
  898. void HBFileSelector::onButtonOK(void* user_data)
  899. {
  900. HBFileSelector* self = (HBFileSelector*)user_data;
  901. if (self)
  902. {
  903. self->setSelectionData();
  904. sLastPath = self->mCurrentPath;
  905. sContextToPathMap[self->mContext] = sLastPath;
  906. #if LL_WINDOWS
  907. // If we are at the drive selection level, we cannot return a selection
  908. if (sLastPath.empty())
  909. {
  910. self->mFiles.clear();
  911. self->mCurrentSelection.clear();
  912. }
  913. #endif
  914. self->doCallback();
  915. // We cannot close the floater now, because it would mean destroying
  916. // this instance while this method is also called for keyboard events
  917. // occurring at a point its member variables would be used later on,
  918. // causing a crash. So we instead hide the floater (so that nothing any
  919. // more can be performed using it) and we register a one-shot idle
  920. // callback to the close_selector() function.
  921. self->setVisible(false);
  922. doOnIdleOneTime(boost::bind(&close_selector, self));
  923. }
  924. }
  925. //static
  926. void HBFileSelector::onButtonCancel(void* user_data)
  927. {
  928. HBFileSelector* self = (HBFileSelector*)user_data;
  929. if (self)
  930. {
  931. self->close();
  932. }
  933. }
  934. //static
  935. void HBFileSelector::onSelectDirectory(LLUICtrl*, void* user_data)
  936. {
  937. HBFileSelector* self = (HBFileSelector*)user_data;
  938. if (self)
  939. {
  940. self->mCurrentEntry = self->mInputLine->getText();
  941. self->setSelectionData();
  942. }
  943. }
  944. //static
  945. void HBFileSelector::onLevelDown(void* user_data)
  946. {
  947. HBFileSelector* self = (HBFileSelector*)user_data;
  948. if (self && self->mDirectoriesList)
  949. {
  950. if (self->mDirPicker)
  951. {
  952. self->mCurrentEntry.clear();
  953. }
  954. LLScrollListItem* item = self->mDirectoriesList->getFirstSelected();
  955. if (item && item->getColumn(0))
  956. {
  957. #if LL_WINDOWS
  958. if (!self->mCurrentPath.empty())
  959. #endif
  960. {
  961. self->mCurrentPath += LL_DIR_DELIM_STR;
  962. }
  963. self->mCurrentPath += item->getColumn(0)->getValue().asString();
  964. self->mDirectoriesList->deselectAllItems(true);
  965. self->isCurrentPathAtRoot();
  966. self->mIsDirty = true;
  967. }
  968. }
  969. }
  970. //static
  971. void HBFileSelector::onSelectFile(LLUICtrl*, void* user_data)
  972. {
  973. HBFileSelector* self = (HBFileSelector*)user_data;
  974. if (self)
  975. {
  976. self->mCurrentEntry = self->mInputLine->getText();
  977. self->setSelectionData();
  978. }
  979. }
  980. //static
  981. void HBFileSelector::onCommitCheckBox(LLUICtrl*, void* user_data)
  982. {
  983. HBFileSelector* self = (HBFileSelector*)user_data;
  984. if (self)
  985. {
  986. self->mIsDirty = true;
  987. }
  988. }
  989. //static
  990. bool HBFileSelector::onHandleKeyCallback(KEY key, MASK mask,
  991. LLLineEditor* caller, void* user_data)
  992. {
  993. bool handled = false;
  994. HBFileSelector* self = (HBFileSelector*)user_data;
  995. if (self && key == KEY_RETURN && mask == MASK_NONE)
  996. {
  997. if (self->mCreatingDirectory)
  998. {
  999. self->mCreatingDirectory = false;
  1000. std::string new_dir = self->mInputLine->getText();
  1001. if (!new_dir.empty())
  1002. {
  1003. std::string dir_name = self->mInputLine->getText();
  1004. std::string new_dir = self->mCurrentPath + LL_DIR_DELIM_STR +
  1005. dir_name;
  1006. LLFile::mkdir(new_dir);
  1007. if (LLFile::isdir(new_dir))
  1008. {
  1009. if (self->mDirPicker)
  1010. {
  1011. // Adopt the new directory as the current entry
  1012. self->mCurrentEntry = dir_name;
  1013. }
  1014. else
  1015. {
  1016. // Change to the newly created directory
  1017. self->mCurrentPath = new_dir;
  1018. self->mDirectoriesList->deselectAllItems(true);
  1019. self->isCurrentPathAtRoot();
  1020. // Restore file suggestion, if any
  1021. self->mInputLine->setText(self->mCurrentEntry);
  1022. }
  1023. }
  1024. }
  1025. if (!self->mSavePicker)
  1026. {
  1027. self->mInputLine->setEnabled(false);
  1028. }
  1029. self->mDirLevelFlyoutBtn->setEnabled(true);
  1030. self->mCreateBtn->setEnabled(true);
  1031. self->mRefreshBtn->setEnabled(true);
  1032. self->mOKBtn->setEnabled(true);
  1033. self->mShowHiddenCheck->setEnabled(true);
  1034. if (self->mShowAllTypesCheck)
  1035. {
  1036. self->mShowAllTypesCheck->setEnabled(true);
  1037. }
  1038. self->mDirectoriesList->setEnabled(true);
  1039. if (self->mFilesList)
  1040. {
  1041. self->mFilesList->setEnabled(true);
  1042. }
  1043. self->setPrompt();
  1044. self->mIsDirty = true;
  1045. handled = true;
  1046. }
  1047. else if (self->mSavePicker)
  1048. {
  1049. self->setSelectionData();
  1050. onButtonOK(self);
  1051. handled = true;
  1052. }
  1053. }
  1054. return handled;
  1055. }
  1056. //static
  1057. void HBFileSelector::onKeystrokeCallback(LLLineEditor* caller, void* user_data)
  1058. {
  1059. HBFileSelector* self = (HBFileSelector*)user_data;
  1060. if (self && caller && caller->getEnabled())
  1061. {
  1062. // We must deselect any selected entry if we just typed a new letter,
  1063. // else the selected entry would override what the user entered in the
  1064. // input line whenever a suggested file/dir name corresponds to an
  1065. // existing file/dir...
  1066. if (self->mDirPicker)
  1067. {
  1068. self->mDirectoriesList->deselectAllItems();
  1069. }
  1070. else if (self->mSavePicker && !self->mCreatingDirectory)
  1071. {
  1072. self->mFilesList->deselectAllItems();
  1073. }
  1074. }
  1075. }
  1076. //static
  1077. void HBFileSelector::saveDefaultPaths(const std::string& filename)
  1078. {
  1079. std::string fullpath = gDirUtil.getFullPath(LL_PATH_USER_SETTINGS,
  1080. filename);
  1081. llofstream out(fullpath.c_str());
  1082. if (!out.is_open())
  1083. {
  1084. llwarns << "Unable to open \"" << fullpath
  1085. << "\" for writing. Default paths not saved." << llendl;
  1086. return;
  1087. }
  1088. llinfos << "Saving default selector paths to: " << fullpath << llendl;
  1089. LLSD data;
  1090. context_map_t::iterator end = sContextToPathMap.end();
  1091. for (S32 context = CONTEXT_DEFAULT; context < CONTEXT_END; ++context)
  1092. {
  1093. std::string path;
  1094. context_map_t::iterator it = sContextToPathMap.find(context);
  1095. if (it != end)
  1096. {
  1097. path = it->second;
  1098. }
  1099. data.set(context, path);
  1100. }
  1101. LLSDSerialize::toPrettyXML(data, out);
  1102. out.close();
  1103. }
  1104. //static
  1105. void HBFileSelector::loadDefaultPaths(const std::string& filename)
  1106. {
  1107. std::string fullpath = gDirUtil.getFullPath(LL_PATH_USER_SETTINGS,
  1108. filename);
  1109. LLSD data;
  1110. llifstream input(fullpath.c_str());
  1111. if (input.is_open())
  1112. {
  1113. llinfos << "Loading default selector paths from: " << fullpath
  1114. << llendl;
  1115. LLSDSerialize::fromXML(data, input);
  1116. }
  1117. if (data.isUndefined() || !data.isArray())
  1118. {
  1119. llinfos << "Default selector paths file \"" << fullpath
  1120. << "\" is missing, ill-formed, or simply undefined." << llendl;
  1121. return;
  1122. }
  1123. for (S32 context = CONTEXT_DEFAULT; context < CONTEXT_END; ++context)
  1124. {
  1125. std::string path = data.get(context).asString();
  1126. if (!path.empty())
  1127. {
  1128. sContextToPathMap.emplace(context, path);
  1129. }
  1130. }
  1131. input.close();
  1132. }