// 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 "uitemplatecontroller.h"

#if VSTGUI_LIVE_EDITING

#include "../iviewfactory.h"
#include "../uiattributes.h"
#include "../viewcreator/viewcreator.h"
#include "../../lib/controls/ctextedit.h"
#include "../../lib/controls/coptionmenu.h"
#include "../../lib/controls/cscrollbar.h"
#include "../../lib/algorithm.h"
#include "../../lib/cgraphicspath.h"
#include "../../lib/cdropsource.h"
#include "../../lib/coffscreencontext.h"
#include "../../lib/dragging.h"
#include "../../lib/malloc.h"
#include "uieditcontroller.h"
#include "uiselection.h"
#include "uiundomanager.h"
#include "uiactions.h"
#include "uieditmenucontroller.h"
#include <cassert>

namespace VSTGUI {

//----------------------------------------------------------------------------------------------------
class UINavigationDataSource : public GenericStringListDataBrowserSource
{
public:
	UINavigationDataSource (GenericStringListDataBrowserSourceSelectionChanged* delegate)
	: GenericStringListDataBrowserSource (nullptr, delegate) { textInset.x = 4.; }

	void dbOnKeyboardEvent (KeyboardEvent& event, CDataBrowser* browser) override
	{
		if (event.type == EventType::KeyDown &&
		    dynamic_cast<CTextEdit*> (browser->getFrame ()->getFocusView ()) == nullptr)
		{
			if (event.virt == VirtualKey::Left)
			{
				if (auto parent = browser->getParentView ()->asViewContainer ())
				{
					if (parent->advanceNextFocusView (browser, true))
					{
						browser->unselectAll ();
						event.consumed = true;
						return;
					}
				}
			}
			else if (event.virt == VirtualKey::Right)
			{
				if (auto parent = browser->getParentView ()->asViewContainer ())
				{
					if (parent->advanceNextFocusView (browser, false))
					{
						if (auto* focusView = browser->getFrame ()->getFocusView ())
						{
							auto* focusBrowser = dynamic_cast<CDataBrowser*>(focusView);
							parent = focusView->getParentView ()->asViewContainer ();
							while (!focusBrowser && parent != browser->getFrame ())
							{
								if (parent->getParentView () == nullptr)
									break;
								parent = parent->getParentView ()->asViewContainer ();
								focusBrowser = dynamic_cast<CDataBrowser*> (parent);
							}
							if (focusBrowser)
							{
								if (focusBrowser->getSelectedRow () == CDataBrowser::kNoSelection)
									focusBrowser->setSelectedRow (0);
							}
						}
						event.consumed = true;
						return;
					}
				}
			}
		}
		GenericStringListDataBrowserSource::dbOnKeyboardEvent (event, browser);
	}
	virtual const UTF8String& getHeaderTitle () const { return headerTitle; }
	void dbDrawHeader (CDrawContext* context, const CRect& size, int32_t column, int32_t flags, CDataBrowser* browser) override
	{
		context->setDrawMode (kAliasing);
		if (!headerGradient)
		{
			headerGradient = UIEditController::getEditorDescription ()->getGradient ("shading.light");
			UIEditController::getEditorDescription ()->getColor ("shading.light.frame", headerLineColor);
		}
		if (headerGradient)
		{
			if (auto path = owned (context->createGraphicsPath ()))
			{
				path->addRect (size);
				context->fillLinearGradient (path, *headerGradient, CPoint (size.left, size.top), CPoint (size.left, size.bottom));
			}
		}
		if (!getHeaderTitle ().empty ())
		{
			if (headerFont == nullptr)
			{
				headerFont = makeOwned<CFontDesc> (*drawFont);
				headerFont->setStyle (kBoldFace);
				headerFont->setSize (headerFont->getSize ()-1);
			}
			context->setFont (headerFont);
			context->setFontColor (fontColor);
			context->drawString (getHeaderTitle ().getPlatformString (), size, kCenterText);
		}
		auto hairlineSize = context->getHairlineSize ();
		context->setLineWidth (hairlineSize);
		context->setFrameColor (headerLineColor);
		context->drawLine (CPoint (size.right-hairlineSize, size.top), CPoint (size.right-hairlineSize, size.bottom));
		context->drawLine (CPoint (size.left, size.bottom), CPoint (size.right-hairlineSize, size.bottom));
	}

	static void drawTriangle (CDrawContext* context, const CRect& size)
	{
		if (auto path = owned (context->createGraphicsPath ()))
		{
			CRect r (size);
			r.left = r.right - r.getHeight ();
			r.inset (4, 4);
			path->beginSubpath (r.getTopLeft ());
			path->addLine (r.getBottomLeft ());
			path->addLine (r.right, r.top + r.getHeight () / 2.);
			path->closeSubpath ();
			context->setFillColor (CColor (0, 0, 0, 30));
			context->drawGraphicsPath (path);
		}
	}

protected:
	mutable UTF8String headerTitle;
	CColor headerLineColor {kBlackCColor};
	SharedPointer<CFontDesc> headerFont;
	SharedPointer<CGradient> headerGradient;
};

//----------------------------------------------------------------------------------------------------
class UITemplatesDataSource : public UINavigationDataSource
{
public:
	UITemplatesDataSource (GenericStringListDataBrowserSourceSelectionChanged* delegate, UIDescription* description, IActionPerformer* actionPerformer, const std::string* templateName);
	
	CMouseEventResult dbOnMouseDown (const CPoint& where, const CButtonState& buttons, int32_t row, int32_t column, CDataBrowser* browser) override;
	void dbCellTextChanged (int32_t row, int32_t column, UTF8StringPtr newText, CDataBrowser* browser) override;
	void dbCellSetupTextEdit (int32_t row, int32_t column, CTextEdit* textEditControl, CDataBrowser* browser) override;
	void dbAttached (CDataBrowser* browser) override;
	void dbDrawCell (CDrawContext* context, const CRect& size, int32_t row, int32_t column, int32_t flags, CDataBrowser* browser) override;
protected:
	SharedPointer<UIDescription> description;
	IActionPerformer* actionPerformer;
	std::string firstSelectedTemplateName;
};

//----------------------------------------------------------------------------------------------------
class UIViewListDataSource : public UINavigationDataSource, public IUIUndoManagerListener
{
public:
	UIViewListDataSource (CViewContainer* view, const IViewFactory* viewFactory, UISelection* selection, UIUndoManager* undoManager ,GenericStringListDataBrowserSourceSelectionChanged* delegate);
	~UIViewListDataSource () override;

	CViewContainer* getView () const { return view; }
	CView* getSubview (int32_t index);
	bool setSelectedView (CView* view, bool makeRowVisible = false);
	UIViewListDataSource* getNext () const { return next; }

	bool update (CViewContainer* vc);
	void remove ();
protected:
	UTF8String getViewDisplayString (CView* v) const
	{
		uint32_t outSize = 0;
		if (v->getAttributeSize (UIViewCreator::ViewCreator::labelAttrID, outSize))
		{
			Buffer<char> buffer (outSize);
			if (v->getAttribute (UIViewCreator::ViewCreator::labelAttrID, outSize, buffer.data (),
			                     outSize))
			{
				return buffer.data ();
			}
		}
		if (const auto* vfEditingSupport =
				dynamic_cast<const IViewFactoryEditingSupport*> (viewFactory))
			return vfEditingSupport->getViewDisplayName (v);
		return {};
	}
	const UTF8String& getHeaderTitle () const override
	{
		headerTitle = "";
		if (view)
		{
			headerTitle = getViewDisplayString (view);
			if (headerTitle.empty () && view->getParentView ())
				headerTitle = getViewDisplayString (view->getParentView ());
		}
		return headerTitle;
	}
	
	CCoord calculateSubViewWidth (CViewContainer* view) const;
	void dbSelectionChanged (CDataBrowser* browser) override;
	CMouseEventResult dbOnMouseDown (const CPoint& where, const CButtonState& buttons, int32_t row,
	                                 int32_t column, CDataBrowser* browser) override;
	CMouseEventResult dbOnMouseMoved (const CPoint& where, const CButtonState& buttons, int32_t row,
	                                  int32_t column, CDataBrowser* browser) override;
	void dbOnKeyboardEvent (KeyboardEvent& event, CDataBrowser* browser) override;
	void dbDrawCell (CDrawContext* context, const CRect& size, int32_t row, int32_t column, int32_t flags, CDataBrowser* browser) override;
	DragOperation dbOnDragEnterCell (int32_t row, int32_t column, const CPoint& where,
	                                 IDataPackage* drag, CDataBrowser* browser) override;
	DragOperation dbOnDragMoveInCell (int32_t row, int32_t column, const CPoint& where,
	                                  IDataPackage* drag, CDataBrowser* browser) override;
	void dbOnDragExitCell (int32_t row, int32_t column, IDataPackage* drag,
	                       CDataBrowser* browser) override;
	bool dbOnDropInCell (int32_t row, int32_t column, const CPoint& where, IDataPackage* drag,
	                     CDataBrowser* browser) override;

	// IUIUndoManagerListener
	void onUndoManagerChange () override;

	CViewContainer* view;
	const IViewFactory* viewFactory;
	UIViewListDataSource* next;
	SharedPointer<UISelection> selection;
	SharedPointer<UIUndoManager> undoManager;
	CView* selectedView;
	StringVector names;
	std::vector<CView*> subviews;
	bool inUpdate;
	DragStartMouseObserver dragStartMouseObserver;
	int32_t dragRow {-1};
	int32_t dragDestinationRow {-1};
};

//----------------------------------------------------------------------------------------------------
UITemplateController::UITemplateController (IController* baseController, UIDescription* description, UISelection* selection, UIUndoManager* undoManager, IActionPerformer* actionPerformer)
: DelegationController (baseController)
, editDescription (description)
, selection (selection)
, undoManager (undoManager)
, actionPerformer (actionPerformer)
, templateView (nullptr)
, templateDataBrowser (nullptr)
, mainViewDataSource (nullptr)
, selectedTemplateName (nullptr)
{
	editDescription->registerListener (this);
}

//----------------------------------------------------------------------------------------------------
UITemplateController::~UITemplateController ()
{
	if (templateDataBrowser)
		templateDataBrowser->unregisterViewListener (this);
	if (mainViewDataSource)
		mainViewDataSource->forget ();
	editDescription->unregisterListener (this);
}

//----------------------------------------------------------------------------------------------------
void UITemplateController::setupDataBrowser (CDataBrowser* orignalBrowser, CDataBrowser* dataBrowser)
{
	if (orignalBrowser)
	{
		dataBrowser->setTransparency (orignalBrowser->getTransparency ());
		dataBrowser->setBackgroundColor (orignalBrowser->getBackgroundColor ());
		dataBrowser->setAutosizeFlags (orignalBrowser->getAutosizeFlags ());
		dataBrowser->setStyle (orignalBrowser->getStyle ());
		dataBrowser->setScrollbarWidth (orignalBrowser->getScrollbarWidth ());
		CScrollbar* sb1 = orignalBrowser->getHorizontalScrollbar ();
		CScrollbar* sb2 = dataBrowser->getHorizontalScrollbar ();
		if (sb1 && sb2)
		{
			sb2->setScrollerColor (sb1->getScrollerColor ());
			sb2->setBackgroundColor (sb1->getBackgroundColor ());
			sb2->setFrameColor (sb1->getFrameColor ());
		}
		sb1 = orignalBrowser->getVerticalScrollbar ();
		sb2 = dataBrowser->getVerticalScrollbar ();
		if (sb1 && sb2)
		{
			sb2->setScrollerColor (sb1->getScrollerColor ());
			sb2->setBackgroundColor (sb1->getBackgroundColor ());
			sb2->setFrameColor (sb1->getFrameColor ());
		}
	}
}

//----------------------------------------------------------------------------------------------------
void UITemplateController::selectTemplate (UTF8StringPtr name)
{
	if (templateDataBrowser)
	{
		if (name == nullptr)
		{
			templateDataBrowser->unselectAll ();
			return;
		}
		int32_t index = 0;
		for (auto& templName : templateNames)
		{
			if (templName == name)
			{
				templateDataBrowser->setSelectedRow (index, true);
				break;
			}
			index++;
		}
	}
}

//----------------------------------------------------------------------------------------------------
void UITemplateController::dbSelectionChanged (int32_t selectedRow, GenericStringListDataBrowserSource* source)
{
	if (source->getStringList () == &templateNames)
	{
		UTF8String* newName = nullptr;
		if (selectedRow == CDataBrowser::kNoSelection)
			newName = nullptr;
		else
			newName = &templateNames[static_cast<uint32_t> (selectedRow)];

		if ((newName == nullptr && selectedTemplateName != nullptr)
		 || (newName != nullptr && selectedTemplateName == nullptr)
		 || (newName != selectedTemplateName && *newName != *selectedTemplateName))
		{
			selectedTemplateName = newName;
			auto attr = editDescription->getCustomAttributes ("UITemplateController", true);
			if (attr)
			{
				attr->setAttribute ("SelectedTemplate", selectedTemplateName ? selectedTemplateName->getString () : "");
			}

			forEachListener ([] (IUITemplateControllerListener* l) { l->onTemplateSelectionChanged ();});
		}
		else if (templateView)
		{
			selection->setExclusive (templateView);
		}
		else
			selection->clear ();
		return;
	}
}

//----------------------------------------------------------------------------------------------------
void UITemplateController::onUIDescTemplateChanged (UIDescription* desc)
{
	if (!templateDataBrowser)
		return;
	auto dataSource = dynamic_cast<GenericStringListDataBrowserSource*>(templateDataBrowser->getDelegate ());
	if (dataSource)
	{
		int32_t rowToSelect = templateDataBrowser->getSelectedRow ();
		int32_t index = 0;
		UTF8String selectedTemplateStr = selectedTemplateName ? selectedTemplateName->data () : "";
		templateNames.clear ();
		dataSource->setStringList (&templateNames);
		std::list<const std::string*> tmp;
		editDescription->collectTemplateViewNames (tmp);
		tmp.sort (UIEditController::std__stringCompare);
		for (auto& name : tmp)
		{
			templateNames.emplace_back (*name);
			if (*name == selectedTemplateStr)
				rowToSelect = index;
			++index;
		}
		if (rowToSelect < 0)
			rowToSelect = 0;
		dataSource->setStringList (&templateNames);
		templateDataBrowser->setSelectedRow (rowToSelect, true);
	}
}

//----------------------------------------------------------------------------------------------------
void UITemplateController::setTemplateView (CViewContainer* view)
{
	if (view != templateView && templateDataBrowser && templateDataBrowser->getParentView ())
	{
		templateView = view;
		if (mainViewDataSource)
		{
			mainViewDataSource->remove ();
			mainViewDataSource->forget ();
			mainViewDataSource = nullptr;
		}
		if (templateView && templateDataBrowser)
		{
			CViewContainer* parentView = static_cast<CViewContainer*>(templateDataBrowser->getParentView ());
			if (parentView)
			{
				const IViewFactory* viewFactory = editDescription->getViewFactory ();
				mainViewDataSource = new UIViewListDataSource (templateView, viewFactory, selection, undoManager, this);
				UIEditController::setupDataSource (mainViewDataSource);
				CRect r (templateDataBrowser->getViewSize ());
				r.offset (r.getWidth (), 0);
				CDataBrowser* browser = new CDataBrowser (r, mainViewDataSource);
				setupDataBrowser (templateDataBrowser, browser);
				parentView->addView (browser);
			}
		}
	}
}

//------------------------------------------------------------------------
void UITemplateController::navigateTo (CView* view)
{
	std::list<CView*> parents;
	CView* v = view;
	while (auto parent = v->getParentView ())
	{
		if (parent == parent->getFrame ())
			return; // view is not a child of the templateView
		if (parent == templateView)
			break;
		if (IViewFactory::getViewName (parent) == nullptr)
		{
			v = parent;
			continue;
		}
		parents.emplace_front (parent);
		v = parent;
	}
	UIViewListDataSource* dataSource = mainViewDataSource;
	for (auto parent : parents)
	{
		dataSource->setSelectedView (parent, true);
		dataSource = dataSource->getNext ();
		if (dataSource == nullptr)
			break;
	}
	if (dataSource)
		dataSource->setSelectedView (view, true);
}

//----------------------------------------------------------------------------------------------------
void UITemplateController::viewWillDelete (CView* view)
{
	if (view == templateDataBrowser)
	{
		templateDataBrowser->unregisterViewListener (this);
		templateDataBrowser = nullptr;
	}
}

//----------------------------------------------------------------------------------------------------
CView* UITemplateController::createView (const UIAttributes& attributes, const IUIDescription* description)
{
	const std::string* name = attributes.getAttributeValue (IUIDescription::kCustomViewName);
	if (name)
	{
		if (*name == "TemplateBrowser")
		{
			vstgui_assert (templateDataBrowser == nullptr);
			std::list<const std::string*> tmp;
			editDescription->collectTemplateViewNames (tmp);
			tmp.sort (UIEditController::std__stringCompare);
			for (auto& name2 : tmp)
				templateNames.emplace_back (*name2);
			
			auto attr = editDescription->getCustomAttributes ("UITemplateController", true);
			const std::string* templateName = attr ? attr->getAttributeValue ("SelectedTemplate") : nullptr;
			UITemplatesDataSource* dataSource = new UITemplatesDataSource (this, editDescription, actionPerformer, templateName);
			dataSource->setStringList (&templateNames);
			UIEditController::setupDataSource (dataSource);
			templateDataBrowser = new CDataBrowser (CRect (0, 0, 0, 0), dataSource, CDataBrowser::kDrawRowLines|CScrollView::kAutoHideScrollbars|CScrollView::kHorizontalScrollbar|CScrollView::kVerticalScrollbar|CDataBrowser::kDrawHeader);
			dataSource->forget ();
			templateDataBrowser->registerViewListener (this);
			return templateDataBrowser;
		}
	}
	return DelegationController::createView (attributes, description);
}

//----------------------------------------------------------------------------------------------------
CView* UITemplateController::verifyView (CView* view, const UIAttributes& attributes, const IUIDescription* description)
{
	return DelegationController::verifyView (view, attributes, description);
}

//----------------------------------------------------------------------------------------------------
IController* UITemplateController::createSubController (UTF8StringPtr name, const IUIDescription* description)
{
	return DelegationController::createSubController (name, description);
}

//----------------------------------------------------------------------------------------------------
void UITemplateController::appendContextMenuItems (COptionMenu& contextMenu, CView* view, const CPoint& where)
{
	CPoint w (where);
	view->localToFrame (w);
	templateDataBrowser->frameToLocal (w);
	if (!templateDataBrowser->hitTest (w))
		return;
	auto cell = templateDataBrowser->getCellAt (w);
	if (!cell.isValid ())
		return;
	auto dataSource = dynamic_cast<UITemplatesDataSource*> (templateDataBrowser->getDelegate ());
	auto templateName = dataSource->getStringList()->at (static_cast<uint32_t> (cell.row));
	vstgui_assert (dataSource);
	auto item = new CCommandMenuItem ("Duplicate Template '" + templateName + "'");
	item->setActions ([this, cell, dataSource] (CCommandMenuItem*) {
		std::list<const std::string*> tmp;
		editDescription->collectTemplateViewNames (tmp);
		std::string newName (dataSource->getStringList ()->at (static_cast<uint32_t> (cell.row)).data ());
		UIEditMenuController::createUniqueTemplateName (tmp, newName);
		actionPerformer->performDuplicateTemplate (dataSource->getStringList ()->at (static_cast<uint32_t> (cell.row)).data (), newName.data ());
	});
	contextMenu.addEntry (item);
	item = new CCommandMenuItem ("Delete Template '" + templateName + "'");
	item->setActions ([this, cell, dataSource] (CCommandMenuItem*) {
		actionPerformer->performDeleteTemplate (dataSource->getStringList ()->at (static_cast<uint32_t> (cell.row)).data ());
	});
	contextMenu.addEntry (item);
}

//----------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------
UIViewListDataSource::UIViewListDataSource (
	CViewContainer* view, const IViewFactory* viewFactory, UISelection* selection,
	UIUndoManager* undoManager, GenericStringListDataBrowserSourceSelectionChanged* delegate)
: UINavigationDataSource (delegate)
, view (view)
, viewFactory (viewFactory)
, next (nullptr)
, selection (selection)
, undoManager (undoManager)
, selectedView (nullptr)
, inUpdate (false)
{
	update (view);
	undoManager->registerListener (this);
}

//----------------------------------------------------------------------------------------------------
UIViewListDataSource::~UIViewListDataSource ()
{
	undoManager->unregisterListener (this);
}

//----------------------------------------------------------------------------------------------------
CView* UIViewListDataSource::getSubview (int32_t index)
{
	if (index >= 0 && index < (int32_t)subviews.size ())
		return subviews[static_cast<uint32_t> (index)];
	return nullptr;
}

//----------------------------------------------------------------------------------------------------
bool UIViewListDataSource::update (CViewContainer* vc)
{
	inUpdate = true;
	names.clear ();
	subviews.clear ();
	vc->forEachChild ([&] (CView* subview) {
		auto viewName = getViewDisplayString (subview);
		if (!viewName.empty ())
		{
			names.emplace_back (std::move (viewName));
			subviews.emplace_back (subview);
		}
	});
	if (names.empty () && vc->getNbViews () > 0)
	{
		ViewIterator it (vc);
		while (*it)
		{
			if (auto subview = (*it)->asViewContainer ())
			{
				if (update (subview))
				{
					view = subview;
					inUpdate = false;
					return true;
				}
			}
			++it;
		}
		inUpdate = false;
		return false;
	}
	setStringList (&names);
	inUpdate = false;
	return true;
}

//----------------------------------------------------------------------------------------------------
CCoord UIViewListDataSource::calculateSubViewWidth (CViewContainer* inView) const
{
	CCoord result = 0;
	
	inView->forEachChild ([&result] (CView* subView) {
		result += subView->getViewSize ().getWidth ();
	});
	return result;
}

//----------------------------------------------------------------------------------------------------
bool UIViewListDataSource::setSelectedView (CView* newView, bool makeRowVisible)
{
	auto index = indexOf (subviews.begin (), subviews.end (), newView);
	if (!index)
		return false;

	selectedView = newView;
	dataBrowser->selectRow (*index);
	if (makeRowVisible)
		dataBrowser->makeRowVisible (dataBrowser->getSelectedRow ());

	if (next)
	{
		next->remove ();
		next = nullptr;
	}
	if (auto container = selectedView ? selectedView->asViewContainer () : nullptr)
	{
		UIViewListDataSource* dataSource = new UIViewListDataSource (container, viewFactory, selection, undoManager, delegate);
		UIEditController::setupDataSource (dataSource);
		CRect r (dataBrowser->getViewSize ());
		r.offset (r.getWidth (), 0);
		CDataBrowser* newDataBrowser = new CDataBrowser (r, dataSource);
		UITemplateController::setupDataBrowser (dataBrowser, newDataBrowser);
		CViewContainer* parentView = static_cast<CViewContainer*>(dataBrowser->getParentView ());
		parentView->addView (newDataBrowser);
		next = dataSource;
		dataSource->forget ();
		CScrollView* scrollView = dynamic_cast<CScrollView*>(parentView->getParentView ());
		if (scrollView)
		{
			CRect containerSize (scrollView->getContainerSize ());
			containerSize.right = calculateSubViewWidth (parentView);
			scrollView->setContainerSize (containerSize, true);
		}
	}
	return true;
}

//----------------------------------------------------------------------------------------------------
void UIViewListDataSource::dbSelectionChanged (CDataBrowser* browser)
{
	CView* subview = getSubview (browser->getSelectedRow ());
	if (subview == selectedView || inUpdate)
		return;
	setSelectedView (subview);
	GenericStringListDataBrowserSource::dbSelectionChanged (dataBrowser);
}

//----------------------------------------------------------------------------------------------------
void UIViewListDataSource::remove ()
{
	if (next)
	{
		next->remove ();
		next = nullptr;
	}
	if (dataBrowser)
	{
		CViewContainer* parentView = static_cast<CViewContainer*>(dataBrowser->getParentView ());
		CScrollView* scrollView = dynamic_cast<CScrollView*>(parentView->getParentView ());
		parentView->removeView (dataBrowser);
		if (scrollView)
		{
			CRect containerSize (scrollView->getContainerSize ());
			containerSize.right = calculateSubViewWidth (parentView);
			scrollView->setContainerSize (containerSize, true);
		}
	}
}

//----------------------------------------------------------------------------------------------------
CMouseEventResult UIViewListDataSource::dbOnMouseDown (const CPoint& where, const CButtonState& buttons, int32_t row, int32_t column, CDataBrowser* browser)
{
	if (buttons.isLeftButton ())
	{
		if (buttons.isDoubleClick ())
		{
			CView* subview = getSubview (row);
			if (subview)
			{
				if (buttons.getModifierState () & kControl)
				{
					if (selection->contains (subview))
						selection->remove (subview);
					else
						selection->add (subview);
				}
				else
					selection->setExclusive (subview);
			}
		}
		dragStartMouseObserver.init (where);
	}
	return kMouseEventHandled;
}

//------------------------------------------------------------------------
CMouseEventResult UIViewListDataSource::dbOnMouseMoved (const CPoint& where,
                                                        const CButtonState& buttons, int32_t row,
                                                        int32_t column, CDataBrowser* browser)
{
	if (row >= 0 && buttons.isLeftButton () && dragStartMouseObserver.shouldStartDrag (where))
	{
		row = browser->getSelectedRow ();
		dragRow = row;

		auto cellBounds = browser->getCellBounds ({row, column});
		auto offscreen = COffscreenContext::create (cellBounds.getSize (),
		                                            browser->getFrame ()->getScaleFactor ());
		auto offscreenSize = cellBounds;
		offscreenSize.originize ();
		offscreen->beginDraw ();
		dbDrawCell (offscreen, offscreenSize, row, column, 0, browser);
		offscreen->endDraw ();
		
		auto startPos = dragStartMouseObserver.getInitPosition ();
		DragDescription dd (CDropSource::create (&row, sizeof (int32_t), IDataPackage::kBinary),
		                    {cellBounds.left - startPos.x, cellBounds.top - startPos.y},
		                    shared (offscreen->getBitmap ()));
		auto callbackFunc = makeOwned<DragCallbackFunctions> ();
		auto Self = shared (this);
		callbackFunc->endedFunc = [Self] (IDraggingSession*, CPoint, DragOperation) {
			Self->dragRow = -1;
			Self->dragDestinationRow = -1;
		};

		browser->doDrag (dd, callbackFunc);
		return kMouseMoveEventHandledButDontNeedMoreEvents;
	}
	return kMouseEventHandled;
}

//----------------------------------------------------------------------------------------------------
DragOperation UIViewListDataSource::dbOnDragEnterCell (int32_t row, int32_t column,
                                                       const CPoint& where, IDataPackage* drag,
                                                       CDataBrowser* browser)
{
	if (dragRow >= 0)
	{
		if (dragDestinationRow >= 0)
			browser->invalidateRow (dragDestinationRow);
		if (dragRow == row)
		{
			dragDestinationRow = -1;
		}
		else
		{
			dragDestinationRow = row;
			browser->invalidateRow (dragDestinationRow);
		}
		return DragOperation::Move;
	}
	return DragOperation::None;
}

//----------------------------------------------------------------------------------------------------
DragOperation UIViewListDataSource::dbOnDragMoveInCell (int32_t row, int32_t column,
                                                        const CPoint& where, IDataPackage* drag,
                                                        CDataBrowser* browser)
{
	if (dragRow >= 0)
		return DragOperation::Move;
	return DragOperation::None;
}

//----------------------------------------------------------------------------------------------------
void UIViewListDataSource::dbOnDragExitCell (int32_t row, int32_t column, IDataPackage* drag,
                                             CDataBrowser* browser)
{
	if (dragDestinationRow >= 0)
	{
		browser->invalidateRow (dragDestinationRow);
		dragDestinationRow = -1;
	}
}

//----------------------------------------------------------------------------------------------------
bool UIViewListDataSource::dbOnDropInCell (int32_t row, int32_t column, const CPoint& where,
                                           IDataPackage* drag, CDataBrowser* browser)
{
	bool result = false;
	if (row != dragRow && dragDestinationRow != -1 && row != -1)
	{
		int32_t dir = dragDestinationRow - dragRow;
		undoManager->pushAndPerform (new HierarchyMoveViewOperation (subviews[dragRow], selection, dir));
		result = true;
	}
	dragRow = dragDestinationRow = -1;
	browser->invalidateRow (row);
	return result;
}

//----------------------------------------------------------------------------------------------------
void UIViewListDataSource::onUndoManagerChange ()
{
	update (view);
	if (selectedView)
	{
		if (dataBrowser)
		{
			if (auto index = indexOf (subviews.begin (), subviews.end (), selectedView))
			{
				dataBrowser->setSelectedRow (*index, true);
				return;
			}
		}
		selectedView = nullptr;
		if (next)
		{
			next->remove ();
			next = nullptr;
		}
	}
}

//----------------------------------------------------------------------------------------------------
void UIViewListDataSource::dbOnKeyboardEvent (KeyboardEvent& event, CDataBrowser* browser)
{
	if (event.type == EventType::KeyDown)
	{
		if (event.virt == VirtualKey::Return)
		{
			int32_t row = browser->getSelectedRow ();
			CView* subview = getSubview (row);
			if (subview)
			{
				selection->setExclusive (subview);
				event.consumed = true;
				return;
			}
		}
	}
	UINavigationDataSource::dbOnKeyboardEvent (event, browser);
}

//----------------------------------------------------------------------------------------------------
void UIViewListDataSource::dbDrawCell (CDrawContext* context, const CRect& size, int32_t row, int32_t column, int32_t flags, CDataBrowser* browser)
{
	drawRowBackground (context, size, row, flags, browser);
	auto subview = getSubview (row);
	if (subview && subview->asViewContainer ())
		drawTriangle (context, size);
	drawRowString (context, size, row, flags, browser);
	if (dragDestinationRow == row)
	{
		CColor color = kRedCColor;
		UIEditController::getEditorDescription ()->getColor ("db.drag.indicator", color);
		context->setFrameColor (color);
		context->setLineWidth (1.);
		auto r = size;
		r.top += 1.;
		r.bottom -= 2.;
		if (dragDestinationRow < dragRow)
			context->drawLine (r.getTopLeft (), r.getTopRight ());
		else
			context->drawLine (r.getBottomLeft (), r.getBottomRight ());
	}
}

//----------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------
UITemplatesDataSource::UITemplatesDataSource (GenericStringListDataBrowserSourceSelectionChanged* delegate, UIDescription* description, IActionPerformer* actionPerformer, const std::string* templateName)
: UINavigationDataSource (delegate)
, description (description)
, actionPerformer (actionPerformer)
{
	headerTitle = "Templates";
	if (templateName)
		firstSelectedTemplateName = *templateName;
}

//----------------------------------------------------------------------------------------------------
CMouseEventResult UITemplatesDataSource::dbOnMouseDown (const CPoint& where, const CButtonState& buttons, int32_t row, int32_t column, CDataBrowser* browser)
{
	if (buttons.isLeftButton ())
	{
		if (buttons.isDoubleClick ())
		{
			browser->beginTextEdit (CDataBrowser::Cell (row, column), getStringList ()->at (static_cast<uint32_t> (row)).data ());
			return kMouseDownEventHandledButDontNeedMovedOrUpEvents;
		}
		delegate->dbSelectionChanged (row, this);
		return kMouseDownEventHandledButDontNeedMovedOrUpEvents;
	}
	return UINavigationDataSource::dbOnMouseDown (where, buttons, row, column, browser);
}

//----------------------------------------------------------------------------------------------------
void UITemplatesDataSource::dbCellTextChanged (int32_t row, int32_t column, UTF8StringPtr newText, CDataBrowser* browser)
{
	auto oldName = getStringList ()->at (static_cast<uint32_t> (row));
	if (oldName != newText)
	{
		for (auto& name : *getStringList ())
		{
			if (name == newText)
				return;
		}
		actionPerformer->performTemplateNameChange (oldName.data (), newText);
	}
}

//----------------------------------------------------------------------------------------------------
void UITemplatesDataSource::dbCellSetupTextEdit (int32_t row, int32_t column, CTextEdit* textEditControl, CDataBrowser* browser)
{
	textEditControl->setBackColor (kWhiteCColor);
	textEditControl->setFontColor (fontColor);
	textEditControl->setFont (drawFont);
	textEditControl->setHoriAlign (kLeftText);
	textEditControl->setTextInset (textInset);
}

//----------------------------------------------------------------------------------------------------
void UITemplatesDataSource::dbDrawCell (CDrawContext* context, const CRect& size, int32_t row, int32_t column, int32_t flags, CDataBrowser* browser)
{
	drawRowBackground (context, size, row, flags, browser);
	drawTriangle (context, size);
	drawRowString (context, size, row, flags, browser);
}


//----------------------------------------------------------------------------------------------------
void UITemplatesDataSource::dbAttached (CDataBrowser* browser)
{
	UINavigationDataSource::dbAttached (browser);
	if (getStringList ())
	{
		if (firstSelectedTemplateName.empty ())
		{
			browser->setSelectedRow (0, true);
		}
		else
		{
			uint32_t index = 0;
			for (auto& name : *getStringList ())
			{
				if (name == firstSelectedTemplateName)
				{
					browser->setSelectedRow (static_cast<int32_t> (index), true);
					break;
				}
				index++;
			}
		}
	}
}

} // VSTGUI

#endif // VSTGUI_LIVE_EDITING
