Click here to Skip to main content
14,218,929 members

Form Designer

Rate this:
4.96 (54 votes)
Please Sign up or sign in to vote.
4.96 (54 votes)
1 Aug 2009CPOL
Component for adding scriptable forms capabilities to an application.
MFCDemo.exe
CSDemo
App.ico
Bmp
ABottom.bmp
About.bmp
ALeft.bmp
ARight.bmp
ATop.bmp
AX.bmp
BringFront.bmp
Button.bmp
Center.bmp
Check.bmp
Combo.bmp
Copy.bmp
Cut.bmp
Delete.bmp
Down.bmp
Edit.bmp
Frame.bmp
Grid.bmp
GridSettings.bmp
HCenter.bmp
HScroll.bmp
HSL.bmp
HSpace.bmp
HSS.bmp
Left.bmp
List.bmp
New.bmp
Open.bmp
Paste.bmp
Picture.bmp
Preview.bmp
Progress.bmp
Props.bmp
Radio.bmp
Rectangle.bmp
Redo.bmp
Right.bmp
Save.bmp
SelAll.bmp
SelNone.bmp
SendBack.bmp
SL.bmp
Slider.bmp
Spin.bmp
SS.bmp
Tab.bmp
Text.bmp
Undo.bmp
Up.bmp
Validate.bmp
VCenter.bmp
VScroll.bmp
VSL.bmp
VSpace.bmp
VSS.bmp
Wand.bmp
CSDemo.suo
DaeDoe.bmp
MFCDemo
MFCDemo.dsp
MFCDemo.odl
MFCDemo.reg
res
DaeDoe.bmp
MFCDemo.ico
MFCDemoDoc.ico
Toolbar1.bmp
Toolbar2.bmp
Toolbar3.bmp
Wand.bmp
VBDemo
Bmp
ABottom.bmp
About.bmp
ALeft.bmp
ARight.bmp
ATop.bmp
AX.bmp
BringFront.bmp
Button.bmp
Center.bmp
Check.bmp
Combo.bmp
Copy.bmp
Cut.bmp
Delete.bmp
Down.bmp
Edit.bmp
Frame.bmp
Grid.bmp
GridSettings.bmp
HCenter.bmp
HScroll.bmp
HSL.bmp
HSpace.bmp
HSS.bmp
Left.bmp
List.bmp
New.bmp
Open.bmp
Paste.bmp
Picture.bmp
Preview.bmp
Progress.bmp
Props.bmp
Radio.bmp
Rectangle.bmp
Redo.bmp
Right.bmp
Save.bmp
SelAll.bmp
SelNone.bmp
SendBack.bmp
SL.bmp
Slider.bmp
Spin.bmp
SS.bmp
Tab.bmp
Text.bmp
Undo.bmp
Up.bmp
Validate.bmp
VCenter.bmp
VScroll.bmp
VSL.bmp
VSpace.bmp
VSS.bmp
Wand.bmp
DaeDoe.bmp
VBDemo.vbp
DDForms.chm
DDForms
Archive
FormEditorItems.rgs
vssver.scc
Constants.scr
DaeDoe.bmp
DDForms.def
DDForms.vcproj
DDForms.vcproj.vspscc
EventSinkPassThrough.rgs
FormEditor.bmp
FormEditor.rgs
FormEditorItemCollection.rgs
FormEditorItemDetails.rgs
FormViewer.bmp
FormViewer.rgs
FormViewerItemCollection.rgs
FormViewerItemDetails.rgs
mssccprj.scc
PropPageExtended.rgs
PropPageFormEditor.rgs
PropPageFormViewer.rgs
PropPageSimpleScriptEditor.rgs
SimpleScriptEditor.bmp
SimpleScriptEditor.rgs
vssver.scc
DDFormsTools
CodeMaxDriver
CodeMaxDriver.rgs
CodeMaxDriver.vcproj
CodeMaxDriver.vcproj.vspscc
CodeMaxDriverps.def
CodeMaxDriverPS.vcproj
CodeMaxDriverPS.vcproj.vspscc
mssccprj.scc
vssver.scc
CodeSenseDriver
CodeSenseDriver.rgs
CodeSenseDriver.vcproj
CodeSenseDriver.vcproj.vspscc
CodeSenseDriverps.def
CodeSenseDriverPS.vcproj
CodeSenseDriverPS.vcproj.vspscc
mssccprj.scc
vssver.scc
DDPropPageAll
DDPropPageAll.def
DDPropPageAll.vcproj
DDPropPageAll.vcproj.vspscc
mssccprj.scc
PropPageAll.rgs
vssver.scc
DDUnlock
DDUnlock.vcproj
DDUnlock.vcproj.vspscc
mssccprj.scc
vssver.scc
DDControlPack
DDButton.bmp
DDCheckBox.bmp
DDComboBox.bmp
DDControlPack.rgs
DDControlPack.vcproj
DDControlPack.vcproj.vspscc
DDHorzScrollBar.bmp
DDLabel.bmp
DDListBox.bmp
DDPicture.bmp
DDRadioButton.bmp
DDTextBox.bmp
DDVertScrollBar.bmp
mssccprj.scc
vssver.scc
Redistributables
Ansi
CodeMaxDriver.dll
CodeSenseDriver.dll
DDControlPack.dll
DDForms.dll
DDPropPageAll.dll
Unicode
CodeMaxDriver.dll
CodeSenseDriver.dll
DDControlPack.dll
DDForms.dll
DDPropPageAll.dll
// DragFrame.cpp: implementation of the CDragFrame class.
//
// Author : David Shepherd
//			Copyright (c) 2002, DaeDoe-Software
//
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "DragFrame.h"

// drag handle size (the width and height are the same)
#define DRAG_HANDLE_SIZE	6

/////////////////////////////////////////////////////////////////////////////
// CDragFrame

CDragFrame::CDragFrame()
{
	// initialise everything
	m_BackColor=MAKE_OLE_COLOR(COLOR_HIGHLIGHTTEXT);
	m_ForeColor=MAKE_OLE_COLOR(COLOR_HIGHLIGHT);
	m_pNotifySink=NULL;
	m_EnabledDragHandles=DragHandleAll;
	m_AllowMove=TRUE;
	m_Hollow=FALSE;
	m_Action=ActionNone;
	m_ActionDragHandle=DragHandleNone;
	m_ActionOrigin=CPoint(0,0);
	m_ActionInLimbo=FALSE;
}

CDragFrame::~CDragFrame()
{
	// clean up
}

BOOL CDragFrame::IsDragHandleEnabled(DragHandle Handle) const
{
	// determine if the the specified drag handle is enabled
	return (m_EnabledDragHandles & Handle) ? TRUE : FALSE;
}

CRect CDragFrame::GetDragHandleRect(DragHandle Handle) const
{
	// get the client rect
	CRect ClientRect(0,0,0,0);
	(void)GetClientRect(ClientRect);

	// calculate the right middle and bottom middle drag handle locations
	long dxRight=(ClientRect.Width()-DRAG_HANDLE_SIZE);
	long dxMiddle=dxRight/2;
	long dyBottom=(ClientRect.Height()-DRAG_HANDLE_SIZE);
	long dyMiddle=dyBottom/2;

	// return the specified drag handle rectangle
	CRect Rect(0,0,DRAG_HANDLE_SIZE,DRAG_HANDLE_SIZE);
	switch(Handle)
	{
	// top left
	case DragHandleTopLeft:
		Rect.OffsetRect(0,0);
		break;
	// top
	case DragHandleTop:
		Rect.OffsetRect(dxMiddle,0);
		break;
	// top right
	case DragHandleTopRight:
		Rect.OffsetRect(dxRight,0);
		break;
	// right
	case DragHandleRight:
		Rect.OffsetRect(dxRight,dyMiddle);
		break;
	// bottom right
	case DragHandleBottomRight:
		Rect.OffsetRect(dxRight,dyBottom);
		break;
	// bottom
	case DragHandleBottom:
		Rect.OffsetRect(dxMiddle,dyBottom);
		break;
	// bottom left
	case DragHandleBottomLeft:
		Rect.OffsetRect(0,dyBottom);
		break;
	// left
	case DragHandleLeft:
		Rect.OffsetRect(0,dyMiddle);
		break;
	// unknown
	default:
		ATLASSERT(FALSE);
		break;
	}
	return Rect;
}

CDragFrame::DragHandle CDragFrame::HitTest(const CPoint &Pos) const
{
	// determine which drag handle (if any) is located at the passed position
	DragHandle DragHandles[]= {
		DragHandleTopLeft,
		DragHandleTop,
		DragHandleTopRight,
		DragHandleRight,
		DragHandleBottomRight,
		DragHandleBottom,
		DragHandleBottomLeft,
		DragHandleLeft };
	DragHandle Handle=DragHandleNone;	// assume none
	for(long l=0; l<NUM_ELEMENTS(DragHandles,DragHandle); l++)
	{
		if(GetDragHandleRect(DragHandles[l]).PtInRect(Pos))
		{
			Handle=DragHandles[l];
			break;	// done
		}
	}
	return Handle;
}

void CDragFrame::SetColors(OLE_COLOR BackColor,OLE_COLOR ForeColor)
{
	// if the colors are changing a redraw will be required
	if(m_BackColor!=BackColor or m_ForeColor!=ForeColor)
	{
		if(IsWindow())
		{
			(void)Invalidate(FALSE);
		}
	}
	// set the colors
	m_BackColor=BackColor;
	m_ForeColor=ForeColor;
}

void CDragFrame::GetColors(OLE_COLOR &BackColor,OLE_COLOR &ForeColor) const
{
	// return the colors
	BackColor=m_BackColor;
	ForeColor=m_ForeColor;
}

long CDragFrame::GetFrameSize() const
{
	// return the drag frame size (or thickness)
	return DRAG_HANDLE_SIZE;
}

BOOL CDragFrame::Create(const CWindow &Parent,const CWindow &AssociatedWindow,
	CDragFrameNotifySink *pNotifySink,DWORD EnabledDragHandles/*=DragHandleAll*/,
	BOOL AllowMove/*=TRUE*/,BOOL Hollow/*=FALSE*/)
{
	// check parameters
	ATLASSERT(::IsWindow(Parent));
	ATLASSERT(::IsWindow(AssociatedWindow));
	ATLASSERT(pNotifySink!=NULL);

	// save settings
	m_AssociatedWindow=AssociatedWindow;
	m_pNotifySink=pNotifySink;
	m_EnabledDragHandles=EnabledDragHandles;
	m_AllowMove=AllowMove;
	m_Hollow=Hollow;

	// create the drag frame
	ATLASSERT(IsWindow()==FALSE);
	if(CWindowImpl<CDragFrame>::Create(Parent,CRect(0,0,0,0),
		_T("DragFrame"),WS_CHILD|WS_VISIBLE,WS_EX_TRANSPARENT)==NULL)
	{
		return FALSE;
	}
	AutoPositionAndSize();		// auto position and size
	(void)Invalidate(FALSE);	// required for transparent windows

	return TRUE;
}

void CDragFrame::AutoPositionAndSize()
{
	// get the associated window rectangle
	CRect Rect(0,0,0,0);
	(void)m_AssociatedWindow.GetWindowRect(Rect);

	// convert to our parents coordinates
	CWindow Parent(GetParent());
	(void)Parent.ScreenToClient(Rect);

	// auto position and size the drag frame to hug the associated window
	Rect.InflateRect(DRAG_HANDLE_SIZE,DRAG_HANDLE_SIZE);
	(void)SetWindowPos(NULL,Rect,SWP_NOZORDER);
}

void CDragFrame::EnableDragHandles(DWORD EnabledDragHandles)
{
	// enable the specified drag handles
	if(m_EnabledDragHandles!=EnabledDragHandles)
	{
		m_EnabledDragHandles=EnabledDragHandles;
		(void)Invalidate(FALSE);
	}
}

void CDragFrame::ExternalActivate()
{
	// get the cursor position
	CPoint CursorPos(0,0);
	(void)GetCursorPos(&CursorPos);
	// convert to our client coordinates
	(void)ScreenToClient(&CursorPos);
	// package into an lparam
	LPARAM lParam=MAKELPARAM(CursorPos.x,CursorPos.y);

	// simulate a set cursor message
	// todo : set all parameters correctly when required
	BOOL Dummy=FALSE;
	(void)OnSetCursor(WM_SETCURSOR,0,0,Dummy);
	// simulate a left button down message
	// todo : set all parameters correctly when required
	(void)OnLButtonDown(WM_LBUTTONDOWN,0,lParam,Dummy);
}

LRESULT CDragFrame::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	// get the paint device context
	CPaintDC dc(*this);
	if(dc==NULL)
	{
		return 0;
	}
	// get the client rect
	CRect ClientRect(0,0,0,0);
	if(GetClientRect(ClientRect)==FALSE)
	{
		return 0;
	}
	// set background color
	COLORREF RGBBackColor=RGB(0,0,0);
	if(!SUCCEEDED(OleTranslateColor(m_BackColor,NULL,&RGBBackColor)))
	{
		return 0;
	}
	if(dc.SetBkColor(RGBBackColor)==CLR_INVALID)
	{
		return 0;
	}
	// set foreground color
	COLORREF RGBForeColor=RGB(0,0,0);
	if(!SUCCEEDED(OleTranslateColor(m_ForeColor,NULL,&RGBForeColor)))
	{
		return 0;
	}
	if(dc.SetTextColor(RGBForeColor)==CLR_INVALID)
	{
		return 0;
	}
	// create background brush
	CBrush BackBrush;
	if(BackBrush.CreateSolidBrush(RGBBackColor)==NULL)
	{
		return 0;
	}
	// create foreground brush
	CBrush ForeBrush;
	if(ForeBrush.CreateSolidBrush(RGBForeColor)==NULL)
	{
		return 0;
	}
	// create halftone brush
	CBrush HalfToneBrush=dc.GetHalftoneBrush();
	if(HalfToneBrush==NULL)
	{
		return 0;
	}
	// create foreground pen
	CPen ForePen;
	if(ForePen.CreatePen(PS_SOLID,0,RGBForeColor)==NULL)
	{
		return 0;
	}
	// initialise the dc
	CPen OldPen=dc.SelectPen(ForePen);
	if(OldPen==NULL)
	{
		return 0;
	}

	// fill in the frame background
	// left
	CRect LeftBorder(0,0,DRAG_HANDLE_SIZE,ClientRect.Height());
	dc.FillSolidRect(LeftBorder,RGBBackColor);
	// top
	CRect TopBorder(0,0,ClientRect.Width(),DRAG_HANDLE_SIZE);
	dc.FillSolidRect(TopBorder,RGBBackColor);
	// right
	CRect RightBorder(LeftBorder);
	RightBorder.OffsetRect(ClientRect.Width()-DRAG_HANDLE_SIZE,0);
	dc.FillSolidRect(RightBorder,RGBBackColor);
	// bottom
	CRect BottomBorder(TopBorder);
	BottomBorder.OffsetRect(0,ClientRect.Height()-DRAG_HANDLE_SIZE);
	dc.FillSolidRect(BottomBorder,RGBBackColor);

	// fill in the frame center
	long Deflation=(DRAG_HANDLE_SIZE-/*center width*/2)/2;
	// left
	CRect LeftCenter(LeftBorder);
	LeftCenter.DeflateRect(Deflation,Deflation);
	(void)dc.FillRect(LeftCenter,HalfToneBrush);
	// top
	CRect TopCenter(TopBorder);
	TopCenter.DeflateRect(Deflation,Deflation);
	(void)dc.FillRect(TopCenter,HalfToneBrush);
	// right
	CRect RightCenter(RightBorder);
	RightCenter.DeflateRect(Deflation,Deflation);
	(void)dc.FillRect(RightCenter,HalfToneBrush);
	// bottom
	CRect BottomCenter(BottomBorder);
	BottomCenter.DeflateRect(Deflation,Deflation);
	(void)dc.FillRect(BottomCenter,HalfToneBrush);

	// draw the drag handles
	DragHandle DragHandles[]= {
		DragHandleTopLeft,
		DragHandleTop,
		DragHandleTopRight,
		DragHandleRight,
		DragHandleBottomRight,
		DragHandleBottom,
		DragHandleBottomLeft,
		DragHandleLeft };
	// this is done in reverse order since the drag handle with the
	// highest priority should be at the top
	for(long l=NUM_ELEMENTS(DragHandles,DragHandle)-1; l>=0; l--)
	{
		// draw the drag handle
		CBrushHandle OldBrush=dc.SelectBrush(
			IsDragHandleEnabled(DragHandles[l]) ? ForeBrush : BackBrush);
		if(OldBrush!=NULL)
		{
			(void)dc.Rectangle(GetDragHandleRect(DragHandles[l]));
			(void)dc.SelectBrush(OldBrush);
		}
	}

	// clean up the dc
	if(OldPen!=NULL)
	{
		(void)dc.SelectPen(OldPen);
	}
	return 0;
}

LRESULT CDragFrame::OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	// to prevent flicker do not erase the background
	return TRUE;
}

LRESULT CDragFrame::OnSetCursor(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	// get the cursor position
	CPoint CursorPos(0,0);
	(void)GetCursorPos(&CursorPos);
	// convert to our client coordinates
	(void)ScreenToClient(&CursorPos);

	// get the cursor to display
	HCURSOR hCursor=NULL;
	DragHandle Handle=HitTest(CursorPos);
	switch(Handle)
	{
	// top left / bottom right
	case DragHandleTopLeft:
	case DragHandleBottomRight:
		hCursor=LoadCursor(NULL,IDC_SIZENWSE);
		break;
	// top / bottom
	case DragHandleTop:
	case DragHandleBottom:
		hCursor=LoadCursor(NULL,IDC_SIZENS);
		break;
	// top right / bottom left
	case DragHandleTopRight:
	case DragHandleBottomLeft:
		hCursor=LoadCursor(NULL,IDC_SIZENESW);
		break;
	// right / left
	case DragHandleRight:
	case DragHandleLeft:
		hCursor=LoadCursor(NULL,IDC_SIZEWE);
		break;
	// anything else
	default:
		hCursor=LoadCursor(NULL,(m_AllowMove) ? IDC_SIZEALL : IDC_ARROW);
		break;
	}
	// if the cursor is over a disabled drag handle
	if(Handle!=DragHandleNone and IsDragHandleEnabled(Handle)==FALSE)
	{
		hCursor=LoadCursor(NULL,(m_AllowMove) ? IDC_SIZEALL : IDC_ARROW);
	}	
	// set the cursor
	if(hCursor!=NULL)
	{
		(void)SetCursor(hCursor);
	}
	return TRUE;
}

LRESULT CDragFrame::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	// get the cursor position
	CPoint CursorPos(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam));
	
	// initialise action settings
	ATLASSERT(m_Action==ActionNone);
	m_ActionDragHandle=HitTest(CursorPos);
	m_ActionOrigin=CursorPos;
	m_ActionInLimbo=TRUE;

	// determine which action to perform
	if(m_ActionDragHandle==DragHandleNone or 
		IsDragHandleEnabled(m_ActionDragHandle)==FALSE)
	{
		// initiate a move action (if allowed)
		m_Action=m_AllowMove ? ActionMove : ActionNone;
	}
	else
	{
		// initiate a size action
		m_Action=ActionSize;
	}
	// capture the cursor
	(void)SetCapture();

	return 0;
}

LRESULT CDragFrame::OnLButtonUP(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	try	// catch all errors from the sink
	{
		// if the action is not in limbo
		if(m_ActionInLimbo==FALSE)
		{
			// end the action
			if(m_Action==ActionMove)
			{
				m_pNotifySink->DFNS_EndMove();
			}
			else if (m_Action==ActionSize)
			{
				m_pNotifySink->DFNS_EndSize();
			}
		}
	}
	catch(...)
	{}

	// clean up action settings
	m_Action=ActionNone;
	m_ActionDragHandle=DragHandleNone;
	m_ActionOrigin=CPoint(0,0);
	m_ActionInLimbo=FALSE;

	// release the cursor
	// we need to be careful here since ReleaseCapture() calls
	// OnCaptureChanged() which in turn calls us back just incase
	// the capture was taken from under our feet
	if(GetCapture()==*this)
	{
		(void)ReleaseCapture();
	}
	return 0;
}

LRESULT CDragFrame::OnRButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	// initiate the context menu
	try { m_pNotifySink->DFNS_ContextMenu(); }
	catch(...)
	{}
	return 0;
}

LRESULT CDragFrame::OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	// get the cursor position
	CPoint CursorPos(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam));
	// get the action cursor delta
	CPoint CursorDelta=CursorPos-m_ActionOrigin;

	// determine if we can release the action from limbo
	BOOL ReleaseActionFromLimbo=FALSE;
	if(m_Action!=ActionNone and m_ActionInLimbo)
	{
		if(	abs(CursorDelta.x) > GetSystemMetrics(SM_CXDRAG) or
			abs(CursorDelta.y) > GetSystemMetrics(SM_CYDRAG))
		{
			ReleaseActionFromLimbo=TRUE;	// release it
		}
	}
	try	// catch all errors from the sink
	{
		// update the action
		if(m_Action==ActionMove)
		{
			if(m_ActionInLimbo and ReleaseActionFromLimbo)
			{
				m_pNotifySink->DFNS_BeginMove();	// begin the move action
				m_ActionInLimbo=FALSE;
			}
			if(m_ActionInLimbo==FALSE)
			{
				m_pNotifySink->DFNS_Move(			// update the move action
					CursorDelta.x,CursorDelta.y);
			}
		}
		else if(m_Action==ActionSize)
		{
			if(m_ActionInLimbo and ReleaseActionFromLimbo)
			{
				m_pNotifySink->DFNS_BeginSize();	// begin the size action
				m_ActionInLimbo=FALSE;
			}
			if(m_ActionInLimbo==FALSE)
			{
				m_pNotifySink->DFNS_Size(			// update the size action
					m_ActionDragHandle,CursorDelta.x,CursorDelta.y);
			}
		}
	}
	catch(...)
	{}
	return 0;
}

LRESULT CDragFrame::OnCaptureChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	// simulate a left button up message
	BOOL Dummy=FALSE;
	(void)OnLButtonUP(WM_LBUTTONUP,0,0,Dummy);

	return 0;
}

LRESULT CDragFrame::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	// get client dimensions
	long cx=LOWORD(lParam);
	long cy=HIWORD(lParam);

	// if the drag frame is hollow we need to create our window region
	if(m_Hollow)
	{
		// outer rectangle
		CRect OuterRect(0,0,cx,cy);
		// outer region
		CRgn OuterRgn;
		if(OuterRgn.CreateRectRgnIndirect(OuterRect)==NULL)
		{
			return 0;
		}
		// inner rectangle
		CRect InnerRect(OuterRect);
		InnerRect.InflateRect(-DRAG_HANDLE_SIZE,-DRAG_HANDLE_SIZE);
		// inner region
		CRgn InnerRgn;
		if(InnerRgn.CreateRectRgnIndirect(InnerRect)==NULL)
		{
			return 0;
		}
		// by combining the outer and inner regions together
		// we come up with the final window region
		CRgn WindowRgn;
		if(WindowRgn.CreateRectRgn(0,0,0,0)==NULL)
		{
			return 0;
		}
		if(WindowRgn.CombineRgn(OuterRgn,InnerRgn,RGN_DIFF)==ERROR)
		{
			return 0;
		}
		(void)SetWindowRgn(WindowRgn.Detach(),FALSE);
	}
	return 0;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

DaveShep
Web Developer
United Kingdom United Kingdom
No Biography provided

Stats

300.4K views
79.2K downloads
227 bookmarked