Goby v2
flex_ncurses.cpp
1 // Copyright 2009-2018 Toby Schneider (http://gobysoft.org/index.wt/people/toby)
2 // GobySoft, LLC (2013-)
3 // Massachusetts Institute of Technology (2007-2014)
4 // Community contributors (see AUTHORS file)
5 //
6 //
7 // This file is part of the Goby Underwater Autonomy Project Libraries
8 // ("The Goby Libraries").
9 //
10 // The Goby Libraries are free software: you can redistribute them and/or modify
11 // them under the terms of the GNU Lesser General Public License as published by
12 // the Free Software Foundation, either version 2.1 of the License, or
13 // (at your option) any later version.
14 //
15 // The Goby Libraries are distributed in the hope that they will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public License
21 // along with Goby. If not, see <http://www.gnu.org/licenses/>.
22 
23 #include <algorithm>
24 #include <cmath>
25 #include <iostream>
26 #include <sstream>
27 
28 #include <boost/algorithm/string.hpp>
29 #include <boost/foreach.hpp>
30 #include <ncurses.h>
31 
32 #include "goby/util/as.h"
33 
34 #include "logger_manipulators.h"
35 
36 #include "flex_ncurses.h"
37 #include "term_color.h"
38 
39 using boost::posix_time::ptime;
40 
41 // defined in flex_ostreambuf.cpp
42 extern boost::mutex curses_mutex;
43 
44 goby::common::FlexNCurses::FlexNCurses()
45  : xmax_(0), ymax_(0), xwinN_(1), ywinN_(1), foot_window_(0), is_locked_(false),
46  locked_panel_(0), alive_(true)
47 {
48 }
49 
50 void goby::common::FlexNCurses::startup()
51 {
52  initscr();
53  start_color();
54 
55  init_pair(Colors::white, COLOR_WHITE, COLOR_BLACK);
56  init_pair(Colors::red, COLOR_RED, COLOR_BLACK);
57  init_pair(Colors::green, COLOR_GREEN, COLOR_BLACK);
58  init_pair(Colors::yellow, COLOR_YELLOW, COLOR_BLACK);
59  init_pair(Colors::blue, COLOR_BLUE, COLOR_BLACK);
60  init_pair(Colors::magenta, COLOR_MAGENTA, COLOR_BLACK);
61  init_pair(Colors::cyan, COLOR_CYAN, COLOR_BLACK);
62 
63  refresh();
64  update_size();
65  // special keys (KEY_RESIZE, KEY_LEFT, etc.)
66  keypad(stdscr, TRUE);
67  // no cursor
68  curs_set(false);
69  // tenths of seconds pause waiting for character input (getch)
70  nodelay(stdscr, TRUE);
71  halfdelay(1);
72  noecho();
73  // turn on mouse events
74 
75  // problems with locking screen
76  // mousemask(ALL_MOUSE_EVENTS, 0);
77 }
78 
79 void goby::common::FlexNCurses::update_size()
80 {
81  getmaxyx(stdscr, ymax_, xmax_);
82  ymax_ -= FOOTER_Y;
83 
84  xwinN_ = std::max(1, xmax_ / CHARS_PER_LINE);
85  ywinN_ = (panels_.size() % xwinN_) ? panels_.size() / xwinN_ + 1 : panels_.size() / xwinN_;
86 
87  line_buffer_.resize(xwinN_);
88  BOOST_FOREACH (size_t i, unique_panels_)
89  {
90  int col = (i / ywinN_);
91  panels_[i].col(col);
92  panels_[i].minimized(false);
93  panels_[i].ywidth(ymax_ / ywinN_);
94  line_buffer_[col] = ymax_;
95  //int ywidth = (m_N == ywinN_) ? 0 : (ymax_-m_N*HEAD_Y)/(ywinN_-m_N);
96  }
97 
98  BOOST_FOREACH (size_t i, unique_panels_)
99  {
100  int col = (i / ywinN_);
101  line_buffer_[col] -= panels_[i].ywidth();
102  }
103 }
104 
105 void goby::common::FlexNCurses::alive(bool alive) { alive_ = alive; }
106 void goby::common::FlexNCurses::cleanup()
107 {
108  clear();
109  refresh();
110  endwin();
111 }
112 
113 void goby::common::FlexNCurses::add_win(const Group* g)
114 {
115  int N_windows = panels_.size() + 1;
116 
117  if (xwinN_ * ywinN_ < N_windows)
118  ++ywinN_;
119 
120  Panel new_panel = Panel(g);
121 
122  new_panel.original_order(panels_.size());
123  unique_panels_.insert(panels_.size());
124  panels_.push_back(new_panel);
125 
126  update_size();
127  recalculate_win();
128 }
129 
130 void goby::common::FlexNCurses::recalculate_win()
131 {
132  // clear any old windows
133  BOOST_FOREACH (size_t i, unique_panels_)
134  {
135  delwin(static_cast<WINDOW*>(panels_[i].window()));
136  delwin(static_cast<WINDOW*>(panels_[i].head_window()));
137  panels_[i].window(0);
138  panels_[i].head_window(0);
139  }
140  BOOST_FOREACH (void* p, vert_windows_)
141  {
142  delwin(static_cast<WINDOW*>(p));
143  p = 0;
144  }
145  BOOST_FOREACH (void* p, col_end_windows_)
146  {
147  delwin(static_cast<WINDOW*>(p));
148  p = 0;
149  }
150  delwin(static_cast<WINDOW*>(foot_window_));
151 
152  redrawwin(stdscr);
153  refresh();
154 
155  foot_window_ = newwin(FOOTER_Y - 1, xmax_, ymax_ + 1, 0);
156  mvwaddstr(static_cast<WINDOW*>(foot_window_), 0, 0,
157  "help: [+]/[-]: expand/contract window | [w][a][s][d]: move window | spacebar: "
158  "toggle minimize | [r]: reset | [CTRL][A]: select all | [1][2][3]...[n] select "
159  "window n | [SHIFT][[n] select multiple | [enter] pause and scroll | [c]/[C] "
160  "combine/uncombine selected windows");
161  wrefresh(static_cast<WINDOW*>(foot_window_));
162 
163  col_end_windows_.resize(xwinN_);
164  vert_windows_.resize(xwinN_ - 1);
165  BOOST_FOREACH (size_t i, unique_panels_)
166  {
167  int col = panels_[i].col();
168  int ystart = 0;
169 
170  for (std::set<size_t>::iterator it = unique_panels_.begin(); (*it) < i; ++it)
171  {
172  if (panels_[*it].col() == col)
173  ystart += panels_[*it].ywidth();
174  }
175 
176  int ywidth = panels_[i].ywidth();
177  int xwidth = xmax_ / xwinN_;
178  int xstart = col * xwidth;
179 
180  // vertical divider
181  // not last column, not if only one column
182  if (col < xwinN_ - 1 && xwinN_ > 1)
183  {
184  WINDOW* new_vert = newwin(ymax_, 1, 0, (col + 1) * xwidth - 1);
185  mvwvline(new_vert, 0, 0, 0, ymax_);
186  wrefresh(new_vert);
187  vert_windows_[col] = new_vert;
188  --xwidth;
189  }
190 
191  if (last_in_col(i))
192  {
193  WINDOW* new_col_end = newwin(1, xwidth, ystart + ywidth, xstart);
194  col_end_windows_[col] = new_col_end;
195  mvwhline(new_col_end, 0, 0, 0, xwidth);
196  wrefresh(new_col_end);
197  }
198 
199  // title and horizontal divider
200  WINDOW* new_head = newwin(HEAD_Y, xwidth, ystart, xstart);
201  panels_[i].head_window(new_head);
202  BOOST_FOREACH (size_t j, panels_[i].combined())
203  panels_[j].head_window(new_head);
204 
205  write_head_title(i);
206 
207  if (panels_[i].selected())
208  select(i);
209 
210  if (ywidth > HEAD_Y)
211  {
212  // scrolling text window
213  WINDOW* new_window = newwin(ywidth - HEAD_Y, xwidth, ystart + HEAD_Y, xstart);
214  panels_[i].window(new_window);
215  BOOST_FOREACH (size_t j, panels_[i].combined())
216  panels_[j].window(new_window);
217 
218  // move the cursor to start place for text
219  wmove(new_window, 0, 0);
220 
221  // set the background color
222  wbkgd(new_window, COLOR_PAIR(Colors::white));
223 
224  // scrolling is good!
225  scrollok(new_window, true);
226 
227  redraw_lines(i);
228 
229  // make it all stick
230  wrefresh(new_window);
231  }
232  }
233 }
234 
235 void goby::common::FlexNCurses::insert(ptime t, const std::string& s, Group* g)
236 {
237  size_t i = panel_from_group(g);
238 
239  std::multimap<ptime, std::string>& hist = panels_[i].history();
240  if (hist.size() >= panels_[i].max_hist())
241  hist.erase(hist.begin());
242 
243  // first line shouldn't have newline at the start...
244  if (!panels_[i].locked())
245  {
246  // first line shouldn't have newline at the start...
247  if (hist.empty())
248  putline(boost::trim_left_copy(s), i);
249  else
250  putline(s, i);
251  }
252  hist.insert(std::pair<ptime, std::string>(t, s));
253 }
254 
255 size_t goby::common::FlexNCurses::panel_from_group(Group* g)
256 {
257  // BOOST_FOREACH(size_t i, unique_panels_)
258  for (size_t i = 0, n = panels_.size(); i < n; ++i)
259  {
260  if (panels_[i].group() == g)
261  return i;
262  }
263  return 0;
264 }
265 
266 void goby::common::FlexNCurses::putline(const std::string& s, unsigned scrn,
267  bool refresh /* = true */)
268 {
269  if (s.empty())
270  return;
271 
272  if (scrn < panels_.size())
273  {
274  WINDOW* win = static_cast<WINDOW*>(panels_[scrn].window());
275  if (!win)
276  return;
277 
278  static const std::string esc = "\33[";
279  static const std::string m = "m";
280 
281  size_t esc_pos = -1, m_pos = -1, last_m_pos = -1;
282  size_t length = s.length();
283  while ((esc_pos = s.find(esc, esc_pos + 1)) != std::string::npos &&
284  (m_pos = s.find(m, esc_pos)) != std::string::npos)
285  {
286  std::string esc_sequence = s.substr(esc_pos, m_pos - esc_pos + 1);
287 
288  if (last_m_pos >= 0)
289  waddstr(win, s.substr(last_m_pos + 1, esc_pos - last_m_pos - 1).c_str());
290 
291  // void kills compiler warning and doesn't do anything bad
292  (void)wattrset(win, color2attr_t(TermColor::from_esc_code(esc_sequence)));
293 
294  last_m_pos = m_pos;
295  }
296 
297  if (last_m_pos + 1 < length)
298  waddstr(win, s.substr(last_m_pos + 1).c_str());
299 
300  if (refresh)
301  wrefresh(win);
302  }
303 }
304 
305 void goby::common::FlexNCurses::putlines(
306  unsigned scrn, const std::multimap<ptime, std::string>::const_iterator& alpha,
307  const std::multimap<ptime, std::string>::const_iterator& omega, bool refresh /* = true */)
308 {
309  for (std::multimap<ptime, std::string>::const_iterator it = alpha; it != omega; ++it)
310  {
311  if (it == alpha)
312  putline(boost::trim_left_copy(it->second), scrn, false);
313  else
314  putline(it->second, scrn, false);
315  }
316 
317  if (refresh)
318  wrefresh(static_cast<WINDOW*>(panels_[scrn].window()));
319 }
320 
321 long goby::common::FlexNCurses::color2attr_t(Colors::Color c)
322 {
323  switch (c)
324  {
325  default:
326  case Colors::nocolor: return COLOR_PAIR(Colors::white);
327  case Colors::red: return COLOR_PAIR(Colors::red);
328  case Colors::lt_red: return COLOR_PAIR(Colors::red) | A_BOLD;
329  case Colors::green: return COLOR_PAIR(Colors::green);
330  case Colors::lt_green: return COLOR_PAIR(Colors::green) | A_BOLD;
331  case Colors::yellow: return COLOR_PAIR(Colors::yellow);
332  case Colors::lt_yellow: return COLOR_PAIR(Colors::yellow) | A_BOLD;
333  case Colors::blue: return COLOR_PAIR(Colors::blue);
334  case Colors::lt_blue: return COLOR_PAIR(Colors::blue) | A_BOLD;
335  case Colors::magenta: return COLOR_PAIR(Colors::magenta);
336  case Colors::lt_magenta: return COLOR_PAIR(Colors::magenta) | A_BOLD;
337  case Colors::cyan: return COLOR_PAIR(Colors::cyan);
338  case Colors::lt_cyan: return COLOR_PAIR(Colors::cyan) | A_BOLD;
339  case Colors::white: return COLOR_PAIR(Colors::white);
340  case Colors::lt_white: return COLOR_PAIR(Colors::white) | A_BOLD;
341  }
342 }
343 
344 // find screen containing click
345 size_t goby::common::FlexNCurses::find_containing_window(int y, int x)
346 {
347  BOOST_FOREACH (size_t i, unique_panels_)
348  {
349  if (in_window(panels_[i].window(), y, x) || in_window(panels_[i].head_window(), y, x))
350  return i;
351  }
352  return panels_.size();
353 }
354 
355 bool goby::common::FlexNCurses::in_window(void* p, int y, int x)
356 {
357  if (!p)
358  return false;
359 
360  int ybeg, xbeg;
361  int ymax, xmax;
362  getbegyx(static_cast<WINDOW*>(p), ybeg, xbeg);
363  getmaxyx(static_cast<WINDOW*>(p), ymax, xmax);
364 
365  return (y < ybeg + ymax && y >= ybeg && x < xbeg + xmax && x >= xbeg);
366 }
367 
368 void goby::common::FlexNCurses::write_head_title(size_t i)
369 {
370  WINDOW* win = static_cast<WINDOW*>(panels_[i].head_window());
371 
372  (void)wattrset(win, color2attr_t(common::Colors::lt_white));
373 
374  int ymax, xmax;
375  getmaxyx(win, ymax, xmax);
376  mvwhline(win, 0, 0, 0, xmax);
377 
378  if (panels_[i].selected())
379  wattron(win, A_REVERSE);
380  else
381  wattroff(win, A_REVERSE);
382 
383  attr_t color_attr = color2attr_t(panels_[i].group()->color());
384  attr_t white_attr = color2attr_t(Colors::lt_white);
385  wattron(win, white_attr);
386  mvwaddstr(win, 0, 0, std::string(util::as<std::string>(i + 1) + ". ").c_str());
387 
388  std::stringstream ss;
389  ss << panels_[i].group()->description() << " ";
390  wattron(win, color_attr);
391  waddstr(win, ss.str().c_str());
392 
393  BOOST_FOREACH (size_t j, panels_[i].combined())
394  {
395  wattron(win, white_attr);
396  waddstr(win, "| ");
397  color_attr = color2attr_t(panels_[j].group()->color());
398  waddstr(win, std::string(util::as<std::string>(j + 1) + ". ").c_str());
399 
400  std::stringstream ss_com;
401  ss_com << panels_[j].group()->description() << " ";
402  wattron(win, color_attr);
403  waddstr(win, ss_com.str().c_str());
404  }
405 
406  std::stringstream ss_tags;
407  if (panels_[i].minimized())
408  ss_tags << "(minimized) ";
409  if (panels_[i].locked())
410  ss_tags << "(paused, hit return to unlock) ";
411  wattron(win, white_attr);
412  waddstr(win, ss_tags.str().c_str());
413 
414  wrefresh(win);
415 }
416 
417 void goby::common::FlexNCurses::deselect_all()
418 {
419  if (is_locked_)
420  return;
421 
422  BOOST_FOREACH (size_t i, unique_panels_)
423  {
424  if (panels_[i].selected())
425  {
426  panels_[i].selected(false);
427  write_head_title(i);
428  }
429  }
430 }
431 
432 void goby::common::FlexNCurses::select_all()
433 {
434  BOOST_FOREACH (size_t i, unique_panels_)
435  select(i);
436 }
437 
438 void goby::common::FlexNCurses::select(size_t gt)
439 {
440  if (is_locked_)
441  return;
442 
443  if (gt < panels_.size())
444  {
445  panels_[gt].selected(true);
446  write_head_title(gt);
447  }
448 }
449 
450 size_t goby::common::FlexNCurses::down(size_t curr)
451 {
452  int ybeg, xbeg;
453  int ymax, xmax;
454  getbegyx(static_cast<WINDOW*>(panels_[curr].head_window()), ybeg, xbeg);
455  getmaxyx(static_cast<WINDOW*>(panels_[curr].window()), ymax, xmax);
456  size_t next = find_containing_window(ybeg + ymax + HEAD_Y + 1, xbeg);
457 
458  return next;
459 }
460 
461 size_t goby::common::FlexNCurses::up(size_t curr)
462 {
463  int ybeg, xbeg;
464  getbegyx(static_cast<WINDOW*>(panels_[curr].head_window()), ybeg, xbeg);
465  size_t next = find_containing_window(ybeg - 1, xbeg);
466  return next;
467 }
468 
469 size_t goby::common::FlexNCurses::left(size_t curr)
470 {
471  int ybeg, xbeg;
472  getbegyx(static_cast<WINDOW*>(panels_[curr].head_window()), ybeg, xbeg);
473  // cross the divider
474  size_t next = find_containing_window(ybeg, xbeg - 2);
475  return next;
476 }
477 
478 size_t goby::common::FlexNCurses::right(size_t curr)
479 {
480  int ybeg, xbeg;
481  int ymax, xmax;
482  getbegyx(static_cast<WINDOW*>(panels_[curr].head_window()), ybeg, xbeg);
483  getmaxyx(static_cast<WINDOW*>(panels_[curr].head_window()), ymax, xmax);
484  // cross the divider
485  size_t next = find_containing_window(ybeg, xbeg + xmax + 2);
486  return next;
487 }
488 
489 void goby::common::FlexNCurses::home() { shift(0); }
490 
491 void goby::common::FlexNCurses::end() { shift(panels_.size() - 1); }
492 
493 void goby::common::FlexNCurses::shift(size_t next)
494 {
495  if (next < panels_.size())
496  {
497  deselect_all();
498  select(next);
499  }
500 }
501 
502 void goby::common::FlexNCurses::combine()
503 {
504  size_t lowest;
505  BOOST_FOREACH (size_t i, unique_panels_)
506  {
507  if (panels_[i].selected())
508  {
509  lowest = i;
510  break;
511  }
512  }
513 
514  BOOST_FOREACH (size_t i, unique_panels_)
515  {
516  if (panels_[i].selected() && i != lowest)
517  {
518  panels_[lowest].add_combined(i);
519  BOOST_FOREACH (size_t j, panels_[i].combined())
520  panels_[lowest].add_combined(j);
521 
522  panels_[i].clear_combined();
523 
524  unique_panels_.erase(i);
525  }
526  }
527 
528  update_size();
529  recalculate_win();
530 }
531 
532 void goby::common::FlexNCurses::uncombine(size_t i)
533 {
534  BOOST_FOREACH (size_t j, panels_[i].combined())
535  unique_panels_.insert(j);
536  panels_[i].clear_combined();
537 }
538 
539 void goby::common::FlexNCurses::uncombine_selected()
540 {
541  BOOST_FOREACH (size_t i, unique_panels_)
542  {
543  if (panels_[i].selected())
544  {
545  uncombine(i);
546  }
547  }
548 
549  update_size();
550  recalculate_win();
551 }
552 
553 void goby::common::FlexNCurses::uncombine_all()
554 {
555  BOOST_FOREACH (size_t i, unique_panels_)
556  uncombine(i);
557 
558  update_size();
559  recalculate_win();
560 }
561 
562 void goby::common::FlexNCurses::move_up()
563 {
564  BOOST_FOREACH (size_t i, unique_panels_)
565  {
566  if (!first_in_col(i) && panels_[i].selected())
567  iter_swap(panels_.begin() + i, panels_.begin() + i - 1);
568  }
569  recalculate_win();
570 }
571 
572 void goby::common::FlexNCurses::move_down()
573 {
574  // BOOST_REVERSE_FOREACH(size_t i, unique_panels_)
575  for (std::set<size_t>::reverse_iterator it = unique_panels_.rbegin(), n = unique_panels_.rend();
576  it != n; ++it)
577  {
578  size_t i = *it;
579  if (!last_in_col(i) && panels_[i].selected())
580  iter_swap(panels_.begin() + i, panels_.begin() + i + 1);
581  }
582  recalculate_win();
583 }
584 
585 void goby::common::FlexNCurses::move_right()
586 {
587  // BOOST_REVERSE_FOREACH(size_t i, unique_panels_)
588  for (std::set<size_t>::reverse_iterator it = unique_panels_.rbegin(), n = unique_panels_.rend();
589  it != n; ++it)
590  {
591  size_t i = *it;
592  size_t rpanel = right(i);
593  if (rpanel == panels_.size())
594  {
595  BOOST_FOREACH (size_t j, unique_panels_)
596  {
597  if (last_in_col(j) && panels_[i].col() + 1 == panels_[j].col())
598  rpanel = j + 1;
599  }
600  }
601 
602  int old_col = panels_[i].col();
603  if (panels_[i].selected() && old_col < xwinN_ - 1)
604  {
605  panels_[i].col(old_col + 1);
606  Panel p = panels_[i];
607  int orig_width = p.ywidth();
608  line_buffer_[old_col] += orig_width;
609  p.ywidth(0);
610  panels_.insert(panels_.begin() + rpanel, p);
611  panels_.erase(panels_.begin() + i);
612  for (int j = 0, m = orig_width; j < m; ++j) grow(rpanel - 1);
613  }
614  }
615 
616  recalculate_win();
617 }
618 
619 void goby::common::FlexNCurses::move_left()
620 {
621  BOOST_FOREACH (size_t i, unique_panels_)
622  {
623  size_t lpanel = left(i);
624  if (lpanel == panels_.size())
625  {
626  BOOST_FOREACH (size_t j, unique_panels_)
627  {
628  if (first_in_col(j) && panels_[i].col() == panels_[j].col())
629  lpanel = j;
630  }
631  }
632 
633  int old_col = panels_[i].col();
634  if (panels_[i].selected() && old_col > 0)
635  {
636  panels_[i].col(old_col - 1);
637  Panel p = panels_[i];
638  int orig_width = p.ywidth();
639  line_buffer_[old_col] += orig_width;
640  p.ywidth(0);
641  panels_.erase(panels_.begin() + i);
642  panels_.insert(panels_.begin() + lpanel, p);
643 
644  for (int j = 0, m = orig_width; j < m; ++j) grow(lpanel);
645  }
646  }
647  recalculate_win();
648 }
649 
650 size_t goby::common::FlexNCurses::find_first_selected()
651 {
652  BOOST_FOREACH (size_t i, unique_panels_)
653  {
654  if (panels_[i].selected())
655  return i;
656  }
657  return 0;
658 }
659 
660 bool goby::common::FlexNCurses::last_in_col(size_t val)
661 {
662  BOOST_FOREACH (size_t i, unique_panels_)
663  {
664  if (panels_[i].col() == panels_[val].col() && i > val)
665  return false;
666  }
667  return true;
668 }
669 
670 bool goby::common::FlexNCurses::first_in_col(size_t val)
671 {
672  BOOST_FOREACH (size_t i, unique_panels_)
673  {
674  if (panels_[i].col() == panels_[val].col() && i < val)
675  return false;
676  }
677  return true;
678 }
679 
680 void goby::common::FlexNCurses::grow_all()
681 {
682  BOOST_FOREACH (size_t i, unique_panels_)
683  {
684  if (panels_[i].selected())
685  grow(i);
686  }
687  recalculate_win();
688 }
689 
690 void goby::common::FlexNCurses::shrink_all()
691 {
692  BOOST_FOREACH (size_t i, unique_panels_)
693  {
694  if (panels_[i].selected())
695  shrink(i);
696  }
697  recalculate_win();
698 }
699 
700 void goby::common::FlexNCurses::grow(int i)
701 {
702  panels_[i].set_minimized(false);
703  size_t largest_panel = panels_.size();
704  int largest_panel_size = 0;
705  BOOST_FOREACH (size_t j, unique_panels_)
706  {
707  if (panels_[j].ywidth() >= largest_panel_size && panels_[j].col() == panels_[i].col() &&
708  !panels_[j].minimized() && !panels_[j].selected())
709  {
710  largest_panel_size = panels_[j].ywidth();
711  largest_panel = j;
712  }
713  }
714 
715  // shrink the line buffer
716  if (line_buffer_[panels_[i].col()])
717  {
718  panels_[i].grow();
719  --line_buffer_[panels_[i].col()];
720  }
721  // shrink the largest panel
722  else if (largest_panel < panels_.size() && panels_[largest_panel].shrink())
723  {
724  panels_[i].grow();
725  }
726 }
727 
728 void goby::common::FlexNCurses::shrink(int i)
729 {
730  size_t smallest_panel = panels_.size();
731  int smallest_panel_size = ymax_;
732  BOOST_FOREACH (size_t j, unique_panels_)
733  {
734  if (panels_[j].ywidth() <= smallest_panel_size && panels_[j].col() == panels_[i].col() &&
735  !panels_[j].minimized() && !panels_[j].selected())
736  {
737  smallest_panel_size = panels_[j].ywidth();
738  smallest_panel = j;
739  }
740  }
741  // shrink the panel...
742  if (panels_[i].shrink())
743  {
744  // and add to the the smallest panel
745  if (smallest_panel < panels_.size())
746  panels_[smallest_panel].grow();
747  // or add to the line buffer
748  else
749  ++line_buffer_[panels_[i].col()];
750  }
751 }
752 
753 void goby::common::FlexNCurses::toggle_minimized(int i)
754 {
755  int change = panels_[i].toggle_minimized();
756  for (int j = 0, m = abs(change); j < m; ++j) (change / abs(change) == 1) ? grow(i) : shrink(i);
757 }
758 
759 void goby::common::FlexNCurses::winunlock()
760 {
761  BOOST_FOREACH (size_t j, unique_panels_)
762  {
763  if (panels_[j].locked())
764  {
765  panels_[j].locked(false);
766  BOOST_FOREACH (size_t k, panels_[j].combined())
767  panels_[k].locked(false);
768 
769  write_head_title(j);
770  redraw_lines(j);
771  lines_from_beg(0, j);
772  }
773  }
774 
775  is_locked_ = false;
776 }
777 
778 void goby::common::FlexNCurses::redraw_lines(int j, int offset /* = -1 */)
779 {
780  wclear(static_cast<WINDOW*>(panels_[j].window()));
781 
782  if (offset < 0)
783  {
784  const std::multimap<ptime, std::string>& hist = get_history(j, panels_[j].ywidth());
785  int past = min(static_cast<int>(hist.size()), panels_[j].ywidth() - 1);
786 
787  std::multimap<ptime, std::string>::const_iterator a_it = hist.end();
788  for (int k = 0; k < past; ++k) --a_it;
789 
790  putlines(j, a_it, hist.end());
791  }
792  else
793  {
794  const std::multimap<ptime, std::string>& hist = get_history(j);
795  int past = min(static_cast<int>(hist.size()), panels_[j].ywidth() - 1);
796 
797  int eff_offset = min(static_cast<int>(hist.size()) - past, offset);
798 
799  std::multimap<ptime, std::string>::const_iterator a_it = hist.begin();
800  for (int k = 0; k < eff_offset; ++k) ++a_it;
801 
802  std::multimap<ptime, std::string>::const_iterator o_it = a_it;
803  for (int k = 0; k < past; ++k) ++o_it;
804 
805  putlines(j, a_it, o_it);
806  }
807 }
808 
809 void goby::common::FlexNCurses::winlock()
810 {
811  size_t i = panels_.size();
812  BOOST_FOREACH (size_t j, unique_panels_)
813  {
814  if (panels_[j].selected())
815  {
816  i = j;
817  break;
818  }
819  }
820  if (i == panels_.size())
821  return;
822 
823  deselect_all();
824  select(i);
825 
826  is_locked_ = true;
827  locked_panel_ = i;
828  panels_[i].locked(true);
829  BOOST_FOREACH (size_t j, panels_[i].combined())
830  panels_[j].locked(true);
831 
832  lines_from_beg(get_history_size(i) - panels_[i].ywidth(), i);
833  write_head_title(i);
834 }
835 
836 void goby::common::FlexNCurses::scroll_up()
837 {
838  int i = locked_panel_;
839  int l = panels_[i].lines_from_beg();
840  redraw_lines(i, lines_from_beg(l - 1, i));
841 }
842 
843 void goby::common::FlexNCurses::scroll_down()
844 {
845  int i = locked_panel_;
846  int l = panels_[i].lines_from_beg();
847  redraw_lines(i, lines_from_beg(l + 1, i));
848 }
849 
850 void goby::common::FlexNCurses::page_up()
851 {
852  int i = locked_panel_;
853  int l = panels_[i].lines_from_beg();
854  redraw_lines(i, lines_from_beg(l - (panels_[i].ywidth() - 1), i));
855 }
856 
857 void goby::common::FlexNCurses::page_down()
858 {
859  int i = locked_panel_;
860  int l = panels_[i].lines_from_beg();
861  redraw_lines(i, lines_from_beg(l + (panels_[i].ywidth() - 1), i));
862 }
863 void goby::common::FlexNCurses::scroll_end()
864 {
865  int i = locked_panel_;
866  redraw_lines(i, lines_from_beg(get_history_size(i), i));
867 }
868 void goby::common::FlexNCurses::scroll_home()
869 {
870  int i = locked_panel_;
871  redraw_lines(i, lines_from_beg(0, i));
872 }
873 
874 void goby::common::FlexNCurses::restore_order()
875 {
876  std::vector<Panel> new_panels;
877  new_panels.resize(panels_.size());
878 
879  BOOST_FOREACH (const Panel& p, panels_)
880  {
881  new_panels[p.original_order()] = p;
882  }
883 
884  panels_ = new_panels;
885 }
886 
887 std::multimap<ptime, std::string> goby::common::FlexNCurses::get_history(size_t i,
888  int how_much /* = -1 */)
889 {
890  if (panels_[i].combined().empty())
891  return panels_[i].history();
892  else
893  {
894  std::multimap<ptime, std::string>::iterator i_it_begin;
895  if (how_much < 0)
896  i_it_begin = panels_[i].history().begin();
897  else
898  {
899  i_it_begin = panels_[i].history().end();
900  for (int k = 0; k < how_much && i_it_begin != panels_[i].history().begin(); ++k)
901  --i_it_begin;
902  }
903 
904  std::multimap<ptime, std::string> merged;
905  for (; i_it_begin != panels_[i].history().end(); ++i_it_begin) merged.insert(*i_it_begin);
906 
907  BOOST_FOREACH (size_t j, panels_[i].combined())
908  {
909  std::multimap<ptime, std::string>::iterator j_it_begin;
910  if (how_much < 0)
911  j_it_begin = panels_[j].history().begin();
912  else
913  {
914  j_it_begin = panels_[j].history().end();
915  for (int k = 0; k < how_much && j_it_begin != panels_[j].history().begin(); ++k)
916  --j_it_begin;
917  }
918 
919  for (; j_it_begin != panels_[j].history().end(); ++j_it_begin)
920  merged.insert(*j_it_begin);
921  }
922  return merged;
923  }
924 }
925 
926 size_t goby::common::FlexNCurses::get_history_size(size_t i)
927 {
928  if (panels_[i].combined().empty())
929  return panels_[i].history().size();
930  else
931  {
932  size_t sum = panels_[i].history().size();
933  BOOST_FOREACH (size_t j, panels_[i].combined())
934  {
935  sum += panels_[j].history().size();
936  }
937  return sum;
938  }
939 }
940 
941 int goby::common::FlexNCurses::lines_from_beg(int l, size_t i)
942 {
943  int hist_size = get_history_size(i);
944  int past = std::min(hist_size, panels_[i].ywidth());
945  if (l <= 0)
946  return panels_[i].lines_from_beg(0);
947  else if (l >= hist_size - past + 1)
948  return panels_[i].lines_from_beg(hist_size - past + 1);
949  else
950  return panels_[i].lines_from_beg(l);
951 }
952 
953 int goby::common::FlexNCurses::Panel::lines_from_beg(int i) { return lines_from_beg_ = i; }
954 
955 int goby::common::FlexNCurses::Panel::minimized(bool b)
956 {
957  minimized_ = b;
958  if (b)
959  {
960  unminimized_ywidth_ = ywidth_;
961  return HEAD_Y - unminimized_ywidth_;
962  }
963  else
964  {
965  return unminimized_ywidth_ - HEAD_Y;
966  }
967 }
968 
970 {
971  // sleep(1);
972  // MOOS loves to stomp on me at startup...
973  // if(true)
974  // {
975  // boost::mutex::scoped_lock lock(curses_mutex);
976  // BOOST_FOREACH(size_t i, unique_panels_)
977  // {
978  // WINDOW* win = static_cast<WINDOW*>(panels_[i].window());
979  // if(win) redrawwin(win); // WINDOW* head_win = static_cast<WINDOW*>(panels_[i].head_window());
980  // if(head_win) redrawwin(head_win);
981  // }
982  // BOOST_FOREACH(void* w, vert_windows_)
983  // {
984  // WINDOW* vert_win = static_cast<WINDOW*>(w);
985  // if(vert_win) redrawwin(vert_win);
986  // }
987  // BOOST_FOREACH(void* w, col_end_windows_)
988  // {
989  // WINDOW* win = static_cast<WINDOW*>(w);
990  // if(win) redrawwin(win);
991  // }
992  // redrawwin(static_cast<WINDOW*>(foot_window_));
993  // }
994 
995  while (alive_)
996  {
997  int k = getch();
998 
999  boost::mutex::scoped_lock lock(curses_mutex);
1000  switch (k)
1001  {
1002  // same as resize but restores the order too
1003  case 'r': uncombine_all(); restore_order();
1004  case KEY_RESIZE:
1005  update_size();
1006  recalculate_win();
1007  break;
1008 
1009  case '1':
1010  deselect_all();
1011  select(0);
1012  break;
1013  case '2':
1014  deselect_all();
1015  select(1);
1016  break;
1017  case '3':
1018  deselect_all();
1019  select(2);
1020  break;
1021  case '4':
1022  deselect_all();
1023  select(3);
1024  break;
1025  case '5':
1026  deselect_all();
1027  select(4);
1028  break;
1029  case '6':
1030  deselect_all();
1031  select(5);
1032  break;
1033  case '7':
1034  deselect_all();
1035  select(6);
1036  break;
1037  case '8':
1038  deselect_all();
1039  select(7);
1040  break;
1041  case '9':
1042  deselect_all();
1043  select(8);
1044  break;
1045  case '0':
1046  deselect_all();
1047  select(9);
1048  break;
1049 
1050  // shift + numbers
1051  case '!': select(0); break;
1052  case '@': select(1); break;
1053  case '#': select(2); break;
1054  case '$': select(3); break;
1055  case '%': select(4); break;
1056  case '^': select(5); break;
1057  case '&': select(6); break;
1058  case '*': select(7); break;
1059  case '(': select(8); break;
1060  case ')': select(9); break;
1061 
1062  case 'a': move_left(); break;
1063  case 'd': move_right(); break;
1064  case 'w': move_up(); break;
1065  case 's': move_down(); break;
1066 
1067  case '+':
1068  case '=': grow_all(); break;
1069  case '_':
1070  case '-': shrink_all(); break;
1071 
1072  case 'c': combine(); break;
1073  case 'C': uncombine_selected(); break;
1074 
1075  case 'm':
1076  case ' ':
1077  BOOST_FOREACH (size_t i, unique_panels_)
1078  {
1079  if (panels_[i].selected())
1080  toggle_minimized(i);
1081  }
1082  recalculate_win();
1083  break;
1084 
1085  case 'M':
1086  BOOST_FOREACH (size_t i, unique_panels_)
1087  {
1088  if (!panels_[i].selected())
1089  toggle_minimized(i);
1090  }
1091  recalculate_win();
1092  break;
1093 
1094  case 'D':
1095  deselect_all();
1096  break;
1097  // CTRL-A
1098  case 1: select_all(); break;
1099 
1100  case '\n':
1101  case KEY_ENTER: (is_locked_) ? winunlock() : winlock(); break;
1102 
1103  case KEY_LEFT: shift(left()); break;
1104  case KEY_RIGHT: shift(right()); break;
1105  case KEY_DOWN: (!is_locked_) ? shift(down()) : scroll_down(); break;
1106  case KEY_UP: (!is_locked_) ? shift(up()) : scroll_up(); break;
1107 
1108  case KEY_PPAGE:
1109  if (is_locked_)
1110  page_up();
1111  break;
1112 
1113  case KEY_NPAGE:
1114  if (is_locked_)
1115  page_down();
1116  break;
1117 
1118  case KEY_END: (!is_locked_) ? end() : scroll_end(); break;
1119  case KEY_HOME: (!is_locked_) ? home() : scroll_home(); break;
1120 
1121  case KEY_MOUSE:
1122  MEVENT mort;
1123  getmouse(&mort);
1124  size_t gt = find_containing_window(mort.y, mort.x);
1125  if (gt >= panels_.size())
1126  break;
1127 
1128  switch (mort.bstate)
1129  {
1130  case BUTTON1_CLICKED:
1131  case BUTTON1_PRESSED:
1132  deselect_all();
1133  select(gt);
1134  last_select_x_ = mort.x;
1135  last_select_y_ = mort.y;
1136  break;
1137  case BUTTON1_RELEASED:
1138  for (int x = min(mort.x, last_select_x_), n = max(mort.x, last_select_x_);
1139  x < n; ++x)
1140  {
1141  for (size_t y = min(mort.y, last_select_y_),
1142  m = max(mort.y, last_select_y_);
1143  y < m; ++y)
1144  {
1145  size_t t = find_containing_window(y, x);
1146  if (!panels_[t].selected())
1147  select(t);
1148  }
1149  }
1150 
1151  break;
1152  case BUTTON1_DOUBLE_CLICKED:
1153  toggle_minimized(gt);
1154  recalculate_win();
1155  break;
1156  }
1157  }
1158  refresh();
1159  }
1160 }
STL namespace.
Color
The eight terminal colors (and bold or "light" variants)
Definition: term_color.h:111
void run_input()
run in its own thread to take input from the user
The global namespace for the Goby project.
Enables the Verbosity == gui mode of the Goby logger and displays an NCurses gui for the logger conte...
Definition: flex_ncurses.h:42
Defines a group of messages to be sent to the Goby logger. For Verbosity == verbose streams...