Skip to content
screen.cc 12.6 KiB
Newer Older
Seblu's avatar
Seblu committed
/*
  This file is part of SLC.
Seblu's avatar
Seblu committed
  Copyright (C) 2008 Sebastien LUTTRINGER <contact@seblu.net>
Seblu's avatar
Seblu committed

  SLC is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
Seblu's avatar
Seblu committed
  the Free Software Foundation; version 2 of the License.
Seblu's avatar
Seblu committed

  SLC is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with SLC; if not, write to the Free Software
Seblu's avatar
Seblu committed
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
Seblu's avatar
Seblu committed
*/

Seblu's avatar
Seblu committed
#include "slc.hh"
#include "screen.hh"
#include "options.hh"
#include "history.hh"
Seblu's avatar
Seblu committed
#include "engine.hh"
Seblu's avatar
Seblu committed
#include "sll/connection.hh"
Seblu's avatar
Seblu committed

Seblu's avatar
Seblu committed
#include <ctype.h>
#include <signal.h>
#include <math.h>

Seblu's avatar
Seblu committed
/*
*********************************************************************************
*************************************** PUBLIC **********************************
*********************************************************************************
*/

/*!
** Screen constructor
*/
Screen::Screen() {
Seblu's avatar
Seblu committed
  // msg init
  msg_win_buffer_ = 0;
Seblu's avatar
Seblu committed

  // cmd init
  *cmd_buffer_ = 0;
  cmd_buf_off_ = 0;
  cmd_win_off_ = 0;
  cmd_history_off_ = 0;
}
Seblu's avatar
Seblu committed

Seblu's avatar
Seblu committed
/*!
** Switch in ncurses mode
*/
Seblu's avatar
Seblu committed
void Screen::init() {
  // Init ncurses
  initscr();

  // register end stop ncurses before exit
  atexit((void (*)(void)) endwin);

  // Set good parameters
  cbreak();
  keypad(stdscr, TRUE);

  // refresh parameters (needed by ncurses)
Seblu's avatar
Seblu committed
  refresh();
Seblu's avatar
Seblu committed

Seblu's avatar
Seblu committed
/*!
** Create windows clear the screen, destroy already existing windows,
** create windows with good size and draw it.
*/
void Screen::create_windows() {
Seblu's avatar
Seblu committed
  // Check screen size
  if (LINES < 6 || COLS < 3) {
    std::cerr << "Screen is too small." << std::endl;
    exit(ERR_SCREENSZ);
  }

Seblu's avatar
Seblu committed
  if ((size_t) LINES * 2 > O.history_size)
    O.history_size = 2 * LINES;
  noecho();
  clear();
  refresh();
Seblu's avatar
Seblu committed

  // msg window
  if (msg_)
    delwin(msg_);
  msg_ = newwin(LINES - 3, COLS, 0, 0);
  msg_win_buffer_ = (char *) realloc(msg_win_buffer_, COLS -2 + 1);
  msg_draw();

  // cmd window
  if (cmd_)
    delwin(cmd_);
  cmd_ = newwin(3, COLS, LINES - 3, 0);
  cmd_draw();
}
Seblu's avatar
Seblu committed

Seblu's avatar
Seblu committed
/*!
** Swith into login window mode and
** manage the login/pass ask processus
*/
void Screen::login() {
  char *buf;
  int size;
  const char *smsg;

Seblu's avatar
Seblu committed
  do {
    clear();
    box(stdscr, 0,0);
    move(1, 1);

    // ask login
    if (O.login.empty()) {
      smsg = "Login: ";
      size = COLS - 2 - strlen(smsg);
      buf = new char[size + 1];
      echo();
      printw(smsg);
      refresh();
      getnstr(buf, size);
      buf[size] = 0;
      O.login = buf;
      delete [] buf;
      move(2,1);
    }

    // ask password
    smsg = "Password: ";
    size = COLS - 2 - strlen(smsg);
    buf = new char[size + 1];
    printw(smsg);
Seblu's avatar
Seblu committed
    noecho();
    refresh();
    getnstr(buf, size);
    buf[size] = 0;
Seblu's avatar
Seblu committed
    O.pass = buf;
Seblu's avatar
Seblu committed
  while (O.login.empty() || O.pass.empty());
Seblu's avatar
Seblu committed
/*!
** Switch in dialog window mode
*/
void Screen::dialog() {

  //nominal screen nom
  create_windows();
Seblu's avatar
Seblu committed
/*!
** Wait keyboard events and act accordingly
*/
void Screen::eventsloop() {
Seblu's avatar
Seblu committed
  while (1) {
Seblu's avatar
Seblu committed
    int key = getch();
Seblu's avatar
Seblu committed
    switch(key) {
      // --------------------------- KEY UP ------------------------------------
Seblu's avatar
Seblu committed
    case KEY_UP:
      if (cmd_history_off_ + 1 > H.size())
Seblu's avatar
Seblu committed
	break;
      if (cmd_history_off_ == 0)
	memcpy(cmd_history_buffer_, cmd_buffer_, MAX_LINE_SIZE);
      strncpy(cmd_buffer_, H.get(cmd_history_off_++).c_str(), MAX_LINE_SIZE);
      cmd_buffer_[MAX_LINE_SIZE - 1] = 0;
      cmd_buf_off_ = strlen(cmd_buffer_);
      cmd_win_off_ = 0;
Seblu's avatar
Seblu committed
      break;
      // --------------------------- KEY DOWN ----------------------------------
Seblu's avatar
Seblu committed
    case KEY_DOWN:
      if (cmd_history_off_ == 0)
	break;
      --cmd_history_off_;
      if (cmd_history_off_ == 0)
	memcpy(cmd_buffer_, cmd_history_buffer_, MAX_LINE_SIZE);
Seblu's avatar
Seblu committed
      else {
	strncpy(cmd_buffer_, H.get(cmd_history_off_ - 1).c_str(), MAX_LINE_SIZE);
	cmd_buffer_[MAX_LINE_SIZE - 1] = 0;
Seblu's avatar
Seblu committed
      }
      cmd_buf_off_ = strlen(cmd_buffer_);
      cmd_win_off_ = 0;
Seblu's avatar
Seblu committed
      break;
      // --------------------------- KEY LEFT ----------------------------------
Seblu's avatar
Seblu committed
    case KEY_LEFT:
Seblu's avatar
Seblu committed
      if (cmd_buf_off_ > 0)
	--cmd_buf_off_;
Seblu's avatar
Seblu committed
      break;
      // --------------------------- KEY RIGHT ---------------------------------
Seblu's avatar
Seblu committed
    case KEY_RIGHT:
Seblu's avatar
Seblu committed
      if ((size_t) cmd_buf_off_ < strlen(cmd_buffer_))
	++cmd_buf_off_;
Seblu's avatar
Seblu committed
      break;
      // --------------------------- KEY HOME ----------------------------------
    case KEY_HOME:
      cmd_buf_off_ = 0;
      // --------------------------- KEY END -----------------------------------
    case KEY_END:
      cmd_buf_off_ = strlen(cmd_buffer_);
      // --------------------------- KEY RETURN --------------------------------
Seblu's avatar
Seblu committed
    case KEY_RETURN:
      // check non empty buffer
      if (*cmd_buffer_ == 0) break;
      // save in history
Seblu's avatar
Seblu committed
      if (strncmp(cmd_buffer_, "/pass", 4))
	H.add(cmd_buffer_);
Seblu's avatar
Seblu committed
      // Send to execution
Seblu's avatar
Seblu committed
      cmd::exec(cmd_buffer_);
Seblu's avatar
Seblu committed
      // purge buffer
      *cmd_buffer_ = 0;
      cmd_buf_off_ = 0;
      cmd_win_off_ = 0;
Seblu's avatar
Seblu committed
      cmd_history_off_ = 0;
      // print result
      cmd_draw();
Seblu's avatar
Seblu committed
      break;
      // --------------------------- KEY BACKSPACE------------------------------
Seblu's avatar
Seblu committed
    case KEY_BACKSPACE:
      if (cmd_buf_off_ != 0)
	if (del_char(cmd_buffer_, cmd_buf_off_ - 1, MAX_LINE_SIZE))
	  --cmd_buf_off_;
      cmd_draw();
      // --------------------------- KEY DELETE --------------------------------
    case KEY_DC:
      del_char(cmd_buffer_, cmd_buf_off_, MAX_LINE_SIZE);
      cmd_draw();
      break;
      // --------------------------- KEY RESIZE --------------------------------
    case KEY_RESIZE:
      create_windows();
Seblu's avatar
Seblu committed
      break;
      // --------------------------- KEY EOF -----------------------------------
Seblu's avatar
Seblu committed
    case KEY_EOF:
      return;
      // -------------------------ALL OTHERS KEYS ------------------------------
Seblu's avatar
Seblu committed
    default:
Seblu's avatar
Seblu committed
      if (!isprint(key)) break;
      if (add_char(key, cmd_buffer_, cmd_buf_off_, MAX_LINE_SIZE))
	++cmd_buf_off_;
Seblu's avatar
Seblu committed

Seblu's avatar
Seblu committed
/*!
** Print a message in msg window (on one line)
Seblu's avatar
Seblu committed
**
** @param s message to print
*/
void Screen::msg_println(const string &s) {
  msg_buffer_ << s;
  msg_print("\n");
}

/*!
** Print a number in msg window (on one line)
**
** @param s message to print
*/
void Screen::msg_println(int i) {
  msg_buffer_ << i;
  msg_print("\n");
}

/*!
** Print @param s into msg window (no refresh)
*/
Seblu's avatar
Seblu committed
void Screen::msg_print(const string &s) {
  size_t offset = 0;
  size_t last = 0;

  msg_buffer_ << s;

  const string &buf = msg_buffer_.str();
  // add line by line
  while ((offset = buf.find('\n', last)) != string::npos) {
    msg_add(buf.substr(last, offset - last));
    last = offset + 1;
  }

  // update screen
  if (last > 0) {
    msg_buffer_.str("");
    msg_draw();
    cmd_draw();
  }
/*!
** Print @param i into msg window (no refresh)
*/
void Screen::msg_print(int i) {
  msg_buffer_ << i;
}


Seblu's avatar
Seblu committed
/*
*********************************************************************************
************************************** PRIVATE **********************************
*********************************************************************************
*/


/*!
** Draw the content of the cmd window
*/
void Screen::cmd_draw() {
Seblu's avatar
Seblu committed
  char print_buf[MAX_LINE_SIZE + 1];
Seblu's avatar
Seblu committed

  // clean and redraw the cmd box
  werase(cmd_);
  box(cmd_, 0, 0);
  wmove(cmd_, 1, 1);

  // check cur pos is in screen
Seblu's avatar
Seblu committed
  if (cmd_buf_off_ + 1 > (size_t) COLS - 2 + cmd_win_off_)
    cmd_win_off_ = cmd_buf_off_ + 1 - (COLS - 2) ;
  else if (cmd_buf_off_ < cmd_win_off_)
    cmd_win_off_ = cmd_buf_off_;
Seblu's avatar
Seblu committed

  // copy part of buffer in print buffer
  size_t minsize = (MAX_LINE_SIZE < COLS - 2) ? MAX_LINE_SIZE : COLS - 2;
  strncpy(print_buf, cmd_buffer_ + cmd_win_off_, minsize);
Seblu's avatar
Seblu committed
  print_buf[minsize] = 0;
Seblu's avatar
Seblu committed

  // print print buffer
  wattron(cmd_, A_BOLD);
  wprintw(cmd_, "%s", print_buf);
  wattroff(cmd_, A_BOLD);

  // move cursor to right pos
  wmove(cmd_, 1, 1 + cmd_buf_off_ - cmd_win_off_);
Seblu's avatar
Seblu committed

  // refresh screen
Seblu's avatar
Seblu committed
  wrefresh(cmd_);
}

Seblu's avatar
Seblu committed
/*!
** Draw the content of the msg window
*/
void Screen::msg_draw() {
  werase(msg_);
  box(msg_, 0, 0);
Seblu's avatar
Seblu committed

  t_lines::const_iterator line;
  size_t free_lines = LINES - 5;

  for (line = msg_table_.begin(); line != msg_table_.end() && free_lines > 0; ++line) {
    size_t len = strlen((*line).c_str());

    // print one line
    if (len <= (size_t) COLS - 2)
      mvwprintw(msg_, free_lines--, 1, "%s", (*line).c_str());

    // print multi line
    else {
      // compute line count
      const size_t nline = 1 + (size_t) ceil(((double) ( len - (COLS - 2)) / (double) (COLS - 4)));

      // iterate for each line starting with the last
      for (size_t i = nline; i > 0 && free_lines > 0; --i) {
	// compute string offset for this line
	const size_t print_offset = (i == 1) ? 0 : COLS - 2 + ((i - 2)  * (COLS - 4));
	// copy line into a buffer
	strncpy(msg_win_buffer_, (*line).c_str() + print_offset, COLS - ((i == 1) ? 2 : 4));
	msg_win_buffer_[COLS - ((i == 1) ? 2 : 4)] = 0;
Seblu's avatar
Seblu committed
	// print line into screen
Seblu's avatar
Seblu committed
	mvwprintw(msg_, free_lines--, 1, (i == 1) ? "%s" : "| %s", msg_win_buffer_);
Seblu's avatar
Seblu committed
      }
    }
  }

  // refresh screen
Seblu's avatar
Seblu committed
** Add char @param c in a @param string buffer at offset @param offset.
** @param buf_len is the size of the buffer
Seblu's avatar
Seblu committed
** @return true if char was added
*/
bool Screen::add_char(char c, char *string, ssize_t offset, size_t buf_len) {
Seblu's avatar
Seblu committed
  assert(string);
  size_t len = strlen(string);

  // check if add one char is in range
  if (len + 1 > buf_len - 1)
    return false;
Seblu's avatar
Seblu committed

  // check if offset is good
  if (offset >= (ssize_t) buf_len - 1 || offset < 0)
    return false;
Seblu's avatar
Seblu committed

  // move one char every char
  for (ssize_t i = len; i >= offset; --i)
    string[i + 1] = string[i];
  string[offset] = c;
  return true;
Seblu's avatar
Seblu committed
}

Seblu's avatar
Seblu committed
/*!
** Del a char from @param string at offset @param offset.
** @param buf_len is the buffer size
**
** @return
*/
bool Screen::del_char(char *string, ssize_t offset, size_t buf_len) {
Seblu's avatar
Seblu committed
  assert(string);
  size_t len = strlen(string);

  // check if del one char is possible
  if (len == 0)
    return false;
Seblu's avatar
Seblu committed

  // check if offset is good
  if (offset >= (ssize_t) buf_len - 1 || offset < 0)
    return false;
Seblu's avatar
Seblu committed

  // move one char every char
  for (; string[offset] != 0; ++offset)
    string[offset] = string[offset + 1];

  return true;
Seblu's avatar
Seblu committed
}

Seblu's avatar
Seblu committed
/*!
** Add msg @param s to msg list
*/
void Screen::msg_add(const string &s) {
  assert(s.find('\n') == string::npos);

  if (msg_table_.size() >= O.history_size)
    msg_table_.resize(O.history_size);
  msg_table_.push_front(s);
Seblu's avatar
Seblu committed
}

Seblu's avatar
Seblu committed
/*!
** Retrieve @param i th msg from msg list
** @return string corresponding to @param i in msg list
*/
const string &Screen::msg_get(size_t i) const {
  assert(i < msg_table_.size());
  t_lines::const_iterator it;
  size_t pos;
  static string empty;

  for (pos = 0, it = msg_table_.begin();
       it != msg_table_.end();
       ++pos, ++it)
    if (pos == i)
      return *it;
  return empty;
Seblu's avatar
Seblu committed
}

Seblu's avatar
Seblu committed
/*!
** Compute the size of msg list
**
** @return count of message in msg list
*/
size_t Screen::msg_size() const {
  return msg_table_.size();
}

Seblu's avatar
Seblu committed
/*!
** Load msg list from a file @param filename
*/
void Screen::msg_load(const string &filename) {
  ifstream fs;
  t_lines::const_iterator it;
  char buf[MAX_LINE_SIZE];

  fs.open(filename.c_str(), ifstream::in);

  if (!fs.is_open())
    return;

  while (!fs.eof()) {
    if (fs.fail())
      throw Error(ERR_FILE, "Unable to load history");
    fs.getline(buf, MAX_LINE_SIZE);
    msg_add(buf);
  }

  fs.close();
}

Seblu's avatar
Seblu committed
/*!
** Save msg list to a file @param filename
*/
void Screen::msg_save(const string &filename) const {
  ofstream fs;
  t_lines::const_reverse_iterator rit;

  fs.open(filename.c_str(), ostream::out | ostream::trunc);

  if (!fs.is_open())
    throw Error(ERR_FILE, "Unable to open history file");

  for (rit = msg_table_.rbegin(); rit != msg_table_.rend(); ++rit) {
    fs.write((*rit).c_str(), (*rit).length());
    fs.put('\n');
    if (fs.fail())
      throw Error(ERR_FILE, "Unable to save history");
  }

  fs.close();
Seblu's avatar
Seblu committed
}

Seblu's avatar
Seblu committed
/*!
** This is a sugar to print msg on msg window
**
** @param scr A Screen
** @param s string to print
**
** @return @param scr
*/
Screen &operator<< (Screen &scr, const string &s) {
  scr.msg_print(s);
Seblu's avatar
Seblu committed
  return scr;
}

/*!
** This is a sugar to print msg on msg window
**
** @param scr A Screen
** @param i string to print
**
** @return @param scr
*/
Screen &operator<< (Screen &scr, int i) {
  scr.msg_print(i);
  return scr;
}