/////////////////////////////////////////////////////////////////////////////
// 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.19 2008/03/02 18:57:21 ntalex Exp $
// Copyright:   (c) Alex Thuering
// Licence:     GPL
/////////////////////////////////////////////////////////////////////////////

#include "MenuEditor.h"
#include "MenuPropDlg.h"
#include "MenuObjectPropDlg.h"
#include "MPEG.h"
#include "TitlesetManager.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/mstream.h>
#include <wx/clipbrd.h>
#include <wx/artprov.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)

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

class MenuDnDFile : public wxFileDropTarget
{
  public:
    MenuDnDFile(MenuEditor* menuEditor) { m_menuEditor = menuEditor; }

    virtual 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 objId = m_menuEditor->HitTest(x, y);
      x = (int) (x/m_menuEditor->GetScale());
      y = (int) (y/m_menuEditor->GetScale());
      wxString ext = filename.AfterLast('.').Lower();
      if (ext == _T("xml"))
        m_menuEditor->AddButton(filename, x, y);
      else if (wxImage::FindHandler(ext, -1))
        m_menuEditor->AddImage(filename, x, y);
      else  if (wxThumbnails::IsVideo(filename))
      {
        if (!MPEG::IsValid(filename))
        {
          wxLogError(filename + _(": not valid mpeg-file"));
          return false;
        }
        if (m_menuEditor->GetTitlesetManager()->AddVideo(filename))
        {
          int title = m_menuEditor->GetDVD()->GetPgcArray(m_menuEditor->GetTsi(), false).GetCount()-1;
          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(m_menuEditor->GetTsi(), false, title, 0);
               if (vob)
               {
                 obj->SetParam(initParam->name, vob->GetVideoFrameUri(0));
                 m_menuEditor->Select(objId);
               }
            }
            obj->SetActionPgci(title*2+1);
          }
          else
            m_menuEditor->AddButton(BUTTONS_DIR + wxT("frame.xml"), x, y, title);
        }
      }
      m_menuEditor->SetFocus();
      return true;
    }
	
  protected:
    MenuEditor *m_menuEditor;
};

//////////////////////////////////////////////////////////////////////////////
/////////////////////////// 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_ARRANGEMENU_ID,
  MENUEDITOR_ARRANGEMENU_TOFRONT_ID,
  MENUEDITOR_ARRANGEMENU_FORWARD_ID,
  MENUEDITOR_ARRANGEMENU_BACKWARD_ID,
  MENUEDITOR_ARRANGEMENU_TOBACK_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_TIMER(EVENT_TIMER_ID, MenuEditor::OnEventTimer)
  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_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_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_eventTimer(this, EVENT_TIMER_ID)
{
  m_dvd = NULL;
  m_menu = NULL;
  DoSelect();
  m_safeTV = true;
  m_showGrid = false;
  m_gridOverObejcts = false;
  m_grid = 3;
  SetDropTarget(new MenuDnDFile(this));
  SetFitToFrame(true);
  
  // arrange sub menu
  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"));
  
  // object context menu
  m_objMenu = new wxMenu;
  m_objMenu->Append(MENUEDITOR_ARRANGEMENU_ID, _("A&rrange"), m_arrangeMenu);
  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..."));
  
  // sub menu "Add"
  m_addMenu = new wxMenu;
  wxString fname = wxFindFirstFile(OBJECTS_DIR + _T("*.xml"));
  int addId = MENUEDITOR_ADDMENU_OBJECT_ID;
  Menu menu;
  while (!fname.IsEmpty())
  {
	MenuObject obj(&menu, 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);
  
  // 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(), -1))
  {
    wxImage img;
	if (!img.LoadFile(fname))
	  return false;
  }
  else if (!MPEG::IsValid(fname))
  {
	wxLogError(fname + _(": unknown or not supported 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 assignToTitle)
{
  if (!m_menu || !wxFileExists(fname))
    return false;
  
  int i = 1;
  while(1)
  {
    wxString id = wxT("button") + wxString::Format(wxT("%02d"), i);
    if (m_doc->GetElementById(id) == NULL)
      break;
    i++;
  }
  
  wxString param;
  
  Menu menu;
  MenuObject obj(&menu, fname);
  MenuObjectParam* initParam = obj.GetInitParam();
  if (initParam)
  {
    if (initParam->type == wxT("image")) {
      Vob* vob = m_dvd->GetVob(m_tsi, false, assignToTitle, 0);
      if (vob)
        param = vob->GetVideoFrameUri(0);
    }
    else
      param = wxString::Format(_("button%d"), i);
  }
  
  wxString objId = m_menu->AddObject(fname, param, x, y);
  m_menu->GetObject(objId)->SetActionPgci(assignToTitle*2+1);
  DoSelect(objId, true, true);
  return true;
}

bool MenuEditor::AddObject(wxString fname, int x, int y)
{
  if (!m_menu || !wxFileExists(fname))
    return false;
  
  wxString param;
  
  Menu menu;
  MenuObject obj(&menu, 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(""), wxGetWorkingDirectory(), wxT(""),
        _("Image Files ") + 
#if wxCHECK_VERSION(2,6,0)
        wxImage::GetImageExtWildcard(),
#else
        wxString(wxT("(*.jpg;*.bmp;*.png;*.tif;*.gif;*.pnm;*.pcx)|*.jpg;*.bmp;*.png;*.tif;*.gif;*.pnm;*.pcx")),
#endif
        wxOPEN|wxFILE_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 frist try to find a button
  bool button = true;
  wxSVGUseElement* child = (wxSVGUseElement*)
    m_doc->GetElementById(wxT("buttons"))->GetChildren();
  while (child || button && id.length() == 0)
  {
  	if (!child) {
    	// 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)
{
  if (!m_menu)
	return;
  int x = event.GetX();
  int y = event.GetY();
  m_tt = GetTransformType(x, y);
  m_oldx = x;
  m_oldy = y;
  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
      m_eventTimer.Start(500, wxTIMER_ONE_SHOT); // send changed event
    }
    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
      m_eventTimer.Start(500, wxTIMER_ONE_SHOT); // send changed event
    }
  }
}

void MenuEditor::OnMouseRightButton(wxMouseEvent &event)
{
  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.Inside(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)
{
  x = (int) round(x/GetScale());
  y = (int) round(y/GetScale());
  bool res = MoveObjectInt(x,y);
  x = (int) round(x*GetScale());
  y = (int) round(y*GetScale());
  return res;
}

bool MenuEditor::MoveObjectInt(int& x, int& y)
{
  if (!m_menu)
	return false;
  
  MenuObject* obj = m_menu->GetObject(GetSelected());
  if (!obj)
    return false;
  wxRect rect = obj->GetBBox();
  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() {
	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::OnEventTimer(wxTimerEvent& event)
{
  SendChangedEvent();
}

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

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();
}

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

const wxString dvdstylerDataFormatId =  _T("application/dvdstyler");

void MenuEditor::OnObjectCopy(wxCommandEvent& WXUNUSED(event))
{
  wxMemoryOutputStream stream;
  wxSvgXmlDocument doc;
  doc.SetRoot(m_menu->GetObject(GetSelected())->GetXML(DVDSTYLER_XML,true));
  doc.Save(stream);
  int bufferSize = stream.GetSize();
  char* buffer = new char[bufferSize];
  stream.CopyTo(buffer, bufferSize);
  wxDataFormat format(dvdstylerDataFormatId);
  wxCustomDataObject* dataObj = new wxCustomDataObject(format);
  dataObj->TakeData(bufferSize, buffer);
  wxClipboardLocker locker;
  if (!locker)
	wxLogError(wxT("Can't open clipboard"));
  else if (!wxTheClipboard->SetData(dataObj))
	wxLogError(wxT("Can't copy object to the clipboard"));
}

void MenuEditor::OnObjectPaste(wxCommandEvent& WXUNUSED(event))
{
  if (!wxTheClipboard->Open())
  {
	wxLogError(wxT("Can't open clipboard"));
	return;
  }
  wxDataFormat format(dvdstylerDataFormatId);
  if (wxTheClipboard->IsSupported(format))
  {
	wxCustomDataObject dataObj(format);
	if (!wxTheClipboard->GetData(dataObj))
	{
	  wxLogError(wxT("Can't paste data from the clipboard"));
	  wxTheClipboard->Close();
	  return;
	}
	wxTheClipboard->Close();
	wxMemoryInputStream stream(dataObj.GetData(), dataObj.GetSize());
	wxSvgXmlDocument doc;
	if (!doc.Load(stream))
	{
	  wxLogError(wxT("Can't paste object: error parsing"));
	  return;
	}
    wxString id = m_menu->AddObject(doc.GetRoot(), true);
	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();
  }
  else if (wxTheClipboard->IsSupported(wxDF_TEXT))
  {
	wxTextDataObject textObj;
	if (!wxTheClipboard->GetData(textObj))
	{
	  wxLogError(wxT("Can't paste data from the clipboard"));
	  wxTheClipboard->Close();
	  return;
	}
	wxTheClipboard->Close();
    wxString id = m_menu->AddText(textObj.GetText());
	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();
  }
  else
	wxTheClipboard->Close();
}

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

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

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

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

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

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

void MenuEditor::OnObjectToBack(wxCommandEvent& WXUNUSED(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::OnProperties(wxCommandEvent& WXUNUSED(event))
{
  if (!m_menu)
	return;
  if (!GetSelected().length()) // menu properties
  {
	MenuPropDlg propDlg(this, m_dvd, m_tsi, m_pgci);
	if (propDlg.ShowModal() == wxID_OK)
	  Update();
  }
  else // 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);
}
