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

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

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

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

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

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

#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>

#include <xclass/utils.h>
#include <xclass/OXFrame.h>
#include <xclass/OXWidgets.h>
#include <xclass/OXMsgBox.h>
#include <xclass/OXClient.h>
#include <xclass/OString.h>
#include <xclass/OLayout.h>
#include <xclass/OXListView.h>


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

OSelectedPicture::OSelectedPicture(const OXClient *client, const OPicture *p) :
  OPicture(NULL) {

  XGCValues gcv;
  unsigned  long gcm;
  int w, h;

  _client = client;
  Display *dpy = _client->GetDisplay();
  Window Root  = _client->GetRoot()->GetId();

  w = p->GetWidth();
  h = p->GetHeight();

  _pic  = XCreatePixmap(dpy, Root, w, h, DefaultDepth(dpy, DefaultScreen(dpy)));
  _mask = p->GetMask();

  _attributes.width  = w;
  _attributes.height = h;

  XCopyArea(dpy, p->GetPicture(), _pic, _selectedGC, 0, 0, w, h, 0, 0);

  gcm = GCClipMask | GCClipXOrigin | GCClipYOrigin;
  gcv.clip_mask = p->GetMask();
  gcv.clip_x_origin = 0;
  gcv.clip_y_origin = 0;
  XChangeGC(dpy, _selectedGC, gcm, &gcv);

  XFillRectangle(dpy, _pic, _selectedGC, 0, 0, w, h);

  gcm = GCClipMask;
  gcv.clip_mask = None;
  XChangeGC(dpy, _selectedGC, gcm, &gcv);
}

OSelectedPicture::~OSelectedPicture() {
  XFreePixmap(_client->GetDisplay(), _pic);
}

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

OXListItem::OXListItem(const OXWindow *p,
                   const OPicture *bigpic, const OPicture *smallpic,
                   OString *name, OString **subnames, int ViewMode,
                   unsigned int options, unsigned long back) :
  OXFrame(p, 10, 10, options, back) {
    int i;

    _selpic = NULL;

    _current =
    _bigpic = bigpic;
    _smallpic = smallpic;

    _name = name;
    _subnames = subnames;

    _cpos =
    _jmode = NULL;

    _active = False;

    _normGC = _defaultGC;
    _fontStruct = _defaultFontStruct;

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

    if (_subnames) {
      for (i=0; _subnames[i] != NULL; ++i);
      _ctw = new int[i];
      for (i=0; _subnames[i] != NULL; ++i)
       _ctw[i] = XTextWidth(_fontStruct, _subnames[i]->GetString(),
                                         _subnames[i]->GetLength());
    } else {
      _ctw = NULL;
    }

    _viewMode = -1;
    SetViewMode(ViewMode);
}

OXListItem::~OXListItem() {
  int i;

  if (_name) delete _name;  
  if (_selpic) delete _selpic;
  if (_subnames) {
    for (i = 0; _subnames[i] != NULL; ++i) delete _subnames[i];
    delete[] _subnames;
    delete[] _ctw;
  }
}

void OXListItem::Activate(int a) {
  if (_active == a) return;
  _active = a;
  
  if (_active) {
    _selpic = new OSelectedPicture(_client, _current);
  } else {
    if (_selpic) delete _selpic;
    _selpic = NULL;
  }

  _DoRedraw();
}

void OXListItem::SetViewMode(int ViewMode) {
  if (ViewMode != _viewMode) {
    _viewMode = ViewMode;
    if (ViewMode == LV_LARGE_ICONS)
      _current = _bigpic;
    else
      _current = _smallpic;
    if (_active) {
      if (_selpic) delete _selpic;
      _selpic = new OSelectedPicture(_client, _current);
    }
    XClearWindow(GetDisplay(), _id);
    Resize(GetDefaultSize());
    _client->NeedRedraw(this);
  }
}

void OXListItem::_DoRedraw() {
  int ix, iy, lx, ly;

  if (_viewMode == LV_LARGE_ICONS) {
    ix = (_w - _current->GetWidth()) >> 1;
    iy = 0;
    lx = (_w - _tw) >> 1;
    ly = _h - (_th+1) - 2;
  } else {
    ix = 0;
    iy = (_h - _current->GetHeight()) >> 1;
    lx = _current->GetWidth() + 2;
    ly = (_h - (_th+1)) >> 1;
  }

  if (_active) {
    if (_selpic) _selpic->Draw(GetDisplay(), _id, _normGC, ix, iy);
    XSetForeground(GetDisplay(), _normGC, _defaultSelectedBackground);
    XFillRectangle(GetDisplay(), _id, _normGC, lx, ly, _tw, _th+1);
    XSetForeground(GetDisplay(), _normGC, _selPixel);
  } else {
    _current->Draw(GetDisplay(), _id, _normGC, ix, iy);
    XSetForeground(GetDisplay(), _normGC, _whitePixel);
    XFillRectangle(GetDisplay(), _id, _normGC, lx, ly, _tw, _th+1);
    XSetForeground(GetDisplay(), _normGC, _blackPixel);
  }

  _name->Draw(GetDisplay(), _id, _normGC,
              lx, ly + _fontStruct->max_bounds.ascent);

  XSetForeground(GetDisplay(), _normGC, _blackPixel);

  if (_viewMode == LV_DETAILS) {
    if (_subnames && _cpos && _jmode && _ctw) {
      int i;

      for (i=0; _subnames[i]!=NULL; ++i) {
        if (_jmode[i] == TEXT_RIGHT)
          lx = _cpos[i+1] - _ctw[i] -2;
        else if (_jmode[i] == TEXT_CENTER_X)
          lx = (_cpos[i] + _cpos[i+1] - _ctw[i]) >> 1;
        else // default to TEXT_LEFT
          lx = _cpos[i] +2;
        _subnames[i]->Draw(GetDisplay(), _id, _normGC,
                           lx, ly + _fontStruct->max_bounds.ascent);
      }
    }
  }

}

ODimension OXListItem::GetDefaultSize() const {
  ODimension size;
  ODimension isize(_current->GetWidth(), _current->GetHeight());
  ODimension lsize(_tw, _th+1);

  switch (_viewMode) {
    default:
    case LV_LARGE_ICONS:
      size.w = max(isize.w, lsize.w);
      size.h = isize.h + lsize.h + 6;
      break;

    case LV_SMALL_ICONS:
    case LV_LIST:
    case LV_DETAILS:
      size.w = isize.w + lsize.w + 4;
      size.h = max(isize.h, lsize.h);
      break;
  }

  return size;
}


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

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

    _msgWindow = p;
    _last_active = NULL;
    _dragging = False;
    _total = _selected = 0;

    _cpos = _jmode = NULL;

    _viewMode = LV_LARGE_ICONS;
    _itemLayout = new OLayoutHints(LHINTS_EXPAND_Y | LHINTS_CENTER_X);

    SetLayoutManager(new OTileLayout(this, 8));

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

OXListViewContainer::~OXListViewContainer() {
  RemoveAll();
  delete _itemLayout;
}

void OXListViewContainer::SetViewMode(int ViewMode) {
  SListFrameElt *ptr;

  if (_viewMode != ViewMode) {
    OLayoutHints *_oldLayout = _itemLayout;

    _viewMode = ViewMode;
    if (ViewMode == LV_LARGE_ICONS)
      _itemLayout = new OLayoutHints(LHINTS_EXPAND_Y | LHINTS_CENTER_X);
    else
      _itemLayout = new OLayoutHints(LHINTS_LEFT | LHINTS_CENTER_Y);

    for (ptr=_flist; ptr != NULL; ptr=ptr->next) {
      ptr->layout = _itemLayout;
      ((OXListItem *) ptr->frame)->SetViewMode(ViewMode);
    }
    delete _oldLayout;

    switch (ViewMode) {
      default:
      case LV_LARGE_ICONS:
        SetLayoutManager(new OTileLayout(this, 8));
        break;

      case LV_SMALL_ICONS:
        SetLayoutManager(new OTileLayout(this, 2));
        break;

      case LV_LIST:
        SetLayoutManager(new OListLayout(this, 2));
        break;

      case LV_DETAILS:
        SetLayoutManager(new OListDetailsLayout(this, 2));
        break;
    }

//    OXCanvas *canvas = (OXCanvas *) this->GetParent()->GetParent();
//    canvas->Layout();

  }
}

void OXListViewContainer::SetColumns(int *cpos, int *jmode) {
  SListFrameElt *ptr;
  OXListItem *e;

  _cpos = cpos;
  _jmode = jmode;
  for (ptr=_flist; ptr != NULL; ptr=ptr->next) {
    e = (OXListItem *) ptr->frame;
    e->SetColumns(_cpos, _jmode);
  }
  Layout();
}

ODimension OXListViewContainer::GetMaxItemSize() const {
  SListFrameElt *ptr;
  ODimension csize, maxsize(0,0);

  for (ptr=_flist; ptr != NULL; ptr=ptr->next) {
    csize = ptr->frame->GetDefaultSize();
    maxsize.w = max(maxsize.w, csize.w);
    maxsize.h = max(maxsize.h, csize.h);
  }
  if (_viewMode == LV_LARGE_ICONS) {
    maxsize.w += 8;
    maxsize.h += 8;
  } else {
    maxsize.w += 2;
    maxsize.h += 2;
  }
  return maxsize;
}

int OXListViewContainer::HandleButton(XButtonEvent *event) {
  SListFrameElt *ptr;
  OXListItem *f;
  int total, selected, inv;

  if (event->type == ButtonPress) {
    inv = event->state & ControlMask;
    _xp = event->x;
    _yp = event->y;

    if (_last_active) {
      _last_active->Activate(False);
      _last_active = NULL;
    }
    total = selected = 0;
    for (ptr=_flist; ptr != NULL; ptr=ptr->next) {   
      f = (OXListItem *) ptr->frame;
      ++total;
      if (f->GetId() == event->subwindow) {
        f->Activate(True);
        ++selected;
        _last_active = f;
      } else {
        f->Activate(False);
      }
    }

    if (_total != total || _selected != selected) {
      _total = total;
      _selected = selected;
      SendMessage(_msgWindow, MK_MSG(XC_CONTAINER, CT_SELCHANGED),
                              _total, _selected);
    }

    if (selected == 0) {
      _dragging = True;
      _x0 = _xf = _xp;
      _y0 = _yf = _yp;
      XDrawRectangle(GetDisplay(), _id, _lineGC, _x0, _y0, _xf-_x0, _yf-_y0);
    }
  }  

  if (event->type == ButtonRelease) {
    if (_dragging) {
      _dragging = False;
      XDrawRectangle(GetDisplay(), _id, _lineGC, _x0, _y0, _xf-_x0, _yf-_y0);
    } else {
      SendMessage(_msgWindow, MK_MSG(XC_CONTAINER, CT_ITEMCLICK),
                              event->button, 
                              (event->y_root << 16) | event->x_root);
    }
  }

  return True;
}

int OXListViewContainer::HandleDoubleClick(XButtonEvent *event) {
  SListFrameElt *ptr;
  OXListItem *f;

  for (ptr=_flist; ptr != NULL; ptr=ptr->next) {   
    f = (OXListItem *) ptr->frame;
    if (f->GetId() == event->subwindow) {
      SendMessage(_msgWindow, MK_MSG(XC_CONTAINER, CT_ITEMDBLCLICK),
                              event->button,
                              (event->y_root << 16) | event->x_root);
      break;
    }
  }
  return True;
}

int OXListViewContainer::HandleMotion(XMotionEvent *event) {
  SListFrameElt *ptr;
  OXListItem *f;
  int xf0, yf0, xff, yff, total, selected, inv;

  if (_dragging) {
    inv = event->state & ControlMask;
    XDrawRectangle(GetDisplay(), _id, _lineGC, _x0, _y0, _xf-_x0, _yf-_y0);
    _x0 = min(_xp, event->x);
    _xf = max(_xp, event->x);
    _y0 = min(_yp, event->y);
    _yf = max(_yp, event->y);

    total = selected = 0;
    for (ptr=_flist; ptr != NULL; ptr=ptr->next) {

      f = (OXListItem *) ptr->frame;
      ++total;
      xf0 = f->GetX() + (f->GetWidth() >> 3);
      yf0 = f->GetY() + (f->GetHeight() >> 3);
      xff = xf0 + f->GetWidth() - (f->GetWidth() >> 2);
      yff = yf0 + f->GetHeight() - (f->GetHeight() >> 2);

      if (((xf0 > _x0 && xf0 < _xf) ||
           (xff > _x0 && xff < _xf)) &&
          ((yf0 > _y0 && yf0 < _yf) ||
           (yff > _y0 && yff < _yf))) {
        f->Activate(True);
        ++selected;
      } else {
        f->Activate(False);
      }
    }

    XDrawRectangle(GetDisplay(), _id, _lineGC, _x0, _y0, _xf-_x0, _yf-_y0);

    if (_total != total || _selected != selected) {
      _total = total;
      _selected = selected;
      SendMessage(_msgWindow, MK_MSG(XC_CONTAINER, CT_SELCHANGED),
                              _total, _selected);
    }

  }

  return True;
}  

void OXListViewContainer::UnSelectAll() {
  SListFrameElt *ptr;
  OXListItem *f;

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

  _selected = 0;
  SendMessage(_msgWindow, MK_MSG(XC_CONTAINER, CT_SELCHANGED),
                          _total, _selected);
}

void OXListViewContainer::SelectAll() {
  SListFrameElt *ptr;
  OXListItem *f;

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

  _selected = _total;
  SendMessage(_msgWindow, MK_MSG(XC_CONTAINER, CT_SELCHANGED),
                          _total, _selected);
}

void OXListViewContainer::InvertSelection() {
  SListFrameElt *ptr;
  OXListItem *f;
  int selected;

  selected = 0;
  for (ptr=_flist; ptr != NULL; ptr=ptr->next) {   
    f = (OXListItem *) ptr->frame;
    if (f->IsActive()) {
      f->Activate(False);
    } else {
      f->Activate(True);
      ++selected;
    }
  }

  _selected = selected;
  SendMessage(_msgWindow, MK_MSG(XC_CONTAINER, CT_SELCHANGED),
                          _total, _selected);
}

void OXListViewContainer::RemoveAll() {
  SListFrameElt *ptr, *next;

  ptr = _flist;
  while(ptr) {
    next = ptr->next;
//    ptr->frame->DestroyWindow();
    delete ptr->frame;
    delete ptr;
    ptr = next;
  }
  XDestroySubwindows(GetDisplay(), _id);
  _flist = NULL;
  _selected = _total = 0;
  _last_active = NULL;
  SendMessage(_msgWindow, MK_MSG(XC_CONTAINER, CT_SELCHANGED),
                          _total, _selected);
}

const OXListItem *OXListViewContainer::GetNextSelected(void **current) {
  SListFrameElt *p;
  OXListItem *f;

  p = (SListFrameElt *) *current;
  p = (p == NULL) ? _flist : p->next;
  for (; p != NULL; p = p->next) {   
    f = (OXListItem *) p->frame;
    if (f->IsActive()) {
      *current = (void *) p;
      return f;
    }
  }

  return NULL;
}


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

OXListView::OXListView(const OXWindow *p, int w, int h,
             unsigned int options, unsigned long back) :
  OXCanvas(p, w, h, options, back) {
    int i;

    _viewMode = LV_LARGE_ICONS;

    // for test only...
    _col_header[0] = new OXTextButton(this, new OHotString("Name"),
                     1, _defaultGC, _defaultFontStruct);
    _col_header[1] = new OXTextButton(this, new OHotString("Attributes"),
                     3, _defaultGC, _defaultFontStruct);
    _col_header[2] = new OXTextButton(this, new OHotString("Size"),
                     2, _defaultGC, _defaultFontStruct);
    _col_header[3] = new OXTextButton(this, new OHotString("Owner"),
                     3, _defaultGC, _defaultFontStruct);
    _col_header[4] = new OXTextButton(this, new OHotString("Group"),
                     3, _defaultGC, _defaultFontStruct);
    _col_header[5] = new OXTextButton(this, new OHotString("Modified"),
                     3, _defaultGC, _defaultFontStruct);
    _col_header[6] = new OXTextButton(this, new OHotString(""),
                     4, _defaultGC, _defaultFontStruct);

    _col_header[0]->SetTextJustify(TEXT_LEFT | TEXT_CENTER_Y);
    _col_header[1]->SetTextJustify(TEXT_CENTER_X | TEXT_CENTER_Y);
    _col_header[2]->SetTextJustify(TEXT_RIGHT | TEXT_CENTER_Y);
    _col_header[3]->SetTextJustify(TEXT_CENTER_X | TEXT_CENTER_Y);
    _col_header[4]->SetTextJustify(TEXT_CENTER_X | TEXT_CENTER_Y);
    _col_header[5]->SetTextJustify(TEXT_CENTER_X | TEXT_CENTER_Y);
    _col_header[6]->SetTextJustify(TEXT_CENTER_X | TEXT_CENTER_Y);

    _col_header[6]->SetState(BUTTON_DISABLED);

    //for (i=0; i<7; ++i)
    //  OXCompositeFrame::AddFrame(_col_header[i], NULL);

    int xl = _bw + _col_header[0]->GetDefaultWidth()+20 + 10;

    for (i=1; i<7; ++i) {
      _columns[i-1] = xl;
      xl += _col_header[i]->GetDefaultWidth()+20;
    }
    _columns[6] = 0;

    _jmode[0] = TEXT_CENTER_X;
    _jmode[1] = TEXT_RIGHT;
    _jmode[2] = TEXT_CENTER_X;
    _jmode[3] = TEXT_CENTER_X;
    _jmode[4] = TEXT_CENTER_X;
    _jmode[5] = TEXT_CENTER_X;
    _jmode[6] = TEXT_CENTER_X;

}

OXListView::~OXListView() {
}

void OXListView::SetViewMode(int ViewMode) {
  OXListViewContainer *container;

  if (_viewMode != ViewMode) {
    _viewMode = ViewMode;
    container = (OXListViewContainer *) _vport->GetContainer();
    if (container) container->SetViewMode(ViewMode);
    Layout();
  }
}

void OXListView::SetContainer(OXFrame *f) {
  OXCanvas::SetContainer(f);
  ((OXListViewContainer *) f)->SetColumns(_columns, _jmode);
}

void OXListView::Layout() {
  OXListViewContainer *container;
  int i, w, h, xl = _bw;

  if (_viewMode == LV_DETAILS) {

    // for test only...
    container = (OXListViewContainer *) _vport->GetContainer();
    _maxsize = container->GetMaxItemSize();
    h = _col_header[0]->GetDefaultHeight()-4;
    for (i=0; i<6; ++i) {
      w = _col_header[i]->GetDefaultWidth()+20;
      if (i == 0) w = _maxsize.w + 10;
      _col_header[i]->MoveResize(xl, _bw, w, h);
      _col_header[i]->MapWindow();
      xl += w;
      _columns[i] = xl-_bw-2;  // -2 is _sep in the layout routine
    }
    _col_header[i]->MoveResize(xl, _bw, _vport->GetWidth()-xl+_bw, h);
    _col_header[i]->MapWindow();
    _vscrollbar->RaiseWindow();

    container->SetColumns(_columns, _jmode);

  } else {
    for (i=0; i<7; ++i)
      _col_header[i]->UnmapWindow();
  }

  OXCanvas::Layout();
  if (_viewMode == LV_LIST) {
    container = (OXListViewContainer *) _vport->GetContainer();
    _maxsize = container->GetMaxItemSize();
    if (_maxsize.w > 0)
      _hscrollbar->SetRange(container->GetWidth()/_maxsize.w,
                            _vport->GetWidth()/_maxsize.w);
  }
  if (_viewMode == LV_DETAILS) {
    _col_header[i]->MoveResize(xl, _bw, _vport->GetWidth()-xl+_bw, h);
    _vport->MoveResize(_bw, _bw+h, _vport->GetWidth(), _vport->GetHeight()-h);
  }
}

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

  if ((_viewMode == LV_LIST) && (GET_MSG(msg) == XC_HSCROLL)) {
    switch(GET_SUBMSG(msg)) {
      case SB_SLIDERTRACK:
      case SB_SLIDERPOS:
        _vport->SetHPos(-parm1 * _maxsize.w);
        break;
    }
  } else {
    return OXCanvas::ProcessMessage(msg, parm1, parm2);
  }

  return True;
}
