SelectionTab.cpp 29 KB

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