cmCursesMainForm.cxx 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmCursesMainForm.h"
  4. #include "cmAlgorithms.h"
  5. #include "cmCursesCacheEntryComposite.h"
  6. #include "cmCursesDummyWidget.h"
  7. #include "cmCursesForm.h"
  8. #include "cmCursesLabelWidget.h"
  9. #include "cmCursesLongMessageForm.h"
  10. #include "cmCursesStandardIncludes.h"
  11. #include "cmCursesStringWidget.h"
  12. #include "cmCursesWidget.h"
  13. #include "cmState.h"
  14. #include "cmStateTypes.h"
  15. #include "cmSystemTools.h"
  16. #include "cmVersion.h"
  17. #include "cmake.h"
  18. #include <algorithm>
  19. #include <stdio.h>
  20. #include <string.h>
  21. #include <utility>
  22. inline int ctrl(int z)
  23. {
  24. return (z & 037);
  25. }
  26. cmCursesMainForm::cmCursesMainForm(std::vector<std::string> args,
  27. int initWidth)
  28. : Args(std::move(args))
  29. , InitialWidth(initWidth)
  30. {
  31. this->NumberOfPages = 0;
  32. this->Fields = nullptr;
  33. this->Entries = nullptr;
  34. this->AdvancedMode = false;
  35. this->NumberOfVisibleEntries = 0;
  36. this->OkToGenerate = false;
  37. this->HelpMessage.emplace_back(
  38. "Welcome to ccmake, curses based user interface for CMake.");
  39. this->HelpMessage.emplace_back();
  40. this->HelpMessage.emplace_back(s_ConstHelpMessage);
  41. this->CMakeInstance = new cmake(cmake::RoleProject, cmState::Project);
  42. this->CMakeInstance->SetCMakeEditCommand(
  43. cmSystemTools::GetCMakeCursesCommand());
  44. // create the arguments for the cmake object
  45. std::string whereCMake = cmSystemTools::GetProgramPath(this->Args[0]);
  46. whereCMake += "/cmake";
  47. this->Args[0] = whereCMake;
  48. this->CMakeInstance->SetArgs(this->Args);
  49. this->SearchString = "";
  50. this->OldSearchString = "";
  51. this->SearchMode = false;
  52. }
  53. cmCursesMainForm::~cmCursesMainForm()
  54. {
  55. if (this->Form) {
  56. unpost_form(this->Form);
  57. free_form(this->Form);
  58. this->Form = nullptr;
  59. }
  60. delete[] this->Fields;
  61. // Clean-up composites
  62. if (this->Entries) {
  63. cmDeleteAll(*this->Entries);
  64. }
  65. delete this->Entries;
  66. if (this->CMakeInstance) {
  67. delete this->CMakeInstance;
  68. this->CMakeInstance = nullptr;
  69. }
  70. }
  71. // See if a cache entry is in the list of entries in the ui.
  72. bool cmCursesMainForm::LookForCacheEntry(const std::string& key)
  73. {
  74. return this->Entries &&
  75. std::any_of(this->Entries->begin(), this->Entries->end(),
  76. [&key](cmCursesCacheEntryComposite* entry) {
  77. return key == entry->Key;
  78. });
  79. }
  80. // Create new cmCursesCacheEntryComposite entries from the cache
  81. void cmCursesMainForm::InitializeUI()
  82. {
  83. // Create a vector of cmCursesCacheEntryComposite's
  84. // which contain labels, entries and new entry markers
  85. std::vector<cmCursesCacheEntryComposite*>* newEntries =
  86. new std::vector<cmCursesCacheEntryComposite*>;
  87. std::vector<std::string> cacheKeys =
  88. this->CMakeInstance->GetState()->GetCacheEntryKeys();
  89. newEntries->reserve(cacheKeys.size());
  90. // Count non-internal and non-static entries
  91. int count = 0;
  92. for (std::string const& key : cacheKeys) {
  93. cmStateEnums::CacheEntryType t =
  94. this->CMakeInstance->GetState()->GetCacheEntryType(key);
  95. if (t != cmStateEnums::INTERNAL && t != cmStateEnums::STATIC &&
  96. t != cmStateEnums::UNINITIALIZED) {
  97. ++count;
  98. }
  99. }
  100. int entrywidth = this->InitialWidth - 35;
  101. cmCursesCacheEntryComposite* comp;
  102. if (count == 0) {
  103. // If cache is empty, display a label saying so and a
  104. // dummy entry widget (does not respond to input)
  105. comp = new cmCursesCacheEntryComposite("EMPTY CACHE", 30, 30);
  106. comp->Entry = new cmCursesDummyWidget(1, 1, 1, 1);
  107. newEntries->push_back(comp);
  108. } else {
  109. // Create the composites.
  110. // First add entries which are new
  111. for (std::string const& key : cacheKeys) {
  112. cmStateEnums::CacheEntryType t =
  113. this->CMakeInstance->GetState()->GetCacheEntryType(key);
  114. if (t == cmStateEnums::INTERNAL || t == cmStateEnums::STATIC ||
  115. t == cmStateEnums::UNINITIALIZED) {
  116. continue;
  117. }
  118. if (!this->LookForCacheEntry(key)) {
  119. newEntries->push_back(new cmCursesCacheEntryComposite(
  120. key, this->CMakeInstance, true, 30, entrywidth));
  121. this->OkToGenerate = false;
  122. }
  123. }
  124. // then add entries which are old
  125. for (std::string const& key : cacheKeys) {
  126. cmStateEnums::CacheEntryType t =
  127. this->CMakeInstance->GetState()->GetCacheEntryType(key);
  128. if (t == cmStateEnums::INTERNAL || t == cmStateEnums::STATIC ||
  129. t == cmStateEnums::UNINITIALIZED) {
  130. continue;
  131. }
  132. if (this->LookForCacheEntry(key)) {
  133. newEntries->push_back(new cmCursesCacheEntryComposite(
  134. key, this->CMakeInstance, false, 30, entrywidth));
  135. }
  136. }
  137. }
  138. // Clean old entries
  139. if (this->Entries) {
  140. cmDeleteAll(*this->Entries);
  141. }
  142. delete this->Entries;
  143. this->Entries = newEntries;
  144. // Compute fields from composites
  145. this->RePost();
  146. }
  147. void cmCursesMainForm::RePost()
  148. {
  149. // Create the fields to be passed to the form.
  150. if (this->Form) {
  151. unpost_form(this->Form);
  152. free_form(this->Form);
  153. this->Form = nullptr;
  154. }
  155. delete[] this->Fields;
  156. if (this->AdvancedMode) {
  157. this->NumberOfVisibleEntries = this->Entries->size();
  158. } else {
  159. // If normal mode, count only non-advanced entries
  160. this->NumberOfVisibleEntries = 0;
  161. for (cmCursesCacheEntryComposite* entry : *this->Entries) {
  162. const char* existingValue =
  163. this->CMakeInstance->GetState()->GetCacheEntryValue(entry->GetValue());
  164. bool advanced =
  165. this->CMakeInstance->GetState()->GetCacheEntryPropertyAsBool(
  166. entry->GetValue(), "ADVANCED");
  167. if (!existingValue || (!this->AdvancedMode && advanced)) {
  168. continue;
  169. }
  170. this->NumberOfVisibleEntries++;
  171. }
  172. }
  173. // there is always one even if it is the dummy one
  174. if (this->NumberOfVisibleEntries == 0) {
  175. this->NumberOfVisibleEntries = 1;
  176. }
  177. // Assign the fields: 3 for each entry: label, new entry marker
  178. // ('*' or ' ') and entry widget
  179. this->Fields = new FIELD*[3 * this->NumberOfVisibleEntries + 1];
  180. size_t cc;
  181. for (cc = 0; cc < 3 * this->NumberOfVisibleEntries + 1; cc++) {
  182. this->Fields[cc] = nullptr;
  183. }
  184. // Assign fields
  185. int j = 0;
  186. for (cmCursesCacheEntryComposite* entry : *this->Entries) {
  187. const char* existingValue =
  188. this->CMakeInstance->GetState()->GetCacheEntryValue(entry->GetValue());
  189. bool advanced =
  190. this->CMakeInstance->GetState()->GetCacheEntryPropertyAsBool(
  191. entry->GetValue(), "ADVANCED");
  192. if (!existingValue || (!this->AdvancedMode && advanced)) {
  193. continue;
  194. }
  195. this->Fields[3 * j] = entry->Label->Field;
  196. this->Fields[3 * j + 1] = entry->IsNewLabel->Field;
  197. this->Fields[3 * j + 2] = entry->Entry->Field;
  198. j++;
  199. }
  200. // if no cache entries there should still be one dummy field
  201. if (j == 0) {
  202. const auto& front = *this->Entries->front();
  203. this->Fields[0] = front.Label->Field;
  204. this->Fields[1] = front.IsNewLabel->Field;
  205. this->Fields[2] = front.Entry->Field;
  206. this->NumberOfVisibleEntries = 1;
  207. }
  208. // Has to be null terminated.
  209. this->Fields[3 * this->NumberOfVisibleEntries] = nullptr;
  210. }
  211. void cmCursesMainForm::Render(int left, int top, int width, int height)
  212. {
  213. if (this->Form) {
  214. FIELD* currentField = current_field(this->Form);
  215. cmCursesWidget* cw =
  216. reinterpret_cast<cmCursesWidget*>(field_userptr(currentField));
  217. // If in edit mode, get out of it
  218. if (cw->GetType() == cmStateEnums::STRING ||
  219. cw->GetType() == cmStateEnums::PATH ||
  220. cw->GetType() == cmStateEnums::FILEPATH) {
  221. cmCursesStringWidget* sw = static_cast<cmCursesStringWidget*>(cw);
  222. sw->SetInEdit(false);
  223. }
  224. // Delete the previous form
  225. unpost_form(this->Form);
  226. free_form(this->Form);
  227. this->Form = nullptr;
  228. }
  229. // Wrong window size
  230. if (width < cmCursesMainForm::MIN_WIDTH || width < this->InitialWidth ||
  231. height < cmCursesMainForm::MIN_HEIGHT) {
  232. return;
  233. }
  234. // Leave room for toolbar
  235. height -= 7;
  236. if (this->AdvancedMode) {
  237. this->NumberOfVisibleEntries = this->Entries->size();
  238. } else {
  239. // If normal, display only non-advanced entries
  240. this->NumberOfVisibleEntries = 0;
  241. for (cmCursesCacheEntryComposite* entry : *this->Entries) {
  242. const char* existingValue =
  243. this->CMakeInstance->GetState()->GetCacheEntryValue(entry->GetValue());
  244. bool advanced =
  245. this->CMakeInstance->GetState()->GetCacheEntryPropertyAsBool(
  246. entry->GetValue(), "ADVANCED");
  247. if (!existingValue || (!this->AdvancedMode && advanced)) {
  248. continue;
  249. }
  250. this->NumberOfVisibleEntries++;
  251. }
  252. }
  253. // Re-adjust the fields according to their place
  254. this->NumberOfPages = 1;
  255. if (height > 0) {
  256. bool isNewPage;
  257. int i = 0;
  258. for (cmCursesCacheEntryComposite* entry : *this->Entries) {
  259. const char* existingValue =
  260. this->CMakeInstance->GetState()->GetCacheEntryValue(entry->GetValue());
  261. bool advanced =
  262. this->CMakeInstance->GetState()->GetCacheEntryPropertyAsBool(
  263. entry->GetValue(), "ADVANCED");
  264. if (!existingValue || (!this->AdvancedMode && advanced)) {
  265. continue;
  266. }
  267. int row = (i % height) + 1;
  268. int page = (i / height) + 1;
  269. isNewPage = (page > 1) && (row == 1);
  270. if (isNewPage) {
  271. this->NumberOfPages++;
  272. }
  273. entry->Label->Move(left, top + row - 1, isNewPage);
  274. entry->IsNewLabel->Move(left + 32, top + row - 1, false);
  275. entry->Entry->Move(left + 33, top + row - 1, false);
  276. entry->Entry->SetPage(this->NumberOfPages);
  277. i++;
  278. }
  279. }
  280. // Post the form
  281. this->Form = new_form(this->Fields);
  282. post_form(this->Form);
  283. // Update toolbar
  284. this->UpdateStatusBar();
  285. this->PrintKeys();
  286. touchwin(stdscr);
  287. refresh();
  288. }
  289. void cmCursesMainForm::PrintKeys(int process /* = 0 */)
  290. {
  291. int x, y;
  292. getmaxyx(stdscr, y, x);
  293. if (x < cmCursesMainForm::MIN_WIDTH || x < this->InitialWidth ||
  294. y < cmCursesMainForm::MIN_HEIGHT) {
  295. return;
  296. }
  297. // Give the current widget (if it exists), a chance to print keys
  298. cmCursesWidget* cw = nullptr;
  299. if (this->Form) {
  300. FIELD* currentField = current_field(this->Form);
  301. cw = reinterpret_cast<cmCursesWidget*>(field_userptr(currentField));
  302. }
  303. char fmt_s[] = "%s";
  304. if (cw == nullptr || !cw->PrintKeys()) {
  305. char firstLine[512] = "";
  306. char secondLine[512] = "";
  307. char thirdLine[512] = "";
  308. if (process) {
  309. memset(firstLine, ' ', 68);
  310. memset(secondLine, ' ', 68);
  311. memset(thirdLine, ' ', 68);
  312. } else {
  313. if (this->OkToGenerate) {
  314. sprintf(firstLine,
  315. "Press [c] to configure Press [g] to generate and exit");
  316. } else {
  317. sprintf(firstLine,
  318. "Press [c] to configure ");
  319. }
  320. {
  321. const char* toggleKeyInstruction =
  322. "Press [t] to toggle advanced mode (Currently %s)";
  323. sprintf(thirdLine, toggleKeyInstruction,
  324. this->AdvancedMode ? "On" : "Off");
  325. }
  326. sprintf(secondLine,
  327. "Press [h] for help "
  328. "Press [q] to quit without generating");
  329. }
  330. curses_move(y - 4, 0);
  331. char fmt[512] =
  332. "Press [enter] to edit option Press [d] to delete an entry";
  333. if (process) {
  334. memset(fmt, ' ', 27);
  335. }
  336. printw(fmt_s, fmt);
  337. curses_move(y - 3, 0);
  338. printw(fmt_s, firstLine);
  339. curses_move(y - 2, 0);
  340. printw(fmt_s, secondLine);
  341. curses_move(y - 1, 0);
  342. printw(fmt_s, thirdLine);
  343. }
  344. if (cw) {
  345. char pageLine[512] = "";
  346. sprintf(pageLine, "Page %d of %d", cw->GetPage(), this->NumberOfPages);
  347. curses_move(0, 65 - static_cast<unsigned int>(strlen(pageLine)) - 1);
  348. printw(fmt_s, pageLine);
  349. }
  350. pos_form_cursor(this->Form);
  351. }
  352. // Print the key of the current entry and the CMake version
  353. // on the status bar. Designed for a width of 80 chars.
  354. void cmCursesMainForm::UpdateStatusBar(const char* message)
  355. {
  356. int x, y;
  357. getmaxyx(stdscr, y, x);
  358. // If window size is too small, display error and return
  359. if (x < cmCursesMainForm::MIN_WIDTH || x < this->InitialWidth ||
  360. y < cmCursesMainForm::MIN_HEIGHT) {
  361. curses_clear();
  362. curses_move(0, 0);
  363. char fmt[] = "Window is too small. A size of at least %dx%d is required.";
  364. printw(fmt,
  365. (cmCursesMainForm::MIN_WIDTH < this->InitialWidth
  366. ? this->InitialWidth
  367. : cmCursesMainForm::MIN_WIDTH),
  368. cmCursesMainForm::MIN_HEIGHT);
  369. touchwin(stdscr);
  370. wrefresh(stdscr);
  371. return;
  372. }
  373. // Get the key of the current entry
  374. FIELD* cur = current_field(this->Form);
  375. int findex = field_index(cur);
  376. cmCursesWidget* lbl = nullptr;
  377. if (findex >= 0) {
  378. lbl = reinterpret_cast<cmCursesWidget*>(
  379. field_userptr(this->Fields[findex - 2]));
  380. }
  381. char help[128] = "";
  382. const char* curField = "";
  383. if (lbl) {
  384. curField = lbl->GetValue();
  385. // Get the help string of the current entry
  386. // and add it to the help string
  387. const char* existingValue =
  388. this->CMakeInstance->GetState()->GetCacheEntryValue(curField);
  389. if (existingValue) {
  390. const char* hs = this->CMakeInstance->GetState()->GetCacheEntryProperty(
  391. curField, "HELPSTRING");
  392. if (hs) {
  393. strncpy(help, hs, 127);
  394. help[127] = '\0';
  395. } else {
  396. help[0] = 0;
  397. }
  398. } else {
  399. sprintf(help, " ");
  400. }
  401. }
  402. // Join the key, help string and pad with spaces
  403. // (or truncate) as necessary
  404. char bar[cmCursesMainForm::MAX_WIDTH];
  405. size_t curFieldLen = strlen(curField);
  406. size_t helpLen = strlen(help);
  407. size_t width = std::min<size_t>(x, cmCursesMainForm::MAX_WIDTH);
  408. if (message) {
  409. curField = message;
  410. curFieldLen = strlen(message);
  411. strncpy(bar, curField, width);
  412. if (curFieldLen < width) {
  413. memset(bar + curFieldLen, ' ', width - curFieldLen);
  414. }
  415. } else {
  416. strncpy(bar, curField, width);
  417. if (curFieldLen < width) {
  418. bar[curFieldLen] = ':';
  419. bar[curFieldLen + 1] = ' ';
  420. strncpy(bar + curFieldLen + 2, help, width - curFieldLen - 2);
  421. if (curFieldLen + helpLen + 2 < width) {
  422. memset(bar + curFieldLen + helpLen + 2, ' ',
  423. width - (curFieldLen + helpLen + 2));
  424. }
  425. }
  426. }
  427. bar[width] = '\0';
  428. // Display CMake version info on the next line
  429. // We want to display this on the right
  430. char version[cmCursesMainForm::MAX_WIDTH];
  431. char vertmp[128];
  432. sprintf(vertmp, "CMake Version %s", cmVersion::GetCMakeVersion());
  433. size_t sideSpace = (width - strlen(vertmp));
  434. memset(version, ' ', sideSpace);
  435. sprintf(version + sideSpace, "%s", vertmp);
  436. version[width] = '\0';
  437. // Now print both lines
  438. char fmt_s[] = "%s";
  439. curses_move(y - 5, 0);
  440. attron(A_STANDOUT);
  441. printw(fmt_s, bar);
  442. attroff(A_STANDOUT);
  443. curses_move(y - 4, 0);
  444. printw(fmt_s, version);
  445. pos_form_cursor(this->Form);
  446. }
  447. void cmCursesMainForm::UpdateProgress(const std::string& msg, float prog)
  448. {
  449. char tmp[1024];
  450. const char* cmsg = tmp;
  451. if (prog >= 0) {
  452. sprintf(tmp, "%s %i%%", msg.c_str(), static_cast<int>(100 * prog));
  453. } else {
  454. cmsg = msg.c_str();
  455. }
  456. this->UpdateStatusBar(cmsg);
  457. this->PrintKeys(1);
  458. curses_move(1, 1);
  459. touchwin(stdscr);
  460. refresh();
  461. }
  462. int cmCursesMainForm::Configure(int noconfigure)
  463. {
  464. int xi, yi;
  465. getmaxyx(stdscr, yi, xi);
  466. curses_move(1, 1);
  467. this->UpdateStatusBar("Configuring, please wait...");
  468. this->PrintKeys(1);
  469. touchwin(stdscr);
  470. refresh();
  471. this->CMakeInstance->SetProgressCallback(
  472. [this](const std::string& msg, float prog) {
  473. this->UpdateProgress(msg, prog);
  474. });
  475. // always save the current gui values to disk
  476. this->FillCacheManagerFromUI();
  477. this->CMakeInstance->SaveCache(
  478. this->CMakeInstance->GetHomeOutputDirectory());
  479. this->LoadCache(nullptr);
  480. // Get rid of previous errors
  481. this->Errors = std::vector<std::string>();
  482. // run the generate process
  483. this->OkToGenerate = true;
  484. int retVal;
  485. if (noconfigure) {
  486. retVal = this->CMakeInstance->DoPreConfigureChecks();
  487. this->OkToGenerate = false;
  488. if (retVal > 0) {
  489. retVal = 0;
  490. }
  491. } else {
  492. retVal = this->CMakeInstance->Configure();
  493. }
  494. this->CMakeInstance->SetProgressCallback(nullptr);
  495. keypad(stdscr, true); /* Use key symbols as KEY_DOWN */
  496. if (retVal != 0 || !this->Errors.empty()) {
  497. // see if there was an error
  498. if (cmSystemTools::GetErrorOccuredFlag()) {
  499. this->OkToGenerate = false;
  500. }
  501. int xx, yy;
  502. getmaxyx(stdscr, yy, xx);
  503. cmCursesLongMessageForm* msgs =
  504. new cmCursesLongMessageForm(this->Errors,
  505. cmSystemTools::GetErrorOccuredFlag()
  506. ? "Errors occurred during the last pass."
  507. : "CMake produced the following output.");
  508. // reset error condition
  509. cmSystemTools::ResetErrorOccuredFlag();
  510. CurrentForm = msgs;
  511. msgs->Render(1, 1, xx, yy);
  512. msgs->HandleInput();
  513. // If they typed the wrong source directory, we report
  514. // an error and exit
  515. if (retVal == -2) {
  516. return retVal;
  517. }
  518. CurrentForm = this;
  519. this->Render(1, 1, xx, yy);
  520. }
  521. this->InitializeUI();
  522. this->Render(1, 1, xi, yi);
  523. return 0;
  524. }
  525. int cmCursesMainForm::Generate()
  526. {
  527. int xi, yi;
  528. getmaxyx(stdscr, yi, xi);
  529. curses_move(1, 1);
  530. this->UpdateStatusBar("Generating, please wait...");
  531. this->PrintKeys(1);
  532. touchwin(stdscr);
  533. refresh();
  534. this->CMakeInstance->SetProgressCallback(
  535. [this](const std::string& msg, float prog) {
  536. this->UpdateProgress(msg, prog);
  537. });
  538. // Get rid of previous errors
  539. this->Errors = std::vector<std::string>();
  540. // run the generate process
  541. int retVal = this->CMakeInstance->Generate();
  542. this->CMakeInstance->SetProgressCallback(nullptr);
  543. keypad(stdscr, true); /* Use key symbols as KEY_DOWN */
  544. if (retVal != 0 || !this->Errors.empty()) {
  545. // see if there was an error
  546. if (cmSystemTools::GetErrorOccuredFlag()) {
  547. this->OkToGenerate = false;
  548. }
  549. // reset error condition
  550. cmSystemTools::ResetErrorOccuredFlag();
  551. int xx, yy;
  552. getmaxyx(stdscr, yy, xx);
  553. const char* title = "Messages during last pass.";
  554. if (cmSystemTools::GetErrorOccuredFlag()) {
  555. title = "Errors occurred during the last pass.";
  556. }
  557. cmCursesLongMessageForm* msgs =
  558. new cmCursesLongMessageForm(this->Errors, title);
  559. CurrentForm = msgs;
  560. msgs->Render(1, 1, xx, yy);
  561. msgs->HandleInput();
  562. // If they typed the wrong source directory, we report
  563. // an error and exit
  564. if (retVal == -2) {
  565. return retVal;
  566. }
  567. CurrentForm = this;
  568. this->Render(1, 1, xx, yy);
  569. }
  570. this->InitializeUI();
  571. this->Render(1, 1, xi, yi);
  572. return 0;
  573. }
  574. void cmCursesMainForm::AddError(const std::string& message,
  575. const char* /*unused*/)
  576. {
  577. this->Errors.emplace_back(message);
  578. }
  579. void cmCursesMainForm::RemoveEntry(const char* value)
  580. {
  581. if (!value) {
  582. return;
  583. }
  584. auto removeIt =
  585. std::find_if(this->Entries->begin(), this->Entries->end(),
  586. [value](cmCursesCacheEntryComposite* entry) -> bool {
  587. const char* val = entry->GetValue();
  588. return val != nullptr && !strcmp(value, val);
  589. });
  590. if (removeIt != this->Entries->end()) {
  591. this->CMakeInstance->UnwatchUnusedCli(value);
  592. this->Entries->erase(removeIt);
  593. }
  594. }
  595. // copy from the list box to the cache manager
  596. void cmCursesMainForm::FillCacheManagerFromUI()
  597. {
  598. for (cmCursesCacheEntryComposite* entry : *this->Entries) {
  599. const std::string& cacheKey = entry->Key;
  600. const char* existingValue =
  601. this->CMakeInstance->GetState()->GetCacheEntryValue(cacheKey);
  602. if (existingValue) {
  603. std::string oldValue = existingValue;
  604. std::string newValue = entry->Entry->GetValue();
  605. std::string fixedOldValue;
  606. std::string fixedNewValue;
  607. cmStateEnums::CacheEntryType t =
  608. this->CMakeInstance->GetState()->GetCacheEntryType(cacheKey);
  609. this->FixValue(t, oldValue, fixedOldValue);
  610. this->FixValue(t, newValue, fixedNewValue);
  611. if (!(fixedOldValue == fixedNewValue)) {
  612. // The user has changed the value. Mark it as modified.
  613. this->CMakeInstance->GetState()->SetCacheEntryBoolProperty(
  614. cacheKey, "MODIFIED", true);
  615. this->CMakeInstance->GetState()->SetCacheEntryValue(cacheKey,
  616. fixedNewValue);
  617. }
  618. }
  619. }
  620. }
  621. void cmCursesMainForm::FixValue(cmStateEnums::CacheEntryType type,
  622. const std::string& in, std::string& out) const
  623. {
  624. out = in.substr(0, in.find_last_not_of(' ') + 1);
  625. if (type == cmStateEnums::PATH || type == cmStateEnums::FILEPATH) {
  626. cmSystemTools::ConvertToUnixSlashes(out);
  627. }
  628. if (type == cmStateEnums::BOOL) {
  629. if (cmSystemTools::IsOff(out)) {
  630. out = "OFF";
  631. } else {
  632. out = "ON";
  633. }
  634. }
  635. }
  636. void cmCursesMainForm::HandleInput()
  637. {
  638. int x = 0, y = 0;
  639. if (!this->Form) {
  640. return;
  641. }
  642. FIELD* currentField;
  643. cmCursesWidget* currentWidget;
  644. char debugMessage[128];
  645. for (;;) {
  646. this->UpdateStatusBar();
  647. this->PrintKeys();
  648. if (this->SearchMode) {
  649. std::string searchstr = "Search: " + this->SearchString;
  650. this->UpdateStatusBar(searchstr.c_str());
  651. this->PrintKeys(1);
  652. curses_move(y - 5, static_cast<unsigned int>(searchstr.size()));
  653. // curses_move(1,1);
  654. touchwin(stdscr);
  655. refresh();
  656. }
  657. int key = getch();
  658. getmaxyx(stdscr, y, x);
  659. // If window too small, handle 'q' only
  660. if (x < cmCursesMainForm::MIN_WIDTH || y < cmCursesMainForm::MIN_HEIGHT) {
  661. // quit
  662. if (key == 'q') {
  663. break;
  664. }
  665. continue;
  666. }
  667. currentField = current_field(this->Form);
  668. currentWidget =
  669. reinterpret_cast<cmCursesWidget*>(field_userptr(currentField));
  670. bool widgetHandled = false;
  671. if (this->SearchMode) {
  672. if (key == 10 || key == KEY_ENTER) {
  673. this->SearchMode = false;
  674. if (!this->SearchString.empty()) {
  675. this->JumpToCacheEntry(this->SearchString.c_str());
  676. this->OldSearchString = this->SearchString;
  677. }
  678. this->SearchString = "";
  679. }
  680. /*
  681. else if ( key == KEY_ESCAPE )
  682. {
  683. this->SearchMode = false;
  684. }
  685. */
  686. else if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z') ||
  687. (key >= '0' && key <= '9') || (key == '_')) {
  688. if (this->SearchString.size() <
  689. static_cast<std::string::size_type>(x - 10)) {
  690. this->SearchString += static_cast<char>(key);
  691. }
  692. } else if (key == ctrl('h') || key == KEY_BACKSPACE || key == KEY_DC) {
  693. if (!this->SearchString.empty()) {
  694. this->SearchString.resize(this->SearchString.size() - 1);
  695. }
  696. }
  697. } else if (currentWidget && !this->SearchMode) {
  698. // Ask the current widget if it wants to handle input
  699. widgetHandled = currentWidget->HandleInput(key, this, stdscr);
  700. if (widgetHandled) {
  701. this->OkToGenerate = false;
  702. this->UpdateStatusBar();
  703. this->PrintKeys();
  704. }
  705. }
  706. if ((!currentWidget || !widgetHandled) && !this->SearchMode) {
  707. // If the current widget does not want to handle input,
  708. // we handle it.
  709. sprintf(debugMessage, "Main form handling input, key: %d", key);
  710. cmCursesForm::LogMessage(debugMessage);
  711. // quit
  712. if (key == 'q') {
  713. break;
  714. }
  715. // if not end of page, next field otherwise next page
  716. // each entry consists of fields: label, isnew, value
  717. // therefore, the label field for the prev. entry is index-5
  718. // and the label field for the next entry is index+1
  719. // (index always corresponds to the value field)
  720. // scroll down with arrow down, ctrl+n (emacs binding), or j (vim
  721. // binding)
  722. if (key == KEY_DOWN || key == ctrl('n') || key == 'j') {
  723. FIELD* cur = current_field(this->Form);
  724. size_t findex = field_index(cur);
  725. if (findex == 3 * this->NumberOfVisibleEntries - 1) {
  726. continue;
  727. }
  728. if (new_page(this->Fields[findex + 1])) {
  729. form_driver(this->Form, REQ_NEXT_PAGE);
  730. } else {
  731. form_driver(this->Form, REQ_NEXT_FIELD);
  732. }
  733. }
  734. // if not beginning of page, previous field, otherwise previous page
  735. // each entry consists of fields: label, isnew, value
  736. // therefore, the label field for the prev. entry is index-5
  737. // and the label field for the next entry is index+1
  738. // (index always corresponds to the value field)
  739. // scroll down with arrow up, ctrl+p (emacs binding), or k (vim binding)
  740. else if (key == KEY_UP || key == ctrl('p') || key == 'k') {
  741. FIELD* cur = current_field(this->Form);
  742. int findex = field_index(cur);
  743. if (findex == 2) {
  744. continue;
  745. }
  746. if (new_page(this->Fields[findex - 2])) {
  747. form_driver(this->Form, REQ_PREV_PAGE);
  748. set_current_field(this->Form, this->Fields[findex - 3]);
  749. } else {
  750. form_driver(this->Form, REQ_PREV_FIELD);
  751. }
  752. }
  753. // pg down
  754. else if (key == KEY_NPAGE || key == ctrl('d')) {
  755. form_driver(this->Form, REQ_NEXT_PAGE);
  756. }
  757. // pg up
  758. else if (key == KEY_PPAGE || key == ctrl('u')) {
  759. form_driver(this->Form, REQ_PREV_PAGE);
  760. }
  761. // configure
  762. else if (key == 'c') {
  763. this->Configure();
  764. }
  765. // display help
  766. else if (key == 'h') {
  767. getmaxyx(stdscr, y, x);
  768. FIELD* cur = current_field(this->Form);
  769. int findex = field_index(cur);
  770. cmCursesWidget* lbl = reinterpret_cast<cmCursesWidget*>(
  771. field_userptr(this->Fields[findex - 2]));
  772. const char* curField = lbl->GetValue();
  773. const char* helpString = nullptr;
  774. const char* existingValue =
  775. this->CMakeInstance->GetState()->GetCacheEntryValue(curField);
  776. if (existingValue) {
  777. helpString = this->CMakeInstance->GetState()->GetCacheEntryProperty(
  778. curField, "HELPSTRING");
  779. }
  780. if (helpString) {
  781. char* message = new char
  782. [strlen(curField) + strlen(helpString) +
  783. strlen(
  784. "Current option is: \n Help string for this option is: \n") +
  785. 10];
  786. sprintf(
  787. message,
  788. "Current option is: %s\nHelp string for this option is: %s\n",
  789. curField, helpString);
  790. this->HelpMessage[1] = message;
  791. delete[] message;
  792. } else {
  793. this->HelpMessage[1] = "";
  794. }
  795. cmCursesLongMessageForm* msgs =
  796. new cmCursesLongMessageForm(this->HelpMessage, "Help.");
  797. CurrentForm = msgs;
  798. msgs->Render(1, 1, x, y);
  799. msgs->HandleInput();
  800. CurrentForm = this;
  801. this->Render(1, 1, x, y);
  802. set_current_field(this->Form, cur);
  803. }
  804. // display last errors
  805. else if (key == 'l') {
  806. getmaxyx(stdscr, y, x);
  807. cmCursesLongMessageForm* msgs = new cmCursesLongMessageForm(
  808. this->Errors, "Errors occurred during the last pass.");
  809. CurrentForm = msgs;
  810. msgs->Render(1, 1, x, y);
  811. msgs->HandleInput();
  812. CurrentForm = this;
  813. this->Render(1, 1, x, y);
  814. } else if (key == '/') {
  815. this->SearchMode = true;
  816. this->UpdateStatusBar("Search");
  817. this->PrintKeys(1);
  818. touchwin(stdscr);
  819. refresh();
  820. } else if (key == 'n') {
  821. if (!this->OldSearchString.empty()) {
  822. this->JumpToCacheEntry(this->OldSearchString.c_str());
  823. }
  824. }
  825. // switch advanced on/off
  826. else if (key == 't') {
  827. if (this->AdvancedMode) {
  828. this->AdvancedMode = false;
  829. } else {
  830. this->AdvancedMode = true;
  831. }
  832. getmaxyx(stdscr, y, x);
  833. this->RePost();
  834. this->Render(1, 1, x, y);
  835. }
  836. // generate and exit
  837. else if (key == 'g') {
  838. if (this->OkToGenerate) {
  839. this->Generate();
  840. break;
  841. }
  842. }
  843. // delete cache entry
  844. else if (key == 'd' && this->NumberOfVisibleEntries) {
  845. this->OkToGenerate = false;
  846. FIELD* cur = current_field(this->Form);
  847. size_t findex = field_index(cur);
  848. // make the next or prev. current field after deletion
  849. // each entry consists of fields: label, isnew, value
  850. // therefore, the label field for the prev. entry is findex-5
  851. // and the label field for the next entry is findex+1
  852. // (findex always corresponds to the value field)
  853. FIELD* nextCur;
  854. if (findex == 2) {
  855. nextCur = nullptr;
  856. } else if (findex == 3 * this->NumberOfVisibleEntries - 1) {
  857. nextCur = this->Fields[findex - 5];
  858. } else {
  859. nextCur = this->Fields[findex + 1];
  860. }
  861. // Get the label widget
  862. // each entry consists of fields: label, isnew, value
  863. // therefore, the label field for the is findex-2
  864. // (findex always corresponds to the value field)
  865. cmCursesWidget* lbl = reinterpret_cast<cmCursesWidget*>(
  866. field_userptr(this->Fields[findex - 2]));
  867. if (lbl) {
  868. this->CMakeInstance->GetState()->RemoveCacheEntry(lbl->GetValue());
  869. std::string nextVal;
  870. if (nextCur) {
  871. nextVal =
  872. (reinterpret_cast<cmCursesWidget*>(field_userptr(nextCur))
  873. ->GetValue());
  874. }
  875. getmaxyx(stdscr, y, x);
  876. this->RemoveEntry(lbl->GetValue());
  877. this->RePost();
  878. this->Render(1, 1, x, y);
  879. if (nextCur) {
  880. // make the next or prev. current field after deletion
  881. auto nextEntryIt =
  882. std::find_if(this->Entries->begin(), this->Entries->end(),
  883. [&nextVal](cmCursesCacheEntryComposite* entry) {
  884. return nextVal == entry->Key;
  885. });
  886. if (nextEntryIt != this->Entries->end()) {
  887. set_current_field(this->Form, (*nextEntryIt)->Entry->Field);
  888. }
  889. }
  890. }
  891. }
  892. }
  893. touchwin(stdscr);
  894. wrefresh(stdscr);
  895. }
  896. }
  897. int cmCursesMainForm::LoadCache(const char* /*unused*/)
  898. {
  899. int r = this->CMakeInstance->LoadCache();
  900. if (r < 0) {
  901. return r;
  902. }
  903. this->CMakeInstance->SetCacheArgs(this->Args);
  904. this->CMakeInstance->PreLoadCMakeFiles();
  905. return r;
  906. }
  907. void cmCursesMainForm::JumpToCacheEntry(const char* astr)
  908. {
  909. std::string str;
  910. if (astr) {
  911. str = cmSystemTools::LowerCase(astr);
  912. }
  913. if (str.empty()) {
  914. return;
  915. }
  916. FIELD* cur = current_field(this->Form);
  917. int start_index = field_index(cur);
  918. int findex = start_index;
  919. for (;;) {
  920. if (!str.empty()) {
  921. cmCursesWidget* lbl = nullptr;
  922. if (findex >= 0) {
  923. lbl = reinterpret_cast<cmCursesWidget*>(
  924. field_userptr(this->Fields[findex - 2]));
  925. }
  926. if (lbl) {
  927. const char* curField = lbl->GetValue();
  928. if (curField) {
  929. std::string cfld = cmSystemTools::LowerCase(curField);
  930. if (cfld.find(str) != std::string::npos && findex != start_index) {
  931. break;
  932. }
  933. }
  934. }
  935. }
  936. if (size_t(findex) >= 3 * this->NumberOfVisibleEntries - 1) {
  937. set_current_field(this->Form, this->Fields[2]);
  938. } else if (new_page(this->Fields[findex + 1])) {
  939. form_driver(this->Form, REQ_NEXT_PAGE);
  940. } else {
  941. form_driver(this->Form, REQ_NEXT_FIELD);
  942. }
  943. /*
  944. char buffer[1024];
  945. sprintf(buffer, "Line: %d != %d / %d\n", findex, idx,
  946. this->NumberOfVisibleEntries);
  947. touchwin(stdscr);
  948. refresh();
  949. this->UpdateStatusBar( buffer );
  950. usleep(100000);
  951. */
  952. cur = current_field(this->Form);
  953. findex = field_index(cur);
  954. if (findex == start_index) {
  955. break;
  956. }
  957. }
  958. }
  959. const char* cmCursesMainForm::s_ConstHelpMessage =
  960. "CMake is used to configure and generate build files for software projects. "
  961. "The basic steps for configuring a project with ccmake are as follows:\n\n"
  962. "1. Run ccmake in the directory where you want the object and executable "
  963. "files to be placed (build directory). If the source directory is not the "
  964. "same as this build directory, you have to specify it as an argument on the "
  965. "command line.\n\n"
  966. "2. When ccmake is run, it will read the configuration files and display "
  967. "the current build options. "
  968. "If you have run CMake before and have updated the configuration files "
  969. "since then, any new entries will be displayed on top and will be marked "
  970. "with a *. "
  971. "On the other hand, the first time you run ccmake, all build options will "
  972. "be new and will be marked as such. "
  973. "At this point, you can modify any options (see keys below) you want to "
  974. "change. "
  975. "When you are satisfied with your changes, press 'c' to have CMake process "
  976. "the configuration files. "
  977. "Please note that changing some options may cause new ones to appear. These "
  978. "will be shown on top and will be marked with *. "
  979. "Repeat this procedure until you are satisfied with all the options and "
  980. "there are no new entries. "
  981. "At this point, a new command will appear: G)enerate and Exit. You can now "
  982. "hit 'g' to have CMake generate all the build files (i.e. makefiles or "
  983. "project files) and exit. "
  984. "At any point during the process, you can exit ccmake with 'q'. However, "
  985. "this will not generate/change any build files.\n\n"
  986. "ccmake KEYS:\n\n"
  987. "Navigation: "
  988. "You can use the arrow keys and page up, down to navigate the options. "
  989. "Alternatively, you can use the following keys: \n"
  990. " C-n or j : next option\n"
  991. " C-p or k : previous options\n"
  992. " C-d : down one page\n"
  993. " C-u : up one page\n\n"
  994. "Editing options: "
  995. "To change an option press enter or return. If the current options is a "
  996. "boolean, this will toggle its value. "
  997. "Otherwise, ccmake will enter edit mode. Alternatively, you can toggle "
  998. "a bool variable by pressing space, and enter edit mode with i."
  999. "In this mode you can edit an option using arrow keys and backspace. "
  1000. "Alternatively, you can use the following keys:\n"
  1001. " C-b : back one character\n"
  1002. " C-f : forward one character\n"
  1003. " C-a : go to the beginning of the field\n"
  1004. " C-e : go to the end of the field\n"
  1005. " C-d : delete previous character\n"
  1006. " C-k : kill the rest of the field\n"
  1007. " Esc : Restore field (discard last changes)\n"
  1008. " Enter : Leave edit mode\n"
  1009. "Commands:\n"
  1010. " q : quit ccmake without generating build files\n"
  1011. " h : help, shows this screen\n"
  1012. " c : process the configuration files with the current options\n"
  1013. " g : generate build files and exit, only available when there are no "
  1014. "new options and no errors have been detected during last configuration.\n"
  1015. " l : shows last errors\n"
  1016. " d : delete an option\n"
  1017. " t : toggles advanced mode. In normal mode, only the most important "
  1018. "options are shown. In advanced mode, all options are shown. We recommend "
  1019. "using normal mode unless you are an expert.\n"
  1020. " / : search for a variable name.\n";