/////////////////////////////////////////////////////////////////////////////
// Name:        MenuObject.cpp
// Purpose:     The class to store a DVD Menu Object
// Author:      Alex Thuering
// Created:	04.11.2006
// RCS-ID:      $Id: MenuObject.cpp,v 1.19 2009/12/30 16:56:48 ntalex Exp $
// Copyright:   (c) Alex Thuering
// Licence:     GPL
/////////////////////////////////////////////////////////////////////////////

#include "DVD.h"
#include "Menu.h"
#include <wxVillaLib/utils.h>
#include <wxSVG/svg.h>
#include <wxSVGXML/svgxmlhelpr.h>
#include <wx/mstream.h>
#include <wx/filename.h>

#define BUTTONS_DIR wxFindDataDirectory(_T("buttons"))
#define OBJECTS_DIR wxFindDataDirectory(_T("objects"))

///////////////////////////// MenuObject /////////////////////////////////////

MenuObject::MenuObject(Menu* menu, bool vmg, wxString fileName, int x, int y, wxString param):
		m_action(vmg, BUTTON_ACTION) {
	if (menu == NULL) {
		m_deleteSVG = true;
		m_svg = Menu::CreateSVG(400, 400);
		wxSVGRectElement* bgRect = new wxSVGRectElement;
		bgRect->SetWidth(400);
		bgRect->SetHeight(400);
		bgRect->SetFill(wxSVGPaint(*wxBLACK));
		m_svg->GetRootElement()->InsertChild(bgRect, m_svg->GetRootElement()->GetChildren());
	} else {
		m_deleteSVG = false;
		m_svg = menu->GetSVG();
	}
	m_use = NULL;
	m_symbol = NULL;
	
	m_fileName = fileName;
	m_previewHighlighted = false;

	m_button = false;

	m_defaultSize = true;
	m_defaultWidth.value = m_defaultHeight.value = 0;
	m_defaultWidth.valueInc = m_defaultHeight.valueInc = 0;
	m_defaultWidth.valuePercent = m_defaultHeight.valuePercent = 0;
	m_minWidth.value = m_minHeight.value = 0;
	m_minWidth.valueInc = m_minHeight.valueInc = 0;
	m_minWidth.valuePercent = m_minHeight.valuePercent = 0;

	if (fileName.length())
		Init(fileName, x, y, param);
}

MenuObject::~MenuObject() {
	WX_CLEAR_ARRAY(m_params)
	if (m_use)
		m_use->GetParent()->RemoveChild(m_use);
	if (m_symbol)
		m_symbol->GetParent()->RemoveChild(m_symbol);
	if (m_deleteSVG)
		delete m_svg;
}

bool MenuObject::Init(wxString fileName, int x, int y, wxString param) {
	m_fileName = fileName;
	wxSvgXmlDocument xml;
	xml.Load(fileName);
	if (!xml.GetRoot())
		return false;
	wxSvgXmlNode* root = xml.GetRoot();

	m_button = root->GetName() == wxT("button");
	m_previewHighlighted = root->GetAttribute(wxT("previewHighlighted")) == wxT("true");
	m_title = XmlReadValue(root, wxT("title"));
	
	if (m_id.length()) { // load button
		m_symbol = (wxSVGSVGElement*) m_svg->GetElementById(wxT("s_") + m_id);
		m_use = (wxSVGUseElement*) m_svg->GetElementById(m_id);

		// fix old format (1.5b1 - 1.5b3)
		if (m_symbol && m_symbol->GetDtd() == wxSVG_SYMBOL_ELEMENT) {
			// replace symbol element with svg element
			wxSVGSymbolElement* symbol = (wxSVGSymbolElement*) m_symbol;
			m_symbol = new wxSVGSVGElement();
			m_symbol->SetId(symbol->GetId());
			m_symbol->SetViewBox(symbol->GetViewBox());
			m_symbol->SetPreserveAspectRatio(symbol->GetPreserveAspectRatio());
			wxSvgXmlElement* child = symbol->GetChildren();
			while (child) {
				m_symbol->AddChild(child->CloneNode());
				child = child->GetNext();
			}
			wxSvgXmlElement* parent = symbol->GetParent();
			parent->RemoveChild(symbol);
			parent->AddChild(m_symbol);
			// remove id prefix
			child = m_symbol->GetChildren();
			while (child) {
				wxSVGElement& elem = *(wxSVGElement*) child;
				if (elem.GetId().length())
					elem.SetId(elem.GetId().AfterFirst(wxT('_')).AfterFirst(wxT('_')));
				child = child->GetNext();
			}
		}
	} else
		// create new button
		m_id = GenerateId(m_button ? wxT("button") : wxT("obj"));

	wxSvgXmlNode* svgNode = XmlFindNode(root, wxT("svg"));
	if (svgNode) {
		wxSVGDocument svg;
		LoadSVG(svg, svgNode);
		wxSVGSVGElement* root = svg.GetRootElement();
		if (root) {
			m_defaultWidth.value = root->GetWidth().GetBaseVal();
			m_defaultHeight.value = root->GetHeight().GetBaseVal();
			if (!m_symbol)
				AddSymol(m_id, root);
			if (!m_use)
				AddUse(m_id, x, y, m_defaultWidth.value, m_defaultHeight.value);
		}
	}

	// load parameters 
	wxSvgXmlNode* paramsNode = XmlFindNode(root, wxT("parameters"));
	if (paramsNode) {
		wxSvgXmlNode* child = paramsNode->GetChildren();
		while (child) {
			if (child->GetType() == wxSVGXML_ELEMENT_NODE && child->GetName() == wxT("parameter")) {
				MenuObjectParam* param = new MenuObjectParam;
				param->title = XmlReadValue(child, wxT("title"));
				param->name = XmlReadValue(child, wxT("name"));
				param->type = XmlReadValue(child, wxT("type"));
				param->element = XmlReadValue(child, wxT("element"));
				param->attribute = XmlReadValue(child, wxT("attribute"));
				param->changeable = XmlFindNode(child, wxT("changeable")) != NULL && param->type == wxT("colour");
				m_params.Add(param);
				if (param->changeable) {
					param->changeable = false;
					param->normalColour = GetParamColour(param->name);
					wxCSSStyleDeclaration style;
					style.SetProperty(wxCSS_PROPERTY_STROKE, XmlReadValue(child, wxT("default-value/highlighted")));
					param->highlightedColour = style.GetStroke().GetRGBColor();
					style.SetProperty(wxCSS_PROPERTY_STROKE, XmlReadValue(child, wxT("default-value/selected")));
					param->selectedColour = style.GetStroke().GetRGBColor();
					param->changeable = true;
				}
			}
			child = child->GetNext();
		}
	}

	m_initParameter = XmlReadValue(root, wxT("init-parameter"));
	// set initial parameter value
	if (m_initParameter.length() && param.length())
		SetParam(m_initParameter, param);
	
	// load default size
	wxSvgXmlNode* defaultSizeNode = XmlFindNode(root, wxT("default-size"));
	if (defaultSizeNode) {
		wxString sizeElem = XmlReadValue(defaultSizeNode, wxT("element"));
		if (sizeElem.length() > 0) {
			m_defaultWidth.elements.Add(sizeElem);
			m_defaultHeight.elements.Add(sizeElem);
		}
		InitSize(XmlReadValue(defaultSizeNode, wxT("width")), m_defaultWidth);
		InitSize(XmlReadValue(defaultSizeNode, wxT("height")), m_defaultHeight);
	}

	// load min size
	wxSvgXmlNode* minSizeNode = XmlFindNode(root, wxT("min-size"));
	if (minSizeNode) {
		wxString sizeElem = XmlReadValue(minSizeNode, wxT("element"));
		if (sizeElem.length() > 0) {
			m_minWidth.elements.Add(sizeElem);
			m_minHeight.elements.Add(sizeElem);
		}
		InitSize(XmlReadValue(minSizeNode, wxT("width")), m_minWidth);
		InitSize(XmlReadValue(minSizeNode, wxT("height")), m_minHeight);
	}

	UpdateSize();

	return true;
}

void MenuObject::InitSize(wxString value, MenuObjectSize& size) {
	long lval;
	if (value.Index(wxT('|'))) {
		value.BeforeFirst(wxT('|')).ToLong(&lval);
		size.value = lval;
		value = value.AfterFirst(wxT('|'));
	}
	while (value.Length() > 0) {
		wxString val = value.BeforeFirst(wxT('+'));
		if (val.Last() == wxT('%') && val.SubString(0, val.length()-2).ToLong(&lval))
			size.valuePercent += lval;
		else if (val.ToLong(&lval))
			size.valueInc += lval;
		else
			size.elements.Add(val);
		value = value.AfterFirst(wxT('+'));
	}
}

bool MenuObject::LoadSVG(wxSVGDocument& svg, wxSvgXmlNode* node) {
	bool res;
	wxSvgXmlDocument xml;
	xml.SetRoot(node->CloneNode());
	wxMemoryOutputStream output;
	xml.Save(output);
	wxMemoryInputStream input(output);
	res = svg.Load(input);
	return res;
}

wxString MenuObject::GenerateId(wxString prefix) {
	int i = 1;
	while (1) {
		wxString id = prefix + wxString::Format(wxT("%02d"), i);
		if (m_svg->GetElementById(id) == NULL)
			return id;
		i++;
	}
	return wxT("");
}

MenuObjectParam* MenuObject::GetObjectParam(wxString name) {
	for (int i = 0; i < (int) m_params.Count(); i++) {
		if (m_params[i]->name == name)
			return m_params[i];
	}
	return NULL;
}

MenuObjectParam* MenuObject::GetInitParam() {
	if (!m_initParameter.length())
		return NULL;
	return GetObjectParam(m_initParameter);
}

wxSVGSVGElement* MenuObject::AddSymol(wxString id, wxSVGElement* content) {
	m_symbol = new wxSVGSVGElement;
	m_symbol->SetId(wxT("s_") + id);
	m_svg->GetElementById(wxT("defs"))->AppendChild(m_symbol);
	if (content->GetDtd() == wxSVG_SVG_ELEMENT) {
		m_symbol->SetViewBox(((wxSVGSVGElement*) content)->GetViewBox());
		m_symbol->SetPreserveAspectRatio(((wxSVGSVGElement*) content)->GetPreserveAspectRatio());
	} else if (content->GetDtd() == wxSVG_SYMBOL_ELEMENT) {
		m_symbol->SetViewBox(((wxSVGSymbolElement*) content)->GetViewBox());
		m_symbol->SetPreserveAspectRatio(((wxSVGSymbolElement*) content)->GetPreserveAspectRatio());
	}
	wxSvgXmlElement* child = content->GetChildren();
	while (child) {
		m_symbol->AppendChild(((wxSVGSVGElement*) child)->CloneNode());
		child = child->GetNext();
	}
	return m_symbol;
}

void MenuObject::AddUse(wxString id, int x, int y, int width, int height) {
	m_use = new wxSVGUseElement;
	m_use->SetId(id);
	m_use->SetHref(wxT("#s_") + id);
	m_use->SetX(x);
	m_use->SetY(y);
	m_use->SetWidth(width);
	m_use->SetHeight(height);
	if (IsButton())
		m_svg->GetElementById(wxT("buttons"))->AppendChild(m_use);
	else
		m_svg->GetElementById(wxT("objects"))->AppendChild(m_use);
}

wxString MenuObject::GetId(bool translate) {
	if (!translate)
		return m_id;
	long l = 0;
	m_id.Mid(IsButton() ? 6 : 3).ToLong(&l);
	return (IsButton() ? _("button") : _("object")) + wxString::Format(
			wxT(" %d"), l);
}

int MenuObject::GetX() {
	return m_use->GetX().GetBaseVal();
}
void MenuObject::SetX(int value) {
	m_use->SetX(value);
}

int MenuObject::GetY() {
	return m_use->GetY().GetBaseVal();
}
void MenuObject::SetY(int value) {
	m_use->SetY(value);
}

int MenuObject::GetWidth() {
	return m_use->GetWidth().GetBaseVal();
}
void MenuObject::SetWidth(int value) {
	m_use->SetWidth(value);
}

int MenuObject::GetHeight() {
	return m_use->GetHeight().GetBaseVal();
}
void MenuObject::SetHeight(int value) {
	m_use->SetHeight(value);
}

unsigned int MenuObject::CalcSize(MenuObjectSize& size, bool width) {
	unsigned int result = 0;
	for (unsigned int idx = 0; idx < size.elements.GetCount(); idx++) {
		wxSVGElement* elem = (wxSVGElement*) m_symbol->GetElementById(size.elements[idx]);
		if (elem) {
			if (elem->GetDtd() == wxSVG_IMAGE_ELEMENT) {
				wxSVGImageElement* imgElem = (wxSVGImageElement*) elem;
				result += width ? imgElem->GetDefaultWidth() : imgElem->GetDefaultHeight();
			} else {
				wxSVGRect bbox = wxSVGLocatable::GetElementResultBBox(elem);
				result += width ? (unsigned int) bbox.GetWidth() : (unsigned int) bbox.GetHeight();
			}
		}
	}
	if (size.valuePercent > 0)
		result += (result * size.valuePercent + 99) / 100;
	result += size.valueInc;
	return wxMax(result, size.value);
}

void MenuObject::FixSize(int& width, int& height) {
	if (m_defaultSize) {
		width = CalcSize(m_defaultWidth, true);
		height = CalcSize(m_defaultHeight, false);
	} else {
		width = wxMax(width, (int) CalcSize(m_minWidth, true));
		height = wxMax(height, (int) CalcSize(m_minHeight, false));
	}
	if (width % 2 == 1)
		width++;
	if (height % 2 == 1)
		height++;
}

void MenuObject::UpdateSize() {
	int width = GetWidth();
	int height = GetHeight();
	FixSize(width, height);
	SetWidth(width);
	SetHeight(height);
}

wxString MenuObject::GetParam(wxString name, wxString attrSuffix) {
	MenuObjectParam* param = GetObjectParam(name);
	if (!param)
		return wxT("");
	wxSVGElement* elem = (wxSVGElement*) m_symbol->GetElementById(
			param->element);
	if (!elem)
		return wxT("");
	if (param->attribute.length()) {
		// return attribute value
		if (param->attribute.Find(wxT('#')) > 0) {
			wxString value = elem->GetAttribute(param->attribute.BeforeFirst(wxT('#')));
			long n = 0;
			param->attribute.AfterFirst(wxT('#')).ToLong(&n);
			if (n > 0 && value.length() > 0 && value.Index(wxT('(')) > 0) {
				// return n-parameter of the function (e.g. if you want to get rotation angle of transform)
				value = value.AfterFirst(wxT('(')).BeforeFirst(wxT(')'));
				for (int i = 1; i < n; i++)
					value = value.AfterFirst(wxT(','));
				value = value.BeforeFirst(wxT(','));
				return value.Trim().Trim(false);
			}
		}
		return elem->GetAttribute(param->attribute + attrSuffix);
	} else if (elem->GetDtd() == wxSVG_TEXT_ELEMENT) {
		// return content of text element
		return elem->GetChildren() ? elem->GetChildren()->GetContent() : wxT("");
	}
	return wxT("");
}

void MenuObject::SetParam(wxString name, wxString value, wxString attrSuffix) {
	MenuObjectParam* param = GetObjectParam(name);
	if (!param)
		return;
	wxSVGElement* elem = (wxSVGElement*) m_symbol->GetElementById(
			param->element);
	if (!elem)
		return;
	if (param->attribute.length()) {
		if (param->attribute.Find(wxT('#')) > 0) {
			// sets n-parameter of the function (e.g. if you want to set rotation angle of transform)
			wxString oldValue = elem->GetAttribute(param->attribute.BeforeFirst(wxT('#')));
			long n = 0;
			param->attribute.AfterFirst(wxT('#')).ToLong(&n);
			if (n > 0 && oldValue.length() > 0 && oldValue.Index(wxT('(')) > 0) {
				wxString newValue = oldValue.BeforeFirst(wxT('(')) + wxT('(');
				for (int i = 1; i < n; i++)
					newValue += oldValue.BeforeFirst(wxT(',')) + wxT(',');
				newValue += value;
				if (oldValue.Index(wxT(')')) < oldValue.Index(wxT(',')) && oldValue.Index(wxT(')')) > 0)
					newValue += wxT(')') + oldValue.AfterFirst(wxT(')'));
				else
					newValue += wxT(',') + oldValue.AfterFirst(wxT(','));
				value = newValue;
			}
			elem->SetAttribute(param->attribute.BeforeFirst(wxT('#')), value);
		} else
			elem->SetAttribute(param->attribute + attrSuffix, value);
	} else if (elem->GetDtd() == wxSVG_TEXT_ELEMENT) {
		if (!elem->GetChildren())
			return;
		elem->GetChildren()->SetContent(value);
		((wxSVGTextElement*) elem)->SetCanvasItem(NULL);
	}
}

int MenuObject::GetParamInt(wxString name) {
	long lval = 0;
	GetParam(name).ToLong(&lval);
	return lval;
}

void MenuObject::SetParamInt(wxString name, int value) {
	SetParam(name, wxString::Format(wxT("%d"), value));
}

double MenuObject::GetParamDouble(wxString name) {
	double dval = 0;
	GetParam(name).ToDouble(&dval);
	return dval;
}

void MenuObject::SetParamDouble(wxString name, double value) {
	SetParam(name, wxString::Format(wxT("%g"), value));
}

wxFont MenuObject::GetParamFont(wxString name) {
	MenuObjectParam* param = GetObjectParam(name);
	if (!param)
		return wxFont(20, wxFONTFAMILY_DEFAULT, wxNORMAL, wxNORMAL, false);
	wxSVGElement* elem = (wxSVGElement*) m_symbol->GetElementById(
			param->element);
	if (!elem)
		return wxFont(20, wxFONTFAMILY_DEFAULT, wxNORMAL, wxNORMAL, false);

	int size = 20;
	double dval;
	if (elem->GetAttribute(wxT("font-size")).ToDouble(&dval))
		size = (int) dval;

	int style = wxFONTSTYLE_NORMAL;
	wxString styleStr = elem->GetAttribute(wxT("font-style"));
	if (styleStr == wxT("italic"))
		style = wxFONTSTYLE_ITALIC;
	else if (styleStr == wxT("oblique"))
		style = wxFONTSTYLE_SLANT;

	wxFontWeight weight = wxFONTWEIGHT_NORMAL;
	wxString weightStr = elem->GetAttribute(wxT("font-weight"));
	if (weightStr == wxT("bold"))
		weight = wxFONTWEIGHT_BOLD;
	if (weightStr == wxT("bolder"))
		weight = wxFONTWEIGHT_MAX;
	else if (weightStr == wxT("lighter"))
		weight = wxFONTWEIGHT_LIGHT;

	wxString faceName = elem->GetAttribute(wxT("font-family"));

	return wxFont(size, wxFONTFAMILY_DEFAULT, style, weight, false, faceName);
}

void MenuObject::SetParamFont(wxString name, wxFont value) {
	MenuObjectParam* param = GetObjectParam(name);
	if (!param)
		return;
	wxSVGElement* elem = (wxSVGElement*) m_symbol->GetElementById(
			param->element);
	if (!elem)
		return;

	elem->SetAttribute(wxT("font-size"), wxString::Format(wxT("%d"),
			value.GetPointSize()));

	wxString styleStr = wxT("normal");
	if (value.GetStyle() == wxFONTSTYLE_ITALIC)
		styleStr = wxT("italic");
	else if (value.GetStyle() == wxFONTSTYLE_SLANT)
		styleStr = wxT("oblique");
	elem->SetAttribute(wxT("font-style"), styleStr);

	wxString weightStr = wxT("normal");
	if (value.GetWeight() == wxFONTWEIGHT_BOLD)
		weightStr = wxT("bold");
	if (value.GetWeight() == wxFONTWEIGHT_MAX)
		weightStr = wxT("bolder");
	else if (value.GetWeight() == wxFONTWEIGHT_LIGHT)
		weightStr = wxT("lighter");
	elem->SetAttribute(wxT("font-weight"), weightStr);

	elem->SetAttribute(wxT("font-family"), value.GetFaceName());
}

wxColour MenuObject::GetParamColour(wxString name, MenuButtonState state) {
	MenuObjectParam* param = GetObjectParam(name);
	if (!param)
		return wxT("");
	if (param->changeable) {
		switch (state) {
		case mbsNORMAL:
			return param->normalColour;
		case mbsHIGHLIGHTED:
			return param->highlightedColour;
		case mbsSELECTED:
			return param->selectedColour;
		}
	}
	wxSVGElement* elem = (wxSVGElement*) m_symbol->GetElementById(
			param->element);
	if (!elem)
		return wxT("");
	if (param->attribute.length()) {
		const wxCSSStyleDeclaration& style = wxSVGStylable::GetElementStyle(*elem);
		const wxCSSValue& value = style.GetPropertyCSSValue(param->attribute);
		switch (value.GetCSSValueType()) {
		case wxCSS_PRIMITIVE_VALUE:
			return ((wxCSSPrimitiveValue&) value).GetRGBColorValue();
		case wxCSS_SVG_COLOR:
		case wxCSS_SVG_PAINT:
			return ((wxSVGColor&) value).GetRGBColor();
		default:
			break;
		}
	}
	return wxColour();
}

void MenuObject::SetParamColour(wxString name, wxColour value,
		MenuButtonState state) {
	MenuObjectParam* param = GetObjectParam(name);
	if (!param)
		return;
	if (param->changeable) {
		switch (state) {
		case mbsNORMAL:
			param->normalColour = value;
			break;
		case mbsHIGHLIGHTED:
			param->highlightedColour = value;
			break;
		case mbsSELECTED:
			param->selectedColour = value;
			break;
		}
	}
	if (state != mbsNORMAL)
		return;
	wxSVGElement* elem = (wxSVGElement*) m_symbol->GetElementById(
			param->element);
	if (!elem)
		return;
	if (param->attribute.length()) {
		wxSVGPaint paint(value);
		elem->SetAttribute(param->attribute, paint.GetCSSText());
	}
}

void MenuObject::ToFront() {
	wxSVGElement* parent = (wxSVGElement*) m_use->GetParent();
	parent->RemoveChild(m_use);
	parent->AppendChild(m_use);
}

void MenuObject::Forward() {
	wxSVGElement* parent = (wxSVGElement*) m_use->GetParent();
	wxSVGElement* next = (wxSVGElement*) m_use->GetNextSibling();
	if (next && next->GetType() == wxSVGXML_ELEMENT_NODE && next->GetDtd() == wxSVG_USE_ELEMENT) {
		parent->RemoveChild(m_use);
		parent->InsertChild(m_use, next->GetNextSibling());
	}
}

void MenuObject::Backward() {
	wxSVGElement* parent = (wxSVGElement*) m_use->GetParent();
	wxSVGElement* prev = (wxSVGElement*) m_use->GetPreviousSibling();
	if (prev && prev->GetType() == wxSVGXML_ELEMENT_NODE && prev->GetDtd() == wxSVG_USE_ELEMENT) {
		parent->RemoveChild(m_use);
		parent->InsertChild(m_use, prev);
	}
}

void MenuObject::ToBack() {
	wxSVGElement* parent = (wxSVGElement*) m_use->GetParent();
	wxSVGElement* first = (wxSVGElement*) parent->GetFirstChild();
	while (first && (first->GetType() != wxSVGXML_ELEMENT_NODE
			|| first->GetDtd() != wxSVG_USE_ELEMENT))
		first = (wxSVGElement*) first->GetNextSibling();
	if (first && first != m_use) {
		parent->RemoveChild(m_use);
		parent->InsertChild(m_use, first);
	}
}

bool MenuObject::IsFirst() {
	wxSVGElement* prev = (wxSVGElement*) m_use->GetPreviousSibling();
	return !prev || prev->GetType() != wxSVGXML_ELEMENT_NODE;
}

bool MenuObject::IsLast() {
	wxSVGElement* next = (wxSVGElement*) m_use->GetNextSibling();
	return !next || next->GetType() != wxSVGXML_ELEMENT_NODE;
}

wxImage MenuObject::GetImage(int maxWidth, int maxHeight) {
	if (!m_use)
		return wxImage();
	if (m_previewHighlighted) {
		for (int i = 0; i < GetObjectParamsCount(); i++) {
			MenuObjectParam* param = GetObjectParam(i);
			if (param->changeable) {
				wxSVGElement* elem = (wxSVGElement*) m_symbol->GetElementById(param->element);
				if (elem && param->attribute.length()) {
					wxSVGPaint paint(param->highlightedColour);
					elem->SetAttribute(param->attribute, paint.GetCSSText());
				}
			}
		}
	}
	m_svg->GetRootElement()->SetWidth(GetWidth());
	m_svg->GetRootElement()->SetHeight(GetHeight());
	return m_svg->Render(maxWidth, maxHeight);
}

wxSvgXmlNode* MenuObject::GetXML(DVDFileType type, DVD* dvd, bool withSVG, int playAllRegister) {
	wxString rootName = wxT("object");
	if (IsButton())
		rootName = wxT("button");
	wxSvgXmlNode* rootNode = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, rootName);
	switch (type) {
	case DVDSTYLER_XML:
		if (IsButton())
			rootNode->AddChild(m_action.GetXML(type, dvd));
		if (GetDirection(0).length() || GetDirection(1).length()
				|| GetDirection(2).length() || GetDirection(3).length()) {
			wxSvgXmlNode* directionNode = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, _T("direction"));
			if (GetDirection(0).length())
				directionNode->AddProperty(_T("left"), GetDirection(0));
			if (GetDirection(1).length())
				directionNode->AddProperty(_T("right"), GetDirection(1));
			if (GetDirection(2).length())
				directionNode->AddProperty(_T("up"), GetDirection(2));
			if (GetDirection(3).length())
				directionNode->AddProperty(_T("down"), GetDirection(3));
			rootNode->AddChild(directionNode);
		}
		XmlWriteValue(rootNode, _T("filename"), GetFileName().AfterLast(wxFILE_SEP_PATH));
		for (int i = 0; i < GetObjectParamsCount(); i++) {
			MenuObjectParam* param = GetObjectParam(i);
			if (param->changeable) {
				wxSvgXmlNode* paramNode = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, _T("parameter"));
				paramNode->AddProperty(_T("name"), param->name);
				paramNode->AddProperty(_T("normal"), wxSVGPaint(param->normalColour).GetCSSText());
				paramNode->AddProperty(_T("highlighted"), wxSVGPaint(param->highlightedColour).GetCSSText());
				paramNode->AddProperty(_T("selected"), wxSVGPaint(param->selectedColour).GetCSSText());
				rootNode->AddChild(paramNode);
			}
		}
		rootNode->AddProperty(_T("id"), GetId());
		if (IsDefaultSize())
			rootNode->AddProperty(_T("defSize"), _T("true"));
		break;
	case SPUMUX_XML: {
		int mwidth = (int) m_svg->GetRootElement()->GetWidth().GetBaseVal() - 1;
		int mheight = (int) m_svg->GetRootElement()->GetHeight().GetBaseVal() - 1;
		rootNode->AddProperty(_T("name"), GetId());
		rootNode->AddProperty(_T("x0"), wxString::Format(_T("%d"), GetX() > 0 ? GetX() : 0));
		rootNode->AddProperty(_T("y0"), wxString::Format(_T("%d"), GetY() > 0 ? GetY() : 0));
		rootNode->AddProperty(_T("x1"), wxString::Format(_T("%d"),
				GetX() + GetWidth() < mwidth ? GetX() + GetWidth() : mwidth));
		rootNode->AddProperty(_T("y1"), wxString::Format(_T("%d"),
				GetY() + GetHeight() < mheight ? GetY() + GetHeight() : mheight));
		if (GetDirection(0).length())
			rootNode->AddProperty(_T("left"), GetDirection(0));
		if (GetDirection(1).length())
			rootNode->AddProperty(_T("right"), GetDirection(1));
		if (GetDirection(2).length())
			rootNode->AddProperty(_T("up"), GetDirection(2));
		if (GetDirection(3).length())
			rootNode->AddProperty(_T("down"), GetDirection(3));
		break;
	}
	case DVDAUTHOR_XML: {
		rootNode->AddProperty(_T("name"), GetId());
		wxString action = GetAction().AsString(dvd);
		action.Replace(wxT("vmMenu"), wxT("vmgm menu"));
		if (playAllRegister != -1)
			action = wxString::Format(wxT("g%d=%d;"), playAllRegister, GetAction().IsPlayAll()) + action;
		rootNode->AddChild(new wxSvgXmlNode(wxSVGXML_TEXT_NODE, wxEmptyString, action));
		break;
	}
	default:
		break;
	}
	if (withSVG) {  // used by copy & paste
		rootNode->DeleteProperty(wxT("id"));
		wxSvgXmlNode* svgNode = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("svg"));
		wxSvgXmlNode* defsNode = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("defs"));
		wxSVGElement* symbol = (wxSVGElement*) m_symbol->CloneNode();
		symbol->SetId(wxT(""));
		defsNode->AddChild(symbol);
		svgNode->AddChild(defsNode);
		svgNode->AddChild(m_use->CloneNode());
		rootNode->AddChild(svgNode);
	}
	return rootNode;
}

bool MenuObject::PutXML(wxSvgXmlNode* node) {
	m_button = node->GetName() == wxT("button");

	long lval;
	wxString val;

	if (IsButton()) {
		wxSvgXmlNode* actionNode = XmlFindNodeSimple(node, _T("action"));
		if (actionNode)
			m_action.PutXML(actionNode);

		wxSvgXmlNode* dirNode = XmlFindNodeSimple(node, _T("direction"));
		if (dirNode) {
			if (dirNode->GetPropVal(_T("left"), &val))
				SetDirection(0, val);
			if (dirNode->GetPropVal(_T("right"), &val))
				SetDirection(1, val);
			if (dirNode->GetPropVal(_T("up"), &val))
				SetDirection(2, val);
			if (dirNode->GetPropVal(_T("down"), &val))
				SetDirection(3, val);
		}
	}

	// deprecated
	int x = 0, y = 0;
	if (node->GetPropVal(_T("x"), &val) && val.ToLong(&lval))
		x = (int) (lval / m_svg->GetScale());
	if (node->GetPropVal(_T("y"), &val) && val.ToLong(&lval))
		y = (int) (lval / m_svg->GetScale());
	wxString fileName = XmlReadValue(node, _T("type"));
	if (fileName.length()) {
		wxString text = XmlReadValue(node, _T("text"));
		return Init(BUTTONS_DIR + wxT("text.xml"), x, y, text);
	}

	fileName = XmlReadValue(node, _T("filename"));
	if (!fileName.length())
		return false;

	wxFileName fn(fileName);
	wxString dir = IsButton() ? BUTTONS_DIR : OBJECTS_DIR;
	if (fn.IsRelative()) {
		if (wxFileName::FileExists(dir + fileName))
			fileName = dir + fileName;
		else if (wxFileName::FileExists(dir + wxT("deprecated") + wxFILE_SEP_PATH + fileName))
			fileName = dir + wxT("deprecated") + wxFILE_SEP_PATH + fileName;
		else if (wxFileName::FileExists(dir + fn.GetFullName()))
			fileName = dir + fn.GetFullName();
		else {
			wxLogError(_("can't open file '%s'"), fileName.c_str());
			return false;
		}
	} else if (!wxFileExists(fileName)) {
		if (wxFileName::FileExists(dir + fn.GetFullName()))
			fileName = dir + fn.GetFullName();
		else if (wxFileName::FileExists(dir + wxT("deprecated") + wxFILE_SEP_PATH + fn.GetFullName()))
			fileName = dir + wxT("deprecated") + wxFILE_SEP_PATH + fn.GetFullName();
		else {
			wxLogError(_("can't open file '%s'"), fileName.c_str());
			return false;
		}
	}

	node->GetPropVal(_T("id"), &m_id);
	if (!node->GetPropVal(_T("defSize"), &val) || (val != wxT("true") && val != wxT("1")))
		SetDefaultSize(false);

	// paste button
	wxSvgXmlNode* svgNode = XmlFindNode(node, wxT("svg"));
	if (svgNode) {
		m_id = GenerateId(m_button ? wxT("button") : wxT("obj"));
		wxSVGDocument svg;
		LoadSVG(svg, svgNode);
		wxSVGSVGElement* root = svg.GetRootElement();
		if (root && XmlFindNode(root, wxT("defs")) && XmlFindNode(root, wxT("use"))) {
			wxSVGElement* defsNode = (wxSVGElement*) XmlFindNode(root, wxT("defs"));
			wxSvgXmlElement* child = defsNode->GetChildren();
			while (child) {
				AddSymol(m_id, (wxSVGElement*) child);
				child = child->GetNext();
			}
			wxSVGUseElement* useElem = (wxSVGUseElement*) XmlFindNode(root, wxT("use"));
			AddUse(m_id, useElem->GetX().GetBaseVal(),
					useElem->GetY().GetBaseVal(),
					useElem->GetWidth().GetBaseVal(),
					useElem->GetHeight().GetBaseVal());
		}
	}

	if (!Init(fileName, x, y))
		return false;

	wxSvgXmlNode* child = node->GetChildren();
	while (child) {
		if (child->GetName() == wxT("parameter")) {
			wxString name;
			wxString normal;
			wxString selected;
			wxString highlighted;
			if (child->GetPropVal(wxT("name"), &name)
					&& child->GetPropVal(wxT("normal"), &normal)
					&& child->GetPropVal(wxT("highlighted"), &highlighted)
					&& child->GetPropVal(wxT("selected"), &selected)) {
				wxCSSStyleDeclaration style;
				style.SetProperty(wxT("fill"), normal);
				SetParamColour(name, style.GetFill().GetRGBColor(), mbsNORMAL);
				style.SetProperty(wxT("fill"), highlighted);
				SetParamColour(name, style.GetFill().GetRGBColor(), mbsHIGHLIGHTED);
				style.SetProperty(wxT("fill"), selected);
				SetParamColour(name, style.GetFill().GetRGBColor(), mbsSELECTED);
			}
		}
		child = child->GetNext();
	}

	return true;
}
