/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CTreeView.h  -  description
                             -------------------
    begin                : June 21 2003
    copyright            : (C) 2002-2006 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;
using namespace sptk;

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();

	if (isRoot)
		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(0x40404000);
			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));
	}

	if (isRoot)
		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);
}
