#include #include #include #include "graph_browser.hpp" namespace { std::string historyToString(const std::deque& d, size_t max_length) { if (d.empty()) return std::string(); std::string s(d.back()); for (auto rit = d.crbegin()+1; rit != d.rend(); ++rit) if (s.length() + (*rit).length() + 3 < max_length) s.insert(0, std::string(*rit) + " | "); return s; } std::string toCommaSeparatedList(const std::vector& v, const std::string& skip) { std::string s; for (size_t i = 0; i < v.size()-1; ++i) { if (v[i] == skip) continue; s += v[i]; s += ","; } s += v.back(); return s; } void inline removeFromHistory(std::deque& h, const std::string& s) { std::remove(h.begin(), h.end(), s); } ITEM* getNthItem(const MENU* menu, int n) { const int number_of_items = item_count(menu); if (number_of_items == 0 || n > number_of_items) return 0; ITEM** items = menu_items(menu); ITEM* retval = *items; for (int i = 1; i <= n; ++i) retval = retval->down; return retval; } } void GraphBrowser::init() { int r = 0; WINDOW* w; w = initscr(); assert(w); r = cbreak(); assert(r == OK); r = noecho(); assert(r == OK); r = keypad(stdscr, TRUE); assert(r == OK); //Needed to have ‘immediate’ ESC-Key behavior: if (getenv ("ESCDELAY") == NULL) { r = set_escdelay(25); assert(r == OK); } } void GraphBrowser::destroy() { clrtoeol(); refresh(); endwin(); } GraphBrowser::GraphBrowser(Graph& g) : menu_(0) , current_win_(0) , menu_subwindow_(0) , n_win_(0) , n_of_n_win_(0) , graph_(g) , history_() { updateDimensions(); initLayout(); } void GraphBrowser::initLayout() { int r; current_win_ = newwin(window_height, current_window_width, 1, 0); assert(current_win_); r = keypad(current_win_, TRUE); assert(r == OK); menu_ = new_menu((ITEM **)0); assert(menu_); r = set_menu_win(menu_, current_win_); assert(r == E_OK); menu_subwindow_ = derwin(current_win_, window_height-2, current_window_width-2, 1, 1); assert(menu_subwindow_); r = set_menu_sub(menu_, menu_subwindow_); assert(r == E_NOT_CONNECTED); // No items are connected to the menu. r = set_menu_mark(menu_, " "); assert(r == E_OK); r = wborder(current_win_, ACS_VLINE, ACS_VLINE, ACS_HLINE, ACS_HLINE, ACS_ULCORNER, ACS_TTEE, ACS_LLCORNER, ACS_BTEE); assert(r == OK); r = refresh(); assert(r == OK); r = wrefresh(current_win_); assert(r == OK); n_win_ = newwin(window_height, n_window_width, 1, current_window_width); assert(n_win_); r = wborder(n_win_, ' ', ' ', ACS_HLINE, ACS_HLINE, ACS_HLINE, ACS_HLINE, ACS_HLINE, ACS_HLINE); assert(r == OK); r = refresh(); assert(r == OK); r = wrefresh(n_win_); assert(r == OK); // window of the neighbours'neighbours of the current vertex n_of_n_win_ = newwin(window_height, term_max_x_-current_window_width-n_window_width, 1, n_window_width+current_window_width); assert(n_of_n_win_); r = wborder(n_of_n_win_, ACS_VLINE, ACS_VLINE, ACS_HLINE, ACS_HLINE, ACS_TTEE, ACS_URCORNER, ACS_BTEE, ACS_LRCORNER); assert(r == OK); r = refresh(); assert(r == OK); r = wrefresh(n_of_n_win_); assert(r == OK); r = mvprintw(term_max_y_-1, 0, "ESC:exit, cursors:navigate, d/i:del/ins edge, D/I:del/ins node"); assert(r == OK); r = refresh(); assert(r == OK); } void GraphBrowser::cleanUp() { int r; r = endwin(); assert(r == OK); r = refresh(); assert(r == OK); r = clear(); assert(r == OK); ITEM** old_items = menu_items(menu_); const int old_items_count = item_count(menu_); if (old_items_count > 0) { r = unpost_menu(menu_); assert(r == E_OK); } r = free_menu(menu_); assert(r == E_OK); for (int i = 0; i < old_items_count; ++i) { r = free_item(old_items[i]); assert(r == E_OK); } r = delwin(menu_subwindow_); assert(r == OK); r = delwin(current_win_); assert(r == OK); r = delwin(n_win_); assert(r == OK); r = delwin(n_of_n_win_); assert(r == OK); } GraphBrowser::~GraphBrowser() { cleanUp(); } void GraphBrowser::mainLoop() { int c; while((c = wgetch(current_win_)) != KEY_ESC) { switch(c) { case KEY_DOWN: menu_driver(menu_, REQ_DOWN_ITEM); updateNeighbours(); break; case KEY_UP: menu_driver(menu_, REQ_UP_ITEM); updateNeighbours(); break; case KEY_LEFT: { if (history_.size() == 1) break; history_.pop_back(); const std::string prev = history_.back(); updateCurrent(prev); updateNeighbours(); break; } case KEY_RIGHT: { ITEM* c_item = current_item(menu_); if (c_item == 0) break; std::string next = item_name(c_item); history_.push_back(next); updateCurrent(next); updateNeighbours(); break; } case KEY_NPAGE: { menu_driver(menu_, REQ_SCR_DPAGE); break; } case KEY_PPAGE: { menu_driver(menu_, REQ_SCR_UPAGE); break; } case KEY_UPPER_CASE_D: { // delete node deleteElement(true); break; } case KEY_UPPER_CASE_I: { // insert node break; } case KEY_LOWER_CASE_D: { // delete edge deleteElement(false); break; } case KEY_LOWER_CASE_I: { // insert edge break; } } wrefresh(current_win_); } } void GraphBrowser::deleteElement(bool delete_node) { const std::string current = history_.back(); const ITEM* c_item = current_item(menu_); if (c_item == 0) return; const std::string selected = item_name(c_item); if (delete_node) { if (selected == history_.front()) // cannot delete start node return; graph_.removeVertex(selected); removeFromHistory(history_, selected); } else { graph_.removeEdge(current, selected); } const int selected_index = item_index(c_item); updateCurrent(current); const int number_of_items = item_count(menu_); if (number_of_items == 0) return; ITEM* newly_selected_item = getNthItem(menu_, std::min(selected_index, number_of_items-1)); const int r = set_current_item(menu_, newly_selected_item); assert (r == E_OK); updateNeighbours(); } void GraphBrowser::setStartVertex(const std::string& s) { history_.push_back(s); updateCurrent(s, true); updateNeighbours(); } void GraphBrowser::updateCurrent(const std::string& s, bool first_run) { const std::vector& n = graph_.neighboursOf(s); addItems(n, first_run); updateNeighbours(); int r; r = mvprintw(0, 0, "%s",std::string(term_max_x_,' ').c_str()); assert(r == OK); r = mvprintw(0, 0, historyToString(history_, term_max_x_).c_str()); assert(r == OK); r = refresh(); assert(r == OK); } void GraphBrowser::updateNeighbours() { int r; const size_t n_width = n_window_width-1; const size_t n_of_n_width = term_max_x_-current_window_width-n_window_width-2; for (int i = 1; i < window_height-1; ++i) { r = mvwprintw(n_win_, i, 1, "%s", std::string(n_width,' ').c_str()); assert(r == OK); r = mvwprintw(n_of_n_win_, i, 1, "%s", std::string(n_of_n_width,' ').c_str()); assert(r == OK); } ITEM* c_item = current_item(menu_); if (c_item != 0) { const std::string current = item_name(c_item); const std::vector& n = graph_.neighboursOf(current); for (size_t i = 0; i < n.size() && i < window_height-2; ++i) { const size_t n_win_w = std::min(n[i].length(), n_width); r = mvwprintw(n_win_, i+1, 1, std::string(n[i], 0, n_win_w).c_str()); assert(r == OK); const std::vector& n_of_n = graph_.neighboursOf(n[i]); const std::string n_of_n_string = toCommaSeparatedList(n_of_n, current); const size_t n_of_n_win__w = std::min(n_of_n_string.length(), n_of_n_width-1); r = mvwprintw(n_of_n_win_, i+1, 2, std::string(n_of_n_string, 0, n_of_n_win__w).c_str()); assert(r == OK); } } r = wrefresh(n_win_); assert(r == E_OK); r = wrefresh(n_of_n_win_); assert(r == E_OK); } void GraphBrowser::addItems(const std::vector& stringVector, bool first_run) { int r; // keep pointed to deallocate old items ITEM** old_items = menu_items(menu_); const int old_items_count = item_count(menu_); if (old_items_count > 0) { r = unpost_menu(menu_); assert (r == E_OK || first_run && r == E_NOT_POSTED); } const int number_of_new_items = stringVector.size(); ITEM** new_items = 0; if (number_of_new_items > 0) { new_items = (ITEM **)calloc(number_of_new_items+1, sizeof(ITEM *)); for(size_t i = 0; i < number_of_new_items; ++i) { new_items[i] = new_item(stringVector[i].c_str(), 0); assert (new_items[i]); } new_items[number_of_new_items] = 0; // terminating empty item } r = set_menu_items(menu_, new_items); assert(r == E_OK); if (number_of_new_items > 0) { r = set_menu_format(menu_, window_height-2, 1); assert(r == E_OK); r = post_menu(menu_); assert(r == E_OK); } r = wrefresh(current_win_); assert(r == OK); // deallocate old items for (int i = 0; i < old_items_count; ++i) { r = free_item(old_items[i]); assert(r == E_OK); } } void GraphBrowser::updateDimensions() { term_max_x_ = COLS; term_max_y_ = LINES; window_height = term_max_y_-2; current_window_width = term_max_x_/4; n_window_width = term_max_x_/4; } void GraphBrowser::terminalResizedEvent(int) { cleanUp(); updateDimensions(); initLayout(); updateCurrent(history_.back(), true); }