All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.praxislive.ide.pxr.gui.EditLayer Maven / Gradle / Ivy

The newest version!
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2020 Neil C Smith.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 3 only, as
 * published by the Free Software Foundation.
 *
 * This code 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
 * version 3 for more details.
 *
 * You should have received a copy of the GNU General Public License version 3
 * along with this work; if not, see http://www.gnu.org/licenses/
 *
 *
 * Please visit https://www.praxislive.org if you need additional information or
 * have any questions.
 */
package org.praxislive.ide.pxr.gui;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.event.ChangeListener;
import net.miginfocom.swing.MigLayout;
import org.praxislive.core.ComponentType;
import org.praxislive.core.ControlAddress;
import org.praxislive.core.types.PString;
import org.praxislive.ide.core.api.Callback;
import org.praxislive.ide.model.ComponentProxy;
import org.praxislive.ide.model.ContainerProxy;
import org.praxislive.ide.model.RootProxy;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.nodes.Node;
import org.openide.nodes.NodeTransfer;
import org.openide.util.ChangeSupport;
import org.openide.util.Exceptions;
import org.praxislive.core.Value;

/**
 *
 */
class EditLayer extends JComponent {

    private final static Logger LOG = Logger.getLogger(EditLayer.class.getName());
    private final static Color hoverColor = new Color(143, 171, 196);
    private final static Color selectedColor = hoverColor.brighter();
    
    private final GuiEditor editor;
    private final RootProxy root;
    private final JPanel rootPanel;
    private final MouseController mouse;
    private final ChangeSupport cs;
    private final SelectedListener selectedListener;
    
    private JComponent hovered;
    private JComponent selected;

    EditLayer(GuiEditor editor, JPanel rootPanel) {
        this.editor = editor;
        this.rootPanel = rootPanel;
        root = editor.getRoot();
        cs = new ChangeSupport(this);
        mouse = new MouseController();
        selectedListener = new SelectedListener();
        addMouseListener(mouse);
        addMouseMotionListener(mouse);
        setFocusable(true);
        setFocusTraversalKeysEnabled(false);
        setDropTarget(new DropTarget(this, new DnDController()));

        super.setVisible(false);

    }

    void addChangeListener(ChangeListener listener) {
        cs.addChangeListener(listener);
    }

    void removeChangeListener(ChangeListener listener) {
        cs.removeChangeListener(listener);
    }

    void performLayoutAction(LayoutAction.Type type) {
        if (selected == null || selected == rootPanel) {
            LOG.fine("performLayoutAction() called when shouldn't be enabled");
            return;
        }
        switch (type) {
            case MoveUp:
                Utils.move(root, selected, 0, -1);
                break;
            case MoveDown:
                Utils.move(root, selected, 0, 1);
                break;
            case MoveLeft:
                Utils.move(root, selected, -1, 0);
                break;
            case MoveRight:
                Utils.move(root, selected, 1, 0);
                break;
            case IncreaseSpanX:
                Utils.resize(root, selected, 1, 0);
                break;
            case DecreaseSpanX:
                Utils.resize(root, selected, -1, 0);
                break;
            case IncreaseSpanY:
                Utils.resize(root, selected, 0, 1);
                break;
            case DecreaseSpanY:
                Utils.resize(root, selected, 0, -1);
                break;
        }
    }

    boolean isLayoutActionEnabled(LayoutAction.Type type) {
        return selected != null && selected != rootPanel;
    }

    @Override
    public void setVisible(boolean visible) {
        super.setVisible(visible);
        ComponentProxy pxy;
        if (visible && selected != null && selected != rootPanel) {
            pxy = Utils.findComponentProxy(root, selected);
        } else {
            pxy = null;
        }
        try {
            if (pxy == null) {
                editor.setSelected(new Node[]{root.getNodeDelegate()});
            } else {
                editor.setSelected(new Node[]{pxy.getNodeDelegate()});
            }
        } catch (Exception exception) {
        }
        cs.fireChange();
    }

    @Override
    protected void paintComponent(Graphics g) {
        paintHovered(g);
        paintSelected(g);
    }

    private void paintHovered(Graphics g) {
        if (hovered == null) {
            return;
        }
        g.setColor(hoverColor);
        Point loc = hovered.getLocation();
        loc = SwingUtilities.convertPoint(hovered.getParent(), loc, rootPanel);
        g.drawRect(loc.x, loc.y, hovered.getWidth() - 1, hovered.getHeight() - 1);
    }

    private void paintSelected(Graphics g) {
        if (selected == null || selected == rootPanel) {
            return;
        }
        Graphics2D g2d = (Graphics2D) g;
        Point loc = selected.getLocation();
        loc = loc = SwingUtilities.convertPoint(selected.getParent(), loc, rootPanel);
        g2d.setColor(selectedColor);
        g2d.setComposite(AlphaComposite.SrcOver.derive(0.2f));
        g2d.fillRect(loc.x, loc.y, selected.getWidth(), selected.getHeight());
        g2d.setComposite(AlphaComposite.SrcOver);
        g2d.drawRect(loc.x, loc.y, selected.getWidth() - 1, selected.getHeight() - 1);
    }

    private void clearHoveredComponent() {
        if (hovered != null) {
            hovered = null;
            repaint();
        }
    }

    private void updateHoveredComponent(int x, int y) {
        JComponent cmp = findComponentAtPoint(x, y);
        updateLabel(cmp, x, y);
        if (cmp == rootPanel) {
            cmp = null;
        }
        if (cmp != hovered) {
            hovered = cmp;
            repaint();
        }
    }

    private void updateLabel(JComponent cmp, int x, int y) {
        JComponent cnt = Utils.findContainerComponent(editor.getRoot(), cmp);
        if (cnt != rootPanel) {
            Point loc = SwingUtilities.convertPoint(rootPanel, x, y, cnt);
            x = loc.x;
            y = loc.y;
        }
        int[] position = null;
        if (cnt != null && cnt.getLayout() instanceof MigLayout) {
            if (cnt != cmp) {
                position = Utils.getGridPosition(cnt, cmp);
            } else {
                position = Utils.getGridPosition(cnt, x, y);
            }
        }
        String text = "";
        if (position == null) {
            text = "Unknown";
        } else if (position.length > 3) {
            text = "X : " + position[0] + " Y : " + position[1] + " Span X : " + position[2] + " Span Y : " + position[3];
        } else if (position.length > 1) {
            text = "X : " + position[0] + " Y : " + position[1];
        }
        setToolTipText(text);
    }

    private void updateSelectedComponent(int x, int y) {
        JComponent cmp = findComponentAtPoint(x, y);
        if (cmp == selected) {
            return;
        }
        try {
            if (selected != null) {
                selected.removeAncestorListener(selectedListener);
            }
            selected = cmp;
            selected.addAncestorListener(selectedListener);
            ComponentProxy pxy = Utils.findComponentProxy(root, cmp);
            if (pxy == null) {
                editor.setSelected(new Node[]{root.getNodeDelegate()});
            } else {
                editor.setSelected(new Node[]{pxy.getNodeDelegate()});
            }

        } catch (Exception ex) {
            LOG.log(Level.WARNING, null, ex);
            selected.removeAncestorListener(selectedListener);
            selected = null;
        }
        cs.fireChange();
        repaint();
    }

    private void addComponent(ComponentType type, int x, int y) throws Exception {
        JComponent dropOver = findComponentAtPoint(x, y);
        final JComponent container = Utils.findContainerComponent(root, dropOver);
        if (container != rootPanel) {
            Point loc = SwingUtilities.convertPoint(rootPanel, x, y, container);
            x = loc.x;
            y = loc.y;
        }
        final ContainerProxy pxy = (ContainerProxy) Utils.findComponentProxy(root, container);
        int[] pos;
        if (container == dropOver) {
            if (container.getComponentCount() == 0 || !(container.getLayout() instanceof MigLayout)) {
                pos = new int[]{0, 0};
            } else {
                pos = Utils.getGridPosition(container, x, y);
            }
        } else {
            pos = Utils.getGridPosition(container, dropOver);
        }
        Utils.ensureSpace(container, pos[0], pos[1], 1, 1, Collections.emptySet(), true);
        final PString layout = PString.of("cell " + pos[0] + " " + pos[1]);
        final String id = getFreeID(pxy, type);
        pxy.addChild(id, type)
                .thenCompose(c -> c.send("layout", List.of(layout)))
                .thenRun(() -> Utils.compactGrid(container))
                .exceptionally(ex -> {
                    DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message("Error creating component", NotifyDescriptor.ERROR_MESSAGE));
                    return null;
                });
    }

    private String getFreeID(ContainerProxy container, ComponentType type) {
        String base = type.toString();
        base = base.substring(base.lastIndexOf(":") + 1);
        for (int i = 1; i < 100; i++) {
            if (container.getChild(base + i) == null) {
                return base + i;
            }
        }
        return "";
    }

    private JComponent findComponentAtPoint(int x, int y) {
        Component cmp = SwingUtilities.getDeepestComponentAt(rootPanel, x, y);
        return Utils.findAddressedComponent(cmp);
    }

    private class SelectedListener implements AncestorListener {

        @Override
        public void ancestorAdded(AncestorEvent ae) {
        }

        @Override
        public void ancestorRemoved(AncestorEvent ae) {
            updateSelectedComponent(0, 0);
        }

        @Override
        public void ancestorMoved(AncestorEvent ae) {
        }

    }

    private class MouseController extends MouseAdapter {

        @Override
        public void mouseMoved(MouseEvent me) {
            updateHoveredComponent(me.getX(), me.getY());
        }

        @Override
        public void mouseExited(MouseEvent me) {
            clearHoveredComponent();
        }

        @Override
        public void mousePressed(MouseEvent me) {
            requestFocusInWindow();
            if (!me.isPopupTrigger()) {
                updateSelectedComponent(me.getX(), me.getY());
            }
        }

        @Override
        public void mouseClicked(MouseEvent me) {
            if (me.getClickCount() == 2) {
                updateSelectedComponent(me.getX(), me.getY());
                editor.performPreferredAction();
            }
        }

    }

    private class DnDController extends DropTargetAdapter {

        @Override
        public void dragEnter(DropTargetDragEvent dtde) {
            if (extractType(dtde.getTransferable()) == null) {
                dtde.rejectDrag();
            }
        }

        @Override
        public void dragOver(DropTargetDragEvent dtde) {
            updateHoveredComponent(dtde.getLocation().x, dtde.getLocation().y);
        }

        @Override
        public void drop(DropTargetDropEvent dtde) {
            ComponentType type = extractType(dtde.getTransferable());
            if (type != null) {
                try {
                    addComponent(type, dtde.getLocation().x, dtde.getLocation().y);
                    dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
                    return;
                } catch (Exception ex) {
                    LOG.log(Level.WARNING, null, ex);
                }

            }
            dtde.rejectDrop();

        }

        private ComponentType extractType(Transferable transferable) {
            Node n = NodeTransfer.node(transferable, NodeTransfer.DND_COPY);
            if (n != null) {
                ComponentType t = n.getLookup().lookup(ComponentType.class);
                if (t != null) {
                    return t;
                }
            }
            return null;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy