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"
12 #include "cmCursesCacheEntryComposite.h"
13 #include "cmCursesDummyWidget.h"
14 #include "cmCursesForm.h"
15 #include "cmCursesLabelWidget.h"
16 #include "cmCursesLongMessageForm.h"
17 #include "cmCursesStandardIncludes.h"
18 #include "cmCursesStringWidget.h"
19 #include "cmCursesWidget.h"
21 #include "cmStateTypes.h"
22 #include "cmStringAlgorithms.h"
23 #include "cmSystemTools.h"
24 #include "cmVersion.h"
27 inline int ctrl(int z)
32 cmCursesMainForm::cmCursesMainForm(std::vector<std::string> args,
34 : Args(std::move(args))
35 , InitialWidth(initWidth)
37 this->HasNonStatusOutputs = false;
38 this->NumberOfPages = 0;
39 this->AdvancedMode = false;
40 this->NumberOfVisibleEntries = 0;
41 this->OkToGenerate = false;
42 this->HelpMessage.emplace_back(
43 "Welcome to ccmake, curses based user interface for CMake.");
44 this->HelpMessage.emplace_back();
45 this->HelpMessage.emplace_back(s_ConstHelpMessage);
47 cm::make_unique<cmake>(cmake::RoleProject, cmState::Project);
48 this->CMakeInstance->SetCMakeEditCommand(
49 cmSystemTools::GetCMakeCursesCommand());
51 // create the arguments for the cmake object
52 std::string whereCMake =
53 cmStrCat(cmSystemTools::GetProgramPath(this->Args[0]), "/cmake");
54 this->Args[0] = whereCMake;
55 this->CMakeInstance->SetArgs(this->Args);
56 this->SearchMode = false;
59 cmCursesMainForm::~cmCursesMainForm()
62 unpost_form(this->Form);
63 free_form(this->Form);
68 // See if a cache entry is in the list of entries in the ui.
69 bool cmCursesMainForm::LookForCacheEntry(const std::string& key)
71 return std::any_of(this->Entries.begin(), this->Entries.end(),
72 [&key](cmCursesCacheEntryComposite const& entry) {
73 return key == entry.Key;
77 // Create new cmCursesCacheEntryComposite entries from the cache
78 void cmCursesMainForm::InitializeUI()
80 // Create a vector of cmCursesCacheEntryComposite's
81 // which contain labels, entries and new entry markers
82 std::vector<cmCursesCacheEntryComposite> newEntries;
83 std::vector<std::string> cacheKeys =
84 this->CMakeInstance->GetState()->GetCacheEntryKeys();
85 newEntries.reserve(cacheKeys.size());
87 // Count non-internal and non-static entries
90 for (std::string const& key : cacheKeys) {
91 cmStateEnums::CacheEntryType t =
92 this->CMakeInstance->GetState()->GetCacheEntryType(key);
93 if (t != cmStateEnums::INTERNAL && t != cmStateEnums::STATIC &&
94 t != cmStateEnums::UNINITIALIZED) {
99 int entrywidth = this->InitialWidth - 35;
102 // If cache is empty, display a label saying so and a
103 // dummy entry widget (does not respond to input)
104 cmCursesCacheEntryComposite comp("EMPTY CACHE", 30, 30);
105 comp.Entry = cm::make_unique<cmCursesDummyWidget>(1, 1, 1, 1);
106 newEntries.emplace_back(std::move(comp));
108 // 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) {
119 if (!this->LookForCacheEntry(key)) {
120 newEntries.emplace_back(key, this->CMakeInstance->GetState(), true, 30,
122 this->OkToGenerate = false;
126 // then add entries which are old
127 for (std::string const& key : cacheKeys) {
128 cmStateEnums::CacheEntryType t =
129 this->CMakeInstance->GetState()->GetCacheEntryType(key);
130 if (t == cmStateEnums::INTERNAL || t == cmStateEnums::STATIC ||
131 t == cmStateEnums::UNINITIALIZED) {
135 if (this->LookForCacheEntry(key)) {
136 newEntries.emplace_back(key, this->CMakeInstance->GetState(), false,
142 // Replace old entries
143 this->Entries = std::move(newEntries);
145 // Compute fields from composites
149 void cmCursesMainForm::RePost()
151 // Create the fields to be passed to the form.
153 unpost_form(this->Form);
154 free_form(this->Form);
155 this->Form = nullptr;
157 this->Fields.clear();
158 if (this->AdvancedMode) {
159 this->NumberOfVisibleEntries = this->Entries.size();
161 // If normal mode, count only non-advanced entries
162 this->NumberOfVisibleEntries = 0;
163 for (cmCursesCacheEntryComposite& entry : this->Entries) {
164 const char* existingValue =
165 this->CMakeInstance->GetState()->GetCacheEntryValue(entry.GetValue());
167 this->CMakeInstance->GetState()->GetCacheEntryPropertyAsBool(
168 entry.GetValue(), "ADVANCED");
169 if (!existingValue || (!this->AdvancedMode && advanced)) {
172 this->NumberOfVisibleEntries++;
175 // there is always one even if it is the dummy one
176 if (this->NumberOfVisibleEntries == 0) {
177 this->NumberOfVisibleEntries = 1;
179 // Assign the fields: 3 for each entry: label, new entry marker
180 // ('*' or ' ') and entry widget
181 this->Fields.reserve(3 * this->NumberOfVisibleEntries + 1);
184 for (cmCursesCacheEntryComposite& entry : this->Entries) {
185 const char* existingValue =
186 this->CMakeInstance->GetState()->GetCacheEntryValue(entry.GetValue());
188 this->CMakeInstance->GetState()->GetCacheEntryPropertyAsBool(
189 entry.GetValue(), "ADVANCED");
190 if (!existingValue || (!this->AdvancedMode && advanced)) {
193 this->Fields.push_back(entry.Label->Field);
194 this->Fields.push_back(entry.IsNewLabel->Field);
195 this->Fields.push_back(entry.Entry->Field);
197 // if no cache entries there should still be one dummy field
198 if (this->Fields.empty()) {
199 const auto& front = this->Entries.front();
200 this->Fields.push_back(front.Label->Field);
201 this->Fields.push_back(front.IsNewLabel->Field);
202 this->Fields.push_back(front.Entry->Field);
203 this->NumberOfVisibleEntries = 1;
205 // Has to be null terminated.
206 this->Fields.push_back(nullptr);
209 void cmCursesMainForm::Render(int left, int top, int width, int height)
213 FIELD* currentField = current_field(this->Form);
215 reinterpret_cast<cmCursesWidget*>(field_userptr(currentField));
216 // If in edit mode, get out of it
217 if (cw->GetType() == cmStateEnums::STRING ||
218 cw->GetType() == cmStateEnums::PATH ||
219 cw->GetType() == cmStateEnums::FILEPATH) {
220 cmCursesStringWidget* sw = static_cast<cmCursesStringWidget*>(cw);
221 sw->SetInEdit(false);
223 // Delete the previous form
224 unpost_form(this->Form);
225 free_form(this->Form);
226 this->Form = nullptr;
230 if (width < cmCursesMainForm::MIN_WIDTH || width < this->InitialWidth ||
231 height < cmCursesMainForm::MIN_HEIGHT) {
235 // Leave room for toolbar
238 if (this->AdvancedMode) {
239 this->NumberOfVisibleEntries = this->Entries.size();
241 // If normal, display only non-advanced entries
242 this->NumberOfVisibleEntries = 0;
243 for (cmCursesCacheEntryComposite& entry : this->Entries) {
244 const char* existingValue =
245 this->CMakeInstance->GetState()->GetCacheEntryValue(entry.GetValue());
247 this->CMakeInstance->GetState()->GetCacheEntryPropertyAsBool(
248 entry.GetValue(), "ADVANCED");
249 if (!existingValue || (!this->AdvancedMode && advanced)) {
252 this->NumberOfVisibleEntries++;
256 // Re-adjust the fields according to their place
257 this->NumberOfPages = 1;
261 for (cmCursesCacheEntryComposite& entry : this->Entries) {
262 const char* existingValue =
263 this->CMakeInstance->GetState()->GetCacheEntryValue(entry.GetValue());
265 this->CMakeInstance->GetState()->GetCacheEntryPropertyAsBool(
266 entry.GetValue(), "ADVANCED");
267 if (!existingValue || (!this->AdvancedMode && advanced)) {
270 int row = (i % height) + 1;
271 int page = (i / height) + 1;
272 isNewPage = (page > 1) && (row == 1);
275 this->NumberOfPages++;
277 entry.Label->Move(left, top + row - 1, isNewPage);
278 entry.IsNewLabel->Move(left + 32, top + row - 1, false);
279 entry.Entry->Move(left + 33, top + row - 1, false);
280 entry.Entry->SetPage(this->NumberOfPages);
286 this->Form = new_form(this->Fields.data());
287 post_form(this->Form);
289 this->UpdateStatusBar();
296 void cmCursesMainForm::PrintKeys(int process /* = 0 */)
300 getmaxyx(stdscr, y, x);
301 if (x < cmCursesMainForm::MIN_WIDTH || x < this->InitialWidth ||
302 y < cmCursesMainForm::MIN_HEIGHT) {
306 // Give the current widget (if it exists), a chance to print keys
307 cmCursesWidget* cw = nullptr;
309 FIELD* currentField = current_field(this->Form);
310 cw = reinterpret_cast<cmCursesWidget*>(field_userptr(currentField));
314 if (cw == nullptr || !cw->PrintKeys()) {
315 char firstLine[512] = "";
316 char secondLine[512] = "";
317 char thirdLine[512] = "";
319 memset(firstLine, ' ', 68);
320 memset(secondLine, ' ', 68);
321 memset(thirdLine, ' ', 68);
323 if (this->OkToGenerate) {
325 " [l] Show log output [c] Configure"
329 " [l] Show log output [c] Configure"
333 const char* toggleKeyInstruction =
334 " [t] Toggle advanced mode (currently %s)";
335 sprintf(thirdLine, toggleKeyInstruction,
336 this->AdvancedMode ? "on" : "off");
339 " [h] Help [q] Quit without generating");
342 curses_move(y - 4, 0);
343 char fmt[512] = "Keys: [enter] Edit an entry [d] Delete an entry";
345 memset(fmt, ' ', 57);
348 curses_move(y - 3, 0);
349 printw(fmt_s, firstLine);
350 curses_move(y - 2, 0);
351 printw(fmt_s, secondLine);
352 curses_move(y - 1, 0);
353 printw(fmt_s, thirdLine);
357 char pageLine[512] = "";
358 sprintf(pageLine, "Page %d of %d", cw->GetPage(), this->NumberOfPages);
359 curses_move(0, 65 - static_cast<unsigned int>(strlen(pageLine)) - 1);
360 printw(fmt_s, pageLine);
363 pos_form_cursor(this->Form);
366 // Print the key of the current entry and the CMake version
367 // on the status bar. Designed for a width of 80 chars.
368 void cmCursesMainForm::UpdateStatusBar(cm::optional<std::string> message)
372 getmaxyx(stdscr, y, x);
373 // If window size is too small, display error and return
374 if (x < cmCursesMainForm::MIN_WIDTH || x < this->InitialWidth ||
375 y < cmCursesMainForm::MIN_HEIGHT) {
378 char fmt[] = "Window is too small. A size of at least %dx%d is required.";
380 (cmCursesMainForm::MIN_WIDTH < this->InitialWidth
382 : cmCursesMainForm::MIN_WIDTH),
383 cmCursesMainForm::MIN_HEIGHT);
389 // Find the current label index
390 // Field are grouped by 3, the label should be 2 less than the current index
391 using size_type = decltype(this->Fields)::size_type;
392 size_type currentLabelIndex = field_index(current_field(this->Form)) - 2;
394 // Use the status message if any, otherwise join the key and help string
399 // Get the key of the current entry
400 cmCursesWidget* labelWidget = reinterpret_cast<cmCursesWidget*>(
401 field_userptr(this->Fields[currentLabelIndex]));
402 std::string labelValue = labelWidget->GetValue();
403 bar = labelValue + ": ";
405 // Get the help string of the current entry
406 // and add it to the help string
407 auto cmakeState = this->CMakeInstance->GetState();
408 const char* existingValue = cmakeState->GetCacheEntryValue(labelValue);
410 auto help = cmakeState->GetCacheEntryProperty(labelValue, "HELPSTRING");
416 // Pad with spaces to erase any previous text,
417 // or truncate as necessary to fit the screen
419 curses_move(y - 5, 0);
422 printw(fmt_s, bar.c_str());
425 // Highlight the current label, reset others
426 // Fields are grouped by 3, the first one being the label
427 // so start at 0 and move up by 3 avoiding the last null entry
428 for (size_type index = 0; index < this->Fields.size() - 1; index += 3) {
429 bool currentLabel = index == currentLabelIndex;
430 set_field_fore(this->Fields[index], currentLabel ? A_STANDOUT : A_NORMAL);
433 // Display CMake version under the status bar
434 // We want to display this on the right
435 std::string version = "CMake Version ";
436 version += cmVersion::GetCMakeVersion();
437 version.resize(std::min<std::string::size_type>(x, version.size()));
438 curses_move(y - 4, x - static_cast<int>(version.size()));
439 printw(fmt_s, version.c_str());
441 pos_form_cursor(this->Form);
444 void cmCursesMainForm::UpdateProgress(const std::string& msg, float prog)
447 constexpr int progressBarWidth = 40;
448 int progressBarCompleted = static_cast<int>(progressBarWidth * prog);
449 int percentCompleted = static_cast<int>(100 * prog);
450 this->LastProgress = (percentCompleted < 100 ? " " : "");
451 this->LastProgress += (percentCompleted < 10 ? " " : "");
452 this->LastProgress += std::to_string(percentCompleted) + "% [";
453 this->LastProgress.append(progressBarCompleted, '#');
454 this->LastProgress.append(progressBarWidth - progressBarCompleted, ' ');
455 this->LastProgress += "] " + msg + "...";
457 this->Outputs.emplace_back(msg);
460 this->DisplayOutputs();
463 int cmCursesMainForm::Configure(int noconfigure)
465 this->ResetOutputs();
467 if (noconfigure == 0) {
468 this->UpdateProgress("Configuring", 0);
469 this->CMakeInstance->SetProgressCallback(
470 [this](const std::string& msg, float prog) {
471 this->UpdateProgress(msg, prog);
475 // always save the current gui values to disk
476 this->FillCacheManagerFromUI();
477 this->CMakeInstance->SaveCache(
478 this->CMakeInstance->GetHomeOutputDirectory());
479 this->LoadCache(nullptr);
481 // run the generate process
482 this->OkToGenerate = true;
485 retVal = this->CMakeInstance->DoPreConfigureChecks();
486 this->OkToGenerate = false;
491 retVal = this->CMakeInstance->Configure();
493 this->CMakeInstance->SetProgressCallback(nullptr);
495 keypad(stdscr, true); /* Use key symbols as KEY_DOWN */
497 if (retVal != 0 || this->HasNonStatusOutputs) {
498 // see if there was an error
499 if (cmSystemTools::GetErrorOccuredFlag()) {
500 this->OkToGenerate = false;
504 getmaxyx(stdscr, yy, xx);
505 const char* title = "Configure produced the following output";
506 if (cmSystemTools::GetErrorOccuredFlag()) {
507 title = "Configure failed with the following output";
509 cmCursesLongMessageForm* msgs = new cmCursesLongMessageForm(
510 this->Outputs, title,
511 cmCursesLongMessageForm::ScrollBehavior::ScrollDown);
512 // reset error condition
513 cmSystemTools::ResetErrorOccuredFlag();
515 msgs->Render(1, 1, xx, yy);
517 // If they typed the wrong source directory, we report
524 this->InitializeUI();
528 getmaxyx(stdscr, yi, xi);
529 this->Render(1, 1, xi, yi);
534 int cmCursesMainForm::Generate()
536 this->ResetOutputs();
538 this->UpdateProgress("Generating", 0);
539 this->CMakeInstance->SetProgressCallback(
540 [this](const std::string& msg, float prog) {
541 this->UpdateProgress(msg, prog);
544 // run the generate process
545 int retVal = this->CMakeInstance->Generate();
547 this->CMakeInstance->SetProgressCallback(nullptr);
548 keypad(stdscr, true); /* Use key symbols as KEY_DOWN */
550 if (retVal != 0 || this->HasNonStatusOutputs) {
551 // see if there was an error
552 if (cmSystemTools::GetErrorOccuredFlag()) {
553 this->OkToGenerate = false;
555 // reset error condition
556 cmSystemTools::ResetErrorOccuredFlag();
559 getmaxyx(stdscr, yy, xx);
560 const char* title = "Generate produced the following output";
561 if (cmSystemTools::GetErrorOccuredFlag()) {
562 title = "Generate failed with the following output";
564 cmCursesLongMessageForm* msgs = new cmCursesLongMessageForm(
565 this->Outputs, title,
566 cmCursesLongMessageForm::ScrollBehavior::ScrollDown);
568 msgs->Render(1, 1, xx, yy);
570 // If they typed the wrong source directory, we report
577 this->InitializeUI();
581 getmaxyx(stdscr, yi, xi);
582 this->Render(1, 1, xi, yi);
587 void cmCursesMainForm::AddError(const std::string& message,
588 const char* /*unused*/)
590 this->Outputs.emplace_back(message);
591 this->HasNonStatusOutputs = true;
592 this->DisplayOutputs();
595 void cmCursesMainForm::RemoveEntry(const char* value)
602 std::find_if(this->Entries.begin(), this->Entries.end(),
603 [value](cmCursesCacheEntryComposite& entry) -> bool {
604 const char* val = entry.GetValue();
605 return val != nullptr && !strcmp(value, val);
608 if (removeIt != this->Entries.end()) {
609 this->CMakeInstance->UnwatchUnusedCli(value);
610 this->Entries.erase(removeIt);
614 // copy from the list box to the cache manager
615 void cmCursesMainForm::FillCacheManagerFromUI()
617 for (cmCursesCacheEntryComposite& entry : this->Entries) {
618 const std::string& cacheKey = entry.Key;
619 const char* existingValue =
620 this->CMakeInstance->GetState()->GetCacheEntryValue(cacheKey);
622 std::string oldValue = existingValue;
623 std::string newValue = entry.Entry->GetValue();
624 std::string fixedOldValue;
625 std::string fixedNewValue;
626 cmStateEnums::CacheEntryType t =
627 this->CMakeInstance->GetState()->GetCacheEntryType(cacheKey);
628 this->FixValue(t, oldValue, fixedOldValue);
629 this->FixValue(t, newValue, fixedNewValue);
631 if (!(fixedOldValue == fixedNewValue)) {
632 // The user has changed the value. Mark it as modified.
633 this->CMakeInstance->GetState()->SetCacheEntryBoolProperty(
634 cacheKey, "MODIFIED", true);
635 this->CMakeInstance->GetState()->SetCacheEntryValue(cacheKey,
642 void cmCursesMainForm::FixValue(cmStateEnums::CacheEntryType type,
643 const std::string& in, std::string& out) const
645 out = in.substr(0, in.find_last_not_of(' ') + 1);
646 if (type == cmStateEnums::PATH || type == cmStateEnums::FILEPATH) {
647 cmSystemTools::ConvertToUnixSlashes(out);
649 if (type == cmStateEnums::BOOL) {
658 void cmCursesMainForm::HandleInput()
668 cmCursesWidget* currentWidget;
670 char debugMessage[128];
673 this->UpdateStatusBar();
675 if (this->SearchMode) {
676 std::string searchstr = "Search: " + this->SearchString;
677 this->UpdateStatusBar(searchstr);
679 curses_move(y - 5, static_cast<unsigned int>(searchstr.size()));
686 getmaxyx(stdscr, y, x);
687 // If window too small, handle 'q' only
688 if (x < cmCursesMainForm::MIN_WIDTH || y < cmCursesMainForm::MIN_HEIGHT) {
696 currentField = current_field(this->Form);
698 reinterpret_cast<cmCursesWidget*>(field_userptr(currentField));
700 bool widgetHandled = false;
702 if (this->SearchMode) {
703 if (key == 10 || key == KEY_ENTER) {
704 this->SearchMode = false;
705 if (!this->SearchString.empty()) {
706 this->JumpToCacheEntry(this->SearchString.c_str());
707 this->OldSearchString = this->SearchString;
709 this->SearchString.clear();
712 else if ( key == KEY_ESCAPE )
714 this->SearchMode = false;
717 else if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z') ||
718 (key >= '0' && key <= '9') || (key == '_')) {
719 if (this->SearchString.size() <
720 static_cast<std::string::size_type>(x - 10)) {
721 this->SearchString += static_cast<char>(key);
723 } else if (key == ctrl('h') || key == KEY_BACKSPACE || key == KEY_DC) {
724 if (!this->SearchString.empty()) {
725 this->SearchString.pop_back();
728 } else if (currentWidget && !this->SearchMode) {
729 // Ask the current widget if it wants to handle input
730 widgetHandled = currentWidget->HandleInput(key, this, stdscr);
732 this->OkToGenerate = false;
733 this->UpdateStatusBar();
737 if ((!currentWidget || !widgetHandled) && !this->SearchMode) {
738 // If the current widget does not want to handle input,
740 sprintf(debugMessage, "Main form handling input, key: %d", key);
741 cmCursesForm::LogMessage(debugMessage);
746 // if not end of page, next field otherwise next page
747 // each entry consists of fields: label, isnew, value
748 // therefore, the label field for the prev. entry is index-5
749 // and the label field for the next entry is index+1
750 // (index always corresponds to the value field)
751 // scroll down with arrow down, ctrl+n (emacs binding), or j (vim
753 if (key == KEY_DOWN || key == ctrl('n') || key == 'j') {
754 FIELD* cur = current_field(this->Form);
755 size_t findex = field_index(cur);
756 if (findex == 3 * this->NumberOfVisibleEntries - 1) {
759 if (new_page(this->Fields[findex + 1])) {
760 form_driver(this->Form, REQ_NEXT_PAGE);
762 form_driver(this->Form, REQ_NEXT_FIELD);
765 // if not beginning of page, previous field, otherwise previous page
766 // each entry consists of fields: label, isnew, value
767 // therefore, the label field for the prev. entry is index-5
768 // and the label field for the next entry is index+1
769 // (index always corresponds to the value field)
770 // scroll down with arrow up, ctrl+p (emacs binding), or k (vim binding)
771 else if (key == KEY_UP || key == ctrl('p') || key == 'k') {
772 FIELD* cur = current_field(this->Form);
773 int findex = field_index(cur);
777 if (new_page(this->Fields[findex - 2])) {
778 form_driver(this->Form, REQ_PREV_PAGE);
779 set_current_field(this->Form, this->Fields[findex - 3]);
781 form_driver(this->Form, REQ_PREV_FIELD);
785 else if (key == KEY_NPAGE || key == ctrl('d')) {
786 form_driver(this->Form, REQ_NEXT_PAGE);
789 else if (key == KEY_PPAGE || key == ctrl('u')) {
790 form_driver(this->Form, REQ_PREV_PAGE);
793 else if (key == 'c') {
797 else if (key == 'h') {
798 getmaxyx(stdscr, y, x);
800 FIELD* cur = current_field(this->Form);
801 int findex = field_index(cur);
802 cmCursesWidget* lbl = reinterpret_cast<cmCursesWidget*>(
803 field_userptr(this->Fields[findex - 2]));
804 const char* curField = lbl->GetValue();
805 const char* helpString = nullptr;
807 const char* existingValue =
808 this->CMakeInstance->GetState()->GetCacheEntryValue(curField);
810 helpString = this->CMakeInstance->GetState()->GetCacheEntryProperty(
811 curField, "HELPSTRING");
814 this->HelpMessage[1] =
815 cmStrCat("Current option is: ", curField, '\n',
816 "Help string for this option is: ", helpString, '\n');
818 this->HelpMessage[1] = "";
821 cmCursesLongMessageForm* msgs = new cmCursesLongMessageForm(
822 this->HelpMessage, "Help",
823 cmCursesLongMessageForm::ScrollBehavior::NoScroll);
825 msgs->Render(1, 1, x, y);
828 this->Render(1, 1, x, y);
829 set_current_field(this->Form, cur);
831 // display last errors
832 else if (key == 'l') {
833 getmaxyx(stdscr, y, x);
834 cmCursesLongMessageForm* msgs = new cmCursesLongMessageForm(
835 this->Outputs, "CMake produced the following output",
836 cmCursesLongMessageForm::ScrollBehavior::NoScroll);
838 msgs->Render(1, 1, x, y);
841 this->Render(1, 1, x, y);
842 } else if (key == '/') {
843 this->SearchMode = true;
844 this->UpdateStatusBar("Search");
848 } else if (key == 'n') {
849 if (!this->OldSearchString.empty()) {
850 this->JumpToCacheEntry(this->OldSearchString.c_str());
853 // switch advanced on/off
854 else if (key == 't') {
855 if (this->AdvancedMode) {
856 this->AdvancedMode = false;
858 this->AdvancedMode = true;
860 getmaxyx(stdscr, y, x);
862 this->Render(1, 1, x, y);
865 else if (key == 'g') {
866 if (this->OkToGenerate) {
871 // delete cache entry
872 else if (key == 'd' && this->NumberOfVisibleEntries) {
873 this->OkToGenerate = false;
874 FIELD* cur = current_field(this->Form);
875 size_t findex = field_index(cur);
877 // make the next or prev. current field after deletion
878 // each entry consists of fields: label, isnew, value
879 // therefore, the label field for the prev. entry is findex-5
880 // and the label field for the next entry is findex+1
881 // (findex always corresponds to the value field)
885 } else if (findex == 3 * this->NumberOfVisibleEntries - 1) {
886 nextCur = this->Fields[findex - 5];
888 nextCur = this->Fields[findex + 1];
891 // Get the label widget
892 // each entry consists of fields: label, isnew, value
893 // therefore, the label field for the is findex-2
894 // (findex always corresponds to the value field)
895 cmCursesWidget* lbl = reinterpret_cast<cmCursesWidget*>(
896 field_userptr(this->Fields[findex - 2]));
898 this->CMakeInstance->GetState()->RemoveCacheEntry(lbl->GetValue());
903 (reinterpret_cast<cmCursesWidget*>(field_userptr(nextCur))
907 getmaxyx(stdscr, y, x);
908 this->RemoveEntry(lbl->GetValue());
910 this->Render(1, 1, x, y);
913 // make the next or prev. current field after deletion
914 auto nextEntryIt = std::find_if(
915 this->Entries.begin(), this->Entries.end(),
916 [&nextVal](cmCursesCacheEntryComposite const& entry) {
917 return nextVal == entry.Key;
920 if (nextEntryIt != this->Entries.end()) {
921 set_current_field(this->Form, nextEntryIt->Entry->Field);
933 int cmCursesMainForm::LoadCache(const char* /*unused*/)
936 int r = this->CMakeInstance->LoadCache();
940 this->CMakeInstance->SetCacheArgs(this->Args);
941 this->CMakeInstance->PreLoadCMakeFiles();
945 void cmCursesMainForm::JumpToCacheEntry(const char* astr)
949 str = cmSystemTools::LowerCase(astr);
955 FIELD* cur = current_field(this->Form);
956 int start_index = field_index(cur);
957 int findex = start_index;
960 cmCursesWidget* lbl = nullptr;
962 lbl = reinterpret_cast<cmCursesWidget*>(
963 field_userptr(this->Fields[findex - 2]));
966 const char* curField = lbl->GetValue();
968 std::string cfld = cmSystemTools::LowerCase(curField);
969 if (cfld.find(str) != std::string::npos && findex != start_index) {
975 if (size_t(findex) >= 3 * this->NumberOfVisibleEntries - 1) {
976 set_current_field(this->Form, this->Fields[2]);
977 } else if (new_page(this->Fields[findex + 1])) {
978 form_driver(this->Form, REQ_NEXT_PAGE);
980 form_driver(this->Form, REQ_NEXT_FIELD);
982 cur = current_field(this->Form);
983 findex = field_index(cur);
984 if (findex == start_index) {
990 void cmCursesMainForm::ResetOutputs()
992 this->LogForm.reset();
993 this->Outputs.clear();
994 this->HasNonStatusOutputs = false;
995 this->LastProgress.clear();
998 void cmCursesMainForm::DisplayOutputs()
1002 getmaxyx(stdscr, yi, xi);
1004 auto newLogForm = new cmCursesLongMessageForm(
1005 this->Outputs, this->LastProgress.c_str(),
1006 cmCursesLongMessageForm::ScrollBehavior::ScrollDown);
1007 CurrentForm = newLogForm;
1008 this->LogForm.reset(newLogForm);
1009 this->LogForm->Render(1, 1, xi, yi);
1012 const char* cmCursesMainForm::s_ConstHelpMessage =
1013 "CMake is used to configure and generate build files for software projects. "
1014 "The basic steps for configuring a project with ccmake are as follows:\n\n"
1015 "1. Run ccmake in the directory where you want the object and executable "
1016 "files to be placed (build directory). If the source directory is not the "
1017 "same as this build directory, you have to specify it as an argument on the "
1019 "2. When ccmake is run, it will read the configuration files and display "
1020 "the current build options. "
1021 "If you have run CMake before and have updated the configuration files "
1022 "since then, any new entries will be displayed on top and will be marked "
1024 "On the other hand, the first time you run ccmake, all build options will "
1025 "be new and will be marked as such. "
1026 "At this point, you can modify any options (see keys below) you want to "
1028 "When you are satisfied with your changes, press 'c' to have CMake process "
1029 "the configuration files. "
1030 "Please note that changing some options may cause new ones to appear. These "
1031 "will be shown on top and will be marked with *. "
1032 "Repeat this procedure until you are satisfied with all the options and "
1033 "there are no new entries. "
1034 "At this point, a new command will appear: G)enerate and Exit. You can now "
1035 "hit 'g' to have CMake generate all the build files (i.e. makefiles or "
1036 "project files) and exit. "
1037 "At any point during the process, you can exit ccmake with 'q'. However, "
1038 "this will not generate/change any build files.\n\n"
1041 "You can use the arrow keys and page up, down to navigate the options. "
1042 "Alternatively, you can use the following keys: \n"
1043 " C-n or j : next option\n"
1044 " C-p or k : previous options\n"
1045 " C-d : down one page\n"
1046 " C-u : up one page\n\n"
1048 "To change an option press enter or return. If the current options is a "
1049 "boolean, this will toggle its value. "
1050 "Otherwise, ccmake will enter edit mode. Alternatively, you can toggle "
1051 "a bool variable by pressing space, and enter edit mode with i."
1052 "In this mode you can edit an option using arrow keys and backspace. "
1053 "Alternatively, you can use the following keys:\n"
1054 " C-b : back one character\n"
1055 " C-f : forward one character\n"
1056 " C-a : go to the beginning of the field\n"
1057 " C-e : go to the end of the field\n"
1058 " C-d : delete previous character\n"
1059 " C-k : kill the rest of the field\n"
1060 " Esc : Restore field (discard last changes)\n"
1061 " Enter : Leave edit mode\n"
1063 " q : quit ccmake without generating build files\n"
1064 " h : help, shows this screen\n"
1065 " c : process the configuration files with the current options\n"
1066 " g : generate build files and exit, only available when there are no "
1067 "new options and no errors have been detected during last configuration.\n"
1068 " l : shows cmake output\n"
1069 " d : delete an option\n"
1070 " t : toggles advanced mode. In normal mode, only the most important "
1071 "options are shown. In advanced mode, all options are shown. We recommend "
1072 "using normal mode unless you are an expert.\n"
1073 " / : search for a variable name.\n";