#include <stdio.h>
#include <stdarg.h>
#include "config.h"
#include "sdl-widgets.h"
#include "sw-pixmaps.h"

Uint32 cWhite,cBlack, cGrey, cRed, cBlue, cButBground, cMenuBground,
       cBorder, cBackground, cForeground, cSelRBut, cPointer, cScrollbar;

const Uint32 pixdepth=16,
             light=0xffffffff,
             dark=0x606060ff;

const char *def_fontpath=FONTPATH;

const bool grad=true;  // gradient colors?

static int nominal_font_size=12,
       char_wid=0,   // -> 7
       ontop_nr=-1;  // index 

RenderText *draw_ttf,
           *draw_title_ttf,
           *draw_mono_ttf;

void (*handle_uev)(int cmd,int param,int param2);
void (*handle_kev)(SDL_keysym *key,bool down);
void (*handle_rsev)(int dw,int dh);

static TopWin *topw;
static CmdMenu *the_menu,     // set at mousebutton up
               *the_menu_tmp; // set by CmdMenu::init()

static Uint32 cSelCmdMenu, cSlBackgr,
              cGrey1,cGrey2,cGrey3,cGrey4,cGrey5,cGrey6,
              video_flag,
              go_ticks; // increased by keep_alive thread
static const int
  LBMAX=20,     // labels etc.
  max_ontopwin=5;
static Color5 cGradientBlue, cGradientDarkBlue, cGradientRose, cGradientWheat, cGradientGreen, cGradientGrey;
Color5 *grad_colors[5]={
  &cGradientBlue,&cGradientGrey,&cGradientGreen,&cGradientWheat,&cGradientRose
};
bool sdl_running; // set true by get_events()
static bool quit=false;
static TTF_Font
  *def_font,
  *title_font,
  *mono_font;
const SDL_Color def_text_col={ 0,0,0 };

SDL_mutex *mtx=SDL_CreateMutex();
SDL_cond *cond=SDL_CreateCond();

static struct {
  SDL_Surface *rbut_pm,
              *checkbox_pm,
              *lamp_pm,
              *cross_pm;
} pixm;

void err(const char *form,...) {
  va_list ap;
  va_start(ap,form);
  printf("Error: ");
  vprintf(form,ap);
  va_end(ap);
  putchar('\n'); fflush(stdout);
  exit(1);
}

void say(const char *form,...) {   // for debugging
  va_list ap;
  va_start(ap,form);
  printf("say: "); vprintf(form,ap); putchar('\n');
  va_end(ap);
  fflush(stdout);
}

static struct AlertWin *alert_win;
Point alert_position(4,4);

static int min(int a, int b) { return a<=b ? a : b; }
static int max(int a, int b) { return a>=b ? a : b; }
static int minmax(int a, int x, int b) { return x>=b ? b : x<=a ? a : x; }
static int idiv(int a,int b) { return (2 * a + b)/(b*2); }

Color5 *int2col5(int i) {
  return i<5 && i>0 ? grad_colors[i] : grad_colors[0];
}

static void cross(SDL_Surface *win,int nr,int y_off) {
  if (!pixm.cross_pm) pixm.cross_pm=create_pixmap(cross_xpm);
  SDL_BlitSurface(pixm.cross_pm,0,win,rp(3,3,0,0));
}

Rect* calc_overlap(Rect a,Rect b) {
  static Rect clip;
  clip.x=max(a.x,b.x);
  clip.y=max(a.y,b.y);
  int w=min(a.x+a.w,b.x+b.w)-clip.x;
  int h=min(a.y+a.h,b.y+b.h)-clip.y;
  if (w<=0 || h<=0) return 0;
  clip.w=w; clip.h=h;
  return &clip;
}

struct OnTopWin {
  WinBase *wb;         // set by keep_on_top()
  SDL_Surface *backup;
  bool backup_done;
  void to_top(Rect* area) {
    Rect *clip=calc_overlap(*area,wb->tw_area);
    if (clip) {
      Rect r4(clip->x-wb->tw_area.x,clip->y-wb->tw_area.y,clip->w,clip->h);
      SDL_BlitSurface(topw->win,clip,backup,&r4);  // update backup
      SDL_BlitSurface(wb->win,&r4,topw->win,clip); // restore top window
    }
    if (wb->title) {
      Rect tr2(wb->tw_area.x+wb->tit_os().x,wb->tw_area.y+wb->tit_os().y,wb->title_area.w,wb->title_area.h);
      clip=calc_overlap(*area,tr2);
      if (clip)
        SDL_BlitSurface(wb->title,0,topw->win,&tr2);
    }
  }
};

static OnTopWin ontop_win[max_ontopwin];

struct AlertWin {  // should not work recursively!
  BgrWin *bgr;
  TextWin *text;
  Point mouse_point;
  static void close_cmd(Id) { alert_win->bgr->hide(); }
  static void mouse_down_cmd(Id id,int x,int y,int but) { 
    if (but==SDL_BUTTON_LEFT) {
      SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
      alert_win->mouse_down(x,y);
    }
  }
  static void move_cmd(Id id,int x,int y,int but) { alert_win->move(x,y); }
  static void mouse_up_cmd(Id id,int x,int y,int but) {
    alert_win->bgr->hidden=false;
    SDL_BlitSurface(alert_win->bgr->win,0,topw->win,&alert_win->bgr->tw_area);
    SDL_UpdateRects(topw->win,1,&alert_win->bgr->tw_area);
  }
  static void disp_cmd(BgrWin *bgwin) { bgwin->draw_raised(0,calc_color(0xffa0a0),true); }
  AlertWin() {
    int wid=300,hight=150,
        w=topw->area.w,
        h=topw->area.h;
    Rect rect(alert_position.x,alert_position.y,wid,hight);
    if (rect.x+wid>w) rect.x=w-wid;     // will it fit?
    if (rect.y+hight>h) rect.y=h-hight;
    if (rect.x<0) { rect.x=0; rect.w=min(w,wid); }
    if (rect.y<0) { rect.y=0; rect.h=min(h,hight); }

    bgr=new BgrWin(topw,rect,0,disp_cmd,mouse_down_cmd,move_cmd,mouse_up_cmd,cWhite);
    bgr->hidden=true;
    bgr->keep_on_top();
    text=new TextWin(bgr,0,Rect(3,22,rect.w-7,rect.h-26),(rect.h-26)/TDIST,0);
    new Button(bgr,Style(0,1),Rect(rect.w-20,5,14,13),cross,close_cmd);
  }
  void mouse_down(int x,int y) {
    mouse_point.set(x,y);
    bgr->hide();
    draw_outline();
  }
  void move(int x,int y) {
    if (abs(x-mouse_point.x)<=5 && abs(y-mouse_point.y)<=5)
      return;
    Rect old(bgr->tw_area);
    if (bgr->move_if_ok(x - mouse_point.x,y - mouse_point.y)) { // move bgr
      SDL_BlitSurface(alert_win->bgr->ontopw->backup,0,topw->win,&old); // restore the screen
      SDL_UpdateRects(topw->win,1,&old);
      SDL_BlitSurface(topw->win,&bgr->tw_area,bgr->ontopw->backup,0); // backup the screen
      bgr->ontopw->backup_done=true;
      draw_outline();
    }
  }
  void alert(char *buf) {
    if (sdl_running) {
      if (the_menu && the_menu->buttons) {
        if (calc_overlap(the_menu->mrect,bgr->tw_area)) the_menu->close();
      }
      if (bgr->hidden) {
        SDL_BlitSurface(topw->win,&bgr->tw_area,bgr->ontopw->backup,0); // backup the screen
        bgr->hidden=false;
        bgr->ontopw->backup_done=true;
        text->reset();
      }
      text->add_text(buf,false);
      if (sdl_running) {
        bgr->draw_blit_recur();
        bgr->upd();
      }
    }
    else {
      bgr->hidden=false;
      text->add_text(buf,false);
    }
  }
  void draw_outline() {
    Rect *r=&bgr->tw_area;
    rectangleColor(topw->win,r->x,r->y,r->x + r->w - 1,r->y + r->h - 1,0xff0000ff);
    SDL_UpdateRects(topw->win,1,r);
  }
};

void update(Rect *rect,WinBase *winb) {
  int n=0;
  OnTopWin *otw;
  if (winb && winb->ontopw)
    n=winb->ontopw-ontop_win+1; // 1 past own entry
  for (;n<=ontop_nr;++n) {
    otw=ontop_win+n;
    if (!otw->wb->hidden)
      otw->to_top(rect);
  }
  SDL_UpdateRects(topw->win,1,rect);
}

void alert(const char *form,...) {
  static char buf[201];
  va_list ap;
  va_start(ap,form);
  vsnprintf(buf,200,form,ap);
  va_end(ap);
  if (alert_win)
    alert_win->alert(buf);
  else
    puts(buf);
}

static void sdl_quit(int n) {
  SDL_Quit();
  puts("Goodbye!");
  exit(n);
}

Rect::Rect() { x=0; y=0; w=0; h=0; }
Rect::Rect(Sint16 _x,Sint16 _y,Uint16 _dx,Uint16 _dy) { x=_x; y=_y; w=_dx; h=_dy; }
void Rect::set(Sint16 _x,Sint16 _y,Uint16 _dx,Uint16 _dy) { x=_x; y=_y; w=_dx; h=_dy; }

Point::Point(short x1,short y1):x(x1),y(y1) { }
Point::Point():x(0),y(0) { }
void Point::set(short x1,short y1) { x=x1; y=y1; }
bool Point::operator==(Point b) { return x==b.x && y==b.y; }
bool Point::operator!=(Point b) { return x!=b.x || y!=b.y; }

void Int2::set(int x1,int y1) { x=x1; y=y1; }
Int2::Int2():x(0),y(0) { }
Int2::Int2(int x1,int y1):x(x1),y(y1) { }
bool Int2::operator!=(Int2 b) { return x!=b.x || y!=b.y; }

Id::Id(int _id1):id1(_id1),id2(0) { }
Id::Id(int _id1,int _id2):id1(_id1),id2(_id2) { }

Label::Label(const char* t):
  render_t(draw_ttf),
  draw_cmd(0),
  str(t) {
}
Label::Label(void (*dr)(SDL_Surface *win,int nr,int y_off)):
  render_t(draw_ttf),
  draw_cmd(dr),
  str(0) {
}
Label::Label(const char *t,void (*dr)(SDL_Surface *win,int nr,int y_off)):
  render_t(draw_ttf),
  draw_cmd(dr),
  str(t) {
}

void Label::draw(SDL_Surface *win,int nr,Point pnt) {
  if (str) render_t->draw_string(win,str,pnt);
  if (draw_cmd) draw_cmd(win,nr,pnt.y);
}

Style::Style(int _st):st(_st),param(0),param2(0) { }
Style::Style(int _st,int par):st(_st),param(par),param2(0) { }
Style::Style(int _st,int par,int par2):st(_st),param(par),param2(par2) { }

Uint32 calc_color(Uint32 c) {
  return SDL_MapRGB(topw->win->format,c>>16 & 0xff,c>>8 & 0xff,c & 0xff);
  //return SDL_MapRGBA(topw->win->format,c>>16 & 0xff,c>>8 & 0xff,c & 0xff,SDL_ALPHA_OPAQUE);
}

void Color5::set_color(Uint32 c0,Uint32 c1,Uint32 c2,Uint32 c3,Uint32 c4) {
  c[0]=calc_color(c0); c[1]=calc_color(c1); c[2]=calc_color(c2); c[3]=calc_color(c3); c[4]=calc_color(c4);
}

WinBase::WinBase(WinBase *pw,const char *t,int x,int y,int dx,int dy,Uint32 _bgcol,Id _id):
    win(0),
    title(t ? TTF_RenderText_Blended(title_font,t,def_text_col) : 0),
    parent(pw),
    children(0),
    ontopw(0),
    area(x,y,dx,dy),
    tw_area(area),
    title_area(x,y-15,0,0),
    title_top(x,y-15),
    lst_child(-1),
    end_child(5),
    bgcol(_bgcol),
    hidden(false),
    id(_id) {
  if (parent) { // parent = 0 if top window
    if (parent!=topw) {
      tw_area.x+=parent->area.x; tw_area.y+=parent->area.y;
    }
    parent->add_child(this);
  }
  if (title) {
    title_area.w=title->w; title_area.h=title->h;
  }
};

WinBase::~WinBase() {
  while (lst_child>=0) delete children[lst_child];
  if (parent)
    parent->remove_child(this);
  SDL_FreeSurface(win);
  SDL_FreeSurface(title);
}

template<class T> T* re_alloc(T* arr,int& len,T ival) {
  int i;
  T* new_arr=new T[len*2];
  for (i=0;i<len;++i) new_arr[i]=arr[i];
  delete[] arr;
  len*=2;
  for(;i<len;++i) new_arr[i]=ival;
  return new_arr;
}

void WinBase::reloc_title(int dx,int dy) {
  if (title) {
    title_area.x=title_top.x+dx; title_area.y=title_top.y+dy;
  }
}

void WinBase::add_child(WinBase *child) {
  if (!children)
    children=new WinBase*[end_child];
  else if (lst_child==end_child-1)
    children=re_alloc<WinBase*>(children,end_child,0);
  if (this==topw && alert_win && children[lst_child]==alert_win->bgr) {
    children[lst_child]=child;
    children[++lst_child]=alert_win->bgr;
  }
  else
    children[++lst_child]=child;
}

void WinBase::remove_child(WinBase *child) {
  for (int i=lst_child;i>=0;--i)
    if (children[i]==child) {
      for (;i<lst_child;++i) children[i]=children[i+1];
      --lst_child;
      return;
    }
  alert("remove_child: child %p not found",child);
}

void WinBase::clear() { SDL_FillRect(win,0,bgcol); }

void WinBase::clear(Rect rect) { SDL_FillRect(win,&rect,bgcol); }

void WinBase::clear(Rect rect,Uint32 col,bool _upd) {
  SDL_FillRect(win,&rect,col);
  if (_upd) update(&rect,this);
}

static void move_tw_area(WinBase *wb,int delta_x,int delta_y) { // recursive
  wb->tw_area.x+=delta_x;
  wb->tw_area.y+=delta_y;
  for (int i=0;i<=wb->lst_child;++i)
    move_tw_area(wb->children[i],delta_x,delta_y);
}

bool WinBase::move_if_ok(int delta_x,int delta_y) {
  bool x_not_ok=tw_area.x+delta_x < 0 || tw_area.x+tw_area.w+delta_x > topw->area.w,
       y_not_ok=tw_area.y+delta_y < 0 || tw_area.y+tw_area.h+delta_y > topw->area.h;
  if (x_not_ok && y_not_ok)
    return false;
  if (x_not_ok)
    delta_x=0;
  else if (y_not_ok)
    delta_y=0;
  move_tw_area(this,delta_x,delta_y);
  area.x+=delta_x;
  area.y+=delta_y;
  return true;
}
  
void WinBase::hide() {
  if (hidden) return;
  hidden=true;
  if (!sdl_running) return;
  WinBase *wb,*nxt,
          *ot=0; // on-top window?
  Rect r2(0,0,area.w,area.h),
       tr2(tit_os().x,tit_os().y,title_area.w,title_area.h);
  for (wb=this,nxt=parent;nxt;wb=nxt,nxt=nxt->parent) {
    r2.x+=wb->area.x; r2.y+=wb->area.y;    // win
    nxt->clear(r2,parent->bgcol,false);
    if (title) {
      tr2.x+=wb->area.x; tr2.y+=wb->area.y;  // title
      nxt->clear(tr2,parent->bgcol,false);
    }
    if (nxt->hidden) return;
    if (wb->ontopw) ot=wb;
    if (nxt==topw) {
      if (ontopw) {
        SDL_BlitSurface(ontopw->backup,0,topw->win,&r2); // restore the screen
        ontopw->backup_done=false;
      }
      update(&r2,ot);
      if (title) update(&tr2,ot);
      return;
    }
  }
}

Point WinBase::tit_os() { return Point(title_area.x-area.x,title_area.y-area.y); }

void WinBase::show() {
  if (!hidden) return;
  hidden=false;
  if (!sdl_running) return;
  WinBase *wb,*nxt,
          *ot=0; // on-top window?
  Rect r1(0,0,area.w,area.h),
       tr1(tit_os().x,tit_os().y,title_area.w,title_area.h);
  for (wb=this,nxt=parent;nxt;wb=nxt,nxt=nxt->parent) {
    if (wb->hidden) return;
    if (wb->ontopw) ot=wb;
    r1.x+=wb->area.x; r1.y+=wb->area.y;    // win
    SDL_BlitSurface(win,0,nxt->win,&r1);
    if (title) {
      tr1.x+=wb->area.x; tr1.y+=wb->area.y;  // title
      SDL_BlitSurface(title,0,nxt->win,&tr1);
    }
    if (nxt==topw) {
      update(&r1,ot);
      if (title) update(&tr1,ot);
      return;
    }
  }
}

void WinBase::draw_raised(Rect *rect,Uint32 col,bool up) {
  SDL_FillRect(win,rect,col);
  if (!rect) rect=rp(0,0,win->w,win->h);
  if (up) {
    hlineColor(win,rect->x,rect->x+rect->w-1,rect->y,light); // upper
    vlineColor(win,rect->x,rect->y,rect->y+rect->h-1,light);  // left
    hlineColor(win,rect->x,rect->x+rect->w-1,rect->y+rect->h-1,dark); // bottom
    vlineColor(win,rect->x+rect->w-1,rect->y,rect->y+rect->h-1,dark); // right
  }
  else {
    hlineColor(win,rect->x,rect->x+rect->w-1,rect->y,dark);  // upper
    hlineColor(win,rect->x,rect->x+rect->w-1,rect->y+rect->h-1,light); // bottom
    vlineColor(win,rect->x,rect->y,rect->y+rect->h-1,dark);  // left
    vlineColor(win,rect->x+rect->w-1,rect->y,rect->y+rect->h-1,light); // right
  }
}

void WinBase::draw_gradient(Rect rect,Color5 *col,bool vertical,bool hollow) {
  const int x[6]={ 0,rect.w/5,rect.w*2/5,rect.w*3/5,rect.w*4/5,rect.w },
            y[6]={ 0,rect.h/5,rect.h*2/5,rect.h*3/5,rect.h*4/5,rect.h };
  for (int i=0;i<5;++i) {
    Uint32 c= hollow ? col->c[4-i] : col->c[i];
    if (vertical)
      SDL_FillRect(win,rp(rect.x+x[i],rect.y,x[i+1]-x[i],rect.h),c);
    else
      SDL_FillRect(win,rp(rect.x,rect.y+y[i],rect.w,y[i+1]-y[i]),c);
  }
  rectangleColor(win,rect.x,rect.y,rect.x+rect.w-1,rect.y+rect.h-1,0x707070ff);
}

void set_text(char *&txt,const char *form,...) {
  if (!txt) txt=new char[LBMAX];
  va_list ap;
  va_start(ap,form);
  vsnprintf(txt,LBMAX,form,ap);
  va_end(ap);
}

SDL_Surface *create_pixmap(const char* pm_data[]) {
  int i,dx,dy,nr_col;
  char ch;
  sscanf(pm_data[0],"%d %d %d",&dx,&dy,&nr_col);
  SDL_Surface *pm=SDL_CreateRGBSurface(SDL_SWSURFACE,dx,dy,pixdepth,0,0,0,0);
  SDL_SetColorKey(pm,SDL_SRCCOLORKEY,0);
  //printf("dx=%d dy=%d nr_col=%d\n",dx,dy,nr_col);
  SDL_FillRect(pm,0,0); // default = None
  struct ColSym {
    char ch;
    Uint32 col;
    bool none;
  };
  ColSym *colsym=new ColSym[nr_col];
  for (i=0;i<nr_col;++i) {
    Uint32 col;
    bool none=false;
    char s[10];
    sscanf(pm_data[i+1],"%c c %c%6s",&colsym[i].ch,&ch,s); // expected "* c #606060" or "* c None"
    if (ch=='#')
      sscanf(s,"%x",&col);
    else if (ch=='N' && !strcmp(s,"one")) {
      col=0; none=true;
    }
    else {
      printf("unexpected char '%c' in xpm\n",ch); col=0;
    }
    if ((col & 0xff) < 8 && (col>>8 & 0xff) < 8 && (col>>16 & 0xff) < 8)  // to prevent transparent color
      col |= 8;
    colsym[i].col= col<<8 | 0xff;
    colsym[i].none=none;
  }
  for (int col=0;col<nr_col;++col) {
    ch=colsym[col].ch;
    Uint32 pixcol=colsym[col].col;
    bool none=colsym[col].none;
    for (int y=0;y<dy;++y)
      for (int x=0;x<dx;++x)
        if (pm_data[y+nr_col+1][x]==ch && !none)
          pixelColor(pm,x,y,pixcol);
  }
  delete[] colsym;
  return pm;
}

SDL_Cursor *init_system_cursor(const char *image[]) {
  int i, row, col,
      dx,dy,nr_color;
  Uint8 data[4*32];
  Uint8 mask[4*32];
  int hot_x=-1, hot_y=-1;
  sscanf(image[0],"%d %d %d",&dx,&dy,&nr_color); // expected: "20 20 3 1"
  if (nr_color!=3) {
    alert("%d colors in cursor data, expected 3",nr_color);
    return 0;
  }
  if (dx>32 || dy>32) {
    alert("dx=%d, dy=%d in cursor data, must be <= 32",dx,dy);
    return 0;
  }
  i = -1;
  for ( row=0; row<32; ++row ) {
    for ( col=0; col<32; ++col ) {
      if ( col % 8 ) {
        data[i] <<= 1;
        mask[i] <<= 1;
      } else {
        ++i;
        data[i] = mask[i] = 0;
      }
      char ch= col<dx && row<dy ? image[4+row][col] : ' ';
      switch (ch) {
        case 'X':
          data[i] |= 0x01;
          mask[i] |= 0x01;
          break;
        case '.':
          mask[i] |= 0x01;
          break;
        case ' ':
          break;
        default:
          alert("char '%c' in cursor data, expected 'X','.' or ' '",ch);
          return 0;
      }
    }
  }
  if (2!=sscanf(image[4+dy], "%d,%d", &hot_x, &hot_y) ||
      hot_x<0 || hot_y<0) {
    alert("hot-x and hot-y missing in cursor data");
    return 0;
  }
  return SDL_CreateCursor(data, mask, 32, 32, hot_x, hot_y);
}

TopWin::TopWin(const char* wm_title,Rect rect,Uint32 init_flag,Uint32 vflag,void (*draw_cmd)(),void (*set_icon)()):
    WinBase(0,0,0,0,rect.w,rect.h,0,0),
    display_cmd(draw_cmd) {
  if (SDL_Init(init_flag) < 0) err("SDL init problem");
  if (TTF_Init() < 0) err("SDL ttf init problem");
  if (!(title_font=TTF_OpenFont(FONTPATH_BOLD,nominal_font_size)))  // fontpath from config.h
    err("font-spec FreeSansBold.ttf not found");
  if (!(def_font=TTF_OpenFont(FONTPATH,nominal_font_size)))
    err("font-spec FreeSans.ttf not found");
  if (!(mono_font=TTF_OpenFont(FONTPATH_MONO,nominal_font_size)))
    err("font-spec FreeMono.ttf not found");
  TTF_SizeText(mono_font," ",&char_wid,0); // width of ' '
  video_flag=vflag ? vflag : SDL_SWSURFACE;
  if (set_icon) set_icon();
  win=SDL_SetVideoMode(rect.w,rect.h,pixdepth,video_flag);
  SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
  draw_ttf=new RenderText(def_font,def_text_col);
  draw_title_ttf=new RenderText(title_font,def_text_col);
  draw_mono_ttf=new RenderText(mono_font,def_text_col);
  SDL_SetAlpha(win, 0,SDL_ALPHA_OPAQUE);
  SDL_WM_SetCaption(wm_title,wm_title);
  topw=this; // needed for calc_color()

  cWhite       = calc_color(0xffffff);
  cBlack       = calc_color(0);
  cGrey1       = calc_color(0x707070),
  cGrey2       = calc_color(0xb0b0b0),
  cGrey3       = calc_color(0xc0c0c0),
  cGrey4       = calc_color(0xd0d0d0),
  cGrey5       = calc_color(0xe0e0e0),
  cGrey6       = calc_color(0xeaeaea);
  cBorder      = cGrey1;
  cSlBackgr    = cGrey2; // slider and scrollbar background
  cGrey        = cGrey3;
  cMenuBground = cGrey5;
  cButBground  = cGrey5;
  cBlue        = calc_color(0xff);
  cRed         = calc_color(0xff0000);
  cForeground  = calc_color(0xcdba96); // wheat3
  cBackground  = calc_color(0xdcd8c0);//eee8cd); // cornsilk2
  bgcol=cBackground;
  cPointer     = calc_color(0xff7d7d); // slider pointer
  cSelRBut     = calc_color(0xe7e760); // selected radiobutton
  cSelCmdMenu  = calc_color(0xb0b0ff); // selected cmd menu item
  cScrollbar   = calc_color(0x03abff); // scrollbar
  cGradientBlue.set_color(0xf0ffff,0xd7f7ff,0xc0f0ff,0xb0e0e7,0xa0d0e0);
  cGradientDarkBlue.set_color(0xa0f0ff,0x60c4ff,0x50a8ff,0x40a0dc,0x3094d0);
  cGradientWheat.set_color(0xfff0d7,0xeee8cd,0xe0e0c0,0xd0d0b0,0xc0c0a0);
  cGradientGrey.set_color(0xffffff,0xf0f0f0,0xe0e0e0,0xd0d0d0,0xc7c7c7);
  cGradientGreen.set_color(0xe0ffe0,0xb7f0b7,0xb0e0b0,0x84d084,0x77c077);
  cGradientRose.set_color(0xfff0de,0xffd6ce,0xffc6be,0xffb6ae,0xffa69e);
  alert_win=new AlertWin();
  alert_win->bgr->hidden=true;
  SDL_Cursor *curs=init_system_cursor(arrow);
  if (curs) SDL_SetCursor(curs);
}

void TopWin::draw() { if (display_cmd) display_cmd(); }

BgrWin::BgrWin(WinBase *pw,
         Rect rect,
         const char* tit,
         void (*dis_cmd)(BgrWin*),
         void (*do_cmd)(Id id,int,int,int),
         void (*mo_cmd)(Id id,int,int,int),
         void (*u_cmd)(Id id,int,int,int),
         Uint32 _bgcol,
         Id _id):
    WinBase(pw,tit,rect.x,rect.y,rect.w,rect.h,_bgcol,_id),
         display_cmd(dis_cmd),
         down_cmd(do_cmd),
         moved_cmd(mo_cmd),
         up_cmd(u_cmd) {
  win=SDL_CreateRGBSurface(SDL_SWSURFACE,area.w,area.h,pixdepth,0,0,0,0);
}

void BgrWin::draw() { if (display_cmd) display_cmd(this); }

HSlider::HSlider(WinBase *pw,Style st,Rect rect,int minval,int maxval,
        const char* tit,void (*_cmd)(HSlider*,int fire,bool rel),Id _id):
    WinBase(pw,tit,rect.x,rect.y,rect.w,9+TDIST,0,_id),
    sdx(rect.w-8),
    minv(minval),maxv(max(minval+1,maxval)),
    def_data(minval),
    d(&def_data),
    lab_left(0),
    lab_right(0),
    text(0),
    style(st),
    cmd(_cmd) {
  win=SDL_CreateRGBSurface(SDL_SWSURFACE,area.w,area.h,pixdepth,0,0,0,0);
}

VSlider::VSlider(WinBase *pw,Style st,Rect rect,int minval,int maxval,
        const char* tit,void (*_cmd)(VSlider*,int fire,bool rel),Id _id):
    WinBase(pw,tit,rect.x,rect.y,rect.w?rect.w:30,rect.h,0,_id),
    sdy(rect.h-8),
    minv(minval),maxv(max(minval+1,maxval)),
    def_data(minval),
    d(&def_data),
    lab_top(0),
    lab_bottom(0),
    text(0),
    style(st),
    cmd(_cmd) {
  win=SDL_CreateRGBSurface(SDL_SWSURFACE,area.w,area.h,pixdepth,0,0,0,0);
}

int &HSlider::value() { return *d; }

void HSlider::draw_sliderbar(int val) {
  int range=maxv-minv,
      xpos=(val - minv) * sdx / range;
  draw_raised(rp(0,0,area.w,12),cSlBackgr,false);
  if (style.param==0) 
    for (int i=0;i<=range;++i) {
      int x2=3 + sdx * i / range;
      vlineColor(win,x2,3,7,0xe0e0e0ff);
    }
  draw_raised(rp(xpos+1,1,6,10),cScrollbar,true);
}

void HSlider::draw() {
  SDL_FillRect(win,0,parent->bgcol);
  draw_sliderbar(*d);
  Rect os;
  if (text)
    draw_ttf->draw_string(win,text,Point((area.w-draw_ttf->text_width(text))/2,area.h-14));
  if (lab_left)
    draw_ttf->draw_string(win,lab_left,Point(1,area.h-14));
  if (lab_right) {
    int x1=area.w - draw_ttf->text_width(lab_right) - 4;
    draw_ttf->draw_string(win,lab_right,Point(x1,area.h-14));
  }
}

void HSlider::set_hsval(int val,int fire,bool do_draw) {
  *d=val;
  if (fire && cmd) cmd(this,fire,false);
  if (do_draw)
    draw_blit_upd();
}

void HSlider::calc_hslval(int x1) {
  *d=minmax(minv,minv + idiv((x1-4) * (maxv - minv),sdx),maxv);
}

int &VSlider::value() { return *d; }

void VSlider::draw_sliderbar(int val) {
  int range=maxv-minv,
      ypos=sdy - (val - minv) * sdy / range;
  draw_raised(rp(0,0,12,area.h),cSlBackgr,false);
  if (style.param==0) 
    for (int i=0;i<=range;++i) {
      int y3=3 + sdy - sdy * i / range;
      hlineColor(win,3,7,y3,0xe0e0e0ff);
    }
  draw_raised(rp(1,ypos+1,10,6),cScrollbar,true);
}

void VSlider::draw() {
  SDL_FillRect(win,0,parent->bgcol);
  draw_sliderbar(*d);
  Rect os;
  if (text)
    draw_ttf->draw_string(win,text,Point(14,area.h/2-5));
  if (lab_top)
    draw_ttf->draw_string(win,lab_top,Point(14,1));
  if (lab_bottom) {
    draw_ttf->draw_string(win,lab_bottom,Point(14,area.h-14));
  }
}

void VSlider::set_vsval(int val,int fire,bool do_draw) {
  *d=val;
  if (fire && cmd) cmd(this,fire,false);
  if (do_draw)
    draw_blit_upd();
}

void VSlider::calc_vslval(int y1) {
  *d=minmax(minv,minv + idiv((sdy+4-y1) * (maxv - minv),sdy),maxv);
}

HVSlider::HVSlider(WinBase *pw,Style st,Rect rect,int x_ins,Int2 minval,Int2 maxval,
                   const char* tit,void (*_cmd)(HVSlider*,int fire,bool rel),Id _id):
    WinBase(pw,tit,rect.x,rect.y,rect.w,rect.h,0,_id),
    x_inset(x_ins),
    sdx(rect.w-10-x_inset),sdy(rect.h-10-TDIST),
    minv(minval),maxv(maxval),
    def_data(minval),
    d(&def_data),
    lab_left(0),
    lab_right(0),
    lab_top(0),
    lab_bottom(0),
    text_x(0),text_y(0),
    style(st),
    cmd(_cmd) {
  win=SDL_CreateRGBSurface(SDL_SWSURFACE,area.w,area.h,pixdepth,0,0,0,0);
}

Int2 &HVSlider::value() { return *d; }

void HVSlider::draw_2dim_slider(Int2 val) {
  Int2 range(maxv.x - minv.x,maxv.y - minv.y),
       pos((val.x - minv.x) * sdx / range.x,
           sdy - (val.y - minv.y) * sdy / range.y);
  int i;
  const int dx=area.w-x_inset,
            dy=area.h-TDIST;
  draw_raised(rp(0,0,dx,dy),cSlBackgr,false);
  if (style.param==0) {
    for (i=0;i<=range.x;++i) {
      int x1=4 + sdx * i / range.x;
      vlineColor(win,x1,dy-6,dy-2,0xe0e0e0ff);
    }
    for (i=0;i<=range.y;++i) {
      int y1=4 + sdy - sdy * i / range.y;
      hlineColor(win,1,5,y1,0xe0e0e0ff);
    }
  }
  //if (grad) draw_gradient(Rect(pos.x,pos.y,9,9),&cGradientDarkBlue);
  draw_raised(rp(pos.x+1,pos.y+1,8,8),cScrollbar,true);
}

void HVSlider::draw() {
  SDL_FillRect(win,0,parent->bgcol);
  draw_2dim_slider(*d);
  if (text_x) {
    int x1=max(0,8+(sdx-draw_ttf->text_width(text_x))/2);
    draw_ttf->draw_string(win,text_x,Point(x1,area.h-14));
  }
  if (lab_left)
    draw_ttf->draw_string(win,lab_left,Point(1,area.h-14));
  if (lab_right) {
    int x1=4 + sdx - draw_ttf->text_width(lab_right)/2;
    draw_ttf->draw_string(win,lab_right,Point(x1,area.h-14));
  }
  if (text_y)
    draw_ttf->draw_string(win,text_y,Point(12+sdx,-3+sdy/2));
  if (lab_top)
    draw_ttf->draw_string(win,lab_top,Point(12+sdx,0));
  if (lab_bottom)
    draw_ttf->draw_string(win,lab_bottom,Point(12+sdx,-6+sdy));
}

void HVSlider::set_hvsval(Int2 val,int fire,bool do_draw) {
  *d=val;
  if (fire && cmd) cmd(this,fire,false);
  if (do_draw)
    draw_blit_upd();
}

void HVSlider::calc_hvslval(Int2 i2) {
  Int2 range(maxv.x - minv.x,maxv.y - minv.y),
       val1(minv.x + idiv((i2.x - 5) * range.x,sdx),
            minv.y + idiv((sdy + 5 - i2.y) * range.y,sdy));
  d->set(minmax(minv.x,val1.x,maxv.x),minmax(minv.y,val1.y,maxv.y));
}

CheckBox::CheckBox(WinBase *pw,Style st,Rect rect,Label lab,void (*_cmd)(CheckBox*),Id _id):
    WinBase(pw,0,rect.x,rect.y,rect.w ? rect.w : lab.str ? draw_ttf->text_width(lab.str)+16 : 14,rect.h ? rect.h : 14,0,_id),
    def_val(false),
    d(&def_val),
    label(lab),
    style(st),
    cmd(_cmd) {
  switch (style.st) {
    case 0:
    case 1:
      win=SDL_CreateRGBSurface(SDL_SWSURFACE,area.w,area.h,pixdepth,0,0,0,0);
      break;
  }
}

bool &CheckBox::value() { return *d; }

void CheckBox::draw() {
  Rect os;
  if (!pixm.checkbox_pm) pixm.checkbox_pm=create_pixmap(check_xpm);
  switch (style.st) {
    case 0:  // square 3D
      SDL_FillRect(win,0,parent->bgcol);
      //draw_raised(rp(0,1,area.h-2,area.h-2),parent->bgcol,false);
      draw_raised(rp(0,1,area.h-2,area.h-2),cGrey5,false);
      if (*d)
        SDL_BlitSurface(pixm.checkbox_pm,0,win,rp(1,3,0,0));
      label.draw(win,id.id2,Point(area.h+2,area.h/2-8));
      break;
    case 1:  // round 3D
      SDL_FillRect(win,0,parent->bgcol);
      if (!pixm.rbut_pm) pixm.rbut_pm=create_pixmap(rbut_pm);
      SDL_BlitSurface(pixm.rbut_pm,0,win,rp(0,1,0,0));
      if (*d) {
        aacircleColor(win,6,7,2,0xff);
        filledCircleColor(win,6,7,1,0xff);
      }
      label.draw(win,id.id2,Point(area.h+2,area.h/2-8));
      break;
  }
}

void CheckBox::set_cbval(bool val,int fire,bool do_draw) {
  *d=val;
  if (do_draw) draw_blit_upd();
  if (fire && cmd) cmd(this);
}

Button::Button(WinBase *pw,Style st,Rect rect,Label lab,void (*_cmd)(Id),Id _id):
    WinBase(pw,0,rect.x,rect.y,rect.w?rect.w:16,rect.h?rect.h:16,0,_id),
    is_down(false),
    style(st),
    label(lab),
    cmd(_cmd) {
  switch (style.st) {
    case 0:  // normal
      win=SDL_CreateRGBSurface(SDL_SWSURFACE,area.w,area.h,pixdepth,0,0,0,0);
      break;
    case 1: {  // square, label at rightside
      int wid=18;
      if (lab.str) wid += draw_ttf->text_width(lab.str);
      win=SDL_CreateRGBSurface(SDL_SWSURFACE,wid,area.h,pixdepth,0,0,0,0);
      break;
    }
    case 2:  // for menu
    case 3:  // 2D, no redraw if is_down
      win=SDL_CreateRGBSurface(SDL_SWSURFACE,area.w,area.h,pixdepth,0,0,0,0);
      bgcol=parent->bgcol;
      label.render_t=draw_title_ttf;
      break;
    case 4:  // for menu, with variable label
      win=SDL_CreateRGBSurface(SDL_SWSURFACE,area.w,area.h,pixdepth,0,0,0,0);
      bgcol=cWhite;
      break;
  }
}

void Button::draw() {
  Color5 *bcol=int2col5(style.param);
  int y=area.h/2-8;
  switch (style.st) {
    case 0:
      if (is_down)
        draw_raised(0,bcol->c[2],false);
      else
        draw_gradient(Rect(0,0,area.w,area.h),bcol);
      label.draw(win,id.id2,Point(3,y));
      break;
    case 1:
      SDL_FillRect(win,0,parent->bgcol);
      if (is_down)
        draw_raised(rp(0,0,area.h,area.h),bcol->c[2],false);
      else
        draw_gradient(Rect(0,0,area.h,area.h),bcol);
      label.draw(win,id.id2,Point(area.h+1,1));
      break;
    case 2: // for menu
      if (is_down) break;
      clear();
      label.draw(win,id.id2,Point(3,y));
      if (style.param==1)
        rectangleColor(win,0,0,area.w-1,area.h-1,dark);
      break;
    case 3: // 2D, no drawing if is_down
      if (is_down) break;
      clear();
      label.draw(win,id.id2,Point(3,y));
      if (style.param==1)
        rectangleColor(win,0,0,area.w-1,area.h-1,dark);
      break;
    case 4: // for menu, with variable label
      if (is_down) break;
      bgcol=cWhite; // restore bgcol
      clear();
      label.draw(win,id.id2,Point(3,y));
      SDL_FillRect(win,rp(area.w-12,0,12,area.h),parent->bgcol);
      rectangleColor(win,0,0,area.w-1,area.h-1,dark);
      vlineColor(win,area.w-13,0,area.h-1,dark);
      filledTrigonColor(win,area.w-10,y+5,area.w-4,y+5,area.w-7,y+10,0xff);
      break;
  }
}

RenderText::RenderText(TTF_Font *font,SDL_Color tcol):
    ttf_font(font),
    text_col(tcol) {
  for (int i=0;i<dim;++i) { chars[i]=0; ch_wid[i]=0; }
}

int RenderText::draw_string(SDL_Surface *win,const char *s,Point pt) {
  Rect offset(pt.x,pt.y,0,0);
  int ind;
  for (int i=0;s[i];++i) {
    if (s[i]=='\n') {
      offset.y+=TDIST; offset.x=pt.x;
      continue;
    }
    if (s[i]<' ') {
      alert("draw_string: unexpected char (nr: %d)",s[i]);
      return 0;
    }
    ind=s[i]-' ';
    if (!chars[ind]) set_char(ind);
    SDL_BlitSurface(chars[ind],0,win,&offset);
    offset.x+=ch_wid[ind];
  }
  return offset.y + TDIST;
}

void RenderText::set_char(const int ind) {
  static char buf[2];
  buf[0]=ind+' ';
  chars[ind]=TTF_RenderText_Blended(ttf_font,buf,text_col);
  ch_wid[ind]=chars[ind]->w;
}

int RenderText::text_width(const char *s) {
  int wid=0;
  if (!s) return char_wid;
  for (int i=0;s[i];++i) {
    if (s[i]<' ') {
      alert("text_width: bad char in string %s",s);
      return 0;
    }
    const int ind=s[i]-' ';
    if (!chars[ind]) set_char(ind);
    wid+=ch_wid[ind];
  }
  return wid;
}

struct UevQueue {  // user-event queue
  int queue_len,
    (*buffer)[3],
    ptr1,
    ptr2;
  UevQueue():
      queue_len(50),
      buffer(new int[queue_len][3]),
      ptr1(0),
      ptr2(0) {
  }
  void push(int param0,int param1,int param2) {
    int n=(ptr2+1)%queue_len;
    if (n==ptr1) {
      printf("push: inc queue-buffer len to: %d\n",2*queue_len);
      int (*prev)[3]=buffer,
          i1,i2,i3;
      buffer=new int[2*queue_len][3];
      for (i1=ptr1,i2=0;i1!=ptr2;i1=(i1+1)%queue_len,++i2)
        for (i3=0;i3<3;++i3)
          buffer[i2][i3]=prev[i1][i3];
      ptr1=0;
      n=queue_len;
      ptr2=n-1;
      queue_len*=2;
      delete[] prev;
    }
    buffer[ptr2][0]=param0;
    buffer[ptr2][1]=param1;
    buffer[ptr2][2]=param2;
    ptr2=n;
  }
  bool is_empty() { return ptr1==ptr2; }
  void pop(int *dat) {
    for (int i=0;i<3;++i)
      dat[i]=buffer[ptr1][i];
    ptr1=(ptr1+1)%queue_len;
  }
} uev_queue;

struct Repeat {
  bool on,
       direction;
  SDL_MouseButtonEvent ev;
} repeat;

void send_uev(int cmd,int param1,int param2) {
  SDL_mutexP(mtx);
  uev_queue.push(cmd,param1,param2);
  SDL_CondSignal(cond);
  SDL_mutexV(mtx);
}

WinBase *WinBase::in_a_win(int x,int y) {
  if (hidden) return 0;
  int x1=tw_area.x, y1=tw_area.y;
  if (x >= x1 && x < x1+tw_area.w && y >= y1 && y < y1+tw_area.h) {
    for (int i=lst_child;i>=0;--i) { // last child first
      WinBase *wb=children[i]->in_a_win(x,y);
      if (wb)
        return wb;
    }
    return this;
  }
  return 0;
}

void WinBase::draw_blit_recur() {  // from this downward, also the title is blitted
  draw(); // watch out in case of BgrWin!
  for (int i=0;i<=lst_child;++i)
    children[i]->draw_blit_recur();
  if (hidden) return;
  if (parent) {
    if (ontopw && parent==topw) {
      Rect *olap=0;
      if (!ontopw->backup_done) {
        ontopw->backup_done=true;
        SDL_BlitSurface(topw->win,&tw_area,ontopw->backup,0);
        if (!alert_win->bgr->hidden) {
          olap=calc_overlap(alert_win->bgr->tw_area,tw_area);
          if (olap) {
            Rect r1(olap->x - alert_win->bgr->tw_area.x,olap->y - alert_win->bgr->tw_area.y,olap->w,olap->h),
                 r2(max(0,alert_win->bgr->tw_area.x - tw_area.x),max(0,alert_win->bgr->tw_area.y - tw_area.y),0,0);
                 //printf("r1=%d %d %d %d, r2=%d %d\n",r1.x,r1.y,r1.w,r1.h,r2.x,r2.y);
            SDL_BlitSurface(alert_win->bgr->ontopw->backup,&r1,ontopw->backup,&r2);
          }
        }
      }
    }
    SDL_BlitSurface(win,0,parent->win,&area);
    if (title)
      SDL_BlitSurface(title,0,parent->win,&title_area);
  }
}

void WinBase::upd() {
  int n=0;
  if (ontopw) n=ontopw - ontop_win + 1;  // 1 past own entry
  for (;n<=ontop_nr;++n) {
    OnTopWin *otw=ontop_win+n;
    if (!otw->wb->hidden)
      otw->to_top(&tw_area);
  }
  SDL_UpdateRects(topw->win,1,&tw_area);
}


RButWin::RButWin(WinBase *pw,Style st,Rect rect,const char *tit,bool mbz,void(*cmd)(Id id,int nr,int fire),Id _id):
  WinBase(pw,tit,rect.x,rect.y,rect.w,rect.h,pw->bgcol,_id),
    y_off(0),
    b_dist(st.param2 ? st.param2 : TDIST),
    maybe_z(mbz),
    rb_cmd(cmd),
    d(&def_buttons),
    style(st) {
  win=SDL_CreateRGBSurface(SDL_SWSURFACE,area.w,area.h,pixdepth,0,0,0,0);
}

RButWin::~RButWin() {
  delete[] d->but; d->but=0;
}

RButWinData::RButWinData():
    butnr(-1),
    rb_max(20),
    but(new RButton[rb_max]),
    act_button(0) {
}

void RButWin::draw() {
  switch (style.st) {
    case 0: // normal
      draw_raised(0,cButBground,false);
      break;
    case 1: // checkboxes
      clear();
      break;
  }
  for (int i=0;i<=d->butnr;++i) draw_rbutton(d->but+i);
};

RButton* RButWin::is_in_rbutton(SDL_MouseButtonEvent *ev) {
  int nr=(ev->y-tw_area.y + y_off)/b_dist;
  if (nr>d->butnr) return 0;
  return d->but+nr;
}

void RButWin::draw_rbutton(RButton *rb) {
  int nr=rb-d->but,
      y1=nr*b_dist-y_off;
  if (y1<0 || y1>=area.h) return;
  switch (style.st) {
    case 0: // normal
      SDL_FillRect(win,rp(1,y1+1,area.w-2,b_dist-2),rb==d->act_button ? cSelRBut : cButBground);
      rb->label.draw(win,nr,Point(4,y1));
      break;
    case 1: // with checkboxes
      SDL_FillRect(win,rp(0,y1,area.w,b_dist),parent->bgcol);
      switch(style.param) {
        case 1:  // square
          draw_raised(rp(0,y1,12,12),cButBground,false);
          break;
        default: // round
          if (!pixm.rbut_pm) pixm.rbut_pm=create_pixmap(rbut_pm);
          SDL_BlitSurface(pixm.rbut_pm,0,win,rp(0,y1,0,0));
      }
      if (rb==d->act_button) {
        aacircleColor(win,6,y1+6,2,0xff);
        filledCircleColor(win,6,y1+6,1,0xff);
      }
      rb->label.draw(win,nr,Point(15,y1));
      break;
    case 2: // for menu
      SDL_FillRect(win,rp(1,y1+1,area.w-2,b_dist-2),rb==d->act_button ? cSelCmdMenu : cMenuBground);
      rb->label.draw(win,nr,Point(4,y1));
      break;
  }
}

int RButWin::act_rbutnr() {
  if (!d->act_button) return -1;
  return d->act_button-d->but;
}

void RButWin::reset() {
  d->butnr=-1; y_off=0;
  d->act_button=0;
  SDL_FillRect(win,0,bgcol);
}
void RButWin::set_rbut(RButton *rb,int fire,bool do_draw) {
  d->act_button=rb;
  if (fire && d->act_button->cmd) d->act_button->cmd(id,rb-d->but,fire);
  if (do_draw) draw_blit_upd();
}
void RButWin::set_rbutnr(int nr,int fire,bool do_draw) {
  if (nr>d->butnr)
    alert("set_rbutnr: bad index %d",nr);
  else {
    if (nr<0)
      d->act_button=0;
    else
      d->act_button=d->but+nr;
    if (fire && d->act_button && d->act_button->cmd) d->act_button->cmd(id,d->act_button-d->but,fire);
    if (do_draw) draw_blit_upd();
  }
}

RButton* RButWin::add_rbut(Label lab) {
  RButton *rb;
  int nr=d->next();
  d->but[nr].label=lab;
  rb=d->but+nr;
  if (nr==0 && !maybe_z) d->act_button=rb;
  rb->cmd=rb_cmd;
  if (sdl_running) draw_rbutton(rb);
  return rb;
}
int RButWinData::next() {
  if (butnr==rb_max-1) {
    but=re_alloc<RButton>(but,rb_max,RButton());
  }
  return ++butnr;
}
RButton::RButton(int nr1,Label lab):
  label(lab) {
}
RButton::RButton():label(0,0) {
}

ExtRButCtrl::ExtRButCtrl(Style st,void(*cmd)(Id,bool)):
    butnr(-1),
    maybe_z(true),
    style(st),
    act_lbut(0),
    reb_cmd(cmd) {
}

int ExtRButCtrl::next() {
  return ++butnr;
}

RExtButton::RExtButton(WinBase *pw,Style st,Rect rect,Label lab,Id _id):
    WinBase(pw,0,rect.x,rect.y,rect.w?rect.w:16,rect.h?rect.h:16,0,_id),
    style(st),
    label(lab) {
  win=SDL_CreateRGBSurface(SDL_SWSURFACE,area.w,area.h,pixdepth,0,0,0,0);
}

RExtButton *ExtRButCtrl::add_extrbut(WinBase *pw,Rect rect,Label lab,Id id) {
  RExtButton *rb;
  ++butnr;
  rb=new RExtButton(pw,style,rect,lab,id);
  rb->rxb_ctr=this;
  return rb;
}

void ExtRButCtrl::set_rbut(RExtButton* rb,int fire) {
  RExtButton *act_old=act_lbut;
  act_lbut=rb;
  if (act_old) act_old->draw_blit_upd();
  rb->draw_blit_upd();
  if (fire && reb_cmd) reb_cmd(rb->id,true);
}

void RExtButton::draw() {
  bool is_act = rxb_ctr->act_lbut==this;
  switch (style.st) {
    case 0: {  // especially for tabbed windows
    case 3:
        if (is_act)   // like draw_raised(), no lowest/left line
          SDL_FillRect(win,0,style.param);
        else {
          SDL_FillRect(win,0,cGrey);
          if (style.st==0) // for top tabs
            hlineColor(win,0,win->w-1,win->h-1,light);
          else             // for rightside tabs
            vlineColor(win,0,0,win->h-1,dark); 
        }
        hlineColor(win,1,win->w-1,0,light);
        vlineColor(win,win->w-1,0,win->h-2,dark); 
        if (style.st==0)
          vlineColor(win,0,0,win->h-1,light);
        else
          hlineColor(win,0,win->w-1,win->h-1,dark);
        label.draw(win,id.id2,Point(3,area.h/2-7));
      }
      break;
    case 1:
    case 2:
      if (style.st==1)
        draw_gradient(Rect(0,0,area.w,area.h),int2col5(style.param),false,true);
      else
        SDL_FillRect(win,rp(0,0,area.w,area.h),style.param);
      if (is_act) {
        rectangleColor(win,0,0,area.w-1,area.h-1,0xff0000ff);
      }
      else {
        rectangleColor(win,0,0,area.w-2,area.h-2,light);
        rectangleColor(win,1,1,area.w-1,area.h-1,dark);
      }
      label.draw(win,id.id2,Point(3,area.h/2-8));
      break;
  }
}

TextWin::TextWin(WinBase *pw,Style st,Rect rect,int lm,const char *t,Id _id):
    WinBase(pw,t,rect.x,rect.y,rect.w,rect.h?rect.h:lm*TDIST+4,cWhite,_id),
    linenr(-1),
    lmax(lm),
    style(st),
    textbuf(new char[lmax][SMAX]) {
  win=SDL_CreateRGBSurface(SDL_SWSURFACE,area.w,area.h,pixdepth,0,0,0,0);
}

void TextWin::add_text(const char *s,bool do_draw) {
  const char *p,
             *prev=s;
  for (p=s;p<s+SMAX;++p) {
    if (!*p || *p=='\n') {
      ++linenr;
      char *txt=textbuf[linenr % lmax];
      strncpy(txt,prev,p-prev);
      txt[p-prev]=0;
      if (!*p) break;
      prev=p+1;
    }
  }
  if (do_draw) draw_blit_upd();
}

void TextWin::reset() {
  linenr=-1;
  switch (style.st) {
    case 0: clear(); break;
    case 1: draw_raised(0,bgcol,style.param);
  }
}

void TextWin::draw() {
  int n,n1;
  char *p;
  switch (style.st) {
    case 0: clear(); break;
    case 1: draw_raised(0,bgcol,style.param); break;
  }
  n= linenr<lmax ? 0 : linenr-lmax+1;
  for (n1=n;n1<n+lmax && n1<=linenr;++n1) {
    int ind=n1%lmax;
    p=textbuf[ind];
    draw_ttf->draw_string(win,p,Point(4,TDIST*(n1-n+1)-12));
  }
}

HScrollbar::HScrollbar(WinBase *pw,Style st,Rect rect,int r,void (*_cmd)(Id,int,int),Id _id):
    WinBase(pw,0,rect.x,rect.y,rect.w,rect.h ? rect.h : 12,cSlBackgr,_id),
    p0(0),xpos(0),value(0),
    style(st),
    ssdim(style.st==1 ? 10 : 0),
    cmd(_cmd) {
  win=SDL_CreateRGBSurface(SDL_SWSURFACE,area.w,area.h,pixdepth,0,0,0,0);
  if (!style.param2) style.param2=5; // step if repeat
  calc_params(r);
}

void HScrollbar::calc_params(int r) {
  range=max(area.w-2*ssdim,r-2*ssdim);
  wid=max(2,(area.w-2*ssdim) * (area.w-2*ssdim) / range);
}

void HScrollbar::set_range(int r) {
  calc_params(r);
  int xp=minmax(0,value*(area.w-2*ssdim)/range,area.w-2*ssdim-wid);
  p0-=xpos-xp;
  xpos=xp;
  draw_blit_upd();
}

void HScrollbar::draw() {
  draw_raised(0,bgcol,false);
  if (grad) draw_gradient(Rect(xpos+ssdim,0,wid,area.h-1),&cGradientDarkBlue);
  else draw_raised(rp(xpos+ssdim,1,wid,area.h-2),cScrollbar,true);
  if (style.st==1) {
    draw_raised(rp(0,1,ssdim,area.h-2),cScrollbar,true);
    draw_raised(rp(area.w-ssdim,1,ssdim,area.h-2),cScrollbar,true);
    filledTrigonColor(win,6,2,6,8,2,5,0xff);
    filledTrigonColor(win,area.w-7,2,area.w-4,5,area.w-7,8,0xff);
  }
}

void HScrollbar::inc_value(bool incr) {
  int val=value,
      xp;
  if (incr) {
    val+=style.param2;
    xp=val*(area.w-2*ssdim)/range;
    if (xp>area.w-2*ssdim-wid) return;
  }
  else {
    val-=style.param2;
    if (val<0) return;
    xp=val*(area.w-2*ssdim)/range;
  }
  value=val;
  if (cmd) cmd(id,value,range);
  if (xp!=xpos) {
    xpos=xp;
    draw_blit_upd();
  }
}

void HScrollbar::calc_xpos(int newx) {
  int xp=minmax(0,xpos + newx - p0,area.w-2*ssdim-wid);
  p0=newx;
  if (xp!=xpos) {
    xpos=xp;
    value=xpos * range / (area.w-2*ssdim);
    draw_blit_upd();
    if (cmd) cmd(id,value,range);
  }
}

void HScrollbar::set_xpos(int newx) {
  int xp=minmax(0,newx*(area.w-2*ssdim)/range,area.w-2*ssdim-wid);
  if (xp!=xpos) {
    xpos=xp;
    draw_blit_upd();
  }
}

bool HScrollbar::in_ss_area(SDL_MouseButtonEvent *ev,bool *dir) {
  if (style.st==0) return false;
  if (ev->x >= tw_area.x + area.w - ssdim) { if (dir) *dir=true; return true; }
  if (ev->x <= tw_area.x + ssdim) { if (dir) *dir=false; return true; }
  return false;
}

VScrollbar::VScrollbar(WinBase *pw,Style st,Rect rect,int r,void (*_cmd)(Id,int,int),Id _id):
    WinBase(pw,0,rect.x,rect.y,rect.w ? rect.w : 12,rect.h,cSlBackgr,_id),
    p0(0),ypos(0),value(0),
    style(st),
    ssdim(style.st==1 ? 10 : 0),
    cmd(_cmd) {
  win=SDL_CreateRGBSurface(SDL_SWSURFACE,area.w,area.h,pixdepth,0,0,0,0);
  if (!style.param2) style.param2=5; // step if repeat
  calc_params(r);
}

void VScrollbar::set_range(int r) {
  calc_params(r);
  int yp=minmax(0,value*(area.h-2*ssdim)/range,area.h-2*ssdim-height);
  p0-=ypos-yp;
  ypos=yp;
  draw_blit_upd();
}

void VScrollbar::calc_params(int r) {
  range=max(area.h-2*ssdim,r-2*ssdim);
  height=max((area.h-2*ssdim) * (area.h-2*ssdim) / range,2);
}

void VScrollbar::draw() {
  draw_raised(0,bgcol,false);
  if (grad) draw_gradient(Rect(0,ypos+ssdim,area.w-1,height),&cGradientDarkBlue,true);
  else draw_raised(rp(1,ypos+ssdim,area.w-2,height),cScrollbar,true);
  if (style.st==1) {
    draw_raised(rp(1,0,area.w-2,ssdim),cScrollbar,true);
    draw_raised(rp(1,area.h-ssdim,area.w-2,ssdim),cScrollbar,true);
    filledTrigonColor(win,2,5,8,5,5,1,0xff);
    filledTrigonColor(win,2,area.h-7,5,area.h-3,8,area.h-7,0xff);
  }
}

void VScrollbar::inc_value(bool incr) {
  int val=value,
      yp;
  if (incr) { 
    val+=style.param2;
    yp=val*(area.h-2*ssdim)/range;
    if (yp>area.h-2*ssdim-height) return;
  }
  else {
    val-=style.param2;
    if (val<0) return;
    yp=val*(area.h-2*ssdim)/range;
  }
  value=val;
  if (cmd) cmd(id,value,range);
  if (yp!=ypos) {
    ypos=yp;
    draw_blit_upd();
  }
}

void VScrollbar::calc_ypos(int newy) {
  int yp=minmax(0,ypos + newy - p0,area.h-2*ssdim-height);
  p0=newy;
  if (yp!=ypos) {
    ypos=yp;
    value=ypos * range / (area.h-2*ssdim);
    draw_blit_upd();
    if (cmd) cmd(id,value,range);
  }
}

void VScrollbar::set_ypos(int newy) {
  int yp=minmax(0,newy*(area.h-2*ssdim)/range,area.h-2*ssdim-height);
  if (yp!=ypos) {
    ypos=yp;
    draw_blit_upd();
  }
}

bool VScrollbar::in_ss_area(SDL_MouseButtonEvent *ev,bool *dir) {
  if (style.st==0) return false;
  if (ev->y >= tw_area.y + area.h - ssdim) { if (dir) *dir=true; return true; }
  if (ev->y <= tw_area.y + ssdim) { if (dir) *dir=false; return true; }
  return false;
}

Lamp::Lamp(WinBase *pw,Rect r):
    pwin(pw),
    rect(r),
    col(light),
    hidden(false) {
}

void Lamp::draw() {
  if (hidden) return;
  if (!pixm.lamp_pm) pixm.lamp_pm=create_pixmap(lamp_pm);
  SDL_BlitSurface(pixm.lamp_pm,0,pwin->win,&rect);
  filledCircleColor(pwin->win,rect.x+6,rect.y+6,4,col);
}

void Lamp::set_color(Uint32 c) {
  col=c;
  draw();
  pwin->blit_upd(&rect);
}

void Lamp::show() {
  if (!hidden) return;
  hidden=false;
  draw();
  pwin->blit_upd(&rect);
}

Message::Message(WinBase *pw,Style st,const char* lab,Point top):
    pwin(pw),
    style(st),
    label(lab),
    lab_pt(top),
    mes_r(0,top.y,0,15),
    bgcol(style.param ? style.param : pw->bgcol) {
  mes_r.x= label ? draw_ttf->text_width(label)+top.x+2 : top.x;
}

void Message::draw_label(bool upd) {
  draw_ttf->draw_string(pwin->win,label,lab_pt);
  if (upd) pwin->blit_upd(rp(lab_pt.x,lab_pt.y,mes_r.x,14));
}

void Message::draw_mes(const char *form,...) {
  va_list ap;
  va_start(ap,form);
  draw_message(form,ap);
  va_end(ap);
  pwin->blit_upd(&mes_r);
}

void Message::draw(const char *form,...) {
  draw_label();
  va_list ap;
  va_start(ap,form);
  draw_message(form,ap);
  va_end(ap);
}

void Message::draw_message(const char *form,va_list ap) {
  char buf[100];
  vsnprintf(buf,100,form,ap);
  int twid=draw_title_ttf->text_width(buf);
  switch (style.st) {
    case 0:  // bold font
      if (mes_r.w<twid) mes_r.w=twid;
      SDL_FillRect(pwin->win,&mes_r,bgcol);
      draw_title_ttf->draw_string(pwin->win,buf,Point(mes_r.x,mes_r.y));
      break;
    case 1:  // 3D
      if (mes_r.w<twid+2) mes_r.w=twid+2;
      pwin->draw_raised(&mes_r,bgcol,false);
      draw_title_ttf->draw_string(pwin->win,buf,Point(mes_r.x+1,mes_r.y));
      break;
    case 2:  // regular font
      if (mes_r.w<twid) mes_r.w=twid;
      SDL_FillRect(pwin->win,&mes_r,bgcol);
/*
        //not yet available in Ubuntu 8.10:
        int r=mes_r.h/2;
        arcColor(pwin->win,mes_r.x+r,mes_r.y+r,r,90,270,dark);
*/
      draw_ttf->draw_string(pwin->win,buf,Point(mes_r.x,mes_r.y));
  }
}

CmdMenu::CmdMenu(Button *_src):
    nr_buttons(0),
    src(_src),
    buttons(0) {
}

bool CmdMenu::init(int wid,int nr_b,void (*menu_cmd)(Id id,int nr,int fire)) {
  if (the_menu) the_menu->close();
  if (buttons) return false;
  nr_buttons=nr_b;
  src->bgcol=cSelCmdMenu;
  int bdist=src->style.param2 ? src->style.param2 : TDIST,
      left=src->tw_area.x,
      top=src->tw_area.y+src->area.h-1,
      height=nr_buttons*bdist+2;
  if (top+height>topw->area.h) {
    left+=src->area.w;
    top=max(0,src->tw_area.y-height/2);
    if (top+height>topw->area.h)
      top=max(0,topw->area.h-height);
  }
  else if (left+wid>topw->area.w)
    left=src->area.x+src->area.w-wid;
  mrect.set(left,top,wid,height);
  if (!alert_win->bgr->hidden && calc_overlap(mrect,alert_win->bgr->tw_area))
    alert_win->bgr->hide();
  backup=SDL_CreateRGBSurface(SDL_SWSURFACE,mrect.w,mrect.h,pixdepth,0,0,0,0);
  SDL_BlitSurface(topw->win,&mrect,backup,0);
  buttons=new RButWin(topw,Style(2,0,bdist),mrect,0,true,menu_cmd,src->id);
  buttons->bgcol=cMenuBground;
  buttons->clear();
  rectangleColor(buttons->win,0,0,mrect.w-1,mrect.h-1,dark);
  the_menu_tmp=this;
  return true;
}

void CmdMenu::add_mbut(Label lab) {
  buttons->add_rbut(lab);
  int nrb=buttons->d->butnr;
  if (nrb==nr_buttons-1) buttons->blit_upd(0);
  else if (nrb>=nr_buttons) alert("add_mbut: > %d buttons",nr_buttons);
}

void CmdMenu::close() {
  src->bgcol=src->parent->bgcol;
  src->draw_blit_upd();
  delete buttons;
  buttons=0;
  the_menu=0;
  if (backup) { // can be 0
    SDL_BlitSurface(backup,0,topw->win,&mrect); // restore the screen
    SDL_UpdateRects(topw->win,1,&mrect);
    SDL_FreeSurface(backup);
    backup=0;
  }
}

void WinBase::draw_blit_upd() {  // from child upward, title is not blitted
  if (!sdl_running) return;
  draw();
  blit_upd(0);
}

void WinBase::blit_upd(Rect *rect) {
  if (!sdl_running) return;
  WinBase *wb,*nxt,
          *ot=0; // on-top window?
  Rect r1,r2;
  if (rect) r1=*rect;
  else { r1=area; r1.x=r1.y=0; }
  r2=r1;
  if (this==topw) update(&r2);
  else {
    bool hid=false;
    for (wb=this,nxt=parent;nxt;wb=nxt,nxt=nxt->parent) {
      if (wb->hidden) hid=true;
      if (hid && wb->parent==topw) return;
      if (wb->ontopw) ot=wb;
      r2.x+=wb->area.x; r2.y+=wb->area.y;
      SDL_BlitSurface(wb->win,&r1,nxt->win,&r2);
      if (wb->parent==topw) break;
      r1.x+=wb->area.x; r1.y+=wb->area.y;
    }
    // now: wb->parent = topw, r2 = wb->tw_area
    if (!hid) update(&r2,ot);
  }
}

void WinBase::border(WinBase *child,int wid) {  // default: wid=1
  Rect r=child->area;
  switch (wid) {
    case 1:
      rectangleColor(win,r.x-1,r.y-1,r.x+r.w,r.y+r.h,dark);
      break;
    case 2:
      rectangleColor(win,r.x-2,r.y-2,r.x+r.w+1,r.y+r.h+1,light);
      rectangleColor(win,r.x-1,r.y-1,r.x+r.w+1,r.y+r.h+1,dark);
      break;
    case 3:
      rectangleColor(win,r.x-3,r.y-3,r.x+r.w+1,r.y+r.h+1,light);
      rectangleColor(win,r.x-2,r.y-2,r.x+r.w+2,r.y+r.h+2,dark);
      rectangleColor(win,r.x-1,r.y-1,r.x+r.w,r.y+r.h,0xa0a0a0ff);
      rectangleColor(win,r.x-4,r.y-4,r.x+r.w+3,r.y+r.h+3,0xa0a0a0ff);
      break;
    }
  child->reloc_title(0,-wid); //title_area.y=child->area.y-15-wid;
}

void WinBase::widen(int dx,int dy) {
  area.w += dx; tw_area.w += dx;
  area.h += dy; tw_area.h += dy;
  SDL_FreeSurface(win);
  win=SDL_CreateRGBSurface(SDL_SWSURFACE,area.w,area.h,pixdepth,0,0,0,0);
}

void WinBase::move(int dx,int dy) {
  area.x += dx; title_area.x += dx;
  area.y += dy; title_area.y += dy;
  move_tw_area(this,dx,dy); 
}

void WinBase::keep_on_top() {
  if (ontopw) return; // called more then once?
  if (ontop_nr>=max_ontopwin-1) {
    alert("ontop windows > %d",max_ontopwin); return;
  }
  OnTopWin *otw;
  if (alert_win && ontop_win[ontop_nr].wb==alert_win->bgr) {
    otw=ontop_win+ontop_nr;
    ontop_win[++ontop_nr]=*otw;
    alert_win->bgr->ontopw=ontop_win+ontop_nr;
  }
  else {
    ++ontop_nr;
    otw=ontop_win+ontop_nr;
  }
  otw->wb=this;
  ontopw=otw;
  otw->backup=SDL_CreateRGBSurface(SDL_SWSURFACE,tw_area.w,tw_area.h,pixdepth,0,0,0,0);
  otw->backup_done=false;
}

struct TheCursor {
  struct DialogWin *diawd;
  struct EditWin *edwd;
  bool keep;
  void unset() {
    if (diawd) diawd->unset_cursor(); diawd=0;
    /*if (edwd) edwd->unset_cursor();*/ edwd=0;
  }
} the_cursor;

struct Line {
  int dlen,     // occupied in data[]
      leftside,
      dmax;     // length of data[]
  char *data;
  Line():dlen(0),leftside(0),dmax(50),data(new char[dmax]) {
    data[0]=0;
  }
  int xpos(int nr) {
    return 2+char_wid*(nr-leftside);
  }
  void reset() { data[0]=0; dlen=leftside=0; }
  void insert_char(int ks,int& n) {
    int i;
    if (dlen>=dmax-1) data=re_alloc<char>(data,dmax,0);
    for (i=dlen;i>n;--i) data[i]=data[i-1];
    data[i]=ks;
    ++dlen; ++n; data[dlen]=0;
  }
  void rm_char(int& n) {
    int i;
    if (dlen==0 || n==0) return;
    for (i=n-1;i<dlen;++i) data[i]=data[i+1];
    --dlen; --n;
  }
  void cpy(const char *s) {
    dlen=strlen(s);
    while (dmax<=dlen) data=re_alloc<char>(data,dmax,0);
    strcpy(data,s);
  }
  void cat(char *s) {
    dlen+=strlen(s);
    while (dmax<=dlen) data=re_alloc<char>(data,dmax,0);
    strcat(data,s);
  }
};

DialogWin::DialogWin(WinBase *pw,Rect rect,Id _id):
    WinBase(pw,0,rect.x,rect.y,rect.w,2*TDIST+2,pw->bgcol,_id),
    textr(0,TDIST,rect.w,TDIST+2),
    cursor(-1),
    label(0),
    lin(new Line),
    cmd(0) {
  win=SDL_CreateRGBSurface(SDL_SWSURFACE,area.w,area.h,pixdepth,0,0,0,0);
}

void DialogWin::set_cursor(int pos) {
  cursor=pos;
  if (the_cursor.edwd) { /*the_cursor.edwd->unset_cursor();*/ the_cursor.edwd=0; }
  the_cursor.diawd=this;
  print_line();
}

void DialogWin::unset_cursor() {
  if (cursor>=0) {
    cursor=-1;
    lin->leftside=0;
    print_line();
  }
}

void DialogWin::dok() {
  if (cmd) cmd(lin->data,cmd_id);
  else alert("dialog: no action specified");
}

void DialogWin::draw() {
  clear(Rect(0,0,area.w,TDIST));
  draw_raised(&textr,cButBground,false);
  if (label)
    draw_ttf->draw_string(win,label,Point(2,0));
}

void DialogWin::dialog_label(const char *s,Uint32 col) {
  label=s;
  Rect rect(0,0,area.w,TDIST);
  clear(rect);
  blit_upd(&rect);
  if (label) {
    rect.w=draw_ttf->text_width(label)+3;
    if (col) clear(rect,col,false);
    draw_ttf->draw_string(win,label,Point(2,0));
    blit_upd(&rect);
  }
}

void DialogWin::print_line() {
  char *s=lin->data;
  const int rmargin=15;
  draw_raised(&textr,cButBground,false);
  if (cursor>=0) {
    if (lin->xpos(cursor)>=area.w-rmargin)
      lin->leftside=cursor - (area.w-rmargin)/char_wid;
    int x1=lin->xpos(cursor);
    vlineColor(win,x1,textr.y+2,textr.y+textr.h-2,0xff);
  }
  draw_mono_ttf->draw_string(win,s+lin->leftside,Point(2,textr.y));
  blit_upd(&textr);
}

void DialogWin::dialog_def(const char *s,void(*_cmd)(const char*,int cmdid),int _cmd_id) {
  cmd=_cmd;
  cmd_id=_cmd_id;
  if (s) {
    lin->data[lin->dmax-1]=0;
    strncpy(lin->data,s,lin->dmax-1);
    lin->dlen=strlen(lin->data);
  }
  else { lin->dlen=0; lin->data[0]=0; }
  lin->leftside=0;
  set_cursor(lin->dlen);
  the_cursor.keep=true;
}

static void lr_shift(int sym,int &ks) {  // left or right shift key
  if (sym!=KMOD_LSHIFT && sym!=KMOD_RSHIFT) return;
  switch (ks) {  // todo: more symbols
    case '`': ks='~'; break;
    case '1': ks='!'; break;
    case '2': ks='@'; break;
    case '3': ks='#'; break;
    case '4': ks='$'; break;
    case '5': ks='%'; break;
    case '6': ks='^'; break;
    case '7': ks='&'; break;
    case '8': ks='*'; break;
    case '9': ks='('; break;
    case '0': ks=')'; break;
    case '-': ks='_'; break;
    case '=': ks='+'; break;
    case '\\': ks='|'; break;
    case '[': ks='{'; break;
    case ']': ks='}'; break;
    case '\'': ks='"'; break; 
    case ',': ks='<'; break;
    case '.': ks='>'; break;
    case '/': ks='?'; break;
    default:
      if (ks>='a' && ks<='z')
        ks += 'A'-'a';
      else ks='?';
  }
}

bool DialogWin::handle_key(SDL_keysym *key,bool down) {
  int ks=key->sym; // sym is an enum
  if (!down) return false;
  switch (ks) {
    case SDLK_LCTRL: case SDLK_RCTRL:
      break;
    case SDLK_RETURN:
      unset_cursor();
      return true;
    case SDLK_BACKSPACE:
      lin->rm_char(cursor);
      if (lin->xpos(cursor)<10 && lin->leftside>0) --lin->leftside;
      print_line();
      break;
    case SDLK_LEFT:
      if (cursor>0) {
        --cursor;
        if (lin->xpos(cursor)<10 && lin->leftside>0) --lin->leftside;
        print_line();
      }
      break;
    case SDLK_RIGHT:
      if (cursor<lin->dlen) {
        ++cursor;
        if (lin->xpos(cursor)>=area.w-10) ++lin->leftside;
        print_line();
      }
      break;
    default:
      if (ks>=0x20 && ks<0x80) {
        if (key->mod==KMOD_LCTRL || key->mod==KMOD_RCTRL) {
          if (ks==SDLK_d) {
            lin->reset();
            cursor=0; 
            print_line();
          }
        }
        else {
          lr_shift(key->mod,ks);
          lin->insert_char(ks,cursor);
          if (lin->xpos(cursor)>area.w-10) ++lin->leftside;
          print_line();
        }
      }
  }
  return false;
}


void handle_events(SDL_Event *ev) {
   SDL_MouseButtonEvent *mbe=reinterpret_cast<SDL_MouseButtonEvent*>(ev);
/* does not work: motion events of right button get button nr 4 instead of SDL_BUTTON_RIGHT
   switch (mbe->button) { 
     case SDL_BUTTON_LEFT:case SDL_BUTTON_MIDDLE:case SDL_BUTTON_RIGHT: break;
     default: return;
   }
*/
   WinBase *wb;
   static Button *but=0;
   static DialogWin *dial=0;
   CheckBox *chb=0;
   static RExtButton *rxbw=0;
   static HSlider *hsl=0;
   static VSlider *vsl=0;
   static HVSlider *hvsl=0;
   static HScrollbar *hscr=0;
   static VScrollbar *vscr=0;
   static BgrWin *bgw;
   RButWin *rbw=0;
   switch (ev->type) {
     case SDL_MOUSEBUTTONDOWN: { 
         wb=topw->in_a_win(mbe->x,mbe->y);
         if (!wb) break;
         if ((but=dynamic_cast<Button*>(wb))!=0) {
             but->is_down=true;
             if (but->cmd) but->cmd(but->id);
             but->draw_blit_upd();
         }
         else if ((dial=dynamic_cast<DialogWin*>(wb))!=0) {
           dial->set_cursor(min(dial->lin->dlen,(mbe->x - dial->tw_area.x)/char_wid));
           the_cursor.keep=true;
         }
         else if ((chb=dynamic_cast<CheckBox*>(wb))!=0) {
           *chb->d=!*chb->d;
           chb->draw_blit_upd();
           if (chb->cmd) chb->cmd(chb);
         }
         else if ((hsl=dynamic_cast<HSlider*>(wb))!=0) {
           SDL_EventState(SDL_MOUSEMOTION,SDL_ENABLE);
         }
         else if ((vsl=dynamic_cast<VSlider*>(wb))!=0) {
           SDL_EventState(SDL_MOUSEMOTION,SDL_ENABLE);
         }
         else if ((hvsl=dynamic_cast<HVSlider*>(wb))!=0) {
           SDL_EventState(SDL_MOUSEMOTION,SDL_ENABLE);
         }
         else if ((bgw=dynamic_cast<BgrWin*>(wb))!=0) {
           if (bgw->down_cmd)
             bgw->down_cmd(bgw->id,mbe->x-bgw->tw_area.x,mbe->y-bgw->tw_area.y,mbe->button);
         }
         else if ((rbw=dynamic_cast<RButWin*>(wb))!=0) {
           RButton *rb=rbw->is_in_rbutton(mbe);
           if (rb) {
             if (rb==rbw->d->act_button) {
               if (rbw->maybe_z)
                 rbw->d->act_button=0;
             }
             else
               rbw->d->act_button=rb;
             rbw->draw_blit_upd();
             if (rb->cmd) rb->cmd(rbw->id,rb-rbw->d->but,1);
           }
         }
         else if ((rxbw=dynamic_cast<RExtButton*>(wb))!=0) {
           ExtRButCtrl *ctr=rxbw->rxb_ctr;
           bool is_act = ctr->act_lbut==rxbw;
           if (is_act) {
             if (ctr->maybe_z) {
               RExtButton *rb=ctr->act_lbut;
               ctr->act_lbut=0;
               if (ctr->reb_cmd)
                 ctr->reb_cmd(rxbw->id,false);
               rb->draw_blit_upd();
             }
           }
           else {
             if (ctr->act_lbut) {
               RExtButton *rb=ctr->act_lbut;
               ctr->act_lbut=rxbw;
               if (ctr->reb_cmd) ctr->reb_cmd(rb->id,false);
               rb->draw_blit_upd();
             }
             else ctr->act_lbut=rxbw;
             if (ctr->reb_cmd) ctr->reb_cmd(rxbw->id,true);
             rxbw->draw_blit_upd();
           }
         }
         else if ((hscr=dynamic_cast<HScrollbar*>(wb))!=0) {
           if (repeat.on)
             hscr->inc_value(repeat.direction);
           else {
             if (hscr->in_ss_area(mbe,&repeat.direction)) {
               if (repeat.on)
                 hscr->inc_value(repeat.direction);
               else {
                 repeat.ev=*mbe;
                 repeat.on=true;
               }
             }
             else {
               hscr->p0=mbe->x;
               SDL_EventState(SDL_MOUSEMOTION,SDL_ENABLE);
             }
           }
         }
         else if ((vscr=dynamic_cast<VScrollbar*>(wb))!=0) {
           if (repeat.on)
             vscr->inc_value(repeat.direction);
           else {
             if (vscr->in_ss_area(mbe,&repeat.direction)) {
               if (repeat.on)
                 vscr->inc_value(repeat.direction);
               else {
                 repeat.ev=*mbe;
                 repeat.on=true;
               }
             }
             else {
               vscr->p0=mbe->y;
               SDL_EventState(SDL_MOUSEMOTION,SDL_ENABLE);
             }
           }
         }
         if (the_menu && the_menu->buttons && rbw!=the_menu->buttons)
           the_menu->close(); // sets the_menu=0
         if (!the_cursor.keep)
           the_cursor.unset();
         the_cursor.keep=false;
       }
       break;
     case SDL_MOUSEMOTION:
       if (hsl) {
         int val=*hsl->d;
         hsl->calc_hslval(mbe->x - hsl->tw_area.x);
         if (val!=*hsl->d) {
           if (hsl->cmd) hsl->cmd(hsl,1,false);
           hsl->draw_blit_upd();
         }
       }
       else if (vsl) {
         int val=*vsl->d;
         vsl->calc_vslval(mbe->y - vsl->tw_area.y);
         if (val!=*vsl->d) {
           if (vsl->cmd) vsl->cmd(vsl,1,false);
           vsl->draw_blit_upd();
         }
       }
       else if (hvsl) {
         Int2 val=*hvsl->d;
         hvsl->calc_hvslval(Int2(mbe->x - hvsl->tw_area.x,mbe->y - hvsl->tw_area.y));
         if (val!=*hvsl->d) {
           if (hvsl->cmd) hvsl->cmd(hvsl,1,false);
           hvsl->draw_blit_upd();
         }
       }
       else if (bgw) {
         if (bgw->moved_cmd)
           bgw->moved_cmd(bgw->id,mbe->x-bgw->tw_area.x,mbe->y-bgw->tw_area.y,mbe->button);
       }
       else if (hscr) {
         if (!repeat.on)
           hscr->calc_xpos(mbe->x);
       }
       else if (vscr) {
         if (!repeat.on)
           vscr->calc_ypos(mbe->y);
       }
       break;
     case SDL_MOUSEBUTTONUP:
       SDL_EventState(SDL_MOUSEMOTION,SDL_IGNORE);
       if (but) {
         but->is_down=false;
         but->draw_blit_upd();
         the_menu=the_menu_tmp;
         the_menu_tmp=0;
         but=0;
       }
       else if (hsl) {
         if (hsl->cmd) hsl->cmd(hsl,1,true);
         hsl=0;
       }
       else if (vsl) {
         if (vsl->cmd) vsl->cmd(vsl,1,true);
         vsl=0;
       }
       else if (hvsl) {
         if (hvsl->cmd) hvsl->cmd(hvsl,1,true);
         hvsl=0;
       }
       else if (bgw) {
         if (bgw->up_cmd)
           bgw->up_cmd(bgw->id,mbe->x-bgw->tw_area.x,mbe->y-bgw->tw_area.y,mbe->button);
         bgw=0;
       }
       else if (hscr) {
         repeat.on=false;
         hscr=0;
       }
       else if (vscr) {
         repeat.on=false;
         vscr=0;
       }
       break;
     case SDL_VIDEOEXPOSE:
       say("expose");
       topw->draw_blit_recur();
       SDL_Flip(topw->win);
       break;
     case SDL_ACTIVEEVENT:
       //say("active");
       break;
     case SDL_KEYDOWN:
       //SDL_KeyboardEvent *kbe=reinterpret_cast<SDL_KeyboardEvent*>(ev);
       if (the_cursor.diawd && the_cursor.diawd->cursor>=0) {
         if (the_cursor.diawd->handle_key(&ev->key.keysym,true)) // enter key pressed?
           the_cursor.diawd->dok();
       }
       else if (handle_kev) handle_kev(&ev->key.keysym,true);
       break;
     case SDL_KEYUP:
       if (the_cursor.diawd) the_cursor.diawd->handle_key(&ev->key.keysym,false);
       else if (handle_kev) handle_kev(&ev->key.keysym,false);
       break;
     case SDL_VIDEORESIZE: {
         int dw=ev->resize.w - topw->area.w,
             dh=ev->resize.h - topw->area.h;
         if (abs(dw)>=10 || abs(dh)>=10) {
           topw->area.w=topw->tw_area.w=ev->resize.w;
           topw->area.h=topw->tw_area.h=ev->resize.h;
           topw->win=SDL_SetVideoMode(ev->resize.w, ev->resize.h,pixdepth,video_flag);
           bool a_hid=alert_win->bgr->hidden;
           alert_win->bgr->hidden=true;
           if (handle_rsev)
             handle_rsev(dw,dh);
           else
             topw->draw_blit_recur();
           if (a_hid) SDL_Flip(topw->win);
           else {
             alert_win->bgr->show();
           }
         }
       }
       break;
     default:
       printf("unexpected event %d\n",ev->type);
   }
}

static void def_handle_uev(int cmd,int par1,int par2) {
  printf("user event: %d\n",cmd);
}

int keep_alivefun(void* data) {
  while (!quit) {
    ++go_ticks; // no mutex
    SDL_Delay(50); 
    send_uev('go');
  }
  return 0;
}

void get_events() {
  topw->draw_blit_recur();
  SDL_Flip(topw->win);
  sdl_running=true;
  SDL_Event ev;
  SDL_CreateThread(keep_alivefun,0);
  while (!quit) {
    while (true) {
      SDL_mutexP(mtx);
      if (uev_queue.is_empty()) {
        SDL_mutexV(mtx);
        break;
      }
      int uev_dat[3];
      uev_queue.pop(uev_dat);
      SDL_mutexV(mtx);
      if (!handle_uev) handle_uev=def_handle_uev;
      if (uev_dat[0]=='go') {
        if (repeat.on && go_ticks%3==0)  // 3 * 50 ms
          handle_events(reinterpret_cast<SDL_Event*>(&repeat.ev));
      }
      else
        handle_uev(uev_dat[0],uev_dat[1],uev_dat[2]);
    }
    if (SDL_PollEvent(&ev)) {
      if (ev.type==SDL_QUIT) {
        quit=true; break;
      }
      handle_events(&ev);
    }
    else {
      SDL_mutexP(mtx);
      SDL_CondWait(cond,mtx);
      SDL_mutexV(mtx);
    }
  }
  sdl_quit(0);
}
