1112 lines
36 KiB
C++
1112 lines
36 KiB
C++
|
/*
|
||
|
==============================================================================
|
||
|
|
||
|
This file is part of the JUCE library.
|
||
|
Copyright (c) 2020 - Raw Material Software Limited
|
||
|
|
||
|
JUCE is an open source library subject to commercial or open-source
|
||
|
licensing.
|
||
|
|
||
|
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||
|
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||
|
|
||
|
End User License Agreement: www.juce.com/juce-6-licence
|
||
|
Privacy Policy: www.juce.com/juce-privacy-policy
|
||
|
|
||
|
Or: You may also use this code under the terms of the GPL v3 (see
|
||
|
www.gnu.org/licenses).
|
||
|
|
||
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||
|
DISCLAIMED.
|
||
|
|
||
|
==============================================================================
|
||
|
*/
|
||
|
|
||
|
#include "../Application/jucer_Headers.h"
|
||
|
#include "jucer_JucerDocument.h"
|
||
|
#include "jucer_ObjectTypes.h"
|
||
|
#include "UI/jucer_JucerDocumentEditor.h"
|
||
|
#include "Components/jucer_ComponentUndoableAction.h"
|
||
|
|
||
|
//==============================================================================
|
||
|
ComponentLayout::ComponentLayout()
|
||
|
: document (nullptr),
|
||
|
nextCompUID (1)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
ComponentLayout::~ComponentLayout()
|
||
|
{
|
||
|
components.clear();
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void ComponentLayout::changed()
|
||
|
{
|
||
|
if (document != nullptr)
|
||
|
document->changed();
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::perform (UndoableAction* action, const String& actionName)
|
||
|
{
|
||
|
jassert (document != nullptr);
|
||
|
|
||
|
if (document != nullptr)
|
||
|
{
|
||
|
document->getUndoManager().perform (action, actionName);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
std::unique_ptr<UndoableAction> deleter (action);
|
||
|
action->perform();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void ComponentLayout::clearComponents()
|
||
|
{
|
||
|
selected.deselectAll();
|
||
|
selected.dispatchPendingMessages();
|
||
|
components.clear();
|
||
|
changed();
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
class AddCompAction : public UndoableAction
|
||
|
{
|
||
|
public:
|
||
|
AddCompAction (XmlElement* const xml_, ComponentLayout& layout_)
|
||
|
: indexAdded (-1),
|
||
|
xml (xml_),
|
||
|
layout (layout_)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
bool perform()
|
||
|
{
|
||
|
showCorrectTab();
|
||
|
Component* const newComp = layout.addComponentFromXml (*xml, false);
|
||
|
jassert (newComp != nullptr);
|
||
|
|
||
|
indexAdded = layout.indexOfComponent (newComp);
|
||
|
jassert (indexAdded >= 0);
|
||
|
return indexAdded >= 0;
|
||
|
}
|
||
|
|
||
|
bool undo()
|
||
|
{
|
||
|
showCorrectTab();
|
||
|
layout.removeComponent (layout.getComponent (indexAdded), false);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
int getSizeInUnits() { return 10; }
|
||
|
|
||
|
int indexAdded;
|
||
|
|
||
|
private:
|
||
|
std::unique_ptr<XmlElement> xml;
|
||
|
ComponentLayout& layout;
|
||
|
|
||
|
static void showCorrectTab()
|
||
|
{
|
||
|
if (JucerDocumentEditor* const ed = JucerDocumentEditor::getActiveDocumentHolder())
|
||
|
ed->showLayout();
|
||
|
}
|
||
|
|
||
|
AddCompAction (const AddCompAction&);
|
||
|
AddCompAction& operator= (const AddCompAction&);
|
||
|
};
|
||
|
|
||
|
//==============================================================================
|
||
|
class DeleteCompAction : public ComponentUndoableAction <Component>
|
||
|
{
|
||
|
public:
|
||
|
DeleteCompAction (Component* const comp, ComponentLayout& l)
|
||
|
: ComponentUndoableAction <Component> (comp, l),
|
||
|
oldIndex (-1)
|
||
|
{
|
||
|
if (ComponentTypeHandler* const h = ComponentTypeHandler::getHandlerFor (*comp))
|
||
|
xml.reset (h->createXmlFor (comp, &layout));
|
||
|
else
|
||
|
jassertfalse;
|
||
|
|
||
|
oldIndex = l.indexOfComponent (comp);
|
||
|
}
|
||
|
|
||
|
bool perform()
|
||
|
{
|
||
|
showCorrectTab();
|
||
|
layout.removeComponent (getComponent(), false);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool undo()
|
||
|
{
|
||
|
Component* c = layout.addComponentFromXml (*xml, false);
|
||
|
jassert (c != nullptr);
|
||
|
|
||
|
layout.moveComponentZOrder (layout.indexOfComponent (c), oldIndex);
|
||
|
|
||
|
showCorrectTab();
|
||
|
return c != nullptr;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
std::unique_ptr<XmlElement> xml;
|
||
|
int oldIndex;
|
||
|
};
|
||
|
|
||
|
void ComponentLayout::removeComponent (Component* comp, const bool undoable)
|
||
|
{
|
||
|
if (comp != nullptr && components.contains (comp))
|
||
|
{
|
||
|
if (undoable)
|
||
|
{
|
||
|
perform (new DeleteCompAction (comp, *this), "Delete components");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
selected.deselect (comp);
|
||
|
selected.changed (true); // synchronous message to get rid of any property components
|
||
|
|
||
|
components.removeObject (comp);
|
||
|
changed();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
class FrontBackCompAction : public ComponentUndoableAction <Component>
|
||
|
{
|
||
|
public:
|
||
|
FrontBackCompAction (Component* const comp, ComponentLayout& l, int newIndex_)
|
||
|
: ComponentUndoableAction <Component> (comp, l),
|
||
|
newIndex (newIndex_)
|
||
|
{
|
||
|
oldIndex = l.indexOfComponent (comp);
|
||
|
}
|
||
|
|
||
|
bool perform()
|
||
|
{
|
||
|
showCorrectTab();
|
||
|
Component* comp = layout.getComponent (oldIndex);
|
||
|
layout.moveComponentZOrder (oldIndex, newIndex);
|
||
|
newIndex = layout.indexOfComponent (comp);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool undo()
|
||
|
{
|
||
|
showCorrectTab();
|
||
|
layout.moveComponentZOrder (newIndex, oldIndex);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
int newIndex, oldIndex;
|
||
|
};
|
||
|
|
||
|
void ComponentLayout::moveComponentZOrder (int oldIndex, int newIndex)
|
||
|
{
|
||
|
jassert (components [oldIndex] != nullptr);
|
||
|
|
||
|
if (oldIndex != newIndex && components [oldIndex] != nullptr)
|
||
|
{
|
||
|
components.move (oldIndex, newIndex);
|
||
|
changed();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::componentToFront (Component* comp, const bool undoable)
|
||
|
{
|
||
|
if (comp != nullptr && components.contains (comp))
|
||
|
{
|
||
|
if (undoable)
|
||
|
perform (new FrontBackCompAction (comp, *this, -1), "Move components to front");
|
||
|
else
|
||
|
moveComponentZOrder (components.indexOf (comp), -1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::componentToBack (Component* comp, const bool undoable)
|
||
|
{
|
||
|
if (comp != nullptr && components.contains (comp))
|
||
|
{
|
||
|
if (undoable)
|
||
|
perform (new FrontBackCompAction (comp, *this, 0), "Move components to back");
|
||
|
else
|
||
|
moveComponentZOrder (components.indexOf (comp), 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
const char* const ComponentLayout::clipboardXmlTag = "COMPONENTS";
|
||
|
|
||
|
void ComponentLayout::copySelectedToClipboard()
|
||
|
{
|
||
|
if (selected.getNumSelected() == 0)
|
||
|
return;
|
||
|
|
||
|
XmlElement clip (clipboardXmlTag);
|
||
|
|
||
|
for (int i = 0; i < components.size(); ++i)
|
||
|
{
|
||
|
auto c = components.getUnchecked(i);
|
||
|
|
||
|
if (selected.isSelected (c))
|
||
|
{
|
||
|
if (auto type = ComponentTypeHandler::getHandlerFor (*c))
|
||
|
{
|
||
|
auto e = type->createXmlFor (c, this);
|
||
|
clip.addChildElement (e);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SystemClipboard::copyTextToClipboard (clip.toString());
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::paste()
|
||
|
{
|
||
|
if (auto doc = parseXMLIfTagMatches (SystemClipboard::getTextFromClipboard(), clipboardXmlTag))
|
||
|
{
|
||
|
selected.deselectAll();
|
||
|
|
||
|
for (auto* e : doc->getChildIterator())
|
||
|
if (Component* newComp = addComponentFromXml (*e, true))
|
||
|
selected.addToSelection (newComp);
|
||
|
|
||
|
startDragging();
|
||
|
dragSelectedComps (Random::getSystemRandom().nextInt (40),
|
||
|
Random::getSystemRandom().nextInt (40));
|
||
|
endDragging();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::deleteSelected()
|
||
|
{
|
||
|
const SelectedItemSet <Component*> temp (selected);
|
||
|
selected.deselectAll();
|
||
|
selected.changed (true); // synchronous message to get rid of any property components
|
||
|
|
||
|
if (temp.getNumSelected() > 0)
|
||
|
{
|
||
|
for (int i = temp.getNumSelected(); --i >= 0;)
|
||
|
removeComponent (temp.getSelectedItem (i), true);
|
||
|
|
||
|
changed();
|
||
|
|
||
|
if (document != nullptr)
|
||
|
document->dispatchPendingMessages(); // forces the change to propagate before a paint() callback can happen,
|
||
|
// in case there are components floating around that are now dangling pointers
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::selectAll()
|
||
|
{
|
||
|
for (int i = 0; i < components.size(); ++i)
|
||
|
selected.addToSelection (components.getUnchecked (i));
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::selectedToFront()
|
||
|
{
|
||
|
const SelectedItemSet <Component*> temp (selected);
|
||
|
|
||
|
for (int i = temp.getNumSelected(); --i >= 0;)
|
||
|
componentToFront (temp.getSelectedItem(i), true);
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::selectedToBack()
|
||
|
{
|
||
|
const SelectedItemSet <Component*> temp (selected);
|
||
|
|
||
|
for (int i = 0; i < temp.getNumSelected(); ++i)
|
||
|
componentToBack (temp.getSelectedItem(i), true);
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::alignTop()
|
||
|
{
|
||
|
if (selected.getNumSelected() > 1)
|
||
|
{
|
||
|
auto* main = selected.getSelectedItem (0);
|
||
|
auto yPos = main->getY();
|
||
|
|
||
|
for (auto* other : selected)
|
||
|
{
|
||
|
if (other != main)
|
||
|
setComponentBoundsAndProperties (other,
|
||
|
other->getBounds().withPosition (other->getX(), yPos),
|
||
|
main, true);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::alignRight()
|
||
|
{
|
||
|
if (selected.getNumSelected() > 1)
|
||
|
{
|
||
|
auto* main = selected.getSelectedItem (0);
|
||
|
auto rightPos = main->getRight();
|
||
|
|
||
|
for (auto* other : selected)
|
||
|
{
|
||
|
if (other != main)
|
||
|
setComponentBoundsAndProperties (other, other->getBounds().withPosition (rightPos - other->getWidth(), other->getY()), main, true);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::alignBottom()
|
||
|
{
|
||
|
if (selected.getNumSelected() > 1)
|
||
|
{
|
||
|
auto* main = selected.getSelectedItem (0);
|
||
|
auto bottomPos = main->getBottom();
|
||
|
|
||
|
for (auto* other : selected)
|
||
|
{
|
||
|
if (other != main)
|
||
|
setComponentBoundsAndProperties (other, other->getBounds().withPosition (other->getX(), bottomPos - other->getHeight()), main, true);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::alignLeft()
|
||
|
{
|
||
|
if (selected.getNumSelected() > 1)
|
||
|
{
|
||
|
auto* main = selected.getSelectedItem (0);
|
||
|
auto xPos = main->getX();
|
||
|
|
||
|
for (auto* other : selected)
|
||
|
{
|
||
|
if (other != main)
|
||
|
setComponentBoundsAndProperties (other, other->getBounds().withPosition (xPos, other->getY()), main, true);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::bringLostItemsBackOnScreen (int width, int height)
|
||
|
{
|
||
|
for (int i = components.size(); --i >= 0;)
|
||
|
{
|
||
|
Component* const c = components[i];
|
||
|
|
||
|
if (! c->getBounds().intersects (Rectangle<int> (0, 0, width, height)))
|
||
|
{
|
||
|
c->setTopLeftPosition (width / 2, height / 2);
|
||
|
updateStoredComponentPosition (c, false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Component* ComponentLayout::addNewComponent (ComponentTypeHandler* const type, int x, int y)
|
||
|
{
|
||
|
std::unique_ptr<Component> c (type->createNewComponent (getDocument()));
|
||
|
jassert (c != nullptr);
|
||
|
|
||
|
if (c != nullptr)
|
||
|
{
|
||
|
c->setSize (type->getDefaultWidth(), type->getDefaultHeight());
|
||
|
c->setCentrePosition (x, y);
|
||
|
updateStoredComponentPosition (c.get(), false);
|
||
|
|
||
|
c->getProperties().set ("id", nextCompUID++);
|
||
|
|
||
|
std::unique_ptr<XmlElement> xml (type->createXmlFor (c.get(), this));
|
||
|
c.reset (addComponentFromXml (*xml, true));
|
||
|
|
||
|
String memberName (build_tools::makeValidIdentifier (type->getClassName (c.get()), true, true, false));
|
||
|
setComponentMemberVariableName (c.get(), memberName);
|
||
|
|
||
|
selected.selectOnly (c.get());
|
||
|
}
|
||
|
|
||
|
return c.release();
|
||
|
}
|
||
|
|
||
|
|
||
|
Component* ComponentLayout::addComponentFromXml (const XmlElement& xml, const bool undoable)
|
||
|
{
|
||
|
if (undoable)
|
||
|
{
|
||
|
AddCompAction* const action = new AddCompAction (new XmlElement (xml), *this);
|
||
|
perform (action, "Add new components");
|
||
|
return components [action->indexAdded];
|
||
|
}
|
||
|
|
||
|
if (ComponentTypeHandler* const type
|
||
|
= ComponentTypeHandler::getHandlerForXmlTag (xml.getTagName()))
|
||
|
{
|
||
|
std::unique_ptr<Component> newComp (type->createNewComponent (getDocument()));
|
||
|
|
||
|
if (type->restoreFromXml (xml, newComp.get(), this))
|
||
|
{
|
||
|
// ensure that the new comp's name is unique
|
||
|
setComponentMemberVariableName (newComp.get(), getComponentMemberVariableName (newComp.get()));
|
||
|
|
||
|
// check for duped IDs..
|
||
|
while (findComponentWithId (ComponentTypeHandler::getComponentId (newComp.get())) != nullptr)
|
||
|
ComponentTypeHandler::setComponentId (newComp.get(), Random::getSystemRandom().nextInt64());
|
||
|
|
||
|
components.add (newComp.get());
|
||
|
changed();
|
||
|
return newComp.release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
Component* ComponentLayout::findComponentWithId (const int64 componentId) const
|
||
|
{
|
||
|
for (int i = 0; i < components.size(); ++i)
|
||
|
if (ComponentTypeHandler::getComponentId (components.getUnchecked(i)) == componentId)
|
||
|
return components.getUnchecked(i);
|
||
|
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
static const char* const dimensionSuffixes[] = { "X", "Y", "W", "H" };
|
||
|
|
||
|
Component* ComponentLayout::getComponentRelativePosTarget (Component* comp, int whichDimension) const
|
||
|
{
|
||
|
jassert (comp != nullptr);
|
||
|
|
||
|
if (PaintElement* const pe = dynamic_cast<PaintElement*> (comp))
|
||
|
{
|
||
|
int64 compId;
|
||
|
|
||
|
if (whichDimension == 0) compId = pe->getPosition().relativeToX;
|
||
|
else if (whichDimension == 1) compId = pe->getPosition().relativeToY;
|
||
|
else if (whichDimension == 2) compId = pe->getPosition().relativeToW;
|
||
|
else compId = pe->getPosition().relativeToH;
|
||
|
|
||
|
return findComponentWithId (compId);
|
||
|
}
|
||
|
|
||
|
return findComponentWithId (comp->getProperties() [String ("relativeTo") + dimensionSuffixes [whichDimension]]
|
||
|
.toString().getHexValue64());
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::setComponentRelativeTarget (Component* comp, int whichDimension, Component* compToBeRelativeTo)
|
||
|
{
|
||
|
PaintElement* const pe = dynamic_cast<PaintElement*> (comp);
|
||
|
|
||
|
jassert (comp != nullptr);
|
||
|
jassert (pe != nullptr || components.contains (comp));
|
||
|
jassert (compToBeRelativeTo == nullptr || components.contains (compToBeRelativeTo));
|
||
|
jassert (compToBeRelativeTo == nullptr || ! dependsOnComponentForRelativePos (compToBeRelativeTo, comp));
|
||
|
|
||
|
if (compToBeRelativeTo != getComponentRelativePosTarget (comp, whichDimension)
|
||
|
&& (compToBeRelativeTo == nullptr || ! dependsOnComponentForRelativePos (compToBeRelativeTo, comp)))
|
||
|
{
|
||
|
const int64 compId = ComponentTypeHandler::getComponentId (compToBeRelativeTo);
|
||
|
|
||
|
Rectangle<int> oldBounds (comp->getBounds());
|
||
|
RelativePositionedRectangle pos;
|
||
|
|
||
|
if (pe != nullptr)
|
||
|
{
|
||
|
oldBounds = pe->getCurrentBounds (dynamic_cast<PaintRoutineEditor*> (pe->getParentComponent())->getComponentArea());
|
||
|
pos = pe->getPosition();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pos = ComponentTypeHandler::getComponentPosition (comp);
|
||
|
}
|
||
|
|
||
|
if (whichDimension == 0) pos.relativeToX = compId;
|
||
|
else if (whichDimension == 1) pos.relativeToY = compId;
|
||
|
else if (whichDimension == 2) pos.relativeToW = compId;
|
||
|
else if (whichDimension == 3) pos.relativeToH = compId;
|
||
|
|
||
|
if (pe != nullptr)
|
||
|
{
|
||
|
pe->setPosition (pos, true);
|
||
|
pe->setCurrentBounds (oldBounds, dynamic_cast<PaintRoutineEditor*> (pe->getParentComponent())->getComponentArea(), true);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
setComponentPosition (comp, pos, true);
|
||
|
comp->setBounds (oldBounds);
|
||
|
updateStoredComponentPosition (comp, false);
|
||
|
}
|
||
|
|
||
|
changed();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool ComponentLayout::dependsOnComponentForRelativePos (Component* comp, Component* possibleDependee) const
|
||
|
{
|
||
|
for (int i = 0; i < 4; ++i)
|
||
|
if (Component* const c = getComponentRelativePosTarget (comp, i))
|
||
|
if (c == possibleDependee || dependsOnComponentForRelativePos (c, possibleDependee))
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool ComponentLayout::isComponentPositionRelative (Component* comp) const
|
||
|
{
|
||
|
for (int i = 0; i < getNumComponents(); ++i)
|
||
|
if (dependsOnComponentForRelativePos (comp, getComponent (i)))
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
const int menuIdBase = 0x63240000;
|
||
|
|
||
|
PopupMenu ComponentLayout::getRelativeTargetMenu (Component* comp, int whichDimension) const
|
||
|
{
|
||
|
PopupMenu m;
|
||
|
|
||
|
auto current = getComponentRelativePosTarget (comp, whichDimension);
|
||
|
|
||
|
m.addItem (menuIdBase, "Relative to parent component", true, current == nullptr);
|
||
|
m.addSeparator();
|
||
|
|
||
|
for (int i = 0; i < components.size(); ++i)
|
||
|
{
|
||
|
auto* const c = components.getUnchecked (i);
|
||
|
|
||
|
if (c != nullptr && c != comp)
|
||
|
m.addItem (menuIdBase + i + 1,
|
||
|
"Relative to " + getComponentMemberVariableName (c)
|
||
|
+ " (class: " + ComponentTypeHandler::getHandlerFor (*c)->getClassName (c) + ")",
|
||
|
! dependsOnComponentForRelativePos (c, comp),
|
||
|
current == c);
|
||
|
}
|
||
|
|
||
|
return m;
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::processRelativeTargetMenuResult (Component* comp, int whichDimension, int menuResultID)
|
||
|
{
|
||
|
if (menuResultID != 0)
|
||
|
{
|
||
|
Component* const newTarget = components [menuResultID - menuIdBase - 1];
|
||
|
setComponentRelativeTarget (comp, whichDimension, newTarget);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
class ChangeCompPositionAction : public ComponentUndoableAction <Component>
|
||
|
{
|
||
|
public:
|
||
|
ChangeCompPositionAction (Component* const comp, ComponentLayout& l,
|
||
|
const RelativePositionedRectangle& newPos_)
|
||
|
: ComponentUndoableAction <Component> (comp, l),
|
||
|
newPos (newPos_), oldPos (ComponentTypeHandler::getComponentPosition (comp))
|
||
|
{
|
||
|
}
|
||
|
|
||
|
bool perform()
|
||
|
{
|
||
|
showCorrectTab();
|
||
|
layout.setComponentPosition (getComponent(), newPos, false);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool undo()
|
||
|
{
|
||
|
showCorrectTab();
|
||
|
layout.setComponentPosition (getComponent(), oldPos, false);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
RelativePositionedRectangle newPos, oldPos;
|
||
|
};
|
||
|
|
||
|
class ChangeCompBoundsAndPropertiesAction : public ComponentUndoableAction<Component>
|
||
|
{
|
||
|
public:
|
||
|
ChangeCompBoundsAndPropertiesAction (Component* const comp, ComponentLayout& l,
|
||
|
const Rectangle<int>& bounds, const NamedValueSet& props)
|
||
|
: ComponentUndoableAction <Component> (comp, l),
|
||
|
newBounds (bounds),
|
||
|
oldBounds (comp->getBounds()),
|
||
|
newProps (props),
|
||
|
oldProps(comp->getProperties())
|
||
|
{
|
||
|
}
|
||
|
|
||
|
bool perform()
|
||
|
{
|
||
|
showCorrectTab();
|
||
|
getComponent()->setBounds (newBounds);
|
||
|
getComponent()->getProperties() = newProps;
|
||
|
layout.updateStoredComponentPosition (getComponent(), false);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool undo()
|
||
|
{
|
||
|
showCorrectTab();
|
||
|
getComponent()->setBounds (oldBounds);
|
||
|
getComponent()->getProperties() = oldProps;
|
||
|
layout.updateStoredComponentPosition (getComponent(), false);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
Rectangle<int> newBounds, oldBounds;
|
||
|
NamedValueSet newProps, oldProps;
|
||
|
};
|
||
|
|
||
|
void ComponentLayout::setComponentPosition (Component* comp,
|
||
|
const RelativePositionedRectangle& newPos,
|
||
|
const bool undoable)
|
||
|
{
|
||
|
if (ComponentTypeHandler::getComponentPosition (comp) != newPos)
|
||
|
{
|
||
|
if (undoable)
|
||
|
{
|
||
|
perform (new ChangeCompPositionAction (comp, *this, newPos), "Move components");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ComponentTypeHandler::setComponentPosition (comp, newPos, this);
|
||
|
changed();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::setComponentBoundsAndProperties (Component* componentToPosition, const Rectangle<int>& newBounds,
|
||
|
Component* referenceComponent, const bool undoable)
|
||
|
{
|
||
|
auto props = NamedValueSet (componentToPosition->getProperties());
|
||
|
|
||
|
auto rect = ComponentTypeHandler::getComponentPosition (componentToPosition).rect;
|
||
|
auto referenceComponentPosition = ComponentTypeHandler::getComponentPosition (referenceComponent);
|
||
|
auto referenceComponentRect = referenceComponentPosition.rect;
|
||
|
|
||
|
rect.setModes (referenceComponentRect.getAnchorPointX(), referenceComponentRect.getPositionModeX(),
|
||
|
referenceComponentRect.getAnchorPointY(), referenceComponentRect.getPositionModeY(),
|
||
|
referenceComponentRect.getWidthMode(), referenceComponentRect.getHeightMode(),
|
||
|
componentToPosition->getBounds());
|
||
|
|
||
|
props.set ("pos", rect.toString());
|
||
|
props.set ("relativeToX", String::toHexString (referenceComponentPosition.relativeToX));
|
||
|
props.set ("relativeToY", String::toHexString (referenceComponentPosition.relativeToY));
|
||
|
props.set ("relativeToW", String::toHexString (referenceComponentPosition.relativeToW));
|
||
|
props.set ("relativeToH", String::toHexString (referenceComponentPosition.relativeToH));
|
||
|
|
||
|
if (componentToPosition->getBounds() != newBounds || componentToPosition->getProperties() != props)
|
||
|
{
|
||
|
if (undoable)
|
||
|
{
|
||
|
perform (new ChangeCompBoundsAndPropertiesAction (componentToPosition, *this, newBounds, props), "Change component bounds");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
componentToPosition->setBounds (newBounds);
|
||
|
componentToPosition->getProperties() = props;
|
||
|
updateStoredComponentPosition (componentToPosition, false);
|
||
|
changed();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::updateStoredComponentPosition (Component* comp, const bool undoable)
|
||
|
{
|
||
|
RelativePositionedRectangle newPos (ComponentTypeHandler::getComponentPosition (comp));
|
||
|
|
||
|
newPos.updateFromComponent (*comp, this);
|
||
|
|
||
|
setComponentPosition (comp, newPos, undoable);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void ComponentLayout::startDragging()
|
||
|
{
|
||
|
for (int i = 0; i < components.size(); ++i)
|
||
|
{
|
||
|
Component* const c = components[i];
|
||
|
c->getProperties().set ("xDragStart", c->getX());
|
||
|
c->getProperties().set ("yDragStart", c->getY());
|
||
|
}
|
||
|
|
||
|
jassert (document != nullptr);
|
||
|
document->beginTransaction();
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::dragSelectedComps (int dx, int dy, const bool allowSnap)
|
||
|
{
|
||
|
if (allowSnap && document != nullptr && selected.getNumSelected() > 1)
|
||
|
{
|
||
|
dx = document->snapPosition (dx);
|
||
|
dy = document->snapPosition (dy);
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < selected.getNumSelected(); ++i)
|
||
|
{
|
||
|
Component* const c = selected.getSelectedItem (i);
|
||
|
|
||
|
const int startX = c->getProperties() ["xDragStart"];
|
||
|
const int startY = c->getProperties() ["yDragStart"];
|
||
|
|
||
|
if (allowSnap && document != nullptr && selected.getNumSelected() == 1)
|
||
|
{
|
||
|
c->setTopLeftPosition (document->snapPosition (startX + dx),
|
||
|
document->snapPosition (startY + dy));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
c->setTopLeftPosition (startX + dx,
|
||
|
startY + dy);
|
||
|
}
|
||
|
|
||
|
updateStoredComponentPosition (c, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::endDragging()
|
||
|
{
|
||
|
// after the drag, roll back all the comps to their start position, then
|
||
|
// back to their finish positions using an undoable command.
|
||
|
document->beginTransaction();
|
||
|
|
||
|
for (int i = 0; i < selected.getNumSelected(); ++i)
|
||
|
{
|
||
|
Component* const c = selected.getSelectedItem (i);
|
||
|
|
||
|
const int newX = c->getX();
|
||
|
const int newY = c->getY();
|
||
|
|
||
|
const int startX = c->getProperties() ["xDragStart"];
|
||
|
const int startY = c->getProperties() ["yDragStart"];
|
||
|
|
||
|
c->setTopLeftPosition (startX, startY);
|
||
|
updateStoredComponentPosition (c, false);
|
||
|
|
||
|
c->setTopLeftPosition (newX, newY);
|
||
|
updateStoredComponentPosition (c, true);
|
||
|
}
|
||
|
|
||
|
document->beginTransaction();
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::moveSelectedComps (int dx, int dy, bool snap)
|
||
|
{
|
||
|
startDragging();
|
||
|
dragSelectedComps (dx, dy, snap);
|
||
|
endDragging();
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::stretchSelectedComps (int dw, int dh, bool allowSnap)
|
||
|
{
|
||
|
int neww, newh;
|
||
|
|
||
|
if (document != nullptr && selected.getNumSelected() == 1)
|
||
|
{
|
||
|
Component* const c = selected.getSelectedItem (0);
|
||
|
|
||
|
if (allowSnap)
|
||
|
{
|
||
|
int bot = c->getBottom() + dh;
|
||
|
int right = c->getRight() + dw;
|
||
|
bot = (dh != 0) ? document->snapPosition (bot) : bot;
|
||
|
right = (dw != 0) ? document->snapPosition (right) : right;
|
||
|
newh = bot - c->getY();
|
||
|
neww = right - c->getX();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
newh = c->getHeight() + dh;
|
||
|
neww = c->getWidth() + dw;
|
||
|
}
|
||
|
|
||
|
c->setSize (neww, newh);
|
||
|
|
||
|
updateStoredComponentPosition (c, true);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for (int i = 0; i < selected.getNumSelected(); ++i)
|
||
|
{
|
||
|
Component* const c = selected.getSelectedItem (i);
|
||
|
|
||
|
neww = c->getWidth() + dw;
|
||
|
newh = c->getHeight() + dh;
|
||
|
c->setSize (neww, newh);
|
||
|
|
||
|
updateStoredComponentPosition (c, true);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void ComponentLayout::fillInGeneratedCode (GeneratedCode& code) const
|
||
|
{
|
||
|
for (int i = 0; i < components.size(); ++i)
|
||
|
if (Component* const comp = components.getUnchecked(i))
|
||
|
if (ComponentTypeHandler* const type = ComponentTypeHandler::getHandlerFor (*comp))
|
||
|
type->fillInGeneratedCode (comp, code);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
String ComponentLayout::getComponentMemberVariableName (Component* comp) const
|
||
|
{
|
||
|
if (comp == nullptr)
|
||
|
return {};
|
||
|
|
||
|
String name (comp->getProperties() ["memberName"].toString());
|
||
|
|
||
|
if (name.isEmpty())
|
||
|
name = getUnusedMemberName (build_tools::makeValidIdentifier (comp->getName(), true, true, false), comp);
|
||
|
|
||
|
return name;
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::setComponentMemberVariableName (Component* comp, const String& newName)
|
||
|
{
|
||
|
jassert (comp != nullptr);
|
||
|
const String oldName (getComponentMemberVariableName (comp));
|
||
|
|
||
|
comp->getProperties().set ("memberName", String());
|
||
|
|
||
|
const String n (getUnusedMemberName (build_tools::makeValidIdentifier (newName, false, true, false), comp));
|
||
|
comp->getProperties().set ("memberName", n);
|
||
|
|
||
|
if (n != oldName)
|
||
|
changed();
|
||
|
}
|
||
|
|
||
|
String ComponentLayout::getUnusedMemberName (String nameRoot, Component* comp) const
|
||
|
{
|
||
|
String n (nameRoot);
|
||
|
|
||
|
while (CharacterFunctions::isDigit (nameRoot.getLastCharacter()))
|
||
|
nameRoot = nameRoot.dropLastCharacters (1);
|
||
|
|
||
|
int suffix = 2;
|
||
|
|
||
|
for (;;)
|
||
|
{
|
||
|
bool alreadyUsed = false;
|
||
|
|
||
|
for (int i = 0; i < components.size(); ++i)
|
||
|
{
|
||
|
if (components[i] != comp
|
||
|
&& components[i]->getProperties() ["memberName"] == n)
|
||
|
{
|
||
|
alreadyUsed = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (! alreadyUsed)
|
||
|
break;
|
||
|
|
||
|
n = nameRoot + String (suffix++);
|
||
|
}
|
||
|
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
String ComponentLayout::getComponentVirtualClassName (Component* comp) const
|
||
|
{
|
||
|
if (comp == nullptr)
|
||
|
return {};
|
||
|
|
||
|
return comp->getProperties() ["virtualName"];
|
||
|
}
|
||
|
|
||
|
void ComponentLayout::setComponentVirtualClassName (Component* comp, const String& newName)
|
||
|
{
|
||
|
jassert (comp != nullptr);
|
||
|
const String name (build_tools::makeValidIdentifier (newName, false, false, true));
|
||
|
|
||
|
if (name != getComponentVirtualClassName (comp))
|
||
|
{
|
||
|
comp->getProperties().set ("virtualName", name);
|
||
|
changed();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void ComponentLayout::addToXml (XmlElement& xml) const
|
||
|
{
|
||
|
for (int i = 0; i < components.size(); ++i)
|
||
|
if (ComponentTypeHandler* h = ComponentTypeHandler::getHandlerFor (*components [i]))
|
||
|
xml.addChildElement (h->createXmlFor (components [i], this));
|
||
|
}
|
||
|
|
||
|
static String bracketIfNeeded (const String& s)
|
||
|
{
|
||
|
return s.containsAnyOf ("+-*/%") ? "(" + s + ")" : s;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void positionToCode (const RelativePositionedRectangle& position,
|
||
|
const ComponentLayout* layout,
|
||
|
String& x, String& y, String& w, String& h)
|
||
|
{
|
||
|
// these are the code sections for the positions of the relative comps
|
||
|
String xrx, xry, xrw, xrh;
|
||
|
if (Component* const relCompX = layout != nullptr ? layout->findComponentWithId (position.relativeToX) : nullptr)
|
||
|
positionToCode (ComponentTypeHandler::getComponentPosition (relCompX), layout, xrx, xry, xrw, xrh);
|
||
|
|
||
|
String yrx, yry, yrw, yrh;
|
||
|
|
||
|
if (Component* const relCompY = layout != nullptr ? layout->findComponentWithId (position.relativeToY) : nullptr)
|
||
|
positionToCode (ComponentTypeHandler::getComponentPosition (relCompY), layout, yrx, yry, yrw, yrh);
|
||
|
|
||
|
String wrx, wry, wrw, wrh;
|
||
|
|
||
|
if (Component* const relCompW = (layout != nullptr && position.rect.getWidthMode() != PositionedRectangle::absoluteSize)
|
||
|
? layout->findComponentWithId (position.relativeToW) : nullptr)
|
||
|
positionToCode (ComponentTypeHandler::getComponentPosition (relCompW), layout, wrx, wry, wrw, wrh);
|
||
|
|
||
|
String hrx, hry, hrw, hrh;
|
||
|
|
||
|
if (Component* const relCompH = (layout != nullptr && position.rect.getHeightMode() != PositionedRectangle::absoluteSize)
|
||
|
? layout->findComponentWithId (position.relativeToH) : nullptr)
|
||
|
positionToCode (ComponentTypeHandler::getComponentPosition (relCompH), layout, hrx, hry, hrw, hrh);
|
||
|
|
||
|
// width
|
||
|
if (position.rect.getWidthMode() == PositionedRectangle::proportionalSize)
|
||
|
{
|
||
|
if (wrw.isNotEmpty())
|
||
|
w << "juce::roundToInt (" << bracketIfNeeded (wrw) << " * " << CodeHelpers::floatLiteral (position.rect.getWidth(), 4) << ")";
|
||
|
else
|
||
|
w << "proportionOfWidth (" << CodeHelpers::floatLiteral (position.rect.getWidth(), 4) << ")";
|
||
|
}
|
||
|
else if (position.rect.getWidthMode() == PositionedRectangle::parentSizeMinusAbsolute)
|
||
|
{
|
||
|
if (wrw.isNotEmpty())
|
||
|
w << bracketIfNeeded (wrw) << " - " << roundToInt (position.rect.getWidth());
|
||
|
else
|
||
|
w << "getWidth() - " << roundToInt (position.rect.getWidth());
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (wrw.isNotEmpty())
|
||
|
w << bracketIfNeeded (wrw) << " + ";
|
||
|
|
||
|
w << roundToInt (position.rect.getWidth());
|
||
|
}
|
||
|
|
||
|
// height
|
||
|
if (position.rect.getHeightMode() == PositionedRectangle::proportionalSize)
|
||
|
{
|
||
|
if (hrh.isNotEmpty())
|
||
|
h << "juce::roundToInt (" << bracketIfNeeded (hrh) << " * " << CodeHelpers::floatLiteral (position.rect.getHeight(), 4) << ")";
|
||
|
else
|
||
|
h << "proportionOfHeight (" << CodeHelpers::floatLiteral (position.rect.getHeight(), 4) << ")";
|
||
|
}
|
||
|
else if (position.rect.getHeightMode() == PositionedRectangle::parentSizeMinusAbsolute)
|
||
|
{
|
||
|
if (hrh.isNotEmpty())
|
||
|
h << bracketIfNeeded (hrh) << " - " << roundToInt (position.rect.getHeight());
|
||
|
else
|
||
|
h << "getHeight() - " << roundToInt (position.rect.getHeight());
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (hrh.isNotEmpty())
|
||
|
h << bracketIfNeeded (hrh) << " + ";
|
||
|
|
||
|
h << roundToInt (position.rect.getHeight());
|
||
|
}
|
||
|
|
||
|
// x-pos
|
||
|
if (position.rect.getPositionModeX() == PositionedRectangle::proportionOfParentSize)
|
||
|
{
|
||
|
if (xrx.isNotEmpty() && xrw.isNotEmpty())
|
||
|
x << bracketIfNeeded (xrx) << " + juce::roundToInt (" << bracketIfNeeded (xrw) << " * " << CodeHelpers::floatLiteral (position.rect.getX(), 4) << ")";
|
||
|
else
|
||
|
x << "proportionOfWidth (" << CodeHelpers::floatLiteral (position.rect.getX(), 4) << ")";
|
||
|
}
|
||
|
else if (position.rect.getPositionModeX() == PositionedRectangle::absoluteFromParentTopLeft)
|
||
|
{
|
||
|
if (xrx.isNotEmpty())
|
||
|
x << bracketIfNeeded (xrx) << " + ";
|
||
|
|
||
|
x << roundToInt (position.rect.getX());
|
||
|
}
|
||
|
else if (position.rect.getPositionModeX() == PositionedRectangle::absoluteFromParentBottomRight)
|
||
|
{
|
||
|
if (xrx.isNotEmpty())
|
||
|
x << bracketIfNeeded (xrx) << " + " << bracketIfNeeded (xrw);
|
||
|
else
|
||
|
x << "getWidth()";
|
||
|
|
||
|
const int d = roundToInt (position.rect.getX());
|
||
|
if (d != 0)
|
||
|
x << " - " << d;
|
||
|
}
|
||
|
else if (position.rect.getPositionModeX() == PositionedRectangle::absoluteFromParentCentre)
|
||
|
{
|
||
|
if (xrx.isNotEmpty())
|
||
|
x << bracketIfNeeded (xrx) << " + " << bracketIfNeeded (xrw) << " / 2";
|
||
|
else
|
||
|
x << "(getWidth() / 2)";
|
||
|
|
||
|
const int d = roundToInt (position.rect.getX());
|
||
|
if (d != 0)
|
||
|
x << " + " << d;
|
||
|
}
|
||
|
|
||
|
if (w != "0")
|
||
|
{
|
||
|
if (position.rect.getAnchorPointX() == PositionedRectangle::anchorAtRightOrBottom)
|
||
|
x << " - " << bracketIfNeeded (w);
|
||
|
else if (position.rect.getAnchorPointX() == PositionedRectangle::anchorAtCentre)
|
||
|
x << " - (" << bracketIfNeeded (w) << " / 2)";
|
||
|
}
|
||
|
|
||
|
// y-pos
|
||
|
if (position.rect.getPositionModeY() == PositionedRectangle::proportionOfParentSize)
|
||
|
{
|
||
|
if (yry.isNotEmpty() && yrh.isNotEmpty())
|
||
|
y << bracketIfNeeded (yry) << " + juce::roundToInt (" << bracketIfNeeded (yrh) << " * " << CodeHelpers::floatLiteral (position.rect.getY(), 4) << ")";
|
||
|
else
|
||
|
y << "proportionOfHeight (" << CodeHelpers::floatLiteral (position.rect.getY(), 4) << ")";
|
||
|
}
|
||
|
else if (position.rect.getPositionModeY() == PositionedRectangle::absoluteFromParentTopLeft)
|
||
|
{
|
||
|
if (yry.isNotEmpty())
|
||
|
y << bracketIfNeeded (yry) << " + ";
|
||
|
|
||
|
y << roundToInt (position.rect.getY());
|
||
|
}
|
||
|
else if (position.rect.getPositionModeY() == PositionedRectangle::absoluteFromParentBottomRight)
|
||
|
{
|
||
|
if (yry.isNotEmpty())
|
||
|
y << bracketIfNeeded (yry) << " + " << bracketIfNeeded (yrh);
|
||
|
else
|
||
|
y << "getHeight()";
|
||
|
|
||
|
const int d = roundToInt (position.rect.getY());
|
||
|
if (d != 0)
|
||
|
y << " - " << d;
|
||
|
}
|
||
|
else if (position.rect.getPositionModeY() == PositionedRectangle::absoluteFromParentCentre)
|
||
|
{
|
||
|
if (yry.isNotEmpty())
|
||
|
y << bracketIfNeeded (yry) << " + " << bracketIfNeeded (yrh) << " / 2";
|
||
|
else
|
||
|
y << "(getHeight() / 2)";
|
||
|
|
||
|
const int d = roundToInt (position.rect.getY());
|
||
|
if (d != 0)
|
||
|
y << " + " << d;
|
||
|
}
|
||
|
|
||
|
if (h != "0")
|
||
|
{
|
||
|
if (position.rect.getAnchorPointY() == PositionedRectangle::anchorAtRightOrBottom)
|
||
|
y << " - " << bracketIfNeeded (h);
|
||
|
else if (position.rect.getAnchorPointY() == PositionedRectangle::anchorAtCentre)
|
||
|
y << " - (" << bracketIfNeeded (h) << " / 2)";
|
||
|
}
|
||
|
}
|