panel.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. #include "door.h"
  2. #include <set>
  3. #include <string.h>
  4. // #include <memory>
  5. namespace door {
  6. Panel::Panel(int xp, int yp, int panelWidth) : border_color() {
  7. x = xp;
  8. y = yp;
  9. width = panelWidth;
  10. hidden = false;
  11. border_style = BorderStyle::NONE;
  12. // border_color = ANSIColor();
  13. }
  14. Panel::Panel(Panel &&ref) {
  15. x = ref.x;
  16. y = ref.y;
  17. width = ref.width;
  18. hidden = ref.hidden;
  19. border_style = ref.border_style;
  20. title = std::move(ref.title);
  21. offset = ref.offset;
  22. lines = std::move(lines);
  23. }
  24. /*
  25. Panel::Panel(const Panel &original) : border_color(original.border_color) {
  26. x = original.x;
  27. y = original.y;
  28. width = original.width;
  29. hidden = original.hidden;
  30. border_style = original.border_style;
  31. // door::Line title_copy(*original.title);
  32. // title = std::move(std::make_unique<door::Line>(title_copy));
  33. // What's wrong with making copies of unique_ptr objects?
  34. title = std::move(std::make_unique<door::Line>(*original.title));
  35. offset = original.offset;
  36. for (auto &line : original.lines) {
  37. // door::Line l(*line);
  38. // lines.push_back(std::move(std::make_unique<door::Line>(l)));
  39. // door::Line l(*line);
  40. lines.push_back(std::move(std::make_unique<door::Line>(*line)));
  41. }
  42. }
  43. */
  44. void Panel::set(int xp, int yp) {
  45. x = xp;
  46. y = yp;
  47. }
  48. void Panel::setTitle(std::unique_ptr<Line> t, int off) {
  49. title = std::move(t);
  50. offset = off;
  51. }
  52. void Panel::setStyle(BorderStyle bs) { border_style = bs; }
  53. // Panel::Panel(Panel &old) = { }
  54. void Panel::setColor(ANSIColor c) { border_color = c; }
  55. void Panel::hide(void) { hidden = true; }
  56. void Panel::show(void) { hidden = false; }
  57. void Panel::addLine(std::unique_ptr<Line> l) { lines.push_back(std::move(l)); }
  58. // or possibly std::move(l)); }
  59. /*
  60. bool Panel::delLine(std::shared_ptr<Line> l) {
  61. size_t size = lines.size();
  62. remove(lines.begin(), lines.end(), l);
  63. return (size != lines.size());
  64. }
  65. */
  66. /**
  67. * Utility structure that stores the characters needed to
  68. * render boxes.
  69. *
  70. * We assume that Top can be used in Bottom and Middle.
  71. * We assume that Side can be used on Left and Right.
  72. */
  73. struct box_styles {
  74. /// Top Left
  75. const char *tl;
  76. /// Top Right
  77. const char *tr;
  78. /// Top
  79. const char *top;
  80. /// Side
  81. const char *side;
  82. /// Bottom Left
  83. const char *bl;
  84. /// Bottom Right
  85. const char *br;
  86. /// Middle Left
  87. const char *ml;
  88. /// Middle Right
  89. const char *mr;
  90. };
  91. /**
  92. *
  93. * use https://en.wikipedia.org/wiki/Code_page_437 for translations between
  94. * CP437 and unicode symbols.
  95. *
  96. * This holds the characters needed to render the different box styles.
  97. * tl tr top side bl br ml mr
  98. */
  99. struct box_styles UBOXES[] = {{"\u250c", "\u2510", "\u2500", "\u2502", "\u2514",
  100. "\u2518", "\u251c", "\u2524"},
  101. {"\u2554", "\u2557", "\u2550", "\u2551", "\u255a",
  102. "\u255d", "\u2560", "\u2563"},
  103. {"\u2553", "\u2556", "\u2500", "\u2551", "\u2559",
  104. "\u255c", "\u255f", "\u2562"},
  105. {"\u2552", "\u2555", "\u2550", "\u2502", "\u2558",
  106. "\u255b", "\u255e", "\u2561"}};
  107. struct box_styles BOXES[] = {
  108. /*
  109. # ┌──┐
  110. # │ │
  111. # ├──┤
  112. # └──┘
  113. */
  114. {
  115. "\xda",
  116. "\xbf",
  117. "\xc4",
  118. "\xb3",
  119. "\xc0",
  120. "\xd9",
  121. "\xc3",
  122. "\xb4",
  123. },
  124. /*
  125. # ╔══╗
  126. # ║ ║
  127. # ╠══╣
  128. # ╚══╝
  129. */
  130. {
  131. "\xc9",
  132. "\xbb",
  133. "\xcd",
  134. "\xba",
  135. "\xc8",
  136. "\xbc",
  137. "\xcc",
  138. "\xb9",
  139. },
  140. /*
  141. # ╓──╖
  142. # ║ ║
  143. # ╟──╢
  144. # ╙──╜
  145. */
  146. {
  147. "\xd6",
  148. "\xb7",
  149. "\xc4",
  150. "\xba",
  151. "\xd3",
  152. "\xbd",
  153. "\xc7",
  154. "\xb6",
  155. },
  156. /*
  157. # ╒══╕
  158. # │ │
  159. # ╞══╡
  160. # ╘══╛
  161. */
  162. {
  163. "\xd5",
  164. "\xb8",
  165. "\xcd",
  166. "\xb3",
  167. "\xd4",
  168. "\xbe",
  169. "\xc6",
  170. "\xb5",
  171. },
  172. };
  173. /*
  174. void Panel::display(void) {
  175. }
  176. void Panel::update(void) {
  177. }
  178. */
  179. bool Panel::update(Door &d) {
  180. int row = y;
  181. int style = (int)border_style;
  182. if (style > 0)
  183. ++row;
  184. bool updated = false;
  185. for (auto &line : lines) {
  186. if (line->update()) {
  187. /*
  188. std::string output = d.previous.debug();
  189. d.log(output);
  190. output = "update():";
  191. output.append(line->debug());
  192. d.log(output);
  193. */
  194. updated = true;
  195. int col = x;
  196. if (style > 0)
  197. ++col;
  198. d << door::Goto(col, row);
  199. d << *line;
  200. }
  201. ++row;
  202. }
  203. return updated;
  204. }
  205. void Panel::update(Door &d, int line) {
  206. int row = y;
  207. int style = (int)border_style;
  208. if (style > 0)
  209. ++row;
  210. // ok, I have the line number to update.
  211. auto &l = lines[line];
  212. row += line;
  213. int col = x;
  214. if (style > 0)
  215. ++col;
  216. d << door::Goto(col, row);
  217. d << *l;
  218. }
  219. door::Goto Panel::gotoEnd(void) {
  220. int row = y;
  221. int style = (int)border_style;
  222. if (style > 0)
  223. ++row;
  224. row += lines.size();
  225. int col = x;
  226. if (style > 0)
  227. col += 2;
  228. col += width;
  229. return door::Goto(col, row);
  230. }
  231. // operator<< Panel is called to output the Menu.
  232. // Menu has been massively changed to use Render instead of Colorizer.
  233. std::ostream &operator<<(std::ostream &os, const Panel &p) {
  234. if (p.hidden)
  235. return os;
  236. // Handle borders
  237. int style = (int)p.border_style;
  238. struct box_styles s;
  239. // If there's no style, then everything works.
  240. // If I try style, it prints out first line
  241. // and dies. (Yet it says there's 4 lines!)
  242. if (style > 0) {
  243. // Ok, there needs to be something in this style;
  244. if (style < 5) {
  245. if (unicode)
  246. s = UBOXES[style - 1];
  247. else
  248. s = BOXES[style - 1];
  249. } else {
  250. s.bl = s.br = s.mr = s.ml = " ";
  251. s.top = s.side = " ";
  252. s.tl = s.tr = " ";
  253. }
  254. }
  255. /*
  256. Door *d = dynamic_cast<Door *>(&os);
  257. if (d != nullptr) {
  258. */
  259. /*
  260. os << "style " << style << "."
  261. << " width " << p.width;
  262. os << " SIZE " << p.lines.size() << " ! " << nl;
  263. */
  264. // os << s.tl << s.top << s.tr << s.side << s.br << s.bl;
  265. int row = p.y;
  266. if (style > 0) {
  267. // Top line of border (if needed)
  268. os << door::Goto(p.x, row);
  269. os << p.border_color << s.tl;
  270. if (p.title) {
  271. for (int c = 0; c < p.offset; c++)
  272. os << s.top;
  273. os << *(p.title);
  274. os << p.border_color;
  275. int left = p.width - (p.offset + (p.title)->length());
  276. if (left > 0) {
  277. for (int c = 0; c < left; c++)
  278. os << s.top;
  279. };
  280. os << s.tr;
  281. } else {
  282. for (int c = 0; c < p.width; c++)
  283. os << s.top;
  284. os << s.tr;
  285. };
  286. // os << "";
  287. ++row;
  288. };
  289. for (auto &line : p.lines) {
  290. os << door::Goto(p.x, row);
  291. if (style > 0) {
  292. os << p.border_color << s.side;
  293. };
  294. // os << "[" << row << "," << p.x << "] ";
  295. os << *line;
  296. if (style > 0) {
  297. os << p.border_color << s.side;
  298. };
  299. // os << "row " << row;
  300. row++;
  301. // forcing reset works. But why doesn't it work without this?
  302. // os << Color();
  303. }
  304. // Display bottom (if needed)
  305. if (style > 0) {
  306. os << door::Goto(p.x, row);
  307. os << p.border_color << s.bl;
  308. for (int c = 0; c < p.width; c++)
  309. os << s.top;
  310. // os << "";
  311. os << s.br;
  312. };
  313. // };
  314. // os << flush;
  315. return os;
  316. } // namespace door
  317. /*
  318. std::function<void(Door &d, std::string &)> Menu::defaultSelectedColorizer =
  319. Menu::makeColorizer(ANSIColor(COLOR::BLUE, COLOR::WHITE),
  320. ANSIColor(COLOR::BLUE, COLOR::WHITE),
  321. ANSIColor(COLOR::BLUE, COLOR::WHITE),
  322. ANSIColor(COLOR::BLUE, COLOR::WHITE));
  323. std::function<void(Door &d, std::string &)> Menu::defaultUnselectedColorizer =
  324. makeColorizer(ANSIColor(COLOR::WHITE, COLOR::BLUE, ATTR::BOLD),
  325. ANSIColor(COLOR::WHITE, COLOR::BLUE, ATTR::BOLD),
  326. ANSIColor(COLOR::WHITE, COLOR::BLUE, ATTR::BOLD),
  327. ANSIColor(COLOR::YELLOW, COLOR::BLUE, ATTR::BOLD));
  328. */
  329. renderFunction Menu::defaultSelectedRender = Menu::makeRender(
  330. ANSIColor(COLOR::BLUE, COLOR::WHITE), ANSIColor(COLOR::BLUE, COLOR::WHITE),
  331. ANSIColor(COLOR::BLUE, COLOR::WHITE), ANSIColor(COLOR::BLUE, COLOR::WHITE));
  332. renderFunction Menu::defaultUnselectedRender =
  333. Menu::makeRender(ANSIColor(COLOR::WHITE, COLOR::BLUE, ATTR::BOLD),
  334. ANSIColor(COLOR::WHITE, COLOR::BLUE, ATTR::BOLD),
  335. ANSIColor(COLOR::WHITE, COLOR::BLUE, ATTR::BOLD),
  336. ANSIColor(COLOR::YELLOW, COLOR::BLUE, ATTR::BOLD));
  337. Menu::Menu(int x, int y, int width) : Panel(x, y, width) {
  338. setStyle(BorderStyle::DOUBLE);
  339. // Setup initial sensible default values.
  340. // setColorizer(true, defaultSelectedColorizer);
  341. setRender(true, defaultSelectedRender);
  342. /* makeColorizer(Color(Colors::BLUE, Colors::WHITE, 0),
  343. Color(Colors::BLUE, Colors::WHITE, 0),
  344. Color(Colors::BLUE, Colors::WHITE, 0),
  345. Color(Colors::BLUE, Colors::WHITE, 0))); */
  346. setRender(false, defaultUnselectedRender);
  347. // setColorizer(false, defaultUnselectedColorizer);
  348. /* makeColorizer(Color(Colors::LWHITE, Colors::BLUE, 0),
  349. Color(Colors::LWHITE, Colors::BLUE),
  350. Color(Colors::LWHITE, Colors::BLUE, 0),
  351. Color(Colors::LYELLOW, Colors::BLUE))); */
  352. chosen = 0;
  353. }
  354. /*
  355. Menu::Menu(const Menu &original)
  356. : Panel(original.x, original.y, original.width) {
  357. x = original.x;
  358. y = original.y;
  359. width = original.width;
  360. setStyle(original.border_style);
  361. setRender(true, original.selectedRender);
  362. setRender(false, original.unselectedRender);
  363. options = original.options;
  364. chosen = 0;
  365. }
  366. */
  367. Menu::Menu(Menu &&ref) : Panel(ref.x, ref.y, ref.width) {
  368. x = ref.x;
  369. y = ref.y;
  370. width = ref.width;
  371. border_style = ref.border_style;
  372. setRender(true, ref.selectedRender);
  373. setRender(false, ref.unselectedRender);
  374. options = ref.options;
  375. lines = std::move(ref.lines);
  376. chosen = ref.chosen;
  377. }
  378. void Menu::addSelection(char c, const char *line) {
  379. std::string menuline;
  380. menuline.reserve(5 + strlen(line));
  381. menuline = "[ ] ";
  382. menuline[1] = c;
  383. menuline += line;
  384. // problem: How do I update the "Lines" from this point?
  385. // L->makeWidth(width);
  386. addLine(std::make_unique<Line>(menuline, width));
  387. options.push_back(c);
  388. }
  389. void Menu::defaultSelection(int d) { chosen = d; }
  390. char Menu::which(int d) { return options[d]; }
  391. /*
  392. void Menu::setColorizer(bool selected,
  393. std::function<void(Door &d, std::string &)> colorizer)
  394. { if (selected) selectedColorizer = colorizer; else unselectedColorizer =
  395. colorizer;
  396. }
  397. */
  398. void Menu::setRender(bool selected, renderFunction render) {
  399. if (selected)
  400. selectedRender = render;
  401. else
  402. unselectedRender = render;
  403. }
  404. renderFunction Menu::makeRender(ANSIColor c1, ANSIColor c2, ANSIColor c3,
  405. ANSIColor c4) {
  406. renderFunction render = [c1, c2, c3, c4](const std::string &txt) -> Render {
  407. Render r(txt);
  408. bool option = true;
  409. ColorOutput co;
  410. /*
  411. bool uc = true;
  412. ANSIColor blue(COLOR::BLUE, ATTR::BOLD);
  413. ANSIColor cyan(COLOR::YELLOW, ATTR::BOLD);
  414. */
  415. co.pos = 0;
  416. co.len = 0;
  417. co.c = c1;
  418. // d << blue;
  419. int tpos = 0;
  420. for (char const &c : txt) {
  421. if (option) {
  422. if (c == '[' or c == ']') {
  423. if (co.c != c1)
  424. if (co.len != 0) {
  425. r.outputs.push_back(co);
  426. co.reset();
  427. co.pos = tpos;
  428. }
  429. co.c = c1;
  430. if (c == ']')
  431. option = false;
  432. } else {
  433. if (co.c != c2)
  434. if (co.len != 0) {
  435. r.outputs.push_back(co);
  436. co.reset();
  437. co.pos = tpos;
  438. }
  439. co.c = c2;
  440. }
  441. } else {
  442. if (isupper(c)) {
  443. // possible color change
  444. if (co.c != c3)
  445. if (co.len != 0) {
  446. r.outputs.push_back(co);
  447. co.reset();
  448. co.pos = tpos;
  449. }
  450. co.c = c3;
  451. } else {
  452. if (co.c != c4)
  453. if (co.len != 0) {
  454. r.outputs.push_back(co);
  455. co.reset();
  456. co.pos = tpos;
  457. }
  458. co.c = c4;
  459. }
  460. }
  461. co.len++;
  462. tpos++;
  463. }
  464. if (co.len != 0) {
  465. r.outputs.push_back(co);
  466. }
  467. return r;
  468. };
  469. return render;
  470. }
  471. /*
  472. std::function<void(Door &d, std::string &)>
  473. Menu::makeColorizer(ANSIColor c1, ANSIColor c2, ANSIColor c3, ANSIColor c4) {
  474. std::function<void(Door & d, std::string & txt)> colorize =
  475. [c1, c2, c3, c4](Door &d, std::string txt) {
  476. bool option = true;
  477. for (char const &c : txt) {
  478. if (option) {
  479. if (c == '[' or c == ']') {
  480. d << c1 << c;
  481. if (c == ']')
  482. option = false;
  483. } else {
  484. d << c2 << c;
  485. }
  486. } else {
  487. if (isupper(c)) {
  488. d << c3 << c;
  489. } else {
  490. d << c4 << c;
  491. }
  492. }
  493. }
  494. };
  495. return colorize;
  496. }
  497. */
  498. /*
  499. Should this return the index number, or
  500. the actual option?
  501. */
  502. /**
  503. * @todo Fix this, so it only updates the lines that have been changed when
  504. * the user selects something. Also, add the "Up/Down Move" maybe to the
  505. * bottom?
  506. *
  507. * Needs timeout.
  508. *
  509. * Should we return the index offset, or return the actual char? (Like in the
  510. * case of Quit or Help?)
  511. *
  512. * @param door
  513. * @return int
  514. */
  515. int Menu::choose(Door &door) {
  516. // Display menu and make a choice
  517. // step 1: fix up the lines
  518. bool updated = true;
  519. bool update_and_exit = false;
  520. std::set<int> changed;
  521. while (true) {
  522. if (updated) {
  523. for (unsigned int x = 0; x < lines.size(); ++x) {
  524. if (x == chosen) {
  525. lines[x]->setRender(
  526. selectedRender); // setColorize(selectedColorizer);
  527. } else {
  528. lines[x]->setRender(
  529. unselectedRender); // setColorize(unselectedColorizer);
  530. }
  531. }
  532. // this outputs the entire menu
  533. if (changed.empty())
  534. door << *this;
  535. else {
  536. // update just the lines that have changed.
  537. for (auto si : changed) {
  538. // *this->update(door, si);
  539. update(door, si);
  540. }
  541. // Cursor is positioned at the end of the panel/menu.
  542. // The cursor changes colors as you arrow up or down.
  543. // Interesting!
  544. door << gotoEnd();
  545. }
  546. // door << flush;
  547. // door.update();
  548. };
  549. if (update_and_exit)
  550. return chosen + 1;
  551. // Maybe we want to position the cursor on the currently
  552. // selected item?
  553. updated = false;
  554. // Ok, when the option changes, can I control what gets updated??!?
  555. int event = door.sleep_key(door.inactivity);
  556. if (event < 0) {
  557. // timeout!
  558. return event;
  559. }
  560. unsigned int previous_choice = chosen;
  561. changed.clear();
  562. // od_get_input(&event, OD_NO_TIMEOUT, GETIN_NORMAL);
  563. if (event > XKEY_START) {
  564. switch (event) {
  565. case XKEY_UP_ARROW:
  566. if (chosen > 0) {
  567. chosen--;
  568. updated = true;
  569. }
  570. break;
  571. case XKEY_DOWN_ARROW:
  572. if (chosen < lines.size() - 1) {
  573. chosen++;
  574. updated = true;
  575. }
  576. break;
  577. case XKEY_HOME:
  578. if (chosen != 0) {
  579. chosen = 0;
  580. updated = true;
  581. }
  582. break;
  583. case XKEY_END:
  584. if (chosen != lines.size() - 1) {
  585. chosen = lines.size() - 1;
  586. updated = true;
  587. }
  588. }
  589. } else if (event == 0x0d) {
  590. // ENTER -- use current selection
  591. return chosen + 1;
  592. }
  593. for (unsigned int x = 0; x < lines.size(); x++) {
  594. if (toupper(options[x]) == toupper(event)) {
  595. // is the selected one current chosen?
  596. if (chosen == x) {
  597. return x + 1;
  598. }
  599. // No, it isn't!
  600. // Update the screen, and then exit
  601. updated = true;
  602. chosen = x;
  603. update_and_exit = true;
  604. }
  605. }
  606. if (previous_choice != chosen) {
  607. changed.insert(previous_choice);
  608. changed.insert(chosen);
  609. }
  610. }
  611. return 0;
  612. }
  613. Screen::Screen() { hidden = false; }
  614. /*
  615. Screen::Screen(Screen &s) {
  616. hidden = s.hidden;
  617. parts = s.parts;
  618. }
  619. */
  620. void Screen::addPanel(std::shared_ptr<Panel> p) { parts.push_back(p); }
  621. void Screen::hide(void) { hidden = true; }
  622. void Screen::show(void) { hidden = false; }
  623. std::ostream &operator<<(std::ostream &os, const Screen &s) {
  624. if (!s.hidden) {
  625. for (auto part : s.parts) {
  626. os << part;
  627. };
  628. // os << flush;
  629. }
  630. return os;
  631. }
  632. } // namespace door