GCC Code Coverage Report


Directory: ./
File: lib/geogram_gfx/gui/command.cpp
Date: 2026-09-07 02:28:19
Exec Total Coverage
Lines: 0 277 0.0%
Functions: 0 24 0.0%
Branches: 0 335 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2000-2022 Inria
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * * Neither the name of the ALICE Project-Team nor the names of its
14 * contributors may be used to endorse or promote products derived from this
15 * software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * Contact: Bruno Levy
30 *
31 * https://www.inria.fr/fr/bruno-levy
32 *
33 * Inria,
34 * Domaine de Voluceau,
35 * 78150 Le Chesnay - Rocquencourt
36 * FRANCE
37 *
38 */
39
40 #include <geogram_gfx/gui/command.h>
41 #include <geogram_gfx/imgui_ext/imgui_ext.h>
42 #include <geogram_gfx/imgui_ext/icon_font.h>
43
44 namespace {
45 /**
46 * \brief Removes the underscores from a string and
47 * replaces them with spaces.
48 * \details Utility for the prototype parser used by
49 * Command.
50 * \param[in] s a const reference to the input string
51 * \return a copy of \p s with underscores replaced with
52 * spaces
53 */
54 std::string remove_underscores(const std::string& s) {
55 std::string result = s;
56 for(GEO::index_t i=0; i<result.size(); ++i) {
57 if(result[i] == '_') {
58 result[i] = ' ';
59 }
60 }
61 return result;
62 }
63
64
65
66 /**
67 * \brief Removes leading and trailing spaces from
68 * a string.
69 * \details Utility for the prototype parser used by
70 * Command.
71 * \param[in,out] line the line from which spaces should
72 * be trimmed.
73 */
74 void trim_spaces(std::string& line) {
75 size_t i=0;
76 for(i=0; i<line.length(); ++i) {
77 if(line[i] != ' ') {
78 break;
79 }
80 }
81 size_t j=line.length();
82 for(j=line.length(); j>0; --j) {
83 if(line[j-1] != ' ') {
84 break;
85 }
86 }
87 if(j>i) {
88 line = line.substr(i, j-i);
89 } else {
90 line = "";
91 }
92 }
93 }
94
95 namespace GEO {
96
97 /*****************************************************************/
98
99 CommandInvoker::CommandInvoker() {
100 }
101
102 CommandInvoker::~CommandInvoker() {
103 }
104
105 /*****************************************************************/
106
107 void Command::flush_queue() {
108 if(queued_ != nullptr) {
109 // Steal the queued command to avoid
110 // infinite recursion.
111 /*
112 SmartPointer<Command> queued = queued_;
113 queued_ = nullptr;
114 queued->apply();
115 *(queued->is_visible_ptr()) = false;
116 */
117
118 latest_ = queued_;
119 queued_ = nullptr;
120 latest_->apply();
121 *(latest_->is_visible_ptr()) = false;
122 }
123 }
124
125 void Command::replay_latest() {
126 if(!latest_.is_null()) {
127 latest_->apply();
128 }
129 }
130
131 Command::~Command() {
132 }
133
134 Command::Command(const std::string& prototype_in) : visible_(false) {
135
136 // If there is no brace, then protype only has function
137 // name, and then the invoker will construct the arguments
138 // with names arg1, arg2 ...
139 auto_create_args_ =
140 (prototype_in.find('(') == std::string::npos);
141
142 if(auto_create_args_) {
143 name_ = prototype_in;
144 if(name_ == "") {
145 name_ = "command";
146 }
147 help_ = "Hay You Programmer ! No prototype was specified, \n"
148 "see Command::make_current() documentation\n"
149 "in geogram_gfx/glup_viewer/command.h\n"
150 "to specify parameter names (and tooltips)";
151 return;
152 }
153
154 // Parsing the prototype...
155
156 std::string prototype = prototype_in;
157
158 // Transform carriage returns into spaces
159 {
160 for(index_t i=0; i<prototype.size(); ++i) {
161 if(prototype[i] == '\n') {
162 prototype[i] = ' ';
163 }
164 }
165 }
166
167 // Separate function name from argument list
168 size_t p1 = std::string::npos;
169 size_t p2 = std::string::npos;
170 {
171 int level = 0;
172 for(size_t i=0; i<prototype.length(); ++i) {
173 switch(prototype[i]) {
174 case '[':
175 ++level;
176 break;
177 case ']':
178 --level;
179 break;
180 case '(':
181 if(level == 0) {
182 p1 = i;
183 }
184 break;
185 case ')':
186 if(level == 0) {
187 p2 = i;
188 }
189 break;
190 }
191 }
192 }
193
194
195 geo_assert(p1 != std::string::npos && p2 != std::string::npos);
196 name_ = prototype.substr(0,p1);
197
198 // Trim spaces, and remove return type if it was specified.
199 {
200 std::vector<std::string> name_parts;
201 String::split_string(name_, ' ', name_parts);
202 name_ = name_parts[name_parts.size()-1];
203 }
204
205 // Find help if any
206 {
207 size_t bq1 = prototype.find('[',p2);
208 size_t bq2 = prototype.find(']',p2);
209 if(bq1 != std::string::npos && bq2 != std::string::npos) {
210 help_ = prototype.substr(bq1+1, bq2-bq1-1);
211 }
212 }
213
214 std::string args_string = prototype.substr(p1+1,p2-p1-1);
215 std::vector<std::string> args;
216 String::split_string(args_string, ',', args);
217
218 for(index_t i=0; i<args.size(); ++i) {
219 std::string arg = args[i];
220 std::string default_value;
221 std::string help;
222
223 // Find help if any
224 {
225 size_t bq1 = arg.find('[');
226 size_t bq2 = arg.find(']');
227 if(bq1 != std::string::npos && bq2 != std::string::npos) {
228 help = arg.substr(bq1+1, bq2-bq1-1);
229 arg = arg.substr(0, bq1);
230 }
231 }
232
233 // Find default value if any (to the right of the '=' sign)
234 {
235 size_t eq = arg.find('=');
236 if(eq != std::string::npos) {
237 default_value = arg.substr(eq+1, arg.length()-eq-1);
238 trim_spaces(default_value);
239 arg = arg.substr(0, eq);
240 }
241 }
242
243 // Analyze argument type and name
244 std::vector<std::string> arg_words;
245 String::split_string(arg, ' ', arg_words);
246
247 // Argument name is the last word
248 const std::string& arg_name = arg_words[arg_words.size()-1];
249 int type = -1;
250 if(arg_words.size() > 1) {
251 bool is_unsigned = false;
252 for(index_t w=0; w<arg_words.size()-1; ++w) {
253 if(arg_words[w] == "unsigned") {
254 is_unsigned = true;
255 } else if(arg_words[w] == "bool") {
256 type = Arg::ARG_BOOL;
257 } else if(arg_words[w] == "int") {
258 type = (is_unsigned) ? Arg::ARG_UINT : Arg::ARG_INT;
259 } else if(
260 arg_words[w] == "index_t" ||
261 arg_words[w] == "GEO::index_t"
262 ) {
263 type = Arg::ARG_UINT;
264 } else if(
265 arg_words[w] == "float" ||
266 arg_words[w] == "double"
267 ) {
268 type = Arg::ARG_FLOAT;
269 } else if(
270 arg_words[w] == "string" ||
271 arg_words[w] == "std::string" ||
272 arg_words[w] == "string&" ||
273 arg_words[w] == "std::string&"
274 ) {
275 type = Arg::ARG_STRING;
276 }
277 }
278 }
279
280 switch(type) {
281 case Arg::ARG_BOOL: {
282 bool val = false;
283 if(default_value != "") {
284 String::from_string(default_value, val);
285 }
286 add_arg(arg_name, val, help);
287 } break;
288 case Arg::ARG_INT: {
289 int val = 0;
290 if(default_value != "") {
291 String::from_string(default_value, val);
292 }
293 add_arg(arg_name, val, help);
294 } break;
295 case Arg::ARG_UINT: {
296 unsigned int val = 0;
297 if(default_value != "") {
298 String::from_string(default_value, val);
299 }
300 add_arg(arg_name, val, help);
301 } break;
302 case Arg::ARG_FLOAT: {
303 float val = 0.0f;
304 if(default_value != "") {
305 String::from_string(default_value, val);
306 }
307 add_arg(arg_name, val, help);
308 } break;
309 case Arg::ARG_STRING: {
310 if(default_value != "") {
311 // Remove quotes
312 default_value = default_value.substr(
313 1, default_value.length()-2
314 );
315 add_arg(arg_name, default_value, help);
316 }
317 } break;
318 default: {
319 geo_assert_not_reached;
320 }
321 }
322 }
323 name_ = remove_underscores(name_);
324 }
325
326 void Command::apply() {
327 if(invoker_ != nullptr) {
328 invoker_->invoke();
329 }
330 }
331
332 int Command::int_arg_by_index(index_t i) const {
333 const Arg& arg = find_arg_by_index(i);
334 geo_assert(
335 arg.type == Arg::ARG_INT ||
336 arg.type == Arg::ARG_UINT
337 );
338 int result = arg.val.int_val;
339 if(
340 arg.type == Arg::ARG_UINT &&
341 result < 0
342 ) {
343 Logger::warn("Cmd")
344 << "Argument " << arg.name
345 << "Of type UNIT had a negative value"
346 << std::endl;
347 result = 0;
348 }
349 return result;
350 }
351
352 unsigned int Command::uint_arg_by_index(index_t i) const {
353 const Arg& arg = find_arg_by_index(i);
354 geo_assert(
355 arg.type == Arg::ARG_INT ||
356 arg.type == Arg::ARG_UINT
357 );
358 int result = arg.val.int_val;
359 if(result < 0) {
360 Logger::warn("Cmd")
361 << "Argument " << arg.name
362 << "queried as uint had a negative value"
363 << std::endl;
364 result = 0;
365 }
366 return (unsigned int)(result);
367 }
368
369 void Command::draw() {
370 ImGui::Text("%s",name().c_str());
371 if(ImGui::SimpleButton(icon_UTF8("window-close").c_str())) {
372 visible_ = false;
373 }
374 ImGui::Tooltip("close command");
375 ImGui::SameLine();
376 if(ImGui::SimpleButton(
377 icon_UTF8("cog").c_str()
378 )) {
379 reset_factory_settings();
380 }
381 ImGui::Tooltip("reset factory settings");
382 ImGui::SameLine();
383 if(ImGui::Button(icon_UTF8("check").c_str(), ImVec2(-1.0, 0.0))) {
384 queued_ = this;
385 }
386 if(help_ == "") {
387 ImGui::Tooltip("apply command");
388 } else {
389 ImGui::Tooltip(help_);
390 }
391 ImGui::Separator();
392 for(index_t i=0; i<args_.size(); ++i) {
393 args_[i].draw();
394 }
395 ImGui::Separator();
396 }
397
398 void Command::reset_factory_settings() {
399 for(index_t i=0; i<args_.size(); ++i) {
400 args_[i].val = args_[i].default_val;
401 }
402 }
403
404 void Command::ArgVal::clear() {
405 bool_val = false;
406 int_val = 0;
407 float_val = 0.0f;
408 string_val[0] = '\0';
409 }
410
411 Command::ArgVal::ArgVal(const Command::ArgVal& rhs) {
412 bool_val = rhs.bool_val;
413 int_val = rhs.int_val;
414 float_val = rhs.float_val;
415 Memory::copy(string_val, rhs.string_val, 64);
416 }
417
418 Command::ArgVal& Command::ArgVal::operator=(const ArgVal& rhs) {
419 if(&rhs != this) {
420 bool_val = rhs.bool_val;
421 int_val = rhs.int_val;
422 float_val = rhs.float_val;
423 Memory::copy(string_val, rhs.string_val, 64);
424 }
425 return *this;
426 }
427
428 Command::Arg::Arg() {
429 name = "unnamed";
430 type = ARG_BOOL;
431 val.clear();
432 default_val.clear();
433 val.bool_val = false;
434 default_val.bool_val = false;
435 }
436
437 Command::Arg::Arg(
438 const std::string& name_in, bool x,
439 const std::string& help_in
440 ) {
441 name = name_in;
442 help = help_in;
443 type = ARG_BOOL;
444 val.clear();
445 default_val.clear();
446 val.bool_val = x;
447 default_val.bool_val = x;
448 }
449
450 Command::Arg::Arg(
451 const std::string& name_in, int x,
452 const std::string& help_in
453 ) {
454 name = name_in;
455 help = help_in;
456 type = ARG_INT;
457 val.clear();
458 default_val.clear();
459 val.int_val = x;
460 default_val.int_val = x;
461 }
462
463 Command::Arg::Arg(
464 const std::string& name_in, unsigned int x,
465 const std::string& help_in
466 ) {
467 name = name_in;
468 help = help_in;
469 type = ARG_UINT;
470 val.clear();
471 default_val.clear();
472 val.int_val = int(x);
473 default_val.int_val = int(x);
474 }
475
476 Command::Arg::Arg(
477 const std::string& name_in, float x,
478 const std::string& help_in
479 ) {
480 name = name_in;
481 help = help_in;
482 type = ARG_FLOAT;
483 val.clear();
484 default_val.clear();
485 val.float_val = x;
486 default_val.float_val = x;
487 }
488
489 Command::Arg::Arg(
490 const std::string& name_in, double x,
491 const std::string& help_in
492 ) {
493 name = name_in;
494 help = help_in;
495 type = ARG_FLOAT;
496 val.clear();
497 default_val.clear();
498 val.float_val = float(x);
499 default_val.float_val = float(x);
500 }
501
502 Command::Arg::Arg(
503 const std::string& name_in, const std::string& x,
504 const std::string& help_in
505 ) {
506 name = name_in;
507 help = help_in;
508 type = ARG_STRING;
509 val.clear();
510 default_val.clear();
511 geo_assert(x.length() < 63);
512 Memory::copy(default_val.string_val,x.c_str(), x.length());
513 default_val.string_val[x.length()] = '\0';
514 Memory::copy(val.string_val,x.c_str(), x.length());
515 val.string_val[x.length()] = '\0';
516 }
517
518 void Command::Arg::draw() {
519 // Some widgets with labels are too wide,
520 // therefore their label is displayed with
521 // a separate ImGui::Text().
522 // Each ImGUI widget requires a unique Id,
523 // that is normally generated from the label.
524 // If label starts with "##", then ImGui
525 // makes it invisible (and uses what's after
526 // "##" to generate the Id).
527 switch(type) {
528 case ARG_BOOL:
529 ImGui::SetNextItemWidth(-1.0f);
530 ImGui::Checkbox(remove_underscores(name).c_str(), &val.bool_val);
531 ImGui::Tooltip(help);
532 break;
533 case ARG_INT:
534 ImGui::Text("%s",remove_underscores(name).c_str());
535 ImGui::SetNextItemWidth(-1.0f);
536 ImGui::Tooltip(help);
537 ImGui::InputInt(("##" + name).c_str(), &val.int_val);
538 break;
539 case ARG_UINT:
540 ImGui::Text("%s",remove_underscores(name).c_str());
541 ImGui::SetNextItemWidth(-1.0f);
542 ImGui::Tooltip(help);
543 ImGui::InputInt(("##" + name).c_str(), &val.int_val);
544 break;
545 case ARG_FLOAT:
546 ImGui::Text("%s",remove_underscores(name).c_str());
547 ImGui::SetNextItemWidth(-1.0f);
548 ImGui::Tooltip(help);
549 ImGui::InputFloat(("##" + name).c_str(), &val.float_val);
550 break;
551 case ARG_STRING:
552 ImGui::Text("%s",remove_underscores(name).c_str());
553 ImGui::SetNextItemWidth(-1.0f);
554 ImGui::Tooltip(help);
555 ImGui::InputText(("##" + name).c_str(), val.string_val, 64);
556 break;
557 }
558 }
559
560 SmartPointer<Command> Command::current_;
561 SmartPointer<Command> Command::queued_;
562 SmartPointer<Command> Command::latest_;
563 }
564