/////////////////////////////////////////////////////////////////////////////
// Name:        MenuEditor.cpp
// Purpose:     The widget to edit a DVD Menu
// Author:      Alex Thuering
// Created:     11.10.2003
// RCS-ID:      $Id: MenuEditor.cpp,v 1.39 2010/02/22 22:47:18 ntalex Exp $
// Copyright:   (c) Alex Thuering
// Licence:     GPL
/////////////////////////////////////////////////////////////////////////////

#include "MenuEditor.h"
#include "MenuPropDlg.h"
#include "MenuObjectPropDlg.h"
#include "MPEG.h"
#include "TitlesetManager.h"
#include "Utils.h"
#include "Config.h"
#include <wxVillaLib/ImageProc.h>
#include <wxVillaLib/Thumbnails.h>
#include <wxVillaLib/utils.h>
#include <wxSVG/svg.h>
#include <wxSVGXML/svgxml.h>
#include "math.h"
#include <wx/dnd.h>
#include <wx/utils.h>
#include <wx/clipbrd.h>
#include <wx/artprov.h>
#include <wx/filename.h>

#define OBJECTS_DIR wxFindDataDirectory(_T("objects"))
#define BUTTONS_DIR wxFindDataDirectory(_T("buttons"))
#define CURSOR_FILE(fname) wxFindDataFile(_T("rc") + wxString(wxFILE_SEP_PATH) + fname)

#if !wxCHECK_VERSION(2,9,0)
#define wxBITMAP_TYPE_ANY -1
#endif

//////////////////////////////////////////////////////////////////////////////
////////////////////////////// MenuDnDFile ///////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

class MenuDnDFile : public wxDropTarget {
public:
	MenuDnDFile(MenuEditor* menuEditor) {
		m_menuEditor = menuEditor;
		wxDataFormat actionDataFormat(DATAFORMAT_ACTION);
		wxDataObjectComposite* dataObject = new wxDataObjectComposite();
		dataObject->Add(m_fileDataObject = new wxFileDataObject);
		dataObject->Add(m_actionDataObject = new wxCustomDataObject(actionDataFormat));
		SetDataObject(dataObject);
	}
	
	wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult def) {
		if (!GetData())
			return wxDragNone;
		
		wxDataObjectComposite* dobj = (wxDataObjectComposite*) m_dataObject;
		
		if (dobj->GetReceivedFormat() == wxDF_FILENAME) {
			return OnDropFiles( x, y, m_fileDataObject->GetFilenames() ) ? def : wxDragNone;
		} else if (dobj->GetReceivedFormat().GetId() == DATAFORMAT_ACTION) {
			wxString actionStr((const char*) m_actionDataObject->GetData(), wxConvISO8859_1, m_actionDataObject->GetSize());
			DVDAction action(false, MENU_ACTION);
			action.Deserialize(actionStr);
			AddButton(x, y, action.GetTsi(), action.GetPgci(), action.IsMenu());
			return def;
		}
		
		return wxDragNone;
	}

	bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) {
		if (!m_menuEditor->GetMenu())
			return false;
		bool res = false;
		for (int i = 0; i<(int)filenames.Count(); i++) {
			res = AddFile(x, y, filenames[i]) || res;
			if (x < m_menuEditor->GetMenu()->GetResolution().x-48
					&& y < m_menuEditor->GetMenu()->GetResolution().y-48) {
				x += 16;
				y += 16;
			}
		}
		return res;
	}

	bool AddFile(wxCoord x, wxCoord y, wxString filename) {
		wxString ext = filename.AfterLast('.').Lower();
		if (ext == _T("xml"))
			m_menuEditor->AddButton(filename, (int) (x/m_menuEditor->GetScale()), (int) (y/m_menuEditor->GetScale()));
		else if (wxImage::FindHandler(ext, wxBITMAP_TYPE_ANY))
			m_menuEditor->AddImage(filename, (int) (x/m_menuEditor->GetScale()), (int) (y/m_menuEditor->GetScale()));
		else if (wxThumbnails::IsVideo(filename)) {
			wxFfmpegMediaDecoder decoder;
			if (!decoder.Load(filename)) {
				wxLogError(filename + _(": unknown format"));
				return false;
			}
			// try to find this file
			bool found = false;
			for (unsigned int tsi = 0; tsi < m_menuEditor->GetDVD()->GetTitlesets().GetCount(); tsi++) {
				Titleset& ts = *m_menuEditor->GetDVD()->GetTitlesets()[tsi];
				for (unsigned int pgci = 0; pgci < ts.GetTitles().GetCount(); pgci++) {
					Pgc& pgc = *ts.GetTitles()[pgci];
					for (unsigned int vobi = 0; vobi < pgc.GetVobs().GetCount(); vobi++) {
						Vob& vob = *pgc.GetVobs()[vobi];
						if (vob.GetFilename() == filename) {
							found = true;
							AddButton(x, y, tsi, pgci);
							break;
						}
					}
				}
			}
			if (!found) {
				// add file to DVD
				int tsi = m_menuEditor->GetTsi();
				if (tsi == -1)
					tsi = m_menuEditor->GetDVD()->GetTitlesets().size() - 1;
				if (tsi == -1)
					tsi = 0;
				if (m_menuEditor->GetTitlesetManager()->AddVideo(filename, true, tsi)) {
					int pgci = m_menuEditor->GetDVD()->GetPgcArray(tsi, false).GetCount()-1;
					AddButton(x, y, tsi, pgci);
				}
			}
		}
		m_menuEditor->SetFocus();
		return true;
	}

	void AddButton(wxCoord x, wxCoord y, int tsi, int pgci, bool isMenu = false) {
		wxString objId = m_menuEditor->HitTest(x, y);
		x = (int) (x/m_menuEditor->GetScale());
		y = (int) (y/m_menuEditor->GetScale());
		if (objId.length() && m_menuEditor->GetMenu()->GetObject(objId)->IsButton()) {
			MenuObject* obj = m_menuEditor->GetMenu()->GetObject(objId);
			MenuObjectParam* initParam = obj->GetInitParam();
			if (initParam && initParam->type == wxT("image")) {
				Vob* vob = m_menuEditor->GetDVD()->GetVob(tsi, false, pgci, 0);
				if (vob) {
					obj->SetParam(initParam->name, vob->GetVideoFrameUri(0));
					m_menuEditor->Select(objId);
				}
			}
			obj->GetAction().SetTsi(tsi);
			obj->GetAction().SetPgci(pgci);
			obj->GetAction().SetMenu(isMenu);
		} else {
			if (isMenu)
				m_menuEditor->AddButton(BUTTONS_DIR + s_config.GetDefTextButton(), x, y, pgci, tsi, isMenu);
			else
				m_menuEditor->AddButton(BUTTONS_DIR + s_config.GetDefButton(), x, y, pgci, tsi, isMenu);
		}
	}

private:
	MenuEditor *m_menuEditor;
	wxFileDataObject* m_fileDataObject;
	wxCustomDataObject* m_actionDataObject;
};

//////////////////////////////////////////////////////////////////////////////
/////////////////////////// Help functions ///////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

wxRect ScaleRect(wxRect rect, double scale)
{
  wxRect res;
  res.x = (int) (rect.x*scale);  
  res.y = (int) (rect.y*scale);
  res.width = (int) (rect.width*scale);
  res.height = (int) (rect.height*scale);
  return res;
}

wxRect ScaleRect(wxSVGRect rect, double scale)
{
  wxRect res;
  res.x = (int) (rect.GetX()*scale);  
  res.y = (int) (rect.GetY()*scale);
  res.width = (int) (rect.GetWidth()*scale);
  res.height = (int) (rect.GetHeight()*scale);
  return res;
}

//////////////////////////////////////////////////////////////////////////////
/////////////////////////////// MenuEditor ///////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

enum {
	EVENT_TIMER_ID = 8500,
	MENUEDITOR_PROP_ID,
	MENUEDITOR_OBJMENU_CUT_ID,
	MENUEDITOR_OBJMENU_COPY_ID,
	MENUEDITOR_OBJMENU_PASTE_ID,
	MENUEDITOR_OBJMENU_DELETE_ID,
	MENUEDITOR_OBJMENU_FIRSTBUTTON_ID,
	MENUEDITOR_ARRANGEMENU_ID,
	MENUEDITOR_ARRANGEMENU_TOFRONT_ID,
	MENUEDITOR_ARRANGEMENU_FORWARD_ID,
	MENUEDITOR_ARRANGEMENU_BACKWARD_ID,
	MENUEDITOR_ARRANGEMENU_TOBACK_ID,
	MENUEDITOR_ALIGNMENU_ID,
	MENUEDITOR_ALIGNMENU_TOCENTERH_ID,
	MENUEDITOR_ALIGNMENU_TOCENTERV_ID,
	MENUEDITOR_VIEWMENU_ID,
	MENUEDITOR_VIEWMENU_SAFETV_ID,
	MENUEDITOR_GRIDMENU_ID = 8600,
	MENUEDITOR_GRIDMENU_ITEM_ID, //8601..8605
	MENUEDITOR_GRIDMENU_SHOW_ID = 8610,
	MENUEDITOR_GRIDMENU_OVEROBJECTS_ID,
	MENUEDITOR_ADDMENU_ID = 9000,
	MENUEDITOR_ADDMENU_OBJECT_ID //9001..9999
};

BEGIN_EVENT_TABLE(MenuEditor, wxSVGCtrl)
  EVT_RIGHT_DOWN(MenuEditor::OnMouseRightButton)
  EVT_RIGHT_UP(MenuEditor::OnMouseRightButton)
  EVT_LEFT_DOWN(MenuEditor::OnMouseLeftButton)
  EVT_LEFT_UP(MenuEditor::OnMouseLeftButton)
  EVT_MOTION(MenuEditor::OnMouseMove)
  EVT_LEFT_DCLICK(MenuEditor::OnMouseDClick)
  EVT_KEY_DOWN(MenuEditor::OnKeyDown)
  EVT_MENU(MENUEDITOR_PROP_ID, MenuEditor::OnProperties)
  EVT_MENU(MENUEDITOR_OBJMENU_CUT_ID, MenuEditor::OnObjectCut)
  EVT_MENU(MENUEDITOR_OBJMENU_COPY_ID, MenuEditor::OnObjectCopy)
  EVT_MENU(MENUEDITOR_OBJMENU_PASTE_ID, MenuEditor::OnObjectPaste)
  EVT_UPDATE_UI(MENUEDITOR_OBJMENU_PASTE_ID, MenuEditor::OnUpdateUIObjectPaste)
  EVT_MENU(MENUEDITOR_OBJMENU_FIRSTBUTTON_ID, MenuEditor::OnFirstButton)
  EVT_UPDATE_UI(MENUEDITOR_OBJMENU_FIRSTBUTTON_ID, MenuEditor::OnUpdateUIFirstButton)
  EVT_MENU(MENUEDITOR_OBJMENU_DELETE_ID, MenuEditor::OnObjectDelete)
  EVT_MENU(MENUEDITOR_ARRANGEMENU_TOFRONT_ID, MenuEditor::OnObjectToFront)
  EVT_MENU(MENUEDITOR_ARRANGEMENU_FORWARD_ID, MenuEditor::OnObjectForward)
  EVT_MENU(MENUEDITOR_ARRANGEMENU_BACKWARD_ID, MenuEditor::OnObjectBackward)
  EVT_MENU(MENUEDITOR_ARRANGEMENU_TOBACK_ID, MenuEditor::OnObjectToBack)
  EVT_UPDATE_UI(MENUEDITOR_ARRANGEMENU_TOFRONT_ID, MenuEditor::OnUpdateUIObjectForward)
  EVT_UPDATE_UI(MENUEDITOR_ARRANGEMENU_FORWARD_ID, MenuEditor::OnUpdateUIObjectForward)
  EVT_UPDATE_UI(MENUEDITOR_ARRANGEMENU_BACKWARD_ID, MenuEditor::OnUpdateUIObjectBackward)
  EVT_UPDATE_UI(MENUEDITOR_ARRANGEMENU_TOBACK_ID, MenuEditor::OnUpdateUIObjectBackward)
  EVT_MENU(MENUEDITOR_ALIGNMENU_TOCENTERH_ID, MenuEditor::OnAlignToCenterH)
  EVT_MENU(MENUEDITOR_ALIGNMENU_TOCENTERV_ID, MenuEditor::OnAlignToCenterV)
  EVT_MENU(MENUEDITOR_VIEWMENU_SAFETV_ID, MenuEditor::OnSafeTV)
  EVT_MENU(MENUEDITOR_GRIDMENU_ITEM_ID, MenuEditor::OnGridMenu)
  EVT_MENU(MENUEDITOR_GRIDMENU_ITEM_ID+1, MenuEditor::OnGridMenu)
  EVT_MENU(MENUEDITOR_GRIDMENU_ITEM_ID+2, MenuEditor::OnGridMenu)
  EVT_MENU(MENUEDITOR_GRIDMENU_ITEM_ID+3, MenuEditor::OnGridMenu)
  EVT_MENU(MENUEDITOR_GRIDMENU_ITEM_ID+4, MenuEditor::OnGridMenu)
  EVT_MENU(MENUEDITOR_GRIDMENU_ITEM_ID+5, MenuEditor::OnGridMenu)
  EVT_MENU(MENUEDITOR_GRIDMENU_SHOW_ID, MenuEditor::OnShowGrid)
  EVT_MENU(MENUEDITOR_GRIDMENU_OVEROBJECTS_ID, MenuEditor::OnShowGridOverObjects)
  EVT_MENU_RANGE(MENUEDITOR_ADDMENU_OBJECT_ID, MENUEDITOR_ADDMENU_OBJECT_ID+999, MenuEditor::OnAddObject)
END_EVENT_TABLE()

DEFINE_EVENT_TYPE(EVT_COMMAND_MENUEDITOR_MENU_CHANGED)
DEFINE_EVENT_TYPE(EVT_COMMAND_MENUEDITOR_OBJECT_POINTED)

MenuEditor::MenuEditor(wxWindow *parent, wxWindowID id): wxSVGCtrl(parent, id) {
	m_dvd = NULL;
	m_menu = NULL;
	objectIsMoving = false;
	DoSelect();
	m_safeTV = true;
	m_showGrid = false;
	m_gridOverObejcts = false;
	m_grid = 3;
	SetDropTarget(new MenuDnDFile(this));
	SetFitToFrame(true);
	
	// sub menu "Arrange"
	m_arrangeMenu = new wxMenu;
	m_arrangeMenu->Append(MENUEDITOR_ARRANGEMENU_TOFRONT_ID, _("&Bring to Front"));
	m_arrangeMenu->Append(MENUEDITOR_ARRANGEMENU_FORWARD_ID, _("Bring &Forward"));
	m_arrangeMenu->Append(MENUEDITOR_ARRANGEMENU_BACKWARD_ID, _("Send Back&ward"));
	m_arrangeMenu->Append(MENUEDITOR_ARRANGEMENU_TOBACK_ID, _("&Send to Back"));

	// sub menu "Align"
	m_alignMenu = new wxMenu;
	m_alignMenu->Append(MENUEDITOR_ALIGNMENU_TOCENTERH_ID, _("Center &Horizontally"));
	m_alignMenu->Append(MENUEDITOR_ALIGNMENU_TOCENTERV_ID, _("Center &Vertically"));

	// sub menu "Add"
	m_addMenu = new wxMenu;
	wxString fname = wxFindFirstFile(OBJECTS_DIR + _T("*.xml"));
	int addId = MENUEDITOR_ADDMENU_OBJECT_ID;
	while (!fname.IsEmpty()) {
		MenuObject obj(NULL, false, fname);
		m_addMenu->Append(addId++, wxGetTranslation((const wxChar*)obj.GetTitle().GetData()));
		m_addMenuObjects.Add(obj.GetFileName());
		fname = wxFindNextFile();
	}
	
	// sub menu "View"
	m_viewMenu = new wxMenu;
	m_viewMenu->Append(MENUEDITOR_VIEWMENU_SAFETV_ID, _("&Show Safe TV area"), wxEmptyString, wxITEM_CHECK);
	m_viewMenu->Check(MENUEDITOR_VIEWMENU_SAFETV_ID, m_safeTV);
	
	// sub menu "Grid"
	m_gridMenu = new wxMenu;
	m_gridMenu->Append(MENUEDITOR_GRIDMENU_ITEM_ID, _("&none"), wxEmptyString, wxITEM_RADIO);
	m_gridMenu->Append(MENUEDITOR_GRIDMENU_ITEM_ID+1, _T("4x4"), wxEmptyString, wxITEM_RADIO);
	m_gridMenu->Append(MENUEDITOR_GRIDMENU_ITEM_ID+2, _T("8x8"), wxEmptyString, wxITEM_RADIO);
	m_gridMenu->Append(MENUEDITOR_GRIDMENU_ITEM_ID+3, _T("16x16"), wxEmptyString, wxITEM_RADIO);
	m_gridMenu->Append(MENUEDITOR_GRIDMENU_ITEM_ID+4, _T("32x32"), wxEmptyString, wxITEM_RADIO);
	m_gridMenu->Append(MENUEDITOR_GRIDMENU_ITEM_ID+5, _T("64x64"), wxEmptyString, wxITEM_RADIO);
	m_gridMenu->Check(MENUEDITOR_GRIDMENU_ITEM_ID + m_grid - 1, true);
	m_gridMenu->Append(MENUEDITOR_GRIDMENU_SHOW_ID, _("&Show"), wxEmptyString, wxITEM_CHECK);
	m_gridMenu->Append(MENUEDITOR_GRIDMENU_OVEROBJECTS_ID, _("&Over objects"), wxEmptyString, wxITEM_CHECK);
	
	// object context menu
	m_objMenu = new wxMenu;
	m_objMenu->Append(MENUEDITOR_ARRANGEMENU_ID, _("A&rrange"), m_arrangeMenu);
	m_objMenu->Append(MENUEDITOR_ALIGNMENU_ID, _("A&lign"), m_alignMenu);
	m_objMenu->Append(MENUEDITOR_ADDMENU_ID, _("&Add"), m_addMenu);
	m_objMenu->Append(MENUEDITOR_VIEWMENU_ID, _("&View"), m_viewMenu);
	m_objMenu->Append(MENUEDITOR_GRIDMENU_ID, _("&Grid"), m_gridMenu);
	m_objMenu->AppendCheckItem(MENUEDITOR_OBJMENU_FIRSTBUTTON_ID, _("&First highlighted button"));
	m_objMenu->AppendSeparator();
	wxMenuItem* item = new wxMenuItem(m_objMenu, MENUEDITOR_OBJMENU_CUT_ID, _("Cu&t"));
	item->SetBitmap(wxArtProvider::GetBitmap(wxART_CUT, wxART_MENU));
	m_objMenu->Append(item);
	item = new wxMenuItem(m_objMenu, MENUEDITOR_OBJMENU_COPY_ID, _("&Copy"));
	item->SetBitmap(wxArtProvider::GetBitmap(wxART_COPY, wxART_MENU));
	m_objMenu->Append(item);
	item = new wxMenuItem(m_objMenu, MENUEDITOR_OBJMENU_PASTE_ID, _("&Paste"));
	item->SetBitmap(wxArtProvider::GetBitmap(wxART_PASTE, wxART_MENU));
	m_objMenu->Append(item);
	item = new wxMenuItem(m_objMenu, MENUEDITOR_OBJMENU_DELETE_ID, _("&Delete"));
	item->SetBitmap(wxArtProvider::GetBitmap(wxART_DELETE, wxART_MENU));
	m_objMenu->Append(item);
	m_objMenu->AppendSeparator();
	m_objMenu->Append(MENUEDITOR_PROP_ID, _("&Properties..."));

	// context menu
	m_pmenu = new wxMenu;
	m_pmenu->Append(MENUEDITOR_ADDMENU_ID, _("&Add"), m_addMenu);
	m_pmenu->Append(MENUEDITOR_VIEWMENU_ID, _("&View"), m_viewMenu);
	m_pmenu->Append(MENUEDITOR_GRIDMENU_ID, _("&Grid"), m_gridMenu);
	item = new wxMenuItem(m_pmenu, MENUEDITOR_OBJMENU_PASTE_ID, _("&Paste"));
	item->SetBitmap(wxArtProvider::GetBitmap(wxART_PASTE, wxART_MENU));
	m_pmenu->Append(item);
	m_pmenu->Append(MENUEDITOR_PROP_ID, _("&Properties..."));
}

void MenuEditor::SetMenu(DVD* dvd, Menu* menu, int tsi, int pgci) {
	m_dvd = dvd;
	m_menu = menu;
	m_tsi = tsi;
	m_pgci = pgci;
	if (m_menu) {
		SetSVG(m_menu->GetSVG());
		ShowSafeTV();
		ShowGrid();
	} else
		Clear();
	DoSelect();
}

bool MenuEditor::SetBackground(wxString fname) {
	if (!m_menu)
		return false;

	if (wxImage::FindHandler(fname.AfterLast(wxT('.')).Lower(), wxBITMAP_TYPE_ANY)) {
		wxImage img;
		if (!img.LoadFile(fname))
			return false;
	} else if (wxThumbnails::IsVideo(fname)) {
		wxFfmpegMediaDecoder decoder;
		if (!decoder.Load(fname)) {
			wxLogError(fname + _(": unknown format"));
			return false;
		}
	}

	if (m_menu->GetBackground() != fname) {
		m_menu->SetBackground(fname);
		Update();
	}

	return true;
}

void MenuEditor::SetBackgroundColour(wxColour colour) {
	if (!m_menu)
		return;

	if (m_menu->GetBackgroundColour() != colour) {
		m_menu->SetBackgroundColour(colour);
		Update();
	}
}

bool MenuEditor::AddImage(wxString fname, int x, int y) {
	if (!m_menu)
		return false;
	if (m_menu->HasVideoBackground()) {
		wxLogError(_("You can add only buttons to menu with VIDEO background"));
		return false;
	}
	wxImage img;
	if (!img.LoadFile(fname))
		return false;

	DoSelect(m_menu->AddImage(fname, x, y), true, true);
	return true;
}

bool MenuEditor::AddText(wxString text, int x, int y) {
	if (!m_menu)
		return false;
	if (m_menu->HasVideoBackground()) {
		wxLogError(_("You can add only buttons to menu with VIDEO background"));
		return false;
	}
	DoSelect(m_menu->AddText(text, x, y), true, true);
	return true;
}

bool MenuEditor::AddButton(wxString fname, int x, int y, int pgci, int tsi, bool isMenu) {
	if (!m_menu || !wxFileExists(fname))
		return false;
	
	if (m_menu->GetButtonsCount() > 33) {
		wxLogError(_("DVD menu can contain maximal 34 buttons"));
		return false;
	}

	int btNum = 1;
	while (1) {
		wxString id= wxT("button") + wxString::Format(wxT("%02d"), btNum);
		if (m_doc->GetElementById(id) == NULL)
			break;
		btNum++;
	}

	wxString param;
	tsi = tsi >= 0 ? tsi : m_tsi >= 0 ? m_tsi : 0;

	MenuObject tmpObj(NULL, false, fname);
	MenuObjectParam* initParam = tmpObj.GetInitParam();
	if (initParam) {
		Vob* vob = m_dvd->GetVob(tsi, false, pgci >= 0 ? pgci : 0, 0);
		if (initParam->type == wxT("image")) {
			if (vob)
				param = vob->GetVideoFrameUri(0);
		} else if (pgci >= 0 && !isMenu) {
			if (vob)
				param = wxFileName(vob->GetVideoFrameUri(0)).GetName();
		} else {
			if (isMenu) {
				param = wxString::Format(tsi == -1 ? _("vmMenu %d") : _("Menu %d"), pgci+1);
			} else
				param = wxString::Format(_("button%d"), btNum);
		}
	}
	// set action
	wxString objId = m_menu->AddObject(fname, param, x, y);
	MenuObject* obj = m_menu->GetObject(objId);
	obj->GetAction().SetTsi(tsi == m_tsi ? -2 : tsi);
	obj->GetAction().SetPgci(pgci >= 0 ? pgci : 0);
	obj->GetAction().SetMenu(isMenu);

	// fix position
	int mx = 0;
	int my = 0;
	MoveObjectInt(obj, mx, my);

	// select
	DoSelect(objId, true, true);
	return true;
}

bool MenuEditor::AddObject(wxString fname, int x, int y) {
	if (!m_menu || !wxFileExists(fname))
		return false;

	wxString param;

	MenuObject obj(NULL, false, fname);
	MenuObjectParam* initParam = obj.GetInitParam();
	if (initParam) {
		if (initParam->type == wxT("text") || initParam->type == wxT("string"))
			param = wxGetTextFromUser(_("Please type in text to insert"), _("Input text"), _T("Text"), this);
		else if (initParam->type == wxT("image"))
			param = wxFileSelector( _ ("Please choose an image to insert") , wxT("") ,wxGetCwd (), wxT(""),
					_("Image Files ") + wxImage::GetImageExtWildcard(), wxFD_OPEN|wxFD_FILE_MUST_EXIST);
		else
			param = _("Text");
		if (!param.length())
			return false;
	}

	DoSelect(m_menu->AddObject(fname, param, x, y), true, true);
	return true;
}

wxString MenuEditor::HitTest(int x, int y) {
	x = (int) (x / GetScale());
	y = (int) (y / GetScale());
	wxString id;
	// at first try to find a button
	bool button = true;
	wxSVGUseElement* child = (wxSVGUseElement*) m_doc->GetElementById(wxT("buttons"))->GetChildren();
	while (child || button) {
		if (!child) {
			if (id.length() > 0)
				break;
			// no button was found, try to find a object
			child = (wxSVGUseElement*) m_doc->GetElementById(wxT("objects"))->GetChildren();
			button = false;
			continue;
		}
		if (child->GetType() == wxSVGXML_ELEMENT_NODE && child->GetDtd() == wxSVG_USE_ELEMENT) {
			wxSVGRect bbox = child->GetBBox();
			if (!bbox.IsEmpty()
					&& x >= bbox.GetX() && x <= bbox.GetX() + bbox.GetWidth()
					&& y >= bbox.GetY() && y <= bbox.GetY() + bbox.GetHeight())
				id = child->GetId();
		}
		child = (wxSVGUseElement*) child->GetNext();
	}
	return id;
}

void MenuEditor::DoSelect(wxString id, bool refresh, bool sendEvent) {
	if (!m_menu)
		return;

	m_selected = id;

	// remove old selection
	wxSVGRectElement* selRect = (wxSVGRectElement*) m_doc->GetElementById(wxT("selection"));
	if (selRect) {
		if (refresh)
			RefreshRect(ScaleRect(selRect->GetBBox(), GetScale()).Inflate(4));
		selRect->GetParent()->RemoveChild(selRect);
	}

	// create selection rectangle
	if (id.length()) {
		wxSVGUseElement* elem = (wxSVGUseElement*) m_doc->GetElementById(id);
		if (!elem || elem->GetDtd() != wxSVG_USE_ELEMENT)
			return;
		wxSVGRect bbox = elem->GetBBox();

		selRect = new wxSVGRectElement;
		selRect->SetId(wxT("selection"));
		selRect->SetX(bbox.GetX());
		selRect->SetY(bbox.GetY());
		selRect->SetWidth(bbox.GetWidth());
		selRect->SetHeight(bbox.GetHeight());
		selRect->SetStroke(wxSVGPaint(255, 0, 0));
		selRect->SetFill(wxSVGPaint());
		m_doc->GetRootElement()->AppendChild(selRect);

		if (refresh)
			RefreshRect(ScaleRect(selRect->GetBBox(), GetScale()).Inflate(4));
	}

	if (sendEvent)
		SendChangedEvent();
}

void MenuEditor::OnMouseLeftButton(wxMouseEvent &event) {
	SetFocus();
	if (!m_menu)
		return;
	if (objectIsMoving) {
		objectIsMoving = false;
		SendChangedEvent();
	}
	int x = event.GetX();
	int y = event.GetY();
	m_tt = GetTransformType(x, y);
	m_oldx = x;
	m_oldy = y;
	m_copy = event.ControlDown();
	if (event.LeftDown() && m_tt == ttOut) {
		// select object
		DoSelect(HitTest(x, y), true);
		// change mouse cursor
		event.m_leftDown = false;
		OnMouseMove(event);
	}
}

void MenuEditor::OnMouseMove(wxMouseEvent &event) {
	if (!m_menu)
		return;

	int x = event.GetX();
	int y = event.GetY();

	if (!event.LeftIsDown()) {
		m_tt = GetTransformType(x, y);
		// change mouse cursor
		SetMouseCursor(m_tt);
		// object pointed
		wxString pointed = HitTest(x, y);
		if (GetPointed() != pointed) {
			m_pointed = pointed;
			wxCommandEvent evt(EVT_COMMAND_MENUEDITOR_OBJECT_POINTED, this->GetId());
			GetEventHandler()->ProcessEvent(evt);
		}
		return;
	}

	// left mouse button is down => move or resize
	if (m_tt == ttIn) {
		// move Object
		int x1 = x - m_oldx;
		int y1 = y - m_oldy;
		if (MoveObject(x1, y1)) {
			DoSelect(GetSelected(), true); // refresh
			objectIsMoving = true;
		}
		m_oldx += x1;
		m_oldy += y1;
	} else if (m_tt != ttOut) { // m_tt=TL,TR,BL,BR
		// resize object
		if (ResizeObject(x, y, m_tt)) {
			DoSelect(GetSelected(), true); // refresh
			objectIsMoving = true;
		}
	}
}

void MenuEditor::OnMouseRightButton(wxMouseEvent &event) {
	SetFocus();
	if (event.RightDown() || !m_menu)
		return;
	// select Object
	wxString sel_old = GetSelected();
	DoSelect(HitTest(event.GetX(), event.GetY()), true);
	m_menuPos = event.GetPosition();
	if (event.RightUp())
		PopupMenu(GetSelected().length() ? m_objMenu : m_pmenu, event.GetPosition());
}

TransformType MenuEditor::GetTransformType(int x, int y) {
	if (!m_menu || !GetSelected().length())
		return ttOut;

	// test all covered object
	if (HitTest(x, y) != GetSelected())
		return ttOut; // outside of the selected object

	MenuObject* obj = m_menu->GetObject(GetSelected());
	if (!obj)
		return ttOut;
	wxRect rect = ScaleRect(obj->GetBBox(), GetScale());
	wxRect rect2 = rect;
	rect2.Deflate(3, 3);
	if (!rect2.Contains(x, y)) {
		int x1 = rect.x + rect.width / 2;
		int y1 = rect.y + rect.height / 2;
		if ((x < x1) && (y < y1))
			return ttTL; // top-left
		else if ((x > x1) && (y < y1))
			return ttTR; // top-right
		else if ((x < x1) && (y > y1))
			return ttBL; // bottom-left
		else if ((x > x1) && (y > y1))
			return ttBR; // bottom-right
	}

	return ttIn; // inside of the selected object
}

void MenuEditor::SetMouseCursor(TransformType tt) {
	switch (tt) {
		case ttOut: SetCursor(wxCursor(wxCURSOR_ARROW)); break;
#ifdef __WXMSW__
		case ttIn: SetCursor(wxCursor(_T("wxCURSOR_MOVE"),wxBITMAP_TYPE_CUR_RESOURCE)); break;
		case ttTL:
		case ttBR: SetCursor(wxCursor(wxCURSOR_SIZENWSE)); break;
		case ttTR:
		case ttBL: SetCursor(wxCursor(wxCURSOR_SIZENESW)); break;
#else
		case ttIn: SetCursor(wxImage(CURSOR_FILE(_T("move.cur"))));break;
		case ttTL:
		case ttBR: SetCursor(wxImage(CURSOR_FILE(_T("nwse.cur"))));break;
		case ttTR:
		case ttBL: SetCursor(wxImage(CURSOR_FILE(_T("nesw.cur"))));break;
#endif
	}
}

bool MenuEditor::MoveObject(int& x, int& y) {
	if (!m_menu)
		return false;

	MenuObject* obj = m_menu->GetObject(GetSelected());
	if (!obj)
		return false;

	x = (int) round(x / GetScale());
	y = (int) round(y / GetScale());
	bool res = MoveObjectInt(obj, x, y);
	x = (int) round(x * GetScale());
	y = (int) round(y * GetScale());
	return res;
}

bool MenuEditor::MoveObjectInt(MenuObject* obj, int& x, int& y) {
	wxRect rect = obj->GetBBox();

	if (m_copy) {
		wxString id = m_menu->AddObject(obj->GetXML(DVDSTYLER_XML, m_dvd, true), true);
		if (id.length() == 0)
			return false;
		obj = m_menu->GetObject(id);
		if (obj) {
			obj->SetX(rect.x);
			obj->SetY(rect.y);
		}
		DoSelect(id, true);
		m_pointed = id;
		wxCommandEvent evt(EVT_COMMAND_MENUEDITOR_OBJECT_POINTED, this->GetId());
		GetEventHandler()->ProcessEvent(evt);
		m_copy = false;
	}

	rect.x += x;
	rect.y += y;

	if (m_grid > 0) {
		rect.y = rect.y >> m_grid << m_grid;
		rect.x = rect.x >> m_grid << m_grid;
	}

	if (obj->IsButton()) {
		if (rect.x < 0)
			rect.x = 0;
		else if (rect.x + rect.width >= (int) m_doc->GetRootElement()->GetWidth().GetBaseVal())
			rect.x = (int) m_doc->GetRootElement()->GetWidth().GetBaseVal() - rect.width - 1;
		if (rect.y < 0)
			rect.y = 0;
		else if (rect.y + rect.height >= (int) m_doc->GetRootElement()->GetHeight().GetBaseVal())
			rect.y = (int) m_doc->GetRootElement()->GetHeight().GetBaseVal() - rect.height - 1;
	} else {
		if (rect.x + rect.width < 8)
			rect.x = 8 - rect.width;
		else if (rect.x > (int) m_doc->GetRootElement()->GetWidth().GetBaseVal() - 8)
			rect.x = (int) m_doc->GetRootElement()->GetWidth().GetBaseVal() - 8;
		if (rect.y + rect.height < 8)
			rect.y = 8 - rect.height;
		else if (rect.y > (int) m_doc->GetRootElement()->GetHeight().GetBaseVal() - 8)
			rect.y = (int) m_doc->GetRootElement()->GetHeight().GetBaseVal() - 8;
	}

	x = rect.x - obj->GetX();
	y = rect.y - obj->GetY();

	if (obj->GetX() != rect.x || obj->GetY() != rect.y) {
		obj->SetX(rect.x);
		obj->SetY(rect.y);
		return true;
	}

	return false;
}

bool MenuEditor::ResizeObject(int x, int y, TransformType transformType)
{
  x = (int) round(x/GetScale());
  y = (int) round(y/GetScale());
  return ResizeObjectInt(x, y, transformType);
}

bool MenuEditor::ResizeObjectInt(int x, int y, TransformType transformType) {
	if (!m_menu)
		return false;

	MenuObject* obj = m_menu->GetObject(GetSelected());
	if (!obj)
		return false;
	wxRect rect = obj->GetBBox();

	if ((transformType == ttBR || transformType == ttTR) && x < 8)
		x = 8;
	if ((transformType == ttBL || transformType == ttTL)
			&& x > (int) m_doc->GetRootElement()->GetWidth().GetBaseVal() - 8)
		x = (int) m_doc->GetRootElement()->GetWidth().GetBaseVal() - 8;
	if ((transformType == ttBL || transformType == ttBR) && y < 8)
		y = 8;
	if ((transformType == ttTL || transformType == ttTR)
			&& y > (int) m_doc->GetRootElement()->GetHeight().GetBaseVal() - 8)
		y = (int) m_doc->GetRootElement()->GetHeight().GetBaseVal() - 8;

	int sx, sy;

	if ((transformType == ttBR) || (transformType == ttTR))
		sx = x - rect.x + 1;
	else
		sx = rect.x + rect.width - x + 1;

	if ((transformType == ttBR) || (transformType == ttBL))
		sy = y - rect.y + 1;
	else
		sy = rect.y + rect.height - y + 1;

	if (sx <= 4)
		sx = 4;
	if (sy <= 4)
		sy = 4;

	obj->SetDefaultSize(false);
	obj->FixSize(sx, sy);

	// set new x, y, width, height
	if ((transformType == ttTL) || (transformType == ttBL))
		rect.x += rect.width - sx;
	if ((transformType == ttTL) || (transformType == ttTR))
		rect.y += rect.height - sy;
	rect.width = sx;
	rect.height = sy;

	if (obj->GetBBox() != rect) {
		obj->SetX(rect.x);
		obj->SetY(rect.y);
		obj->SetWidth(rect.width);
		obj->SetHeight(rect.height);
		return true;
	}
	return false;
}

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

void MenuEditor::ShowSafeTV() {
	if (!m_doc)
		return;
	wxSVGGElement* gElem =
    		(wxSVGGElement*) m_doc->GetElementById(wxT("safeTV"));
    if (gElem)
		gElem->GetParent()->RemoveChild(gElem);
    
	if (m_safeTV) {
		gElem = new wxSVGGElement;
		gElem->SetId(wxT("safeTV"));
		wxSVGRectElement* rectElem = new wxSVGRectElement;
	    rectElem->SetX(0);
	    rectElem->SetY(0);
	    rectElem->SetWidth(m_menu->GetResolution().GetWidth());
	    rectElem->SetHeight(32);
	    rectElem->SetStroke(wxSVGPaint());
	    rectElem->SetFill(wxSVGPaint(255, 255, 255));
	    rectElem->SetFillOpacity(0.1);
	    gElem->AppendChild(rectElem);
	    rectElem = (wxSVGRectElement*) rectElem->CloneNode();
	    rectElem->SetY(m_menu->GetResolution().GetHeight()-33);
	    gElem->AppendChild(rectElem);
	    rectElem = (wxSVGRectElement*) rectElem->CloneNode();
	    rectElem->SetX(0);
	    rectElem->SetY(32);
	    rectElem->SetWidth(40);
	    rectElem->SetHeight(m_menu->GetResolution().GetHeight()-65);
	    gElem->AppendChild(rectElem);
	    rectElem = (wxSVGRectElement*) rectElem->CloneNode();
	    rectElem->SetX(m_menu->GetResolution().GetWidth()-41);
	    gElem->AppendChild(rectElem);
		m_doc->GetRootElement()->AppendChild(gElem);
	}
	Refresh();
}

void MenuEditor::ShowGrid() {
	wxSVGGElement* gElem =
    		(wxSVGGElement*) m_doc->GetElementById(wxT("grid"));
    if (gElem)
		gElem->GetParent()->RemoveChild(gElem);
    
	if (m_showGrid && m_grid>0) {
		gElem = new wxSVGGElement;
		gElem->SetId(wxT("grid"));
		wxSVGLineElement* lineElem = new wxSVGLineElement;
	    lineElem->SetY1(0);
	    lineElem->SetY2(m_menu->GetResolution().GetHeight()-1);
	    lineElem->SetStroke(wxSVGPaint(255, 255, 255));
	    lineElem->SetFill(wxSVGPaint());
	    lineElem->SetStrokeOpacity(0.1);
	    int step = 1<<m_grid;
	    for (int x = step; x<m_menu->GetResolution().GetWidth()-1; x+=step) {
		    lineElem = (wxSVGLineElement*) lineElem->CloneNode();
		    lineElem->SetX1(x);
		    lineElem->SetX2(x);
		    gElem->AppendChild(lineElem);
	    }
	    lineElem = (wxSVGLineElement*) lineElem->CloneNode();
	    lineElem->SetX1(0);
	    lineElem->SetX2(m_menu->GetResolution().GetWidth()-1);
	    for (int y = step; y<m_menu->GetResolution().GetHeight()-1; y+=step) {
		    lineElem = (wxSVGLineElement*) lineElem->CloneNode();
		    lineElem->SetY1(y);
		    lineElem->SetY2(y);
		    gElem->AppendChild(lineElem);
	    }
	    if (m_gridOverObejcts)
	    	m_doc->GetRootElement()->AppendChild(gElem);
	    else
	    	m_doc->GetRootElement()->InsertBefore(gElem, m_doc->GetElementById(wxT("objects")));
	}
	Refresh();
}

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

void MenuEditor::OnMouseDClick(wxMouseEvent& WXUNUSED(event)) {
	wxCommandEvent evt;
	OnProperties(evt);
}

void MenuEditor::OnKeyDown(wxKeyEvent& event) {
	if (event.ControlDown() && event.GetKeyCode() == 'V') {
		m_menuPos = wxPoint(-1, -1);
		wxCommandEvent evt;
		OnObjectPaste(evt);
		return;
	}
	if (GetSelected().length()) {
		if (event.GetKeyCode() == WXK_DELETE) {
			wxCommandEvent evt;
			OnObjectDelete(evt);
			return;
		} else if (event.ControlDown() && event.GetKeyCode() == 'C') {
			wxCommandEvent evt;
			OnObjectCopy(evt);
			return;
		} else if (event.ControlDown() && event.GetKeyCode() == 'X') {
			wxCommandEvent evt;
			OnObjectCopy(evt);
			OnObjectDelete(evt);
			return;
		}
	}
	event.Skip();
}

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

void MenuEditor::OnObjectCopy(wxCommandEvent& WXUNUSED(event)) {
	CopyXmlToClipboard(m_menu->GetObject(GetSelected())->GetXML(DVDSTYLER_XML, m_dvd, true),
			DATAFORMAT_MENU_OBJECT);
}

void MenuEditor::OnObjectPaste(wxCommandEvent& WXUNUSED(event)) {
	if (wxTheClipboard->IsSupported(wxDataFormat(DATAFORMAT_MENU_OBJECT))) {
		wxSvgXmlDocument doc;
		if (!GetXmlFromClipboard(DATAFORMAT_MENU_OBJECT, doc))
			return;
		wxString id = m_menu->AddObject(doc.GetRoot(), true);
		if (id.length() > 0) {
			MenuObject* obj = m_menu->GetObject(id);
			if (obj && m_menuPos.x != -1 && m_menuPos.y != -1) {
				obj->SetX((int)(m_menuPos.x/GetScale()));
				obj->SetY((int)(m_menuPos.y/GetScale()));
			}
		}
	} else if (wxTheClipboard->IsSupported(wxDF_TEXT)) {
		wxString text = GetTextFromClipboard();
		if (text.length() == 0)
			return;
		wxString id = m_menu->AddText(text);
		MenuObject* obj = m_menu->GetObject(id);
		if (obj && m_menuPos.x != -1 && m_menuPos.y != -1) {
			obj->SetX((int)(m_menuPos.x/GetScale()));
			obj->SetY((int)(m_menuPos.y/GetScale()));
		}
	}
	Update();
}

void MenuEditor::OnUpdateUIObjectPaste(wxUpdateUIEvent& event) {
	event.Enable(m_menu && (wxTheClipboard->IsSupported(wxDF_TEXT)
			|| wxTheClipboard->IsSupported(wxDataFormat(DATAFORMAT_MENU_OBJECT))));
}

void MenuEditor::OnObjectCut(wxCommandEvent& event) {
	wxCommandEvent evt;
	OnObjectCopy(evt);
	OnObjectDelete(evt);
}

void MenuEditor::OnObjectDelete(wxCommandEvent& event) {
	m_menu->RemoveObject(GetSelected());
	DoSelect(wxT(""), true, true);
}

void MenuEditor::OnFirstButton(wxCommandEvent& event) {
	m_menu->SetFirstButton(GetSelected());
}

void MenuEditor::OnUpdateUIFirstButton(wxUpdateUIEvent& event) {
	event.Enable(m_menu->GetObject(GetSelected())->IsButton());
	event.Check(m_menu->IsFirstButton(GetSelected()));
}

void MenuEditor::OnObjectToFront(wxCommandEvent& event) {
	m_menu->ObjectToFront(GetSelected());
	DoSelect(GetSelected(), true, true);
}

void MenuEditor::OnObjectForward(wxCommandEvent& event) {
	m_menu->ObjectForward(GetSelected());
	DoSelect(GetSelected(), true, true);
}

void MenuEditor::OnObjectBackward(wxCommandEvent& event) {
	m_menu->ObjectBackward(GetSelected());
	DoSelect(GetSelected(), true, true);
}

void MenuEditor::OnObjectToBack(wxCommandEvent& event) {
	m_menu->ObjectToBack(GetSelected());
	DoSelect(GetSelected(), true, true);
}

void MenuEditor::OnUpdateUIObjectForward(wxUpdateUIEvent& event) {
	event.Enable(!m_menu->IsLastObject(GetSelected()));
}

void MenuEditor::OnUpdateUIObjectBackward(wxUpdateUIEvent& event) {
	event.Enable(!m_menu->IsFirstObject(GetSelected()));
}

void MenuEditor::OnAlignToCenterH(wxCommandEvent& event) {
	MenuObject* obj = m_menu->GetObject(GetSelected());
	obj->SetX((m_menu->GetResolution().GetWidth() - obj->GetWidth())/2);
	DoSelect(GetSelected(), true, true);
}

void MenuEditor::OnAlignToCenterV(wxCommandEvent& event) {
	MenuObject* obj = m_menu->GetObject(GetSelected());
	obj->SetY((m_menu->GetResolution().GetHeight() - obj->GetHeight())/2);
	DoSelect(GetSelected(), true, true);
}

void MenuEditor::OnProperties(wxCommandEvent& event) {
	if (!m_menu)
		return;
	if (!GetSelected().length()) {
		// show menu properties
		MenuPropDlg propDlg(this, m_dvd, m_tsi, m_pgci);
		if (propDlg.ShowModal() == wxID_OK)
			Update();
	} else {
		// show object properties
		MenuObject* obj = m_menu->GetObject(GetSelected());
		if (!obj)
			return;
		MenuObjectPropDlg propDlg(this, GetSelected(), m_menu, m_dvd, m_tsi, m_pgci);
		if (propDlg.ShowModal() == wxID_OK)
			DoSelect(GetSelected(), true, true);
	}
}

void MenuEditor::OnSafeTV(wxCommandEvent& event) {
	m_safeTV = !m_safeTV;
	ShowSafeTV();
}

void MenuEditor::OnShowGrid(wxCommandEvent& event) {
	m_showGrid = !m_showGrid;
	ShowGrid();
}

void MenuEditor::OnShowGridOverObjects(wxCommandEvent& event) {
	m_gridOverObejcts = !m_gridOverObejcts;
	ShowGrid();
}

void MenuEditor::OnGridMenu(wxCommandEvent& event) {
	long id = event.GetId() - MENUEDITOR_GRIDMENU_ITEM_ID;
	m_grid = id;
	if (m_grid > 0)
		m_grid += 1;
	if (m_showGrid)
		ShowGrid();
}

void MenuEditor::OnAddObject(wxCommandEvent& event) {
	long n = event.GetId() - MENUEDITOR_ADDMENU_OBJECT_ID;
	AddObject(m_addMenuObjects[n], (int) (m_menuPos.x / GetScale()),
			(int) (m_menuPos.y / GetScale()));
}

void MenuEditor::SendChangedEvent() {
	wxCommandEvent evt(EVT_COMMAND_MENUEDITOR_MENU_CHANGED, this->GetId());
	GetEventHandler()->ProcessEvent(evt);
}
