curseOutput.cpp 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. #include<iostream>
  2. #include <sys/ioctl.h>
  3. #include <unistd.h>
  4. #include <signal.h>
  5. #include <string.h>
  6. #include <utility>
  7. #include "curseOutput.hh"
  8. #include "jsonObject.hh"
  9. #include "jsonArray.hh"
  10. #include "jsonPrimitive.hh"
  11. #include "optional.hpp"
  12. static CurseOutput *runningInst = nullptr;
  13. CurseOutput::CurseOutput(JSonElement *root): data(root), selection(root), indentLevel(4)
  14. { }
  15. CurseOutput::~CurseOutput()
  16. { }
  17. void CurseOutput::run()
  18. {
  19. runningInst = this;
  20. init();
  21. loop();
  22. shutdown();
  23. runningInst = nullptr;
  24. }
  25. void CurseOutput::loop()
  26. {
  27. breakLoop = false;
  28. do
  29. {
  30. redraw();
  31. } while(readInput());
  32. }
  33. bool CurseOutput::onsig(int signo)
  34. {
  35. struct winsize size;
  36. switch (signo)
  37. {
  38. case SIGWINCH:
  39. if (ioctl(fileno(screen_fd ? screen_fd : stdout), TIOCGWINSZ, &size) == 0)
  40. resize_term(size.ws_row, size.ws_col);
  41. clear();
  42. redraw();
  43. break;
  44. case SIGKILL:
  45. case SIGINT:
  46. case SIGTERM:
  47. breakLoop = true;
  48. break;
  49. default:
  50. return false;
  51. }
  52. return true;
  53. }
  54. static void _resizeFnc(int signo)
  55. {
  56. if (!runningInst)
  57. return;
  58. runningInst->onsig(signo);
  59. }
  60. void CurseOutput::redraw()
  61. {
  62. std::pair<int, int> screenSize;
  63. std::pair<int, int> cursor;
  64. select_up = select_down = nullptr;
  65. selectFound = false;
  66. getScreenSize(screenSize, cursor);
  67. cursor.first += topleft.second->getLevel() * indentLevel;
  68. redraw(cursor, screenSize, topleft.second);
  69. move(screenSize.second, screenSize.first);
  70. if (!select_down)
  71. select_down = selection;
  72. if (!select_up)
  73. select_up = selection;
  74. refresh();
  75. }
  76. void CurseOutput::getScreenSize(std::pair<int, int> &ss, std::pair<int, int> &bs)
  77. {
  78. getmaxyx(stdscr, ss.second, ss.first);
  79. getbegyx(stdscr, bs.second, bs.first);
  80. }
  81. CurseOutput::t_nextKey CurseOutput::findNext(const JSonElement *item)
  82. {
  83. const JSonContainer *parent = item->getParent();
  84. if (parent == nullptr)
  85. return t_nextKey::empty(); // Root node, can't have brothers
  86. if (dynamic_cast<const JSonObject *>(parent) != nullptr)
  87. {
  88. const JSonObject *oParent = (const JSonObject *) parent;
  89. JSonObject::const_iterator it = oParent->cbegin();
  90. while (it != oParent->cend())
  91. {
  92. if ((*it).second == item)
  93. {
  94. it++;
  95. if (it == oParent->cend())
  96. return t_nextKey::empty(); // Last item
  97. return t_nextKey(std::pair<Optional<const std::string>, const JSonElement *>((*it).first, (*it).second));
  98. }
  99. it++;
  100. }
  101. return t_nextKey::empty();
  102. }
  103. if (dynamic_cast<const JSonArray *>(parent) != nullptr)
  104. {
  105. const JSonArray *aParent = (const JSonArray *) parent;
  106. JSonArray::const_iterator it = aParent->cbegin();
  107. while (it != aParent->cend())
  108. {
  109. if (*it == item)
  110. {
  111. it++;
  112. if (it == aParent->cend())
  113. return t_nextKey::empty(); // Last item
  114. return t_nextKey(std::pair<Optional<const std::string>, const JSonElement *>(Optional<const std::string>::empty(), *it));
  115. }
  116. it++;
  117. }
  118. return t_nextKey::empty();
  119. }
  120. return t_nextKey::empty(); // Primitive, can't have child (impossible)
  121. }
  122. bool CurseOutput::redraw(std::pair<int, int> &cursor, const std::pair<int, int> &maxSize, const JSonElement *item)
  123. {
  124. do
  125. {
  126. if (dynamic_cast<const JSonObject*>(item) != nullptr)
  127. {
  128. cursor.first += indentLevel /2;
  129. if (selection == item)
  130. selectFound = true;
  131. else if (!selectFound)
  132. select_up = item;
  133. else if (!select_down)
  134. select_down = item;
  135. write(cursor.first, cursor.second, "{", selection == item);
  136. if (++cursor.second > maxSize.second)
  137. return false;
  138. for (JSonObject::const_iterator i = ((JSonObject *)item)->cbegin(); i != ((JSonObject *)item)->cend(); ++i)
  139. {
  140. const std::pair<std::string, JSonElement *> ipair = *i;
  141. cursor.first += indentLevel /2;
  142. writeKey(ipair.first, cursor, selection == ipair.second);
  143. // TODO write primitive<> values inline
  144. cursor.first -= indentLevel /2;
  145. if (!redraw(cursor, maxSize, ipair.second))
  146. return false;
  147. cursor.first -= indentLevel;
  148. }
  149. write(cursor.first, cursor.second, "}", selection == item);
  150. cursor.first -= indentLevel /2;
  151. cursor.second++;
  152. if (++cursor.second > maxSize.second)
  153. return false;
  154. }
  155. else if (dynamic_cast<const JSonArray*>(item) != nullptr)
  156. {
  157. cursor.first += indentLevel /2;
  158. if (selection == item)
  159. selectFound = true;
  160. else if (!selectFound)
  161. select_up = item;
  162. else if (!select_down)
  163. select_down = item;
  164. write(cursor.first, cursor.second, "[", selection == item);
  165. cursor.first += indentLevel /2;
  166. if (++cursor.second > maxSize.second)
  167. return false;
  168. for (JSonArray::const_iterator i = ((JSonArray *)item)->cbegin(); i != ((JSonArray *)item)->cend(); ++i)
  169. if (!redraw(cursor, maxSize, *i))
  170. return false;
  171. cursor.first -= indentLevel /2;
  172. write(cursor.first, cursor.second, "]", selection == item);
  173. cursor.first -= indentLevel /2;
  174. if (++cursor.second > maxSize.second)
  175. return false;
  176. }
  177. else
  178. {
  179. if (item == selection)
  180. selectFound = true;
  181. else if (!selectFound)
  182. select_up = item;
  183. else if (!select_down)
  184. select_down = item;
  185. write(cursor.first, cursor.second, item, selection == item);
  186. if (++cursor.second > maxSize.second)
  187. return false;
  188. }
  189. t_nextKey next = findNext(item);
  190. if (next.isAbsent())
  191. break;
  192. item = next.value().second;
  193. if (next.value().first.isPresent())
  194. writeKey(next.value().first.value(), cursor, selection == item);
  195. } while (true);
  196. return true;
  197. }
  198. void CurseOutput::writeKey(const std::string &key, std::pair<int, int> &cursor, bool selected)
  199. {
  200. write(cursor.first, cursor.second, key +": ", selected);
  201. cursor.first += indentLevel;
  202. cursor.second++;
  203. }
  204. void CurseOutput::write(const int &x, const int &y, const JSonElement *item, bool selected)
  205. {
  206. write(x, y, item->stringify(), selected);
  207. }
  208. void CurseOutput::write(const int &x, const int &y, const std::string &str, bool selected)
  209. {
  210. if (selected)
  211. {
  212. attron(A_REVERSE | A_BOLD);
  213. mvprintw(y, x, str.c_str());
  214. attroff(A_REVERSE | A_BOLD);
  215. }
  216. else
  217. mvprintw(y, x, str.c_str());
  218. }
  219. /**
  220. * Read input and expect signal
  221. * @Return true on:
  222. * - Windows resized
  223. * - Key press and need redraw
  224. * false on:
  225. * - exit signal
  226. **/
  227. bool CurseOutput::readInput()
  228. {
  229. while (!breakLoop)
  230. {
  231. int c;
  232. c = getch();
  233. switch (c)
  234. {
  235. case 'q':
  236. case 'Q':
  237. return false;
  238. case KEY_UP:
  239. case 'K':
  240. case 'k':
  241. selection = select_up;
  242. return true;
  243. case KEY_DOWN:
  244. case 'j':
  245. case 'J':
  246. selection = select_down;
  247. return true;
  248. }
  249. }
  250. return false;
  251. }
  252. void CurseOutput::init()
  253. {
  254. if (!isatty(fileno(stdin)) || !isatty(fileno(stdout)))
  255. {
  256. screen_fd = fopen("/dev/tty", "r+");
  257. setbuf(screen_fd, nullptr);
  258. screen = newterm(nullptr, screen_fd, screen_fd);
  259. }
  260. else
  261. screen = newterm(nullptr, stdout, stdin);
  262. wtimeout(stdscr, 150);
  263. cbreak();
  264. clear();
  265. curs_set(false);
  266. keypad(stdscr, true);
  267. signal(SIGWINCH, _resizeFnc);
  268. signal(SIGINT, _resizeFnc);
  269. signal(SIGTERM, _resizeFnc);
  270. signal(SIGKILL, _resizeFnc);
  271. topleft.first = std::pair<unsigned int, unsigned int>(0, 0);
  272. topleft.second = data;
  273. }
  274. void CurseOutput::shutdown()
  275. {
  276. endwin();
  277. delscreen(screen);
  278. if (screen_fd)
  279. {
  280. fclose(screen_fd);
  281. screen_fd = nullptr;
  282. }
  283. screen = nullptr;
  284. }