//  This may look like C code, but it is really -*- C++ -*-

//  ------------------------------------------------------------------
//  The Goldware Library
//  Copyright (C) 1990-1999 Odinn Sorensen
//  Copyright (C) 1999-2000 Alexander S. Aganichev
//  Copyright (C) 2000 Jacobo Tarrio
//  ------------------------------------------------------------------
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Library General Public
//  License as published by the Free Software Foundation; either
//  version 2 of the License, or (at your option) any later version.
//
//  This library 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
//  Library General Public License for more details.
//
//  You should have received a copy of the GNU Library General Public
//  License along with this program; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
//  MA 02111-1307, USA
//  ------------------------------------------------------------------
//  $Id$
//  ------------------------------------------------------------------
//  GCUI: Golded+ Character-oriented User Interface.
//  Windowing kernel.
//  Based on CXL by Mike Smedley.
//  ------------------------------------------------------------------

#include <cstdio>
#include <cstdarg>
#include <cstdlib>
#include <cstring>
#include <gmemdbg.h>
#include <gutlmisc.h>
#include <gwinall.h>
#include <gkbdcode.h>


//  ------------------------------------------------------------------
//  Local optimizations

#define GOLD_INLINE inline
#define GOLD_WCHK


//  ------------------------------------------------------------------

static GOLD_INLINE int _wchkrow(int wrow) {

  return ((wrow<0) or (wrow>((gwin.active->erow-gwin.active->border)-(gwin.active->srow+gwin.active->border))));
}

static GOLD_INLINE int _wchkcol(int wcol) {

  return ((wcol<0) or (wcol>((gwin.active->ecol-gwin.active->border)-(gwin.active->scol+gwin.active->border))));
}

static GOLD_INLINE int _wchkcoord(int wrow, int wcol) {

  return (_wchkrow(wrow) or _wchkcol(wcol));
}


//  ------------------------------------------------------------------
//  Checks validity of given window row coordinate

int wchkrow(int wrow) {

  return _wchkrow(wrow);
}


//  ------------------------------------------------------------------
//  Checks validity of given window column coordinate

int wchkcol(int wcol) {

  return _wchkcol(wcol);
}


//  ------------------------------------------------------------------
//  Checks validity of given window coordinates

int wchkcoord(int wrow, int wcol) {

  return _wchkcoord(wrow, wcol);
}


//  ------------------------------------------------------------------
//  Checks for valid window box coordinates

int wchkbox(int wsrow, int wscol, int werow, int wecol) {

  return (_wchkcoord(wsrow,wscol) or _wchkcoord(werow,wecol) or (wsrow>werow) or (wscol>wecol));
}


//  ------------------------------------------------------------------
//  Sets window cursor coordinates

int wgotoxy(int wrow, int wcol) {

  // check for valid cursor coordinates
  #ifdef GOLD_WCHK
  if(wchkcoord(wrow,wcol))
    return gwin.werrno=W_INVCOORD;
  #endif

  // calculate effective cursor coordinates and update window record
  const int &row = gwin.active->row    = gwin.active->srow + wrow + gwin.active->border;
  const int &col = gwin.active->column = gwin.active->scol + wcol + gwin.active->border;

  // set cursor location
  vposset(row,col);

  // return with no error
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Opens a window and makes it active

int wopen(int srow, int scol, int erow, int ecol, int btype, vattr battr, vattr wattr, vattr sbattr, vattr loattr) {

  // check for valid box type
  if(btype<0 or btype>7) {
    gwin.werrno=W_INVBTYPE;
    return 0;
  }

  if (ecol >= gvid->numcols)
    ecol = gvid->numcols - 1;

  // see if window is to have a border
  int border = (btype==5) ? NO : YES;

  // check for valid coordinates
  if(srow>(erow-border) or scol>(ecol-border)) {
    gwin.werrno=W_INVCOORD;
    return 0;
  }

  // allocate memory for new record
  _wrec_t* wrec = (_wrec_t*)throw_xmalloc(sizeof(_wrec_t));
  if(wrec==NULL) {
    gwin.werrno=W_ALLOCERR;
    return 0;
  }

  // save affected area of screen
  vsavebuf* wbuf = vsave(srow,scol,erow,ecol);
  if(wbuf==NULL) {
    throw_xrelease(wrec);
    gwin.werrno=W_ALLOCERR;
    return 0;
  }

  // add new record to linked list
  if(gwin.active!=NULL)
    gwin.active->next=wrec;
  wrec->prev=gwin.active;
  wrec->next=NULL;
  gwin.active=wrec;

  // draw and fill text box on screen
  switch(gwin.style) {
    case STYLE_NORMAL:
      if(border)
        vbox(srow,scol,erow,ecol,btype,battr,loattr);
      vfill(srow+border,scol+border,erow-border,ecol-border,gwin.fillch,wattr);
      break;
  }

  // increment window handle counter
  gwin.handle++;

  // save window info in window record
  gwin.active->wbuf     = wbuf;
  gwin.active->whandle  = gwin.handle;
  gwin.active->srow     = srow;
  gwin.active->scol     = scol;
  gwin.active->erow     = erow;
  gwin.active->ecol     = ecol;
  gwin.active->btype    = btype;
  gwin.active->wattr    = wattr;
  gwin.active->battr    = battr;
  gwin.active->loattr   = loattr;
  gwin.active->sbattr   = sbattr;
  gwin.active->border   = border;
  gwin.active->row      = srow+border;
  gwin.active->column   = scol+border;
  gwin.active->attr     = wattr;
  gwin.active->title    = NULL;
  gwin.active->tpos     = 0;
  gwin.active->help     = 0;
  gwin.active->form     = NULL;
  gwin.active->wsbuf    = NULL;

  // increment total number of open windows
  gwin.total++;

  // initialize cursor location to window row 0 column 0
  wgotoxy(0,0);

  // return normally
  gwin.werrno=W_NOERROR;
  return gwin.handle;
}


//  ------------------------------------------------------------------
//  Closes a window

int wclose() {

  // check for active window
  if(!gwin.total or !gwin.active)
    return(gwin.werrno=W_NOACTIVE);

  // if window has a shadow, close shadow first
  if(gwin.active->wsbuf!=NULL)
    wshadoff();

  // restore contents of and free memory held by window
  vrestore(gwin.active->wbuf);
  throw_xrelease(gwin.active->wbuf);

  // decrement total number of open windows
  gwin.total--;

  // free memory held by window's record and update linked list
  _wrec_t *wrec = gwin.active->prev;
  throw_xrelease(gwin.active);
  gwin.active=wrec;
  if(gwin.active!=NULL)
    gwin.active->next=NULL;

  // update cursor location and help category
  if(gwin.active!=NULL) {
    vposset(gwin.active->row,gwin.active->column);
    if(gwin.active->help)
      gwin.help=gwin.active->help;
  }

  // return normally
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Closes all open windows

int wcloseall() {

  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  while(gwin.total)
    if(wclose())
      return gwin.werrno;

  // close hidden windows too
  _wrec_t* prev;
  while(gwin.hidden!=NULL) {
    prev = gwin.hidden->prev;
    throw_xfree(gwin.hidden->wbuf);
    throw_xfree(gwin.hidden);
    gwin.hidden = prev;
  }

  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Gives active window a shadow

int wshadow(vattr attr) {

  // check for active window
  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // see if window already has a shadow
  if(gwin.active->wsbuf!=NULL)
    return gwin.werrno=W_NOERROR;

  // get window coordinates from the window's record
  const int &srow = gwin.active->srow;
  const int &scol = gwin.active->scol;
  const int &erow = gwin.active->erow;
  const int &ecol = gwin.active->ecol;

  // allocate buffer to hold shadow's contents
  vatch* wsbuf = (vatch*)throw_xmalloc((((erow-srow)*sizeof(vatch))+ecol-scol+1)*sizeof(vatch));
  
  if(wsbuf == NULL)
    return gwin.werrno=W_ALLOCERR;

  // start at upper right corner of shadow and work down
  int crow = srow+1;
  int ccol = ecol+1;
  vatch* q = wsbuf;

  // draw shadow to right of window
  while(crow<=erow) {

    // read current screen characters/attributes and save in shadow's buffer
    vatch tmp[2];
    *q = vgetw(crow, ccol);
#if defined(__USE_NCURSES__) || !defined(__UNIX__)
    tmp[0] = vsattr(*q, attr);
#else
    tmp[0] = vsattr(' ', attr);
#endif
    q++;
    *q = vgetw(crow, ccol + 1);
#if defined(__USE_NCURSES__) || !defined(__UNIX__)
    tmp[1] = vsattr(*q, attr);
#else
    tmp[1] = vsattr(' ', attr);
#endif
    q++;

    // write characters back to screen using shadow's attribute
    vputws(crow++, ccol, tmp, 2);
  }

  // start at lower left corner of shadow and work right
  crow = erow+1;
  ccol = scol+2;
  int stop = ecol+2;
  int len = stop - ccol + 1;
  vatch* wptr = (vatch*)gvid->bufwrd;

  // draw bottom shadow
  while(ccol<=stop) {

    // read attribs/chars and store in buffers
    *q = vgetw(crow, ccol++);
#if defined(__USE_NCURSES__) || !defined(__UNIX__)
    *wptr++ = vsattr(*q, attr);
#else
    *wptr++ = vsattr(' ', attr);
#endif
    q++;
  }

  // display complete buffer
  vputws(crow, scol+2, gvid->bufwrd, len);

  // save info in window's record
  gwin.active->wsbuf  = wsbuf;
  gwin.active->wsattr = attr;

  // reset cursor
  vposset(gwin.active->row,gwin.active->column);

  // return with no error
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Removes shadow from active window

int wshadoff() {

  // check for active window
  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // if window doesn't have a shadow, ignore request
  if(gwin.active->wsbuf==NULL)
    return gwin.werrno=W_NOERROR;

  // get window coordinates from the window's record
  const int &srow = gwin.active->srow;
  const int &scol = gwin.active->scol;
  const int &erow = gwin.active->erow;
  const int &ecol = gwin.active->ecol;

  // start at upper right corner of shadow and work down
  int crow = srow+1;
  int ccol = ecol+1;
  vatch* q = gwin.active->wsbuf;

  // delete shadow to right of window
  while(crow<=erow) {
    vputw(crow,   ccol,   *q++);
    vputw(crow++, ccol+1, *q++);
  }

  // start at lower left corner of shadow and work right
  crow = erow+1;
  ccol = scol+2;
  int stop = ecol+2;

  // delete bottom shadow
  while(ccol<=stop)
    vputw(crow,ccol++,*q++);

  // free memory held by shadow
  throw_xrelease(gwin.active->wsbuf);

  // update window's record
  gwin.active->wsattr = WHITE_|_WHITE;

  // return with no error
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Scrolls the active window up or down

int wscroll(int count, int direction) {

  // check for window border
  const int &border = gwin.active->border;

  vscroll(
    gwin.active->srow + border,
    gwin.active->scol + border,
    gwin.active->erow - border,
    gwin.active->ecol - ((border or (gwin.active->sbattr != DEFATTR)) ? 1 : 0),
    gwin.active->wattr,
    direction == SUP ? count : -count
  );

  // return with no error
  return gwin.werrno = W_NOERROR;
}


//  ------------------------------------------------------------------
//  Scrolls a region of the active window up or down

int wscrollbox(int wsrow, int wscol, int werow, int wecol, int count, int direction) {

  // check for window border
  const int &border = gwin.active->border;

  vscroll(
    gwin.active->srow+wsrow+border,
    gwin.active->scol+wscol+border,
    gwin.active->srow+werow+border,
    gwin.active->scol+wecol+border,
    gwin.active->wattr,
    direction == SUP ? count : -count
  );

  // return with no error
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Displays a character inside active window

int wputc(char ch) {

  int cwcol;

  // check for active window
  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // get coordinates from window's record
  int crow = gwin.active->row;
  int ccol = gwin.active->column;
  const int &scol = gwin.active->scol;
  const int &border = gwin.active->border;

  // test the input character for control characters
  switch(ch) {
    case '\n':
      crow++;
    case '\r':
      ccol=scol+border;
      break;
    case '\b':
      if(ccol==(scol+border)) {
        ccol=gwin.active->ecol-border;
        crow--;
        if(crow<(gwin.active->srow+border))
          crow++;
      }
      else {
        ccol--;
      }
      break;
    case '\t':
      cwcol=ccol-border-scol;
      ccol+=(tabstop(cwcol,gwin.tabwidth)-cwcol);
      break;
    default:
      vputc(crow, ccol++, gwin.active->attr, ch);
  }

  // see if wrap-around is needed
  if(ccol > (gwin.active->ecol-border)) {
    ccol = scol+border;
    crow++;
  }

  // see if scroll is needed
  if(crow > (gwin.active->erow-border)) {
    wscroll(1,SUP);
    crow--;
  }

  // update window's record
  gwin.active->row=crow;
  gwin.active->column=ccol;

  // reset cursor position
  vposset(crow,ccol);

  // return normally
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Reads current cursor location inside window

int wreadcur(int* wrow, int* wcol) {

  // check for active window
  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // read effective cursor coordinates
  int row,col;
  vposget(&row,&col);

  // calculate window cursor coordinates
  const int &border = gwin.active->border;
  *wrow = row - gwin.active->srow - border;
  *wcol = col - gwin.active->scol - border;

  // return normally
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Display a character specified number of times

int wdupc(char ch, int count) {

  // check for active window
  if(!gwin.total)
    return(gwin.werrno=W_NOACTIVE);

  // display ch for count times
  while(count--)
    wputc(ch);

  // return with gwin.werrno set by wputc()
  return(gwin.werrno);
}


//  ------------------------------------------------------------------
//  Clears the active window in specified attribute

int wcclear(vattr attr) {

  // check for active window

  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // check for window border

  const int &border = gwin.active->border;

  vfill(
    gwin.active->srow+border,
    gwin.active->scol+border,
    gwin.active->erow-border,
    gwin.active->ecol-border,
    gwin.fillch,
    attr
  );

  // home the cursor

  wgotoxy(0,0);

  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Clears from cursor postion to end of window's line

int wclreol() {

  // check for active window
  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // clear to end of window's line
  const int &column = gwin.active->column;
  vputx(
    gwin.active->row,
    column,
    gwin.active->attr,
    gwin.fillch,
    gwin.active->ecol - gwin.active->border - column + 1
  );

  // return normally
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Clears from cursor postion to end of window

int wclreos() {

  int wrow, werow, wr, wc;

  // check for active window
  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // save current window row and column
  wreadcur(&wr, &wc);

  wrow = wr;
  werow = gwin.active->erow - gwin.active->srow - gwin.active->border;
  wclreol();
  wrow++;

  while(wrow <= werow) {
    wgotoxy(wrow,0);
    wclreol();
    wrow++;
  }

  // restore window row and column
  wgotoxy(wr,wc);

  // return normally
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  This function will process an Escape sequence when encountered

static const char* process_esc(const char* str)
{
  int wrow,wcol;

  const char *p = str;
  for(; *p==ESC; p++) {

    vattr attr = gwin.active->attr;

    switch(*(++p)) {

      case '+':   // increase text attribute
        wtextattr(++attr);
        break;

      case '-':   // decrease text attribute
        wtextattr(--attr);
        break;

      case 'A':   // change attribute
        wtextattr(*++p);
        break;

      case 'F':   // change foreground attribute
        wtextattr((int)((*++p & 0x07) | (attr & ~0x07)));
        break;

      case 'B':   // change background attribute
        wtextattr((int)((*++p & 0x70) | (attr & ~0x70)));
        break;

      case 'I':   // toggle intensity bit
        wtextattr((int)(attr ^ INTENSE));
        break;

      case 'L':   // toggle blinking bit
        wtextattr((int)(attr ^ BLINK));
        break;

      case 'X':   // reverse attribute
        wtextattr(revsattr(attr));
        break;

      case 'R':   // set cursor row
        wreadcur(&wrow,&wcol);
        wgotoxy(*++p,wcol);
        break;

      case 'C':   // set cursor column
        wreadcur(&wrow,&wcol);
        wgotoxy(wrow,*++p);
        break;

      case 'E':   // erase
        switch(*++p) {
          case 'W':   // erase window
            wclear();
            break;
          case 'S':   // erase to end of window
            wclreos();
            break;
          case 'L':   // erase to end of window's line
            wclreol();
            break;
        }
        break;

      case 'D':   // duplicate character
        {
          char ch = *++p;
          wdupc(ch,*++p);
        }
        break;

      default:
        p--;
    }
  }

  return --p;
}


//  ------------------------------------------------------------------
//  Displays a string inside active window

int wputs(const char* str) {

  int cwcol;
  const char* q;

  // get effective coordinates from window's record
  int &crow = gwin.active->row;
  int &ccol = gwin.active->column;
  const int &scol = gwin.active->scol;
  const int &border = gwin.active->border;

  // do while not end of string
  for(q=str; *q; q++) {

    // test the input character for control characters
    switch(*q) {
      case '\n':
        crow++;
      case '\r':
        ccol=scol+border;
        break;
      case '\b':
        if(ccol==(scol+border)) {
          ccol=gwin.active->ecol-border;
          crow--;
          if(crow<(gwin.active->srow+border))
            crow++;
        }
        else {
          ccol--;
        }
        break;
      case '\t':
        cwcol=ccol-border-scol;
        ccol+=(tabstop(cwcol,gwin.tabwidth)-cwcol);
        break;
      case ESC:
        q=process_esc(q);
        break;
      default:
        vputc(crow, ccol++, gwin.active->attr, *q);
    }

    // see if wrap-around is needed
    if(ccol > (gwin.active->ecol-border)) {
      ccol=scol+border;
      crow++;
    }

    // see if scroll is needed
    if(crow > (gwin.active->erow-border)) {
      wscroll(1,SUP);
      crow--;
    }
  }

  // reset cursor position
  vposset(crow,ccol);

  // return normally
  return(gwin.werrno=W_NOERROR);
}


//  ------------------------------------------------------------------
//  Displays a character inside active window

int wprintc(int wrow, int wcol, vattr atr, vchar chr) {

  // check for active window
  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // check for valid coordinates
  #ifdef GOLD_WCHK
  if(wchkcoord(wrow,wcol))
    return gwin.werrno=W_INVCOORD;
  #endif

  vputc(wrow+gwin.active->srow+gwin.active->border, wcol+gwin.active->scol+gwin.active->border, atr, chr);

  // return normally
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Outputs a formatted string to active window

int wprintf(const char* format, ...) {

  va_list argptr;
  char buf[255];

  // format string using specified parameters into buffer
  va_start(argptr,format);            // access argument list
  int result = vsprintf(buf,format,argptr);        // create string using argument list
  va_end(argptr);                     // end access of argument list

  // display the created string
  wputs(buf);

  return result;
}


//  ------------------------------------------------------------------
//  Print a formatted string at a specific position and attribute

int wprintfs(int wrow, int wcol, vattr attr, const char* format, ...) {

  va_list argptr;
  char buf[256];

  *buf = NUL;
  va_start(argptr, format);
  int result = vsprintf(buf, format, argptr);
  va_end(argptr);

  wprints(wrow, wcol, attr, buf);

  return result;
}


//  ------------------------------------------------------------------
//  Displays a string inside active window

int wprints(int wrow, int wcol, vattr attr, const char* str) {

  // check for active window
  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // check for valid coordinates
  #ifdef GOLD_WCHK
  if(wchkcoord(wrow,wcol))
    return gwin.werrno=W_INVCOORD;
  #endif

  const int &border = gwin.active->border;
  vputs(gwin.active->srow+wrow+border,gwin.active->scol+wcol+border,attr,str);
  return gwin.werrno=W_NOERROR;
}

int wprints_box(int wrow, int wcol, vattr attr, const char* str) {

  // check for active window
  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // check for valid coordinates
  #ifdef GOLD_WCHK
  if(wchkcoord(wrow,wcol))
    return gwin.werrno=W_INVCOORD;
  #endif

  const int &border = gwin.active->border;
  vputs_box(gwin.active->srow+wrow+border,gwin.active->scol+wcol+border,attr,str);
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Displays a string inside active window

int wprintvs(int wrow, int wcol, vattr attr, const vchar* str) {

  // check for active window
  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // check for valid coordinates
  #ifdef GOLD_WCHK
  if(wchkcoord(wrow,wcol))
    return gwin.werrno=W_INVCOORD;
  #endif

  const int &border = gwin.active->border;
  vputvs(gwin.active->srow+wrow+border,gwin.active->scol+wcol+border,attr,str);
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------

int wputx(int wrow, int wcol, vattr attr, vchar chr, uint len) {

  const int &border = gwin.active->border;
  vputx(gwin.active->srow+wrow+border,gwin.active->scol+wcol+border,attr,chr,len);
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------

int wputy(int wrow, int wcol, vattr attr, vchar chr, uint len) {

  const int &border = gwin.active->border;
  vputy(gwin.active->srow+wrow+border,gwin.active->scol+wcol+border,attr,chr,len);
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Displays a string inside active window

int wprintns(int wrow, int wcol, vattr attr,  const std::string &str, uint len, vchar fill, vattr fill_attr)
{
  char* istr = throw_xstrdup(str.c_str());
  char* ostr = istr;
  char och = *ostr;
  uint olen = strlen(istr);
  if(len < olen) {
    ostr += len;
    och = *ostr;
    *ostr = NUL;
  }
  int retval = wprints(wrow, wcol, attr, istr);
  if(len < olen)
    *ostr = och;
  else if(len > olen)
    retval = wputx(wrow, wcol+olen, (fill_attr != DEFATTR) ? fill_attr : attr, fill, len-olen);
  throw_xfree(istr);
  return retval;
}


//  ------------------------------------------------------------------
//  Displays attrib/char buffer inside active window

int wprintws(int wrow, int wcol, vatch* buf, uint len) {

  // check for active window
  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // check for valid coordinates
  #ifdef GOLD_WCHK
  if(wchkcoord(wrow,wcol))
    return gwin.werrno=W_INVCOORD;
  #endif

  // see if window has border
  const int &border = gwin.active->border;

  // calculate effective coordinates
  int row = gwin.active->srow+wrow+border;
  int col = gwin.active->scol+wcol+border;

  // display buffer
  vputws(row, col, buf, len);

  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Returns address of record of given window handle

_wrec_t* wfindrec(int whandle) {

  _wrec_t *wrec;

  // scan through linked list for record belonging to requested handle

  wrec = gwin.active;
  while(wrec) {
    if(whandle==wrec->whandle)
      break;
    wrec=wrec->prev;
  }

  if(wrec == NULL) {

    // Search through the hidden windows

    wrec = gwin.hidden;
    while(wrec) {
      if(whandle==wrec->whandle)
        break;
      wrec=wrec->prev;
    }
  }

  // return address of found record
  return wrec;
}


//  ------------------------------------------------------------------
//  Hides active window

int whide() {

  vsavebuf  *p;
  vattr     shattr;
  _wrec_t   *temp;

  // check for active window
  if(!gwin.total)
    return(gwin.werrno=W_NOACTIVE);

  // save active window
  p = vsave(gwin.active->srow,gwin.active->scol,gwin.active->erow,gwin.active->ecol);

  // check for/close window's shadow
  if(gwin.active->wsbuf!=NULL) {
    shattr = gwin.active->wsattr;
    wshadoff();
    gwin.active->wsattr = shattr;
  }
  else {
    gwin.active->wsattr = DEFATTR;
  }

  // restore contents of active window's buffer
  vrestore(gwin.active->wbuf);
  throw_xfree(gwin.active->wbuf);
  gwin.active->wbuf = p;

  // update visible window record linked list
  temp = gwin.active;
  gwin.active = gwin.active->prev;
  if(gwin.active)
    gwin.active->next = NULL;
  gwin.total--;

  // update hidden window record linked list
  if(gwin.hidden)
    gwin.hidden->next = temp;
  temp->prev = gwin.hidden;
  temp->next = NULL;
  gwin.hidden = temp;

  // update cursor location and help category
  if(gwin.active) {
    vposset(gwin.active->row,gwin.active->column);
    if(gwin.active->help)
      gwin.help = gwin.active->help;
  }

  // return normally
  return(gwin.werrno=W_NOERROR);
}


//  ------------------------------------------------------------------
//  Unhides a previously hidden window

int wunhide(int whandle) {

  vsavebuf* p;
  _wrec_t* found;

  // check pointer to hidden window linked list ; must not be NULL
  if(gwin.hidden==NULL)
    return gwin.werrno=W_NOHIDDEN;

  // check to see if input window handle == 0.  if so, then
  // that means to unhide the most recently hidden window.
  if(!whandle)
    whandle=gwin.hidden->whandle;

  // scan through linked list for record belonging to requested handle
  found=gwin.hidden;
  while(found!=NULL) {
    if(whandle==found->whandle)
      break;
    found=found->prev;
  }

  // was handle found in hidden window record linked list?
  if(found==NULL) {

    // see if handle is in visible window record linked list
    if(wfindrec(whandle)==NULL)
      return gwin.werrno=W_NOTFOUND;
    else
      return gwin.werrno=W_NOTHIDD;
  }

  // save area of screen where window is to unhide at
  if((p=vsave(found->srow,found->scol,found->erow,found->ecol))==NULL)
    return gwin.werrno=W_ALLOCERR;

  // restore contents of hidden window back to screen
  vrestore(found->wbuf);
  throw_xfree(found->wbuf);
  found->wbuf=p;

  // update hidden window record linked list
  if(found->prev!=NULL)
    found->prev->next=found->next;
  if(found->next==NULL)
    gwin.hidden=found->prev;
  else
    found->next->prev=found->prev;

  // update visible window record linked list
  if(gwin.active!=NULL)
    gwin.active->next=found;
  found->prev=gwin.active;
  found->next=NULL;
  gwin.active=found;
  gwin.total++;

  // if window had a shadow before hiding, give it one again
  if(gwin.active->wsattr != DEFATTR)
    wshadow(gwin.active->wsattr);

  // update help category
  if(gwin.active->help)
    gwin.help=gwin.active->help;

  // reset cursor
  vposset(gwin.active->row,gwin.active->column);

  // return normally
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Unlinks and frees a window from memory, but does not manipulate
//  the screen at all

int wunlink(int w) {

  _wrec_t *found, *prev, *next;

  // check to see if input window handle == 0.  if
  // so, then that means to unlink the active window.
  if(!w)
    w=gwin.active->whandle;

  // find address of window record for given window handle
  if((found=wfindrec(w))==NULL)
    return gwin.werrno=W_NOTFOUND;

  // free memory held by shadow's buffer (if shadow exists)
  if(found->wsbuf!=NULL)
    throw_xrelease(found->wsbuf);

  // free memory held by window's buffer
  throw_xrelease(found->wbuf);

  // decrement total number of open windows
  gwin.total--;

  // re-link list pointers around window record to remove
  prev=found->prev;
  next=found->next;
  if(prev!=NULL)
    prev->next=next;
  if(next!=NULL)
    next->prev=prev;

  // free memory held by window record
  throw_xfree(found);

  // see if active window has changed
  if(next==NULL) {
    if(prev!=NULL) {
      gwin.active=prev;
      if(gwin.active->help)
        gwin.help=gwin.active->help;
    }
  }

  // return normally
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Local variables

static _wrec_t *__curr, *__found;
static int    __crow, __ccol;
static vattr  __gattr;
static const char* __p;


//  ------------------------------------------------------------------
//  This function dectects if given window is blocking the current
//  window or its shadow at specified coordinates

static GOLD_INLINE int window_blocking() {

  return (__crow>=__curr->srow and __crow<=__curr->erow and __ccol>=__curr->scol and __ccol<=__curr->ecol) ? YES : NO;
}


//  ------------------------------------------------------------------
//  This function detects if a given window's bottom shadow is
//  blocking the current window or its shadow at specified coordinates

static GOLD_INLINE int bshadow_blocking() {

  if(__crow==(__curr->erow+1))
    if((__ccol>=(__curr->scol+2)) and (__ccol<=(__curr->ecol+2)))
      return YES;
    else
      return NO;

  return NO;
}


//  ------------------------------------------------------------------
//  This function detects if a given window's right shadow is blocking
//  the current window or its shadow at specified coordinates

static GOLD_INLINE int rshadow_blocking() {

  if(__ccol==(__curr->ecol+1) or __ccol==(__curr->ecol+2))
    if((__crow>=(__curr->srow+1)) and (__crow<=__curr->erow))
      return YES;
    else
      return NO;

  return NO;
}


//  ------------------------------------------------------------------

static GOLD_INLINE vatch* calc_window(_wrec_t *wrec) {

  return wrec->wbuf->data+((__crow-wrec->srow)*(wrec->ecol-wrec->scol+1))+(__ccol-wrec->scol);
}


//  ------------------------------------------------------------------

static GOLD_INLINE vatch* calc_bshadow(_wrec_t *wrec) {

  return wrec->wsbuf+((((__crow-wrec->srow-1)*2)+(__ccol-wrec->scol-2)));
}


//  ------------------------------------------------------------------

static GOLD_INLINE vatch* calc_rshadow(_wrec_t *wrec) {

  return wrec->wsbuf+((((__crow-wrec->srow-1)*2)+(__ccol-wrec->ecol-1)));
}


//  ------------------------------------------------------------------
//  This function will exchange the contents of the applicable buffers

static void swap_contents(vatch* pfound, vatch* pcurr, int shadow) {

  register _wrec_t *wptr;
  register vatch temp, chat;

  // display character from current position in window to
  // activate on the screen.  if character is part of a
  // shadow, reflect the character on the screen.

  temp = vgetw(__crow, __ccol);

  if(shadow&2)
    *pcurr = vschar(*pcurr, vgchar(temp));
  chat = ((vgattr(temp) & BLINK) and shadow) ? vsattr(*pcurr, vgattr(*pcurr) | BLINK) : *pcurr;
  vputw(__crow, __ccol, chat);

  // let window position directly above position
  // to activate have the character that it holds

  *pcurr = *pfound;

  // if current character position to activate will
  // activate over a shadow in another window

  if(shadow&1) {

    // resolve all shadows upwards

    wptr = __curr;
    chat = vsattr(*pfound, __curr->wsattr);

    for(__curr=__curr->next;__curr!=NULL;__curr=__curr->next) {

      if(window_blocking()) {
        *(calc_window(__curr)) = chat;
        chat = temp;
        break;
      }
      else {
        if(bshadow_blocking())
          *(calc_bshadow(__curr)) = chat;
        else {
          if(rshadow_blocking())
            *(calc_rshadow(__curr)) = chat;
        }
      }
    }

    temp = chat;
    __curr = wptr;
  }

  // let character position activated hold character
  // that was on the screen in the same position

  *pfound = temp;
}


//  ------------------------------------------------------------------

int wactiv(int whandle) {

  register int startcol, stopcol;
  _wrec_t *prev, *next;

  // check for active window

  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // if window is already active, ignore request

  if(whandle==gwin.active->whandle)
    return gwin.werrno=W_NOERROR;

  // find address of window's record

  __found=wfindrec(whandle);
  if(__found==NULL)
    return gwin.werrno=W_NOTFOUND;

  // check every character position in window to activate

  for(__crow=__found->srow;__crow<=__found->erow;__crow++) {
    for(__ccol=__found->scol;__ccol<=__found->ecol;__ccol++) {

      // check all window records "above" window to activate

      for(__curr=__found->next;__curr!=NULL;__curr=__curr->next) {

        // see if current position in window to activate
        // is blocked by same position in test window

        if(window_blocking()) {

          // calculate buffer addresses and swap contents

          swap_contents(calc_window(__found),calc_window(__curr),0);
          break;
        }

        // see if test window has a shadow

        if(__curr->wsbuf!=NULL) {

          // see if shadow to the right of test window is
          // blocking the current position of window to activate

          if(rshadow_blocking()) {
            swap_contents(calc_window(__found),calc_rshadow(__curr),1);
            break;
          }

          // see if shadow to the bottom of test window is
          // blocking the current position of window to activate

          if(bshadow_blocking()) {
            swap_contents(calc_window(__found),calc_bshadow(__curr),1);
            break;
          }
        }
      }
    }
  }

  // if window to activate has a shadow, then check
  // every character position in the shadow to see
  // if it is blocked by another window or window shadow

  if(__found->wsbuf!=NULL) {

    // search the right shadow of window to activiate

    startcol=__found->ecol+1;
    stopcol=startcol+1;
    for(__crow=__found->srow+1;__crow<=__found->erow;__crow++) {
      for(__ccol=startcol;__ccol<=stopcol;__ccol++) {

        // check all window records "above" shadow to activate

        for(__curr=__found->next;__curr!=NULL;__curr=__curr->next) {

          // see if current position in shadow to activate
          // is blocked by same position in current window

          if(window_blocking()) {

            // calculate buffer addresses and swap contents

            swap_contents(calc_rshadow(__found),calc_window(__curr),2);
            break;
          }

          // see if test window has a shadow

          if(__curr->wsbuf!=NULL) {

            // see if current position of window to activate is
            // blocked by the right shadow of the test window

            if(rshadow_blocking()) {
              swap_contents(calc_rshadow(__found),calc_rshadow(__curr),3);
              break;
            }

            // see if current position of window to activate is
            // blocked by the bottom shadow of the test window

            if(bshadow_blocking()) {
              swap_contents(calc_rshadow(__found),calc_bshadow(__curr),3);
              break;
            }
          }
        }
      }
    }

    // search bottom shadow

    startcol=__found->scol+2;
    stopcol=__found->ecol+2;
    __crow=__found->erow+1;
    for(__ccol=startcol;__ccol<=stopcol;__ccol++) {

      // check all window records "above" shadow to activate

      for(__curr=__found->next;__curr!=NULL;__curr=__curr->next) {

        // see if current position in shadow to activate
        // is blocked by same position in test window

        if(window_blocking()) {

          // calculate buffer addresses and swap contents

          swap_contents(calc_bshadow(__found),calc_window(__curr),2);
          break;
        }

        // see if test window has a shadow

        if(__curr->wsbuf!=NULL) {
          if(rshadow_blocking()) {
            swap_contents(calc_bshadow(__found),calc_rshadow(__curr),3);
            break;
          }
          if(bshadow_blocking()) {
            swap_contents(calc_bshadow(__found),calc_bshadow(__curr),3);
            break;
          }
        }
      }
    }
  }

  // re-link pointer to window record to be activated

  prev=__found->prev;
  next=__found->next;
  if(prev!=NULL)
    prev->next=next;
  next->prev=prev;
  gwin.active->next=__found;
  __found->prev=gwin.active;
  __found->next=NULL;
  gwin.active=__found;

  // update help category

  if(gwin.active->help)
    gwin.help=gwin.active->help;

  // reset cursor position

  vposset(gwin.active->row,gwin.active->column);

  // return normally

  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Activates a window without overlap checking

int wactiv_(int whandle) {

  // if window is already active, ignore request
  if(gwin.active and (whandle == gwin.active->whandle))
    return gwin.werrno = W_NOERROR;

  // find address of window's record
  __found = wfindrec(whandle);
  if(__found == NULL)
    return gwin.werrno = W_NOTFOUND;

  // re-link pointer to window record to be activated
  _wrec_t* prev = __found->prev;
  _wrec_t* next = __found->next;
  if(prev)
    prev->next = next;
  next->prev = prev;
  gwin.active->next = __found;
  __found->prev = gwin.active;
  __found->next = NULL;
  gwin.active = __found;

  // update help category
  if(gwin.active->help)
    gwin.help = gwin.active->help;

  // reset cursor position
  vposset(gwin.active->row,gwin.active->column);

  // return normally
  return gwin.werrno = W_NOERROR;
}


//  ------------------------------------------------------------------
//  this function will update buffers above window string will be displayed in

static void update_buffers(vatch* pcurr, int shadow) {

  _wrec_t *tcurr;
  vattr   tgattr;

  // put current string character and attribute into found window's buffer

  *pcurr = vcatch(*__p, __gattr);

  // if window's shadow is what's blocking, check to see
  // if it is the highest shadow.  If it is, display the
  // character in the shadow's attribute, otherwise search
  // for other blocking windows

  if(shadow) {
    if(__curr->next==NULL) {
      vputc(__crow, __ccol, __gattr & BLINK ? (__curr->wsattr | BLINK) : __curr->wsattr, *__p);
    }
    else {
      tcurr = __curr;
      __curr = __curr->next;
      tgattr = __gattr;
      __gattr = __curr->wsattr;
      if(window_blocking())
        update_buffers(calc_window(__curr), 0);
      else {
        if(bshadow_blocking())
          update_buffers(calc_bshadow(__curr), 1);
        else {
          if(rshadow_blocking())
            update_buffers(calc_rshadow(__curr), 1);
        }
      }
      __gattr = tgattr;
      __curr = tcurr;
    }
  }
}

//  ------------------------------------------------------------------

int wwprintc(int whandle, int wrow, int wcol, vattr attr, const vchar chr) {

  // check for existance of active window or hidden windows
  if(!gwin.total and gwin.hidden==NULL)
    return gwin.werrno=W_NOACTIVE;

  // find address of window's record
  _wrec_t* found = wfindrec(whandle);
  if(found==NULL) {
    found = gwin.hidden;
    while(found) {
      if(whandle==found->whandle)
        break;
      found = found->prev;
    }
    if(found==NULL)
      return gwin.werrno=W_NOTFOUND;
  }

  // display character
  vputc(found->srow+wrow+found->border, found->scol+wcol+found->border, attr, chr);

  // return to caller
  return gwin.werrno = W_NOERROR;
}

//  ------------------------------------------------------------------

int wwprints(int whandle, int wrow, int wcol, vattr attr, const char* str) {

  // check for existance of active window or hidden windows
  if(!gwin.total and gwin.hidden==NULL)
    return gwin.werrno=W_NOACTIVE;

  // find address of window's record
  int hidden = NO;
  _wrec_t* found = wfindrec(whandle);
  if(found==NULL) {
    found = gwin.hidden;
    while(found) {
      if(whandle==found->whandle)
        break;
      found = found->prev;
    }
    if(found==NULL)
      return gwin.werrno=W_NOTFOUND;
    hidden = YES;
  }

  // see if window has a border
  int border = found->border;

  // calculate effective coordinates
  int ecol = found->ecol-border;
  __crow  = found->srow+wrow+border;
  __ccol  = found->scol+wcol+border;
  __gattr = attr;
  __p     = str;

  // check for valid coordinates
  if((__crow > (found->erow-border)) or (__ccol > ecol))
    return gwin.werrno=W_INVCOORD;

  // save current cursor position
  int oldrow, oldcol;
  if(gvid->isbios())
    vposget(&oldrow,&oldcol);

  // do while not end-of-string and not end-of-window
  while(__ccol <= ecol and *__p) {

    // see if output window is hidden.  if so, then there
    // is no need to check for blocking windows/shadows
    if(hidden)
      *(calc_window(found)) = vcatch(*__p, attr);
    else {

      // check all window records "above" window to activate
      for(__curr=found->next; __curr!=NULL; __curr=__curr->next) {

        // see if current position in window to activate
        // is blocked by same position in test window
        if(window_blocking()) {

          // calculate buffer addresses and swap contents
          update_buffers(calc_window(__curr), 0);
          break;
        }

        // see if test window has a shadow
        if(__curr->wsbuf!=NULL) {

          // see if shadow to the right of test window is
          // blocking the current position of window to activate
          if(rshadow_blocking()) {
            update_buffers(calc_rshadow(__curr), 1);
            break;
          }

          // see if shadow to the bottom of test window is
          // blocking the current position of window to activate
          if(bshadow_blocking()) {
            update_buffers(calc_bshadow(__curr), 1);
            break;
          }
        }
      }

      // if current position is not blocked,
      // then display char to screen
      if(__curr==NULL)
        vputc(__crow, __ccol, attr, *__p);
    }

    // update pointer into string and current column
    __ccol++;
    __p++;
  }

  // restore old cursor position
  if(gvid->isbios())
    vposset(oldrow,oldcol);

  // return to caller
  return gwin.werrno = *__p ? W_STRLONG : W_NOERROR;
}


//  ------------------------------------------------------------------

int wwprintstr(int whandle, int wrow, int wcol, vattr attr, const char* str) {

  // check for existance of active window or hidden windows
  if(!gwin.total and gwin.hidden==NULL)
    return gwin.werrno=W_NOACTIVE;

  // find address of window's record
  _wrec_t* found = wfindrec(whandle);
  if(found==NULL) {
    found = gwin.hidden;
    while(found) {
      if(whandle==found->whandle)
        break;
      found = found->prev;
    }
    if(found==NULL)
      return gwin.werrno=W_NOTFOUND;
  }

  // display string
  vputs(found->srow+wrow+found->border, found->scol+wcol+found->border, attr, str);

  // return to caller
  return gwin.werrno = W_NOERROR;
}


//  ------------------------------------------------------------------
//  Changes the active window's border box type

int wborder(int btype) {

  register int border;

  // check for active window
  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // check for valid box type
  if((btype<0) or (btype>7))
    return gwin.werrno=W_INVBTYPE;

  // see if window is to have a border
  border = (btype==5) ? NO : YES;

  // redraw window's border
  vbox(
    gwin.active->srow,
    gwin.active->scol,
    gwin.active->erow,
    gwin.active->ecol,
    btype,
    border ? gwin.active->battr : gwin.active->wattr,
    border ? gwin.active->loattr : gwin.active->wattr
  );

  // update window's record
  gwin.active->btype=btype;
  gwin.active->border=border;

  // see if cursor position needs to be updated
  if((gwin.active->row==gwin.active->srow)   or
    (gwin.active->row==gwin.active->erow)    or
    (gwin.active->column==gwin.active->scol) or
    (gwin.active->column==gwin.active->ecol)) {
    wgotoxy(0,0);
  }

  // re-display title if one exists
  if(gwin.active->title!=NULL)
    wtitle(gwin.active->title,gwin.active->tpos,gwin.active->tattr);

  // return normally
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Fills a region of active window w/specified char/attribute

int wfill(int wsrow, int wscol, int werow, int wecol, vchar chr, vattr atr) {

  // check for active window
  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // check for valid coordinates
  if(wchkbox(wsrow,wscol,werow,wecol))
    return gwin.werrno=W_INVCOORD;

  // check for window border
  const int &border = gwin.active->border;

  // fill in specified region
  vfill(
    gwin.active->srow+wsrow+border,
    gwin.active->scol+wscol+border,
    gwin.active->srow+werow+border,
    gwin.active->scol+wecol+border,
    chr,
    atr
  );

  // return with no error
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Returns the handle of the active window

int whandle() {

  // test for active window
  if(!gwin.total) {
    gwin.werrno=W_NOACTIVE;
    return 0;
  }

  // return normally
  gwin.werrno = W_NOERROR;
  return gwin.active->whandle;
}


//  ------------------------------------------------------------------
//  Displays text on window's top or bottom border

int wmessage(const char* str, int border, int leftofs, vattr attr) {

  // check for active window
  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // make sure window has a border
  if(!gwin.active->border)
    return gwin.werrno=W_NOBORDER;

  int left  = gwin.active->scol+1;
  int right = gwin.active->ecol-1;
  int width = right-left+1;
  int len   = strlen(str);

  // Center string
  if(leftofs < 0)
    leftofs = (len > (width-2)) ? left : (((width/2)+left)-(len/2));

  // make sure string fits in window
  if((gwin.active->scol+leftofs+len-1) > (gwin.active->ecol))
    return gwin.werrno=W_STRLONG;

  // display string
  vputs(border ? gwin.active->erow : gwin.active->srow, gwin.active->scol+leftofs, attr, str);

  // return normally
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------
//  Proportion bar

void wpropbar(int xx, int yy, long len, vattr attr, long pos, long size) {

  //  xx, yy = start position in window.
  //  len    = length (in chars) of progress field.
  //  attr   = color to use for progress field.
  //  pos    = present position.
  //  size   = total size of field.

  const vchar barchar   = _box_table(gwin.active->btype, 13);
#ifdef __UNIX__ // prefferable under xterm
  const vchar thumbchar = ' ';
  vattr thumbattr       = revsattr(attr);
#else
  const vchar thumbchar = '\xDB';
  vattr thumbattr       = attr;
#endif

  long thumblen = (pos*len)/size;

  int x = xx;
  if(thumblen != 0) {
    wputx(yy, x, thumbattr|ACSET, thumbchar, thumblen);
    x += thumblen;
  }
  if(thumblen != len)
    wputx(yy, x, attr|ACSET, barchar, len-thumblen);
}


//  ------------------------------------------------------------------
//  Gives active window a title

int wtitle(const char* str, int tpos, vattr tattr) {

  // check for active window
  if(!gwin.total)
    return gwin.werrno=W_NOACTIVE;

  // redraw box if deleting or moving title
  if(str==NULL or gwin.active->title!=NULL) {
    if(gwin.active->border) {
      vputx(
        ((tpos&TBOTTOM) ? gwin.active->erow : gwin.active->srow),
        gwin.active->scol+1,
        gwin.active->battr|ACSET,
        _box_table(gwin.active->btype, (tpos&TBOTTOM)?6:1),
        gwin.active->ecol-gwin.active->scol-1
      );
    }
  }

  // if not deleting the title, calculate position and display it
  if(str) {

    int left  = gwin.active->scol+1;
    int right = gwin.active->ecol-1;
    int width = right-left+1;
    int len   = strlen(str);

    // don't display title if window is borderless
    if(gwin.active->border) {

      int start;

      switch(tpos&~TBOTTOM) {
        case TLEFT:
          //start = (len>(width-3)) ? left : (left+1);
          start = left;
          break;
        case TCENTER:
          start = (len>(width-2)) ? left : (((width/2)+left)-(len/2));
          break;
        default:        // default is TRIGHT
          {
            int offs = width-len;
            if(offs>2)
              offs--;
            start = (len>width) ? left : (left+offs+1);
          }
      }

      // allocate space for window title string, and copy it there
      char* p = (char*)throw_xmalloc(((width>len) ? width : len)+1);
      if(p==NULL)
        return gwin.werrno=W_ALLOCERR;
      strcpy(p, str);
      *(p+width) = NUL;

      // display title string
      vputs((tpos&TBOTTOM)?gwin.active->erow:gwin.active->srow, start, tattr, p);

      // free allocated space
      throw_xfree(p);

    }
  }

  // update window's record
  gwin.active->title=str;
  gwin.active->tpos=tpos;
  gwin.active->tattr=tattr;

  // return normally
  return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------

void wscrollbar(int orientation, uint total, uint maxpos, uint pos, int sadd) {

  vattr attr = (gwin.active->sbattr == DEFATTR) ? gwin.active->battr : gwin.active->sbattr;
  vattr invattr              = revsattr(attr);

  const vchar barchar        = _box_table(gwin.active->btype, 13);
  const vchar arrowupchar    = '\x18';
  const vchar arrowdownchar  = '\x19';
  const vchar arrowleftchar  = '\x1B';
  const vchar arrowrightchar = '\x1A';
#ifdef __UNIX__ // prefferable under xterm
  const vchar thumbchar      = ' ';
  vattr thumbattr            = revsattr(attr);
#else
  const vchar thumbchar      = '\xDB';
  vattr thumbattr            = attr;
#endif

  if(maxpos == 0)
    maxpos = 1;

  uint visiblelen;
  if(orientation == W_VERT)
    visiblelen = (gwin.active->erow - (gwin.active->srow+sadd)) + 1 - (gwin.active->border?2:0);
  else
    visiblelen = (gwin.active->ecol - (gwin.active->scol+sadd)) + 1 - (gwin.active->border?2:0) - 2;
  uint barlen = visiblelen - 2;
  uint thumblen = (visiblelen*barlen) / total;
  if(thumblen == 0)
    thumblen = 1;
  else if(thumblen > barlen)
    thumblen = barlen;
  uint maxthumbpos = barlen - thumblen;
  uint thumbpos = (pos*maxthumbpos) / maxpos;
  uint thumbdiv = (pos*maxthumbpos) % maxpos;
  if((thumbdiv >= (maxpos/2)) and (maxpos > 1))
    thumbpos++;
  if(thumbpos > maxthumbpos)
    thumbpos = maxthumbpos;
  barlen -= thumbpos + thumblen;

  if(orientation == W_VERT) {
    uint scol = gwin.active->ecol - gwin.active->scol - gwin.active->border;
    vputc((sadd++)+gwin.active->srow+gwin.active->border, gwin.active->ecol, invattr|ACSET, arrowupchar);
    if(thumbpos != 0) {
      wputy(sadd, scol, attr|ACSET, barchar, thumbpos);
      sadd += thumbpos;
    }
    if(thumblen != 0) {
      wputy(sadd, scol, thumbattr|ACSET, thumbchar, thumblen);
      sadd += thumblen;
    }
    if(barlen != 0) {
      wputy(sadd, scol, attr|ACSET, barchar, barlen);
      sadd += barlen;
    }
    vputc(sadd+gwin.active->srow+gwin.active->border, gwin.active->ecol, invattr|ACSET, arrowdownchar);
  }
  else {
    uint srow = gwin.active->erow - gwin.active->srow - gwin.active->border;
    vputc(gwin.active->erow, (sadd++)+gwin.active->scol+gwin.active->border, invattr|ACSET, arrowleftchar);
    if(thumbpos != 0) {
      wputx(srow, sadd, attr|ACSET, barchar, thumbpos);
      sadd += thumbpos;
    }
    if(thumblen != 0) {
      wputx(srow, sadd, thumbattr|ACSET, thumbchar, thumblen);
      sadd += thumblen;
    }
    if(barlen != 0) {
      wputx(srow, sadd, attr|ACSET, barchar, barlen);
      sadd += barlen;
    }
    vputc(gwin.active->erow, sadd+gwin.active->scol+gwin.active->border, invattr|ACSET, arrowrightchar);
  }
}


//  ------------------------------------------------------------------
