/**************************************************************************

    This file is part of Xclass95, a Win95-looking GUI toolkit.
    Copyright (C) 1996, 1997 David Barth, Hector Peraza.

    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 library; if not, write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

**************************************************************************/

#include <xclass/utils.h>
#include <xclass/OXWidgets.h>
#include <xclass/ODimension.h>
#include <xclass/OXClient.h>
#include <xclass/OString.h>


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

void OXLBEntry::Activate(int a) {
  if (_active == a) return;
  _active = a;
  _client->NeedRedraw(this);
}

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

OXTextLBEntry::OXTextLBEntry(const OXWindow *p, OString *s, int ID,
      GC norm, XFontStruct *font, unsigned int options, unsigned long back) :
  OXLBEntry(p, ID, options, back) {

    _text = s;
    _fontStruct = font;
    _normGC = norm;

    _tw = XTextWidth(_fontStruct, _text->GetString(), _text->GetLength());
    _th = _fontStruct->max_bounds.ascent + _fontStruct->max_bounds.descent;
    Resize(_tw, _th + 1);
}

OXTextLBEntry::~OXTextLBEntry() {
  if (_text) delete _text;
}

void OXTextLBEntry::_DoRedraw() {
  int x, y;
  
  //OXFrame::_DoRedraw();

  x = 3;
  y = (_h - _th) >> 1;

  if (_active) {
    SetBackgroundColor(_defaultSelectedBackground);
    XClearWindow(GetDisplay(), _id);
    XSetForeground(GetDisplay(), _normGC, _selPixel);
    _text->Draw(GetDisplay(), _id, _normGC, x, y + _fontStruct->max_bounds.ascent);
  } else {
    SetBackgroundColor(_bkcolor);
    XClearWindow(GetDisplay(), _id);
    XSetForeground(GetDisplay(), _normGC, _blackPixel);
    _text->Draw(GetDisplay(), _id, _normGC, x, y + _fontStruct->max_bounds.ascent);
  }
}

void OXTextLBEntry::SetText(OString *new_text) {

  if (_text) delete _text;
  _text = new_text;

  _tw = XTextWidth(_fontStruct, _text->GetString(), _text->GetLength());
  _th = _fontStruct->max_bounds.ascent + _fontStruct->max_bounds.descent;

  Resize(_tw, _th + 1);  //////

  _client->NeedRedraw(this);
}

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

OXLBContainer::OXLBContainer(const OXWindow *p, int w, int h,
                             unsigned int options, unsigned long back) :
  OXCompositeFrame(p, w, h, options, back) {

    _last_active = NULL;
    _msgWindow = p;

    _multipleSelect = False;
    _dragging = False;

//    SetLayoutManager(new OColumnLayout(this, 0));

    XGrabButton(GetDisplay(), AnyButton, AnyModifier, _id, True,
                ButtonPressMask | ButtonReleaseMask |
                PointerMotionMask,
                GrabModeAsync, GrabModeAsync, None, None);
}

OXLBContainer::~OXLBContainer() {
  SListFrameElt *ptr, *next;
  OXFrame *f;
  OLayoutHints *l;

  ptr = _flist;
  while (ptr) {
    next = ptr->next;
    f = ptr->frame;
    l = ptr->layout;
    delete f;
    delete l;
    delete ptr;
    ptr = next;
  }
  XDestroySubwindows(GetDisplay(), _id);
  _flist = NULL;
}

void OXLBContainer::AddEntry(OXLBEntry *lbe, OLayoutHints *lhints) {
  lbe->SetBackgroundColor(_whitePixel);
  AddFrame(lbe, lhints);
  //Layout(); // called from OXListBox
  //lbe->MapWindow();
}

void OXLBContainer::InsertEntry(OXLBEntry *lbe, OLayoutHints *lhints, int afterID) {
  OXLBEntry *e;
  SListFrameElt *nw, *ptr;

  lbe->SetBackgroundColor(_whitePixel);

  for (ptr=_flist; ptr != NULL; ptr=ptr->next) {
    e = (OXLBEntry *) ptr->frame;
    if (e->ID() == afterID) break;
  }

  if (ptr == NULL) {
    AddFrame(lbe, lhints);
  } else {
    nw = new SListFrameElt;
    nw->frame = lbe;
    nw->layout = lhints;
    nw->state = 1;
    if (afterID == -1) {
      nw->next = _flist;
      _flist = nw;
    } else {
      nw->next = ptr->next;
      ptr->next = nw;
    }
  }
  //lbe->MapWindow();
  //Layout(); // called from OXListBox
}

void OXLBContainer::RemoveEntry(int ID) {
  OXLBEntry *e;
  SListFrameElt *ptr;
  OLayoutHints *l;

  for (ptr=_flist; ptr != NULL; ptr=ptr->next) {
    e = (OXLBEntry *) ptr->frame;
    l = ptr->layout;
    if (e->ID() == ID) {
      if (_last_active == e) _last_active = NULL;
      e->DestroyWindow();
      RemoveFrame(e);
      delete e;
      delete l;
      //Layout(); // called from OXListBox
      break;
    }
  }
}

void OXLBContainer::RemoveEntries(int from_ID, int to_ID) {
  OXLBEntry *e;
  SListFrameElt *ptr, *next;
  OLayoutHints *l;

  ptr = _flist;
  while (ptr) {
    next = ptr->next;
    e = (OXLBEntry *) ptr->frame;
    l = ptr->layout;
    if ((e->ID() >= from_ID) && (e->ID() <= to_ID)) {
      if (_last_active == e) _last_active = NULL;
      e->DestroyWindow();
      RemoveFrame(e);
      delete e;
      delete l;
    }
    ptr = next;
  }
  //Layout();  // called from OXListBox
}

void OXLBContainer::RemoveAllEntries() {
  OXLBEntry *e;
  SListFrameElt *ptr, *next;
  OLayoutHints *l;

  ptr = _flist;
  while (ptr) {
    next = ptr->next;
    e = (OXLBEntry *) ptr->frame;
    l = ptr->layout;
    //e->DestroyWindow();  // XDestroySubwindows() below is faster...
    RemoveFrame(e);
    delete e;
    delete l;
    ptr = next;
  }
  XDestroySubwindows(GetDisplay(), _id);
  _flist = NULL;
  _last_active = NULL;
  //Layout();  // called from OXListBox
}

void OXLBContainer::UnSelectAll() {
  SListFrameElt *ptr;
  OXLBEntry *f;

  for (ptr=_flist; ptr != NULL; ptr=ptr->next) {   
    f = (OXLBEntry *) ptr->frame;
    f->Activate(False);
  }

  _last_active = NULL;
  SendMessage(_msgWindow, MK_MSG(XC_CONTAINER, CT_SELCHANGED),
                          0, 0);
}

void OXLBContainer::SelectAll() {
  SListFrameElt *ptr;
  OXLBEntry *f;
  int total = 0;

  if (!_multipleSelect) return;

  for (ptr=_flist; ptr != NULL; ptr=ptr->next) {   
    f = (OXLBEntry *) ptr->frame;
    f->Activate(True);
    ++total;
  }

  SendMessage(_msgWindow, MK_MSG(XC_CONTAINER, CT_SELCHANGED),
                          total, total);
}


OXLBEntry *OXLBContainer::Select(int ID) {
  SListFrameElt *ptr;
  OXLBEntry *f;

  if (_last_active) {
    f = (OXLBEntry *) _last_active;
    f->Activate(False);
    _last_active = NULL;
  }

  for (ptr=_flist; ptr != NULL; ptr=ptr->next) {   
    f = (OXLBEntry *) ptr->frame;
    if (f->ID() == ID) {
      f->Activate(True);
      _last_active = f;
    } else {
      f->Activate(False);
    }
  }

  return _last_active;
}

OXLBEntry *OXLBContainer::FindEntry(int ID) {
  SListFrameElt *ptr;
  OXLBEntry *e;

  for (ptr=_flist; ptr != NULL; ptr=ptr->next) {   
    e = (OXLBEntry *) ptr->frame;
    if (e->ID() == ID) return e;
  }

  return NULL;
}

int OXLBContainer::GetSelected() const {
  if (_last_active == NULL) return -1;
  return _last_active->ID();
}

int OXLBContainer::HandleButton(XButtonEvent *event) {
  SListFrameElt *ptr;
  OXLBEntry *f;
  int selected = 0, total = 0;

  if (event->type == ButtonPress) {
    if (event->state & ShiftMask) {
      if (_multipleSelect) {
        if (_last_active) {
          // highlight everything between _last_active
          // and this.
          int highlighting = False;

          for (ptr=_flist; ptr != NULL; ptr=ptr->next) {
            f = (OXLBEntry *) ptr->frame;
            if ((f->GetId() == event->subwindow) ||
                 (f == _last_active)) {
              f->Activate(True);
              if (highlighting) break;
              highlighting = True;
            }
            if (highlighting) f->Activate(True);
          }
          // TODO: send the proper values for selected and total
          SendMessage(_msgWindow, MK_MSG(XC_CONTAINER, CT_SELCHANGED),
                      0, 0);
        }
      }
    } else if (event->state & ControlMask) {
      if (_multipleSelect) {
        // toggle this item
        for (ptr=_flist; ptr != NULL; ptr=ptr->next) {
          f = (OXLBEntry *) ptr->frame;
          if (f->GetId() == event->subwindow) {
            f->Activate(!f->IsActive());
            break;
          }
        }
        // TODO: send the proper values for selected and total
        SendMessage(_msgWindow, MK_MSG(XC_CONTAINER, CT_SELCHANGED),
                    0, 0);
      }
    } else {
      if (_multipleSelect) UnSelectAll();
      if (_last_active) {
        f = (OXLBEntry *) _last_active;
        f->Activate(False);
        _last_active = NULL;
      }
      for (ptr=_flist; ptr != NULL; ptr=ptr->next) {   
        f = (OXLBEntry *) ptr->frame;
        if (f->GetId() == event->subwindow) {
          f->Activate(True);
          _last_active = f;
        } else {
          f->Activate(False);
        }
      }
    }
  } else {
    if (_last_active && !(_multipleSelect && _dragging)) {
      f = (OXLBEntry *) _last_active;
      SendMessage(_msgWindow, MK_MSG(XC_CONTAINER, CT_ITEMCLICK),
                              f->ID(), 0L);
    } else if (_multipleSelect && _dragging) {
      // TODO: send the proper values for selected and total
      SendMessage(_msgWindow, MK_MSG(XC_CONTAINER, CT_SELCHANGED),
                  0, 0);
    }
  }

  _dragging = False;
  return True;
}

int OXLBContainer::HandleMotion(XMotionEvent *event) {
  SListFrameElt *ptr;
  OXLBEntry *f;

//  if (_last_active) {
//    f = (OXLBEntry *) _last_active;
//    f->Activate(False);
//    _last_active = NULL;
//  }
  for (ptr=_flist; ptr != NULL; ptr=ptr->next) {   
    f = (OXLBEntry *) ptr->frame;
    if (f->GetId() == event->subwindow) {
      f->Activate(True);
      if (_last_active != f) _dragging = True;
      _last_active = f;
    } else {
      if (!_multipleSelect) f->Activate(False);
    }
  }

  return True;
}

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

OXListBox::OXListBox(const OXWindow *p, int ID,
           unsigned int options, unsigned long back) :
    OXCompositeFrame(p, 10, 10, options, back) {

    _vport = new OXViewPort(this, 6, 6, CHILD_FRAME|OWN_BKGND, _whitePixel);
    _vscrollbar = new OXVScrollBar(this, SB_WIDTH, 6);
    _lbc = new OXLBContainer(_vport, 10, 10, VERTICAL_FRAME, _whitePixel);
    _lbc->Associate(this);
    _SetContainer(_lbc);

    _msgWindow = p;
    _ID = ID;

    AddFrame(_vport, NULL);
    AddFrame(_vscrollbar, NULL);

    _vscrollbar->Associate(this);
    _itemVsize = 1;

    _integralHeight = True;
    _autoUpdate = True;

    XSelectInput(GetDisplay(), _vscrollbar->GetId(),
                 ButtonPressMask | ButtonReleaseMask | PointerMotionMask);
    XSelectInput(GetDisplay(), _lbc->GetId(),
                 ButtonPressMask | ButtonReleaseMask /*|PointerMotionMask*/);

}

OXListBox::~OXListBox() {
  delete _vscrollbar;
  delete _lbc; // should delete contents...
  delete _vport;
}

void OXListBox::DrawBorder() {
  switch (_options & (SUNKEN_FRAME | RAISED_FRAME | DOUBLE_BORDER)) {
  case SUNKEN_FRAME | DOUBLE_BORDER:
    XDrawLine (GetDisplay(), _id, _shadowGC, 0, 0, _w-2, 0);
    XDrawLine (GetDisplay(), _id, _shadowGC, 0, 0, 0, _h-2);
    XDrawLine (GetDisplay(), _id, _blackGC, 1, 1, _w-3, 1);
    XDrawLine (GetDisplay(), _id, _blackGC, 1, 1, 1, _h-3);

    XDrawLine (GetDisplay(), _id, _hilightGC, 0, _h-1, _w-1, _h-1);
    XDrawLine (GetDisplay(), _id, _hilightGC, _w-1, _h-1, _w-1, 0);
    XDrawLine (GetDisplay(), _id, _bckgndGC,  1, _h-2, _w-2, _h-2);
    XDrawLine (GetDisplay(), _id, _bckgndGC,  _w-2, 1, _w-2, _h-2);
    break;

  default:
    OXCompositeFrame::DrawBorder();
    break;
  }
}

void OXListBox::AddEntry(OString *s, int ID) {
  OXTextLBEntry *lbe;
  OLayoutHints *lhints;

  lbe = new OXTextLBEntry(_lbc, s, ID);
  lhints = new OLayoutHints(LHINTS_EXPAND_X | LHINTS_TOP);
  _itemVsize = max(_itemVsize, lbe->GetDefaultHeight());
  _lbc->AddEntry(lbe, lhints);
  if (_autoUpdate) Update();
}

void OXListBox::AddEntry(OXLBEntry *lbe, OLayoutHints *lhints) {
  _itemVsize = max(_itemVsize, lbe->GetDefaultHeight());
  _lbc->AddEntry(lbe, lhints);
  if (_autoUpdate) Update();
}

void OXListBox::InsertEntry(OString *s, int ID, int afterID) {
  OXTextLBEntry *lbe;
  OLayoutHints *lhints;

  lbe = new OXTextLBEntry(_lbc, s, ID);
  lhints = new OLayoutHints(LHINTS_EXPAND_X | LHINTS_TOP);
  _itemVsize = max(_itemVsize, lbe->GetDefaultHeight());
  _lbc->InsertEntry(lbe, lhints, afterID);
  if (_autoUpdate) Update();
}

void OXListBox::InsertEntry(OXLBEntry *lbe, OLayoutHints *lhints, int afterID) {
  _itemVsize = max(_itemVsize, lbe->GetDefaultHeight());
  _lbc->InsertEntry(lbe, lhints, afterID);
  if (_autoUpdate) Update();
}

void OXListBox::Update() {
  _lbc->MapSubwindows();
  Layout();
}

void OXListBox::Resize(int w, int h) {
  if (_integralHeight) 
    h = max(_itemVsize, ((h - (_bw << 1)) / _itemVsize) * _itemVsize) 
        + (_bw << 1);
  OXCompositeFrame::Resize(w, h);
}

void OXListBox::MoveResize(int x, int y, int w, int h) {
  if (_integralHeight) 
    h = max(_itemVsize, ((h - (_bw << 1)) / _itemVsize) * _itemVsize)
        + (_bw << 1);
  OXCompositeFrame::MoveResize(x, y, w, h);
}

ODimension OXListBox::GetDefaultSize() const {
  int h;

  if (_integralHeight)
    h = max(_itemVsize, ((_h - (_bw << 1)) / _itemVsize) * _itemVsize)
        + (_bw << 1);
  else
    h = _h;

  return ODimension(_w, h);
}

void OXListBox::RemoveEntry(int ID) {
  _lbc->RemoveEntry(ID);
  if (_autoUpdate) Update();
}

void OXListBox::RemoveEntries(int from_ID, int to_ID) {
  _lbc->RemoveEntries(from_ID, to_ID);
  if (_autoUpdate) Update();
}

void OXListBox::RemoveAllEntries() {
  _lbc->RemoveAllEntries();
  if (_autoUpdate) Update();
}

void OXListBox::Layout() {
  OXFrame *_container;
  int need_vsb, cw, ch, tch;

  need_vsb = False;

  _container = (OXFrame *) _vport->GetContainer();    

  // test whether we need vertical scrollbar or not

  cw = _w-(_bw << 1);
  ch = _h-(_bw << 1);

  _container->SetWidth(cw);
  _container->SetHeight(ch);

  if (_container->GetDefaultHeight() > ch) {
    need_vsb = True;
    cw = _w-(_bw << 1)-_vscrollbar->GetDefaultWidth();
    _container->SetWidth(cw);
  }

  _vport->MoveResize(_bw, _bw, cw, ch);
  _container->Layout();
  tch = max(_container->GetDefaultHeight(), ch);
  _container->SetHeight(0); // force a resize in OXFrame::Resize
  _container->Resize(cw, tch);
//  _vport->SetPos(0, 0);

  if (need_vsb) {
    _vscrollbar->MoveResize(cw+_bw, _bw, _vscrollbar->GetDefaultWidth(), ch);
    _vscrollbar->MapWindow();
  } else {
    _vscrollbar->UnmapWindow();
    _vscrollbar->SetPosition(0);
  }

  _vscrollbar->SetRange(_container->GetHeight()/_itemVsize, _vport->GetHeight()/_itemVsize);
}

int OXListBox::GetSelected() const {
  OXLBContainer *ct = (OXLBContainer *) _vport->GetContainer();
  return ct->GetSelected();
}

int OXListBox::ProcessMessage(long msg, long parm1, long parm2) {

  switch(GET_MSG(msg)) {
    case XC_VSCROLL:
      switch(GET_SUBMSG(msg)) {
        case SB_SLIDERTRACK:
        case SB_SLIDERPOS:
          _vport->SetVPos(-parm1 * _itemVsize);
          break;
      }
      break;

    case XC_CONTAINER:
      switch(GET_SUBMSG(msg)) {
        case CT_ITEMCLICK:
          SendMessage(_msgWindow, MK_MSG(XC_COMMAND, CM_LISTBOX),
                                  _ID, parm1);
          break;

/*
        case CT_SELCHANGED:
          SendMessage(_msgWindow, MK_MSG(XC_COMMAND, CM_LISTBOX),
                                  _ID, parm1);
          break;
*/
      }
      break;

    default:
      break;
   
  }

  return True;
}
