///////////////////////////////////////////////////////////////////////////// // Name: Menu.cpp // Purpose: The class to store a DVD Menu // Author: Alex Thuering // Created: 04.11.2003 // RCS-ID: $Id: Menu.cpp,v 1.61 2007/07/20 06:03:21 ntalex Exp $ // Copyright: (c) Alex Thuering // Licence: GPL ///////////////////////////////////////////////////////////////////////////// #include "Menu.h" #include "MenuPalettes.h" #include "Palette3D.h" #include #include #include #include #include #define OBJECTS_DIR wxFindDataDirectory(_T("objects")) #define BUTTON_MAX_COLORS 4 Menu::Menu(VideoFormat videoFormat) { m_videoFormat = videoFormat; m_startTime = _T("00:00:00.00"); m_transpColour = wxColour(8,8,8); m_svg = new wxSVGDocument; wxSVGSVGElement* svgElement = new wxSVGSVGElement; svgElement->SetWidth(GetResolution().GetWidth()); svgElement->SetHeight(GetResolution().GetHeight()); m_svg->AppendChild(svgElement); wxSVGDefsElement* defsElement = new wxSVGDefsElement; defsElement->SetId(wxT("defs")); svgElement->AppendChild(defsElement); wxSVGGElement* gElem = new wxSVGGElement; gElem->SetId(wxT("objects")); svgElement->AppendChild(gElem); gElem = new wxSVGGElement; gElem->SetId(wxT("buttons")); svgElement->AppendChild(gElem); SetBackgroundColour(wxColour(0,0,0)); } Menu::~Menu() { delete m_svg; WX_CLEAR_ARRAY(m_objects) } void Menu::SetVideoFormat(VideoFormat format) { m_videoFormat = format; // svg root element m_svg->GetRootElement()->SetWidth(GetResolution().GetWidth()); m_svg->GetRootElement()->SetHeight(GetResolution().GetHeight()); // background wxSVGElement* bgElement = m_svg->GetElementById(wxT("background")); if (bgElement) { if (bgElement->GetDtd() == wxSVG_RECT_ELEMENT) { ((wxSVGRectElement*)bgElement)->SetWidth(GetResolution().GetWidth()); ((wxSVGRectElement*)bgElement)->SetHeight(GetResolution().GetHeight()); } else if (bgElement->GetDtd() == wxSVG_IMAGE_ELEMENT) { ((wxSVGImageElement*)bgElement)->SetWidth(GetResolution().GetWidth()); ((wxSVGImageElement*)bgElement)->SetHeight(GetResolution().GetHeight()); } } } wxSize Menu::GetResolution() { if (m_videoFormat == vfNTSC) return wxSize(720,480); return wxSize(720,576); // vfPAL } void Menu::SetBackground(wxString fileName) { bool image = wxImage::FindHandler(fileName.AfterLast(wxT('.')).Lower(), -1); // test if element exists wxSVGElement* bgElement = m_svg->GetElementById(wxT("background")); if (!bgElement || (image && bgElement->GetDtd() != wxSVG_IMAGE_ELEMENT) || (!image && bgElement->GetDtd() != wxSVG_VIDEO_ELEMENT)) { if (bgElement) // remove old bgElement->GetParent()->RemoveChild(bgElement); if (image) // create image element { bgElement = new wxSVGImageElement; ((wxSVGImageElement*) bgElement)->SetWidth(GetResolution().GetWidth()); ((wxSVGImageElement*) bgElement)->SetHeight(GetResolution().GetHeight()); } else // create video element { bgElement = new wxSVGVideoElement; ((wxSVGVideoElement*) bgElement)->SetWidth(GetResolution().GetWidth()); ((wxSVGVideoElement*) bgElement)->SetHeight(GetResolution().GetHeight()); } bgElement->SetId(wxT("background")); m_svg->GetRootElement()->InsertChild(bgElement, m_svg->GetRootElement()->GetChildren()); } // set href if (image) ((wxSVGImageElement*) bgElement)->SetHref(fileName); else ((wxSVGVideoElement*) bgElement)->SetHref(fileName); } wxString Menu::GetBackground() { wxSVGElement* bgElement = (wxSVGElement*) m_svg->GetElementById(wxT("background")); if (bgElement) { if (bgElement->GetDtd() == wxSVG_IMAGE_ELEMENT) return ((wxSVGImageElement*)bgElement)->GetHref(); else if (bgElement->GetDtd() == wxSVG_VIDEO_ELEMENT) return ((wxSVGVideoElement*)bgElement)->GetHref(); } return wxT(""); } void Menu::SetBackgroundColour(wxColour colour) { wxSVGElement* bgElement = m_svg->GetElementById(wxT("background")); wxSVGRectElement* bgRect = (wxSVGRectElement*) bgElement; if (!bgElement || bgElement->GetDtd() != wxSVG_RECT_ELEMENT) { if (bgElement) bgElement->GetParent()->RemoveChild(bgElement); bgRect = new wxSVGRectElement; bgRect->SetId(wxT("background")); bgRect->SetWidth(GetResolution().GetWidth()); bgRect->SetHeight(GetResolution().GetHeight()); m_svg->GetRootElement()->InsertChild(bgRect, m_svg->GetRootElement()->GetChildren()); } bgRect->SetFill(wxSVGPaint(colour.Red(), colour.Green(), colour.Blue())); } wxColour Menu::GetBackgroundColour() { wxSVGRectElement* bgRect = (wxSVGRectElement*) m_svg->GetElementById(wxT("background")); if (bgRect && bgRect->GetDtd() == wxSVG_RECT_ELEMENT) return bgRect->GetFill().GetRGBColor(); return wxColour(0,0,0); } bool Menu::HasVideoBackground() { wxSVGElement* bgElement = m_svg->GetElementById(wxT("background")); return bgElement && bgElement->GetDtd() == wxSVG_VIDEO_ELEMENT; } void Menu::SetTransparentColor() { m_transpColour = wxColour(0,0,0); wxImageHistogram h1, h2, h3; wxImage* images = GetImages(); images[1].ComputeHistogram(h1); images[2].ComputeHistogram(h2); images[3].ComputeHistogram(h3); for (int i = 1; i< 30; i++) { m_transpColour = wxColour(i*8, i*8, i*8); unsigned long colour = (i*8<<16) + (i*8<<8) + i*8; if (h1.find(colour) == h1.end() && h2.find(colour) == h2.end() && h3.find(colour) == h3.end()) break; } } bool Menu::IsDefElement(wxSVGElement* element) { if (!element || element->GetDtd() == wxSVG_SVG_ELEMENT) return false; if (element->GetDtd() == wxSVG_DEFS_ELEMENT) return true; return IsDefElement((wxSVGElement*) element->GetParent()); } void Menu::RemoveChangeable(wxSVGElement* element, MenuObject* obj) { wxSVGElement* child = (wxSVGElement*) element->GetChildren(); while (child) { wxSVGElement* elem = child; child = (wxSVGElement*) child->GetNext(); if (elem->GetType() != wxXML_ELEMENT_NODE) continue; // don't remove def elements if (IsDefElement(elem)) continue; // check if elem changeable if (elem->GetId().length()) { // check if element has changeable attributes bool isChangeable = false; for (int i=0; iGetObjectParamsCount(); i++) { MenuObjectParam* param = obj->GetObjectParam(i); if (param->element == elem->GetId() && param->changeable) { isChangeable = true; break; } } if (isChangeable) { // todo: don't remove if it has not changeable attributes, but // remove all changeable attributes: SetFill(none),SetStroke(none) elem->GetParent()->RemoveChild(elem); continue; } } // remove changeable children RemoveChangeable(elem, obj); } } bool Menu::RemoveNotChangeable(wxSVGElement* element, MenuObject* obj) { wxSVGElement* child = (wxSVGElement*) element->GetChildren(); while (child) { wxSVGElement* elem = child; child = (wxSVGElement*) child->GetNext(); if (elem->GetType() != wxXML_ELEMENT_NODE) { elem->GetParent()->RemoveChild(elem); continue; } // don't remove def elements if (IsDefElement(elem)) continue; // check if child changeable if (elem->GetId().length()) { // check if element has changeable attributes bool isChangeable = false; for (int i=0; iGetObjectParamsCount(); i++) { MenuObjectParam* param = obj->GetObjectParam(i); if (param->element == elem->GetId() && param->changeable) { isChangeable = true; break; } } if (isChangeable) { // todo: remove not changeable attributes: SetFill(none),SetStroke(none) continue; } } // check if it has changeable children if (RemoveNotChangeable(elem, obj)) elem->GetParent()->RemoveChild(elem); } return element->GetChildren() == NULL; // return if has no children } wxSVGSVGElement* Menu::GetSVGCopy() { wxSVGSVGElement* svgNode = (wxSVGSVGElement*) m_svg->GetRoot()->CloneNode(); // remove selection rectangle wxXmlElement* elem = svgNode->GetElementById(wxT("selection")); if (elem) elem->GetParent()->RemoveChild(elem); // remove safeTV rectangle elem = svgNode->GetElementById(wxT("safeTV")); if (elem) elem->GetParent()->RemoveChild(elem); // remove grid elem = svgNode->GetElementById(wxT("grid")); if (elem) elem->GetParent()->RemoveChild(elem); return svgNode; } ////////////////////////////// Render //////////////////////////////////////// wxImage Menu::RenderImage(MenuDrawType drawType, int width, int height) { wxSVGDocument svg; svg.AppendChild(GetSVGCopy()); if (drawType == mdBACKGROUND) { for (int i=0; iIsButton()) { wxSVGElement* elem = svg.GetElementById(wxT("s_") + obj->GetId()); if (elem) RemoveChangeable(elem, obj); } } } else if (drawType == mdBUTTONS_NORMAL || drawType == mdBUTTONS_HIGHLIGHTED || drawType == mdBUTTONS_SELECTED) { wxSVGElement* bgElement = svg.GetElementById(wxT("background")); wxSVGRectElement* bgRect = (wxSVGRectElement*) bgElement; if (!bgElement || bgElement->GetDtd() != wxSVG_RECT_ELEMENT) { if (bgElement) bgElement->GetParent()->RemoveChild(bgElement); bgRect = new wxSVGRectElement; bgRect->SetId(wxT("background")); bgRect->SetWidth(GetResolution().GetWidth()); bgRect->SetHeight(GetResolution().GetHeight()); svg.GetRootElement()->InsertChild(bgRect, svg.GetRootElement()->GetChildren()); } bgRect->SetFill(wxSVGPaint(m_transpColour.Red(), m_transpColour.Green(), m_transpColour.Blue())); for (int i=0; iIsButton()) { wxSVGSVGElement* symbol = (wxSVGSVGElement*) svg.GetElementById(wxT("s_") + obj->GetId()); if (symbol) RemoveNotChangeable(symbol, obj); for (int i=0; iGetObjectParamsCount(); i++) { MenuObjectParam* param = obj->GetObjectParam(i); if (param->changeable && drawType != mdBUTTONS_NORMAL) { wxSVGElement* elem = (wxSVGElement*) symbol->GetElementById(param->element); if (elem && param->attribute.length()) { wxSVGPaint paint(drawType == mdBUTTONS_SELECTED ? param->selectedColour : param->highlightedColour); elem->SetAttribute(param->attribute, paint.GetCSSText()); } } } } else { wxSVGElement* elem = svg.GetElementById(obj->GetId()); if (elem && elem->GetParent()) elem->GetParent()->RemoveChild(elem); elem = svg.GetElementById(wxT("s_") + obj->GetId()); if (elem && elem->GetParent()) elem->GetParent()->RemoveChild(elem); } } } return svg.Render(width, height); } wxImage Menu::GetImage(int width, int height) { return RenderImage(mdALL, width, height); } bool Menu::ReduceColours() { // create 3d palette Palette3D palette; for (int i=0; ichangeable) { palette.Add(param->normalColour, param->highlightedColour, param->selectedColour); } } } palette.Add(wxColour(), wxColour(), wxColour()); // reduce the number of colours if (palette.GetColoursCount() <= BUTTON_MAX_COLORS) return false; palette.ReduceColours(BUTTON_MAX_COLORS); // apply palette for (int i=0; ichangeable) { if (palette.Apply(param->normalColour, param->highlightedColour, param->selectedColour)) { obj.SetParamColour(param->name, param->normalColour, mbsNORMAL); obj.SetParamColour(param->name, param->highlightedColour, mbsHIGHLIGHTED); obj.SetParamColour(param->name, param->selectedColour, mbsSELECTED); } } } } return true; } wxImage* Menu::GetImages() { ReduceColours(); // render images wxImage* images = new wxImage[4]; images[0] = RenderImage(mdBACKGROUND, -1, -1); images[1] = RenderImage(mdBUTTONS_NORMAL, -1, -1); images[2] = RenderImage(mdBUTTONS_HIGHLIGHTED, -1, -1); images[3] = RenderImage(mdBUTTONS_SELECTED, -1, -1); // make aliasing for buttons for (int i=0; i=obj.GetX() && x < obj.GetX() + obj.GetWidth() && y>=obj.GetY() && y < obj.GetY() + obj.GetHeight()) { found = true; break; } } if (!found) { img1[0] = m_transpColour.Red(); img1[1] = m_transpColour.Green(); img1[2] = m_transpColour.Blue(); img2[0] = m_transpColour.Red(); img2[1] = m_transpColour.Green(); img2[2] = m_transpColour.Blue(); img3[0] = m_transpColour.Red(); img3[1] = m_transpColour.Green(); img3[2] = m_transpColour.Blue(); } img1+=3; img2+=3; img3+=3; } } return images; } MenuObject* Menu::GetObject(wxString id) { for (int i=0; i<(int)m_objects.Count(); i++) if (m_objects[i]->GetId() == id) return m_objects[i]; return NULL; } void Menu::RemoveObject(wxString id) { MenuObject* obj = GetObject(id); if (obj) { m_objects.Remove(obj); delete obj; } } wxString Menu::AddImage(wxString fileName, int x, int y) { return AddObject(OBJECTS_DIR + wxT("/image.xml"), fileName, x, y); } wxString Menu::AddText(wxString text, int x, int y) { return AddObject(OBJECTS_DIR + wxT("/text.xml"), text, x, y); } wxString Menu::AddObject(wxString fileName, wxString param, int x, int y) { MenuObject* obj = new MenuObject(this, fileName, x, y, param); m_objects.Add(obj); return obj->GetId(); } bool Menu::SaveSpumux(wxString fileName, wxString btFile, wxString hlFile, wxString selFile) { wxXmlDocument xml; wxXmlNode* root = new wxXmlNode(wxXML_ELEMENT_NODE, _T("subpictures")); wxXmlNode* streamNode = new wxXmlNode(wxXML_ELEMENT_NODE, _T("stream")); wxXmlNode* spuNode = new wxXmlNode(wxXML_ELEMENT_NODE, _T("spu")); if (GetStartTime().Length()) spuNode->AddProperty(_T("start"), GetStartTime()); if (GetEndTime().Length()) spuNode->AddProperty(_T("end"), GetEndTime()); spuNode->AddProperty(_T("image"), btFile); spuNode->AddProperty(_T("highlight"), hlFile); spuNode->AddProperty(_T("select"), selFile); spuNode->AddProperty(_T("transparent"), SConv::ToString(m_transpColour, false)); spuNode->AddProperty(_T("force"), _T("yes")); // buttons for (int i=0; i<(int)GetObjectsCount(); i++) { MenuObject* obj = GetObject(i); if (obj->IsButton()) spuNode->AddChild(obj->GetXML(SPUMUX_XML)); } // actions for (unsigned int i=0; iAddChild(m_actions[i]->GetXML(SPUMUX_XML)); streamNode->AddChild(spuNode); root->AddChild(streamNode); xml.SetRoot(root); return xml.Save(fileName); } wxXmlNode* Menu::GetXML(DVDFileType type, int playAllRegister, wxXmlNode* node) { if (node == NULL) node = new wxXmlNode(wxXML_ELEMENT_NODE, _T("menu")); if (type == DVDSTYLER_XML) { node->AddProperty(_T("videoFormat"), (m_videoFormat == vfPAL) ? _T("PAL") : _T("NTSC")); if (GetBackground().length()) node->AddProperty(_T("bgFile"), GetBackground()); else node->AddProperty(_T("bgColour"), SConv::ToString(GetBackgroundColour())); // add svg if (m_svg && m_svg->GetRoot()) node->AppendChild(GetSVGCopy()); } // add buttons info (action, etc.) for (int i=0; iIsButton()) node->AddChild(obj->GetXML(type, false, playAllRegister)); } // actions for (unsigned int i=0; iAddChild(m_actions[i]->GetXML(type)); return node; } bool Menu::PutXML(wxXmlNode* node) { if (node->GetName() == _T("spumux")) node = node->GetChildren(); if (node != NULL && node->GetName() == _T("menu")) { wxString val; m_videoFormat = vfPAL; if (node->GetPropVal(_T("videoFormat"), &val) && val == _T("NTSC")) m_videoFormat = vfNTSC; wxXmlNode* svgNode = XmlFindNode(node, wxT("svg")); if (svgNode) { wxXmlDocument xml; xml.SetRoot(svgNode->CloneNode()); wxMemoryOutputStream output; xml.Save(output); #if wxCHECK_VERSION(2,6,0) wxMemoryInputStream input(output); m_svg->Load(input); #else char* data = new char[output.GetSize()]; output.CopyTo(data, output.GetSize()); wxMemoryInputStream input(data, output.GetSize()); m_svg->Load(input); delete[] data; #endif } else // deprecated { if (node->GetPropVal(_T("bgFile"), &val)) SetBackground(val); else if (node->GetPropVal(_T("bgColour"), &val)) SetBackgroundColour(SConv::ToColour(val)); } for (wxXmlNode* child = node->GetChildren(); child != NULL; child = child->GetNext()) if (child->GetType() == wxXML_ELEMENT_NODE) AddObject(child); // fix for old version (<=1.5b5) if (m_svg->GetElementById(wxT("objects")) == NULL) { wxSVGGElement* objectsElem = new wxSVGGElement; objectsElem->SetId(wxT("objects")); m_svg->GetRoot()->AppendChild(objectsElem); wxSVGGElement* buttonsElem = new wxSVGGElement; buttonsElem->SetId(wxT("buttons")); m_svg->GetRoot()->AppendChild(buttonsElem); m_svg->GetRootElement()->GetChildren(); for (unsigned int i = 0; i < m_objects.GetCount(); i++) { MenuObject& obj = *m_objects.Item(i); wxSVGElement* useElem = m_svg->GetElementById(obj.GetId()); useElem->GetParent()->RemoveChild(useElem); if (obj.IsButton()) buttonsElem->AppendChild(useElem); else objectsElem->AppendChild(useElem); } } } return true; } wxString Menu::AddObject(wxXmlNode* node, bool fixPosition) { if (node->GetName() == _T("action")) { MenuAction* action = new MenuAction(); if (!action->PutXML(node)) { delete action; return wxT(""); } m_actions.Add(action); return action->GetId(); } else if (node->GetName() == _T("button") || node->GetName() == _T("object")) { MenuObject* obj = new MenuObject(this); if (!obj->PutXML(node)) { delete obj; return wxT(""); } if (fixPosition) // for copy & paste FixPosition(obj); m_objects.Add(obj); return obj->GetId(); } return wxT(""); } void Menu::FixPosition(MenuObject* obj) { if (obj->GetX() == -1) obj->SetX(GetResolution().x/4); if (obj->GetY() == -1) obj->SetY(GetResolution().y/4); while (obj->GetX()GetY()GetX() == obj->GetX() && obj1->GetY() == obj->GetY()) { found = true; break; } } if (!found) break; obj->SetX(obj->GetX()+16); obj->SetY(obj->GetY()+16); } }