/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CTreeView.h  -  description
                             -------------------
    begin                : June 21 2003
    copyright            : (C) 2002-2005 by Alexey Parshin
    email                : alexeyp@gmail.com
 ***************************************************************************/

/***************************************************************************
   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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.

   Please report all bugs and problems to "alexeyp@gmail.com"
 ***************************************************************************/

//#include <stdio.h>
#include <string.h>

#include <FL/Fl.h>
#include <FL/fl_draw.h>
#include <FL/Fl_Image.h>

#include <sptk3/CTreeControl.h>
#include <sptk3/CSmallPixmaps.h>

#include <FL/Fl_Pixmap.h>
#include <pixmaps/tree_opened.xpm>
#include <pixmaps/tree_closed.xpm>
#include <pixmaps/folder_small.xpm>
#include <pixmaps/folder_small_opened.xpm>
#include <pixmaps/document_small.xpm>

using namespace std;

static Fl_Pixmap treeOpened(tree_opened_xpm);
static Fl_Pixmap treeClosed(tree_closed_xpm);
static Fl_Pixmap folderClosed(folder_small_xpm);
static Fl_Pixmap folderOpened(folder_small_opened_xpm);
static Fl_Pixmap document(document_small_xpm);

CTreeItem::CTreeItem(const char *lbl,Fl_Image *openedImage,Fl_Image *closedImage,void *data)
: CGroup("",10,SP_ALIGN_TOP) {
   layoutSpacing(0);
   label(lbl);
   m_drawClipped = false;
   m_tree = dynamic_cast<CTreeControl *>(parent());
   bool root = false;
   if (m_tree) {
      // This widget is the root item
      m_itemHeight = 0;
      m_indent = 18;
      root = true;
   } else {
      CTreeItem *par = dynamic_cast<CTreeItem *>(parent());
      if (par) {
         m_tree = par->tree();
      }
      m_itemHeight = 16;
      m_indent = 18;
   }

   m_opened = true;

   m_openedImage = openedImage;

   if (closedImage != 0L)
      m_closedImage = closedImage;
   else  m_closedImage = openedImage;

   if (!root) {
      if (m_openedImage) {
         if (m_indent < m_openedImage->w() + 2)
            m_indent = m_openedImage->w() + 2;
         if (m_itemHeight < m_openedImage->h() + 2)
            m_itemHeight = m_openedImage->h() + 2;
      }

      if (m_closedImage) {
         if (m_indent < m_closedImage->w() + 2)
            m_indent = m_closedImage->w() + 2;
         if (m_itemHeight < m_closedImage->h() + 2)
            m_itemHeight = m_closedImage->h() + 2;
      }
   }
   user_data(data);
}

void CTreeItem::resize(int xx,int yy,int ww,int hh) {
   Fl_Widget::resize(xx,yy,ww,hh);

   int offsetX = frameWidth() + m_indent;

   bool isRoot = dynamic_cast<CTreeControl *>(parent()) != 0;

   const char *lbl = m_caption.c_str();
   if (isRoot) {
      m_itemHeight = 0;
   } else {
      if (lbl[0]) {
         int tw = ww - 8, th = 0;
         fl_font(labelfont(),labelsize());
         fl_measure(lbl,tw,th);
         m_itemHeight = th + 2;
         if (tw + m_indent + 2 > ww)
            ww = tw + m_indent + 2;
      } else {
         m_itemHeight = 12;
      }
      if (m_openedImage && m_openedImage->h() + 2 > m_itemHeight)
         m_itemHeight = m_openedImage->h() + 2;
   }

   int offsetY = m_itemHeight;
   if (isRoot) offsetY = 0;

   int www = ww - frameWidth() * 2 + m_indent;
   int hhh = hh - offsetY;
   autoLayout(xx + offsetX,yy + offsetY,www,hhh,true);
}

void CTreeItem::label(const char *lbl) { 
   m_caption = lbl; 
   fl_font(labelfont(),labelsize());
   m_labelWidth = m_labelHeight = 0;
   if (m_caption[0])
      fl_measure(m_caption.c_str(),m_labelWidth,m_labelHeight);
}

void CTreeItem::preferredSize(int& ww,int& hh) {
   bool isRoot = dynamic_cast<CTreeControl *>(parent()) != 0;

   int wl = m_labelWidth + m_indent + 2;
   if (ww < wl) ww = wl;
   hh = m_labelHeight + 2;

   int offsetX = m_indent + 2;
   int offsetY = m_itemHeight;
   if (isRoot)
      offsetY = 0;

   int www = ww - (offsetX + 2);
   int hhh = hh - offsetY;
   autoLayout(offsetX,offsetY,www,hhh,false);
   ww = www + (offsetX + 2);
   hh = hhh + offsetY;
}

int CTreeItem::handle(int event) {
   bool relayout = false;

   switch (event) {

      case FL_PUSH: 
         tree()->m_tabPressed = false;
         if (opened() && Fl::event_inside(x(), y() + m_itemHeight, m_indent, h() - m_itemHeight)) {
            unsigned cnt = children();
            for (unsigned i = 0; i < cnt; i++) {
               CTreeItem *item = (CTreeItem *) child(i);
               if (item->y() + 12 >= Fl::event_y()) {
                  if (item->opened())
                     item->close();
                  else item->open();
                  relayout = true;
                  break;
               }
            }
         }
         else if (Fl::event_inside(m_indent, y(), w()-m_indent, m_itemHeight)) {
            //Fl::focus(this);
            return handle(FL_FOCUS);
         }
         break;

      case FL_KEYBOARD:
         switch (Fl::event_key()) {
            case FL_Tab:
               tree()->m_tabPressed = true;
               break;

            case '=':
               if (Fl::event_shift()) {
                  visibleChildren(true);
                  relayout = true;
               }
               break;

            case '-':
               visibleChildren(false);
               relayout = true;
               break;

            default: 
               return 0;
         }
         break;

      case FL_UNFOCUS:
         redraw();
         return 1;

      case FL_FOCUS:
         {
            if (tree()->m_tabPressed) return 0;
            CTreeItem *old_selection = tree()->selected();
            tree()->select(this);
            tree()->makeVisible(this);
            redraw();
            if (old_selection)
               old_selection->redraw();
         }
         return 1;

      default:
         return 0;
   }
   if (relayout) {
      tree()->relayout();
      tree()->redraw();
      return 1;
   }
   return CGroup::handle(event);
}

CTreeItem *CTreeItem::parentItem() const { 
   CTreeItem *par = dynamic_cast<CTreeItem *>(parent());
   return par;
}

CTreeItem *CTreeItem::addItem(const char *label,Fl_Image *openedImage,Fl_Image *closedImage,void *data) {
   if (!closedImage)
      closedImage = openedImage;

   Fl_Group *saveCurrent = Fl_Group::current();
   begin();
   CTreeItem *item = new CTreeItem(label,openedImage,closedImage,data);
   //insert(*item,children());

   if (!m_opened)
      item->hide();

   Fl_Group::current(saveCurrent);

   return item;
}

void CTreeItem::draw() {

   int xx = x();
   int yy = y();

   bool isRoot = dynamic_cast<CTreeControl *>(parent()) != 0;

   Fl_Image *image = 0L;

   if (!isRoot) {
      if (m_opened) {
         image = m_openedImage;
         if (!image) {
            if (children())
               image = &folderOpened;
            else
               image = &document;
         }
      } else {
         image = m_closedImage;
         if (!image) {
            if (children())
               image = &folderClosed;
            else
               image = &document;
         }
      }

      fl_font(labelfont(),labelsize());
   }

   Fl_Color backClr = tree()->color();
   draw_box(FL_FLAT_BOX, x(), y(), w(), h(), backClr);

   CGroup::draw();

   fl_push_clip(x(),y(),w(),h());

   // draw the lines
   int lx = x()+m_indent/2;
   int sly = y()+m_itemHeight/2;
   unsigned cnt = children();
   int cly = sly;
   fl_color(0x40404000);
   if (m_opened) { 
      for (unsigned i = 0; i < cnt; i++) {
         Fl_Widget *item = child(i);
         cly = item->y() + item->labelsize() / 2 + 1;
         if (isRoot && i == 0) sly = cly;
         fl_line(lx,cly,item->x()-1,cly);
      }
      if (cnt) 
         fl_line(lx,sly,lx,cly);
      for (unsigned j = 0; j < cnt; j++) {
         CTreeItem *item = (CTreeItem *) child(j);
         if (!item->children()) continue;
         cly = item->y() + item->labelsize() / 2 + 1;
         draw_box(FL_THIN_DOWN_BOX, lx-5,cly-5,10,10, backClr);
         //fl_color(backClr);
         //fl_rectf(lx-5,cly-5,10,10);
         fl_color(0x40404000);
         //fl_rect(lx-5,cly-5,11,11);
         fl_line(lx-3,cly,lx+3,cly);
         if (!item->opened())
            fl_line(lx,cly-3,lx,cly+3);
      }
   }

   if (!isRoot) {
      if (image)
         image->draw(xx,yy,image->w(),image->h(),0,0);

      int ww = w() - (m_indent + 4);
      if (this == tree()->m_selected) {
         draw_box(FL_FLAT_BOX, x() + m_indent, y(), ww, m_itemHeight, fl_darker(backClr));
         if (Fl::focus() == this) 
            draw_focus(FL_FLAT_BOX, x() + m_indent, y(), ww, m_itemHeight);
      }

      fl_color(labelcolor());
      fl_draw(m_caption.c_str(), x() + m_indent + 2, yy + 1, ww - 2, h() - 4, Fl_Align(FL_ALIGN_LEFT|FL_ALIGN_TOP));
   }

   fl_pop_clip();
}

CTreeItem *CTreeItem::findItem(const char *label) const {
   unsigned cnt = children();
   for (unsigned i = 0; i < cnt; i++) {
      CTreeItem *item = (CTreeItem *)child(i);
      if (item->m_caption == label)
         return item;
      if (item->children()) {
         CTreeItem *fitem = item->findItem(label);
         if (fitem)
            return fitem;
      }
   }
   return 0L;
}

CTreeItem *CTreeItem::findData(const void *itemData) const {
   unsigned cnt = children();
   for (unsigned i = 0; i < cnt; i++) {
      CTreeItem *item = (CTreeItem *)child(i);
      if (item->user_data() == itemData)
         return item;
      if (item->children()) {
         item = item->findData(itemData);
         if (item)
            return item;
      }
   }
   return 0;
}

CTreeItem *CTreeItem::addPathOffset(const vector<string>& pathFolders,unsigned offset,Fl_Image *openedImage,Fl_Image *closedImage,Fl_Image *itemImage,void *data) {
   CTreeItem *node = this;
   int cnt = pathFolders.size();
   for (int i = offset; i < cnt - 1; i++) {
      const string& folder = pathFolders[i];
      CTreeItem *n = node->findItem(folder.c_str());
      if (!n)
         n = node->addItem(folder.c_str(),openedImage,closedImage);
      node = n;
   }
   if (cnt - 1 >= 0) 
      return node->addItem(pathFolders[cnt - 1].c_str(),itemImage,itemImage,data);
   else return 0L;
}

CTreeItem *CTreeItem::addPath(const vector<string>& pathFolders,Fl_Image *openedImage,Fl_Image *closedImage,Fl_Image *itemImage,void *data) {
   return addPathOffset(pathFolders,0,openedImage,closedImage,itemImage,data);
}

CTreeItem *CTreeItem::addPath(const vector<string>& pathFolders,Fl_Image *itemImage,void *data) {
   if (!itemImage)
      itemImage = CSmallPixmaps::pixmaps[SXPM_DOCUMENT];
   return addPath(pathFolders,CSmallPixmaps::pixmaps[SXPM_FOLDER_OPENED],CSmallPixmaps::pixmaps[SXPM_FOLDER],itemImage,data);
}

void CTreeItem::visibleChildren(bool show) {
   m_opened = show;
   unsigned cnt = children();
   for (unsigned i = 0; i < cnt; i++) {
      CTreeItem *item = dynamic_cast<CTreeItem *>(child(i));
      if (!item) continue;
      if (show) {
         item->show();
         if (item->m_opened) 
            item->visibleChildren(true);
      } else {
         item->hide();
         item->visibleChildren(false);
      }
   }
}

void CTreeItem::moveItem(CTreeItem *item,CTreeItem *beforeItem) {
   insert(*item,beforeItem);
}

void CTreeItem::moveItem(CTreeItem *item,int direction) {
   int itemPosition = find(item);
   if (itemPosition == children()) return;
   if (direction == 0) return;
   if (direction < 0) {                          // move item up 
      if (itemPosition == 0) return;             // can't move - at the start already
      insert(*item,child(itemPosition-1));
   } else {                                      // move item down 
      if (itemPosition == children()-1) return;  // can't move - at the end already
      insert(*item,itemPosition+1);
   }
}

void CTreeItem::removeItem(CTreeItem *item) {
   int itemPosition = find(item);
   if (itemPosition == children()) return;

   Fl_Widget *newSelected = 0;
   bool wasFocused = (Fl::focus() == item);
   if (item == tree()->m_selected || wasFocused) {
      if (itemPosition < children() - 1) {
         newSelected = (CTreeItem *) child(itemPosition+1);
      } else {
         if (children()>1)
            newSelected = (CTreeItem *) child(itemPosition-1);
         else 
            newSelected = this;
      }
   }

   remove(item);
   delete item;

   if (newSelected) {
      tree()->m_selected = (CTreeItem *) newSelected;
      if (wasFocused)
         Fl::focus(newSelected);
   }
}

void CTreeItem::clear() {
   CGroup::clear();
   tree()->relayout();
}

//--------------------------------------------------------------------------
CTreeControl::CTreeControl(const char *label,int layoutSize,CLayoutAlign align)
: CScroll(label,layoutSize,align) {
   m_tabPressed = false;
   m_selected = 0;
   box(FL_THIN_DOWN_BOX);
   layoutSpacing(0);
   begin();
   m_root = new CTreeItem("root");
   end();
   color(FL_LIGHT3);
}

CTreeItem *CTreeControl::addItem(const char *label,Fl_Image *openedImage,Fl_Image *closedImage,void *data) {
   return m_root->addItem(label,openedImage,closedImage,data);
}

CTreeItem *CTreeControl::addPath(const vector<string>& path,Fl_Image *openedImage,Fl_Image *closedImage,Fl_Image *itemImage,void *data) {
   return m_root->addPath(path,openedImage,closedImage,itemImage,data);
}

CTreeItem *CTreeControl::addPath(const vector<string>& path,Fl_Image *itemImage,void *data) {
   return addPath(path,CSmallPixmaps::pixmaps[SXPM_FOLDER_OPENED],CSmallPixmaps::pixmaps[SXPM_FOLDER],itemImage,data);
}

void CTreeControl::moveItem(CTreeItem *item,CTreeItem *beforeItem) {
   CTreeItem *parentItem = item->parentItem();
   parentItem->moveItem(item,beforeItem);
}

void CTreeControl::removeItem(CTreeItem *item) {
   CTreeItem *parentItem = item->parentItem();
   parentItem->removeItem(item);
   relayout();
   redraw();
}

CTreeItem *CTreeControl::findItem(const char *label) const {
   return m_root->findItem(label);
}

CTreeItem *CTreeControl::findData(const void *itemData) const {
   return m_root->findData(itemData);
}

void CTreeControl::select(CTreeItem *item) {
   if (item->tree() != this) return;
   Fl::focus(item);
   if (m_selected != item) {
      m_selected = item;
      do_callback();
   }
}

void CTreeControl::makeVisible(CTreeItem *item) {
   if (item->tree() != this) return;
   int dy = y() - item->y();
   if (dy > 0) {
      position(xposition(),yposition()-dy);
      redraw();
   } 
   dy = y() + h() - (item->y() + item->h());
   if (dy < 0) {
      position(xposition(),yposition()-dy);
      redraw();
   }
}

int CTreeControl::handle(int event) {
   CTreeItem *old_selection;

   if (event == FL_FOCUS) {
      m_tabPressed = false;
      old_selection = selected();
      if (old_selection) {
         Fl::focus(old_selection);
         redraw();
         return true;
      }
   }

   return CScroll::handle(event);
}
