// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE

#include "uieditview.h"

#if VSTGUI_LIVE_EDITING

#include "uiundomanager.h"
#include "uiactions.h"
#include "uicrosslines.h"
#include "igridprocessor.h"
#include "uiselection.h"
#include "uioverlayview.h"
#include "../icontroller.h"
#include "../uiattributes.h"
#include "../uidescription.h"
#include "../iviewfactory.h"
#include "../cstream.h"
#include "../detail/uiviewcreatorattributes.h"
#include "../../lib/cvstguitimer.h"
#include "../../lib/cexternalview.h"
#include "../../lib/cframe.h"
#include "../../lib/cscrollview.h"
#include "../../lib/cdropsource.h"
#include "../../lib/coffscreencontext.h"
#include "../../lib/clayeredviewcontainer.h"
#include "../../lib/dragging.h"
#include "../../lib/events.h"
#include "../../lib/idatapackage.h"
#include "../../lib/controls/ctextedit.h"
#include <cassert>

namespace VSTGUI {

namespace UIEditViewInternal {

//----------------------------------------------------------------------------------------------------
class UISelectionView : public UIOverlayView, public UISelectionListenerAdapter
//----------------------------------------------------------------------------------------------------
{
public:
	UISelectionView (CViewContainer* editView, UISelection* selection, const CColor& selectionColor, CCoord handleSize);
	~UISelectionView () override;

private:
	void draw (CDrawContext* pContext) override;
	void drawResizeHandle (const CPoint& p, CDrawContext* pContext);

	void selectionWillChange (UISelection*) override { onSelectionChanged (); }
	void selectionDidChange (UISelection*) override { onSelectionChanged (); }
	void selectionViewsWillChange (UISelection*) override { onSelectionChanged (); }
	void selectionViewsDidChange (UISelection*) override { onSelectionChanged (); }

	void onSelectionChanged ();

	SharedPointer<UISelection> selection;
	CColor selectionColor;
	CCoord handleInset;
};

//----------------------------------------------------------------------------------------------------
UISelectionView::UISelectionView (CViewContainer* editView, UISelection* selection, const CColor& selectionColor, CCoord handleSize)
: UIOverlayView (editView)
, selection (selection)
, selectionColor (selectionColor)
, handleInset (handleSize / 2.)
{
	selection->registerListener (this);
}

//----------------------------------------------------------------------------------------------------
UISelectionView::~UISelectionView ()
{
	selection->unregisterListener (this);
}

//----------------------------------------------------------------------------------------------------
void UISelectionView::drawResizeHandle (const CPoint& p, CDrawContext* pContext)
{
	CRect r (p.x, p.y, p.x, p.y);
	r.inset (-handleInset, -handleInset);
	pContext->drawRect (r, kDrawFilledAndStroked);
}

//----------------------------------------------------------------------------------------------------
void UISelectionView::draw (CDrawContext* pContext)
{
	auto lineWidth = pContext->getHairlineSize ();
	CRect r (getVisibleViewSize ());
	ConcatClip cc (*pContext, r);
	pContext->setDrawMode (kAliasing);
	pContext->setLineStyle (kLineSolid);
	pContext->setLineWidth (lineWidth);

	CColor lightColor (kWhiteCColor);
	lightColor.alpha = 140;
	pContext->setFillColor (lightColor);

	CView* mainView = getTargetView ()->getView (0);
	CPoint p;
	frameToLocal (p);
	for (auto view : *selection)
	{
		CRect vs = selection->getGlobalViewCoordinates (view);
		vs.offsetInverse (p);
		vs.extend (lineWidth, lineWidth);
		pContext->setFrameColor (lightColor);
		pContext->drawRect (vs);
		vs.inset (lineWidth, lineWidth);
		pContext->setFrameColor (selectionColor);
		pContext->drawRect (vs);
		if (vs.getWidth () > handleInset * 2. && vs.getHeight () > handleInset * 2.)
		{
			drawResizeHandle (vs.getBottomRight (), pContext);
			if (view != mainView)
			{
				drawResizeHandle (vs.getTopLeft (), pContext);
				drawResizeHandle (vs.getBottomLeft (), pContext);
				drawResizeHandle (vs.getTopRight (), pContext);
			}
			if (vs.getHeight () > handleInset * 4)
			{
				drawResizeHandle (vs.getRightCenter (), pContext);
				if (view != mainView)
					drawResizeHandle (vs.getLeftCenter (), pContext);
			}
			if (vs.getWidth () > handleInset * 4)
			{
				drawResizeHandle (vs.getBottomCenter (), pContext);
				if (view != mainView)
					drawResizeHandle (vs.getTopCenter (), pContext);
			}
		}
	}
}

//----------------------------------------------------------------------------------------------------
void UISelectionView::onSelectionChanged ()
{
	CPoint p;
	frameToLocal (p);
	for (auto view : *selection)
	{
		CRect vs = selection->getGlobalViewCoordinates (view);
		vs.offsetInverse (p);
		vs.extend (handleInset + 2, handleInset + 2);
		invalidRect (vs);
	}
}

//----------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------
class UIHighlightView : public UIOverlayView
{
public:
	UIHighlightView (CViewContainer* editView, const CColor& viewHighlightColor);

	void setHighlightView (CView* view);
private:
	void draw (CDrawContext* pContext) override;

	CView* highlightView;
	CColor strokeColor;
	CColor fillColor;
};

//----------------------------------------------------------------------------------------------------
UIHighlightView::UIHighlightView (CViewContainer* editView, const CColor& viewHighlightColor)
: UIOverlayView (editView)
, highlightView (nullptr)
, strokeColor (viewHighlightColor)
{
	double h,s,l;
	strokeColor.toHSL (h, s, l);
	l /= 2.;
	fillColor.fromHSL (h, s, l);
	fillColor.alpha = strokeColor.alpha;
}

//----------------------------------------------------------------------------------------------------
void UIHighlightView::setHighlightView (CView* view)
{
	if (highlightView != view)
	{
		highlightView = view;
		invalid ();
	}
}

//----------------------------------------------------------------------------------------------------
void UIHighlightView::draw (CDrawContext* pContext)
{
	if (highlightView == nullptr)
		return;
	CRect r = UISelection::getGlobalViewCoordinates (highlightView);
	CPoint p;
	frameToLocal (p);
	r.offsetInverse (p);
	r.inset (2, 2);
	pContext->setFillColor (fillColor);
	pContext->setFrameColor (strokeColor);
	pContext->setLineStyle (kLineSolid);
	pContext->setLineWidth (3);
	pContext->drawRect (r, kDrawFilledAndStroked);
}

//------------------------------------------------------------------------
template<typename T>
void collectExternalViewsOnInlineEditing (CViewContainer* container, T& array)
{
	container->forEachChild ([&] (auto view) {
		if (view.template cast<ExternalView::IViewEmbedder> ())
			array.emplace_back (view);
		else if (auto c = view->asViewContainer ())
			collectExternalViewsOnInlineEditing (c, array);
	});
}

} // UIEditViewInternal

//------------------------------------------------------------------------
struct UIEditView::ViewAddedObserver : IViewAddedRemovedObserver,
									   ViewListenerAdapter
{
	ViewAddedObserver (CViewContainer* root) : root (root) {}
	~ViewAddedObserver () override
	{
		for (auto view : views)
		{
			if (auto viewEmbedder = dynamic_cast<ExternalView::IViewEmbedder*> (view))
			{
				if (auto ev = viewEmbedder->getExternalView ())
					ev->setMouseEnabled (view->getMouseEnabled ());
			}
			view->unregisterViewListener (this);
		}
	}

	bool isViewSubViewOfRoot (CView* view)
	{
		CView* parent = view->getParentView ();
		if (parent == nullptr || parent == root->getFrame ())
			return false;
		if (parent->asViewContainer () == root)
			return true;
		return isViewSubViewOfRoot (parent);
	}

	void onViewAdded (CFrame* frame, CView* view) override
	{
		if (auto viewEmbedder = dynamic_cast<ExternalView::IViewEmbedder*> (view))
		{
			if (!isViewSubViewOfRoot (view))
				return;
			if (auto ev = viewEmbedder->getExternalView ())
				ev->setMouseEnabled (false);
			view->registerViewListener (this);
			views.emplace_back (view);
		}
	}
	void onViewRemoved (CFrame* frame, CView* view) override {}

	void viewWillDelete (CView* view) override
	{
		view->unregisterViewListener (this);
		auto it = std::find (views.begin (), views.end (), view);
		if (it != views.end ())
			views.erase (it);
	}
	void viewOnMouseEnabled (CView* view, bool state) override
	{
		if (auto viewEmbedder = dynamic_cast<ExternalView::IViewEmbedder*> (view))
		{
			if (auto ev = viewEmbedder->getExternalView ())
				ev->setMouseEnabled (false);
		}
	}

	CViewContainer* root;
	std::vector<CView*> views;
};

//----------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------
IdStringPtr UIEditView::kMsgAttached = "UIEditView::kMsgAttached";
IdStringPtr UIEditView::kMsgRemoved = "UIEditView::kMsgRemoved";

static constexpr auto kResizeHandleSize = 6.;
static constexpr auto UIEditViewMargin = 8.;

//----------------------------------------------------------------------------------------------------
UIEditView::UIEditView (const CRect& size, UIDescription* uidescription)
: CViewContainer (size)
, description (uidescription)
, gridProcessor (nullptr)
{
	setScale (1.);
	setWantsFocus (true);
}

//----------------------------------------------------------------------------------------------------
UIEditView::~UIEditView ()
{
	editTimer = nullptr;
	setUndoManager (nullptr);
	setSelection (nullptr);
}

//------------------------------------------------------------------------
void UIEditView::updateSize ()
{
	if (CView* view = getEditView ())
	{
		CRect r (getViewSize ());
		CPoint size (view->getWidth (), view->getHeight ());
		getTransform ().transform (size);
		r.setSize (size);
		r.right += UIEditViewMargin;
		r.bottom += UIEditViewMargin;
		if (r != getViewSize ())
		{
			setAutosizingEnabled (false);
			setViewSize (r);
			setMouseableArea (getViewSize ());
			setAutosizingEnabled (true);
			getParentView ()->invalid ();
		}
	}
}

//----------------------------------------------------------------------------------------------------
void UIEditView::setScale (double scale)
{
	scale = std::round (scale * 100) / 100.;
	setTransform (CGraphicsTransform ().scale (scale, scale));
	updateSize ();
}

//----------------------------------------------------------------------------------------------------
void UIEditView::enableEditing (bool state)
{
	if (editing != state)
	{
		editing = state;
		invalid ();
		CFrame* parent = getFrame ();
		if (parent == nullptr)
			return;

		if (editing)
		{
			CRect r = parent->getViewSize ();
			r.originize ();
			vstgui_assert (overlayView == nullptr);
			overlayView = new CLayeredViewContainer (r);
			overlayView->setAutosizeFlags (kAutosizeAll);
			overlayView->setMouseEnabled (false);
			overlayView->setTransparency (true);
			overlayView->setZIndex (1000);
			parent->addView (overlayView);

			highlightView = new UIEditViewInternal::UIHighlightView (this, viewHighlightColor);
			overlayView->addView (highlightView);
			auto selectionView = new UIEditViewInternal::UISelectionView (this, getSelection (), viewSelectionColor, kResizeHandleSize);
			overlayView->addView (selectionView);
		}
		else
		{
			parent->removeView (overlayView);
			overlayView = nullptr;
			highlightView = nullptr;
			lines = nullptr;
		}
		disableExternalViewsOnInlineEditing (editing);
	}
}

//------------------------------------------------------------------------
void UIEditView::disableExternalViewsOnInlineEditing (bool state)
{
	CFrame* parent = getFrame ();
	if (editingViewAddedObserver)
		parent->setViewAddedRemovedObserver (nullptr);
	editingViewAddedObserver.reset ();
	if (state)
	{
		editingViewAddedObserver = std::make_unique<ViewAddedObserver> (this);
		std::vector<CView*> views;
		UIEditViewInternal::collectExternalViewsOnInlineEditing (this, views);
		for (auto* v : views)
			editingViewAddedObserver->onViewAdded (parent, v);
		parent->setViewAddedRemovedObserver (editingViewAddedObserver.get ());
	}
}

//----------------------------------------------------------------------------------------------------
void UIEditView::enableAutosizing (bool state)
{
	autosizing = state;
}

//----------------------------------------------------------------------------------------------------
void UIEditView::setUndoManager (UIUndoManager* manager)
{
	undoManger = manager;
}

//----------------------------------------------------------------------------------------------------
UIUndoManager* UIEditView::getUndoManager ()
{
	if (undoManger == nullptr)
		undoManger = makeOwned<UIUndoManager> ();
	return undoManger;
}

//----------------------------------------------------------------------------------------------------
void UIEditView::setSelection (UISelection* inSelection)
{
	selection = inSelection;
}

//----------------------------------------------------------------------------------------------------
UISelection* UIEditView::getSelection ()
{
	if (selection == nullptr)
	{
		selection = makeOwned<UISelection> ();
	}
	return selection;
}

//----------------------------------------------------------------------------------------------------
void UIEditView::setGridProcessor (IGridProcessor* inGrid)
{
	gridProcessor = inGrid;
}

//----------------------------------------------------------------------------------------------------
void UIEditView::setEditView (CView* view)
{
	if (view != getEditView ())
	{
		disableExternalViewsOnInlineEditing (false);
		invalid ();
		removeAll ();
		CRect vs (getViewSize ());
		if (view)
		{
			addView (view);
			updateSize ();
		}
		else
		{
			vs.setWidth (0);
			vs.setHeight (0);
			setMouseableArea (vs);
			setViewSize (vs);
		}
		disableExternalViewsOnInlineEditing (editing);
		invalid ();
	}
}

//----------------------------------------------------------------------------------------------------
CView* UIEditView::getEditView () const
{
	return getView (0);
}

//----------------------------------------------------------------------------------------------------
CMessageResult UIEditView::notify (CBaseObject* sender, IdStringPtr message)
{
	if (message == CVSTGUITimer::kMsgTimer)
	{
		if (sender == editTimer)
		{
			if (lines == nullptr)
			{
				lines = new UICrossLines (this, UICrossLines::kSelectionStyle, crosslineBackgroundColor, crosslineForegroundColor);
				overlayView->addView (lines);
				lines->update (selection);
				getFrame ()->setCursor (kCursorMoveObject);
			}
			editTimer = nullptr;
		}
		return kMessageNotified;
	}
	else if (message == kMsgViewSizeChanged)
	{
		CView* view = dynamic_cast<CView*> (sender);
		if (view && view == getEditView ())
		{
			updateSize ();
		}
	}
	return CViewContainer::notify (sender, message);
}

//----------------------------------------------------------------------------------------------------
void UIEditView::draw (CDrawContext *pContext)
{
	drawRect (pContext, getViewSize ());
}

//----------------------------------------------------------------------------------------------------
void UIEditView::drawRect (CDrawContext *pContext, const CRect& updateRect)
{
	// disable focus drawing
	bool focusDrawing = getFrame ()->focusDrawingEnabled ();
	if (!editing && focusDrawing)
		getFrame ()->setFocusDrawingEnabled (false);

	CViewContainer::drawRect (pContext, updateRect);

	if (!editing && focusDrawing)
		getFrame ()->setFocusDrawingEnabled (focusDrawing);

	pContext->setClipRect (updateRect);

	CDrawContext::Transform transform (*pContext, CGraphicsTransform ().translate (getViewSize ().left, getViewSize ().top));

	const CCoord dashLength[] = {5, 5};
	const CLineStyle lineDash (CLineStyle::kLineCapButt, CLineStyle::kLineJoinMiter, 0, 2, dashLength);
	pContext->setLineStyle (lineDash);
	pContext->setLineWidth (1);
	pContext->setDrawMode (kAliasing);
	pContext->setFrameColor (kBlueCColor);
	pContext->drawRect (
	    CRect (0, 0, getWidth () - UIEditViewMargin, getHeight () - UIEditViewMargin),
	    kDrawStroked);
}

//----------------------------------------------------------------------------------------------------
CView* UIEditView::getViewAt (const CPoint& p, const GetViewOptions& options) const
{
	CView* view = CViewContainer::getViewAt (p, options);
	if (editing)
	{
		while (view && IViewFactory::getViewName (view) == nullptr)
		{
			view = view->getParentView ();
		}
	}
	return view;
}

//----------------------------------------------------------------------------------------------------
CViewContainer* UIEditView::getContainerAt (const CPoint& p, const GetViewOptions& options) const
{
	CViewContainer* view = CViewContainer::getContainerAt (p, options);
	if (editing)
	{
		while (view && IViewFactory::getViewName (view) == nullptr)
		{
			view = view->getParentView ()->asViewContainer ();
		}
	}
	return view;
}

//----------------------------------------------------------------------------------------------------
bool UIEditView::advanceNextFocusView (CView* oldFocus, bool reverse)
{
	if (editing)
		return false;
	return CViewContainer::advanceNextFocusView (oldFocus, reverse);
}

//----------------------------------------------------------------------------------------------------
void UIEditView::onMouseWheelEvent (MouseWheelEvent& event)
{
	if (!editing)
		CViewContainer::onMouseWheelEvent (event);
}

//------------------------------------------------------------------------
void UIEditView::onZoomGestureEvent (ZoomGestureEvent& event)
{
	if (editing)
	{
		auto scale = getTransform ().m11;
		auto newScale = scale + scale * event.zoom;
		setScale (newScale);
		event.consumed = true;
	}
}

//----------------------------------------------------------------------------------------------------
void UIEditView::invalidSelection ()
{
	getSelection ()->invalidRects ();
}

namespace UIEditViewInternal {

//----------------------------------------------------------------------------------------------------
static bool pointInResizeHandleRect (const CPoint& where, const CPoint& handle)
{
	CRect r (handle.x, handle.y, handle.x, handle.y);
	r.extend (kResizeHandleSize/2., kResizeHandleSize/2.);
	return r.pointInside (where);
}

} // UIEditViewInternal

//----------------------------------------------------------------------------------------------------
UIEditView::MouseSizeMode UIEditView::selectionHitTest (const CPoint& _where, CView** resultView)
{
	CPoint where (_where);
	where.offset (-getViewSize ().left, -getViewSize ().top);
	CPoint p;
	frameToLocal (p);

	CView* mainView = getEditView ();
	for (auto it = getSelection ()->rbegin (), end = getSelection ()->rend (); it != end; ++it)
	{
		auto view = (*it);
		CRect r = getSelection ()->getGlobalViewCoordinates (view);
		bool isMainView = (mainView == view) ? true : false;
		r.offset (p);
		r.extend (kResizeHandleSize, kResizeHandleSize);
		if (r.pointInside (where))
		{
			if (resultView)
				*resultView = view;
			r.inset (kResizeHandleSize, kResizeHandleSize);
			if (UIEditViewInternal::pointInResizeHandleRect (where, r.getBottomRight ()))
				return MouseSizeMode::BottomRight;
			if (UIEditViewInternal::pointInResizeHandleRect (where, r.getRightCenter ()))
				return MouseSizeMode::Right;
			if (UIEditViewInternal::pointInResizeHandleRect (where, r.getBottomCenter ()))
				return MouseSizeMode::Bottom;
			if (!isMainView)
			{
				if (UIEditViewInternal::pointInResizeHandleRect (where, r.getLeftCenter ()))
					return MouseSizeMode::Left;
				if (UIEditViewInternal::pointInResizeHandleRect (where, r.getTopCenter ()))
					return MouseSizeMode::Top;
				if (UIEditViewInternal::pointInResizeHandleRect (where, r.getBottomLeft ()))
					return MouseSizeMode::BottomLeft;
				if (UIEditViewInternal::pointInResizeHandleRect (where, r.getTopLeft ()))
					return MouseSizeMode::TopLeft;
				if (UIEditViewInternal::pointInResizeHandleRect (where, r.getTopRight ()))
					return MouseSizeMode::TopRight;
				if (r.pointInside (where))
					return MouseSizeMode::None;
			}
			if (resultView)
				*resultView = nullptr;
		}
	}
	if (resultView)
		*resultView = nullptr;
	return MouseSizeMode::None;
}

//----------------------------------------------------------------------------------------------------
bool UIEditView::hitTestSubViews (const CPoint& where, const Event& event)
{
	return hitTest (where, event);
}

//----------------------------------------------------------------------------------------------------
CMouseEventResult UIEditView::onMouseDown (CPoint &where, const CButtonState& buttons)
{
	if (!editing)
		return CViewContainer::onMouseDown (where, buttons);
	if (!buttons.isLeftButton ())
		return kMouseEventHandled;

	getFrame ()->setFocusView (this);

	CPoint where2 (where);
	where2.offset (-getViewSize ().left, -getViewSize ().top);
	getTransform ().inverse ().transform (where2);

	if (buttons.isShiftSet ())
	{
		if (!buttons.isControlSet ())
			getSelection ()->clear ();

		mouseEditMode = MouseEditMode::WaitLasso;
		dragStartMouseObserver.init (where);
		mouseStartPoint = where2;
		return kMouseEventHandled;
	}

	CView* selectionHitView = nullptr;
	MouseSizeMode sizeMode = selectionHitTest (where, &selectionHitView);
	CView* mouseHitView = getViewAt (where, GetViewOptions ().deep ().includeViewContainer ().includeInvisible ());
	if (selectionHitView == nullptr && mouseHitView == nullptr)
	{
		getSelection ()->clear ();
		return kMouseEventHandled;
	}
	if (getSelection ()->contains (mouseHitView))
	{
		if (buttons.isControlSet ())
		{
			getSelection ()->remove (mouseHitView);
			onMouseMoved (where, CButtonState (buttons.getModifierState ()));
			return kMouseEventHandled;
		}
	}
	else if (mouseHitView && sizeMode == MouseSizeMode::None)
	{
		if (buttons.isControlSet ())
		{
			getSelection ()->add (mouseHitView);
			selectionHitView = mouseHitView;
			onMouseMoved (where, CButtonState (buttons.getModifierState ()));
		}
		else if (selectionHitView == nullptr || selectionHitView == getEditView ())
		{
			getSelection ()->setExclusive (mouseHitView);
			selectionHitView = mouseHitView;
			onMouseMoved (where, CButtonState (buttons.getModifierState ()));
		}
	}
	if (selectionHitView)
	{
		if (buttons.isDoubleClick ())
		{
			onDoubleClickEditing (selectionHitView);
			return kMouseEventHandled;
		}
		if (buttons.isAltSet () && !getSelection ()->contains (getEditView ()))
		{
			mouseEditMode = MouseEditMode::WaitDrag;
			dragStartMouseObserver.init (where);
			return kMouseEventHandled;
		}
		if (sizeMode == MouseSizeMode::None)
		{
			if (getSelection ()->contains (getEditView ()))
			{
				return kMouseEventHandled;
			}
			mouseEditMode = MouseEditMode::DragEditing;
			mouseStartPoint = where2;
			if (gridProcessor)
				gridProcessor->process (mouseStartPoint);
			editTimer = owned (new CVSTGUITimer (this, 500));
			editTimer->start ();
			return kMouseEventHandled;
		}
		else
		{
			mouseEditMode = MouseEditMode::SizeEditing;
			mouseStartPoint = where2;
			if (gridProcessor)
				gridProcessor->process (mouseStartPoint);
			mouseSizeMode = sizeMode;
			if (true)
			{
				int32_t crossLineMode = 0;
				switch (sizeMode)
				{
					case MouseSizeMode::Left:
					case MouseSizeMode::Right:
					case MouseSizeMode::Top:
					case MouseSizeMode::Bottom: crossLineMode = UICrossLines::kSelectionStyle; break;
					default : crossLineMode = UICrossLines::kDragStyle; break;
				}
				lines = new UICrossLines (this, crossLineMode, crosslineBackgroundColor, crosslineForegroundColor);
				overlayView->addView (lines);
				if (crossLineMode == UICrossLines::kSelectionStyle)
					lines->update (selection);
				else
					lines->update (CPoint (mouseStartPoint.x, mouseStartPoint.y));
			}
			return kMouseEventHandled;
		}
	}
	return kMouseEventHandled;
}

//----------------------------------------------------------------------------------------------------
CMouseEventResult UIEditView::onMouseUp (CPoint &where, const CButtonState& buttons)
{
	if (!editing)
		return CViewContainer::onMouseUp (where, buttons);

	editTimer = nullptr;
	if (mouseEditMode == MouseEditMode::LassoSelection)
	{
		CPoint where2 (where);
		where2.offset (-getViewSize ().left, -getViewSize ().top);
		getTransform ().inverse ().transform (where2);

		CRect area;
		area.setTopLeft (mouseStartPoint);
		area.setBottomRight (where2);
		area.normalize ();
		auto result = findChildsInArea (getEditView ()->asViewContainer (), area);
		for (auto& view : result)
		{
			if (IViewFactory::getViewName (view) && !getSelection ()->contains (view))
				getSelection ()->add (view);
		}
	}
	else if (mouseEditMode != MouseEditMode::NoEditing && !moveSizeOperation && buttons == kLButton && !lines)
	{
		CView* view = getViewAt (where, GetViewOptions ().deep ().includeViewContainer ().includeInvisible ());
		if (view == this)
			view = nullptr;
		if (view)
		{
			getSelection ()->setExclusive (view);
		}
	}
	if (lines)
	{
		overlayView->removeView (lines);
		lines = nullptr;
	}
	mouseEditMode = MouseEditMode::NoEditing;
	if (moveSizeOperation)
	{
		if (moveSizeOperation->didChange ())
			getUndoManager ()->pushAndPerform (moveSizeOperation);
		else
			delete moveSizeOperation;
		moveSizeOperation = nullptr;
	}
	onMouseMoved (where, CButtonState (buttons.getModifierState ()));
	return kMouseEventHandled;
}

//----------------------------------------------------------------------------------------------------
CMouseEventResult UIEditView::onMouseMoved (CPoint &where, const CButtonState& buttons)
{
	if (!editing)
		return CViewContainer::onMouseMoved (where, buttons);
	if (inlineAttrTextEditOpen)
		return kMouseEventHandled;

	CPoint where2 (where);
	where2.offset (-getViewSize ().left, -getViewSize ().top);
	getTransform ().inverse ().transform (where2);
	if (buttons & kLButton)
	{
		if (mouseEditMode == MouseEditMode::LassoSelection)
		{
			if (lines)
			{
				CRect r;
				r.setTopLeft (mouseStartPoint);
				r.setBottomRight (where2);
				r.normalize ();
				lines->update (r);
			}
			getFrame ()->setCursor (kCursorDefault);
		}
		else if (mouseEditMode == MouseEditMode::WaitLasso && buttons.isShiftSet ())
		{
			if (dragStartMouseObserver.shouldStartDrag (where))
			{
				mouseEditMode = MouseEditMode::LassoSelection;
				lines = new UICrossLines (this, UICrossLines::kLassoStyle, lassoFrameColor,
										  lassoFillColor);
				overlayView->addView (lines);
				getFrame ()->setCursor (kCursorDefault);
				CViewContainer::onMouseMoved (where, buttons);
			}
		}
		else if (getSelection ()->total () > 0)
		{
			if (mouseEditMode == MouseEditMode::DragEditing)
			{
				doDragEditingMove (where2);
			}
			else if (mouseEditMode == MouseEditMode::SizeEditing)
			{
				doSizeEditingMove (where2);
			}
			else if (mouseEditMode == MouseEditMode::WaitDrag)
			{
				if (dragStartMouseObserver.shouldStartDrag (where))
				{
					mouseEditMode = MouseEditMode::DragEditing;
					invalidSelection ();
					startDrag (where);
					mouseEditMode = MouseEditMode::NoEditing;
					invalidSelection ();
				}
			}
		}
		CScrollView* scrollView = dynamic_cast<CScrollView*>(getParentView ()->getParentView ());
		if (scrollView)
		{
			scrollView->makeRectVisible (CRect (where, CPoint (1, 1)));
		}
		return kMouseEventHandled;
	}
	else if (buttons.getButtonState () == 0 && !buttons.isShiftSet ())
	{
		CView* view = nullptr;
		CCursorType ctype = kCursorDefault;
		auto mode = selectionHitTest (where, &view);
		if (view)
		{
			switch (mode)
			{
				case MouseSizeMode::Right:
				case MouseSizeMode::Left: ctype = kCursorHSize; break;
				case MouseSizeMode::Top:
				case MouseSizeMode::Bottom: ctype = kCursorVSize; break;
				case MouseSizeMode::TopLeft:
				case MouseSizeMode::BottomRight: ctype = kCursorNWSESize; break;
				case MouseSizeMode::TopRight:
				case MouseSizeMode::BottomLeft: ctype = kCursorNESWSize; break;
				case MouseSizeMode::None:
				{
					if (getSelection ()->contains (getEditView ()) == false)
						ctype = kCursorMovableObject;
					break;
				}
				default: ctype = kCursorDefault; break;
			}
		}
		getFrame ()->setCursor (ctype);
	}
	else
		getFrame ()->setCursor (kCursorDefault);
	return kMouseEventHandled;
}

//------------------------------------------------------------------------
void UIEditView::onKeyboardEvent (KeyboardEvent& event)
{
	if (mouseEditMode != MouseEditMode::NoEditing && event.virt == VirtualKey::Escape &&
	    event.type == EventType::KeyDown)
	{
		if (lines)
		{
			overlayView->removeView (lines);
			lines = nullptr;
		}
		if (moveSizeOperation)
		{
			moveSizeOperation->undo ();
			delete moveSizeOperation;
			moveSizeOperation = nullptr;
		}
		mouseEditMode = MouseEditMode::NoEditing;
		getFrame ()->setCursor (kCursorDefault);
		event.consumed = true;
		return;
	}
	CViewContainer::onKeyboardEvent (event);
}

//-----------------------------------------------------------------------------
void UIEditView::doKeyMove (const CPoint& delta)
{
	if (delta.x != 0. || delta.y != 0.)
	{
		if (getSelection ()->contains (getEditView ()))
			return;
		if (!moveSizeOperation)
			moveSizeOperation = new ViewSizeChangeOperation (selection, false, autosizing);
		getSelection ()->moveBy (delta);
		if (moveSizeOperation)
		{
			getUndoManager ()->pushAndPerform (moveSizeOperation);
			moveSizeOperation = nullptr;
		}
	}
}

//-----------------------------------------------------------------------------
void UIEditView::doKeySize (const CPoint& delta)
{
	if (delta.x != 0. || delta.y != 0.)
	{
		if (!moveSizeOperation)
			moveSizeOperation = new ViewSizeChangeOperation (selection, true, autosizing);
		getSelection ()->viewsWillChange ();
		for (auto view : *selection)
		{
			CRect viewSize = view->getViewSize ();
			CPoint bottomRight = viewSize.getBottomRight ();
			bottomRight += delta;
			viewSize.setBottomRight (bottomRight);
			view->setViewSize (viewSize);
			view->setMouseableArea (viewSize);
		}
		getSelection ()->viewsDidChange ();
		getUndoManager ()->pushAndPerform (moveSizeOperation);
		moveSizeOperation = nullptr;
	}
}

//----------------------------------------------------------------------------------------------------
std::vector<CView*> UIEditView::findChildsInArea (CViewContainer* view, CRect r) const
{
	std::vector<CView*> views;
	view->forEachChild ([&] (CView* child) {
		if (r.rectOverlap (child->getViewSize ()))
		{
			if (auto container = child->asViewContainer ())
			{
				auto r2 = r;
				auto viewSize = container->getViewSize ();
				r2.bound (viewSize);
				if (!r2.isEmpty ())
				{
					r2.offsetInverse (viewSize.getTopLeft ());
					auto res2 = findChildsInArea (container, r2);
					std::move (res2.begin (), res2.end (), std::back_inserter (views));
				}
			}
			else
			{
				views.push_back (child);
			}
		}
	});

	return views;
}

//----------------------------------------------------------------------------------------------------
void UIEditView::doDragEditingMove (CPoint& where)
{
	if (gridProcessor)
		gridProcessor->process (where);
	CPoint diff (where.x - mouseStartPoint.x, where.y - mouseStartPoint.y);
	if (diff.x != 0. || diff.y != 0.)
	{
		if (!moveSizeOperation)
			moveSizeOperation = new ViewSizeChangeOperation (selection, false, autosizing);
		getSelection ()->moveBy (diff);
		mouseStartPoint = where;
		if (editTimer)
		{
			editTimer = nullptr;
			if (!lines)
			{
				lines = new UICrossLines (this, UICrossLines::kSelectionStyle, crosslineBackgroundColor, crosslineForegroundColor);
				overlayView->addView (lines);
				lines->update (selection);
			}
			getFrame ()->setCursor (kCursorMoveObject);
		}
		if (lines)
			lines->update (selection);
	}
}

//----------------------------------------------------------------------------------------------------
void UIEditView::doSizeEditingMove (CPoint& where)
{
	if (!moveSizeOperation)
		moveSizeOperation = new ViewSizeChangeOperation (selection, true, autosizing);
	if (gridProcessor)
		gridProcessor->process (where);
	if (mouseStartPoint == where)
		return;

	CRect diff;
	switch (mouseSizeMode)
	{
		case MouseSizeMode::Left:
		{
			diff.left = where.x - mouseStartPoint.x;
			break;
		}
		case MouseSizeMode::Right:
		{
			diff.right = where.x - mouseStartPoint.x;
			break;
		}
		case MouseSizeMode::Top:
		{
			diff.top = where.y - mouseStartPoint.y;
			break;
		}
		case MouseSizeMode::Bottom:
		{
			diff.bottom = where.y - mouseStartPoint.y;
			break;
		}
		case MouseSizeMode::TopLeft:
		{
			diff.left = where.x - mouseStartPoint.x;
			diff.top = where.y - mouseStartPoint.y;
			break;
		}
		case MouseSizeMode::TopRight:
		{
			diff.right = where.x - mouseStartPoint.x;
			diff.top = where.y - mouseStartPoint.y;
			break;
		}
		case MouseSizeMode::BottomRight:
		{
			diff.right = where.x - mouseStartPoint.x;
			diff.bottom = where.y - mouseStartPoint.y;
			break;
		}
		case MouseSizeMode::BottomLeft:
		{
			diff.left = where.x - mouseStartPoint.x;
			diff.bottom = where.y - mouseStartPoint.y;
			break;
		}
		default: break;
	}
	std::vector<bool> oldAutosizeState;
	if (!autosizing)
	{
		for (auto& view : *selection)
		{
			if (auto container = view->asViewContainer ())
			{
				oldAutosizeState.emplace_back (container->getAutosizingEnabled ());
				container->setAutosizingEnabled (false);
			}
		}
	}
	selection->sizeBy (diff);
	if (!autosizing)
	{
		size_t index = 0;
		for (auto& view : *selection)
		{
			if (auto container = view->asViewContainer ())
			{
				container->setAutosizingEnabled (oldAutosizeState[index]);
				++index;
			}
		}
	}
	mouseStartPoint = where;
	if (lines)
	{
		if (lines->getStyle () == UICrossLines::kSelectionStyle)
			lines->update (selection);
		else
			lines->update (mouseStartPoint);
	}
}

//----------------------------------------------------------------------------------------------------
CMouseEventResult UIEditView::onMouseExited (CPoint& where, const CButtonState& buttons)
{
	getFrame()->setCursor (kCursorDefault);
	return kMouseEventHandled;
}

//----------------------------------------------------------------------------------------------------
void UIEditView::startDrag (CPoint& where)
{
	auto bitmap = createBitmapFromSelection (selection, getFrame (), this);
	if (bitmap == nullptr)
		return;
	where.makeIntegral ();

	CRect selectionBounds = getSelection ()->getBounds ();
	CPoint p;
	getParentView ()->localToFrame (p);
	selectionBounds.offsetInverse (p);

	CPoint offset;
	offset.x = (selectionBounds.left - where.x);
	offset.y = (selectionBounds.top - where.y);

	getSelection ()->setDragOffset (CPoint (offset.x, offset.y));

	std::string templateName;
	if (description->getTemplateNameFromView (getEditView (), templateName))
		description->updateViewDescription (templateName.c_str (), getEditView ());

	CMemoryStream stream (1024, 1024, false);
	if (!getSelection ()->store (stream, description))
		return;
	stream.end ();

	auto callback = makeOwned<DragCallbackFunctions> ();
	callback->endedFunc = [this] (IDraggingSession*, CPoint pos, DragOperation) {
		frameToLocal (pos);
		onMouseMoved (pos, 0);
	};

	auto dropSource = CDropSource::create (stream.getBuffer (), static_cast<uint32_t>(stream.tell ()), CDropSource::kText);
	doDrag (DragDescription (dropSource, offset, bitmap), callback);
}

//----------------------------------------------------------------------------------------------------
SharedPointer<UISelection> UIEditView::getSelectionOutOfDrag (IDataPackage* drag) const
{
	IDataPackage::Type type;
	const void* dragData;
	uint32_t size;
	if ((size = drag->getData (0, dragData, type)) > 0 && type == IDataPackage::kText)
	{
		auto oldController = description->getController ();
		if (auto* controller = getEditor () ? dynamic_cast<IController*> (getEditor ()) : nullptr)
			description->setController (controller);
		CMemoryStream stream (static_cast<const int8_t*> (dragData), size, false);
		auto newSelection = makeOwned<UISelection> ();
		if (newSelection->restore (stream, description))
		{
			description->setController (oldController);
			return newSelection;
		}
		description->setController (oldController);
	}
	return nullptr;
}

//----------------------------------------------------------------------------------------------------
SharedPointer<IDropTarget> UIEditView::getDropTarget ()
{
	if (editing)
		return this;
	return CViewContainer::getDropTarget ();
}

//----------------------------------------------------------------------------------------------------
bool UIEditView::onDrop (DragEventData data)
{
	if (lines)
	{
		overlayView->removeView (lines);
		lines = nullptr;
	}
	if (dragSelection)
	{
		if (highlightView)
		{
			highlightView->setHighlightView (nullptr);
		}
		CPoint where2 (data.pos);
		where2.offset (dragSelection->getDragOffset ().x, dragSelection->getDragOffset ().y);
		if (gridProcessor)
		{
			getTransform ().inverse ().transform (where2);
			gridProcessor->process (where2);
			getTransform ().transform (where2);
		}
		CViewContainer* viewContainer = getContainerAt (where2, GetViewOptions ().deep ());
		if (viewContainer && viewContainer != this)
		{
			where2.offset (-getViewSize ().left, -getViewSize ().top);
			getTransform ().inverse ().transform (where2);
			CPoint containerOffset;
			viewContainer->localToFrame (containerOffset);
			frameToLocal (containerOffset);
			where2.offset (-containerOffset.x, -containerOffset.y);

			where2.makeIntegral ();
			IAction* action = new ViewCopyOperation (dragSelection, getSelection (), viewContainer, where2, description);
			getUndoManager()->pushAndPerform (action);
		}
		dragSelection = nullptr;
	}
	return true;
}

//----------------------------------------------------------------------------------------------------
DragOperation UIEditView::onDragEnter (DragEventData data)
{
	dragSelection = getSelectionOutOfDrag (data.drag);
	if (dragSelection)
	{
		if (!lines)
		{
			lines = new UICrossLines (this, UICrossLines::kDragStyle, crosslineBackgroundColor, crosslineForegroundColor);
			overlayView->addView (lines);
		}
		return onDragMove (data);
	}
	return DragOperation::None;
}

//----------------------------------------------------------------------------------------------------
void UIEditView::onDragLeave (DragEventData data)
{
	dragSelection = nullptr;
	if (highlightView)
	{
		highlightView->setHighlightView (nullptr);
	}
	if (lines)
	{
		overlayView->removeView (lines);
		lines = nullptr;
	}
}

//----------------------------------------------------------------------------------------------------
DragOperation UIEditView::onDragMove (DragEventData data)
{
	if (editing)
	{
		if (lines)
		{
			if (dragSelection)
			{
				CPoint where2 (data.pos);
				where2.offset (dragSelection->getDragOffset ().x, dragSelection->getDragOffset ().y);
				where2.offset (-getViewSize ().left, -getViewSize ().top);
				if (gridProcessor)
				{
					getTransform ().inverse ().transform (where2);
					gridProcessor->process (where2);
					getTransform ().transform (where2);
				}
				CPoint where3 (where2);
				getTransform ().inverse ().transform (where3);
				lines->update (where3);
				if (highlightView)
				{
					CRect visibleRect = getVisibleViewSize ();
					where2.offset (getViewSize ().left, getViewSize ().top);
					where2.offset (-visibleRect.left, -visibleRect.top);
					auto container = getContainerAt (where2, GetViewOptions ().deep ());
					if (container == this)
					{
						container = nullptr;
						highlightView->setHighlightView (nullptr);
						return DragOperation::None;
					}
					highlightView->setHighlightView (container);
				}
				return DragOperation::Copy;
			}
		}
	}
	return DragOperation::None;
}

//-----------------------------------------------------------------------------
void UIEditView::onDoubleClickEditing (CView* view)
{
	struct AttributeInlineEditorController : ViewListenerAdapter
	{
		using Callback = std::function<void ()>;
		AttributeInlineEditorController (CTextEdit* edit, Callback&& callback)
		: edit (edit), callback (std::move (callback))
		{
			edit->registerViewListener (this);
		}
		void viewWillDelete (CView*) override
		{
			edit->unregisterViewListener (this);
			delete this;
		}
		void viewLostFocus (CView*) override
		{
			callback ();
		}

	private:
		CTextEdit* edit;
		Callback callback;
	};

	const auto* factory = description->getViewFactory ();
	vstgui_assert (factory);
	std::string attrValue;
	if (!factory ||
	    !factory->getAttributeValue (view, UIViewCreator::kAttrTitle, attrValue, description))
		return;

	auto frame = getFrame ();
	frame->setCursor (kCursorDefault);

	auto r = selection->getGlobalViewCoordinates (view);
	r.offsetInverse (getViewSize ().getTopLeft ());
	translateToLocal (r, true);
	auto textEdit = new CTextEdit (r, nullptr, 0);
	textEdit->setText (attrValue.data ());
	addView (textEdit);
	new AttributeInlineEditorController (textEdit, [this, textEdit, attrValue] () {
		const auto& text = textEdit->getText ();
		if (text != attrValue)
		{
			auto action = new AttributeChangeAction (description, selection,
			                                         UIViewCreator::kAttrTitle, text.getString ());
			getUndoManager ()->pushAndPerform (action);
		}
		textEdit->getParentView ()->asViewContainer ()->removeView (textEdit);
		inlineAttrTextEditOpen = false;
	});
	frame->setFocusView (textEdit);
	inlineAttrTextEditOpen = true;
}

//-----------------------------------------------------------------------------
void UIEditView::looseFocus ()
{
	if (editing == false)
		CViewContainer::looseFocus ();
}

//-----------------------------------------------------------------------------
void UIEditView::takeFocus ()
{
	if (editing == false)
		CViewContainer::takeFocus ();
}

//-----------------------------------------------------------------------------
bool UIEditView::attached (CView* parent)
{
	if (CViewContainer::attached (parent))
	{
		editing = !editing;
		enableEditing (!editing);
		IController* controller = getViewController (this, true);
		if (controller)
		{
			CBaseObject* obj = dynamic_cast<CBaseObject*>(controller);
			if (obj)
				obj->notify (this, kMsgAttached);
		}
		return true;
	}
	return false;
}

//-----------------------------------------------------------------------------
bool UIEditView::removed (CView* parent)
{
	auto frame = getFrame ();
	if (editingViewAddedObserver)
	{
		frame->setViewAddedRemovedObserver (nullptr);
		editingViewAddedObserver.reset ();
	}
	IController* controller = getViewController (this, true);
	if (controller)
	{
		CBaseObject* obj = dynamic_cast<CBaseObject*>(controller);
		if (obj)
			obj->notify (this, kMsgRemoved);
	}
	if (overlayView)
	{
		frame->removeView (overlayView);
		overlayView = nullptr;
	}
	frame->setCursor (kCursorDefault);
	return CViewContainer::removed (parent);
}

//-----------------------------------------------------------------------------
void UIEditView::setupColors (const IUIDescription* desc)
{
	desc->getColor ("editView.crosslines.background", crosslineBackgroundColor);
	desc->getColor ("editView.crosslines.foreground", crosslineForegroundColor);
	desc->getColor ("editView.lasso.fill", lassoFillColor);
	desc->getColor ("editView.lasso.frame", lassoFrameColor);
	desc->getColor ("editView.view.highlight", viewHighlightColor);
	desc->getColor ("editView.view.selection", viewSelectionColor);
}

} // VSTGUI

#endif // VSTGUI_LIVE_EDITING
