
function DProps:Init(theCObj)
{
	super:Init(theCObj);
	
	DProps.mEditing = true;
	
	def mOverObj;
	def mSelectedObj;
	def mResizeRectIn;
	def mDragRelX;
	def mDragRelY;
	def mDidDrag = false;
	def mDownX = 0;
	def mDownY = 0;
	def mLastX = 0;
	def mLastY = 0;
	def mPlacingComponent = false;
	def mTyping = false;
	
	def mCurMenu;
	def mAddWidgetMenu;
	
	local aScriptDir = GetDirectoryFromPos(__POS);
	def mDialogTex = gCApp:GetImage(aScriptDir .. "/dialogtex");
	mDialogTex:SetD3DFlags(D3DImageFlags.UseA8R8G8B8);
	def mWidgetIcons = gCApp:GetImage(aScriptDir .. "/widgeticons");
	
	def mDrawOrderAcc = 0;
	
	def mFont8 = gCApp:GetFont(aScriptDir .. "/Arial8.txt");
	def mFont10 = gCApp:GetFont(aScriptDir .. "/Arial10.txt");
}

function DProps.AddWidgetType(theName, theClass, theImage, theImageNum)
{
	if (*DProps.mShared.mWidgetTypeOrder == 0)
		DProps.mShared.mCurAddWidgetType = theName;
	
	if (DProps.mShared.mWidgetTypes[theName] == nil)
		DProps.mShared.mWidgetTypeOrder[*DProps.mShared.mWidgetTypeOrder] = theName;
	
	DProps.mShared.mWidgetTypes[theName] = 
		{mName=theName, mClass=theClass, mImage=theImage, mImageNum=theImageNum};
}

function DProps.WidgetCreated(theWidget)
{
	local aCWidget = theWidget.mCObj:GetWidget();
	DProps.mShared.mWidgetToLuaWidget[aCWidget] = theWidget;
	
	local anObjTab = {mProps={}, mPropsPos={}, mFiles={}, mDrawOrder=0};
	DProps.mShared.mObjects[theWidget] = anObjTab;
}

function DProps.WidgetDeleted(theWidget)
{
	DProps.mShared.mObjects[theWidget] = nil;
	local aCWidget = theWidget.mCObj:GetWidget();
	DProps.mShared.mWidgetToLuaWidget[aCWidget] = nil;
}

//////////////////////////////////////////////////////////////////////////

function DProps:Draw(g)
{
	mDrawOrderAcc = 0;

	if (mPlacingComponent)
		mCObj:DeferOverlay(2);
	
	g:SetColor(255, 0, 0, 255);
	g:DrawRect(0, 0, mCObj:GetWidth()-1, mCObj:GetHeight()-1);
}

function DProps:IsValidPlacement(x, y)
{
	local aWidgetTypeData = DProps.mShared.mWidgetTypes[DProps.mShared.mCurAddWidgetType];
	local aSizeX, aSizeY = aWidgetTypeData.mClass.DPGetDefaultSize();
	
	local anAbsX, anAbsY = mSelectedObj.mCObj:GetAbsPos();
	local aWidth, aHeight = mSelectedObj.mCObj:GetSize();
	local aRect = Rect(anAbsX, anAbsY, aWidth, aHeight);
	return (RectContains(aRect, x, y)) && (RectContains(aRect, x+aSizeX-1, y+aSizeY-1));
}

function DProps:DrawOverlay(g)
{
	local anAbsX, anAbsY = mSelectedObj.mCObj:GetAbsPos();
	local aMouseX, aMouseY = gCApp:GetWidgetManager():GetLastMousePos();
	
	local aWidgetTypeData = DProps.mShared.mWidgetTypes[DProps.mShared.mCurAddWidgetType];
	local aSizeX, aSizeY = aWidgetTypeData.mClass.DPGetDefaultSize();
	local aX, aY = ApplyGrid(aMouseX - anAbsX - aSizeX/2, aMouseY - anAbsY - aSizeY/2);
	
	if (IsValidPlacement(aX + anAbsX, aY + anAbsY))
	{
		local aColorCycle = {Color(128, 128, 128, 255), Color(255, 255, 255, 255)};
		g:SetColor(aColorCycle[(mUpdateCnt / 12) % 2]);
	}
	else
	{
		local aColorCycle = {Color(128, 0, 0, 255), Color(255, 0, 0, 255)};
		g:SetColor(aColorCycle[(mUpdateCnt / 12) % 2]);
	}
	
	g:DrawRect(aX + anAbsX, aY + anAbsY, aSizeX, aSizeY);
}

function DProps:FillHorzLine(g, x, y, theWidth, theColor1, theColor2)
{
	local aSep = 8;
	
	local anOfs = (mUpdateCnt / 8) % aSep;
	local aChunks = (theWidth + aSep*2) / aSep;
	
	g:SetColor(255, 255, 255);
	for (i < 2)
	{
		if (i == 0)
			g:SetColor(theColor1);
		else
			g:SetColor(theColor2);
		
		for (aChunkNum < aChunks)
		{
			local aX = x-1 + (aChunkNum-1)*aSep + (i*aSep/2) + anOfs;
			g:FillRect(aX, y, 4, 1);
		}
	}
}

function DProps:FillVertLine(g, x, y, theHeight, theColor1, theColor2)
{
	local aSep = 8;
	
	local anOfs = (mUpdateCnt / 8) % aSep;
	local aChunks = (theHeight + aSep*2) / aSep;
	
	g:SetColor(255, 255, 255);
	for (i < 2)
	{
		if (i == 0)
			g:SetColor(theColor1);
		else
			g:SetColor(theColor2);
		
		for (aChunkNum < aChunks)
		{
			local aY = y-1 + (aChunkNum-1)*aSep + (i*aSep/2) + anOfs;
			g:FillRect(x, aY, 1, 4);
		}
	}
}

function DProps:DrawOutline(g, theWidget, theColor1, theColor2)
{
	local x, y  = theWidget.mCObj:GetAbsPos()
	local width, height = theWidget.mCObj:GetSize();
	
	g:ClipRect(0, 0, width, height);
	FillHorzLine(g, 0, 0, width, theColor1, theColor2);
	FillHorzLine(g, 0, height - 1, width, theColor1, theColor2);
	FillVertLine(g, width - 1, 0, height, theColor1, theColor2);
	FillVertLine(g, 0, 0, height, theColor1, theColor2);
	g:ClearClipRect();
}

function DProps:WidgetDraw(g, theCWidget)
{
	local aLuaWidget = DProps.mShared.mWidgetToLuaWidget[theCWidget];
	if (aLuaWidget != nil)
	{
		local anObjTab = DProps.mShared.mObjects[aLuaWidget];
		if (anObjTab != nil)
		{
			anObjTab.mDrawOrder = mDrawOrderAcc;
			mDrawOrderAcc++;
			local aProps = anObjTab.mProps;
	
			local wantGrid = (DProps.mShared.mGridOn) && (DProps.mShared.mGridSizeX > 1) && (DProps.mShared.mGridSizeY > 1);
			
			if (aLuaWidget == mSelectedObj)
			{
				local aParent = aLuaWidget.mCObj:GetParent();
				
				//local wantGrid = (DProps.mShared.mGridOn) && (DProps.mShared.mGridSizeX > 1) && (DProps.mShared.mGridSizeY > 1);
				
				/*if (wantGrid && !mPlacingComponent)
				{
					g:SetColor(Color(255, 255, 255));
					g:ClearClipRect();
					
					local aParentAbsX, aParentAbsY = aParent:GetAbsPos();
					local aChildAbsX, aChildAbsY = aLuaWidget.mCObj:GetAbsPos();
					g:Translate(aParentAbsX - aChildAbsX, aParentAbsY - aChildAbsY);
					
					mCObj:DrawGrid(g, DProps.mShared.mGridOfsX % DProps.mShared.mGridSizeX, 
						DProps.mShared.mGridOfsY % DProps.mShared.mGridSizeY,
						DProps.mShared.mGridSizeX, DProps.mShared.mGridSizeY, 
						aParent:GetWidth(), aParent:GetHeight());
					
					g:Translate(aChildAbsX - aParentAbsX, aChildAbsY - aParentAbsY);
				}*/
				
				theCWidget:Draw(g);
				
				if ((mPlacingComponent) && (wantGrid))
				{
					// WE are the grid
					
					g:SetColor(Color(255, 255, 255));
					mCObj:DrawGrid(g, DProps.mShared.mGridOfsX % DProps.mShared.mGridSizeX, 
						DProps.mShared.mGridOfsY % DProps.mShared.mGridSizeY,
						DProps.mShared.mGridSizeX, DProps.mShared.mGridSizeY, 
						mSelectedObj.mCObj:GetWidth(), mSelectedObj.mCObj:GetHeight());
				}
				
				if (IsMovable(aLuaWidget))
					DrawOutline(g, aLuaWidget, Color(255, 255, 255), Color(0, 0, 0));
				else
					DrawOutline(g, aLuaWidget, Color(255, 220, 220), Color(80, 64, 64));
				
				if ((IsMovable(aLuaWidget)) && (!mTyping))
				{
					for (aYOfs = -1, 1)
					{
						for (aXOfs = -1, 1)
						{
							if ((aXOfs != 0) || (aYOfs != 0))
							{
								local aRect = GetResizeRect(aLuaWidget, aXOfs, aYOfs);
								g:SetColor(Color(0, 0, 0));
								g:FillRect(aRect);
								g:SetColor(Color(220, 220, 255));
								g:FillRect(aRect.mX+1, aRect.mY+1, aRect.mWidth-2, aRect.mHeight-2);
							}
						}
					}
				}
			}
			else if ((aLuaWidget == mOverObj) && (mCurMenu == nil)) // Don't show "over" when property menu is up
			{
				theCWidget:Draw(g);
				
				if (IsMovable(aLuaWidget))
					DrawOutline(g, aLuaWidget, Color(200, 255, 200, 80), Color(64, 90, 64, 80));
				else
					DrawOutline(g, aLuaWidget, Color(255, 200, 200, 80), Color(90, 64, 64, 80));
			}
			else
			{
				theCWidget:Draw(g);
			}
			
			if ((mSelectedObj != nil) && (wantGrid) && (!mPlacingComponent) && (theCWidget == mSelectedObj.mCObj:GetParent()))
			{
				g:SetColor(Color(255, 255, 255));
				mCObj:DrawGrid(g, DProps.mShared.mGridOfsX % DProps.mShared.mGridSizeX, 
					DProps.mShared.mGridOfsY % DProps.mShared.mGridSizeY,
					DProps.mShared.mGridSizeX, DProps.mShared.mGridSizeY, 
					theCWidget:GetWidth(), theCWidget:GetHeight());
			}
			
			return;
		}
	}
	
	theCWidget:Draw(g);
}

function DProps:FindWidget(x, y)
{
	local aBest = nil;
	local aBestTab = nil;
	
	/*for aLuaWidget, anObjTab in DProps.mObjects
	{
		local aProps = anObjTab.mProps;
		
		local aScrX, aScrY = aLuaWidget.mCObj:GetScreenCoords(0, 0);
		
		if ((x >= aScrX) && (y >= aScrY) &&
			(x < aScrX + aProps.width) && (y < aScrY + aProps.height))
		{
			if ((aBest == nil) || (anObjTab.mDrawOrder > aBestTab.mDrawOrder))
			{
				aBest = aLuaWidget;
				aBestTab = anObjTab;
			}
		}
	}*/
	
	mCObj:SetMouseVisible(false);
	local aWidget = gCApp:GetWidgetManager():GetAnyWidgetAt(x, y);
	mCObj:SetMouseVisible(true);
	if (aWidget == nil)
		return nil;
	
	local aLuaWidget = DProps.mShared.mWidgetToLuaWidget[aWidget];
	return aLuaWidget;
}

function DProps:MouseMove(x, y)
{
	mLastX = x;
	mLastY = y;
	
	if (mPlacingComponent)
		return;
	
	local aLuaWidget = FindWidget(x, y);
	mOverObj = aLuaWidget;
	
	mResizeRectIn = GetResizeRectIn(mSelectedObj, x, y);
	if (mResizeRectIn == nil)
		gCApp:SetCursor(CURSOR.POINTER);
	else if (((mResizeRectIn[0] == -1) && (mResizeRectIn[1] == -1)) ||
		((mResizeRectIn[0] == 1) && (mResizeRectIn[1] == 1)))
		gCApp:SetCursor(CURSOR.SIZENWSE);
	else if (((mResizeRectIn[0] == 1) && (mResizeRectIn[1] == -1)) ||
		((mResizeRectIn[0] == -1) && (mResizeRectIn[1] == 1)))
		gCApp:SetCursor(CURSOR.SIZENESW);
	else if (((mResizeRectIn[0] == 1) && (mResizeRectIn[1] == 0)) ||
		((mResizeRectIn[0] == -1) && (mResizeRectIn[1] == 0)))
		gCApp:SetCursor(CURSOR.SIZEWE);
	else if (((mResizeRectIn[0] == 0) && (mResizeRectIn[1] == 1)) ||
		((mResizeRectIn[0] == 0) && (mResizeRectIn[1] == -1)))
		gCApp:SetCursor(CURSOR.SIZENS);
	else
		gCApp:SetCursor(CURSOR.POINTER);
}

function DProps:ApplyGrid(x, y)
{
	if ((DProps.mShared.mGridOn) && (DProps.mShared.mGridSizeX > 1) && (DProps.mShared.mGridSizeY > 1))
	{
		x = (((x + DProps.mShared.mGridSizeX/2) / DProps.mShared.mGridSizeX) * DProps.mShared.mGridSizeX) + 
			(DProps.mShared.mGridOfsX % DProps.mShared.mGridSizeX);
		y = (((y + DProps.mShared.mGridSizeY/2) / DProps.mShared.mGridSizeY) * DProps.mShared.mGridSizeY) + 
			(DProps.mShared.mGridOfsY % DProps.mShared.mGridSizeY); 
	}
	
	return x, y;
}

function DProps:MouseDrag(x, y)
{
	super:MouseDrag(x, y);
	
	if ((x == mLastX) && (y == mLastY))
		return;
	
	// On normal drag, don't start moving until we move more than 4 pixels
	if ((Math.abs(x - mLastX) < 4) && (Math.abs(y - mLastY) < 4) && (!mDidDrag) && 
		(mResizeRectIn != nil) && (mResizeRectIn[0] == 0) && (mResizeRectIn[1] == 0))
		return;
	
	mLastX = x;
	mLastY = y;
	
	if (mDragRelX != nil)
	{
		mDidDrag = true;

		local aNewValX, aNewValY = ApplyGrid(x + mDragRelX, y + mDragRelY);
		local aNewValX2, aNewValY2 = ApplyGrid(x + mDragRelX + mSelectedObj.mCObj:GetWidth() - 1, y + mDragRelY + mSelectedObj.mCObj:GetHeight() - 1);
		
		local aXPos = mSelectedObj.mCObj:GetX();
		local aYPos = mSelectedObj.mCObj:GetY();
		local aWidth = mSelectedObj.mCObj:GetWidth();
		local aHeight = mSelectedObj.mCObj:GetHeight();
		
		if ((mResizeRectIn[0] == -1) && (mResizeRectIn[1] == -1))
		{
			aWidth = aWidth + (aXPos - aNewValX);
			aHeight = aHeight + (aYPos - aNewValY);
			aXPos = aNewValX;
			aYPos = aNewValY;
		}
		else if ((mResizeRectIn[0] == 0) && (mResizeRectIn[1] == -1))
		{
			aHeight = aHeight + (aYPos - aNewValY);
			aYPos = aNewValY;
		}
		else if ((mResizeRectIn[0] == 1) && (mResizeRectIn[1] == -1))
		{
			aHeight = aHeight + (aYPos - aNewValY);
			aYPos = aNewValY;
			local aWidthInc = aWidth - (aNewValX2 - aXPos);
			aWidth -= aWidthInc;
			mDragRelX += aWidthInc;
		}
		
		else if ((mResizeRectIn[0] == -1) && (mResizeRectIn[1] == 0))
		{
			aWidth = aWidth + (aXPos - aNewValX);
			aXPos = aNewValX;
		}
		else if ((mResizeRectIn[0] == 0) && (mResizeRectIn[1] == 0))
		{
			aXPos = aNewValX;
			aYPos = aNewValY;
		}
		else if ((mResizeRectIn[0] == 1) && (mResizeRectIn[1] == 0))
		{
			local aWidthInc = aWidth - (aNewValX2 - aXPos);
			aWidth -= aWidthInc;
			mDragRelX += aWidthInc;
		}
		
		else if ((mResizeRectIn[0] == -1) && (mResizeRectIn[1] == 1))
		{
			aWidth = aWidth + (aXPos - aNewValX);
			aXPos = aNewValX;
			local aHeightInc = aHeight - (aNewValY2 - aYPos);
			aHeight -= aHeightInc;
			mDragRelY += aHeightInc;
		}
		else if ((mResizeRectIn[0] == 0) && (mResizeRectIn[1] == 1))
		{
			local aHeightInc = aHeight - (aNewValY2 - aYPos);
			aHeight -= aHeightInc;
			mDragRelY += aHeightInc;
		}
		else if ((mResizeRectIn[0] == 1) && (mResizeRectIn[1] == 1))
		{
			local aWidthInc = aWidth - (aNewValX2 - aXPos);
			aWidth -= aWidthInc;
			mDragRelX += aWidthInc;
			local aHeightInc = aHeight - (aNewValY2 - aYPos);
			aHeight -= aHeightInc;
			mDragRelY += aHeightInc;
		}
		
		mSelectedObj:Resize(aXPos, aYPos, aWidth, aHeight);
	}
}

function DProps:MouseUp(x, y, theBtnNum, theClickCount)
{
	super:MouseUp(x, y, theBtnNum, theClickCount);
	
	if ((mDragRelX != nil) && (mDidDrag))
	{
		RehupWidgetPos(mSelectedObj);
		gCApp:SetCursor(CURSOR.POINTER);
	}
	
	mDragRelX = nil;
	mDragRelY = nil;
}

function DProps:KillMenu()
{
	if (mCurMenu != nil)
	{
		RemoveWidget(mCurMenu);
		gCApp:SafeDeleteWidget(mCurMenu.mCObj);
		mCurMenu = nil;
		gCApp:GetWidgetManager():SetFocus(mCObj);
	}
}

function DProps:KillAddWidgetMenu()
{
	if (mAddWidgetMenu != nil)
	{
		RemoveWidget(mAddWidgetMenu);
		gCApp:SafeDeleteWidget(mAddWidgetMenu.mCObj);
		mAddWidgetMenu = nil;
		gCApp:GetWidgetManager():SetFocus(mCObj);
	}
}

function DProps:AdjustWindow(theMenu, theFocusX, theFocusY, theFocusWidth, theFocusHeight)
{
	local anAppWidth, anAppHeight = gCApp:GetWidth(), gCApp:GetHeight();
			
	local aMenuWidth, aMenuHeight = theMenu.mCObj:GetWidth(), theMenu.mCObj:GetHeight();
	
	local aWantX = theFocusX;
	if (theFocusWidth > 0)
		aWantX += theFocusWidth + 8;
	local aWantY = theFocusY + theFocusHeight/2 - theMenu.mCObj:GetHeight()/2;
	
	if (aWantX + aMenuWidth > anAppWidth - 2)
	{
		if (theFocusWidth > 0)
			aWantX = theFocusX - aMenuWidth - 6; // Move to left of widget
		else
			aWantX = anAppWidth - aMenuWidth - 2;
	}
	if (aWantX < 2)
	{
		if (theFocusWidth > 0)
			aWantX = anAppWidth - aMenuWidth - 6; // Move to far right of screen
		else
			aWantX = 2;
	}
	
	if (aWantY + aMenuHeight > anAppHeight - 2)
		aWantY = anAppHeight - aMenuHeight - 2;
	if (aWantY < 2)
		aWantY = 2;
	
	theMenu:Move(aWantX, aWantY);
}

function DProps:MouseDown(x, y, theBtnNum, theClickCount)
{
	gCApp:GetWidgetManager():SetFocus(mCObj);
	
	mDownX = x;
	mDownY = y;
	mLastX = x;
	mLastY = y;
	
	if (theBtnNum == 0)
	{
		if (mCurMenu != nil)
		{
			KillMenu();
		}
		else
		{
			if (mPlacingComponent)
			{
				mPlacingComponent = false;
				
				local anAbsX, anAbsY = mSelectedObj.mCObj:GetAbsPos();
				local aMouseX, aMouseY = gCApp:GetWidgetManager():GetLastMousePos();
				
				local aWidgetTypeData = DProps.mShared.mWidgetTypes[DProps.mShared.mCurAddWidgetType];
				local aWidth, aHeight = aWidgetTypeData.mClass.DPGetDefaultSize();
				local aX, aY = ApplyGrid(aMouseX - anAbsX - aWidth/2, aMouseY - anAbsY - aHeight/2);
			 	
				if (IsValidPlacement(aX + anAbsX, aY + anAbsY))
				{
					//local aWidget, aText = aWidgetTypeData.mClass.DPGetCreateDefault(mSelectedObj, aX, aY, aSizeX, aSizeY);
					AddInitCode(aWidgetTypeData.mClass, mSelectedObj, aX, aY, aWidth, aHeight);
				}
			}
			else
			{
				if ((mSelectedObj != nil) && (mResizeRectIn != nil) && 
					((mResizeRectIn[0] != 0) || (mResizeRectIn[1] != 0)))
				{
					
				}
				else
				{
					mSelectedObj = mOverObj;
					mResizeRectIn = {0, 0}; // Default - just move it around
				}
				
				mTyping = false;
				
				if ((mSelectedObj != nil) && (IsMovable(mSelectedObj)))
				{
					mDidDrag = false;
					mDragRelX = mSelectedObj.mCObj:GetX() - x;
					mDragRelY = mSelectedObj.mCObj:GetY() - y;
				}
			}
		}
	}
	else if (theBtnNum == 1)
	{
		if (mPlacingComponent)
		{
			mPlacingComponent = false;
			return
		}
		
		KillMenu();
		mSelectedObj = mOverObj;
		
		if (mSelectedObj != nil)
		{
			mCurMenu = gApp:CreateLuaWidget(DPropsMenu);
			mCurMenu:Resize(0, 0, 194, 200);
			mCurMenu:LinkTo(self, mSelectedObj);
			local aScrX, aScrY = mSelectedObj.mCObj:GetAbsPos();
			local aWidth, aHeight = mSelectedObj.mCObj:GetSize();
			AdjustWindow(mCurMenu, aScrX, aScrY, aWidth, aHeight);
			AddWidget(mCurMenu);
			gCApp:GetWidgetManager():SetFocus(mCurMenu.mCObj);
		}
	}
}

function DProps:IsMovable(theWidget)
{
	local anObjTab = DProps.mShared.mObjects[theWidget];
	return table.contains(anObjTab.mProps, "x");
}

function DProps:FindParamEnd(theString, thePos)
{
	local hasHex = false;
	local aPrevChar;
	
	while (true)
	{
		local aChar = string.sub(theString, thePos, 1);
		
		if (aChar == "")
			return nil, hasHex;
		if ((aChar == ",") || (aChar == " ") || (aChar == ")") || (aChar == "}"))
			return thePos, hasHex;
		
		if ((aChar == "x") && (aPrevChar == "0"))
			hasHex = true;
		
		thePos++;
		
		if (aChar == "\"")
		{
			//Ah, we have a string to parse now...
			while (true)
			{
				local aChar = string.sub(theString, thePos, 1);
				thePos++;
				if (aChar == "\\")
					thePos++;
				if ((aChar == "\"") || (aChar == ""))
					break; // end of string
			}
		}
		
		if (aChar == "{")
		{
			//Ah, we have a table to parse now...
			while (true)
			{
				local aChar = string.sub(theString, thePos, 1);
				
				if ((aChar == "\"") || (aChar == "{"))
				{
					local subHasHex = false;
					thePos, subHasHex = FindParamEnd(theString, thePos);
					if (subHasHex)
						hasHex = true;
					if (thePos == nil)
						return nil, hasHex;
				}
				else 
				{
					thePos++;
					if ((aChar == "}") || (aChar == ""))
						break; // end of table
				}
				
				if ((aChar == "x") && (aPrevChar == "0"))
					hasHex = true;
				aPrevChar = aChar;
			}
		}
		
		aPrevChar = aChar;
	}
}

function DProps.FixString(theString)
{
	for (i < string.len(theString))
	{
		local aChar = string.sub(theString, i, 1);
		if (aChar == "\\")
		{
			if (i == string.len(theString)-1)
				theString = theString .. "\\";
			else
				i++; // Skip over next dude
		}
		else if (aChar == "\"")
		{
			theString = string.sub(theString, 0, i) .. "\\" .. string.sub(theString, i);
			_forlimit++; // One more char
			i++;
		}
	}
	
	return theString;
}

function DProps.FormatVal(theVal, func, doHex)
{
	local aType = type(theVal);
	
	if (aType == "string")
	{
		return "\"" .. func(theVal) .. "\"";
	}
	else if (aType == "boolean")
	{
		if (theVal)
			return "true";
		else
			return "false";
	}
	else if (aType == "table")
	{
		local aTableStr = "{";
		local aParamNum = 0;
		
		for aKey, aVal in theVal
		{
			if (aParamNum > 0)
				aTableStr = aTableStr .. ", ";
			
			if (aKey != aParamNum)
				aTableStr = aTableStr .. aKey .. "=";
			
			aTableStr = aTableStr .. DProps.FormatVal(aVal, func, doHex);
			aParamNum++;
		}
		
		aTableStr = aTableStr .. "}";
		return aTableStr;
	}
	else if ((aType == "int") && (doHex))
	{
		return string.format("0x%08X", theVal);
	}
	
	return theVal;
}

function DProps:GetResizeRect(theWidget, theXOfs, theYOfs)
{
	local aXCenter = ((theWidget.mCObj:GetWidth()-1) * ((theXOfs + 1.0) / 2.0));
	local aYCenter = ((theWidget.mCObj:GetHeight()-1) * ((theYOfs + 1.0) / 2.0));
	
	return Rect(aXCenter - 3, aYCenter - 3, 7, 7);
}

function DProps:GetResizeRectIn(theWidget, x, y)
{
	if ((theWidget == nil) || (!IsMovable(mSelectedObj)))
		return nil;
	
	local anAbsPosX, anAbsPosY = theWidget.mCObj:GetAbsPos();
	
	for (aYOfs = -1, 1)
	{
		for (aXOfs = -1, 1)
		{
			local aRect = GetResizeRect(theWidget, aXOfs, aYOfs);
			aRect.mX += anAbsPosX;
			aRect.mY += anAbsPosY;
			if (RectContains(aRect, x, y))
				return {aXOfs, aYOfs};
		}
	}
	
	local aRect = Rect(anAbsPosX, anAbsPosY, theWidget.mCObj:GetSize());
	if (RectContains(aRect, x, y))
		return {0, 0};
	return nil;
}

function DProps:HasProp(theObj, thePropName)
{
	local anObjTab = DProps.mShared.mObjects[theObj];
	return table.contains(anObjTab.mProps, thePropName);
}

function DProps:AddInitCode(theComponentClass, theClass, theX, theY, theWidth, theHeight)
{
	local aDebugInfo = debug.getinfo(theClass.Init);
	local aNewWidget;
	
	local aFileName = aDebugInfo.source;
	local aFile = io.open(string.sub(aFileName, 1), "rb+");
	if (aFile != nil)
	{
		local aCurLineNum = 1;
		local addLines = false;
		local aFoundPos = -1;
		local aNewLines = {};
		local anInsertLine = -1;
		local anInsertedLines;
		
		for aLine in aFile:lines()
		{
			if ((aCurLineNum > aDebugInfo.linedefined) && (!addLines))
			{
				if (string.sub(aLine, 0, 1) == "}")
				{
					anInsertLine = aCurLineNum;
					addLines = true; // Wee!
			
					aNewWidget, anInsertedLines = theComponentClass.DPGetCreateDefault({aFileName, aCurLineNum+1}, mSelectedObj, theX, theY, theWidth, theHeight);
					
					aNewLines[*aNewLines] = "\r";
					for (aLineNum < *anInsertedLines)
						aNewLines[*aNewLines] = "\t" .. anInsertedLines[aLineNum] .. "\r";
				}
				else
					aFoundPos = aFile:seek("cur", 0);
			}
			
			if (addLines)
				aNewLines[*aNewLines] = aLine;
			
			aCurLineNum++;
		}
		
		if (aFoundPos != -1)
		{
			aFile:seek("set", aFoundPos);
			for (i < *aNewLines)
			{
				local aLine = aNewLines[i];
				aLine = aLine .. "\n";
				aFile:write(aLine);
			}
		}
		else
		{
			error("Unable to find end of constructor");
		}
		
		aFile:close();
		
		// Increment line numbers for other guys
		if (anInsertLine != -1)
		{
			for aCheckObj, aCheckObjTab in DProps.mShared.mObjects
			{
				if ((aCheckObj != aNewWidget) && (aCheckObjTab.mFiles[aFileName] != nil))
				{
					local aNewVals = {};
					for aLine, aTrackData in aCheckObjTab.mFiles[aFileName]
					{
						if (aLine > anInsertLine)
							aNewVals[aLine + *anInsertedLines + 1] = aTrackData;
						else
							aNewVals[aLine] = aTrackData;
					}
					
					aCheckObjTab.mFiles[aFileName] = aNewVals;
				}
			}
		}
	}
	else
	{
		error("Unable to open file");
	}
	
	mCObj:NeedReload();
	
	return aNewWidget;
}

function DProps:SetProp(theObj, thePropName, theNewVal)
{
	local anObjTab = DProps.mShared.mObjects[theObj];
	
	local aNewVal = loadstring("return " .. DProps.FormatVal(theNewVal, FixString, false))();
	
	local aProps = anObjTab.mProps;
	if (aProps[thePropName] == aNewVal)
		return true;
	aProps[thePropName] = aNewVal;
	
	local found = false;
	
	// Now find a matching call
	for aFileName, aFileTab in anObjTab.mFiles
	{
		for aLineNum, aTrackInfo in aFileTab
		{
			local has = false;
			
			for (anArgNum < *aTrackInfo[2])
				if (aTrackInfo[2][anArgNum] == thePropName)
					has = true;

			if (has) // We're in business!
			{
				local anArgs = {};
				for (anArgNum < *aTrackInfo[2])
					anArgs[anArgNum] = anObjTab.mProps[aTrackInfo[2][anArgNum]];
				
				// Make call to set new value now
				if (aTrackInfo[0] != nil)
				{
					for aCheckObj, aCheckObjTab in DProps.mShared.mObjects
					{
						// Change all objects that were modified by this same file and line
						if ((aCheckObjTab.mFiles[aFileName] != nil) && (aCheckObjTab.mFiles[aFileName][aLineNum] != nil))
						{
							:gLinkedDynCallTable.a = aTrackInfo[0];	
							gLinkedDynCallTable@aCheckObj:a(unpack(anArgs));
						}
					}
				}
				else
				{
					// Read-only error or something?
				}
				
				local aFile = io.open(string.sub(aFileName, 1), "rb+");
				if (aFile != nil)
				{
					local aCurLineNum = 1;
				
					local aLastPos = 0;
					local aFoundPos = -1;
					local aNewLines = {};
					local addLines = false;
					
					for aLine in aFile:lines()
					{
						if (aCurLineNum == aLineNum)
						{
							local aFindStr = "__POS,";
							
							local aStrPos = string.find(aLine, aFindStr);
							if (aStrPos != nil)
							{
								aStrPos += string.len(aFindStr);
								
								local aNewLine = string.sub(aLine, 0, aStrPos);
								
								// Now we are at the first param position
								local aParamNum = 0;
								
								while (true)
								{
									local foundComma = false;
									
									while (true)
									{
										local aChar = string.sub(aLine, aStrPos, 1);
										if (aChar == ",")
											foundComma = true;
										if ((aChar != " ") && (aChar != "\t") && (aChar != ","))
											break;
										aNewLine = aNewLine .. aChar;
										aStrPos++;
									}
									
									local anEndPos, doHex = FindParamEnd(aLine, aStrPos);
									if (anEndPos == nil)
										break;
									
									if ((!foundComma) && (aParamNum != 0))
										error("Invalid dynamic code - only immediates allowed");
									
									local anOldVal = string.sub(aLine, aStrPos, anEndPos - aStrPos);
									
									local aPropVal = aProps[aTrackInfo[2][aParamNum]];
									
									aNewLine = aNewLine .. DProps.FormatVal(aPropVal, string.addslashes, doHex);
									
									aStrPos = anEndPos;
									
									local anEndChar = string.sub(aLine, aStrPos, 1);
									if (anEndChar == ")")
									{
										// End of param
										aNewLine = aNewLine .. string.sub(aLine, aStrPos);
										break;
									}
									
									aParamNum++;
								}
								
								aNewLines[*aNewLines] = aNewLine;
								//aNewLines[*aNewLines] = aLine;
								if (string.len(aNewLine) != string.len(aLine))
									addLines = true;
							}
							else
							{
								error("Invalid line");
							}
						}
						else if (addLines)
							aNewLines[*aNewLines] = aLine;
						
						if (aCurLineNum == aLineNum-1)
						{
							aFoundPos = aFile:seek("cur", 0);
						}
						
						aCurLineNum++;
					}
					
					if (aFoundPos != -1)
					{
						aFile:seek("set", aFoundPos);
						for (i < *aNewLines)
						{
							local aLine = aNewLines[i];
							if (i == *aNewLines - 1)
							{
								local aPos = string.len(aLine);
								while (aPos > 0)
								{
									local aChar = string.sub(aLine, aPos-1, 1);
									if ((aChar != " ") && (aChar != "\r"))
										break;
									aPos--;
								}
								
								aLine = string.sub(aLine, 0, aPos);
								if (string.len(aLine) > 0)
								{
									aFile:write(aLine .. "\r\n");
									aLine = "";
								}
								
								if (addLines)
								{
									local aCurPos = aFile:seek("cur", 0);
									local aNextPos = aCurPos + string.len(aLine);
									local aSize = aFile:seek("end", 0);
									aFile:seek("set", aCurPos);
									aLine = aLine .. string.rep(" ", aSize - aNextPos);
								}
							}
							else
								aLine = aLine .. "\n";
							
							aFile:write(aLine);
						}
					}
					
					aFile:close();
				}
				else
				{
					error("Unable to open file");
				}
				
				found = true;
			}
		}
	}
	
	if (found)
		mCObj:NeedReload();
	else
		error("Unable to find dynamic property def");

	return found;
}

function DProps:MoveWidget(theWidget, theDeltaX, theDeltaY)
{
	local aCurX, aCurY = mSelectedObj.mCObj:GetX(), mSelectedObj.mCObj:GetY();
	
	SetProp(mSelectedObj, "X", aCurX + theDeltaX);
	SetProp(mSelectedObj, "Y", aCurY + theDeltaY);
}

function DProps:RehupWidgetPos(theWidget, theDeltaX, theDeltaY)
{
	local aX, aY, aWidth, aHeight = mSelectedObj.mCObj:GetX(), mSelectedObj.mCObj:GetY(),
		mSelectedObj.mCObj:GetWidth(), mSelectedObj.mCObj:GetHeight();
	
	if (HasProp(mSelectedObj, "X"))
		SetProp(mSelectedObj, "X", aX);
	if (HasProp(mSelectedObj, "Y"))
		SetProp(mSelectedObj, "Y", aY);
	if (HasProp(mSelectedObj, "Width"))
		SetProp(mSelectedObj, "Width", aWidth);
	if (HasProp(mSelectedObj, "Height"))
		SetProp(mSelectedObj, "Height", aHeight);
}

function DProps:ShowStdMenu(theMethod, theWidth)
{
	KillMenu();
	local aMenu = gApp:CreateLuaWidget(DPropsMenu);
	aMenu:Resize(0, 0, theWidth, 8); // Temporary height
	:gLinkedDynCallTable.a = theMethod;
	gLinkedDynCallTable@aMenu:a(self, mSelectedObj);
	AdjustWindow(aMenu, mLastX - theWidth/2, mLastY, 0, 0);
	AddWidget(aMenu);
	gCApp:GetWidgetManager():SetFocus(aMenu.mCObj);
	return aMenu;
}

function DProps:KeyChar(theChar)
{
	if (theChar == string.char(13))
	{
		mTyping = false;
		return;
	}
	
	if (mSelectedObj != nil)
	{
		local anObjTab = DProps.mShared.mObjects[mSelectedObj];
		if (anObjTab.mProps["Label"] != nil)
		{
			local aNewStr;
			if (!mTyping)
				aNewStr = theChar;
			else
				aNewStr = anObjTab.mProps["Label"] .. theChar;
			
			if (theChar == string.char(8))
				aNewStr = string.sub(aNewStr, 0, string.len(aNewStr)-2);
			else if (theChar < string.char(32))
				return;
			
			SetProp(mSelectedObj, "Label", aNewStr);
			mTyping = true;
		}
	}
}

function DProps:KeyDown(theKey)
{
	local aChar = string.char(theKey);
	
	if (theKey == KEYCODE.ESCAPE)
	{
		DProps.mEditing = false;
		mCObj:Done();
	}
	else if (gCApp:GetWidgetManager():IsKeyDown(KeyCode.CONTROL))
	{
		if (aChar == "G")
		{
			mCurMenu = ShowStdMenu(DPropsMenu.ShowGridOptions, 150);
		}
		else if (aChar == "A")
		{
			if (mAddWidgetMenu != nil)
				KillAddWidgetMenu();
			else
			{
				mAddWidgetMenu = ShowStdMenu(DPropsMenu.ShowAddWidget, 126);
				if (DProps.mShared.mLastAddWidgetX == nil)
				{
					DProps.mShared.mLastAddWidgetX = mCObj:GetWidth() - mAddWidgetMenu.mCObj:GetWidth() - 4;
					DProps.mShared.mLastAddWidgetY = 4;
				}
				
				mAddWidgetMenu:Move(DProps.mShared.mLastAddWidgetX, DProps.mShared.mLastAddWidgetY);
			}
		}
	}
	
	if (mSelectedObj == nil)
		return;
	
	local anObjTab = DProps.mShared.mObjects[mSelectedObj];
	
	if (IsMovable(mSelectedObj))
	{
		if (theKey == KeyCode.LEFT) // Left
		{
			if (gCApp:GetWidgetManager():IsKeyDown(KeyCode.SHIFT))
				MoveWidget(mSelectedObj, -10, 0);
			else
				MoveWidget(mSelectedObj, -1, 0);
		}
		else if (theKey == KeyCode.RIGHT) // Right
		{
			if (gCApp:GetWidgetManager():IsKeyDown(KeyCode.SHIFT))
				MoveWidget(mSelectedObj, 10, 0);
			else
				MoveWidget(mSelectedObj, 1, 0);
		}
		else if (theKey == KeyCode.UP) // Up
		{
			if (gCApp:GetWidgetManager():IsKeyDown(KeyCode.SHIFT))
				MoveWidget(mSelectedObj, 0, -10);
			else
				MoveWidget(mSelectedObj, 0, -1);
		}
		else if (theKey == KeyCode.DOWN) // Down
		{
			if (gCApp:GetWidgetManager():IsKeyDown(KeyCode.SHIFT))
				MoveWidget(mSelectedObj, 0, 10);
			else
				MoveWidget(mSelectedObj, 0, 1);
		}
	}
}

//////////////////////////////////////////////////////////////////////////

class DPropsEdit : LuaWidget(theCObj)
{
	super:Init(theCObj);
	
	def mDPropsMenu;
	def mPropName;
	def mOrigVal;
	def mWantUpdateText;
}

function DPropsEdit:SetVal(theVal)
{
	mOrigVal = theVal;
	if (type(theVal) == "string")
		theVal = string.addslashes(theVal);
	else if (type(theVal) == "table")
		theVal = DProps.FormatVal(theVal, string.addslashes, false);
	mCObj:SetText(theVal, true);
}

function DPropsEdit:KeyDown(theKey)
{
	if (theKey == KEYCODE.ESCAPE)
		mDPropsMenu:KeyDown(theKey);
	else
		super:KeyDown(theKey);
}

function DPropsEdit:Commit(theText)
{
	return mDPropsMenu:SetProp(mPropName, theText);
}

function DPropsEdit:UpdateText(theText)
{
	return Commit(theText);
}

function DPropsEdit:DeferUpdateText(theText, strict)
{
	local aVal = theText;
	
	if ((type(mOrigVal) == "number") || (type(mOrigVal) == "int"))
	{
		aVal = string.tonum(theText);
		if (aVal == nil)
			return false;
	}
	else if (type(mOrigVal) == "table")
	{
		local aConvFunc = loadstring("return " .. theText);
		if (aConvFunc == nil)
			return !strict; // Let them type anything they want
		aVal = aConvFunc();
	}
	
	mWantUpdateText = aVal;
	return true;
}

function DPropsEdit:LostFocus()
{
	super:LostFocus();
	if (!DeferUpdateText(mCObj:GetText(), true))
	{
		//TODO: Show error!
	}
}

//////////////////////////////////////////////////////////////////////////

class AddWidgetButton : LuaWidget(theCObj)
{
	super:Init(theCObj);
	def mPropsMenu;
	def mWidgetTypeName;
}

function AddWidgetButton:Draw(g)
{
	super:Draw(g);
	
	if ((mCObj:GetIsDown()) && (mCObj:GetIsOver()))
		g:Translate(1, 1);
	
	local aWidgetTypeData = DProps.mShared.mWidgetTypes[mWidgetTypeName];
	local anImage = aWidgetTypeData.mImage;
	if (anImage == nil)
		anImage = mPropsMenu.mDProps.mWidgetIcons;
	
	g:DrawImage(anImage, 2, 0, Rect(aWidgetTypeData.mImageNum*18, 0, 18, 18));
	g:SetColor(Color(0, 0, 0));
	g:SetFont(mPropsMenu.mDProps.mFont8);
	g:DrawString(mWidgetTypeName, 24, 14);
	
	if (mWidgetTypeName == DProps.mShared.mCurAddWidgetType)
		g:SetColor(Color(255, 255, 255, 0));
	else
		g:SetColor(Color(0, 0, 0, 80));
	g:FillRect(0, 0, mCObj:GetWidth(), mCObj:GetHeight());
}

//////////////////////////////////////////////////////////////////////////

class DPropsTextWidget : LuaTextWidget(theCObj)
{
	super:Init(theCObj);
	mCObj:SetMouseVisible(false);
};

//////////////////////////////////////////////////////////////////////////

class DPropsMenu : LuaWidget(theCObj)
{
	super:Init(theCObj);
	
	def mWidget;
	def mDragRelX;
	def mDragRelY;
	
	def mDProps;
	def mProps;
	def mColors;
	def mButtons = {};
	
	def mPropEditList = {};
	def mCheckboxPropNames = {};
	def mMenuType;
}

const DPropsMenu.TYPE_PROPS = 0;
const DPropsMenu.TYPE_GRID = 1;
const DPropsMenu.TYPE_ADDWIDGET = 2;

function DPropsMenu:SetProp(thePropName, theNewVal)
{
	if (mWidget == nil)
		return true; // No properties!
	return mDProps:SetProp(mWidget, thePropName, theNewVal);
}

function DPropsMenu:AllowText(theId, theText)
{
	if (theId != -1)
	{
		if (mPropEditList[theId]:DeferUpdateText(theText, false))
		{
			if (mMenuType == TYPE_GRID)
			{
				if (theId == 0)
					DProps.mShared.mGridSizeX = string.tonum(theText);
				else if (theId == 1)
					DProps.mShared.mGridSizeY = string.tonum(theText);
				else if (theId == 2)
					DProps.mShared.mGridOfsX = string.tonum(theText);
				else if (theId == 3)
					DProps.mShared.mGridOfsY = string.tonum(theText);
				DProps.mShared:Save();
			}
			
			return true;
		}
		return ((theText == "") || (theText == "-"));
	}
	else
		return true;
}

function DPropsMenu:CheckboxChecked(theId, checked)
{
	if (mMenuType == TYPE_GRID)
	{
		DProps.mShared.mGridOn = checked;
		DProps.mShared:Save();
	}
	else 
	{
		SetProp(mCheckboxPropNames[theId], checked);
	}
}

function DPropsMenu:CreateLabel(x, y, thePropName)
{
	local aLabel = gApp:CreateTextWidget(DPropsTextWidget);
	aLabel:Resize(x+2, y, 200, 24);
	aLabel.mCObj:SetFont(mDProps.mFont8);
	aLabel.mCObj:SetLabel(thePropName);
	AddWidget(aLabel);
}

function DPropsMenu:CreateLabeledEdit(x, y, width, thePropName, theLabelValue)
{
	CreateLabel(x, y, thePropName);
	
	local anEdit = gApp:CreateEditWidget(*mPropEditList, self, DPropsEdit);
	anEdit.mDPropsMenu = self;
	anEdit.mPropName = thePropName;
	anEdit.mCObj:SetFont(mDProps.mFont8);
	anEdit:Resize(x, y + 12, width, 20);
	anEdit:SetVal(theLabelValue);
	AddWidget(anEdit);
	
	mPropEditList[*mPropEditList] = anEdit;
}

function DPropsMenu:LinkTo(theDProps, theWidget)
{
	mDProps = theDProps;
	mWidget = theWidget;
	mMenuType = TYPE_PROPS;
	
	local aCurY = 8;
	
	local anObjTab = DProps.mShared.mObjects[theWidget];
	mProps = anObjTab.mProps;
	
	local aStr = "Dynamic Widget Properties";
	local aLabel = gApp:CreateTextWidget(DPropsTextWidget);
	aLabel:Resize((mCObj:GetWidth() - mDProps.mFont10:StringWidth(aStr))/2, aCurY, 200, 24);
	aLabel.mCObj:SetLabel(aStr);
	aLabel.mCObj:SetFont(mDProps.mFont10);
	aLabel.mCObj:SetColors({0xFFFFFF80});
	AddWidget(aLabel);
	aCurY += 20;
	
	local aPropsLeft = :DupTable(mProps);
	
	if (table.contains(mProps, "X"))
	{
		CreateLabeledEdit(8, aCurY, 40, "X", mProps.x);
		CreateLabeledEdit(8+(40+6), aCurY, 40, "Y", mProps.y);
		CreateLabeledEdit(8+(40+6)*2, aCurY, 40, "Width", mProps.width);
		CreateLabeledEdit(8+(40+6)*3, aCurY, 40, "Height", mProps.height);
		aPropsLeft.x = nil; aPropsLeft.y = nil; aPropsLeft.width = nil; aPropsLeft.height = nil;
		aCurY += 36;
	}
	
	if (table.contains(mProps, "Label"))
	{
		CreateLabeledEdit(8, aCurY, 178, "Label", mProps.label);
		aPropsLeft.label = nil;
		aCurY += 36;
	}
	
	if (table.contains(mProps, "Colors"))
	{
		CreateLabel(8, aCurY, "Colors");
		
		mColors = {};
		for (aColorNum < *mProps.colors)
		{
			local aColor = mProps.colors[aColorNum];
			if ((aColor < 0x7F000000) || (aColor > -0x80000000))
				aColor += 0xFF000000;
			
			mColors[*mColors] = {mColor=aColor, mRect=Rect(8 + aColorNum*22, aCurY+13, 20, 16)};
		}
		aPropsLeft.Colors = nil;
		aCurY += 34;
	}
	
	for aKey, aValue in aPropsLeft
	{
		local aType = type(aValue);
		if (aType == "boolean")
		{
			local aCheck = gApp:CreateCheckboxWidget(nil, nil, *mCheckboxPropNames, self);
			aCheck:Resize(8, aCurY+2, 16, 16);
			aCheck:SetChecked(aValue);
			AddWidget(aCheck);
			CreateLabel(28, aCurY+4, aKey);
			mCheckboxPropNames[*mCheckboxPropNames] = aKey;
			aCurY += 28;
		}
		else
		{
			CreateLabeledEdit(8, aCurY, 178, aKey, aValue);
			aCurY += 36;
		}
	}
	
	if (aCurY == 28) // No props
	{
		local aStr = "< No Properties >";
		local aLabel = gApp:CreateTextWidget(DPropsTextWidget);
		aLabel:Resize((mCObj:GetWidth() - mDProps.mFont10:StringWidth(aStr))/2, aCurY, 200, 24);
		aLabel.mCObj:SetLabel(aStr);
		aLabel.mCObj:SetFont(mDProps.mFont10);
		aLabel.mCObj:SetColors({0xFFFF4040});
		AddWidget(aLabel);
		aCurY += 20;
	}
	
	Resize(mCObj:GetX(), mCObj:GetY(), mCObj:GetWidth(), aCurY + 6);
}

function DPropsMenu:ShowGridOptions(theDProps)
{
	mDProps = theDProps;
	mMenuType = TYPE_GRID;
	
	local aCurY = 8;
	
	local aStr = "Grid Options";
	local aLabel = gApp:CreateTextWidget(DPropsTextWidget);
	aLabel:Resize((mCObj:GetWidth() - mDProps.mFont10:StringWidth(aStr))/2, aCurY, 200, 24);
	aLabel.mCObj:SetLabel(aStr);
	aLabel.mCObj:SetFont(mDProps.mFont10);
	aLabel.mCObj:SetColors({0xFFFFFF80});
	AddWidget(aLabel);
	aCurY += 22;
	
	local aCheckbox = gApp:CreateCheckboxWidget(nil, nil, 0, self);
	aCheckbox.mCObj:SetChecked(DProps.mShared.mGridOn);
	aCheckbox:Resize(12, aCurY, 16, 16);
	AddWidget(aCheckbox);
	
	CreateLabel(32, aCurY + 2, "Enable Grid");
	
	aCurY += 24;
	
	CreateLabeledEdit(8, aCurY, 64, "Grid Size X", DProps.mShared.mGridSizeX);
	CreateLabeledEdit(8+(64+6), aCurY, 64, "Grid Size Y", DProps.mShared.mGridSizeY);
	aCurY += 36;
	CreateLabeledEdit(8, aCurY, 64, "Grid Ofs X", DProps.mShared.mGridOfsX);
	CreateLabeledEdit(8+(64+6), aCurY, 64, "Grid Ofs Y", DProps.mShared.mGridOfsY);
	aCurY += 36;
	
	Resize(mCObj:GetX(), mCObj:GetY(), mCObj:GetWidth(), aCurY + 6);
}

function DPropsMenu:ShowAddWidget(theDProps)
{
	mDProps = theDProps;
	mMenuType = TYPE_ADDWIDGET;
	
	local aCurY = 6;

	local aStr = "Add Widget";
	local aLabel = gApp:CreateTextWidget(DPropsTextWidget);
	aLabel:Resize((mCObj:GetWidth() - mDProps.mFont8:StringWidth(aStr))/2, aCurY, 200, 24);
	aLabel.mCObj:SetLabel(aStr);
	aLabel.mCObj:SetFont(mDProps.mFont8);
	aLabel.mCObj:SetColors({0xFFFFFF80});
	AddWidget(aLabel);
	aCurY += 16;
	
	//for aWidgetTypeName, aWidgetType in DProps.mShared.mWidgetTypes
	for (aButtonIdx < *DProps.mShared.mWidgetTypeOrder)
	{
		local aButton = gApp:CreateButtonWidget(aButtonIdx, self, AddWidgetButton);
		aButton.mPropsMenu = self;
		aButton.mWidgetTypeName = DProps.mShared.mWidgetTypeOrder[aButtonIdx];
		aButton:Resize(8, aCurY, 110, 20);
		mButtons[aButtonIdx] = aButton;
		AddWidget(aButton);
		aCurY += 22;
	}
	
	mCObj:Resize(mCObj:GetX(), mCObj:GetY(), mCObj:GetWidth(), aCurY + 6);
}

function DPropsMenu:DrawDialogFrame(g, x, y, theWidth, theHeight)
{
	g:ClipRect(0, 0, theWidth, theHeight);
	for (aRow = 0, theHeight-1, 8)
		g:DrawImage(mDProps.mDialogTex, x + aRow - (theHeight / 2) - 512, y + aRow);
	g:SetColor(Color(0, 0, 0, 128));
	g:DrawRect(x, y, theWidth-1, theHeight-1);
	g:DrawRect(x + 2, y + 2, theWidth-5, theHeight-5);
	g:SetColor(Color(255, 255, 255, 128));
	g:DrawRect(x + 1, y + 1, theWidth-3, theHeight-3);
}

function DPropsMenu:Update()
{
	for (aPropEditNum < *mPropEditList)
	{
		local aPropEdit = mPropEditList[aPropEditNum];
		if (aPropEdit.mWantUpdateText != nil)
		{
			aPropEdit:UpdateText(aPropEdit.mWantUpdateText);
			aPropEdit.mWantUpdateText = nil;
		}
	}
}

function DPropsMenu:Draw(g)
{
	DrawDialogFrame(g, 0, 0, mCObj:GetWidth(), mCObj:GetHeight());
	
	if (mColors != nil)
	{
		for (aColorNum < *mColors)
		{
			local aColorDat = mColors[aColorNum];
			
			g:SetColor(0xFF000000);
			g:DrawRect(aColorDat.mRect.mX, aColorDat.mRect.mY, aColorDat.mRect.mWidth-1, aColorDat.mRect.mHeight-1);
			
			g:SetColor(aColorDat.mColor);
			g:FillRect(aColorDat.mRect.mX+1, aColorDat.mRect.mY+1, aColorDat.mRect.mWidth-2, aColorDat.mRect.mHeight-2);
		}
	}
}

function DPropsMenu:MouseMove(x, y)
{
	if (mColors != nil)
	{
		for (aColorNum < *mColors)
		{
			if (RectContains(mColors[aColorNum].mRect, x, y))
			{
				gCApp:SetCursor(CURSOR.HAND);
				return;
			}
		}
	}
	
	gCApp:SetCursor(CURSOR.POINTER);
}

function DPropsMenu:SetColorsProp()
{
	local aPropStr = "{";
	local aPropVal = {};
	
	for (aColorNum < *mColors)
	{
		local aColor = mColors[aColorNum].mColor;
		aPropVal[aColorNum] = aColor;
		
		if (aColorNum > 0)
			aPropStr = aPropStr .. ", ";
		aPropStr = aPropStr .. string.format("%08X", aColor);
	}
	
	aPropStr = aPropStr .. "}";
	return SetProp("Colors", aPropVal);
}

function DPropsMenu:MouseDown(x, y, theBtnNum, theClickCount)
{
	super:MouseDown(x, y, theBtnNum, theClickCount);

	if (theBtnNum == 1)
	{
		mDProps.mPlacingComponent = false;
		return;
	}
	
	gCApp:GetWidgetManager():SetFocus(mCObj);
	
	if (mColors != nil)
	{
		for (aColorNum < *mColors)
		{
			if (RectContains(mColors[aColorNum].mRect, x, y))
			{
				local anOldColor = mColors[aColorNum].mColor;
				local aNewColor = mDProps.mCObj:SelectColor(anOldColor);
				if (aNewColor != anOldColor)
				{
					mColors[aColorNum].mColor = aNewColor;
					if (!SetColorsProp())
						mColors[aColorNum].mColor = anOldColor;
				}
				return;
			}
		}
	}
	
	gCApp:SetCursor(CURSOR.DRAGGING);
	
	mDragRelX = x;
	mDragRelY = y;
}

function DPropsMenu:MouseDrag(x, y)
{
	if (mDragRelX != nil)
		Resize(mCObj:GetX() + (x - mDragRelX), mCObj:GetY() + (y - mDragRelY), mCObj:GetWidth(), mCObj:GetHeight());
}

function DPropsMenu:MouseUp(x, y, theBtnNum, theClickCount)
{
	gCApp:SetCursor(CURSOR.POINTER);
	mDragRelX = nil;
	mDragRelY = nil;
}

function DPropsMenu:KeyDown(theKey)
{
	if ((mDProps.mCurMenu == nil) && (mMenuType == TYPE_ADDWIDGET) && (mDProps.mPlacingComponent) && (theKey == KEYCODE.ESCAPE))
	{
		mDProps.mPlacingComponent = false;
		return;
	}
	
	mDProps:KillMenu();

	if (theKey != KEYCODE.ESCAPE)
		mDProps:KeyDown(theKey);
}

function DPropsMenu:Resize(theX, theY, theWidth, theHeight)
{
	super:Resize(theX, theY, theWidth, theHeight);
	if ((mDProps != nil) && (mMenuType == TYPE_ADDWIDGET))
	{
		DProps.mShared.mLastAddWidgetX = theX;
		DProps.mShared.mLastAddWidgetY = theY;
		DProps.mShared:Save();
	}
}

function DPropsMenu:ButtonPress(theId)
{
	gCApp:GetWidgetManager():SetFocus(mCObj);
	DProps.mShared.mCurAddWidgetType = mButtons[theId].mWidgetTypeName;
}

function DPropsMenu:ButtonDepress(theId)
{
	if (mDProps.mSelectedObj != nil)
		mDProps.mPlacingComponent = true;
}

//////////////////////////////////////////////////////////////////////////

RegisterLinkedClass("DPropsWidget", DProps);
