/*  GNU Moe - My Own Editor
    Copyright (C) 2005, 2006, 2007, 2008, 2009 Antonio Diaz Diaz.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <algorithm>
#include <cctype>
#include <cerrno>
#include <cstdlib>
#include <ctime>
#include <string>
#include <vector>
#include <unistd.h>
#include <ncurses.h>

#include "buffer.h"
#include "buffer_handle.h"
#include "block.h"
#include "iso_8859.h"
#include "menu.h"
#include "rc.h"
#include "screen.h"
#include "window.h"
#include "window_vector.h"


namespace Screen {

struct Lines_and_cursor
  {
  int line, lines;
  std::vector< chtype > buf;
  Point cursor;

  Lines_and_cursor( const int ln, const int lns ) throw()
    : line( ln ), lines( lns ) {}
  };

int _height, _width;
int clock_limit;		//   0: disable clock
				//   1: showing help
				// > 1: first line used by menu
std::string _clock_string( " 0:00" );

std::vector< Lines_and_cursor > lines_and_cursor_vector;


void dummy_endwin() throw() { endwin(); }

void initialize_ncurses() throw()
  {
  atexit( dummy_endwin );
  initscr();			// initializes curses data structures
  raw();			// read a char at a time and disable signals
  keypad( stdscr, true );	// enables single value for function keys
  nodelay( stdscr, false );	// forces getch() to wait for key
  noecho();			// disables echoing of getch()
  nonl();			// disables CR LF translation
  scrollok( stdscr, false );	// Disables automatic scrolling
  }


void rep_addch( int n, const chtype ch ) throw()
  { for( ; n > 0; --n ) waddch( stdscr, ch ); }


void out_raw_string( const int line, const int col, const std::string & s,
                     const int len, int pos = 0, const chtype atr = A_NORMAL ) throw()
  {
  wmove( stdscr, line, col );
  int x = col, limit = std::min( x + len, _width );
  for( ; x < limit && pos < (int)s.size(); ++pos, ++x )
    {
    unsigned char ch = s[pos];
    waddch( stdscr, atr | ch );
    }
  if( x < limit ) rep_addch( limit - x, atr | ' ' );
  }


// '@b' sets-resets bold mode
// '@i' sets-resets inverse video mode
// '@u' sets-resets underline mode
// '@@' shows a '@'
//
void out_string( const int line, const int col, const std::string & s,
                 const int len, int pos = 0 ) throw()
  {
  wmove( stdscr, line, col );
  int x = col, limit = std::min( x + len, _width );
  chtype atr = A_NORMAL;
  for( ; x < limit && pos < (int)s.size(); ++pos )
    {
    unsigned char ch = s[pos];
    if( ch == '@' && pos + 1 < (int)s.size() )
      switch( s[pos+1] )
        {
        case '@': ++pos; break;
        case 'b': atr ^= A_BOLD; ++pos; ch = 0; break;
        case 'i': atr ^= A_REVERSE; ++pos; ch = 0; break;
        case 'u': atr ^= A_UNDERLINE; ++pos; ch = 0; break;
        }
    if( ch ) { waddch( stdscr, atr | ch ); ++x; }
    }
  if( x < limit ) rep_addch( limit - x, atr | ' ' );
  }


void remove_duplicates( std::vector< std::string > & history ) throw()
  {
  for( unsigned int i = 0; i + 1 < history.size(); )
    {
    if( history[i].size() && history[i] != history.back() ) ++i;
    else history.erase( history.begin() + i );
    }
  }


void alt_key_loop( int key ) throw()
  {
  Window & w = Window_vector::curwin();
  w.show_status_line( ISO_8859::control_name( key ) );
  do key = wait_kbhit(); while( key < 0 );
  w.show_status_line();
  key = ISO_8859::decontrolize( key );
  if( std::isdigit( key ) ) w.goto_mark( key - '0' );
  else switch( key )
    {
    case 'A': w.scroll_horizontal( -8 ); break;
    case 'B': w.goto_begin_of_block(); break;
    case 'C': w.goto_column(); break;
    case 'D': w.center_cursor(); break;
    case 'F': w.goto_matching_delimiter(); break;
    case 'G': w.goto_matching_delimiter( false ); break;
    case 'K': w.goto_end_of_block(); break;
    case 'L': w.goto_line(); break;
    case 'S': w.scroll_horizontal( 8 ); break;
    case 'U': w.goto_bof(); break;
    case 'V': w.goto_eof(); break;
    case 'W': w.scroll_page( false ); break;
    case 'Z': w.scroll_page( true ); break;
    }
  }


void control_k_loop( int key )
  {
  Window & w = Window_vector::curwin();
  const Buffer * old_bufferp = Block::bufferp();
  w.show_status_line( ISO_8859::control_name( key ) );
  do key = wait_kbhit(); while( key < 0 );
  w.show_status_line();
  key = ISO_8859::decontrolize( key );
  if( std::isdigit( key ) ) w.set_mark( key - '0' );
  else switch( key )
    {
    case 'B': Block::set_begin( w.buffer(), w.pointer() );
              if( old_bufferp ) repaint( old_bufferp );
              break;
    case 'C': Window_vector::copy_block(); break;
    case 'I': Window_vector::indent_block(); break;
    case 'K': Block::set_end( w.buffer(), w.pointer() );
              if( old_bufferp ) repaint( old_bufferp );
              break;
    case 'M': Window_vector::move_block(); break;
    case 'R': Window_vector::read_block(); break;
    case 'U': Window_vector::unindent_block(); break;
    case 'W': Window_vector::write_block(); break;
    case 'Y': Window_vector::delete_block(); break;
    }
  }


void control_o_loop( int key )
  {
  Window & w = Window_vector::curwin();
  w.show_status_line( ISO_8859::control_name( key ) );
  do key = wait_kbhit(); while( key < 0 );
  w.show_status_line();
  key = ISO_8859::decontrolize( key );
  switch( key )
    {
    case '1': Window_vector::encode_base64(); break;
    case '2': Window_vector::decode_base64(); break;
    case '3': Window_vector::encode_rot1347( true ); break;
    case '4': Window_vector::encode_rot1347( false ); break;
    case '6': Window_vector::decode_quoted_printable_utf8( true ); break;
    case '8': Window_vector::decode_quoted_printable_utf8( false ); break;
    case 'C': Window_vector::center_line(); break;
    case 'D': Window_vector::remove_duplicate_lines(); break;
    case 'E': Window_vector::remove_duplicate_lines( true ); break;
    case 'L': Window_vector::change_case( false ); break;
    case 'N': Window_vector::change_buffer_name(); break;
    case 'R': werase( stdscr ); wrefresh( stdscr ); repaint(); break;
    case 'S': Window_vector::split_window(); break;
    case 'U': Window_vector::change_case( true ); break;
    }
  }


int control_q_loop( int key )
  {
  Window & w = Window_vector::curwin();
  w.show_status_line( ISO_8859::control_name( key ) );
  do key = wait_kbhit(); while( key < 0 );
  w.show_status_line();
  key = ISO_8859::decontrolize( key );
  switch( key )
    {
    case KEY_F(2): Bufhandle_vector::save_all_named(); break;
    case 'C': if( Window_vector::close_and_exit( true ) == 0 ) return 0; break;
    case 'U': Window_vector::copyright_update(); break;
    case 'X': if( Window_vector::close_and_exit( false ) == 0 ) return 0; break;
    }
  return -1;
  }


void control_s_loop( int key, const std::string & version_msg ) throw()
  {
  Window & w = Window_vector::curwin();
  w.show_status_line( ISO_8859::control_name( key ) );
  do key = wait_kbhit(); while( key < 0 );
  w.show_status_line();
  key = ISO_8859::decontrolize( key );
  switch( key )
    {
    case 'C': RC::editor_options().show_code = !RC::editor_options().show_code;
              show_status_lines(); break;
    case 'G': Bufhandle_vector::show_status(); break;
    case 'I': w.show_character_info(); break;
    case 'V': show_message( version_msg ); break;
    }
  }


void user_control_char()
  {
  const std::string prompt( "Ctrl(A-Z) or 0-255 or 0x00-0xFF or 0-0377 or Escape sequence (^C to abort): " );
  int key;
  while( true )
    {
    key = show_message( prompt, true, true );
    if( key == KEY_F(1) ) Menu::help_menu( KEY_F(1) );
    else break;
    }
  if( key < 0 || key >= 256 || key == 3 ) return;
  if( key != '\\' && !std::isdigit( key ) )
    { key = ISO_8859::controlize( key ); if( key >= 32 ) key = -1; }
  else
    {
    std::vector< std::string > dummy;		// dummy history
    dummy.push_back( std::string() );
    dummy.back() += key;
    if( get_string( prompt, dummy ) <= 0 ) return;
    if( dummy.back()[0] == '\\' )
      key = ISO_8859::escape( dummy.back(), 1 );
    else if( !RC::parse_int( dummy.back(), key ) ) key = -1;
    }
  if( key < 0 || key >= 256 ) show_message( "Invalid code" );
  else Window_vector::add_char( key, true );
  }


// `read_key' implements a buffer to speed up pastes, because `wgetch'
// always calls `wrefresh'.
//
int read_key( const bool wait, const bool get_key )
  {
  static std::vector< int > buf;
  static unsigned int index = 0;	// index of key to be returned

  if( index >= buf.size() )		// buf is empty
    {
    buf.clear(); index = 0;
    if( wait )
      {
      const int key = wgetch( stdscr );
      if( key == ERR ) return -1; else buf.push_back( key );
      }
    nodelay( stdscr, true );
    while( true )
      {
      const int key = wgetch( stdscr );
      if( key == ERR ) break; else buf.push_back( key );
      }
    nodelay( stdscr, false );
    }
  if( index < buf.size() )
    { const int key = buf[index]; if( get_key ) ++index; return key; }
  return -1;
  }

} // end namespace Screen


bool Screen::init()
  {
  initialize_ncurses();
  getmaxyx( stdscr, _height, _width );
  if( _height < 24 || _width < 80 ) return false;
  clock_limit = 0;
  clock_handler( 0 );
  Window_vector::init();
  return true;
  }


//void Screen::beep() throw() { ::beep(); wrefresh( stdscr ); }


const std::string & Screen::clock_string() throw() { return _clock_string; }


// updates the clock
extern "C" void Screen::clock_handler( int sig ) throw()
  {
  time_t dummy = time( 0 ); struct tm *t = localtime( &dummy );
  if( t )
    {
    int h = t->tm_hour % 12; if( h == 0 ) h = 12;
    _clock_string[0] = ( h >= 10 ) ? '0' + ( h / 10 ) : ' ';
    _clock_string[1] = '0' + ( h % 10 );
    _clock_string[2] = (_clock_string[2] != ':') ? ':' : ' ';
    _clock_string[3] = '0' + ( t->tm_min / 10 );
    _clock_string[4] = '0' + ( t->tm_min % 10 );
    if( sig )
      {
      Point old_cursor; save_cursor( old_cursor );
      if( clock_limit == 1 )
        out_raw_string( 0, 60, _clock_string, 5, 0, A_REVERSE );
      else for( int i = 0; i < Window_vector::windows(); ++i )
        {
        const Point cursor = Window_vector::window( i ).clock_position();
        if( cursor.line >= clock_limit ) break;
        out_raw_string( cursor.line, cursor.col, _clock_string, 5, 0, A_REVERSE );
        }
      wmove( stdscr, old_cursor.line, old_cursor.col );
      wrefresh( stdscr ); alarm( 1 );
      }
    }
  }


int Screen::kbhit() { return read_key( false, false ); }


// Wait for a keystroke.
// The clock only blinks when waiting here for a keystroke.
// The screen is also only refreshed here (and in clock_handler).
// This function is the real "main loop" of the program.
//
int Screen::wait_kbhit( const int new_clock_limit, const bool get_key,
                        const bool force_refresh )
  {
  if( force_refresh || kbhit() < 0 )
    {
    if( new_clock_limit < 0 )
      move_to( Window_vector::curwin().absolute_cursor() );

    clock_limit = ( new_clock_limit >= 0 ) ? new_clock_limit : _height;
    if( clock_limit > 0 ) alarm( 1 );
    wrefresh( stdscr );
    }

  int key = read_key( true, get_key );
  alarm( 0 );
  if( key < 0 ) key = -1;
  return key;
  }


// 'history.back()' contains the default result (may be empty), and
// stores the result (if any).
// Returns the size of the result string, 0 if none, -1 if aborted.
//
int Screen::get_string( const std::string & prompt,
                        std::vector< std::string > & history,
                        const int abort_key,
                        const bool allow_copy, const bool usetab )
  {
  const int line = _height - 1, lines = 1;
  save_lines_and_cursor( line, lines );

  const std::string original( history.back() );
  remove_duplicates( history );
  std::string s( prompt );
  s += history.back();
  unsigned int scol = s.size(), idx = history.size() - 1;
  int key = 0;
  bool modified = false;
  while( key >= 0 )
    {
    int pos = scol; pos -= ( _width - 1 ); if( pos < 0 ) pos = 0;
    out_raw_string( line, 0, s, _width, pos );
    wmove( stdscr, line, scol - pos );
    do key = wait_kbhit( line ); while( key < 0 );
    if( key == abort_key ) key = 3;
    switch( key )
      {
      case   3 : key = -1; break;				// ^C
      case '\t': if( usetab )
                   {
                   const int namelen = scol - prompt.size();
                   std::string name( s, prompt.size(), namelen );
                   if( !Menu::file_menu( prompt, name ) ) { key = -1; break; }
                   scol += name.size() - namelen; modified = true;
                   s.erase( prompt.size(), namelen );
                   if( s.size() > prompt.size() && name.size() &&
                       s[prompt.size()] == '/' && name[name.size()-1] == '/' )
                     name.erase( name.size() - 1 );
                   s.insert( prompt.size(), name );
                   }
                 break;
      case '\r': {
                 std::string name( s, prompt.size(), s.size() );
                 if( !usetab || !name.size() )
                   { key = -2; history.back() = name; }
                 else if( Menu::test_slash_filename( name ) )
                   { if( !Menu::is_regular( name ) ) key = -1;
                     else { key = -2; history.back() = name; } }
                 }
                 break;
      case  25 : if( s.size() > prompt.size() )			// ^Y
                   { s.resize( prompt.size() ); scol = s.size();
                     modified = true; }
                 break;
      case   8 :						// ^H
      case KEY_F(1) : Menu::help_menu( 8, KEY_F(1) ); break;
      case KEY_F(9) : if( allow_copy && Block::valid() )
                        {
                        std::string tmp;
                        Block::bufferp()->to_string( Block::begin(), Block::end(), tmp );
                        ISO_8859::escapize( tmp );
                        if( tmp.size() )
                          { s.insert( scol, tmp ); scol += tmp.size(); modified = true; }
                        }
                      break;
      case KEY_DC   : if( scol < s.size() )
                        { s.erase( scol, 1 ); modified = true; } break;
      case KEY_BACKSPACE: if( scol > prompt.size() )
                            { s.erase( --scol, 1 ); modified = true; } break;
      case KEY_HOME : scol = prompt.size(); break;
      case KEY_END  : scol = s.size(); break;
      case KEY_LEFT : if( scol > prompt.size() ) { --scol; } break;
      case KEY_RIGHT: if( scol < s.size() ) { ++scol; } break;
      case KEY_PPAGE:
      case KEY_UP   : if( idx > 0 )
        {
        if( modified )
          {
          modified = false;
          if( s.size() > prompt.size() )
            history[idx].assign( s, prompt.size(), s.size() );
          else if( idx == history.size() - 1 ) history[idx].clear();
          else history.erase( history.begin() + idx );
          }
        if( key == KEY_UP ) --idx; else idx = 0;
        s.erase( prompt.size(), s.size() ); s += history[idx]; scol = s.size();
        } break;
      case KEY_NPAGE:
      case KEY_DOWN : if( idx + 1 < history.size() )
        {
        if( modified )
          {
          modified = false;
          if( s.size() > prompt.size() )
            history[idx].assign( s, prompt.size(), s.size() );
          else { history.erase( history.begin() + idx ); --idx; }
          }
        if( key == KEY_DOWN ) ++idx; else idx = history.size() - 1;
        s.erase( prompt.size(), s.size() ); s += history[idx]; scol = s.size();
        } break;
      default: if( key < 256 && ISO_8859::isprint( key ) )
                 { s.insert( scol++, 1, key ); modified = true; }
      }
    }

  restore_lines_and_cursor();
  if( original.size() && original != history.back() )
    history.insert( history.end() - 1, original );
  if( key != -1 ) remove_duplicates( history );
  if( ( key == -1 && !original.size() ) || !history.back().size() )
    { history.pop_back(); if( key != -1 ) return 0; }
  if( key == -1 ) return -1;
  return history.back().size();
  }


int Screen::height() throw() { return _height; }

int Screen::width() throw() { return _width; }

int Screen::max_windows() throw() { return _height / Window::min_height; }


void Screen::move_to( const Point & cursor ) throw()
  { wmove( stdscr, cursor.line, cursor.col ); }


void Screen::save_cursor( Point & cursor ) throw()
  { getyx( stdscr, cursor.line, cursor.col ); }


void Screen::save_lines_and_cursor( const int line, const int lines )
  {
  lines_and_cursor_vector.push_back( Lines_and_cursor( line, lines ) );
  Lines_and_cursor & lc = lines_and_cursor_vector.back();

  save_cursor( lc.cursor );
  for( int y = line; y < line + lines; ++y )
    for( int x = 0; x < _width; ++x )
      lc.buf.push_back( mvinch( y, x ) );
  }


void Screen::restore_lines_and_cursor() throw()
  {
  const Lines_and_cursor & lc = lines_and_cursor_vector.back();

  for( int y = lc.line, i = 0; y < lc.line + lc.lines; ++y )
    for( int x = 0; x < _width; ++x )
      { wmove( stdscr, y, x ); waddch( stdscr, lc.buf[i++] ); }
  wmove( stdscr, lc.cursor.line, lc.cursor.col );
  lines_and_cursor_vector.pop_back();
  }


void Screen::out_buffer_line( const Buffer & buffer, Point c, const int line ) throw()
  {
  wmove( stdscr, line, 0 );
  if( c.line < 0 || c.line >= buffer.lines() )
    { rep_addch( _width, ' ' ); return; }
  Point pc, p = buffer.to_pointer( c, pc );
  if( pc.col < c.col ) pc.col = c.col;		// landed in a tab

  for( int i = 0; i < _width; ++i, ++c.col, ++p.col )
    {
    int ch = buffer[p];
    if( ch < 0 || ( p == buffer.eol( p ) && ch == '\n' ) )
      { rep_addch( _width - i, ' ' ); break; }
    int block_attr = Block::in_block( buffer, p ) ? A_REVERSE : 0;
    if( ch == '\t' )
      {
      ch = ' ';
      while( i + 1 < _width && c.col % 8 != 7 )
        { waddch( stdscr, ch | block_attr ); ++i; ++c.col; }
      }
    else if( ch < 32 ) ch = ( ch + '@' ) | A_UNDERLINE;
    else if( ch >= 127 && ch < 160 )
      ch = ( ch + '@' - 128 ) | A_REVERSE | A_UNDERLINE;
    waddch( stdscr, ch | block_attr );
    }
  }


bool Screen::out_line( const std::string & s, const int line, const bool shift,
                       const char mode ) throw()
  {
  if( line < 0 || line >= _height ) return false;
  int pos = s.size(); pos -= ( _width - 1 );
  if( !shift || pos < 0 ) pos = 0;
  if( !mode ) out_string( line, 0, s, _width, pos );
  else
    {
    chtype atr = A_NORMAL;
    if( mode == 'b' ) atr = A_BOLD;
    else if( mode == 'i' ) atr = A_REVERSE;
    else if( mode == 'u' ) atr = A_UNDERLINE;
    out_raw_string( line, 0, s, _width, pos, atr );
    }
  return true;
  }


bool Screen::out_menu_line( const std::string & s, const int line,
                            const int hbegin, const int hend ) throw()
  {
  if( line < 0 || line >= _height ) return false;
  if( hbegin >= 0 && hbegin < hend && hend <= (int)s.size() )
    {
    if( hbegin > 0 )
      out_raw_string( line, 0, s, hbegin, 0, A_NORMAL );
    if( hbegin < _width )
      out_raw_string( line, hbegin, s, hend - hbegin, hbegin, A_REVERSE );
    if( hend < _width )
      out_raw_string( line, hend, s, _width, hend, A_NORMAL );
    }
  else out_raw_string( line, 0, s, _width, 0, A_NORMAL );
  return true;
  }


void Screen::repaint( const Buffer * bufp ) throw()
  {
  for( int i = 0; i < Window_vector::windows(); ++i )
    {
    Window & w = Window_vector::window( i );
    if( !bufp || bufp == &w.buffer() )
      {
      if( bufp == &w.buffer() ) w.update_points( w.pointer(), false );
      w.repaint();
      }
    }
  }


int Screen::show_message( const std::string & s, const bool get_key,
                          const bool take_cursor )
  {
  const int line = _height - 1;

  save_lines_and_cursor( line, 1 );

  out_string( line, 0, s, _width );
  wmove( stdscr, line, std::min( (int)s.size(), _width - 1 ) );
  int key;
  do key = wait_kbhit( take_cursor ? line : -1, get_key ); while( key < 0 );

  restore_lines_and_cursor();
  return key;
  }


void Screen::show_status_lines( const Buffer * bufp ) throw()
  {
  for( int i = 0; i < Window_vector::windows(); ++i )
    if( !bufp || bufp == &Window_vector::window( i ).buffer() )
      Window_vector::window( i ).show_status_line();
  }


Screen::Keys Screen::convert_key( const int key ) throw()
  {
  switch( key )
    {
    case KEY_UP   : return key_up;
    case KEY_DOWN : return key_down;
    case KEY_LEFT : return key_left;
    case KEY_RIGHT: return key_right;
    case KEY_NPAGE: return key_npage;
    case KEY_PPAGE: return key_ppage;
    case KEY_HOME : return key_home;
    case KEY_END  : return key_end;
    default: return key_invalid;
    }
  }


int Screen::user_loop( const std::string & version_msg ) throw()
  {
  show_message( version_msg );
  int retval = -1;
  bool force_refresh = false;
  while( true )
    {
    const int key = wait_kbhit( -1, true, force_refresh );
    if( key < 0 ) continue;
    force_refresh = false;
    { Window & w = Window_vector::curwin();
    switch( key )
      {
      case  0: Block::toggle_marking( w.buffer(), w.pointer() ); break;	// ^SPACE
      case  2: Window_vector::reformat(); break;		// ^B
      case  3: if( !Window_vector::close( true ) ) retval = 0; break;	// ^C
      case  6: Window_vector::search( key ); break;		// ^F
      case  7: Window_vector::search( key, true ); break;	// ^G
      case 11: control_k_loop( key ); break;			// ^K
      case 15: control_o_loop( key ); break;			// ^O
      case 16: user_control_char(); break;			// ^P
      case 17: retval = control_q_loop( key ); break;		// ^Q
      case 19: control_s_loop( key, version_msg ); break;	// ^S
      case 23: Window_vector::search_word( key ); break;	// ^W
      case 24: if( !Window_vector::close( false ) ) retval = 0; break;	// ^X
      case 25: Window_vector::delete_line(); break;		// ^Y
      case 27: alt_key_loop( key ); break;			// Alt or Esc
      case  8:							// ^H
      case KEY_F(1) : Menu::help_menu( 8, KEY_F(1) ); break;
      case KEY_F(2) : Window_vector::save_file( key ); break;
      case KEY_F(3) : Window_vector::load_file( key ); break;
      case KEY_F(4) : if( !Window_vector::load( -1 ) ) retval = 3; break;
      case KEY_F(5) : Window_vector::prev(); break;
      case KEY_F(6) : Window_vector::next(); break;
      case KEY_F(7) : Window_vector::undo(); break;
      case KEY_F(8) : Window_vector::redo(); break;
      case KEY_F(9) : Window_vector::copy_block(); break;
      case KEY_F(10): Menu::options_menu( w.buffer(), key ); break;
      case KEY_F(11): Window_vector::bufhandle_menu( key ); break;
      case KEY_F(12): Window_vector::last_visited(); break;
      case KEY_BACKSPACE: Window_vector::delete_char( true ); break;
      case KEY_DC   : Window_vector::delete_char(); break;
      case KEY_IC   : w.buffer().options.overwrite = !w.buffer().options.overwrite;
                      show_status_lines( &w.buffer() ); break;
      case KEY_HOME : w.goto_home(); break;
      case KEY_END  : w.goto_eol(); break;
      case KEY_PPAGE: w.move_page( false ); break;
      case KEY_NPAGE: w.move_page( true ); break;
      case KEY_UP   : w.move_vertical( -1 ); break;
      case KEY_DOWN : w.move_vertical( +1 ); break;
      case KEY_LEFT : w.goto_pprev(); break;
      case KEY_RIGHT: w.goto_pnext(); break;
      default       : if( key < 256 )
                        { if( key == '\r' || key == '\n' ) force_refresh = true;
                          Window_vector::add_char( key ); }
      } }
    if( retval >= 0 ) break;
    { Window & w = Window_vector::curwin();
    if( Block::follow_marking( w.buffer(), w.pointer() ) )
      repaint( &w.buffer() ); }
    }
  wmove( stdscr, _height - 1, 0 );	// clear bottom line on exit
  wclrtoeol( stdscr ); wrefresh( stdscr );
  return retval;
  }
