/*
// Copyright (c) 2021-2022 Timothy Schoen.
// For information on usage and redistribution, and for a DISCLAIMER OF ALL
// WARRANTIES, see the file, "LICENSE.txt," in this distribution.
*/
#include
#include "Utility/Config.h"
#include "Utility/Fonts.h"
#include "Object.h"
#include "Canvas.h"
#include "Components/SuggestionComponent.h"
#include "Connection.h"
#include "Iolet.h"
#include "Constants.h"
#include "PluginEditor.h"
#include "PluginProcessor.h"
#include "Sidebar/Sidebar.h"
#include "ObjectGrid.h"
#include "Objects/ObjectBase.h"
#include "Dialogs/Dialogs.h"
#include "Heavy/CompatibleObjects.h"
#include "Pd/Patch.h"
extern "C" {
#include
}
Object::Object(Canvas* parent, String const& name, Point position)
: NVGComponent(this)
, cnv(parent)
, editor(parent->editor)
, ds(parent->dragState)
{
setTopLeftPosition(position - Point(margin, margin));
initialise();
// Open editor for undefined objects
// Delay the setting of the type to prevent creating an invalid object first
if (name.isEmpty()) {
setSize(58, height);
} else {
setType(name);
}
if (!getValue(locked)) {
showEditor();
}
}
Object::Object(pd::WeakReference object, Canvas* parent)
: NVGComponent(this)
, cnv(parent)
, editor(parent->editor)
, ds(parent->dragState)
{
initialise();
setType("", object);
}
Object::~Object()
{
hideEditor(); // Make sure the editor is not still open, that could lead to issues with listeners attached to the editor (i.e. suggestioncomponent)
cnv->selectedComponents.removeChangeListener(this);
}
Rectangle Object::getObjectBounds()
{
return getBounds().reduced(margin) - cnv->canvasOrigin;
}
Rectangle Object::getSelectableBounds()
{
if (gui) {
return gui->getSelectableBounds() + cnv->canvasOrigin;
}
return getBounds().reduced(margin);
}
void Object::setObjectBounds(Rectangle bounds)
{
setBounds(bounds.expanded(margin) + cnv->canvasOrigin);
}
void Object::initialise()
{
cnv->objectLayer.addAndMakeVisible(this);
cnv->selectedComponents.addChangeListener(this);
// Updates lock/unlock mode
locked.referTo(cnv->locked);
commandLocked.referTo(cnv->pd->commandLocked);
presentationMode.referTo(cnv->presentationMode);
hvccMode.referTo(SettingsFile::getInstance()->getValueTree(), Identifier("hvcc_mode"), nullptr, false);
patchDownwardsOnly.referTo(SettingsFile::getInstance()->getValueTree(), Identifier("patch_downwards_only"), nullptr);
presentationMode.addListener(this);
locked.addListener(this);
commandLocked.addListener(this);
originalBounds.setBounds(0, 0, 0, 0);
setAccessible(false); // TODO: implement accessibility. We disable default, since it makes stuff slow on macOS
}
void Object::timerCallback()
{
activeStateAlpha -= 0.06f;
repaint();
if (activeStateAlpha <= 0.0f) {
activeStateAlpha = 0.0f;
stopTimer();
}
}
void Object::changeListenerCallback(ChangeBroadcaster* source)
{
if (auto selectedItems = dynamic_cast>*>(source))
setSelected(selectedItems->isSelected(this));
}
void Object::setSelected(bool shouldBeSelected)
{
if (selectedFlag != shouldBeSelected) {
selectedFlag = shouldBeSelected;
repaint();
}
}
bool Object::isSelected() const
{
return selectedFlag;
}
void Object::settingsChanged(String const& name, var const& value)
{
if (name == "hvcc_mode") {
isHvccCompatible = checkIfHvccCompatible();
if (gui && !isHvccCompatible) {
cnv->pd->logWarning(String("Warning: object \"" + gui->getType() + "\" is not supported in Compiled Mode").toRawUTF8());
}
repaint();
}
}
void Object::valueChanged(Value& v)
{
if (v.refersToSameSourceAs(cnv->presentationMode)) {
// else it was a lock/unlock/presentation mode action
// Hide certain objects in GOP
setVisible(!((cnv->isGraph || cnv->presentationMode == var(true)) && gui && gui->hideInGraph()));
} else if (v.refersToSameSourceAs(cnv->locked) || v.refersToSameSourceAs(cnv->commandLocked)) {
if (gui) {
gui->lock(cnv->isGraph || locked == var(true) || commandLocked == var(true));
}
}
}
bool Object::checkIfHvccCompatible() const
{
if (gui) {
auto typeName = gui->getType();
// Check hvcc compatibility
bool isSubpatch = gui->getPatch() != nullptr;
return !hvccMode.get() || isSubpatch || HeavyCompatibleObjects::getAllCompatibleObjects().contains(typeName);
}
return true;
}
bool Object::hitTest(int x, int y)
{
if (::getValue(presentationMode)) {
if (cnv->isPointOutsidePluginArea(cnv->getLocalPoint(this, Point(x, y))))
return false;
}
if (cnv->panningModifierDown())
return false;
// If the hit-test get's to here, and any of these are still true
// return! Otherwise it will test non-existent iolets and return true!
bool blockIolets = presentationMode.getValue() || locked.getValue() || commandLocked.getValue();
// Mouse over iolets
for (auto* iolet : iolets) {
if (!blockIolets && iolet->getBounds().contains(x, y))
return true;
}
if (selectedFlag) {
for (auto& corner : getCorners()) {
if (corner.contains(x, y))
return true;
}
return getLocalBounds().reduced(margin).contains(x, y);
}
if (gui && !gui->canReceiveMouseEvent(x, y)) {
return false;
}
// Mouse over object
if (getLocalBounds().reduced(margin).contains(x, y)) {
return true;
}
return false;
}
// To make iolets show/hide
void Object::mouseEnter(MouseEvent const& e)
{
drawIoletExpanded = true;
repaint();
}
void Object::mouseExit(MouseEvent const& e)
{
// we need to reset the resizeZone & validResizeZone,
// otherwise it can have an old zone already selected on re-entry
resizeZone = ResizableBorderComponent::Zone(ResizableBorderComponent::Zone::centre);
validResizeZone = false;
drawIoletExpanded = false;
repaint();
}
void Object::mouseMove(MouseEvent const& e)
{
if (!selectedFlag || locked == var(true) || commandLocked == var(true)) {
setMouseCursor(MouseCursor::NormalCursor);
updateMouseCursor();
return;
}
int zone = 0;
auto b = getLocalBounds().toFloat().reduced(margin - 2);
if (b.contains(e.position)
&& !b.reduced(7).contains(e.position)) {
auto corners = getCorners();
auto minW = jmax(b.getWidth() / 10.0f, jmin(10.0f, b.getWidth() / 3.0f));
auto minH = jmax(b.getHeight() / 10.0f, jmin(10.0f, b.getHeight() / 3.0f));
if (corners[0].contains(e.position) || corners[1].contains(e.position) || (e.position.x < jmax(7.0f, minW) && b.getX() > 0.0f))
zone |= ResizableBorderComponent::Zone::left;
else if (corners[2].contains(e.position) || corners[3].contains(e.position) || (e.position.x >= b.getWidth() - jmax(7.0f, minW)))
zone |= ResizableBorderComponent::Zone::right;
if (corners[0].contains(e.position) || corners[3].contains(e.position) || (e.position.y < jmax(7.0f, minH)))
zone |= ResizableBorderComponent::Zone::top;
else if (corners[1].contains(e.position) || corners[2].contains(e.position) || (e.position.y >= b.getHeight() - jmax(7.0f, minH)))
zone |= ResizableBorderComponent::Zone::bottom;
}
resizeZone = static_cast<:zone>(zone);
validResizeZone = resizeZone.getZoneFlags() != ResizableBorderComponent::Zone::centre && e.originalComponent == this && !(gui && gui->isEditorShown()) && !newObjectEditor;
setMouseCursor(validResizeZone ? resizeZone.getMouseCursor() : MouseCursor::NormalCursor);
updateMouseCursor();
}
void Object::applyBounds()
{
UnorderedMap