//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//

#include <vgui/IBorder.h>
#include <vgui/IInputInternal.h>
#include <vgui/IPanel.h>
#include <vgui/IScheme.h>
#include <vgui/IVGui.h>
#include <vgui/KeyCode.h>
#include <KeyValues.h>
#include <vgui/MouseCode.h>
#include <vgui/ISurface.h>
#include <vgui_controls/Button.h>
#include <vgui_controls/Controls.h>
#include <vgui_controls/Label.h>
#include <vgui_controls/PropertySheet.h>
#include <vgui_controls/ComboBox.h>
#include <vgui_controls/Panel.h>
#include <vgui_controls/ToolWindow.h>
#include <vgui_controls/TextImage.h>
#include <vgui_controls/ImagePanel.h>
#include <vgui_controls/PropertyPage.h>
#include "vgui_controls/AnimationController.h"

#include "strtools_local.h" // TODO: temp

// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>

using namespace vgui2;

namespace vgui2
{
class ContextLabel : public Label
{
	DECLARE_CLASS_SIMPLE(ContextLabel, Label);

public:
	ContextLabel(Button *parent, char const *panelName, char const *text)
	    : BaseClass((Panel *)parent, panelName, text),
	      m_pTabButton(parent)
	{
		SetBlockDragChaining(true);
	}

	virtual void OnMousePressed(MouseCode code)
	{
		if(m_pTabButton)
		{
			m_pTabButton->FireActionSignal();
		}
	}

	virtual void OnMouseReleased(MouseCode code)
	{
		BaseClass::OnMouseReleased(code);

		if(GetParent())
		{
			GetParent()->OnCommand("ShowContextMenu");
		}
	}

	virtual void ApplySchemeSettings(IScheme *pScheme)
	{
		BaseClass::ApplySchemeSettings(pScheme);

		HFont marlett = pScheme->GetFont("Marlett");
		SetFont(marlett);
		SetTextInset(0, 0);
		SetContentAlignment(Label::a_northwest);

		if(GetParent())
		{
			SetFgColor(pScheme->GetColor("Button.TextColor", GetParent()->GetFgColor()));
			SetBgColor(GetParent()->GetBgColor());
		}
	}

private:
	Button *m_pTabButton;
};

//-----------------------------------------------------------------------------
// Purpose: Helper for drag drop
// Input  : msglist -
// Output : static PropertySheet
//-----------------------------------------------------------------------------
static PropertySheet *IsDroppingSheet(CUtlVector<KeyValues *> &msglist)
{
	if(msglist.Count() == 0)
		return NULL;

	KeyValues *data = msglist[0];
	PropertySheet *sheet = reinterpret_cast<PropertySheet *>(data->GetPtr("propertysheet"));
	if(sheet)
		return sheet;

	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose: A single tab
//-----------------------------------------------------------------------------
class PageTab : public Button
{
	DECLARE_CLASS_SIMPLE(PageTab, Button);

private:
	bool _active;
	SDK_Color _textColor;
	SDK_Color _dimTextColor;
	int m_bMaxTabWidth;
	IBorder *m_pActiveBorder;
	IBorder *m_pNormalBorder;
	PropertySheet *m_pParent;
	Panel *m_pPage;
	ImagePanel *m_pImage;
	char *m_pszImageName;
	bool m_bShowContextLabel;
	ContextLabel *m_pContextLabel;

public:
	PageTab(PropertySheet *parent, const char *panelName, const char *text, char const *imageName, int maxTabWidth, Panel *page, bool showContextButton)
	    : Button((Panel *)parent, panelName, text),
	      m_pParent(parent),
	      m_pPage(page),
	      m_pImage(0),
	      m_pszImageName(0),
	      m_bShowContextLabel(showContextButton)
	{
		SetCommand(new KeyValues("TabPressed"));
		_active = false;
		m_bMaxTabWidth = maxTabWidth;
		SetDropEnabled(true);
		SetDragEnabled(m_pParent->IsDraggableTab());
		if(imageName)
		{
			m_pImage = new ImagePanel(this, text);
			int buflen = Q_strlen(imageName) + 1;
			m_pszImageName = new char[buflen];
			Q_strncpy(m_pszImageName, imageName, buflen);
		}
		SetMouseClickEnabled(MOUSE_RIGHT, true);
		m_pContextLabel = m_bShowContextLabel ? new ContextLabel(this, "Context", "9") : NULL;

		REGISTER_COLOR_AS_OVERRIDABLE(_textColor, "selectedcolor");
		REGISTER_COLOR_AS_OVERRIDABLE(_dimTextColor, "unselectedcolor");
	}

	~PageTab()
	{
		delete[] m_pszImageName;
	}

	virtual void Paint()
	{
		BaseClass::Paint();
	}

	virtual bool IsDroppable(CUtlVector<KeyValues *> &msglist)
	{
		// It's never droppable, but should activate
		FireActionSignal();
		SetSelected(true);
		Repaint();
		if(!GetParent())
			return false;

		PropertySheet *sheet = IsDroppingSheet(msglist);
		if(sheet)
		{
			return GetParent()->IsDroppable(msglist);
		}

		// Defer to active page...
		Panel *active = m_pParent->GetActivePage();
		if(!active || !active->IsDroppable(msglist))
			return false;

		return active->IsDroppable(msglist);
	}

	virtual void OnDroppablePanelPaint(CUtlVector<KeyValues *> &msglist, CUtlVector<Panel *> &dragPanels)
	{
		PropertySheet *sheet = IsDroppingSheet(msglist);
		if(sheet)
		{
			Panel *target = GetParent()->GetDropTarget(msglist);
			if(target)
			{
				// Fixme, mouse pos could be wrong...
				target->OnDroppablePanelPaint(msglist, dragPanels);
				return;
			}
		}

		// Just highlight the tab if dropping onto active page via the tab
		BaseClass::OnDroppablePanelPaint(msglist, dragPanels);
	}

	virtual void OnPanelDropped(CUtlVector<KeyValues *> &msglist)
	{
		PropertySheet *sheet = IsDroppingSheet(msglist);
		if(sheet)
		{
			Panel *target = GetParent()->GetDropTarget(msglist);
			if(target)
			{
				// Fixme, mouse pos could be wrong...
				target->OnPanelDropped(msglist);
			}
		}

		// Defer to active page...
		Panel *active = m_pParent->GetActivePage();
		if(!active || !active->IsDroppable(msglist))
			return;

		active->OnPanelDropped(msglist);
	}

	virtual void OnDragFailed(CUtlVector<KeyValues *> &msglist)
	{
		PropertySheet *sheet = IsDroppingSheet(msglist);
		if(!sheet)
			return;

		// Create a new property sheet
		if(m_pParent->IsDraggableTab())
		{
			if(msglist.Count() == 1)
			{
				KeyValues *data = msglist[0];
				int screenx = data->GetInt("screenx");
				int screeny = data->GetInt("screeny");

				// m_pParent->ScreenToLocal( screenx, screeny );
				if(!m_pParent->IsWithin(screenx, screeny))
				{
					Panel *page = reinterpret_cast<Panel *>(data->GetPtr("propertypage"));
					PropertySheet *sheet = reinterpret_cast<PropertySheet *>(data->GetPtr("propertysheet"));
					char const *title = data->GetString("tabname", "");
					if(!page || !sheet)
						return;

					// Can only create if sheet was part of a ToolWindow derived object
					ToolWindow *tw = dynamic_cast<ToolWindow *>(sheet->GetParent());
					if(tw)
					{
						IToolWindowFactory *factory = tw->GetToolWindowFactory();
						if(factory)
						{
							bool hasContextMenu = sheet->PageHasContextMenu(page);
							sheet->RemovePage(page);
							factory->InstanceToolWindow(tw->GetParent(), sheet->ShouldShowContextButtons(), page, title, hasContextMenu);

							if(sheet->GetNumPages() == 0)
							{
								tw->MarkForDeletion();
							}
						}
					}
				}
			}
		}
	}

	virtual void OnCreateDragData(KeyValues *msg)
	{
		Assert(m_pParent->IsDraggableTab());

		msg->SetPtr("propertypage", m_pPage);
		msg->SetPtr("propertysheet", m_pParent);
		char sz[256];
		GetText(sz, sizeof(sz));
		msg->SetString("tabname", sz);
		msg->SetString("text", sz);
	}

	virtual void ApplySchemeSettings(IScheme *pScheme)
	{
		// set up the scheme settings
		Button::ApplySchemeSettings(pScheme);

		_textColor = GetSchemeColor("PropertySheet.SelectedTextColor", GetFgColor(), pScheme);
		_dimTextColor = GetSchemeColor("PropertySheet.TextColor", GetFgColor(), pScheme);
		m_pActiveBorder = pScheme->GetBorder("TabActiveBorder");
		m_pNormalBorder = pScheme->GetBorder("TabBorder");

		if(m_pImage)
		{
			ClearImages();
			m_pImage->SetImage(scheme()->GetImage(m_pszImageName, false));
			AddImage(m_pImage->GetImage(), 2);
			int w, h;
			m_pImage->GetSize(w, h);
			w += m_pContextLabel ? 10 : 0;
			if(m_pContextLabel)
			{
				m_pImage->SetPos(10, 0);
			}
			SetSize(w + 4, h + 2);
		}
		else
		{
			int wide, tall;
			int contentWide, contentTall;
			GetSize(wide, tall);
			GetContentSize(contentWide, contentTall);

			wide = std::max(m_bMaxTabWidth, contentWide + 10); // 10 = 5 pixels margin on each side
			wide += m_pContextLabel ? 10 : 0;
			SetSize(wide, tall);
		}

		if(m_pContextLabel)
		{
			SetTextInset(12, 0);
		}
	}

	virtual void OnCommand(char const *cmd)
	{
		if(!Q_stricmp(cmd, "ShowContextMenu"))
		{
			KeyValues *kv = new KeyValues("OpenContextMenu");
			kv->SetPtr("page", m_pPage);
			kv->SetPtr("contextlabel", m_pContextLabel);
			PostActionSignal(kv);
			return;
		}
		BaseClass::OnCommand(cmd);
	}

	IBorder *GetBorder(bool depressed, bool armed, bool selected, bool keyfocus)
	{
		if(_active)
		{
			return m_pActiveBorder;
		}
		return m_pNormalBorder;
	}

	virtual SDK_Color GetButtonFgColor()
	{
		if(_active)
		{
			return _textColor;
		}
		else
		{
			return _dimTextColor;
		}
	}

	virtual void SetActive(bool state)
	{
		_active = state;
		InvalidateLayout();
		Repaint();
	}

	virtual bool CanBeDefaultButton(void)
	{
		return false;
	}

	//Fire action signal when mouse is pressed down instead  of on release.
	virtual void OnMousePressed(MouseCode code)
	{
		// check for context menu open
		if(!IsEnabled())
			return;

		if(!IsMouseClickEnabled(code))
			return;

		if(IsUseCaptureMouseEnabled())
		{
			{
				RequestFocus();
				FireActionSignal();
				SetSelected(true);
				Repaint();
			}

			// lock mouse input to going to this button
			input()->SetMouseCapture(GetVPanel());
		}
	}

	virtual void OnMouseReleased(MouseCode code)
	{
		// ensure mouse capture gets released
		if(IsUseCaptureMouseEnabled())
		{
			input()->SetMouseCapture(NULL_HANDLE);
		}

		// make sure the button gets unselected
		SetSelected(false);
		Repaint();

		if(code == MOUSE_RIGHT)
		{
			KeyValues *kv = new KeyValues("OpenContextMenu");
			kv->SetPtr("page", m_pPage);
			kv->SetPtr("contextlabel", m_pContextLabel);
			PostActionSignal(kv);
		}
	}

	virtual void PerformLayout()
	{
		BaseClass::PerformLayout();

		if(m_pContextLabel)
		{
			int w, h;
			GetSize(w, h);
			m_pContextLabel->SetBounds(0, 0, 10, h);
		}
	}
};

}; // namespace vgui2

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
PropertySheet::PropertySheet(
Panel *parent,
const char *panelName,
bool draggableTabs /*= false*/)
    : BaseClass(parent, panelName)
{
	_activePage = NULL;
	_activeTab = NULL;
	_tabWidth = 64;
	_activeTabIndex = 0;
	_showTabs = true;
	_combo = NULL;
	_tabFocus = false;
	m_flPageTransitionEffectTime = 0.0f;
	m_bSmallTabs = false;
	m_tabFont = 0;
	m_bDraggableTabs = draggableTabs;

	if(m_bDraggableTabs)
	{
		SetDropEnabled(true);
	}

	m_bKBNavigationEnabled = true;
}

//-----------------------------------------------------------------------------
// Purpose: Constructor, associates pages with a combo box
//-----------------------------------------------------------------------------
PropertySheet::PropertySheet(Panel *parent, const char *panelName, ComboBox *combo)
    : BaseClass(parent, panelName)
{
	_activePage = NULL;
	_activeTab = NULL;
	_tabWidth = 64;
	_activeTabIndex = 0;
	_combo = combo;
	_combo->AddActionSignalTarget(this);
	_showTabs = false;
	_tabFocus = false;
	m_flPageTransitionEffectTime = 0.0f;
	m_bSmallTabs = false;
	m_tabFont = 0;
	m_bDraggableTabs = false;
}

//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
PropertySheet::~PropertySheet()
{
}

//-----------------------------------------------------------------------------
// Purpose: ToolWindow uses this to drag tools from container to container by dragging the tab
// Input  :  -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool PropertySheet::IsDraggableTab() const
{
	return m_bDraggableTabs;
}

void PropertySheet::SetDraggableTabs(bool state)
{
	m_bDraggableTabs = state;
}

//-----------------------------------------------------------------------------
// Purpose: Lower profile tabs
// Input  : state -
//-----------------------------------------------------------------------------
void PropertySheet::SetSmallTabs(bool state)
{
	m_bSmallTabs = state;
	m_tabFont = scheme()->GetIScheme(GetScheme())->GetFont(m_bSmallTabs ? "DefaultVerySmall" : "Default");
	int c = m_PageTabs.Count();
	for(int i = 0; i < c; ++i)
	{
		PageTab *tab = m_PageTabs[i];
		Assert(tab);
		tab->SetFont(m_tabFont);
	}
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  :  -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool PropertySheet::IsSmallTabs() const
{
	return m_bSmallTabs;
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  : state -
//-----------------------------------------------------------------------------
void PropertySheet::ShowContextButtons(bool state)
{
	m_bContextButton = state;
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  :  -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool PropertySheet::ShouldShowContextButtons() const
{
	return m_bContextButton;
}

int PropertySheet::FindPage(Panel *page) const
{
	int c = m_Pages.Count();
	for(int i = 0; i < c; ++i)
	{
		if(m_Pages[i].page == page)
			return i;
	}

	return m_Pages.InvalidIndex();
}

//-----------------------------------------------------------------------------
// Purpose: adds a page to the sheet
//-----------------------------------------------------------------------------
void PropertySheet::AddPage(Panel *page, const char *title, char const *imageName /*= NULL*/, bool bHasContextMenu /*= false*/)
{
	if(!page)
		return;

	// don't add the page if we already have it
	if(FindPage(page) != m_Pages.InvalidIndex())
		return;

	PageTab *tab = new PageTab(this, "tab", title, imageName, _tabWidth, page, m_bContextButton && bHasContextMenu);
	if(m_bDraggableTabs)
	{
		tab->SetDragEnabled(true);
	}

	tab->SetFont(m_tabFont);
	if(_showTabs)
	{
		tab->AddActionSignalTarget(this);
	}
	else if(_combo)
	{
		_combo->AddItem(title, NULL);
	}
	m_PageTabs.AddToTail(tab);

	Page_t info;
	info.page = page;
	info.contextMenu = m_bContextButton && bHasContextMenu;

	m_Pages.AddToTail(info);

	page->SetParent(this);
	page->AddActionSignalTarget(this);
	PostMessage(page, new KeyValues("ResetData"));

	page->SetVisible(false);
	InvalidateLayout();

	if(!_activePage)
	{
		// first page becomes the active page
		ChangeActiveTab(0);
		if(_activePage)
		{
			_activePage->RequestFocus(0);
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void PropertySheet::SetActivePage(Panel *page)
{
	// walk the list looking for this page
	int index = FindPage(page);
	if(!m_Pages.IsValidIndex(index))
		return;

	ChangeActiveTab(index);
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void PropertySheet::SetTabWidth(int pixels)
{
	_tabWidth = pixels;
	InvalidateLayout();
}

//-----------------------------------------------------------------------------
// Purpose: reloads the data in all the property page
//-----------------------------------------------------------------------------
void PropertySheet::ResetAllData()
{
	// iterate all the dialogs resetting them
	for(int i = 0; i < m_Pages.Count(); i++)
	{
		ipanel()->SendMessage(m_Pages[i].page->GetVPanel(), new KeyValues("ResetData"), GetVPanel());
	}
}

//-----------------------------------------------------------------------------
// Purpose: Applies any changes made by the dialog
//-----------------------------------------------------------------------------
void PropertySheet::ApplyChanges()
{
	// iterate all the dialogs resetting them
	for(int i = 0; i < m_Pages.Count(); i++)
	{
		ipanel()->SendMessage(m_Pages[i].page->GetVPanel(), new KeyValues("ApplyChanges"), GetVPanel());
	}
}

//-----------------------------------------------------------------------------
// Purpose: gets a pointer to the currently active page
//-----------------------------------------------------------------------------
Panel *PropertySheet::GetActivePage()
{
	return _activePage;
}

//-----------------------------------------------------------------------------
// Purpose: gets a pointer to the currently active tab
//-----------------------------------------------------------------------------
Panel *PropertySheet::GetActiveTab()
{
	return _activeTab;
}

//-----------------------------------------------------------------------------
// Purpose: returns the number of panels in the sheet
//-----------------------------------------------------------------------------
int PropertySheet::GetNumPages()
{
	return m_Pages.Count();
}

//-----------------------------------------------------------------------------
// Purpose: returns the name contained in the active tab
// Input  : a text buffer to contain the output
//-----------------------------------------------------------------------------
void PropertySheet::GetActiveTabTitle(char *textOut, int bufferLen)
{
	if(_activeTab)
		_activeTab->GetText(textOut, bufferLen);
}

//-----------------------------------------------------------------------------
// Purpose: returns the name contained in the active tab
// Input  : a text buffer to contain the output
//-----------------------------------------------------------------------------
bool PropertySheet::GetTabTitle(int i, char *textOut, int bufferLen)
{
	if(i < 0 && i > m_PageTabs.Count())
	{
		return false;
	}

	m_PageTabs[i]->GetText(textOut, bufferLen);
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Returns the index of the currently active page
//-----------------------------------------------------------------------------
int PropertySheet::GetActivePageNum()
{
	for(int i = 0; i < m_Pages.Count(); i++)
	{
		if(m_Pages[i].page == _activePage)
		{
			return i;
		}
	}
	return -1;
}

//-----------------------------------------------------------------------------
// Purpose: Forwards focus requests to current active page
//-----------------------------------------------------------------------------
void PropertySheet::RequestFocus(int direction)
{
	if(direction == -1 || direction == 0)
	{
		if(_activePage)
		{
			_activePage->RequestFocus(direction);
			_tabFocus = false;
		}
	}
	else
	{
		if(_showTabs && _activeTab)
		{
			_activeTab->RequestFocus(direction);
			_tabFocus = true;
		}
		else if(_activePage)
		{
			_activePage->RequestFocus(direction);
			_tabFocus = false;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: moves focus back
//-----------------------------------------------------------------------------
bool PropertySheet::RequestFocusPrev(VPANEL panel)
{
	if(_tabFocus || !_showTabs || !_activeTab)
	{
		_tabFocus = false;
		return BaseClass::RequestFocusPrev(panel);
	}
	else
	{
		if(GetVParent())
		{
			PostMessage(GetVParent(), new KeyValues("FindDefaultButton"));
		}
		_activeTab->RequestFocus(-1);
		_tabFocus = true;
		return true;
	}
}

//-----------------------------------------------------------------------------
// Purpose: moves focus forward
//-----------------------------------------------------------------------------
bool PropertySheet::RequestFocusNext(VPANEL panel)
{
	if(!_tabFocus || !_activePage)
	{
		return BaseClass::RequestFocusNext(panel);
	}
	else
	{
		if(!_activeTab)
		{
			return BaseClass::RequestFocusNext(panel);
		}
		else
		{
			_activePage->RequestFocus(1);
			_tabFocus = false;
			return true;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Gets scheme settings
//-----------------------------------------------------------------------------
void PropertySheet::ApplySchemeSettings(IScheme *pScheme)
{
	BaseClass::ApplySchemeSettings(pScheme);

	// a little backwards-compatibility with old scheme files
	IBorder *pBorder = pScheme->GetBorder("PropertySheetBorder");
	if(pBorder == pScheme->GetBorder("Default"))
	{
		// get the old name
		pBorder = pScheme->GetBorder("RaisedBorder");
	}

	SetBorder(pBorder);
	m_flPageTransitionEffectTime = atof(pScheme->GetResourceString("PropertySheet.TransitionEffectTime"));

	m_tabFont = pScheme->GetFont(m_bSmallTabs ? "DefaultVerySmall" : "Default");
}

//-----------------------------------------------------------------------------
// Purpose: Paint our border specially, with the tabs in mind
//-----------------------------------------------------------------------------
void PropertySheet::PaintBorder()
{
	IBorder *border = GetBorder();
	if(!border)
		return;

	// draw the border, but with a break at the active tab
	int px = 0, py = 0, pwide = 0, ptall = 0;
	if(_activeTab)
	{
		_activeTab->GetBounds(px, py, pwide, ptall);
		ptall -= 1;
	}

	// draw the border underneath the buttons, with a break
	int wide, tall;
	GetSize(wide, tall);
	border->Paint(0, py + ptall, wide, tall, IBorder::SIDE_TOP, px + 1, px + pwide - 1);
}

//-----------------------------------------------------------------------------
// Purpose: Lays out the dialog
//-----------------------------------------------------------------------------
void PropertySheet::PerformLayout()
{
	BaseClass::PerformLayout();

	int x, y, wide, tall;
	GetBounds(x, y, wide, tall);
	if(_activePage)
	{
		int tabHeight = IsSmallTabs() ? 14 : 28;

		if(_showTabs)
		{
			_activePage->SetBounds(0, tabHeight, wide, tall - tabHeight);
		}
		else
		{
			_activePage->SetBounds(0, 0, wide, tall);
		}
		_activePage->InvalidateLayout();
	}

	int xtab;
	int limit = m_PageTabs.Count();

	xtab = 0;

	// draw the visible tabs
	if(_showTabs)
	{
		for(int i = 0; i < limit; i++)
		{
			int tabHeight = IsSmallTabs() ? 13 : 27;

			int width, tall;
			m_PageTabs[i]->GetSize(width, tall);
			if(m_PageTabs[i] == _activeTab)
			{
				// active tab is taller
				_activeTab->SetBounds(xtab, 2, width, tabHeight);
			}
			else
			{
				m_PageTabs[i]->SetBounds(xtab, 4, width, tabHeight - 2);
			}
			m_PageTabs[i]->SetVisible(true);
			xtab += (width + 1);
		}
	}
	else
	{
		for(int i = 0; i < limit; i++)
		{
			m_PageTabs[i]->SetVisible(false);
		}
	}

	// ensure draw order (page drawing over all the tabs except one)
	if(_activePage)
	{
		_activePage->MoveToFront();
		_activePage->Repaint();
	}
	if(_activeTab)
	{
		_activeTab->MoveToFront();
		_activeTab->Repaint();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Switches the active panel
//-----------------------------------------------------------------------------
void PropertySheet::OnTabPressed(Panel *panel)
{
	// look for the tab in the list
	for(int i = 0; i < m_PageTabs.Count(); i++)
	{
		if(m_PageTabs[i] == panel)
		{
			// flip to the new tab
			ChangeActiveTab(i);
			return;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: returns the panel associated with index i
// Input  : the index of the panel to return
//-----------------------------------------------------------------------------
Panel *PropertySheet::GetPage(int i)
{
	if(i < 0 && i > m_Pages.Count())
	{
		return NULL;
	}

	return m_Pages[i].page;
}

//-----------------------------------------------------------------------------
// Purpose: disables page by name
//-----------------------------------------------------------------------------
void PropertySheet::DisablePage(const char *title)
{
	SetPageEnabled(title, false);
}

//-----------------------------------------------------------------------------
// Purpose: enables page by name
//-----------------------------------------------------------------------------
void PropertySheet::EnablePage(const char *title)
{
	SetPageEnabled(title, true);
}

//-----------------------------------------------------------------------------
// Purpose: enabled or disables page by name
//-----------------------------------------------------------------------------
void PropertySheet::SetPageEnabled(const char *title, bool state)
{
	for(int i = 0; i < m_PageTabs.Count(); i++)
	{
		if(_showTabs)
		{
			char tmp[50];
			m_PageTabs[i]->GetText(tmp, 50);
			if(!strnicmp(title, tmp, strlen(tmp)))
			{
				m_PageTabs[i]->SetEnabled(state);
			}
		}
		else
		{
			_combo->SetItemEnabled(title, state);
		}
	}
}

void PropertySheet::RemoveAllPages()
{
	int c = m_Pages.Count();
	for(int i = c - 1; i >= 0; --i)
	{
		RemovePage(m_Pages[i].page);
	}
}

//-----------------------------------------------------------------------------
// Purpose: deletes the page associated with panel
// Input  : *panel - the panel of the page to remove
//-----------------------------------------------------------------------------
void PropertySheet::RemovePage(Panel *panel)
{
	int location = FindPage(panel);
	if(location == m_Pages.InvalidIndex())
		return;

	// Since it's being deleted, don't animate!!!
	m_hPreviouslyActivePage = NULL;
	_activeTab = NULL;

	// ASSUMPTION = that the number of pages equals number of tabs
	if(_showTabs)
	{
		m_PageTabs[location]->RemoveActionSignalTarget(this);
	}
	// now remove the tab
	PageTab *tab = m_PageTabs[location];
	m_PageTabs.Remove(location);
	tab->MarkForDeletion();

	// Remove from page list
	m_Pages.Remove(location);

	// Unparent
	panel->SetParent((Panel *)NULL);

	if(_activePage == panel)
	{
		_activePage = NULL;
		// if this page is currently active, backup to the page before this.
		ChangeActiveTab(std::max(location - 1, 0));
	}

	PerformLayout();
}

//-----------------------------------------------------------------------------
// Purpose: deletes the page associated with panel
// Input  : *panel - the panel of the page to remove
//-----------------------------------------------------------------------------
void PropertySheet::DeletePage(Panel *panel)
{
	Assert(panel);
	RemovePage(panel);
	panel->MarkForDeletion();
}

//-----------------------------------------------------------------------------
// Purpose: flips to the new tab, sending out all the right notifications
//			flipping to a tab activates the tab.
//-----------------------------------------------------------------------------
void PropertySheet::ChangeActiveTab(int index)
{
	if(!m_Pages.IsValidIndex(index))
	{
		_activeTab = NULL;
		if(m_Pages.Count() > 0)
		{
			_activePage = NULL;
			ChangeActiveTab(0);
		}
		return;
	}

	if(m_Pages[index].page == _activePage)
	{
		if(_activeTab)
		{
			_activeTab->RequestFocus();
		}
		_tabFocus = true;
		return;
	}

	int c = m_Pages.Count();
	for(int i = 0; i < c; ++i)
	{
		m_Pages[i].page->SetVisible(false);
	}

	m_hPreviouslyActivePage = _activePage;
	// notify old page
	if(_activePage)
	{
		ivgui()->PostMessage(_activePage->GetVPanel(), new KeyValues("PageHide"), GetVPanel());
		KeyValues *msg = new KeyValues("PageTabActivated");
		msg->SetPtr("panel", (Panel *)NULL);
		ivgui()->PostMessage(_activePage->GetVPanel(), msg, GetVPanel());
	}
	if(_activeTab)
	{
		//_activeTabIndex=index;
		_activeTab->SetActive(false);

		// does the old tab have the focus?
		_tabFocus = _activeTab->HasFocus();
	}
	else
	{
		_tabFocus = false;
	}

	// flip page
	_activePage = m_Pages[index].page;
	_activeTab = m_PageTabs[index];
	_activeTabIndex = index;

	_activePage->SetVisible(true);
	_activePage->MoveToFront();

	_activeTab->SetVisible(true);
	_activeTab->MoveToFront();
	_activeTab->SetActive(true);

	if(_tabFocus)
	{
		// if a tab already has focused,give the new tab the focus
		_activeTab->RequestFocus();
	}
	else
	{
		// otherwise, give the focus to the page
		_activePage->RequestFocus();
	}

	if(!_showTabs)
	{
		_combo->ActivateItemByRow(index);
	}

	_activePage->MakeReadyForUse();

	// transition effect
	if(m_flPageTransitionEffectTime)
	{
		if(m_hPreviouslyActivePage.Get())
		{
			// fade out the previous page
			GetAnimationController()->RunAnimationCommand(m_hPreviouslyActivePage, "Alpha", 0.0f, 0.0f, m_flPageTransitionEffectTime / 2, AnimationController::INTERPOLATOR_LINEAR);
		}

		// fade in the new page
		_activePage->SetAlpha(0);
		GetAnimationController()->RunAnimationCommand(_activePage, "Alpha", 255.0f, m_flPageTransitionEffectTime / 2, m_flPageTransitionEffectTime / 2, AnimationController::INTERPOLATOR_LINEAR);
	}
	else
	{
		if(m_hPreviouslyActivePage.Get())
		{
			// no transition, just hide the previous page
			m_hPreviouslyActivePage->SetVisible(false);
		}
		_activePage->SetAlpha(255);
	}

	// notify
	ivgui()->PostMessage(_activePage->GetVPanel(), new KeyValues("PageShow"), GetVPanel());

	KeyValues *msg = new KeyValues("PageTabActivated");
	msg->SetPtr("panel", (Panel *)_activeTab);
	ivgui()->PostMessage(_activePage->GetVPanel(), msg, GetVPanel());

	// tell parent
	PostActionSignal(new KeyValues("PageChanged"));

	// Repaint
	InvalidateLayout();
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: Gets the panel with the specified hotkey, from the current page
//-----------------------------------------------------------------------------
Panel *PropertySheet::HasHotkey(wchar_t key)
{
	if(!_activePage)
		return NULL;

	for(int i = 0; i < _activePage->GetChildCount(); i++)
	{
		Panel *hot = _activePage->GetChild(i)->HasHotkey(key);
		if(hot)
		{
			return hot;
		}
	}

	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose: catches the opencontextmenu event
//-----------------------------------------------------------------------------
void PropertySheet::OnOpenContextMenu(KeyValues *params)
{
	// tell parent
	KeyValues *kv = params->MakeCopy();
	PostActionSignal(kv);
	Panel *page = reinterpret_cast<Panel *>(params->GetPtr("page"));
	if(page)
	{
		PostMessage(page->GetVPanel(), params->MakeCopy());
	}
}

//-----------------------------------------------------------------------------
// Purpose: Handle key presses, through tabs.
//-----------------------------------------------------------------------------
void PropertySheet::OnKeyCodeTyped(KeyCode code)
{
	bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT));
	bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL));
	bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT));

	if(ctrl && shift && alt && code == KEY_B)
	{
		// enable build mode
		EditablePanel *ep = dynamic_cast<EditablePanel *>(GetActivePage());
		if(ep)
		{
			ep->ActivateBuildMode();
			return;
		}
	}

	if(IsKBNavigationEnabled())
	{
		switch(code)
		{
		// for now left and right arrows just open or close submenus if they are there.
		case KEY_RIGHT:
		{
			ChangeActiveTab(_activeTabIndex + 1);
			break;
		}
		case KEY_LEFT:
		{
			ChangeActiveTab(_activeTabIndex - 1);
			break;
		}
		default:
			BaseClass::OnKeyCodeTyped(code);
			break;
		}
	}
	else
	{
		BaseClass::OnKeyCodeTyped(code);
	}
}

//-----------------------------------------------------------------------------
// Purpose: Called by the associated combo box (if in that mode), changes the current panel
//-----------------------------------------------------------------------------
void PropertySheet::OnTextChanged(Panel *panel, const wchar_t *wszText)
{
	if(panel == _combo)
	{
		wchar_t tabText[30];
		for(int i = 0; i < m_PageTabs.Count(); i++)
		{
			tabText[0] = 0;
			m_PageTabs[i]->GetText(tabText, 30);
			if(!wcsicmp(wszText, tabText))
			{
				ChangeActiveTab(i);
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void PropertySheet::OnCommand(const char *command)
{
	// propogate the close command to our parent
	if(!stricmp(command, "Close") && GetVParent())
	{
		CallParentFunction(new KeyValues("Command", "command", command));
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void PropertySheet::OnApplyButtonEnable()
{
	// tell parent
	PostActionSignal(new KeyValues("ApplyButtonEnable"));
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void PropertySheet::OnCurrentDefaultButtonSet(Panel *defaultButton)
{
	// forward the message up
	if(GetVParent())
	{
		KeyValues *msg = new KeyValues("CurrentDefaultButtonSet");
		msg->SetPtr("button", defaultButton);
		PostMessage(GetVParent(), msg);
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void PropertySheet::OnDefaultButtonSet(Panel *defaultButton)
{
	// forward the message up
	if(GetVParent())
	{
		KeyValues *msg = new KeyValues("DefaultButtonSet");
		msg->SetPtr("button", defaultButton);
		PostMessage(GetVParent(), msg);
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void PropertySheet::OnFindDefaultButton()
{
	if(GetVParent())
	{
		PostMessage(GetVParent(), new KeyValues("FindDefaultButton"));
	}
}

bool PropertySheet::PageHasContextMenu(Panel *page) const
{
	int pageNum = FindPage(page);
	if(pageNum == m_Pages.InvalidIndex())
		return false;

	return m_Pages[pageNum].contextMenu;
}

void PropertySheet::OnPanelDropped(CUtlVector<KeyValues *> &msglist)
{
	if(msglist.Count() != 1)
	{
		return;
	}

	PropertySheet *sheet = IsDroppingSheet(msglist);
	if(!sheet)
	{
		// Defer to active page
		if(_activePage && _activePage->IsDropEnabled())
		{
			return _activePage->OnPanelDropped(msglist);
		}
		return;
	}

	KeyValues *data = msglist[0];

	Panel *page = reinterpret_cast<Panel *>(data->GetPtr("propertypage"));
	char const *title = data->GetString("tabname", "");
	if(!page || !sheet)
		return;

	// Can only create if sheet was part of a ToolWindow derived object
	ToolWindow *tw = dynamic_cast<ToolWindow *>(sheet->GetParent());
	if(tw)
	{
		IToolWindowFactory *factory = tw->GetToolWindowFactory();
		if(factory)
		{
			bool showContext = sheet->PageHasContextMenu(page);
			sheet->RemovePage(page);
			if(sheet->GetNumPages() == 0)
			{
				tw->MarkForDeletion();
			}

			AddPage(page, title, NULL, showContext);
		}
	}
}

bool PropertySheet::IsDroppable(CUtlVector<KeyValues *> &msglist)
{
	if(!m_bDraggableTabs)
		return false;

	if(msglist.Count() != 1)
	{
		return false;
	}

	int mx, my;
	input()->GetCursorPos(mx, my);
	ScreenToLocal(mx, my);

	int tabHeight = IsSmallTabs() ? 14 : 28;
	if(my > tabHeight)
		return false;

	PropertySheet *sheet = IsDroppingSheet(msglist);
	if(!sheet)
	{
		return false;
	}

	if(sheet == this)
		return false;

	return true;
}

// Mouse is now over a droppable panel
void PropertySheet::OnDroppablePanelPaint(CUtlVector<KeyValues *> &msglist, CUtlVector<Panel *> &dragPanels)
{
	// Convert this panel's bounds to screen space
	int x, y, w, h;

	GetSize(w, h);

	int tabHeight = IsSmallTabs() ? 14 : 28;
	h = tabHeight + 4;

	x = y = 0;
	LocalToScreen(x, y);

	surface()->DrawSetColor(GetDropFrameColor());
	// Draw 2 pixel frame
	surface()->DrawOutlinedRect(x, y, x + w, y + h);
	surface()->DrawOutlinedRect(x + 1, y + 1, x + w - 1, y + h - 1);

	if(!IsDroppable(msglist))
	{
		return;
	}

	if(!_showTabs)
	{
		return;
	}

	// Draw a fake new tab...

	x = 0;
	y = 2;
	w = 1;
	h = tabHeight;

	int last = m_PageTabs.Count();
	if(last != 0)
	{
		m_PageTabs[last - 1]->GetBounds(x, y, w, h);
	}

	// Compute left edge of "fake" tab

	x += (w + 1);

	// Compute size of new panel
	KeyValues *data = msglist[0];
	char const *text = data->GetString("tabname", "");
	Assert(text);

	PageTab *fakeTab = new PageTab(this, "FakeTab", text, NULL, _tabWidth, NULL, false);
	fakeTab->SetBounds(x, 4, w, tabHeight - 4);
	fakeTab->SetFont(m_tabFont);
	SETUP_PANEL(fakeTab);
	fakeTab->Repaint();
	surface()->SolveTraverse(fakeTab->GetVPanel(), true);
	surface()->PaintTraverse(fakeTab->GetVPanel());
	delete fakeTab;
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  : state -
//-----------------------------------------------------------------------------
void PropertySheet::SetKBNavigationEnabled(bool state)
{
	m_bKBNavigationEnabled = state;
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  :  -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool PropertySheet::IsKBNavigationEnabled() const
{
	return m_bKBNavigationEnabled;
}
