SelectionTab.cpp 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031
  1. /*
  2. * SelectionTab.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "SelectionTab.h"
  12. #include "CSelectionBase.h"
  13. #include "CLobbyScreen.h"
  14. #include "../CGameInfo.h"
  15. #include "../CPlayerInterface.h"
  16. #include "../CServerHandler.h"
  17. #include "../gui/CGuiHandler.h"
  18. #include "../gui/Shortcut.h"
  19. #include "../gui/WindowHandler.h"
  20. #include "../widgets/CComponent.h"
  21. #include "../widgets/Buttons.h"
  22. #include "../widgets/MiscWidgets.h"
  23. #include "../widgets/ObjectLists.h"
  24. #include "../widgets/Slider.h"
  25. #include "../widgets/TextControls.h"
  26. #include "../windows/GUIClasses.h"
  27. #include "../windows/InfoWindows.h"
  28. #include "../render/CAnimation.h"
  29. #include "../render/Canvas.h"
  30. #include "../render/IImage.h"
  31. #include "../render/IRenderHandler.h"
  32. #include "../render/Graphics.h"
  33. #include "../../CCallback.h"
  34. #include "../../lib/NetPacksLobby.h"
  35. #include "../../lib/CGeneralTextHandler.h"
  36. #include "../../lib/CConfigHandler.h"
  37. #include "../../lib/GameSettings.h"
  38. #include "../../lib/filesystem/Filesystem.h"
  39. #include "../../lib/campaign/CampaignState.h"
  40. #include "../../lib/mapping/CMap.h"
  41. #include "../../lib/mapping/CMapService.h"
  42. #include "../../lib/mapping/CMapInfo.h"
  43. #include "../../lib/mapping/CMapHeader.h"
  44. #include "../../lib/mapping/MapFormat.h"
  45. #include "../../lib/TerrainHandler.h"
  46. #include "../../lib/serializer/Connection.h"
  47. bool mapSorter::operator()(const std::shared_ptr<ElementInfo> aaa, const std::shared_ptr<ElementInfo> bbb)
  48. {
  49. if(aaa->isFolder || bbb->isFolder)
  50. {
  51. if(aaa->isFolder != bbb->isFolder)
  52. return (aaa->isFolder > bbb->isFolder);
  53. else
  54. {
  55. if(boost::algorithm::starts_with(aaa->folderName, "..") || boost::algorithm::starts_with(bbb->folderName, ".."))
  56. return boost::algorithm::starts_with(aaa->folderName, "..");
  57. return boost::ilexicographical_compare(aaa->folderName, bbb->folderName);
  58. }
  59. }
  60. auto a = aaa->mapHeader.get();
  61. auto b = bbb->mapHeader.get();
  62. if(a && b) //if we are sorting scenarios
  63. {
  64. switch(sortBy)
  65. {
  66. case _format: //by map format (RoE, WoG, etc)
  67. return (a->version < b->version);
  68. break;
  69. case _loscon: //by loss conditions
  70. return (a->defeatIconIndex < b->defeatIconIndex);
  71. break;
  72. case _playerAm: //by player amount
  73. int playerAmntB, humenPlayersB, playerAmntA, humenPlayersA;
  74. playerAmntB = humenPlayersB = playerAmntA = humenPlayersA = 0;
  75. for(int i = 0; i < 8; i++)
  76. {
  77. if(a->players[i].canHumanPlay)
  78. {
  79. playerAmntA++;
  80. humenPlayersA++;
  81. }
  82. else if(a->players[i].canComputerPlay)
  83. {
  84. playerAmntA++;
  85. }
  86. if(b->players[i].canHumanPlay)
  87. {
  88. playerAmntB++;
  89. humenPlayersB++;
  90. }
  91. else if(b->players[i].canComputerPlay)
  92. {
  93. playerAmntB++;
  94. }
  95. }
  96. if(playerAmntB != playerAmntA)
  97. return (playerAmntA < playerAmntB);
  98. else
  99. return (humenPlayersA < humenPlayersB);
  100. break;
  101. case _size: //by size of map
  102. return (a->width < b->width);
  103. break;
  104. case _viccon: //by victory conditions
  105. return (a->victoryIconIndex < b->victoryIconIndex);
  106. break;
  107. case _name: //by name
  108. return boost::ilexicographical_compare(a->name, b->name);
  109. case _fileName: //by filename
  110. return boost::ilexicographical_compare(aaa->fileURI, bbb->fileURI);
  111. default:
  112. return boost::ilexicographical_compare(a->name, b->name);
  113. }
  114. }
  115. else //if we are sorting campaigns
  116. {
  117. switch(sortBy)
  118. {
  119. case _numOfMaps: //by number of maps in campaign
  120. return aaa->campaign->scenariosCount() < bbb->campaign->scenariosCount();
  121. case _name: //by name
  122. return boost::ilexicographical_compare(aaa->campaign->getName(), bbb->campaign->getName());
  123. default:
  124. return boost::ilexicographical_compare(aaa->campaign->getName(), bbb->campaign->getName());
  125. }
  126. }
  127. }
  128. // pick sorting order based on selection
  129. static ESortBy getSortBySelectionScreen(ESelectionScreen Type)
  130. {
  131. switch(Type)
  132. {
  133. case ESelectionScreen::newGame:
  134. return ESortBy::_name;
  135. case ESelectionScreen::loadGame:
  136. case ESelectionScreen::saveGame:
  137. return ESortBy::_fileName;
  138. case ESelectionScreen::campaignList:
  139. return ESortBy::_name;
  140. }
  141. // Should not reach here. But let's not crash the game.
  142. return ESortBy::_name;
  143. }
  144. SelectionTab::SelectionTab(ESelectionScreen Type)
  145. : CIntObject(LCLICK | SHOW_POPUP | KEYBOARD | DOUBLECLICK), callOnSelect(nullptr), tabType(Type), selectionPos(0), sortModeAscending(true), inputNameRect{32, 539, 350, 20}, curFolder(""), currentMapSizeFilter(0)
  146. {
  147. OBJ_CONSTRUCTION;
  148. generalSortingBy = getSortBySelectionScreen(tabType);
  149. if(tabType != ESelectionScreen::campaignList)
  150. {
  151. sortingBy = _format;
  152. background = std::make_shared<CPicture>(ImagePath::builtin("SCSELBCK.bmp"), 0, 6);
  153. pos = background->pos;
  154. inputName = std::make_shared<CTextInput>(inputNameRect, Point(-32, -25), ImagePath::builtin("GSSTRIP.bmp"), 0);
  155. inputName->filters += CTextInput::filenameFilter;
  156. labelMapSizes = std::make_shared<CLabel>(87, 62, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[510]);
  157. int sizes[] = {36, 72, 108, 144, 0};
  158. const char * filterIconNmes[] = {"SCSMBUT.DEF", "SCMDBUT.DEF", "SCLGBUT.DEF", "SCXLBUT.DEF", "SCALBUT.DEF"};
  159. for(int i = 0; i < 5; i++)
  160. buttonsSortBy.push_back(std::make_shared<CButton>(Point(158 + 47 * i, 46), AnimationPath::builtin(filterIconNmes[i]), CGI->generaltexth->zelp[54 + i], std::bind(&SelectionTab::filter, this, sizes[i], true)));
  161. int xpos[] = {23, 55, 88, 121, 306, 339};
  162. const char * sortIconNames[] = {"SCBUTT1.DEF", "SCBUTT2.DEF", "SCBUTCP.DEF", "SCBUTT3.DEF", "SCBUTT4.DEF", "SCBUTT5.DEF"};
  163. for(int i = 0; i < 6; i++)
  164. {
  165. ESortBy criteria = (ESortBy)i;
  166. if(criteria == _name)
  167. criteria = generalSortingBy;
  168. buttonsSortBy.push_back(std::make_shared<CButton>(Point(xpos[i], 86), AnimationPath::builtin(sortIconNames[i]), CGI->generaltexth->zelp[107 + i], std::bind(&SelectionTab::sortBy, this, criteria)));
  169. }
  170. }
  171. int positionsToShow = 18;
  172. std::string tabTitle;
  173. switch(tabType)
  174. {
  175. case ESelectionScreen::newGame:
  176. tabTitle = CGI->generaltexth->arraytxt[229];
  177. break;
  178. case ESelectionScreen::loadGame:
  179. tabTitle = CGI->generaltexth->arraytxt[230];
  180. break;
  181. case ESelectionScreen::saveGame:
  182. positionsToShow = 16;
  183. tabTitle = CGI->generaltexth->arraytxt[231];
  184. break;
  185. case ESelectionScreen::campaignList:
  186. tabTitle = CGI->generaltexth->allTexts[726];
  187. setRedrawParent(true); // we use parent background so we need to make sure it's will be redrawn too
  188. pos.w = parent->pos.w;
  189. pos.h = parent->pos.h;
  190. pos.x += 3;
  191. pos.y += 6;
  192. buttonsSortBy.push_back(std::make_shared<CButton>(Point(23, 86), AnimationPath::builtin("CamCusM.DEF"), CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _numOfMaps)));
  193. buttonsSortBy.push_back(std::make_shared<CButton>(Point(55, 86), AnimationPath::builtin("CamCusL.DEF"), CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _name)));
  194. break;
  195. default:
  196. assert(0);
  197. break;
  198. }
  199. iconsMapFormats = GH.renderHandler().loadAnimation(AnimationPath::builtin("SCSELC.DEF"));
  200. iconsVictoryCondition = GH.renderHandler().loadAnimation(AnimationPath::builtin("SCNRVICT.DEF"));
  201. iconsLossCondition = GH.renderHandler().loadAnimation(AnimationPath::builtin("SCNRLOSS.DEF"));
  202. for(int i = 0; i < positionsToShow; i++)
  203. listItems.push_back(std::make_shared<ListItem>(Point(30, 129 + i * 25), iconsMapFormats, iconsVictoryCondition, iconsLossCondition));
  204. labelTabTitle = std::make_shared<CLabel>(205, 28, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, tabTitle);
  205. slider = std::make_shared<CSlider>(Point(372, 86), tabType != ESelectionScreen::saveGame ? 480 : 430, std::bind(&SelectionTab::sliderMove, this, _1), positionsToShow, (int)curItems.size(), 0, Orientation::VERTICAL, CSlider::BLUE);
  206. slider->setPanningStep(24);
  207. // create scroll bounds that encompass all area in this UI element to the left of slider (including area of slider itself)
  208. // entire screen can't be used in here since map description might also have slider
  209. slider->setScrollBounds(Rect(pos.x - slider->pos.x, 0, slider->pos.x + slider->pos.w - pos.x, slider->pos.h ));
  210. filter(0);
  211. }
  212. void SelectionTab::toggleMode()
  213. {
  214. if(CSH->isGuest())
  215. {
  216. allItems.clear();
  217. curItems.clear();
  218. if(slider)
  219. slider->block(true);
  220. }
  221. else
  222. {
  223. switch(tabType)
  224. {
  225. case ESelectionScreen::newGame:
  226. {
  227. inputName->disable();
  228. auto files = getFiles("Maps/", EResType::MAP);
  229. files.erase(ResourcePath("Maps/Tutorial.tut", EResType::MAP));
  230. parseMaps(files);
  231. break;
  232. }
  233. case ESelectionScreen::loadGame:
  234. inputName->disable();
  235. parseSaves(getFiles("Saves/", EResType::SAVEGAME));
  236. break;
  237. case ESelectionScreen::saveGame:
  238. parseSaves(getFiles("Saves/", EResType::SAVEGAME));
  239. inputName->enable();
  240. inputName->activate();
  241. restoreLastSelection();
  242. break;
  243. case ESelectionScreen::campaignList:
  244. parseCampaigns(getFiles("Maps/", EResType::CAMPAIGN));
  245. break;
  246. default:
  247. assert(0);
  248. break;
  249. }
  250. if(slider)
  251. {
  252. slider->block(false);
  253. filter(0);
  254. }
  255. if(CSH->campaignStateToSend)
  256. {
  257. CSH->setCampaignState(CSH->campaignStateToSend);
  258. CSH->campaignStateToSend.reset();
  259. }
  260. else
  261. {
  262. restoreLastSelection();
  263. }
  264. }
  265. slider->setAmount((int)curItems.size());
  266. updateListItems();
  267. redraw();
  268. }
  269. void SelectionTab::clickReleased(const Point & cursorPosition)
  270. {
  271. int line = getLine();
  272. if(line != -1)
  273. {
  274. select(line);
  275. }
  276. #ifdef VCMI_IOS
  277. // focus input field if clicked inside it
  278. else if(inputName && inputName->isActive() && inputNameRect.isInside(cursorPosition))
  279. inputName->giveFocus();
  280. #endif
  281. }
  282. void SelectionTab::keyPressed(EShortcut key)
  283. {
  284. int moveBy = 0;
  285. switch(key)
  286. {
  287. case EShortcut::MOVE_UP:
  288. moveBy = -1;
  289. break;
  290. case EShortcut::MOVE_DOWN:
  291. moveBy = +1;
  292. break;
  293. case EShortcut::MOVE_PAGE_UP:
  294. moveBy = -(int)listItems.size() + 1;
  295. break;
  296. case EShortcut::MOVE_PAGE_DOWN:
  297. moveBy = +(int)listItems.size() - 1;
  298. break;
  299. case EShortcut::MOVE_FIRST:
  300. select(-slider->getValue());
  301. return;
  302. case EShortcut::MOVE_LAST:
  303. select((int)curItems.size() - slider->getValue());
  304. return;
  305. default:
  306. return;
  307. }
  308. select((int)selectionPos - slider->getValue() + moveBy);
  309. }
  310. void SelectionTab::clickDouble(const Point & cursorPosition)
  311. {
  312. int position = getLine();
  313. int itemIndex = position + slider->getValue();
  314. if(itemIndex >= curItems.size())
  315. return;
  316. if(itemIndex >= 0 && curItems[itemIndex]->isFolder)
  317. {
  318. select(position);
  319. return;
  320. }
  321. if(getLine() != -1) //double clicked scenarios list
  322. {
  323. (static_cast<CLobbyScreen *>(parent))->buttonStart->clickPressed(cursorPosition);
  324. (static_cast<CLobbyScreen *>(parent))->buttonStart->clickReleased(cursorPosition);
  325. }
  326. }
  327. void SelectionTab::showPopupWindow(const Point & cursorPosition)
  328. {
  329. int position = getLine();
  330. int py = position + slider->getValue();
  331. if(py >= curItems.size())
  332. return;
  333. if(!curItems[py]->isFolder)
  334. {
  335. std::string text = boost::str(boost::format("{%1%}\r\n\r\n%2%:\r\n%3%") % curItems[py]->getName() % CGI->generaltexth->translate("vcmi.lobby.filename") % curItems[py]->fullFileURI);
  336. if(curItems[py]->date != "")
  337. text += boost::str(boost::format("\r\n\r\n%1%:\r\n%2%") % CGI->generaltexth->translate("vcmi.lobby.creationDate") % curItems[py]->date);
  338. GH.windows().createAndPushWindow<CMapInfoTooltipBox>(text, ResourcePath(curItems[py]->fileURI), tabType);
  339. }
  340. else
  341. CRClickPopup::createAndPush(curItems[py]->folderName);
  342. }
  343. auto SelectionTab::checkSubfolder(std::string path)
  344. {
  345. struct Ret
  346. {
  347. std::string folderName;
  348. std::string baseFolder;
  349. bool parentExists;
  350. bool fileInFolder;
  351. } ret;
  352. ret.parentExists = (curFolder != "");
  353. ret.fileInFolder = false;
  354. std::vector<std::string> filetree;
  355. // delete first element (e.g. 'MAPS')
  356. boost::split(filetree, path, boost::is_any_of("/"));
  357. filetree.erase(filetree.begin());
  358. std::string pathWithoutPrefix = boost::algorithm::join(filetree, "/");
  359. if(!filetree.empty())
  360. {
  361. filetree.pop_back();
  362. ret.baseFolder = boost::algorithm::join(filetree, "/");
  363. }
  364. else
  365. ret.baseFolder = "";
  366. if(boost::algorithm::starts_with(ret.baseFolder, curFolder))
  367. {
  368. std::string folder = ret.baseFolder.substr(curFolder.size());
  369. if(folder != "")
  370. {
  371. boost::split(filetree, folder, boost::is_any_of("/"));
  372. ret.folderName = filetree[0];
  373. }
  374. }
  375. if(boost::algorithm::starts_with(pathWithoutPrefix, curFolder))
  376. if(boost::count(pathWithoutPrefix.substr(curFolder.size()), '/') == 0)
  377. ret.fileInFolder = true;
  378. return ret;
  379. }
  380. // A new size filter (Small, Medium, ...) has been selected. Populate
  381. // selMaps with the relevant data.
  382. void SelectionTab::filter(int size, bool selectFirst)
  383. {
  384. if(size == -1)
  385. size = currentMapSizeFilter;
  386. currentMapSizeFilter = size;
  387. curItems.clear();
  388. for(auto elem : allItems)
  389. {
  390. if((elem->mapHeader && (!size || elem->mapHeader->width == size)) || tabType == ESelectionScreen::campaignList)
  391. {
  392. auto [folderName, baseFolder, parentExists, fileInFolder] = checkSubfolder(elem->originalFileURI);
  393. if(parentExists)
  394. {
  395. auto folder = std::make_shared<ElementInfo>();
  396. folder->isFolder = true;
  397. folder->folderName = ".. (" + curFolder + ")";
  398. auto itemIt = boost::range::find_if(curItems, [](std::shared_ptr<ElementInfo> e) { return boost::starts_with(e->folderName, ".."); });
  399. if (itemIt == curItems.end()) {
  400. curItems.push_back(folder);
  401. }
  402. }
  403. std::shared_ptr<ElementInfo> folder = std::make_shared<ElementInfo>();
  404. folder->isFolder = true;
  405. folder->folderName = folderName;
  406. auto itemIt = boost::range::find_if(curItems, [folder](std::shared_ptr<ElementInfo> e) { return e->folderName == folder->folderName; });
  407. if (itemIt == curItems.end() && folderName != "") {
  408. curItems.push_back(folder);
  409. }
  410. if(fileInFolder)
  411. curItems.push_back(elem);
  412. }
  413. }
  414. if(curItems.size())
  415. {
  416. slider->block(false);
  417. slider->setAmount((int)curItems.size());
  418. sort();
  419. if(selectFirst)
  420. {
  421. int firstPos = boost::range::find_if(curItems, [](std::shared_ptr<ElementInfo> e) { return !e->isFolder; }) - curItems.begin();
  422. if(firstPos < curItems.size())
  423. {
  424. slider->scrollTo(firstPos);
  425. callOnSelect(curItems[firstPos]);
  426. selectAbs(firstPos);
  427. }
  428. }
  429. }
  430. else
  431. {
  432. updateListItems();
  433. redraw();
  434. slider->block(true);
  435. if(callOnSelect)
  436. callOnSelect(nullptr);
  437. }
  438. }
  439. void SelectionTab::sortBy(int criteria)
  440. {
  441. if(criteria == sortingBy)
  442. {
  443. sortModeAscending = !sortModeAscending;
  444. }
  445. else
  446. {
  447. sortingBy = (ESortBy)criteria;
  448. sortModeAscending = true;
  449. }
  450. sort();
  451. selectAbs(-1);
  452. }
  453. void SelectionTab::sort()
  454. {
  455. if(sortingBy != generalSortingBy)
  456. std::stable_sort(curItems.begin(), curItems.end(), mapSorter(generalSortingBy));
  457. std::stable_sort(curItems.begin(), curItems.end(), mapSorter(sortingBy));
  458. int firstMapIndex = boost::range::find_if(curItems, [](std::shared_ptr<ElementInfo> e) { return !e->isFolder; }) - curItems.begin();
  459. if(!sortModeAscending)
  460. std::reverse(std::next(curItems.begin(), firstMapIndex), curItems.end());
  461. updateListItems();
  462. redraw();
  463. }
  464. void SelectionTab::select(int position)
  465. {
  466. if(!curItems.size())
  467. return;
  468. // New selection. py is the index in curItems.
  469. int py = position + slider->getValue();
  470. vstd::amax(py, 0);
  471. vstd::amin(py, curItems.size() - 1);
  472. selectionPos = py;
  473. if(position < 0)
  474. slider->scrollBy(position);
  475. else if(position >= listItems.size())
  476. slider->scrollBy(position - (int)listItems.size() + 1);
  477. if(curItems[py]->isFolder) {
  478. if(boost::starts_with(curItems[py]->folderName, ".."))
  479. {
  480. std::vector<std::string> filetree;
  481. boost::split(filetree, curFolder, boost::is_any_of("/"));
  482. filetree.pop_back();
  483. filetree.pop_back();
  484. curFolder = filetree.size() > 0 ? boost::algorithm::join(filetree, "/") + "/" : "";
  485. }
  486. else
  487. curFolder += curItems[py]->folderName + "/";
  488. filter(-1);
  489. slider->scrollTo(0);
  490. int firstPos = boost::range::find_if(curItems, [](std::shared_ptr<ElementInfo> e) { return !e->isFolder; }) - curItems.begin();
  491. if(firstPos < curItems.size())
  492. {
  493. selectAbs(firstPos);
  494. }
  495. return;
  496. }
  497. rememberCurrentSelection();
  498. if(inputName && inputName->isActive())
  499. {
  500. auto filename = *CResourceHandler::get()->getResourceName(ResourcePath(curItems[py]->fileURI, EResType::SAVEGAME));
  501. inputName->setText(filename.stem().string());
  502. }
  503. updateListItems();
  504. redraw();
  505. if(callOnSelect)
  506. callOnSelect(curItems[py]);
  507. }
  508. void SelectionTab::selectAbs(int position)
  509. {
  510. if(position == -1)
  511. position = boost::range::find_if(curItems, [](std::shared_ptr<ElementInfo> e) { return !e->isFolder; }) - curItems.begin();
  512. select(position - slider->getValue());
  513. }
  514. void SelectionTab::sliderMove(int slidPos)
  515. {
  516. if(!slider)
  517. return; // ignore spurious call when slider is being created
  518. updateListItems();
  519. redraw();
  520. }
  521. void SelectionTab::updateListItems()
  522. {
  523. // elemIdx is the index of the maps or saved game to display on line 0
  524. // slider->capacity contains the number of available screen lines
  525. // slider->positionsAmnt is the number of elements after filtering
  526. int elemIdx = slider->getValue();
  527. for(auto item : listItems)
  528. {
  529. if(elemIdx < curItems.size())
  530. {
  531. item->updateItem(curItems[elemIdx], elemIdx == selectionPos);
  532. elemIdx++;
  533. }
  534. else
  535. {
  536. item->updateItem();
  537. }
  538. }
  539. }
  540. bool SelectionTab::receiveEvent(const Point & position, int eventType) const
  541. {
  542. // FIXME: widget should instead have well-defined pos so events will be filtered using standard routine
  543. return getLine(position - pos.topLeft()) != -1;
  544. }
  545. int SelectionTab::getLine() const
  546. {
  547. Point clickPos = GH.getCursorPosition() - pos.topLeft();
  548. return getLine(clickPos);
  549. }
  550. int SelectionTab::getLine(const Point & clickPos) const
  551. {
  552. int line = -1;
  553. // Ignore clicks on save name area
  554. int maxPosY;
  555. if(tabType == ESelectionScreen::saveGame)
  556. maxPosY = 516;
  557. else
  558. maxPosY = 564;
  559. if(clickPos.y > 115 && clickPos.y < maxPosY && clickPos.x > 22 && clickPos.x < 371)
  560. {
  561. line = (clickPos.y - 115) / 25; //which line
  562. }
  563. return line;
  564. }
  565. void SelectionTab::selectFileName(std::string fname)
  566. {
  567. boost::to_upper(fname);
  568. for(int i = (int)allItems.size() - 1; i >= 0; i--)
  569. {
  570. if(allItems[i]->fileURI == fname)
  571. {
  572. auto [folderName, baseFolder, parentExists, fileInFolder] = checkSubfolder(allItems[i]->originalFileURI);
  573. curFolder = baseFolder != "" ? baseFolder + "/" : "";
  574. }
  575. }
  576. for(int i = (int)curItems.size() - 1; i >= 0; i--)
  577. {
  578. if(curItems[i]->fileURI == fname)
  579. {
  580. slider->scrollTo(i);
  581. selectAbs(i);
  582. return;
  583. }
  584. }
  585. filter(-1);
  586. selectAbs(-1);
  587. }
  588. std::shared_ptr<ElementInfo> SelectionTab::getSelectedMapInfo() const
  589. {
  590. return curItems.empty() || curItems[selectionPos]->isFolder ? nullptr : curItems[selectionPos];
  591. }
  592. void SelectionTab::rememberCurrentSelection()
  593. {
  594. if(getSelectedMapInfo()->isFolder)
  595. return;
  596. // TODO: this can be more elegant
  597. if(tabType == ESelectionScreen::newGame)
  598. {
  599. Settings lastMap = settings.write["general"]["lastMap"];
  600. lastMap->String() = getSelectedMapInfo()->fileURI;
  601. }
  602. else if(tabType == ESelectionScreen::loadGame)
  603. {
  604. Settings lastSave = settings.write["general"]["lastSave"];
  605. lastSave->String() = getSelectedMapInfo()->fileURI;
  606. }
  607. else if(tabType == ESelectionScreen::campaignList)
  608. {
  609. Settings lastCampaign = settings.write["general"]["lastCampaign"];
  610. lastCampaign->String() = getSelectedMapInfo()->fileURI;
  611. }
  612. }
  613. void SelectionTab::restoreLastSelection()
  614. {
  615. switch(tabType)
  616. {
  617. case ESelectionScreen::newGame:
  618. selectFileName(settings["general"]["lastMap"].String());
  619. break;
  620. case ESelectionScreen::campaignList:
  621. selectFileName(settings["general"]["lastCampaign"].String());
  622. break;
  623. case ESelectionScreen::loadGame:
  624. case ESelectionScreen::saveGame:
  625. selectFileName(settings["general"]["lastSave"].String());
  626. }
  627. }
  628. bool SelectionTab::isMapSupported(const CMapInfo & info)
  629. {
  630. switch (info.mapHeader->version)
  631. {
  632. case EMapFormat::ROE:
  633. return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA)["supported"].Bool();
  634. case EMapFormat::AB:
  635. return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE)["supported"].Bool();
  636. case EMapFormat::SOD:
  637. return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH)["supported"].Bool();
  638. case EMapFormat::WOG:
  639. return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS)["supported"].Bool();
  640. case EMapFormat::HOTA:
  641. return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS)["supported"].Bool();
  642. case EMapFormat::VCMI:
  643. return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_JSON_VCMI)["supported"].Bool();
  644. }
  645. return false;
  646. }
  647. void SelectionTab::parseMaps(const std::unordered_set<ResourcePath> & files)
  648. {
  649. logGlobal->debug("Parsing %d maps", files.size());
  650. allItems.clear();
  651. for(auto & file : files)
  652. {
  653. try
  654. {
  655. auto mapInfo = std::make_shared<ElementInfo>();
  656. mapInfo->mapInit(file.getName());
  657. if (isMapSupported(*mapInfo))
  658. allItems.push_back(mapInfo);
  659. }
  660. catch(std::exception & e)
  661. {
  662. logGlobal->error("Map %s is invalid. Message: %s", file.getName(), e.what());
  663. }
  664. }
  665. }
  666. void SelectionTab::parseSaves(const std::unordered_set<ResourcePath> & files)
  667. {
  668. for(auto & file : files)
  669. {
  670. try
  671. {
  672. auto mapInfo = std::make_shared<ElementInfo>();
  673. mapInfo->saveInit(file);
  674. // Filter out other game modes
  675. bool isCampaign = mapInfo->scenarioOptionsOfSave->mode == StartInfo::CAMPAIGN;
  676. bool isMultiplayer = mapInfo->amountOfHumanPlayersInSave > 1;
  677. bool isTutorial = boost::to_upper_copy(mapInfo->scenarioOptionsOfSave->mapname) == "MAPS/TUTORIAL";
  678. switch(CSH->getLoadMode())
  679. {
  680. case ELoadMode::SINGLE:
  681. if(isMultiplayer || isCampaign || isTutorial)
  682. mapInfo->mapHeader.reset();
  683. break;
  684. case ELoadMode::CAMPAIGN:
  685. if(!isCampaign)
  686. mapInfo->mapHeader.reset();
  687. break;
  688. case ELoadMode::TUTORIAL:
  689. if(!isTutorial)
  690. mapInfo->mapHeader.reset();
  691. break;
  692. default:
  693. if(!isMultiplayer)
  694. mapInfo->mapHeader.reset();
  695. break;
  696. }
  697. allItems.push_back(mapInfo);
  698. }
  699. catch(const std::exception & e)
  700. {
  701. logGlobal->error("Error: Failed to process %s: %s", file.getName(), e.what());
  702. }
  703. }
  704. }
  705. void SelectionTab::parseCampaigns(const std::unordered_set<ResourcePath> & files)
  706. {
  707. allItems.reserve(files.size());
  708. for(auto & file : files)
  709. {
  710. auto info = std::make_shared<ElementInfo>();
  711. //allItems[i].date = std::asctime(std::localtime(&files[i].date));
  712. info->fileURI = file.getName();
  713. info->campaignInit();
  714. if(info->campaign)
  715. allItems.push_back(info);
  716. }
  717. }
  718. std::unordered_set<ResourcePath> SelectionTab::getFiles(std::string dirURI, EResType resType)
  719. {
  720. boost::to_upper(dirURI);
  721. CResourceHandler::get()->updateFilteredFiles([&](const std::string & mount)
  722. {
  723. return boost::algorithm::starts_with(mount, dirURI);
  724. });
  725. std::unordered_set<ResourcePath> ret = CResourceHandler::get()->getFilteredFiles([&](const ResourcePath & ident)
  726. {
  727. return ident.getType() == resType && boost::algorithm::starts_with(ident.getName(), dirURI);
  728. });
  729. return ret;
  730. }
  731. SelectionTab::CMapInfoTooltipBox::CMapInfoTooltipBox(std::string text, ResourcePath resource, ESelectionScreen tabType)
  732. : CWindowObject(BORDERED | RCLICK_POPUP)
  733. {
  734. drawPlayerElements = tabType == ESelectionScreen::newGame;
  735. renderImage = tabType == ESelectionScreen::newGame && settings["lobby"]["mapPreview"].Bool();
  736. OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
  737. std::vector<std::shared_ptr<IImage>> mapLayerImages;
  738. if(renderImage)
  739. mapLayerImages = createMinimaps(ResourcePath(resource.getName(), EResType::MAP), IMAGE_SIZE);
  740. if(mapLayerImages.size() == 0)
  741. renderImage = false;
  742. pos = Rect(0, 0, 3 * BORDER + 2 * IMAGE_SIZE, 2000);
  743. auto drawLabel = [&]() {
  744. label = std::make_shared<CTextBox>(text, Rect(BORDER, BORDER, BORDER + 2 * IMAGE_SIZE, 350), 0, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE);
  745. if(!label->slider)
  746. label->resize(Point(BORDER + 2 * IMAGE_SIZE, label->label->textSize.y));
  747. };
  748. drawLabel();
  749. int textHeight = std::min(350, label->label->textSize.y);
  750. pos.h = BORDER + textHeight + BORDER;
  751. if(renderImage)
  752. pos.h += IMAGE_SIZE + BORDER;
  753. backgroundTexture = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), pos);
  754. updateShadow();
  755. drawLabel();
  756. if(renderImage)
  757. {
  758. if(mapLayerImages.size() == 1)
  759. image1 = std::make_shared<CPicture>(mapLayerImages[0], Point(BORDER + (BORDER + IMAGE_SIZE) / 2, textHeight + 2 * BORDER));
  760. else
  761. {
  762. image1 = std::make_shared<CPicture>(mapLayerImages[0], Point(BORDER, textHeight + 2 * BORDER));
  763. image2 = std::make_shared<CPicture>(mapLayerImages[1], Point(BORDER + IMAGE_SIZE + BORDER, textHeight + 2 * BORDER));
  764. }
  765. }
  766. center(GH.getCursorPosition()); //center on mouse
  767. #ifdef VCMI_MOBILE
  768. moveBy({0, -pos.h / 2});
  769. #endif
  770. fitToScreen(10);
  771. }
  772. Canvas SelectionTab::CMapInfoTooltipBox::createMinimapForLayer(std::unique_ptr<CMap> & map, int layer)
  773. {
  774. Canvas canvas = Canvas(Point(map->width, map->height));
  775. for (int y = 0; y < map->height; ++y)
  776. for (int x = 0; x < map->width; ++x)
  777. {
  778. TerrainTile & tile = map->getTile(int3(x, y, layer));
  779. ColorRGBA color = tile.terType->minimapUnblocked;
  780. if (tile.blocked && (!tile.visitable))
  781. color = tile.terType->minimapBlocked;
  782. if(drawPlayerElements)
  783. // if object at tile is owned - it will be colored as its owner
  784. for (const CGObjectInstance *obj : tile.blockingObjects)
  785. {
  786. PlayerColor player = obj->getOwner();
  787. if(player == PlayerColor::NEUTRAL)
  788. {
  789. color = graphics->neutralColor;
  790. break;
  791. }
  792. if (player.isValidPlayer())
  793. {
  794. color = graphics->playerColors[player.getNum()];
  795. break;
  796. }
  797. }
  798. canvas.drawPoint(Point(x, y), color);
  799. }
  800. return canvas;
  801. }
  802. std::vector<std::shared_ptr<IImage>> SelectionTab::CMapInfoTooltipBox::createMinimaps(ResourcePath resource, int size)
  803. {
  804. std::vector<std::shared_ptr<IImage>> ret = std::vector<std::shared_ptr<IImage>>();
  805. CMapService mapService;
  806. std::unique_ptr<CMap> map;
  807. try
  808. {
  809. map = mapService.loadMap(resource);
  810. }
  811. catch (...)
  812. {
  813. return ret;
  814. }
  815. for(int i = 0; i < (map->twoLevel ? 2 : 1); i++)
  816. {
  817. Canvas canvas = createMinimapForLayer(map, i);
  818. Canvas canvasScaled = Canvas(Point(size, size));
  819. canvasScaled.drawScaled(canvas, Point(0, 0), Point(size, size));
  820. std::shared_ptr<IImage> img = GH.renderHandler().createImage(canvasScaled.getInternalSurface());
  821. ret.push_back(img);
  822. }
  823. return ret;
  824. }
  825. SelectionTab::ListItem::ListItem(Point position, std::shared_ptr<CAnimation> iconsFormats, std::shared_ptr<CAnimation> iconsVictory, std::shared_ptr<CAnimation> iconsLoss)
  826. : CIntObject(LCLICK, position)
  827. {
  828. OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
  829. pictureEmptyLine = std::make_shared<CPicture>(GH.renderHandler().loadImage(ImagePath::builtin("camcust")), Rect(25, 121, 349, 26), -8, -14);
  830. labelName = std::make_shared<CLabel>(184, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
  831. labelName->setAutoRedraw(false);
  832. labelAmountOfPlayers = std::make_shared<CLabel>(8, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
  833. labelAmountOfPlayers->setAutoRedraw(false);
  834. labelNumberOfCampaignMaps = std::make_shared<CLabel>(8, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
  835. labelNumberOfCampaignMaps->setAutoRedraw(false);
  836. labelMapSizeLetter = std::make_shared<CLabel>(41, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
  837. labelMapSizeLetter->setAutoRedraw(false);
  838. // FIXME: This -12 should not be needed, but for some reason CAnimImage displaced otherwise
  839. iconFolder = std::make_shared<CPicture>(ImagePath::builtin("lobby/iconFolder.png"), -8, -12);
  840. iconFormat = std::make_shared<CAnimImage>(iconsFormats, 0, 0, 59, -12);
  841. iconVictoryCondition = std::make_shared<CAnimImage>(iconsVictory, 0, 0, 277, -12);
  842. iconLossCondition = std::make_shared<CAnimImage>(iconsLoss, 0, 0, 310, -12);
  843. }
  844. void SelectionTab::ListItem::updateItem(std::shared_ptr<ElementInfo> info, bool selected)
  845. {
  846. if(!info)
  847. {
  848. labelAmountOfPlayers->disable();
  849. labelMapSizeLetter->disable();
  850. iconFolder->disable();
  851. pictureEmptyLine->disable();
  852. iconFormat->disable();
  853. iconVictoryCondition->disable();
  854. iconLossCondition->disable();
  855. labelNumberOfCampaignMaps->disable();
  856. labelName->disable();
  857. return;
  858. }
  859. auto color = selected ? Colors::YELLOW : Colors::WHITE;
  860. if(info->isFolder)
  861. {
  862. labelAmountOfPlayers->disable();
  863. labelMapSizeLetter->disable();
  864. iconFolder->enable();
  865. pictureEmptyLine->enable();
  866. iconFormat->disable();
  867. iconVictoryCondition->disable();
  868. iconLossCondition->disable();
  869. labelNumberOfCampaignMaps->disable();
  870. labelName->enable();
  871. labelName->setText(info->folderName);
  872. labelName->setColor(color);
  873. return;
  874. }
  875. if(info->campaign)
  876. {
  877. labelAmountOfPlayers->disable();
  878. labelMapSizeLetter->disable();
  879. iconFolder->disable();
  880. pictureEmptyLine->disable();
  881. iconFormat->disable();
  882. iconVictoryCondition->disable();
  883. iconLossCondition->disable();
  884. labelNumberOfCampaignMaps->enable();
  885. std::ostringstream ostr(std::ostringstream::out);
  886. ostr << info->campaign->scenariosCount();
  887. labelNumberOfCampaignMaps->setText(ostr.str());
  888. labelNumberOfCampaignMaps->setColor(color);
  889. }
  890. else
  891. {
  892. labelNumberOfCampaignMaps->disable();
  893. std::ostringstream ostr(std::ostringstream::out);
  894. ostr << info->amountOfPlayersOnMap << "/" << info->amountOfHumanControllablePlayers;
  895. labelAmountOfPlayers->enable();
  896. labelAmountOfPlayers->setText(ostr.str());
  897. labelAmountOfPlayers->setColor(color);
  898. labelMapSizeLetter->enable();
  899. labelMapSizeLetter->setText(info->getMapSizeName());
  900. labelMapSizeLetter->setColor(color);
  901. iconFolder->disable();
  902. pictureEmptyLine->disable();
  903. iconFormat->enable();
  904. iconFormat->setFrame(info->getMapSizeFormatIconId());
  905. iconVictoryCondition->enable();
  906. iconVictoryCondition->setFrame(info->mapHeader->victoryIconIndex, 0);
  907. iconLossCondition->enable();
  908. iconLossCondition->setFrame(info->mapHeader->defeatIconIndex, 0);
  909. }
  910. labelName->enable();
  911. labelName->setText(info->getNameForList());
  912. labelName->setColor(color);
  913. }