/* Copyright (C) 2019 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "ScenarioEditor/ScenarioEditor.h"
#include "Common/Tools.h"
#include "Common/Brushes.h"
#include "Common/MiscState.h"
#include "Common/ObjectSettings.h"
#include "GameInterface/Messages.h"
#include
#include
#include
using AtlasMessage::Position;
class TransformObject : public StateDrivenTool
{
DECLARE_DYNAMIC_CLASS(TransformObject);
int m_dx, m_dy;
AtlasMessage::ObjectID m_lastSelected;
wxPoint m_startPoint;
Position m_entPosition;
// TODO: If we don't plan to change hotkeys, just replace with evt.ShiftDown(), etc.
static const wxKeyCode SELECTION_ADD_HOTKEY = WXK_SHIFT;
static const wxKeyCode SELECTION_REMOVE_HOTKEY = WXK_CONTROL; // COMMAND on Macs
static const wxKeyCode SELECTION_ACTORS_HOTKEY = WXK_ALT;
public:
TransformObject() : m_lastSelected(0)
{
SetState(&Waiting);
}
virtual void OnCommand(const wxString& command, void* WXUNUSED(userData))
{
if (command == _T("copy"))
OnCopy();
else if (command == _T("paste"))
OnPasteStart();
}
void OnDisable()
{
g_SelectedObjects.clear();
g_SelectedObjects.NotifyObservers();
POST_MESSAGE(SetSelectionPreview, (g_SelectedObjects));
}
bool OnCopy() const
{
if (g_SelectedObjects.empty())
return false;
AtlasMessage::qGetObjectMapSettings info(g_SelectedObjects);
info.Post();
if (wxTheClipboard->Open())
{
wxString text(info.xmldata.c_str());
wxTheClipboard->SetData(new wxTextDataObject(text));
wxTheClipboard->Close();
}
return true;
}
void OnPasteStart()
{
wxString entities;
if (wxTheClipboard->Open())
{
if (wxTheClipboard->IsSupported(wxDF_TEXT))
{
wxTextDataObject data;
wxTheClipboard->GetData(data);
entities = data.GetText();
}
wxTheClipboard->Close();
}
// First do we need to check if it is a correct xml string
wxInputStream* is = new wxStringInputStream(entities);
wxXmlDocument doc;
if (!doc.Load(*is))
return;
// Entities, Entity(1.*)
wxXmlNode* root = doc.GetRoot();
if (root->GetName() != wxT("Entities"))
return;
// Template, position,orientation
const wxXmlNode* child = root->GetChildren();
while (child)
{
if (child->GetName() != wxT("Entity"))
return;
child = child->GetNext();
}
g_SelectedObjects.clear();
POST_MESSAGE(SetSelectionPreview, (g_SelectedObjects));
// is the source code get here now you can add the objects to scene(preview)
// store id to move
child = root->GetChildren();
while (child)
{
wxString templateName;
Position entityPos;
long playerId = 0;
double orientation = 0;
unsigned int actorSeed = 0;
const wxXmlNode* xmlData = child->GetChildren();
while (xmlData)
{
if (xmlData->GetName() == wxT("Template"))
templateName = xmlData->GetNodeContent();
else if (xmlData->GetName() == wxT("Position"))
{
wxString x, z;
xmlData->GetAttribute(wxT("x"), &x);
xmlData->GetAttribute(wxT("z"), &z);
double aux, aux2;
x.ToDouble(&aux);
z.ToDouble(&aux2);
entityPos = Position(aux, 0, aux2);
}
else if (xmlData->GetName() == wxT("Orientation"))
{
wxString y;
xmlData->GetAttribute(wxT("y"), &y);
y.ToDouble(&orientation);
}
else if (xmlData->GetName() == wxT("Player"))
{
wxString x(xmlData->GetNodeContent());
x.ToLong(&playerId);
}
else if (xmlData->GetName() == wxT("ActorSeed"))
{
wxString x(xmlData->GetNodeContent());
unsigned long xTmp = 0;
x.ToULong(&xTmp);
wxASSERT(xTmp <= (unsigned long)std::numeric_limits::max());
actorSeed = xTmp;
}
xmlData = xmlData->GetNext();
}
//Update current Ownership
this->GetScenarioEditor().GetObjectSettings().SetPlayerID(playerId);
this->GetScenarioEditor().GetObjectSettings().NotifyObservers();
POST_MESSAGE(ObjectPreview, ((std::wstring)templateName.c_str(), GetScenarioEditor().GetObjectSettings().GetSettings(), entityPos, false, Position(), orientation, actorSeed, false));
child = child->GetNext();
}
//Set state paste for preview the new objects
this->SetState(&Pasting);
//Update the objects to current mouse position
OnMovingPaste();
}
void OnMovingPaste()
{
//Move the preview(s) object(s)
POST_MESSAGE(MoveObjectPreview, ((m_entPosition)));
}
void OnPasteEnd(bool canceled)
{
if (canceled)
//delete previews objects
POST_MESSAGE(ObjectPreview, (_T(""), GetScenarioEditor().GetObjectSettings().GetSettings(), Position(), false, Position(), 0, 0, true));
else
{
ScenarioEditor::GetCommandProc().FinaliseLastCommand();
//Create new Objects and delete preview objects
POST_MESSAGE(ObjectPreviewToEntity, ());
AtlasMessage::qGetCurrentSelection currentSelection;
currentSelection.Post();
g_SelectedObjects = *currentSelection.ids;
}
//when all is done set default state
this->SetState(&Waiting);
}
// TODO: keys to rotate/move object?
struct sWaiting : public State
{
bool OnMouse(TransformObject* obj, wxMouseEvent& evt)
{
if (evt.LeftDClick() && AtlasMessage::ObjectIDIsValid(obj->m_lastSelected))
{
SET_STATE(SelectSimilar);
return true;
}
else if (evt.LeftDown())
{
bool selectionAdd = wxGetKeyState(SELECTION_ADD_HOTKEY);
bool selectionRemove = wxGetKeyState(SELECTION_REMOVE_HOTKEY);
bool selectionActors = wxGetKeyState(SELECTION_ACTORS_HOTKEY);
// New selection - never merge with movements of other objects
ScenarioEditor::GetCommandProc().FinaliseLastCommand();
// Select the object clicked on:
AtlasMessage::qPickObject qry(Position(evt.GetPosition()), selectionActors);
qry.Post();
// Check they actually clicked on a valid object
if (AtlasMessage::ObjectIDIsValid(qry.id))
{
std::vector::iterator it = std::find(g_SelectedObjects.begin(), g_SelectedObjects.end(), qry.id);
bool objectIsSelected = (it != g_SelectedObjects.end());
if (selectionRemove)
{
// Remove from selection
if (objectIsSelected)
g_SelectedObjects.erase(it);
}
else if (!objectIsSelected)
{
// Add to selection
if (!selectionAdd)
g_SelectedObjects.clear();
g_SelectedObjects.push_back(qry.id);
}
obj->m_lastSelected = qry.id;
// If we're selecting the whole group
if (!selectionAdd && !selectionRemove && !g_SelectedObjects.empty())
{
// Remember the screen-space offset of the mouse from the
// object's centre, so we can add that back when moving it
// (instead of just moving the object's centre to directly
// beneath the mouse)
obj->m_dx = qry.offsetx;
obj->m_dy = qry.offsety;
SET_STATE(Dragging);
}
g_SelectedObjects.NotifyObservers();
POST_MESSAGE(SetSelectionPreview, (g_SelectedObjects));
}
else
{
// Bandboxing
obj->m_lastSelected = 0;
obj->m_startPoint = evt.GetPosition();
SET_STATE(Bandboxing);
}
return true;
}
else if (!g_SelectedObjects.empty() && ((evt.Dragging() && evt.RightIsDown()) || evt.RightDown()))
{
SET_STATE(Rotating);
return true;
}
else if (evt.Moving())
{
//Save position for smooth paste position
obj->m_entPosition = Position(evt.GetPosition());
// Prevent certain events from reaching game UI in this mode
// to prevent selection ring confusion
return true;
}
else
return false;
}
bool OnKey(TransformObject* obj, wxKeyEvent& evt, KeyEventType type)
{
if (type == KEY_CHAR && evt.GetKeyCode() == WXK_DELETE)
{
POST_COMMAND(DeleteObjects, (g_SelectedObjects));
g_SelectedObjects.clear();
g_SelectedObjects.NotifyObservers();
POST_MESSAGE(SetSelectionPreview, (g_SelectedObjects));
return true;
}
else if (type == KEY_CHAR && (evt.GetKeyCode() >= '0' && evt.GetKeyCode() <= '9'))
{
int playerID = evt.GetKeyCode() - '0';
obj->GetScenarioEditor().GetObjectSettings().SetPlayerID(playerID);
obj->GetScenarioEditor().GetObjectSettings().NotifyObservers();
return true;
}
else if (evt.GetModifiers() == wxMOD_CONTROL)
{
if (evt.GetKeyCode() == 'C')
return obj->OnCopy();
else if (evt.GetKeyCode() == 'V')
{
obj->OnPasteStart();
return true;
}
}
return false;
}
}
Waiting;
struct sDragging : public State
{
bool OnMouse(TransformObject* obj, wxMouseEvent& evt)
{
if (evt.LeftUp())
{
POST_MESSAGE(ResetSelectionColor, ());
SET_STATE(Waiting);
return true;
}
else if (evt.Dragging())
{
Position pos(evt.GetPosition() + wxPoint(obj->m_dx, obj->m_dy));
POST_COMMAND(MoveObjects, (g_SelectedObjects, obj->m_lastSelected, pos));
return true;
}
else
return false;
}
bool OnKey(TransformObject* obj, wxKeyEvent& evt, KeyEventType type)
{
if (type == KEY_UP && evt.GetKeyCode() == WXK_ESCAPE)
{
// Cancel move action
ScenarioEditor::GetCommandProc().FinaliseLastCommand();
ScenarioEditor::GetCommandProc().Undo();
SET_STATE(Waiting);
return true;
}
else
return false;
}
}
Dragging;
struct sBandboxing : public State
{
bool OnMouse(TransformObject* obj, wxMouseEvent& evt)
{
if (evt.LeftIsDown() && evt.Dragging())
{
// Update bandbox overlay
POST_MESSAGE(SetBandbox, (true, obj->m_startPoint.x, obj->m_startPoint.y, evt.GetPosition().x, evt.GetPosition().y));
return true;
}
else if (evt.LeftUp())
{
bool selectionAdd = wxGetKeyState(SELECTION_ADD_HOTKEY);
bool selectionRemove = wxGetKeyState(SELECTION_REMOVE_HOTKEY);
bool selectionActors = wxGetKeyState(SELECTION_ACTORS_HOTKEY);
// Now we have both corners of the box, pick objects
AtlasMessage::qPickObjectsInRect qry(Position(obj->m_startPoint), Position(evt.GetPosition()), selectionActors);
qry.Post();
std::vector ids = *qry.ids;
if (!selectionAdd && !selectionRemove)
{
// Just copy new selections (clears list if no selections)
g_SelectedObjects = ids;
}
else
{
for (size_t i = 0; i < ids.size(); ++i)
{
std::vector::iterator it = std::find(g_SelectedObjects.begin(), g_SelectedObjects.end(), ids[i]);
bool objectIsSelected = (it != g_SelectedObjects.end());
if (selectionRemove)
{
// Remove from selection
if (objectIsSelected)
g_SelectedObjects.erase(it);
}
else if (!objectIsSelected)
{
// Add to selection
g_SelectedObjects.push_back(ids[i]);
}
}
}
POST_MESSAGE(SetBandbox, (false, 0, 0, 0, 0));
g_SelectedObjects.NotifyObservers();
POST_MESSAGE(SetSelectionPreview, (g_SelectedObjects));
SET_STATE(Waiting);
return true;
}
else
return false;
}
bool OnKey(TransformObject* obj, wxKeyEvent& evt, KeyEventType type)
{
if (type == KEY_UP && evt.GetKeyCode() == WXK_ESCAPE)
{
// Clear bandbox and return to waiting state
POST_MESSAGE(SetBandbox, (false, 0, 0, 0, 0));
SET_STATE(Waiting);
return true;
}
else
return false;
}
}
Bandboxing;
struct sSelectSimilar : public State
{
bool OnMouse(TransformObject* obj, wxMouseEvent& evt)
{
if (evt.LeftUp())
{
bool selectionAdd = wxGetKeyState(SELECTION_ADD_HOTKEY);
bool selectionRemove = wxGetKeyState(SELECTION_REMOVE_HOTKEY);
// Select similar objects
AtlasMessage::qPickSimilarObjects qry(obj->m_lastSelected);
qry.Post();
std::vector ids = *qry.ids;
if (!selectionAdd && !selectionRemove)
{
// Just copy new selections (clears list if no selections)
g_SelectedObjects = ids;
}
else
{
for (size_t i = 0; i < ids.size(); ++i)
{
std::vector::iterator it = std::find(g_SelectedObjects.begin(), g_SelectedObjects.end(), ids[i]);
bool objectIsSelected = (it != g_SelectedObjects.end());
if (selectionRemove)
{
// Remove from selection
if (objectIsSelected)
g_SelectedObjects.erase(it);
}
else if (!objectIsSelected)
{
// Add to selection
g_SelectedObjects.push_back(ids[i]);
}
}
}
g_SelectedObjects.NotifyObservers();
POST_MESSAGE(SetSelectionPreview, (g_SelectedObjects));
SET_STATE(Waiting);
return true;
}
else
return false;
}
}
SelectSimilar;
struct sPasting : public State
{
bool OnMouse(TransformObject* obj, wxMouseEvent& evt)
{
if (evt.Moving())
{
//Move the object
obj->m_entPosition = Position(evt.GetPosition());
obj->OnMovingPaste();
return true;
}
else if (evt.LeftDown())
{
//Place the object and update
obj->OnPasteEnd(false);
return true;
}
else
return false;
}
bool OnKey(TransformObject* obj, wxKeyEvent& evt, KeyEventType type)
{
if (type == KEY_CHAR && evt.GetKeyCode() == WXK_ESCAPE)
{
obj->OnPasteEnd(true);
return true;
}
else
return false;
}
}
Pasting;
struct sRotating : public State
{
bool fromCenterPoint;
void OnEnter(TransformObject* WXUNUSED(obj))
{
fromCenterPoint = true;
}
bool OnMouse(TransformObject* obj, wxMouseEvent& evt)
{
if (evt.RightUp())
{
POST_MESSAGE(ResetSelectionColor, ());
SET_STATE(Waiting);
return true;
}
else if (evt.Dragging())
{
bool fromGlobalAndIndividualCenterPoints = !evt.ControlDown() && !evt.ShiftDown();
bool newFromCenterPoint = evt.ShiftDown() || fromGlobalAndIndividualCenterPoints;
if (newFromCenterPoint != fromCenterPoint)
{
ScenarioEditor::GetCommandProc().FinaliseLastCommand();
fromCenterPoint = newFromCenterPoint;
}
Position pos(evt.GetPosition());
if (fromCenterPoint)
POST_COMMAND(RotateObjectsFromCenterPoint, (g_SelectedObjects, pos, fromGlobalAndIndividualCenterPoints));
else
POST_COMMAND(RotateObject, (g_SelectedObjects, pos));
return true;
}
return false;
}
bool OnKey(TransformObject* obj, wxKeyEvent& evt, KeyEventType type)
{
if (type == KEY_UP && evt.GetKeyCode() == WXK_ESCAPE)
{
// Cancel move action
ScenarioEditor::GetCommandProc().FinaliseLastCommand();
ScenarioEditor::GetCommandProc().Undo();
SET_STATE(Waiting);
return true;
}
return false;
}
}
Rotating;
};
IMPLEMENT_DYNAMIC_CLASS(TransformObject, StateDrivenTool);