/***************************************************************************
 *   Copyright (C) 2004, 2005 Thomas Nagy                                  *
 *   tnagy2^8@yahoo.fr                                                     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License version 2        *
 *   as published by the Free Software Foundation (see COPYING)            *
 *                                                                         *
 *   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.                          *
 *                                                                         *
 ***************************************************************************/

#ifndef _DCANVASVIEW_H
#define _DCANVASVIEW_H

#include <memory>

#include <qcanvas.h>
#include <qvaluelist.h>

#include "DCanvasRef.h"
#include "DGuiView.h"
#include "DGuiItem.h"
#include "DItem.h"
#include "DCanvasView.h"
#include "DCanvasTip.h"


class QPoint;
class DCanvasItem;
class DCanvasPopup;
class QRect;
class InlineEdit; // bad pun intended
class DCanvasRef;

/**
 * This class is a view for the mindmap
 *
 * @short Mindmap view
 * @author Thomas Nagy <tnagy2^8@yahoo.fr>
 * @version 0.3.1
 */
class DCanvasView : public QCanvasView, public DGuiView
{
Q_OBJECT

friend class InlineEdit;
friend class DCanvasTip;
public:
	enum ActionType {
		act_point,
		act_link,
		act_sort,
		act_scroll
	};

	DCanvasView( QWidget* parent=0, const char* name=0 );
	~DCanvasView();

	bool oneItemIsSelected() const;
	bool itemsAreSelected() const;

	/**
	 * Clear the selection
	 */
	void deselectAll();

	/*
	 * This method is used to change the action type
	 * triggered by the user
	 */
	void setActionType(ActionType action);

	/* 
	 * Use this to avoid casting Item() all the time
	 */
	DCanvasItem* canvasItem(int);

	/*
	 * This method is used to return the minimum window containing
	 * all mindmap items
	 */
	QRect canvasSize();

	inline const DCanvasRef* selectedRef() {return m_selectedref;}
	inline const DCanvasLink* selectedLink() {return m_selectedlink;}

	// action to request an existing color theme
	void applyColorScheme(int sel);
	// action to request a custom color
	void applyCustomColor();

	/**
	 * Extend canvas if it is too small for the given coordinates
	 */
	void extendIfNecessary(DDataItem::Coord, DDataItem::Coord, DDataItem::Coord, DDataItem::Coord);

	/**
	 * Extend canvas if any item is too near a border
	 */
	void extendIfNecessary();

protected:
	void hideInlineEditor(bool save=true);

signals:
	/**
	 *  TedrawDone is emitted after the redrawing of the canvas is done.  It is used
	 *  to detect when the complete loading sequence, including the initial draw and the
	 *  data manipulations resulting from that, is finished. At that point, the changed
	 *  flags can be reset to false, so real document changes can be detected.
	 */
	void redrawDone();

	/**
	 *  SelectionChanged is emitted after the object selected have changed,
	 */
	void selectionChanged();

	/**
	 *  Emitted whenever the action type changes
	 */
	void actionType( int );

protected:
	void contentsMouseReleaseEvent(QMouseEvent* me);
	void contentsMouseMoveEvent(QMouseEvent* me);
	void contentsMouseDoubleClickEvent(QMouseEvent* me);
	void contentsMousePressEvent(QMouseEvent* me);
	void contentsWheelEvent( QWheelEvent* );
	void keyPressEvent( QKeyEvent* );
	void keyReleaseEvent( QKeyEvent* );

	// used for scrolling
	void mousePressEvent(QMouseEvent*);
	void mouseMoveEvent(QMouseEvent*);

	void drawContents(QPainter*, int, int, int, int);
	void dragEnterEvent(QDragEnterEvent *event);
	void dropEvent(QDropEvent *event);

private:

	// TODO : i do not like this code (ITA)
	/** Makes sure that extension is not done inside a
	  critical block, but optionally do it after leaving the
	  block. */
	class ExtensionLock {
		public:
			/** If view is given, extension is done in the
			  destruction if necessary */
			ExtensionLock(DCanvasView *const view=0): m_view(view) {
				++m_neesting;
			}
			~ExtensionLock() {
				if (m_neesting) --m_neesting;
				if (m_view) m_view->extendIfNecessary();
			}
			/** Check whether extension is disallowed. */ 
			static bool abort() {
				return m_neesting!=0;
			}
		private:
			/// neesting level of instances
			static unsigned int m_neesting;
			DCanvasView *const m_view;
	};

	/// Lock used between mouse-pressed and mouse released
	/** This is a special case, since lock must start on
	  mouse click and end on mouse release (normally the
	  lock is only inside one method, not between
	  different methods. Updating the size e.g. during
	  dragging is far too slow, not necessary, and may
	  even result in strange (unintended) behaviour.

	  @note Sometimes (double-click) mouse is released
	  twice! With this construct, it does not matter. */
	std::auto_ptr<ExtensionLock> m_clickLock;


	InlineEdit *m_textedit;

	/// default width and height for the
	static const int m_defaultsize = 3000;

	// global tooltip (TODO : improve it to display pictures, .. like in konqueror)
	DCanvasTip *m_tooltip;

	///
	DCanvasItem *m_startItem;

	/// last item selected
	int m_lastitem;

	/// last item created or selected
	int m_lastitemtoselect;

	/// rubber band for link mode
	QCanvasLine* m_rubberLine;

	/// rubber band for selecting items
	QCanvasRectangle* m_rubberRect;

	/// start of a movement (necessary for rubberbands)
	QPoint m_moveStart;

	/// start of a scroll drag (more accurate)
	QPoint m_scrollStart;

	/// popup menu for item properties
	DCanvasPopup* m_menu;

	/// necessary for connecting the view
	void plug();
	void unplug();

	int m_itemtosort;
	unsigned int m_sortcount;

	ActionType m_currentAction;

	void addItemToSelection( int );
	void removeSelectedItems();

	// selects items recursively
	void selectAllChildren( int, bool=true );

	// on mouse press, locate the corresponding item
	int locateItem( const QPoint& );

	// same as above, but used to change the sort order of an item
	int locateItemPos( const QPoint& );

	/// center the view on a particular mindmap item
	void centerOnObject( int obj );

	void makeObjectVisible( int obj );

	void updateObjLinks( int obj );

	/// is the mouse left button currently pressed ?
	bool m_pressed;

	/// mouse wheel is pressed
	bool m_wheelpressed;
	DCanvasView::ActionType m_wheelpressed_backup;

	/// the user is trying to select multiple items with the rubberband
	bool m_canSelect;

	/// has the user selected something ? he may want to keep the selection
	bool m_justSelectedSomething;

	DCanvasRef* m_selectedref;
	DCanvasLink* m_selectedlink;

	// selected items - by id
	QValueList<int> m_selectedList;
	QValueList<int>::iterator m_it;

public slots:
	void popupMenuSel(int sel);

	void tidyClean();
	void tidyCanvas();
	void tidyUnclutter();

	/// focus on the root of the biggest tree
	void focusOnRoot();

	/// cycle the focus on the roots of trees
	void focusOnNextRoot();

	/// change the view
	void selectObjUp();
	void selectObjDown();
	void selectObjLeft();
	void selectObjRight();

	/// move objects
	void moveSelObjectsUp();
	void moveSelObjectsDown();
	void moveSelObjectsLeft();
	void moveSelObjectsRight();

	/// add objects with an action
	void addChild();
	void addSibling();


protected slots:
	void settingsChanged();
	void updateItem(int id);
	void createItem(int);
	void removeItem(int);
	void updateSelectedItem(int, DGuiView*);
	void changeRef(int, int, bool add=true);
	void updateFromTextEdit();
};

#endif // _DCANVASVIEW_H
